diff --git a/internal/mirroring/main.go b/internal/mirroring/main.go index a3e31cd..a65411b 100644 --- a/internal/mirroring/main.go +++ b/internal/mirroring/main.go @@ -7,6 +7,7 @@ import ( "gitlab-sync/internal/utils" + gitlab "gitlab.com/gitlab-org/api/client-go" "go.uber.org/zap" ) @@ -70,7 +71,7 @@ func MirrorGitlabs(gitlabMirrorArgs *utils.ParserArgs) []error { // In case of dry run, simply print the groups and projects that would be created or updated if gitlabMirrorArgs.DryRun { - DryRun(sourceGitlabInstance, gitlabMirrorArgs) + destinationGitlabInstance.DryRun(sourceGitlabInstance, gitlabMirrorArgs.MirrorMapping) return nil } @@ -128,28 +129,55 @@ func processFilters(filters *utils.MirrorMapping) (map[string]struct{}, map[stri } // DryRun prints the groups and projects that would be created or updated in dry run mode. -func DryRun(sourceGitlabInstance *GitlabInstance, gitlabMirrorArgs *utils.ParserArgs) { +func (destinationGitlabInstance *GitlabInstance) DryRun(sourceGitlabInstance *GitlabInstance, mirrorMapping *utils.MirrorMapping) []error { zap.L().Info("Dry run mode enabled, will not create groups or projects") zap.L().Info("Groups that will be created (or updated if they already exist):") - for sourceGroupPath, copyOptions := range gitlabMirrorArgs.MirrorMapping.Groups { + for sourceGroupPath, copyOptions := range mirrorMapping.Groups { if sourceGroup, ok := sourceGitlabInstance.Groups[sourceGroupPath]; ok { fmt.Printf(" - %s (source gitlab) -> %s (destination gitlab)\n", sourceGroup.WebURL, copyOptions.DestinationPath) } } zap.L().Info("Projects that will be created (or updated if they already exist):") - for sourceProjectPath, copyOptions := range gitlabMirrorArgs.MirrorMapping.Projects { + for sourceProjectPath, copyOptions := range mirrorMapping.Projects { if sourceProject, ok := sourceGitlabInstance.Projects[sourceProjectPath]; ok { fmt.Printf(" - %s (source gitlab) -> %s (destination gitlab)\n", sourceProject.WebURL, copyOptions.DestinationPath) + + if copyOptions.MirrorReleases { + if err := destinationGitlabInstance.DryRunReleases(sourceGitlabInstance, sourceProject, copyOptions); err != nil { + zap.L().Error("Failed to dry run releases", zap.Error(err)) + return []error{err} + } + } } + + } + zap.L().Info("Dry run completed") + return nil +} + +// DryRunReleases prints the releases that would be created in dry run mode. +// It fetches the releases from the source project and prints them. +// It does not create any releases in the destination project. +func (destinationGitlabInstance *GitlabInstance) DryRunReleases(sourceGitlabInstance *GitlabInstance, sourceProject *gitlab.Project, copyOptions *utils.MirroringOptions) error { + // Fetch releases from the source project + sourceReleases, _, err := sourceGitlabInstance.Gitlab.Releases.ListReleases(sourceProject.ID, &gitlab.ListReleasesOptions{}) + if err != nil { + return fmt.Errorf("failed to fetch releases for source project %s: %s", sourceProject.HTTPURLToRepo, err) } + // Print the releases that will be created in the destination project + for _, release := range sourceReleases { + fmt.Printf(" - Release %s will be created in %s (if it does not already exist)\n", release.Name, destinationGitlabInstance.Gitlab.BaseURL().String()+copyOptions.DestinationPath) + } + return nil } -func (destinationGitlab *GitlabInstance) CheckDestinationInstance() error { +// CheckDestinationInstance checks the destination GitLab instance for version and license compatibility. +func (g *GitlabInstance) CheckDestinationInstance() error { zap.L().Info("Checking destination GitLab instance") - if err := destinationGitlab.CheckVersion(); err != nil { + if err := g.CheckVersion(); err != nil { return fmt.Errorf("destination GitLab instance version check failed: %w", err) } - if err := destinationGitlab.CheckVersion(); err != nil { + if err := g.CheckLicense(); err != nil { return fmt.Errorf("destination GitLab instance version check failed: %w", err) } return nil diff --git a/internal/mirroring/main_test.go b/internal/mirroring/main_test.go index 2af06bc..bf65ff3 100644 --- a/internal/mirroring/main_test.go +++ b/internal/mirroring/main_test.go @@ -1,6 +1,7 @@ package mirroring import ( + "net/http" "reflect" "testing" @@ -130,3 +131,116 @@ func TestProcessFilters(t *testing.T) { }) } } + +func TestDryRun(t *testing.T) { + tests := []struct { + name string + sourceSize string + }{ + { + name: "Dry Run Source Small", + sourceSize: INSTANCE_SIZE_SMALL, + }, + { + name: "Dry Run Source Big", + sourceSize: INSTANCE_SIZE_BIG, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + _, sourceGitlabInstance := setupTestServer(t, ROLE_SOURCE, tt.sourceSize) + _, destinationGitlabInstance := setupTestServer(t, ROLE_DESTINATION, INSTANCE_SIZE_SMALL) + gitlabMirrorArgs := &utils.MirrorMapping{ + Projects: map[string]*utils.MirroringOptions{ + TEST_PROJECT.PathWithNamespace: { + DestinationPath: TEST_PROJECT.PathWithNamespace, + MirrorReleases: true, + }, + }, + Groups: map[string]*utils.MirroringOptions{ + TEST_GROUP_2.FullPath: { + DestinationPath: TEST_GROUP_2.FullPath, + MirrorReleases: true, + }, + }, + } + sourceGitlabInstance.addProject(TEST_PROJECT) + sourceGitlabInstance.addGroup(TEST_GROUP_2) + sourceGitlabInstance.addGroup(TEST_GROUP_2) + + destinationGitlabInstance.DryRun(sourceGitlabInstance, gitlabMirrorArgs) + }) + } +} + +func TestCheckDestinationInstance(t *testing.T) { + tests := []struct { + name string + licensePlan string + version string + expectedError bool + }{ + { + name: "Premium license, good version", + licensePlan: PREMIUM_PLAN, + version: "18.0.0", + expectedError: false, + }, + { + name: "Ultimate license, good version", + licensePlan: ULTIMATE_PLAN, + version: "18.0.0", + expectedError: false, + }, + { + name: "Free license, good version", + licensePlan: "free", + version: "18.0.0", + expectedError: true, + }, + { + name: "Premium license, bad version", + licensePlan: PREMIUM_PLAN, + version: "17.0.0", + expectedError: true, + }, + { + name: "Ultimate license, bad version", + licensePlan: ULTIMATE_PLAN, + version: "17.0.0", + expectedError: true, + }, + { + name: "Bad license, good version", + licensePlan: "bad_license", + version: "18.0.0", + expectedError: true, + }, + { + name: "Bad license, bad version", + licensePlan: "bad_license", + version: "17.0.0", + expectedError: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + mux, gitlabInstance := setupEmptyTestServer(t, ROLE_DESTINATION, INSTANCE_SIZE_SMALL) + mux.HandleFunc("/api/v4/license", func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(200) + w.Write([]byte(`{"plan": "` + tt.licensePlan + `", "expired": false}`)) + }) + mux.HandleFunc("/api/v4/metadata", func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(200) + w.Write([]byte(`{"version": "` + tt.version + `"}`)) + }) + + err := gitlabInstance.CheckDestinationInstance() + if (err != nil) != tt.expectedError { + t.Errorf("CheckDestinationInstance() error = %v, expectedError %v", err, tt.expectedError) + } + }) + } +}