Skip to content

Commit

Permalink
Merge pull request #116 from SierraSoftworks/feat/current-user
Browse files Browse the repository at this point in the history
feat: Add support for backing up repositories owned by the current user
  • Loading branch information
notheotherben authored Dec 8, 2024
2 parents 928aa44 + ee617ea commit 1d76dd8
Show file tree
Hide file tree
Showing 7 changed files with 80 additions and 44 deletions.
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 policy;
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 @@ async fn run(args: Args) -> Result<(), Error> {
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() => {
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() => {
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() => {
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 tokio_stream::Stream;
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 @@ impl BackupSource<GitRepo> for GitHubRepoSource {
"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 @@ impl BackupSource<GitRepo> for GitHubRepoSource {
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 BackupSource<GitRepo> for GitHubRepoSource {

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,
}
}

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 @@ mod tests {

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 @@ mod tests {
}

#[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 @@ pub use traced_stream::*;
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))
}

0 comments on commit 1d76dd8

Please sign in to comment.