From ed98d69313c192a0fd0362889004afd3c1d333de Mon Sep 17 00:00:00 2001 From: Jon Hadfield Date: Sun, 10 Mar 2024 20:56:21 +0000 Subject: [PATCH] add support for Azure DevOps repository backups. --- azure_devops.go | 37 +++++++++++++++++++++++++++++++++++++ go.mod | 4 +++- go.sum | 9 +++++++-- main.go | 37 +++++++++++++++++++++++++++++++++---- main_test.go | 32 ++++++++++++++++++++++++++++++++ 5 files changed, 112 insertions(+), 7 deletions(-) create mode 100644 azure_devops.go diff --git a/azure_devops.go b/azure_devops.go new file mode 100644 index 0000000..0ba493e --- /dev/null +++ b/azure_devops.go @@ -0,0 +1,37 @@ +package main + +import ( + "os" + + "github.com/jonhadfield/githosts-utils" + "gitlab.com/tozd/go/errors" +) + +func AzureDevOps(backupDir string) *ProviderBackupResults { + logger.Println("backing up Azure DevOps repos") + + azureDevOpsHost, err := githosts.NewAzureDevOpsHost(githosts.NewAzureDevOpsHostInput{ + Caller: appName, + BackupDir: backupDir, + DiffRemoteMethod: os.Getenv(envAzureDevOpsCompare), + UserName: os.Getenv(envAzureDevOpsUserName), + PAT: os.Getenv(envAzureDevOpsPAT), + Orgs: getOrgsListFromEnvVar(envAzureDevOpsOrgs), + BackupsToRetain: getBackupsToRetain(envAzureDevOpsBackups), + LogLevel: getLogLevel(), + }) + if err != nil { + return &ProviderBackupResults{ + Provider: providerNameAzureDevOps, + Results: githosts.ProviderBackupResult{ + BackupResults: []githosts.RepoBackupResults{}, + Error: errors.Wrap(err, "failed to create AzureDevOps host"), + }, + } + } + + return &ProviderBackupResults{ + Provider: providerNameAzureDevOps, + Results: azureDevOpsHost.Backup(), + } +} diff --git a/go.mod b/go.mod index c958c6e..5ac9282 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ toolchain go1.21.0 require ( github.com/carlescere/scheduler v0.0.0-20170109141437-ee74d2f83d82 github.com/hashicorp/go-retryablehttp v0.7.5 - github.com/jonhadfield/githosts-utils v0.0.0-20240227215907-fdbfc9a27143 + github.com/jonhadfield/githosts-utils v0.0.0-20240310201523-4a29947afd0b github.com/pkg/errors v0.9.1 github.com/stretchr/testify v1.9.0 gitlab.com/tozd/go/errors v0.8.1 @@ -16,9 +16,11 @@ require ( require ( github.com/davecgh/go-spew v1.1.1 // indirect + github.com/google/uuid v1.6.0 // indirect github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/kr/text v0.2.0 // indirect + github.com/microsoft/azure-devops-go-api/azuredevops/v7 v7.1.0 // indirect github.com/peterhellberg/link v1.2.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index 379a2fd..bb076cf 100644 --- a/go.sum +++ b/go.sum @@ -6,6 +6,9 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw= github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI= github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= @@ -14,12 +17,14 @@ github.com/hashicorp/go-hclog v0.9.2 h1:CG6TE5H9/JXsFWJCfoIVpKFIkFe6ysEuHirp4DxC github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= github.com/hashicorp/go-retryablehttp v0.7.5 h1:bJj+Pj19UZMIweq/iie+1u5YCdGrnxCT9yvm0e+Nd5M= github.com/hashicorp/go-retryablehttp v0.7.5/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8= -github.com/jonhadfield/githosts-utils v0.0.0-20240227215907-fdbfc9a27143 h1:A8Kjq6LRh5HQiAGxkzQ99Nd1M4EyHS8eu3m3wurvpMg= -github.com/jonhadfield/githosts-utils v0.0.0-20240227215907-fdbfc9a27143/go.mod h1:2hre3/B2QsIOf6S69L3NCq1kkLu/7+TGFuIwNxDYIH4= +github.com/jonhadfield/githosts-utils v0.0.0-20240310201523-4a29947afd0b h1:MwRgr5H/SVw5omTrgR7Gt2RgzFsNrJly6GpBTe8nk1E= +github.com/jonhadfield/githosts-utils v0.0.0-20240310201523-4a29947afd0b/go.mod h1:WebxzLNgWfwnHFwcXXxYfH3sYk6Ql3OPqxJNp7vtwoI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/microsoft/azure-devops-go-api/azuredevops/v7 v7.1.0 h1:mmJCWLe63QvybxhW1iBmQWEaCKdc4SKgALfTNZ+OphU= +github.com/microsoft/azure-devops-go-api/azuredevops/v7 v7.1.0/go.mod h1:mDunUZ1IUJdJIRHvFb+LPBUtxe3AYB5MI6BMXNg8194= github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32 h1:W6apQkHrMkS0Muv8G/TipAy/FJl/rCYT0+EuS8+Z0z4= github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms= github.com/peterhellberg/link v1.2.0 h1:UA5pg3Gp/E0F2WdX7GERiNrPQrM1K6CVJUUWfHa4t6c= diff --git a/main.go b/main.go index ecd1b1f..16c1829 100644 --- a/main.go +++ b/main.go @@ -37,6 +37,11 @@ const ( envGitBackupDir = "GIT_BACKUP_DIR" envGitHubAPIURL = "GITHUB_APIURL" envGitHubBackups = "GITHUB_BACKUPS" + envAzureDevOpsOrgs = "AZURE_DEVOPS_ORGS" + envAzureDevOpsUserName = "AZURE_DEVOPS_USERNAME" + envAzureDevOpsPAT = "AZURE_DEVOPS_PAT" + envAzureDevOpsCompare = "AZURE_DEVOPS_COMPARE" + envAzureDevOpsBackups = "AZURE_DEVOPS_BACKUPS" // nolint:gosec envGitHubToken = "GITHUB_TOKEN" envGitHubOrgs = "GITHUB_ORGS" @@ -60,10 +65,11 @@ const ( envGiteaOrgs = "GITEA_ORGS" // provider names - providerNameBitBucket = "BitBucket" - providerNameGitHub = "GitHub" - providerNameGitLab = "GitLab" - providerNameGitea = "Gitea" + providerNameAzureDevOps = "AzureDevOps" + providerNameBitBucket = "BitBucket" + providerNameGitHub = "GitHub" + providerNameGitLab = "GitLab" + providerNameGitea = "Gitea" // compare types compareTypeRefs = "refs" @@ -76,6 +82,10 @@ var ( version, tag, sha, buildDate string enabledProviderAuth = map[string][]string{ + providerNameAzureDevOps: { + envAzureDevOpsUserName, + envAzureDevOpsPAT, + }, providerNameGitHub: { envGitHubToken, }, @@ -99,6 +109,7 @@ var ( } userAndPasswordProviders = []string{ providerNameBitBucket, + providerNameAzureDevOps, } numUserDefinedProviders int64 ) @@ -298,6 +309,20 @@ func displayStartupConfig() { logger.Printf("BitBucket compare method: %s", compareTypeClone) } } + + // output azure devops config + // output github config + if azureDevOpsUserName := os.Getenv(envAzureDevOpsUserName); azureDevOpsUserName != "" { + if ghOrgs := strings.ToLower(os.Getenv(envAzureDevOpsOrgs)); ghOrgs != "" { + logger.Printf("Azure DevOps Organistations: %s", ghOrgs) + } + + if strings.EqualFold(os.Getenv(envAzureDevOpsCompare), compareTypeRefs) { + logger.Print("Azure DevOps compare method: refs") + } else { + logger.Print("Azure DevOps compare method: clone") + } + } } func run() error { @@ -415,6 +440,10 @@ func execProviderBackups() { providerBackupResults = append(providerBackupResults, *Gitlab(backupDir)) } + if os.Getenv(envAzureDevOpsUserName) != "" { + providerBackupResults = append(providerBackupResults, *AzureDevOps(backupDir)) + } + logger.Println("cleaning up") // startFileRemovals := time.Now() diff --git a/main_test.go b/main_test.go index 601f46c..3240410 100644 --- a/main_test.go +++ b/main_test.go @@ -23,6 +23,7 @@ var sobaEnvVarKeys = []string{ envGitHubCompare, envGitLabCompare, envBitBucketCompare, envBitBucketUser, envBitBucketKey, envBitBucketSecret, envBitBucketBackups, envGiteaAPIURL, envGiteaToken, envGiteaOrgs, envGiteaCompare, envGiteaBackups, + envAzureDevOpsUserName, envAzureDevOpsPAT, envAzureDevOpsOrgs, envAzureDevOpsCompare, envAzureDevOpsBackups, } func removeContents(dir string) error { @@ -207,6 +208,37 @@ func TestInvalidBundleIsMovedWithRefCompare(t *testing.T) { require.Equal(t, 1, renamedFound) } +func TestAzureDevOpsRepositoryBackupWithBackupsToKeepAsOne(t *testing.T) { + if os.Getenv(envAzureDevOpsUserName) == "" { + t.Skipf("Skipping Azure DevOps test as %s is missing", envAzureDevOpsUserName) + } + + _ = os.Unsetenv(envSobaWebHookURL) + + envBackup := backupEnvironmentVariables() + defer restoreEnvironmentVariables(envBackup) + + preflight() + resetGlobals() + + defer resetBackups() + + // Unset Env Vars but exclude those defined + unsetEnvVarsExcept([]string{ + envGitBackupDir, + envAzureDevOpsUserName, + envAzureDevOpsPAT, + envAzureDevOpsOrgs, + envAzureDevOpsBackups, + envAzureDevOpsCompare, + }) + + // run + require.NoError(t, run()) + + require.NoError(t, run()) +} + func TestPublicGithubRepositoryBackupWithBackupsToKeepAsOne(t *testing.T) { if os.Getenv(envGitHubToken) == "" { t.Skipf("Skipping GitHub test as %s is missing", envGitHubToken)