diff --git a/Makefile b/Makefile index ca6d8b0e..4d186f9e 100644 --- a/Makefile +++ b/Makefile @@ -108,14 +108,14 @@ test: ./hack/test.sh .PHONY: codegen -codegen: $(GOBIN)/mockery $(GOBIN)/interfacer +codegen: $(GOBIN)/mockery go generate ./... .PHONY: pre-commit pre-commit: lint .PHONY: pre-push -pre-push: codegen test check-worktree +pre-push: lint test codegen check-worktree .PHONY: build-docs build-docs: diff --git a/build/ci.yml b/build/ci.yml index 01a96816..78ccaf73 100644 --- a/build/ci.yml +++ b/build/ci.yml @@ -65,6 +65,19 @@ steps: on: - success + codegen: + <<: *deps + stage: Test + title: check codegen + commands: + - make codegen + - make check-worktree + when: + steps: + - name: prepare_env_vars + on: + - success + test: <<: *deps stage: Test @@ -102,19 +115,6 @@ steps: - name: main_clone on: - success - - # codegen: - # <<: *deps - # stage: Test - # title: compile and check codegen - # commands: - # - make codegen - # - make check-worktree - # when: - # steps: - # - name: test - # on: - # - success push_dev: stage: Push Dev @@ -128,15 +128,6 @@ steps: - name: build on: - success - - name: test - on: - - success - - name: lint - on: - - success - # - name: codegen - # on: - # - success branch: ignore: [ "main" ] scale: diff --git a/build/release.yml b/build/release.yml index 56aa9134..bb41d787 100644 --- a/build/release.yml +++ b/build/release.yml @@ -78,6 +78,19 @@ steps: - name: download_modules on: - success + + codegen: + <<: *deps + stage: Release + title: check codegen + commands: + - make codegen + - make check-worktree + when: + steps: + - name: download_modules + on: + - success test: <<: *deps @@ -91,6 +104,7 @@ steps: on: - success + codecov-report: stage: Test type: codecov-reporter @@ -154,6 +168,9 @@ steps: - name: test on: - success + - name: codegen + on: + - success - name: lint on: - success @@ -175,19 +192,6 @@ steps: on: - success - # codegen: - # <<: *deps - # stage: Release - # title: check codegen - # commands: - # - make codegen - # - make check-worktree - # when: - # steps: - # - name: build_binaries - # on: - # - success - push_prod: stage: Release title: promote images diff --git a/cmd/commands/app.go b/cmd/commands/app.go index 5bca2db4..a17ee70f 100644 --- a/cmd/commands/app.go +++ b/cmd/commands/app.go @@ -51,7 +51,7 @@ func NewAppCommand() *cobra.Command { Short: "Manage applications", Run: func(cmd *cobra.Command, args []string) { cmd.HelpFunc()(cmd, args) - os.Exit(1) + exit(1) }, } opts, err := addFlags(cmd) diff --git a/cmd/commands/common.go b/cmd/commands/common.go index ba092048..8f7c899c 100644 --- a/cmd/commands/common.go +++ b/cmd/commands/common.go @@ -3,6 +3,7 @@ package commands import ( "context" "fmt" + "os" "github.com/argoproj/argocd-autopilot/pkg/fs" "github.com/argoproj/argocd-autopilot/pkg/git" @@ -22,6 +23,55 @@ type ( } ) +// used for mocking +var ( + die = util.Die + exit = os.Exit + + clone = func(ctx context.Context, cloneOpts *git.CloneOptions, filesystem fs.FS) (git.Repository, fs.FS, error) { + return cloneOpts.Clone(ctx, filesystem) + } + + prepareRepo = func(ctx context.Context, o *BaseOptions) (git.Repository, fs.FS, error) { + var ( + r git.Repository + err error + ) + log.G().WithFields(log.Fields{ + "repoURL": o.CloneOptions.URL, + "revision": o.CloneOptions.Revision, + }).Debug("starting with options: ") + + // clone repo + log.G().Infof("cloning git repository: %s", o.CloneOptions.URL) + r, repofs, err := clone(ctx, o.CloneOptions, o.FS) + if err != nil { + return nil, nil, fmt.Errorf("Failed cloning the repository: %w", err) + } + + root := repofs.Root() + log.G().Infof("using revision: \"%s\", installation path: \"%s\"", o.CloneOptions.Revision, root) + if !repofs.ExistsOrDie(store.Default.BootsrtrapDir) { + cmd := "repo bootstrap" + if root != "/" { + cmd += " --installation-path " + root + } + + return nil, nil, fmt.Errorf("Bootstrap directory not found, please execute `%s` command", cmd) + } + + if o.ProjectName != "" { + projExists := repofs.ExistsOrDie(repofs.Join(store.Default.ProjectsDir, o.ProjectName+".yaml")) + if !projExists { + return nil, nil, fmt.Errorf(util.Doc(fmt.Sprintf("project '%[1]s' not found, please execute ` project create %[1]s`", o.ProjectName))) + } + } + + log.G().Debug("repository is ok") + return r, repofs, nil + } +) + func addFlags(cmd *cobra.Command) (*BaseOptions, error) { cloneOptions, err := git.AddFlags(cmd) if err != nil { @@ -35,48 +85,3 @@ func addFlags(cmd *cobra.Command) (*BaseOptions, error) { cmd.PersistentFlags().StringVarP(&o.ProjectName, "project", "p", "", "Project name") return o, nil } - -var prepareRepo = func(ctx context.Context, o *BaseOptions) (git.Repository, fs.FS, error) { - var ( - r git.Repository - err error - ) - log.G().WithFields(log.Fields{ - "repoURL": o.CloneOptions.URL, - "revision": o.CloneOptions.Revision, - }).Debug("starting with options: ") - - // clone repo - log.G().Infof("cloning git repository: %s", o.CloneOptions.URL) - r, repofs, err := clone(ctx, o.CloneOptions, o.FS) - if err != nil { - return nil, nil, fmt.Errorf("Failed cloning the repository: %w", err) - } - - root := repofs.Root() - log.G().Infof("using revision: \"%s\", installation path: \"%s\"", o.CloneOptions.Revision, root) - if !repofs.ExistsOrDie(store.Default.BootsrtrapDir) { - cmd := "repo bootstrap" - if root != "/" { - cmd += " --installation-path " + root - } - - return nil, nil, fmt.Errorf("Bootstrap directory not found, please execute `%s` command", cmd) - } - - if o.ProjectName != "" { - projExists := repofs.ExistsOrDie(repofs.Join(store.Default.ProjectsDir, o.ProjectName+".yaml")) - if !projExists { - return nil, nil, fmt.Errorf(util.Doc(fmt.Sprintf("project '%[1]s' not found, please execute ` project create %[1]s`", o.ProjectName))) - } - } - - log.G().Debug("repository is ok") - return r, repofs, nil -} - -var clone = func(ctx context.Context, cloneOpts *git.CloneOptions, filesystem fs.FS) (git.Repository, fs.FS, error) { - return cloneOpts.Clone(ctx, filesystem) -} - -var die = util.Die diff --git a/cmd/commands/project.go b/cmd/commands/project.go index 049502bf..b67a0140 100644 --- a/cmd/commands/project.go +++ b/cmd/commands/project.go @@ -4,7 +4,6 @@ import ( "context" "fmt" "io/ioutil" - "os" "path/filepath" "github.com/argoproj/argocd-autopilot/pkg/argocd" @@ -43,7 +42,7 @@ func NewProjectCommand() *cobra.Command { Short: "Manage projects", Run: func(cmd *cobra.Command, args []string) { cmd.HelpFunc()(cmd, args) - os.Exit(1) + exit(1) }, } @@ -81,7 +80,7 @@ func NewProjectCreateCommand() *cobra.Command { # Create a new project in a specific path inside the GitOps repo - project create --installation-path path/to/bootstrap/root + project create --installation-path path/to/installation_root `), RunE: func(cmd *cobra.Command, args []string) error { if len(args) < 1 { diff --git a/cmd/commands/repo.go b/cmd/commands/repo.go index d4b9c1d7..bf34cd47 100644 --- a/cmd/commands/repo.go +++ b/cmd/commands/repo.go @@ -10,6 +10,7 @@ import ( "time" "github.com/argoproj/argocd-autopilot/pkg/application" + "github.com/argoproj/argocd-autopilot/pkg/argocd" "github.com/argoproj/argocd-autopilot/pkg/fs" "github.com/argoproj/argocd-autopilot/pkg/git" "github.com/argoproj/argocd-autopilot/pkg/kube" @@ -39,7 +40,13 @@ var projectReadme []byte //go:embed assets/kustomization_readme.md var kustomizationReadme []byte -var supportedProviders = []string{"github"} +// used for mocking +var ( + argocdLogin = argocd.Login + getGitProvider = git.NewProvider + currentKubeContext = kube.CurrentContext + runKustomizeBuild = application.GenerateManifests +) type ( RepoCreateOptions struct { @@ -82,7 +89,7 @@ func NewRepoCommand() *cobra.Command { Short: "Manage gitops repositories", Run: func(cmd *cobra.Command, args []string) { cmd.HelpFunc()(cmd, args) - os.Exit(1) + exit(1) }, } @@ -137,7 +144,7 @@ func NewRepoCreateCommand() *cobra.Command { die(viper.BindEnv("git-token", "GIT_TOKEN")) - cmd.Flags().StringVarP(&provider, "provider", "p", "github", "The git provider, "+fmt.Sprintf("one of: %v", strings.Join(supportedProviders, "|"))) + cmd.Flags().StringVarP(&provider, "provider", "p", "github", "The git provider, "+fmt.Sprintf("one of: %v", strings.Join(git.Providers(), "|"))) cmd.Flags().StringVarP(&owner, "owner", "o", "", "The name of the owner or organiaion") cmd.Flags().StringVarP(&repo, "name", "n", "", "The name of the repository") cmd.Flags().StringVarP(&token, "git-token", "t", "", "Your git provider api token [GIT_TOKEN]") @@ -183,7 +190,7 @@ func NewRepoBootstrapCommand() *cobra.Command { # Install argo-cd on the current kubernetes context in the argocd namespace # and persists the bootstrap manifests to a specific folder in the gitops repository - repo bootstrap --repo https://github.com/example/repo --installation-path path/to/bootstrap/root + repo bootstrap --repo https://github.com/example/repo --installation-path path/to/installation_root `), RunE: func(cmd *cobra.Command, args []string) error { return RunRepoBootstrap(cmd.Context(), &RepoBootstrapOptions{ @@ -223,7 +230,7 @@ func NewRepoBootstrapCommand() *cobra.Command { } func RunRepoCreate(ctx context.Context, opts *RepoCreateOptions) error { - p, err := git.NewProvider(&git.Options{ + p, err := getGitProvider(&git.ProviderOptions{ Type: opts.Provider, Auth: &git.Auth{ Username: "git", @@ -283,13 +290,14 @@ func RunRepoBootstrap(ctx context.Context, opts *RepoBootstrapOptions) error { manifests.argocdApp, manifests.rootApp, )) - os.Exit(0) + exit(0) + return nil } log.G().Infof("cloning repo: %s", opts.CloneOptions.URL) // clone GitOps repo - r, opts.FS, err = opts.CloneOptions.Clone(ctx, opts.FS) + r, opts.FS, err = clone(ctx, opts.CloneOptions, opts.FS) if err != nil { return err } @@ -333,8 +341,24 @@ func RunRepoBootstrap(ctx context.Context, opts *RepoBootstrapOptions) error { return err } + passwd, err := getInitialPassword(ctx, opts.KubeFactory, opts.Namespace) + if err != nil { + return err + } + + log.G().Infof("running argocd login to initialize argocd config") + err = argocdLogin(&argocd.LoginOptions{ + Namespace: opts.Namespace, + Username: "admin", + Password: passwd, + }) + if err != nil { + return err + } if !opts.HidePassword { - return printInitialPassword(ctx, opts.KubeFactory, opts.Namespace) + log.G(ctx).Printf("") + log.G(ctx).Infof("argocd initialized. password: %s", passwd) + log.G(ctx).Infof("run:\n\n kubectl port-forward -n %s svc/argocd-server 8080:80\n\n", opts.Namespace) } return nil @@ -345,6 +369,8 @@ func setBootstrapOptsDefaults(opts RepoBootstrapOptions) (*RepoBootstrapOptions, switch opts.InstallationMode { case installationModeFlat, installationModeNormal: + case "": + opts.InstallationMode = installationModeNormal default: return nil, fmt.Errorf("unknown installation mode: %s", opts.InstallationMode) } @@ -363,7 +389,7 @@ func setBootstrapOptsDefaults(opts RepoBootstrapOptions) (*RepoBootstrapOptions, } if opts.KubeContext == "" { - if opts.KubeContext, err = kube.CurrentContext(); err != nil { + if opts.KubeContext, err = currentKubeContext(); err != nil { return nil, err } } @@ -477,22 +503,19 @@ func getRepoCredsSecret(token, namespace string) ([]byte, error) { }) } -func printInitialPassword(ctx context.Context, f kube.Factory, namespace string) error { +func getInitialPassword(ctx context.Context, f kube.Factory, namespace string) (string, error) { cs := f.KubernetesClientSetOrDie() secret, err := cs.CoreV1().Secrets(namespace).Get(ctx, "argocd-initial-admin-secret", metav1.GetOptions{}) if err != nil { - return err + return "", err } passwd, ok := secret.Data["password"] if !ok { - return fmt.Errorf("argocd initial password not found") + return "", fmt.Errorf("argocd initial password not found") } - log.G(ctx).Printf("\n") - log.G(ctx).Infof("argocd initialized. password: %s", passwd) - log.G(ctx).Infof("run:\n\n kubectl port-forward -n %s svc/argocd-server 8080:80\n\n", namespace) - return nil + return string(passwd), nil } func getBootstrapAppSpecifier(namespaced bool) string { @@ -552,7 +575,7 @@ func buildBootstrapManifests(namespace, appSpecifier string, cloneOpts *git.Clon return nil, err } - manifests.applyManifests, err = application.GenerateManifests(k) + manifests.applyManifests, err = runKustomizeBuild(k) if err != nil { return nil, err } diff --git a/cmd/commands/repo_test.go b/cmd/commands/repo_test.go index 4f5e3db7..633ba5e2 100644 --- a/cmd/commands/repo_test.go +++ b/cmd/commands/repo_test.go @@ -2,29 +2,505 @@ package commands import ( "context" + "fmt" + "path/filepath" "testing" + "github.com/argoproj/argocd-autopilot/pkg/argocd" + "github.com/argoproj/argocd-autopilot/pkg/fs" "github.com/argoproj/argocd-autopilot/pkg/git" + gitmocks "github.com/argoproj/argocd-autopilot/pkg/git/mocks" + kubemocks "github.com/argoproj/argocd-autopilot/pkg/kube/mocks" + "github.com/argoproj/argocd-autopilot/pkg/store" + + argocdv1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" + "github.com/ghodss/yaml" + memfs "github.com/go-git/go-billy/v5/memfs" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes/fake" + kusttypes "sigs.k8s.io/kustomize/api/types" ) func TestRunRepoCreate(t *testing.T) { tests := map[string]struct { opts *RepoCreateOptions - assertFn func(t *testing.T, opts *RepoCreateOptions, ret error) + preFn func(*gitmocks.Provider) + assertFn func(t *testing.T, mp *gitmocks.Provider, opts *RepoCreateOptions, ret error) }{ "Invalid provider": { opts: &RepoCreateOptions{ Provider: "foobar", }, - assertFn: func(t *testing.T, _ *RepoCreateOptions, ret error) { + assertFn: func(t *testing.T, mp *gitmocks.Provider, opts *RepoCreateOptions, ret error) { assert.ErrorIs(t, ret, git.ErrProviderNotSupported) }, }, + "Should call CreateRepository": { + opts: &RepoCreateOptions{ + Provider: "github", + Owner: "foo", + Repo: "bar", + Token: "test", + Public: false, + Host: "", + }, + preFn: func(mp *gitmocks.Provider) { + mp.On("CreateRepository", mock.Anything, mock.Anything).Return("", nil) + }, + assertFn: func(t *testing.T, mp *gitmocks.Provider, opts *RepoCreateOptions, ret error) { + mp.AssertCalled(t, "CreateRepository", mock.Anything, mock.Anything) + o := mp.Calls[0].Arguments[1].(*git.CreateRepoOptions) + assert.NotNil(t, o) + assert.Equal(t, opts.Public, !o.Private) + }, + }, + "Should fail to CreateRepository": { + opts: &RepoCreateOptions{ + Provider: "github", + Owner: "foo", + Repo: "bar", + Token: "test", + Public: false, + Host: "", + }, + preFn: func(mp *gitmocks.Provider) { + mp.On("CreateRepository", mock.Anything, mock.Anything).Return("", fmt.Errorf("error")) + }, + assertFn: func(t *testing.T, mp *gitmocks.Provider, opts *RepoCreateOptions, ret error) { + mp.AssertCalled(t, "CreateRepository", mock.Anything, mock.Anything) + assert.EqualError(t, ret, "error") + }, + }, + } + + orgGetProvider := getGitProvider + for tname, tt := range tests { + defer func() { getGitProvider = orgGetProvider }() + mp := &gitmocks.Provider{} + if tt.preFn != nil { + tt.preFn(mp) + getGitProvider = func(opts *git.ProviderOptions) (git.Provider, error) { + return mp, nil + } + } + + t.Run(tname, func(t *testing.T) { + tt.assertFn(t, mp, tt.opts, RunRepoCreate(context.Background(), tt.opts)) + }) + } +} + +func Test_setBootstrapOptsDefaults(t *testing.T) { + tests := map[string]struct { + opts *RepoBootstrapOptions + preFn func() + assertFn func(t *testing.T, opts *RepoBootstrapOptions, ret error) + }{ + "Bad installation mode": { + opts: &RepoBootstrapOptions{ + InstallationMode: "foo", + }, + assertFn: func(t *testing.T, opts *RepoBootstrapOptions, ret error) { + assert.EqualError(t, ret, "unknown installation mode: foo") + }, + }, + "Basic": { + opts: &RepoBootstrapOptions{}, + preFn: func() { + currentKubeContext = func() (string, error) { + return "fooctx", nil + } + }, + assertFn: func(t *testing.T, opts *RepoBootstrapOptions, ret error) { + assert.NoError(t, ret) + assert.Equal(t, "argocd", opts.Namespace) + assert.Equal(t, false, opts.Namespaced) + assert.Equal(t, "manifests", opts.AppSpecifier) + assert.Equal(t, "fooctx", opts.KubeContext) + }, + }, + "With App specifier": { + opts: &RepoBootstrapOptions{ + AppSpecifier: "https://github.com/foo/bar", + KubeContext: "fooctx", + }, + assertFn: func(t *testing.T, opts *RepoBootstrapOptions, ret error) { + assert.NoError(t, ret) + assert.Equal(t, "argocd", opts.Namespace) + assert.Equal(t, false, opts.Namespaced) + assert.Equal(t, installationModeNormal, opts.InstallationMode) + assert.Equal(t, "https://github.com/foo/bar", opts.AppSpecifier) + assert.Equal(t, "fooctx", opts.KubeContext) + }, + }, + "Namespaced": { + opts: &RepoBootstrapOptions{ + InstallationMode: installationModeFlat, + KubeContext: "fooctx", + Namespaced: true, + Namespace: "bar", + }, + assertFn: func(t *testing.T, opts *RepoBootstrapOptions, ret error) { + assert.NoError(t, ret) + assert.Equal(t, "bar", opts.Namespace) + assert.Equal(t, true, opts.Namespaced) + assert.Equal(t, installationModeFlat, opts.InstallationMode) + assert.Equal(t, "manifests/namespace-install", opts.AppSpecifier) + assert.Equal(t, "fooctx", opts.KubeContext) + }, + }, + } + + orgCurrentKubeContext := currentKubeContext + for tname, tt := range tests { + t.Run(tname, func(t *testing.T) { + if tt.preFn != nil { + tt.preFn() + defer func() { currentKubeContext = orgCurrentKubeContext }() + } + + ret, err := setBootstrapOptsDefaults(*tt.opts) + tt.assertFn(t, ret, err) + }) + } +} + +func Test_validateRepo(t *testing.T) { + tests := map[string]struct { + preFn func(t *testing.T, repofs fs.FS) + assertFn func(t *testing.T, repofs fs.FS, ret error) + }{ + "Bootstrap exists": { + preFn: func(t *testing.T, repofs fs.FS) { + _, err := repofs.WriteFile(store.Default.BootsrtrapDir, []byte{}) + assert.NoError(t, err) + }, + assertFn: func(t *testing.T, repofs fs.FS, ret error) { + assert.EqualError(t, ret, fmt.Sprintf("folder %[1]s already exist in: /%[1]s", store.Default.BootsrtrapDir)) + }, + }, + "Projects exists": { + preFn: func(t *testing.T, repofs fs.FS) { + _, err := repofs.WriteFile(store.Default.ProjectsDir, []byte{}) + assert.NoError(t, err) + }, + assertFn: func(t *testing.T, repofs fs.FS, ret error) { + assert.EqualError(t, ret, fmt.Sprintf("folder %[1]s already exist in: /%[1]s", store.Default.ProjectsDir)) + }, + }, + "Valid": { + assertFn: func(t *testing.T, repofs fs.FS, ret error) { + assert.NoError(t, ret) + }, + }, + } + for tname, tt := range tests { + t.Run(tname, func(t *testing.T) { + repofs := fs.Create(memfs.New()) + if tt.preFn != nil { + tt.preFn(t, repofs) + } + + tt.assertFn(t, repofs, validateRepo(repofs)) + }) + } +} + +func Test_buildBootstrapManifests(t *testing.T) { + type args struct { + namespace string + appSpecifier string + cloneOpts *git.CloneOptions + } + tests := map[string]struct { + args args + preFn func() + assertFn func(t *testing.T, b *bootstrapManifests, ret error) + }{ + "Basic": { + args: args{ + namespace: "foo", + appSpecifier: "bar", + cloneOpts: &git.CloneOptions{ + Revision: "main", + URL: "https://github.com/foo/bar", + RepoRoot: "/installation1", + Auth: git.Auth{Password: "test"}, + }, + }, + preFn: func() { + runKustomizeBuild = func(k *kusttypes.Kustomization) ([]byte, error) { + return []byte("test"), nil + } + }, + assertFn: func(t *testing.T, b *bootstrapManifests, ret error) { + assert.NoError(t, ret) + assert.Equal(t, []byte("test"), b.applyManifests) + + argocdApp := &argocdv1alpha1.Application{} + assert.NoError(t, yaml.Unmarshal(b.argocdApp, argocdApp)) + assert.Equal(t, argocdApp.Spec.Source.RepoURL, "https://github.com/foo/bar") + assert.Equal(t, argocdApp.Spec.Source.Path, filepath.Join( + "/installation1", + store.Default.BootsrtrapDir, + store.Default.ArgoCDName, + )) + assert.Equal(t, argocdApp.Spec.Source.TargetRevision, "main") + assert.Equal(t, 0, len(argocdApp.ObjectMeta.Finalizers)) + assert.Equal(t, argocdApp.Spec.Destination.Namespace, "foo") + assert.Equal(t, argocdApp.Spec.Destination.Server, store.Default.DestServer) + + bootstrapApp := &argocdv1alpha1.Application{} + assert.NoError(t, yaml.Unmarshal(b.bootstrapApp, bootstrapApp)) + assert.Equal(t, bootstrapApp.Spec.Source.RepoURL, "https://github.com/foo/bar") + assert.Equal(t, bootstrapApp.Spec.Source.Path, filepath.Join( + "/installation1", + store.Default.BootsrtrapDir, + )) + assert.Equal(t, bootstrapApp.Spec.Source.TargetRevision, "main") + assert.NotEqual(t, 0, len(bootstrapApp.ObjectMeta.Finalizers)) + assert.Equal(t, bootstrapApp.Spec.Destination.Namespace, "foo") + assert.Equal(t, bootstrapApp.Spec.Destination.Server, store.Default.DestServer) + + rootApp := &argocdv1alpha1.Application{} + assert.NoError(t, yaml.Unmarshal(b.rootApp, rootApp)) + assert.Equal(t, rootApp.Spec.Source.RepoURL, "https://github.com/foo/bar") + assert.Equal(t, rootApp.Spec.Source.Path, filepath.Join( + "/installation1", + store.Default.ProjectsDir, + )) + assert.Equal(t, rootApp.Spec.Source.TargetRevision, "main") + assert.NotEqual(t, 0, len(rootApp.ObjectMeta.Finalizers)) + assert.Equal(t, rootApp.Spec.Destination.Namespace, "foo") + assert.Equal(t, rootApp.Spec.Destination.Server, store.Default.DestServer) + + ns := &v1.Namespace{} + assert.NoError(t, yaml.Unmarshal(b.namespace, ns)) + assert.Equal(t, ns.ObjectMeta.Name, "foo") + + creds := &v1.Secret{} + assert.NoError(t, yaml.Unmarshal(b.repoCreds, &creds)) + assert.Equal(t, creds.ObjectMeta.Name, store.Default.RepoCredsSecretName) + assert.Equal(t, creds.ObjectMeta.Namespace, "foo") + assert.Equal(t, creds.Data["git_token"], []byte("test")) + assert.Equal(t, creds.Data["git_username"], []byte(store.Default.GitUsername)) + }, + }, + } + + orgRunKustomizeBuild := runKustomizeBuild + + for tname, tt := range tests { + t.Run(tname, func(t *testing.T) { + if tt.preFn != nil { + tt.preFn() + defer func() { runKustomizeBuild = orgRunKustomizeBuild }() + } + b, ret := buildBootstrapManifests( + tt.args.namespace, + tt.args.appSpecifier, + tt.args.cloneOpts, + ) + tt.assertFn(t, b, ret) + }) + } +} + +func TestRunRepoBootstrap(t *testing.T) { + exitCalled := false + tests := map[string]struct { + opts *RepoBootstrapOptions + preFn func(r *gitmocks.Repository, repofs fs.FS, f *kubemocks.Factory) + assertFn func(t *testing.T, r *gitmocks.Repository, repofs fs.FS, f *kubemocks.Factory, ret error) + }{ + "DryRun": { + opts: &RepoBootstrapOptions{ + DryRun: true, + InstallationMode: installationModeFlat, + KubeContext: "foo", + Namespace: "bar", + CloneOptions: &git.CloneOptions{ + Revision: "main", + URL: "https://github.com/foo/bar", + RepoRoot: "/installation1", + Auth: git.Auth{Password: "test"}, + }, + }, + assertFn: func(t *testing.T, r *gitmocks.Repository, repofs fs.FS, f *kubemocks.Factory, ret error) { + assert.NoError(t, ret) + assert.True(t, exitCalled) + }, + }, + "Flat installation": { + opts: &RepoBootstrapOptions{ + InstallationMode: installationModeFlat, + KubeContext: "foo", + Namespace: "bar", + CloneOptions: &git.CloneOptions{ + Revision: "main", + URL: "https://github.com/foo/bar", + RepoRoot: "/installation1", + Auth: git.Auth{Password: "test"}, + }, + }, + preFn: func(r *gitmocks.Repository, repofs fs.FS, f *kubemocks.Factory) { + mockCS := fake.NewSimpleClientset(&v1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "argocd-initial-admin-secret", + Namespace: "bar", + }, + Data: map[string][]byte{ + "password": []byte("foo"), + }, + }) + f.On("Apply", mock.Anything, mock.Anything, mock.Anything).Return(nil) + f.On("Wait", mock.Anything, mock.Anything).Return(nil) + f.On("KubernetesClientSetOrDie").Return(mockCS) + + r.On("Persist", mock.Anything, mock.Anything).Return(nil) + + }, + assertFn: func(t *testing.T, r *gitmocks.Repository, repofs fs.FS, f *kubemocks.Factory, ret error) { + assert.NoError(t, ret) + assert.False(t, exitCalled) + r.AssertCalled(t, "Persist", mock.Anything, mock.Anything) + f.AssertCalled(t, "Apply", mock.Anything, "bar", mock.Anything) + f.AssertCalled(t, "Wait", mock.Anything, mock.Anything) + f.AssertCalled(t, "KubernetesClientSetOrDie") + f.AssertNumberOfCalls(t, "Apply", 2) + + // bootstrap dir + assert.True(t, repofs.ExistsOrDie(repofs.Join( + store.Default.BootsrtrapDir, + store.Default.ArgoCDName+".yaml", + ))) + assert.True(t, repofs.ExistsOrDie(repofs.Join( + store.Default.BootsrtrapDir, + store.Default.RootAppName+".yaml", + ))) + assert.True(t, repofs.ExistsOrDie(repofs.Join( + store.Default.BootsrtrapDir, + store.Default.ArgoCDName, + "install.yaml", + ))) + + // projects + assert.True(t, repofs.ExistsOrDie(repofs.Join( + store.Default.ProjectsDir, + "README.md", + ))) + + // kustomize + assert.True(t, repofs.ExistsOrDie(repofs.Join( + store.Default.KustomizeDir, + "README.md", + ))) + }, + }, + "Normal installation": { + opts: &RepoBootstrapOptions{ + InstallationMode: installationModeNormal, + KubeContext: "foo", + Namespace: "bar", + CloneOptions: &git.CloneOptions{ + Revision: "main", + URL: "https://github.com/foo/bar", + RepoRoot: "/installation1", + Auth: git.Auth{Password: "test"}, + }, + }, + preFn: func(r *gitmocks.Repository, repofs fs.FS, f *kubemocks.Factory) { + mockCS := fake.NewSimpleClientset(&v1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "argocd-initial-admin-secret", + Namespace: "bar", + }, + Data: map[string][]byte{ + "password": []byte("foo"), + }, + }) + f.On("Apply", mock.Anything, mock.Anything, mock.Anything).Return(nil) + f.On("Wait", mock.Anything, mock.Anything).Return(nil) + f.On("KubernetesClientSetOrDie").Return(mockCS) + + r.On("Persist", mock.Anything, mock.Anything).Return(nil) + + }, + assertFn: func(t *testing.T, r *gitmocks.Repository, repofs fs.FS, f *kubemocks.Factory, ret error) { + assert.NoError(t, ret) + assert.False(t, exitCalled) + r.AssertCalled(t, "Persist", mock.Anything, mock.Anything) + f.AssertCalled(t, "Apply", mock.Anything, "bar", mock.Anything) + f.AssertCalled(t, "Wait", mock.Anything, mock.Anything) + f.AssertCalled(t, "KubernetesClientSetOrDie") + f.AssertNumberOfCalls(t, "Apply", 2) + + // bootstrap dir + assert.True(t, repofs.ExistsOrDie(repofs.Join( + store.Default.BootsrtrapDir, + store.Default.ArgoCDName+".yaml", + ))) + assert.True(t, repofs.ExistsOrDie(repofs.Join( + store.Default.BootsrtrapDir, + store.Default.RootAppName+".yaml", + ))) + assert.True(t, repofs.ExistsOrDie(repofs.Join( + store.Default.BootsrtrapDir, + store.Default.ArgoCDName, + "kustomization.yaml", + ))) + + // projects + assert.True(t, repofs.ExistsOrDie(repofs.Join( + store.Default.ProjectsDir, + "README.md", + ))) + + // kustomize + assert.True(t, repofs.ExistsOrDie(repofs.Join( + store.Default.KustomizeDir, + "README.md", + ))) + }, + }, } + + orgExit := exit + orgClone := clone + orgRunKustomizeBuild := runKustomizeBuild + orgArgoLogin := argocdLogin + for tname, tt := range tests { t.Run(tname, func(t *testing.T) { - tt.assertFn(t, tt.opts, RunRepoCreate(context.Background(), tt.opts)) + exitCalled = false + mockRepo := &gitmocks.Repository{} + mockFactory := &kubemocks.Factory{} + repofs := fs.Create(memfs.New()) + + if tt.preFn != nil { + tt.preFn(mockRepo, repofs, mockFactory) + } + + tt.opts.KubeFactory = mockFactory + + exit = func(code int) { exitCalled = true } + clone = func(ctx context.Context, cloneOpts *git.CloneOptions, filesystem fs.FS) (git.Repository, fs.FS, error) { + return mockRepo, repofs, nil + } + runKustomizeBuild = func(k *kusttypes.Kustomization) ([]byte, error) { return []byte("test"), nil } + argocdLogin = func(opts *argocd.LoginOptions) error { return nil } + + defer func() { + exit = orgExit + clone = orgClone + runKustomizeBuild = orgRunKustomizeBuild + argocdLogin = orgArgoLogin + }() + + tt.assertFn(t, mockRepo, repofs, mockFactory, RunRepoBootstrap(context.Background(), tt.opts)) }) } } diff --git a/docs/commands/argocd-autopilot_application.md b/docs/commands/argocd-autopilot_application.md index 29fdb7cc..14314fe9 100644 --- a/docs/commands/argocd-autopilot_application.md +++ b/docs/commands/argocd-autopilot_application.md @@ -9,7 +9,12 @@ argocd-autopilot application [flags] ### Options ``` - -h, --help help for application + -t, --git-token string Your git provider api token [GIT_TOKEN] + -h, --help help for application + --installation-path string The path where we of the installation files (defaults to the root of the repository [GIT_INSTALLATION_PATH] + -p, --project string Project name + --repo string Repository URL [GIT_REPO] + --revision string Repository branch, tag or commit hash (defaults to HEAD) ``` ### SEE ALSO @@ -17,5 +22,6 @@ argocd-autopilot application [flags] * [argocd-autopilot](argocd-autopilot.md) - argocd-autopilot is used for installing and managing argo-cd installations and argo-cd applications using gitops * [argocd-autopilot application create](argocd-autopilot_application_create.md) - Create an application in a specific project +* [argocd-autopilot application delete](argocd-autopilot_application_delete.md) - Delete an application from a project * [argocd-autopilot application list](argocd-autopilot_application_list.md) - List all applications in a project diff --git a/docs/commands/argocd-autopilot_application_create.md b/docs/commands/argocd-autopilot_application_create.md index 2fa28d9a..4c59afa0 100644 --- a/docs/commands/argocd-autopilot_application_create.md +++ b/docs/commands/argocd-autopilot_application_create.md @@ -32,9 +32,14 @@ argocd-autopilot application create [APP_NAME] [flags] --app string The application specifier (e.g. argocd@v1.0.2) --dest-namespace string K8s target namespace (overrides the namespace specified in the kustomization.yaml) --dest-server string K8s cluster URL (e.g. https://kubernetes.default.svc) (default "https://kubernetes.default.svc") - -t, --git-token string Your git provider api token [GIT_TOKEN] -h, --help help for create --installation-mode string One of: normal|flat. If flat, will commit the application manifests (after running kustomize build), otherwise will commit the kustomization.yaml (default "normal") +``` + +### Options inherited from parent commands + +``` + -t, --git-token string Your git provider api token [GIT_TOKEN] --installation-path string The path where we of the installation files (defaults to the root of the repository [GIT_INSTALLATION_PATH] -p, --project string Project name --repo string Repository URL [GIT_REPO] diff --git a/docs/commands/argocd-autopilot_application_delete.md b/docs/commands/argocd-autopilot_application_delete.md new file mode 100644 index 00000000..47eaa1c6 --- /dev/null +++ b/docs/commands/argocd-autopilot_application_delete.md @@ -0,0 +1,49 @@ +## argocd-autopilot application delete + +Delete an application from a project + +``` +argocd-autopilot application delete [APP_NAME] [flags] +``` + +### Examples + +``` + +# To run this command you need to create a personal access token for your git provider, +# and have a bootstrapped GitOps repository, and provide them using: + + export GIT_TOKEN= + export GIT_REPO= + +# or with the flags: + + --token --repo + +# Get list of installed applications in a specifc project + + argocd-autopilot app delete --project + +``` + +### Options + +``` + -g, --global global + -h, --help help for delete +``` + +### Options inherited from parent commands + +``` + -t, --git-token string Your git provider api token [GIT_TOKEN] + --installation-path string The path where we of the installation files (defaults to the root of the repository [GIT_INSTALLATION_PATH] + -p, --project string Project name + --repo string Repository URL [GIT_REPO] + --revision string Repository branch, tag or commit hash (defaults to HEAD) +``` + +### SEE ALSO + +* [argocd-autopilot application](argocd-autopilot_application.md) - Manage applications + diff --git a/docs/commands/argocd-autopilot_application_list.md b/docs/commands/argocd-autopilot_application_list.md index 74d46714..a3f92d92 100644 --- a/docs/commands/argocd-autopilot_application_list.md +++ b/docs/commands/argocd-autopilot_application_list.md @@ -28,10 +28,16 @@ argocd-autopilot application list [PROJECT_NAME] [flags] ### Options +``` + -h, --help help for list +``` + +### Options inherited from parent commands + ``` -t, --git-token string Your git provider api token [GIT_TOKEN] - -h, --help help for list --installation-path string The path where we of the installation files (defaults to the root of the repository [GIT_INSTALLATION_PATH] + -p, --project string Project name --repo string Repository URL [GIT_REPO] --revision string Repository branch, tag or commit hash (defaults to HEAD) ``` diff --git a/docs/commands/argocd-autopilot_project_create.md b/docs/commands/argocd-autopilot_project_create.md index 41caa124..57e1e5cf 100644 --- a/docs/commands/argocd-autopilot_project_create.md +++ b/docs/commands/argocd-autopilot_project_create.md @@ -26,7 +26,7 @@ argocd-autopilot project create [PROJECT] [flags] # Create a new project in a specific path inside the GitOps repo - argocd-autopilot project create --installation-path path/to/bootstrap/root + argocd-autopilot project create --installation-path path/to/installation_root ``` diff --git a/docs/commands/argocd-autopilot_repo_bootstrap.md b/docs/commands/argocd-autopilot_repo_bootstrap.md index f554e41c..9bf3f71a 100644 --- a/docs/commands/argocd-autopilot_repo_bootstrap.md +++ b/docs/commands/argocd-autopilot_repo_bootstrap.md @@ -27,7 +27,7 @@ argocd-autopilot repo bootstrap [flags] # Install argo-cd on the current kubernetes context in the argocd namespace # and persists the bootstrap manifests to a specific folder in the gitops repository - argocd-autopilot repo bootstrap --repo https://github.com/example/repo --installation-path path/to/bootstrap/root + argocd-autopilot repo bootstrap --repo https://github.com/example/repo --installation-path path/to/installation_root ``` @@ -37,7 +37,7 @@ argocd-autopilot repo bootstrap [flags] --app string The application specifier (e.g. argocd@v1.0.2) --as string Username to impersonate for the operation --as-group stringArray Group to impersonate for the operation, this flag can be repeated to specify multiple groups. - --cache-dir string Default cache directory (default "/Users/roikramer/.kube/cache") + --cache-dir string Default cache directory (default "/home/user/.kube/cache") --certificate-authority string Path to a cert file for the certificate authority --client-certificate string Path to a client certificate file for TLS --client-key string Path to a client key file for TLS diff --git a/hack/check_worktree.sh b/hack/check_worktree.sh index 33d98976..451f489c 100755 --- a/hack/check_worktree.sh +++ b/hack/check_worktree.sh @@ -5,7 +5,7 @@ res=$(git status -s) if [[ -z "$res" ]]; then echo worktree is clean! else - echo error: working tree is not clean! make sure you run 'make pre-commit' and commit the changes. - echo "$res" + echo error: working tree is not clean! make sure you run 'make pre-push' and commit the changes. + GIT_PAGER=cat git diff --minimal exit 1 -fi \ No newline at end of file +fi diff --git a/hack/cmd-docs/main.go b/hack/cmd-docs/main.go index 3872d71e..a4aab339 100644 --- a/hack/cmd-docs/main.go +++ b/hack/cmd-docs/main.go @@ -1,22 +1,64 @@ package main import ( + "io/fs" + "io/ioutil" "log" "os" + "path/filepath" + "strings" "github.com/spf13/cobra/doc" "github.com/argoproj/argocd-autopilot/cmd/commands" ) -var outputDir = "./docs/commands" +const ( + outputDir = "./docs/commands" + home = "/home/user" +) + +var orgHome = os.Getenv("HOME") func main() { - // set HOME env var so that default values involve user's home directory do not depend on the running user. - os.Setenv("HOME", "/home/user") + log.Printf("org home: %s", orgHome) + log.Printf("new home: %s", home) - err := doc.GenMarkdownTree(commands.NewRoot(), outputDir) - if err != nil { + if err := doc.GenMarkdownTree(commands.NewRoot(), outputDir); err != nil { + log.Fatal(err) + } + + if err := replaceHome(); err != nil { log.Fatal(err) } } + +func replaceHome() error { + files, err := fs.Glob(os.DirFS(outputDir), "*.md") + if err != nil { + return err + } + + for _, fname := range files { + fname = filepath.Join(outputDir, fname) + data, err := os.ReadFile(fname) + if err != nil { + return err + } + + datastr := string(data) + newstr := strings.ReplaceAll(datastr, orgHome, home) + + if datastr == newstr { + continue + } + + log.Printf("replaced home at: %s", fname) + + err = ioutil.WriteFile(fname, []byte(newstr), 0422) + if err != nil { + return err + } + } + return nil +} diff --git a/pkg/argocd/argocd.go b/pkg/argocd/argocd.go index 568d50c9..966d2f5b 100644 --- a/pkg/argocd/argocd.go +++ b/pkg/argocd/argocd.go @@ -12,9 +12,18 @@ import ( "github.com/spf13/cobra" ) -type AddClusterCmd interface { - Execute(ctx context.Context, clusterName string) error -} +type ( + // AddClusterCmd when executed calls the 'argocd cluster add' command + AddClusterCmd interface { + Execute(ctx context.Context, clusterName string) error + } + + LoginOptions struct { + Namespace string + Username string + Password string + } +) type addClusterImpl struct { cmd *cobra.Command @@ -43,3 +52,22 @@ func (a *addClusterImpl) Execute(ctx context.Context, clusterName string) error a.cmd.SetArgs(append(a.args, clusterName)) return a.cmd.ExecuteContext(ctx) } + +func Login(opts *LoginOptions) error { + root := commands.NewCommand() + args := []string{ + "login", + "--port-forward", + "--port-forward-namespace", + opts.Namespace, + "--password", + opts.Password, + "--username", + opts.Username, + "--name", + "autopilot", + } + + root.SetArgs(args) + return root.Execute() +} diff --git a/pkg/git/mocks/provider.go b/pkg/git/mocks/provider.go new file mode 100644 index 00000000..4f8fba29 --- /dev/null +++ b/pkg/git/mocks/provider.go @@ -0,0 +1,57 @@ +// Code generated by mockery v1.1.1. DO NOT EDIT. + +package mocks + +import ( + context "context" + + git "github.com/argoproj/argocd-autopilot/pkg/git" + mock "github.com/stretchr/testify/mock" +) + +// Provider is an autogenerated mock type for the Provider type +type Provider struct { + mock.Mock +} + +// CreateRepository provides a mock function with given fields: ctx, opts +func (_m *Provider) CreateRepository(ctx context.Context, opts *git.CreateRepoOptions) (string, error) { + ret := _m.Called(ctx, opts) + + var r0 string + if rf, ok := ret.Get(0).(func(context.Context, *git.CreateRepoOptions) string); ok { + r0 = rf(ctx, opts) + } else { + r0 = ret.Get(0).(string) + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *git.CreateRepoOptions) error); ok { + r1 = rf(ctx, opts) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetRepository provides a mock function with given fields: ctx, opts +func (_m *Provider) GetRepository(ctx context.Context, opts *git.GetRepoOptions) (string, error) { + ret := _m.Called(ctx, opts) + + var r0 string + if rf, ok := ret.Get(0).(func(context.Context, *git.GetRepoOptions) string); ok { + r0 = rf(ctx, opts) + } else { + r0 = ret.Get(0).(string) + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *git.GetRepoOptions) error); ok { + r1 = rf(ctx, opts) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} diff --git a/pkg/git/provider.go b/pkg/git/provider.go index c5571b93..7658ab76 100644 --- a/pkg/git/provider.go +++ b/pkg/git/provider.go @@ -6,6 +6,8 @@ import ( "fmt" ) +//go:generate mockery -name Provider -filename provider.go + type ( // Provider represents a git provider Provider interface { @@ -21,8 +23,8 @@ type ( Password string } - // Options for a new git provider - Options struct { + // ProviderOptions for a new git provider + ProviderOptions struct { Type string Auth *Auth Host string @@ -48,12 +50,25 @@ var ( } ) +var supportedProviders = map[string]func(*ProviderOptions) (Provider, error){ + "github": newGithub, +} + // New creates a new git provider -func NewProvider(opts *Options) (Provider, error) { - switch opts.Type { - case "github": - return newGithub(opts) - default: +func NewProvider(opts *ProviderOptions) (Provider, error) { + cons, exists := supportedProviders[opts.Type] + if !exists { return nil, ErrProviderNotSupported } + + return cons(opts) +} + +func Providers() []string { + res := make([]string, 0, len(supportedProviders)) + for p := range supportedProviders { + res = append(res, p) + } + + return res } diff --git a/pkg/git/provider_github.go b/pkg/git/provider_github.go index 3ca38b41..0190976b 100644 --- a/pkg/git/provider_github.go +++ b/pkg/git/provider_github.go @@ -9,16 +9,14 @@ import ( gh "github.com/google/go-github/v34/github" ) -//go:generate interfacer -for github.com/google/go-github/v34/github.RepositoriesService -as github.Repositories -o github/repos.go -//go:generate interfacer -for github.com/google/go-github/v34/github.UsersService -as github.Users -o github/users.go //go:generate mockery -dir github -all -output github/mocks -case snake type github struct { - opts *Options + opts *ProviderOptions Repositories g.Repositories Users g.Users } -func newGithub(opts *Options) (Provider, error) { +func newGithub(opts *ProviderOptions) (Provider, error) { var ( c *gh.Client err error diff --git a/pkg/git/repository.go b/pkg/git/repository.go index febfb295..b124cdf7 100644 --- a/pkg/git/repository.go +++ b/pkg/git/repository.go @@ -21,8 +21,6 @@ import ( "github.com/spf13/viper" ) -//go:generate interfacer -for github.com/go-git/go-git/v5.Repository -as gogit.Repository -o gogit/repo.go -//go:generate interfacer -for github.com/go-git/go-git/v5.Worktree -as gogit.Worktree -o gogit/worktree.go //go:generate mockery -dir gogit -all -output gogit/mocks -case snake //go:generate mockery -name Repository -filename repository.go diff --git a/pkg/kube/kube.go b/pkg/kube/kube.go index 690d850b..1eb3608d 100644 --- a/pkg/kube/kube.go +++ b/pkg/kube/kube.go @@ -20,6 +20,8 @@ import ( cmdutil "k8s.io/kubectl/pkg/cmd/util" ) +//go:generate mockery -name Factory -filename kube.go + const ( defaultPollInterval = time.Second * 2 defaultPollTimeout = time.Minute * 5 @@ -41,10 +43,11 @@ func WaitDeploymentReady(ctx context.Context, f Factory, ns, name string) (bool, } type Factory interface { - cmdutil.Factory + // KubernetesClientSet returns a new kubernetes clientset or error + KubernetesClientSet() (kubernetes.Interface, error) // KubernetesClientSetOrDie calls KubernetesClientSet() and panics if it returns an error - KubernetesClientSetOrDie() *kubernetes.Clientset + KubernetesClientSetOrDie() kubernetes.Interface // Apply applies the provided manifests on the specified namespace Apply(ctx context.Context, namespace string, manifests []byte) error @@ -77,7 +80,7 @@ type WaitOptions struct { } type factory struct { - cmdutil.Factory + f cmdutil.Factory } func AddFlags(flags *pflag.FlagSet) Factory { @@ -85,7 +88,7 @@ func AddFlags(flags *pflag.FlagSet) Factory { confFlags.AddFlags(flags) mvFlags := cmdutil.NewMatchVersionFlags(confFlags) - return &factory{cmdutil.NewFactory(mvFlags)} + return &factory{f: cmdutil.NewFactory(mvFlags)} } func DefaultIOStreams() genericclioptions.IOStreams { @@ -122,12 +125,16 @@ func GenerateNamespace(namespace string) *corev1.Namespace { } } -func (f *factory) KubernetesClientSetOrDie() *kubernetes.Clientset { +func (f *factory) KubernetesClientSetOrDie() kubernetes.Interface { cs, err := f.KubernetesClientSet() util.Die(err) return cs } +func (f *factory) KubernetesClientSet() (kubernetes.Interface, error) { + return f.f.KubernetesClientSet() +} + func (f *factory) Apply(ctx context.Context, namespace string, manifests []byte) error { reader, buf, err := os.Pipe() if err != nil { @@ -145,7 +152,7 @@ func (f *factory) Apply(ctx context.Context, namespace string, manifests []byte) o.DeleteFlags.FileNameFlags.Filenames = &[]string{"-"} o.Overwrite = true - if err := o.Complete(f, cmd); err != nil { + if err := o.Complete(f.f, cmd); err != nil { return err } diff --git a/pkg/kube/mocks/kube.go b/pkg/kube/mocks/kube.go new file mode 100644 index 00000000..3e0574fc --- /dev/null +++ b/pkg/kube/mocks/kube.go @@ -0,0 +1,84 @@ +// Code generated by mockery v1.1.1. DO NOT EDIT. + +package mocks + +import ( + context "context" + + kube "github.com/argoproj/argocd-autopilot/pkg/kube" + kubernetes "k8s.io/client-go/kubernetes" + + mock "github.com/stretchr/testify/mock" +) + +// Factory is an autogenerated mock type for the Factory type +type Factory struct { + mock.Mock +} + +// Apply provides a mock function with given fields: ctx, namespace, manifests +func (_m *Factory) Apply(ctx context.Context, namespace string, manifests []byte) error { + ret := _m.Called(ctx, namespace, manifests) + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string, []byte) error); ok { + r0 = rf(ctx, namespace, manifests) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// KubernetesClientSet provides a mock function with given fields: +func (_m *Factory) KubernetesClientSet() (kubernetes.Interface, error) { + ret := _m.Called() + + var r0 kubernetes.Interface + if rf, ok := ret.Get(0).(func() kubernetes.Interface); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(kubernetes.Interface) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func() error); ok { + r1 = rf() + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// KubernetesClientSetOrDie provides a mock function with given fields: +func (_m *Factory) KubernetesClientSetOrDie() kubernetes.Interface { + ret := _m.Called() + + var r0 kubernetes.Interface + if rf, ok := ret.Get(0).(func() kubernetes.Interface); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(kubernetes.Interface) + } + } + + return r0 +} + +// Wait provides a mock function with given fields: _a0, _a1 +func (_m *Factory) Wait(_a0 context.Context, _a1 *kube.WaitOptions) error { + ret := _m.Called(_a0, _a1) + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, *kube.WaitOptions) error); ok { + r0 = rf(_a0, _a1) + } else { + r0 = ret.Error(0) + } + + return r0 +} diff --git a/pkg/util/util.go b/pkg/util/util.go index 5f4d956d..55b260d1 100644 --- a/pkg/util/util.go +++ b/pkg/util/util.go @@ -106,6 +106,7 @@ func WithSpinner(ctx context.Context, msg ...string) func() { return func() { cancel() + // wait just enough time to prevent logs jumbling between spinner and main flow time.Sleep(time.Millisecond * 100) } }