diff --git a/api/v1alpha1/blueprint_types.go b/api/v1alpha1/blueprint_types.go index 3eb9d26ea..0842d6f02 100644 --- a/api/v1alpha1/blueprint_types.go +++ b/api/v1alpha1/blueprint_types.go @@ -30,7 +30,7 @@ type Blueprint struct { Metadata Metadata `yaml:"metadata"` // Repository details the source repository of the blueprint. - Repository Repository `yaml:"repository"` + Repository Repository `yaml:"repository,omitempty"` // Sources are external resources referenced by the blueprint. Sources []Source `yaml:"sources"` @@ -64,7 +64,7 @@ type Repository struct { Ref Reference `yaml:"ref"` // SecretName is the secret for repository access. - SecretName string `yaml:"secretName,omitempty"` + SecretName *string `yaml:"secretName,omitempty"` } // Source is an external resource referenced by a blueprint. @@ -269,7 +269,10 @@ func (b *Blueprint) DeepCopy() *Blueprint { Tag: b.Repository.Ref.Tag, Branch: b.Repository.Ref.Branch, }, - SecretName: b.Repository.SecretName, + } + if b.Repository.SecretName != nil { + secretNameCopy := *b.Repository.SecretName + repositoryCopy.SecretName = &secretNameCopy } sourcesCopy := make([]Source, len(b.Sources)) @@ -342,23 +345,7 @@ func (b *Blueprint) StrategicMerge(overlays ...*Blueprint) error { } if overlay.Repository.Url != "" { - b.Repository.Url = overlay.Repository.Url - } - - if overlay.Repository.Ref.Commit != "" { - b.Repository.Ref.Commit = overlay.Repository.Ref.Commit - } else if overlay.Repository.Ref.Name != "" { - b.Repository.Ref.Name = overlay.Repository.Ref.Name - } else if overlay.Repository.Ref.SemVer != "" { - b.Repository.Ref.SemVer = overlay.Repository.Ref.SemVer - } else if overlay.Repository.Ref.Tag != "" { - b.Repository.Ref.Tag = overlay.Repository.Ref.Tag - } else if overlay.Repository.Ref.Branch != "" { - b.Repository.Ref.Branch = overlay.Repository.Ref.Branch - } - - if overlay.Repository.SecretName != "" { - b.Repository.SecretName = overlay.Repository.SecretName + b.Repository = overlay.Repository } sourceMap := make(map[string]Source) diff --git a/api/v1alpha1/blueprint_types_test.go b/api/v1alpha1/blueprint_types_test.go index 017bd8c8b..0f2bba796 100644 --- a/api/v1alpha1/blueprint_types_test.go +++ b/api/v1alpha1/blueprint_types_test.go @@ -343,6 +343,8 @@ func TestBlueprint_StrategicMerge(t *testing.T) { }) t.Run("MergesMetadataAndRepository", func(t *testing.T) { + secretName := "base-secret" + updatedSecretName := "updated-secret" // Given a base blueprint base := &Blueprint{ Metadata: Metadata{ @@ -350,8 +352,9 @@ func TestBlueprint_StrategicMerge(t *testing.T) { Description: "base description", }, Repository: Repository{ - Url: "base-url", - Ref: Reference{Branch: "main"}, + Url: "base-url", + Ref: Reference{Branch: "main"}, + SecretName: &secretName, }, } @@ -362,8 +365,9 @@ func TestBlueprint_StrategicMerge(t *testing.T) { Description: "updated description", }, Repository: Repository{ - Url: "updated-url", - Ref: Reference{Tag: "v1.0.0"}, + Url: "updated-url", + Ref: Reference{Tag: "v1.0.0"}, + SecretName: &updatedSecretName, }, } @@ -378,13 +382,89 @@ func TestBlueprint_StrategicMerge(t *testing.T) { t.Errorf("Expected description 'updated description', got '%s'", base.Metadata.Description) } - // And repository should be updated + // And repository should be completely replaced (not merged field-by-field) if base.Repository.Url != "updated-url" { t.Errorf("Expected url 'updated-url', got '%s'", base.Repository.Url) } if base.Repository.Ref.Tag != "v1.0.0" { t.Errorf("Expected tag 'v1.0.0', got '%s'", base.Repository.Ref.Tag) } + if base.Repository.Ref.Branch != "" { + t.Errorf("Expected branch to be empty (replaced, not merged), got '%s'", base.Repository.Ref.Branch) + } + if base.Repository.SecretName == nil || *base.Repository.SecretName != "updated-secret" { + t.Errorf("Expected secretName to be 'updated-secret', got %v", base.Repository.SecretName) + } + }) + + t.Run("RepositoryReplacementWhenOverlayHasURL", func(t *testing.T) { + baseSecretName := "base-secret" + // Given a base blueprint with repository + base := &Blueprint{ + Repository: Repository{ + Url: "base-url", + Ref: Reference{Branch: "main", Commit: "abc123"}, + SecretName: &baseSecretName, + }, + } + + // And an overlay with only URL set (no ref, no secretName) + overlay := &Blueprint{ + Repository: Repository{ + Url: "overlay-url", + }, + } + + // When strategic merging + base.StrategicMerge(overlay) + + // Then repository should be completely replaced + if base.Repository.Url != "overlay-url" { + t.Errorf("Expected url 'overlay-url', got '%s'", base.Repository.Url) + } + if base.Repository.Ref.Branch != "" { + t.Errorf("Expected branch to be empty (replaced), got '%s'", base.Repository.Ref.Branch) + } + if base.Repository.Ref.Commit != "" { + t.Errorf("Expected commit to be empty (replaced), got '%s'", base.Repository.Ref.Commit) + } + if base.Repository.SecretName != nil { + t.Errorf("Expected secretName to be nil (replaced), got %v", base.Repository.SecretName) + } + }) + + t.Run("RepositoryNotReplacedWhenOverlayURLEmpty", func(t *testing.T) { + baseSecretName := "base-secret" + // Given a base blueprint with repository + base := &Blueprint{ + Repository: Repository{ + Url: "base-url", + Ref: Reference{Branch: "main"}, + SecretName: &baseSecretName, + }, + } + + // And an overlay with empty URL + overlay := &Blueprint{ + Repository: Repository{ + Url: "", + Ref: Reference{Tag: "v1.0.0"}, + }, + } + + // When strategic merging + base.StrategicMerge(overlay) + + // Then repository should remain unchanged + if base.Repository.Url != "base-url" { + t.Errorf("Expected url 'base-url' to remain, got '%s'", base.Repository.Url) + } + if base.Repository.Ref.Branch != "main" { + t.Errorf("Expected branch 'main' to remain, got '%s'", base.Repository.Ref.Branch) + } + if base.Repository.SecretName == nil || *base.Repository.SecretName != "base-secret" { + t.Errorf("Expected secretName 'base-secret' to remain, got %v", base.Repository.SecretName) + } }) t.Run("MergesSourcesUniquely", func(t *testing.T) { diff --git a/pkg/composer/blueprint/blueprint_handler.go b/pkg/composer/blueprint/blueprint_handler.go index ca05afd94..ee860c479 100644 --- a/pkg/composer/blueprint/blueprint_handler.go +++ b/pkg/composer/blueprint/blueprint_handler.go @@ -1720,31 +1720,26 @@ func (b *BaseBlueprintHandler) deepMergeMaps(base, overlay map[string]any) map[s return result } -// setRepositoryDefaults sets the blueprint repository URL if not already specified. -// Uses development URL if dev flag is enabled, otherwise falls back to git remote origin URL. -// In dev mode, always overrides the URL even if it's already set. +// setRepositoryDefaults sets or overrides the blueprint repository URL based on development mode and git configuration. +// If development mode is enabled, the development URL is always used. Otherwise, the git remote origin URL is used if the URL is unset. +// If a URL is set and the repository reference is empty, the branch is set to "main". func (b *BaseBlueprintHandler) setRepositoryDefaults() error { devMode := b.runtime.ConfigHandler.GetBool("dev") - if devMode { url := b.getDevelopmentRepositoryURL() if url != "" { b.blueprint.Repository.Url = url - return nil } } - - // Only set from git remote if URL is not already set - if b.blueprint.Repository.Url != "" { - return nil + if b.blueprint.Repository.Url == "" { + gitURL, err := b.runtime.Shell.ExecSilent("git", "config", "--get", "remote.origin.url") + if err == nil && gitURL != "" { + b.blueprint.Repository.Url = b.normalizeGitURL(strings.TrimSpace(gitURL)) + } } - - gitURL, err := b.runtime.Shell.ExecSilent("git", "config", "--get", "remote.origin.url") - if err == nil && gitURL != "" { - b.blueprint.Repository.Url = b.normalizeGitURL(strings.TrimSpace(gitURL)) - return nil + if b.blueprint.Repository.Url != "" && b.blueprint.Repository.Ref == (blueprintv1alpha1.Reference{}) { + b.blueprint.Repository.Ref = blueprintv1alpha1.Reference{Branch: "main"} } - return nil } diff --git a/pkg/composer/blueprint/blueprint_handler_private_test.go b/pkg/composer/blueprint/blueprint_handler_private_test.go index 1850b2495..1034e8408 100644 --- a/pkg/composer/blueprint/blueprint_handler_private_test.go +++ b/pkg/composer/blueprint/blueprint_handler_private_test.go @@ -4016,6 +4016,168 @@ func TestBaseBlueprintHandler_setRepositoryDefaults(t *testing.T) { } }) + t.Run("SetsDefaultBranchToMainWhenURLSetButRefEmpty", func(t *testing.T) { + handler := setup(t) + handler.blueprint.Repository.Url = "https://github.com/user/repo" + handler.blueprint.Repository.Ref = blueprintv1alpha1.Reference{} + + mockConfigHandler := handler.runtime.ConfigHandler.(*config.MockConfigHandler) + mockConfigHandler.GetBoolFunc = func(key string, defaultValue ...bool) bool { + return false + } + + err := handler.setRepositoryDefaults() + + if err != nil { + t.Fatalf("Expected no error, got %v", err) + } + if handler.blueprint.Repository.Ref.Branch != "main" { + t.Errorf("Expected default branch to be 'main', got '%s'", handler.blueprint.Repository.Ref.Branch) + } + if handler.blueprint.Repository.Url != "https://github.com/user/repo" { + t.Errorf("Expected URL to remain unchanged, got %s", handler.blueprint.Repository.Url) + } + }) + + t.Run("SetsDefaultBranchToMainWhenURLSetFromDevMode", func(t *testing.T) { + handler := setup(t) + + mockConfigHandler := handler.runtime.ConfigHandler.(*config.MockConfigHandler) + mockConfigHandler.GetBoolFunc = func(key string, defaultValue ...bool) bool { + if key == "dev" { + return true + } + return false + } + mockConfigHandler.GetStringFunc = func(key string, defaultValue ...string) string { + if key == "dns.domain" { + return "test.com" + } + return "" + } + + handler.runtime.ProjectRoot = "/path/to/project" + handler.shims.FilepathBase = func(path string) string { + return "project" + } + + err := handler.setRepositoryDefaults() + + if err != nil { + t.Fatalf("Expected no error, got %v", err) + } + expectedURL := "http://git.test.com/git/project" + if handler.blueprint.Repository.Url != expectedURL { + t.Errorf("Expected URL to be %s, got %s", expectedURL, handler.blueprint.Repository.Url) + } + if handler.blueprint.Repository.Ref.Branch != "main" { + t.Errorf("Expected default branch to be 'main', got '%s'", handler.blueprint.Repository.Ref.Branch) + } + }) + + t.Run("SetsDefaultBranchToMainWhenURLSetFromGitRemote", func(t *testing.T) { + handler := setup(t) + + mockConfigHandler := handler.runtime.ConfigHandler.(*config.MockConfigHandler) + mockConfigHandler.GetBoolFunc = func(key string, defaultValue ...bool) bool { + return false + } + + mockShell := handler.runtime.Shell.(*shell.MockShell) + mockShell.ExecSilentFunc = func(command string, args ...string) (string, error) { + if command == "git" && len(args) == 3 && args[0] == "config" && args[2] == "remote.origin.url" { + return "https://github.com/user/repo.git\n", nil + } + return "", fmt.Errorf("command not found") + } + + err := handler.setRepositoryDefaults() + + if err != nil { + t.Fatalf("Expected no error, got %v", err) + } + if handler.blueprint.Repository.Url != "https://github.com/user/repo.git" { + t.Errorf("Expected URL to be set from git remote, got %s", handler.blueprint.Repository.Url) + } + if handler.blueprint.Repository.Ref.Branch != "main" { + t.Errorf("Expected default branch to be 'main', got '%s'", handler.blueprint.Repository.Ref.Branch) + } + }) + + t.Run("PreservesExistingRefWhenURLSet", func(t *testing.T) { + handler := setup(t) + handler.blueprint.Repository.Url = "https://github.com/user/repo" + handler.blueprint.Repository.Ref = blueprintv1alpha1.Reference{Branch: "develop"} + + mockConfigHandler := handler.runtime.ConfigHandler.(*config.MockConfigHandler) + mockConfigHandler.GetBoolFunc = func(key string, defaultValue ...bool) bool { + return false + } + + err := handler.setRepositoryDefaults() + + if err != nil { + t.Fatalf("Expected no error, got %v", err) + } + if handler.blueprint.Repository.Ref.Branch != "develop" { + t.Errorf("Expected branch to remain 'develop', got '%s'", handler.blueprint.Repository.Ref.Branch) + } + if handler.blueprint.Repository.Ref.Tag != "" { + t.Errorf("Expected tag to remain empty, got '%s'", handler.blueprint.Repository.Ref.Tag) + } + }) + + t.Run("PreservesExistingRefWhenRefHasTag", func(t *testing.T) { + handler := setup(t) + handler.blueprint.Repository.Url = "https://github.com/user/repo" + handler.blueprint.Repository.Ref = blueprintv1alpha1.Reference{Tag: "v1.0.0"} + + mockConfigHandler := handler.runtime.ConfigHandler.(*config.MockConfigHandler) + mockConfigHandler.GetBoolFunc = func(key string, defaultValue ...bool) bool { + return false + } + + err := handler.setRepositoryDefaults() + + if err != nil { + t.Fatalf("Expected no error, got %v", err) + } + if handler.blueprint.Repository.Ref.Tag != "v1.0.0" { + t.Errorf("Expected tag to remain 'v1.0.0', got '%s'", handler.blueprint.Repository.Ref.Tag) + } + if handler.blueprint.Repository.Ref.Branch != "" { + t.Errorf("Expected branch to remain empty, got '%s'", handler.blueprint.Repository.Ref.Branch) + } + }) + + t.Run("DoesNotSetBranchWhenURLEmpty", func(t *testing.T) { + handler := setup(t) + handler.blueprint.Repository.Url = "" + handler.blueprint.Repository.Ref = blueprintv1alpha1.Reference{} + + mockConfigHandler := handler.runtime.ConfigHandler.(*config.MockConfigHandler) + mockConfigHandler.GetBoolFunc = func(key string, defaultValue ...bool) bool { + return false + } + + mockShell := handler.runtime.Shell.(*shell.MockShell) + mockShell.ExecSilentFunc = func(command string, args ...string) (string, error) { + return "", fmt.Errorf("not a git repository") + } + + err := handler.setRepositoryDefaults() + + if err != nil { + t.Fatalf("Expected no error, got %v", err) + } + if handler.blueprint.Repository.Url != "" { + t.Errorf("Expected URL to remain empty, got %s", handler.blueprint.Repository.Url) + } + if handler.blueprint.Repository.Ref.Branch != "" { + t.Errorf("Expected branch to remain empty when URL is empty, got '%s'", handler.blueprint.Repository.Ref.Branch) + } + }) + } func TestBaseBlueprintHandler_normalizeGitURL(t *testing.T) { diff --git a/pkg/provisioner/kubernetes/kubernetes_manager.go b/pkg/provisioner/kubernetes/kubernetes_manager.go index 02e7dfac3..af3c7a63e 100644 --- a/pkg/provisioner/kubernetes/kubernetes_manager.go +++ b/pkg/provisioner/kubernetes/kubernetes_manager.go @@ -617,11 +617,15 @@ func (k *BaseKubernetesManager) ApplyBlueprint(blueprint *blueprintv1alpha1.Blue } if blueprint.Repository.Url != "" { + var secretName string + if blueprint.Repository.SecretName != nil { + secretName = *blueprint.Repository.SecretName + } source := blueprintv1alpha1.Source{ Name: blueprint.Metadata.Name, Url: blueprint.Repository.Url, Ref: blueprint.Repository.Ref, - SecretName: blueprint.Repository.SecretName, + SecretName: secretName, } if err := k.applyBlueprintSource(source, namespace); err != nil { return fmt.Errorf("failed to apply blueprint repository: %w", err) @@ -1055,7 +1059,9 @@ func (k *BaseKubernetesManager) waitForNodesReady(ctx context.Context, nodeNames // applyBlueprintGitRepository converts and applies a blueprint Source as a GitRepository. func (k *BaseKubernetesManager) applyBlueprintGitRepository(source blueprintv1alpha1.Source, namespace string) error { sourceUrl := source.Url - if !strings.HasPrefix(sourceUrl, "http://") && !strings.HasPrefix(sourceUrl, "https://") { + if strings.HasPrefix(sourceUrl, "git@") && strings.Contains(sourceUrl, ":") { + sourceUrl = "ssh://" + strings.Replace(sourceUrl, ":", "/", 1) + } else if !strings.HasPrefix(sourceUrl, "http://") && !strings.HasPrefix(sourceUrl, "https://") && !strings.HasPrefix(sourceUrl, "ssh://") { sourceUrl = "https://" + sourceUrl } diff --git a/pkg/provisioner/kubernetes/kubernetes_manager_public_test.go b/pkg/provisioner/kubernetes/kubernetes_manager_public_test.go index c921a5d2a..93fa1cbc6 100644 --- a/pkg/provisioner/kubernetes/kubernetes_manager_public_test.go +++ b/pkg/provisioner/kubernetes/kubernetes_manager_public_test.go @@ -11,6 +11,7 @@ import ( "reflect" kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1" + meta "github.com/fluxcd/pkg/apis/meta" sourcev1 "github.com/fluxcd/source-controller/api/v1" blueprintv1alpha1 "github.com/windsorcli/cli/api/v1alpha1" "github.com/windsorcli/cli/pkg/provisioner/kubernetes/client" @@ -1686,6 +1687,109 @@ func TestBaseKubernetesManager_ApplyGitRepository(t *testing.T) { t.Errorf("Expected error containing 'ToUnstructured requires a non-nil pointer to an object', got %v", err) } }) + + t.Run("SuccessWithSecretRef", func(t *testing.T) { + manager := setup(t) + kubernetesClient := client.NewMockKubernetesClient() + var appliedObj *unstructured.Unstructured + kubernetesClient.ApplyResourceFunc = func(gvr schema.GroupVersionResource, obj *unstructured.Unstructured, opts metav1.ApplyOptions) (*unstructured.Unstructured, error) { + appliedObj = obj + return obj, nil + } + kubernetesClient.GetResourceFunc = func(gvr schema.GroupVersionResource, ns, name string) (*unstructured.Unstructured, error) { + return nil, fmt.Errorf("not found") + } + manager.client = kubernetesClient + manager.shims.ToUnstructured = func(obj any) (map[string]any, error) { + return runtime.DefaultUnstructuredConverter.ToUnstructured(obj) + } + + secretName := "test-secret" + repo := &sourcev1.GitRepository{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-repo", + Namespace: "test-namespace", + }, + Spec: sourcev1.GitRepositorySpec{ + URL: "https://github.com/test/repo", + Interval: metav1.Duration{ + Duration: time.Minute, + }, + SecretRef: &meta.LocalObjectReference{ + Name: secretName, + }, + }, + } + + err := manager.ApplyGitRepository(repo) + if err != nil { + t.Errorf("Expected no error, got %v", err) + } + if appliedObj == nil { + t.Fatal("Expected object to be applied") + } + spec, ok := appliedObj.Object["spec"].(map[string]any) + if !ok { + t.Fatal("Expected spec to be present") + } + secretRef, exists := spec["secretRef"] + if !exists { + t.Error("Expected secretRef to be present in spec") + } else { + secretRefMap, ok := secretRef.(map[string]any) + if !ok { + t.Errorf("Expected secretRef to be a map, got %T", secretRef) + } else if secretRefMap["name"] != secretName { + t.Errorf("Expected secretRef name to be '%s', got '%v'", secretName, secretRefMap["name"]) + } + } + }) + + t.Run("SuccessWithoutSecretRef", func(t *testing.T) { + manager := setup(t) + kubernetesClient := client.NewMockKubernetesClient() + var appliedObj *unstructured.Unstructured + kubernetesClient.ApplyResourceFunc = func(gvr schema.GroupVersionResource, obj *unstructured.Unstructured, opts metav1.ApplyOptions) (*unstructured.Unstructured, error) { + appliedObj = obj + return obj, nil + } + kubernetesClient.GetResourceFunc = func(gvr schema.GroupVersionResource, ns, name string) (*unstructured.Unstructured, error) { + return nil, fmt.Errorf("not found") + } + manager.client = kubernetesClient + manager.shims.ToUnstructured = func(obj any) (map[string]any, error) { + return runtime.DefaultUnstructuredConverter.ToUnstructured(obj) + } + + repo := &sourcev1.GitRepository{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-repo", + Namespace: "test-namespace", + }, + Spec: sourcev1.GitRepositorySpec{ + URL: "https://github.com/test/repo", + Interval: metav1.Duration{ + Duration: time.Minute, + }, + SecretRef: nil, + }, + } + + err := manager.ApplyGitRepository(repo) + if err != nil { + t.Errorf("Expected no error, got %v", err) + } + if appliedObj == nil { + t.Fatal("Expected object to be applied") + } + spec, ok := appliedObj.Object["spec"].(map[string]any) + if !ok { + t.Fatal("Expected spec to be present") + } + if _, exists := spec["secretRef"]; exists { + t.Error("Expected secretRef to not be present in spec when nil") + } + }) } func TestBaseKubernetesManager_CheckGitRepositoryStatus(t *testing.T) { @@ -3255,6 +3359,151 @@ func TestBaseKubernetesManager_ApplyBlueprint(t *testing.T) { } }) + t.Run("SuccessWithRepositoryAndSecretName", func(t *testing.T) { + manager := setup(t) + secretName := "test-secret" + var appliedRepo *sourcev1.GitRepository + kubernetesClient := client.NewMockKubernetesClient() + kubernetesClient.ApplyResourceFunc = func(gvr schema.GroupVersionResource, obj *unstructured.Unstructured, opts metav1.ApplyOptions) (*unstructured.Unstructured, error) { + if gvr.Resource == "gitrepositories" { + var repo sourcev1.GitRepository + if err := runtime.DefaultUnstructuredConverter.FromUnstructured(obj.Object, &repo); err == nil { + appliedRepo = &repo + } + } + return obj, nil + } + kubernetesClient.GetResourceFunc = func(gvr schema.GroupVersionResource, ns, name string) (*unstructured.Unstructured, error) { + return nil, fmt.Errorf("not found") + } + manager.client = kubernetesClient + manager.shims.ToUnstructured = func(obj any) (map[string]any, error) { + return runtime.DefaultUnstructuredConverter.ToUnstructured(obj) + } + manager.shims.FromUnstructured = func(obj map[string]any, target any) error { + return runtime.DefaultUnstructuredConverter.FromUnstructured(obj, target) + } + + blueprint := &blueprintv1alpha1.Blueprint{ + Metadata: blueprintv1alpha1.Metadata{ + Name: "test-blueprint", + }, + Repository: blueprintv1alpha1.Repository{ + Url: "https://github.com/example/repo.git", + Ref: blueprintv1alpha1.Reference{Branch: "main"}, + SecretName: &secretName, + }, + } + + err := manager.ApplyBlueprint(blueprint, "test-namespace") + if err != nil { + t.Errorf("Expected no error, got %v", err) + } + if appliedRepo == nil { + t.Fatal("Expected GitRepository to be applied") + } + if appliedRepo.Spec.SecretRef == nil { + t.Error("Expected SecretRef to be set when SecretName is provided") + } else if appliedRepo.Spec.SecretRef.Name != secretName { + t.Errorf("Expected SecretRef.Name to be '%s', got '%s'", secretName, appliedRepo.Spec.SecretRef.Name) + } + }) + + t.Run("SuccessWithRepositoryAndNilSecretName", func(t *testing.T) { + manager := setup(t) + var appliedRepo *sourcev1.GitRepository + kubernetesClient := client.NewMockKubernetesClient() + kubernetesClient.ApplyResourceFunc = func(gvr schema.GroupVersionResource, obj *unstructured.Unstructured, opts metav1.ApplyOptions) (*unstructured.Unstructured, error) { + if gvr.Resource == "gitrepositories" { + var repo sourcev1.GitRepository + if err := runtime.DefaultUnstructuredConverter.FromUnstructured(obj.Object, &repo); err == nil { + appliedRepo = &repo + } + } + return obj, nil + } + kubernetesClient.GetResourceFunc = func(gvr schema.GroupVersionResource, ns, name string) (*unstructured.Unstructured, error) { + return nil, fmt.Errorf("not found") + } + manager.client = kubernetesClient + manager.shims.ToUnstructured = func(obj any) (map[string]any, error) { + return runtime.DefaultUnstructuredConverter.ToUnstructured(obj) + } + manager.shims.FromUnstructured = func(obj map[string]any, target any) error { + return runtime.DefaultUnstructuredConverter.FromUnstructured(obj, target) + } + + blueprint := &blueprintv1alpha1.Blueprint{ + Metadata: blueprintv1alpha1.Metadata{ + Name: "test-blueprint", + }, + Repository: blueprintv1alpha1.Repository{ + Url: "https://github.com/example/repo.git", + Ref: blueprintv1alpha1.Reference{Branch: "main"}, + SecretName: nil, + }, + } + + err := manager.ApplyBlueprint(blueprint, "test-namespace") + if err != nil { + t.Errorf("Expected no error, got %v", err) + } + if appliedRepo == nil { + t.Fatal("Expected GitRepository to be applied") + } + if appliedRepo.Spec.SecretRef != nil { + t.Errorf("Expected SecretRef to be nil when SecretName is nil, got %v", appliedRepo.Spec.SecretRef) + } + }) + + t.Run("SuccessWithRepositoryAndEmptySecretName", func(t *testing.T) { + manager := setup(t) + emptySecretName := "" + var appliedRepo *sourcev1.GitRepository + kubernetesClient := client.NewMockKubernetesClient() + kubernetesClient.ApplyResourceFunc = func(gvr schema.GroupVersionResource, obj *unstructured.Unstructured, opts metav1.ApplyOptions) (*unstructured.Unstructured, error) { + if gvr.Resource == "gitrepositories" { + var repo sourcev1.GitRepository + if err := runtime.DefaultUnstructuredConverter.FromUnstructured(obj.Object, &repo); err == nil { + appliedRepo = &repo + } + } + return obj, nil + } + kubernetesClient.GetResourceFunc = func(gvr schema.GroupVersionResource, ns, name string) (*unstructured.Unstructured, error) { + return nil, fmt.Errorf("not found") + } + manager.client = kubernetesClient + manager.shims.ToUnstructured = func(obj any) (map[string]any, error) { + return runtime.DefaultUnstructuredConverter.ToUnstructured(obj) + } + manager.shims.FromUnstructured = func(obj map[string]any, target any) error { + return runtime.DefaultUnstructuredConverter.FromUnstructured(obj, target) + } + + blueprint := &blueprintv1alpha1.Blueprint{ + Metadata: blueprintv1alpha1.Metadata{ + Name: "test-blueprint", + }, + Repository: blueprintv1alpha1.Repository{ + Url: "https://github.com/example/repo.git", + Ref: blueprintv1alpha1.Reference{Branch: "main"}, + SecretName: &emptySecretName, + }, + } + + err := manager.ApplyBlueprint(blueprint, "test-namespace") + if err != nil { + t.Errorf("Expected no error, got %v", err) + } + if appliedRepo == nil { + t.Fatal("Expected GitRepository to be applied") + } + if appliedRepo.Spec.SecretRef != nil { + t.Errorf("Expected SecretRef to be nil when SecretName is empty string, got %v", appliedRepo.Spec.SecretRef) + } + }) + t.Run("ErrorOnApplyBlueprintRepository", func(t *testing.T) { manager := setup(t) kubernetesClient := client.NewMockKubernetesClient()