Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add support for backing up repositories owned by the current user #116

Merged
merged 1 commit into from
Dec 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,17 @@ schedule: "0 * * * *"

backups:
- kind: github/repo
from: users/my-user
from: user # The user associated with the provided credentials
to: /backups/personal
credentials: !UsernamePassword:
username: "<your username>"
password: "<your personal access token>"
properties:
query: "affiliation=owner" # Additional query parameters to pass to GitHub when fetching repositories
- kind: github/repo
from: "users/another-user"
to: /backups/friend
credentials: !Token "your_github_token"
- kind: github/repo
from: "orgs/my-org"
to: /backups/work
Expand Down
17 changes: 17 additions & 0 deletions examples/config.yaml
Original file line number Diff line number Diff line change
@@ -1,14 +1,31 @@
schedule: "0 * * * *"

backups:
# Backup all the repositories that the provided credentials have access to
- kind: github/repo
from: user
to: /backup/github
credentials: !Token your_access_token

# Backup the repository from "notheotherben" called "nix-env"
- kind: github/repo
from: users/notheotherben
to: /backup/github
filter: repo.name == "nix-env"

# Backup public, non-forked, repositories called "git-tool" or "grey" from the "SierraSoftworks" organization
- kind: github/repo
from: orgs/SierraSoftworks
to: /backup/github
filter: repo.public && !repo.fork && repo.name in ["git-tool", "grey"]

# Backup production non-source releases from the "SierraSoftworks" organization
- kind: github/release
from: orgs/SierraSoftworks
to: /backup/github
filter: repo.public && !release.prerelease && !artifact.source-code

# Backup all repositories that the user `notheotherben` has starred
- kind: github/star
from: users/notheotherben
to: /backup/github
26 changes: 13 additions & 13 deletions src/helpers/github.rs
Original file line number Diff line number Diff line change
Expand Up @@ -573,7 +573,7 @@ impl MetadataSource for GitHubReleaseAsset {

#[allow(dead_code)]
#[derive(PartialEq, Debug, Clone, serde::Serialize, serde::Deserialize)]
pub enum GitHubKind {
pub enum GitHubArtifactKind {
#[serde(rename = "github/repo")]
Repo,
#[serde(rename = "github/star")]
Expand All @@ -582,20 +582,20 @@ pub enum GitHubKind {
Release,
}

impl GitHubKind {
impl GitHubArtifactKind {
pub fn as_str(&self) -> &'static str {
match self {
GitHubKind::Repo => "github/repo",
GitHubKind::Star => "github/star",
GitHubKind::Release => "github/release",
GitHubArtifactKind::Repo => "github/repo",
GitHubArtifactKind::Star => "github/star",
GitHubArtifactKind::Release => "github/release",
}
}

pub fn api_endpoint(&self) -> &'static str {
match self {
GitHubKind::Repo => "repos",
GitHubKind::Star => "starred",
GitHubKind::Release => "repos",
GitHubArtifactKind::Repo => "repos",
GitHubArtifactKind::Star => "starred",
GitHubArtifactKind::Release => "repos",
}
}
}
Expand Down Expand Up @@ -720,15 +720,15 @@ mod tests {
}

#[rstest]
#[case("github/repo", GitHubKind::Repo, "repos")]
#[case("github/star", GitHubKind::Star, "starred")]
#[case("github/release", GitHubKind::Release, "repos")]
#[case("github/repo", GitHubArtifactKind::Repo, "repos")]
#[case("github/star", GitHubArtifactKind::Star, "starred")]
#[case("github/release", GitHubArtifactKind::Release, "repos")]
fn test_deserialize_gh_repo_kind(
#[case] kind_str: &str,
#[case] expected_kind: GitHubKind,
#[case] expected_kind: GitHubArtifactKind,
#[case] url: &str,
) {
let kind: GitHubKind = serde_yaml::from_str(&format!("\"{}\"", kind_str)).unwrap();
let kind: GitHubArtifactKind = serde_yaml::from_str(&format!("\"{}\"", kind_str)).unwrap();

assert_eq!(kind, expected_kind);
assert_eq!(kind.as_str(), kind_str);
Expand Down
8 changes: 4 additions & 4 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
mod sources;
mod telemetry;

use crate::helpers::github::GitHubKind;
use crate::helpers::github::GitHubArtifactKind;
pub use entities::BackupEntity;
pub use filter::{Filter, FilterValue, Filterable};
pub use policy::BackupPolicy;
Expand Down Expand Up @@ -76,19 +76,19 @@
let _policy_span = tracing::info_span!("backup.policy", policy = %policy).entered();

match policy.kind.as_str() {
k if k == GitHubKind::Repo.as_str() => {
k if k == GitHubArtifactKind::Repo.as_str() => {

Check warning on line 79 in src/main.rs

View check run for this annotation

Codecov / codecov/patch

src/main.rs#L79

Added line #L79 was not covered by tests
info!("Backing up repositories for {}", &policy);
github_repo
.run(policy, &LoggingPairingHandler, &CANCEL)
.await;
}
k if k == GitHubKind::Star.as_str() => {
k if k == GitHubArtifactKind::Star.as_str() => {

Check warning on line 85 in src/main.rs

View check run for this annotation

Codecov / codecov/patch

src/main.rs#L85

Added line #L85 was not covered by tests
info!("Backing up starred repositories for {}", &policy);
github_star
.run(policy, &LoggingPairingHandler, &CANCEL)
.await;
}
k if k == GitHubKind::Release.as_str() => {
k if k == GitHubArtifactKind::Release.as_str() => {

Check warning on line 91 in src/main.rs

View check run for this annotation

Codecov / codecov/patch

src/main.rs#L91

Added line #L91 was not covered by tests
info!("Backing up release artifacts for {}", &policy);
github_release
.run(policy, &LoggingPairingHandler, &CANCEL)
Expand Down
9 changes: 5 additions & 4 deletions src/sources/github_releases.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use crate::{
entities::{Credentials, HttpFile},
errors::{self},
helpers::{
github::{GitHubKind, GitHubRelease, GitHubRepo},
github::{GitHubArtifactKind, GitHubRelease, GitHubRepo},
GitHubClient,
},
policy::BackupPolicy,
Expand All @@ -27,7 +27,7 @@ impl GitHubReleasesSource {

impl BackupSource<HttpFile> for GitHubReleasesSource {
fn kind(&self) -> &str {
GitHubKind::Release.as_str()
GitHubArtifactKind::Release.as_str()
}

fn validate(&self, policy: &BackupPolicy) -> Result<(), crate::Error> {
Expand Down Expand Up @@ -58,14 +58,15 @@ impl BackupSource<HttpFile> for GitHubReleasesSource {
cancel: &'a AtomicBool,
) -> impl Stream<Item = Result<HttpFile, crate::Error>> + 'a {
let url = format!(
"{}/{}/{}",
"{}/{}/{}?{}",
policy
.properties
.get("api_url")
.unwrap_or(&"https://api.github.com".to_string())
.trim_end_matches('/'),
&policy.from.trim_matches('/'),
GitHubKind::Release.api_endpoint()
GitHubArtifactKind::Release.api_endpoint(),
policy.properties.get("query").unwrap_or(&"".to_string())
);

async_stream::stream! {
Expand Down
50 changes: 32 additions & 18 deletions src/sources/github_repo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,20 @@
use crate::{
entities::GitRepo,
errors::{self},
helpers::{github::GitHubKind, github::GitHubRepo, GitHubClient},
helpers::{github::GitHubArtifactKind, github::GitHubRepo, GitHubClient},
policy::BackupPolicy,
BackupSource,
};

#[derive(Clone)]
pub struct GitHubRepoSource {
client: GitHubClient,
kind: GitHubKind,
artifact_kind: GitHubArtifactKind,
}

impl BackupSource<GitRepo> for GitHubRepoSource {
fn kind(&self) -> &str {
self.kind.as_str()
self.artifact_kind.as_str()
}

fn validate(&self, policy: &BackupPolicy) -> Result<(), crate::Error> {
Expand All @@ -34,17 +34,19 @@
"Please provide a target field in the policy using the format 'users/<username>' or 'orgs/<orgname>'.",
)),

t if !t.starts_with("users/") && !t.starts_with("orgs/") => Err(errors::user(
&format!("The target field '{target}' does not include a valid user or org specifier."),
"Please specify either 'users/<username>' or 'orgs/<orgname>' as your target.",
)),
t if t == "user" => Ok(()),
t if t.starts_with("users/") => Ok(()),

t if t.starts_with("orgs/") && self.kind == GitHubKind::Star => Err(errors::user(
t if t.starts_with("orgs/") && self.artifact_kind == GitHubArtifactKind::Star => Err(errors::user(
&format!("The target field '{target}' specifies an org which is not support for kind 'github/star'."),
"Please specify either 'users/<username>' as your target when using 'github/star' as kind.",
)),
t if t.starts_with("orgs/") => Ok(()),

_ => Ok(()),
_ => Err(errors::user(
&format!("The target field '{target}' does not include a valid user or org specifier."),
"Please specify either 'user', 'users/<username>' or 'orgs/<orgname>' as your target.",
)),
}
}

Expand All @@ -54,14 +56,15 @@
cancel: &'a AtomicBool,
) -> impl Stream<Item = Result<GitRepo, errors::Error>> + 'a {
let url = format!(
"{}/{}/{}",
"{}/{}/{}?{}",
policy
.properties
.get("api_url")
.unwrap_or(&"https://api.github.com".to_string())
.trim_end_matches('/'),
&policy.from.trim_matches('/'),
self.kind.api_endpoint()
self.artifact_kind.api_endpoint(),
policy.properties.get("query").unwrap_or(&"".to_string())
);

async_stream::try_stream! {
Expand All @@ -77,21 +80,24 @@

impl GitHubRepoSource {
#[allow(dead_code)]
pub fn with_client(client: GitHubClient, kind: GitHubKind) -> Self {
GitHubRepoSource { client, kind }
pub fn with_client(client: GitHubClient, kind: GitHubArtifactKind) -> Self {
GitHubRepoSource {
client,
artifact_kind: kind,
}

Check warning on line 87 in src/sources/github_repo.rs

View check run for this annotation

Codecov / codecov/patch

src/sources/github_repo.rs#L83-L87

Added lines #L83 - L87 were not covered by tests
}

pub fn repo() -> Self {
GitHubRepoSource {
client: GitHubClient::default(),
kind: GitHubKind::Repo,
artifact_kind: GitHubArtifactKind::Repo,
}
}

pub fn star() -> Self {
GitHubRepoSource {
client: GitHubClient::default(),
kind: GitHubKind::Star,
artifact_kind: GitHubArtifactKind::Star,
}
}
}
Expand All @@ -102,23 +108,30 @@

use rstest::rstest;

use crate::{helpers::github::GitHubKind, BackupPolicy, BackupSource};
use crate::{helpers::github::GitHubArtifactKind, BackupPolicy, BackupSource};

use super::GitHubRepoSource;

static CANCEL: AtomicBool = AtomicBool::new(false);

#[test]
fn check_name_repo() {
assert_eq!(GitHubRepoSource::repo().kind(), GitHubKind::Repo.as_str());
assert_eq!(
GitHubRepoSource::repo().kind(),
GitHubArtifactKind::Repo.as_str()
);
}

#[test]
fn check_name_star() {
assert_eq!(GitHubRepoSource::star().kind(), GitHubKind::Star.as_str());
assert_eq!(
GitHubRepoSource::star().kind(),
GitHubArtifactKind::Star.as_str()
);
}

#[rstest]
#[case("user", true)]
#[case("users/notheotherben", true)]
#[case("orgs/sierrasoftworks", true)]
#[case("notheotherben", false)]
Expand All @@ -145,6 +158,7 @@
}

#[rstest]
#[case("user", true)]
#[case("users/notheotherben", true)]
#[case("orgs/sierrasoftworks", false)]
fn validation_stars(#[case] from: &str, #[case] success: bool) {
Expand Down
6 changes: 2 additions & 4 deletions src/telemetry/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@
use tracing_batteries::*;

pub fn setup() -> Session {
Session::new("github-backup", version!()).with_battery(
OpenTelemetry::new("https://api.honeycomb.io")
.with_protocol(OpenTelemetryProtocol::HttpJson),
)
Session::new("github-backup", version!())
.with_battery(OpenTelemetry::new("").with_protocol(OpenTelemetryProtocol::HttpJson))

Check warning on line 9 in src/telemetry/mod.rs

View check run for this annotation

Codecov / codecov/patch

src/telemetry/mod.rs#L8-L9

Added lines #L8 - L9 were not covered by tests
}
Loading