Skip to content

Commit

Permalink
CR-11473 bootstrap recovery from repo (#296)
Browse files Browse the repository at this point in the history
* adjust RunRepoBootstrap to recovering from repo
  • Loading branch information
rotem-codefresh authored May 8, 2022
1 parent dad3cf3 commit 1b9637a
Show file tree
Hide file tree
Showing 3 changed files with 106 additions and 15 deletions.
43 changes: 29 additions & 14 deletions cmd/commands/repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ type (
DryRun bool
HidePassword bool
Insecure bool
Recover bool
Timeout time.Duration
KubeFactory kube.Factory
CloneOptions *git.CloneOptions
Expand Down Expand Up @@ -113,6 +114,7 @@ func NewRepoBootstrapCommand() *cobra.Command {
dryRun bool
hidePassword bool
insecure bool
recover bool
installationMode string
cloneOpts *git.CloneOptions
f kube.Factory
Expand Down Expand Up @@ -153,6 +155,7 @@ func NewRepoBootstrapCommand() *cobra.Command {
DryRun: dryRun,
HidePassword: hidePassword,
Insecure: insecure,
Recover: recover,
Timeout: util.MustParseDuration(cmd.Flag("request-timeout").Value.String()),
KubeFactory: f,
CloneOptions: cloneOpts,
Expand All @@ -164,6 +167,7 @@ func NewRepoBootstrapCommand() *cobra.Command {
cmd.Flags().StringVar(&appSpecifier, "app", "", "The application specifier (e.g. github.com/argoproj-labs/argocd-autopilot/manifests?ref=v0.2.5), overrides the default installation argo-cd manifests")
cmd.Flags().BoolVar(&dryRun, "dry-run", false, "If true, print manifests instead of applying them to the cluster (nothing will be commited to git)")
cmd.Flags().BoolVar(&hidePassword, "hide-password", false, "If true, will not print initial argo cd password")
cmd.Flags().BoolVar(&recover, "recover", false, "Installs Argo-CD on a cluster without pushing installation manifests to the git repository. This is meant to be used together with --app flag to use the same Argo-CD manifests that exists in the git repository (e.g. --app https://github.com/git-user/repo-name/bootstrap/argo-cd)")
cmd.Flags().BoolVar(&insecure, "insecure", false, "Run Argo-CD server without TLS")
cmd.Flags().StringToStringVar(&namespaceLabels, "namespace-labels", nil, "Optional labels that will be set on the namespace resource. (e.g. \"key1=value1,key2=value2\"")
cmd.Flags().StringVar(&installationMode, "installation-mode", "normal", "One of: normal|flat. "+
Expand Down Expand Up @@ -230,7 +234,8 @@ func RunRepoBootstrap(ctx context.Context, opts *RepoBootstrapOptions) error {
}

log.G(ctx).Infof("using revision: \"%s\", installation path: \"%s\"", opts.CloneOptions.Revision(), opts.CloneOptions.Path())
if err = validateRepo(repofs); err != nil {
err = validateRepo(repofs, opts.Recover)
if err != nil{
return err
}

Expand All @@ -243,9 +248,11 @@ func RunRepoBootstrap(ctx context.Context, opts *RepoBootstrapOptions) error {
return fmt.Errorf("failed to apply bootstrap manifests to cluster: %w", err)
}

// write argocd manifests to repo
if err = writeManifestsToRepo(repofs, manifests, opts.InstallationMode, opts.Namespace); err != nil {
return fmt.Errorf("failed to write manifests to repo: %w", err)
if !opts.Recover {
// write argocd manifests to repo
if err = writeManifestsToRepo(repofs, manifests, opts.InstallationMode, opts.Namespace); err != nil {
return fmt.Errorf("failed to write manifests to repo: %w", err)
}
}

// wait for argocd to be ready before applying argocd-apps
Expand All @@ -258,15 +265,17 @@ func RunRepoBootstrap(ctx context.Context, opts *RepoBootstrapOptions) error {

stop()

// push results to repo
log.G(ctx).Infof("pushing bootstrap manifests to repo")
commitMsg := "Autopilot Bootstrap"
if opts.CloneOptions.Path() != "" {
commitMsg = "Autopilot Bootstrap at " + opts.CloneOptions.Path()
}
if !opts.Recover {
// push results to repo
log.G(ctx).Infof("pushing bootstrap manifests to repo")
commitMsg := "Autopilot Bootstrap"
if opts.CloneOptions.Path() != "" {
commitMsg = "Autopilot Bootstrap at " + opts.CloneOptions.Path()
}

if _, err = r.Persist(ctx, &git.PushOptions{CommitMsg: commitMsg}); err != nil {
return err
if _, err = r.Persist(ctx, &git.PushOptions{CommitMsg: commitMsg}); err != nil {
return err
}
}

// apply "Argo-CD" Application that references "bootstrap/argo-cd"
Expand Down Expand Up @@ -459,11 +468,17 @@ func setBootstrapOptsDefaults(opts RepoBootstrapOptions) (*RepoBootstrapOptions,
return &opts, nil
}

func validateRepo(repofs fs.FS) error {
func validateRepo(repofs fs.FS, recover bool) error {
folders := []string{store.Default.BootsrtrapDir, store.Default.ProjectsDir}
for _, folder := range folders {
if repofs.ExistsOrDie(folder) {
return fmt.Errorf("folder %s already exist in: %s", folder, repofs.Join(repofs.Root(), folder))
if recover {
continue
} else {
return fmt.Errorf("folder %s already exist in: %s", folder, repofs.Join(repofs.Root(), folder))
}
} else if recover {
return fmt.Errorf("recovery failed: invalid repository, %s directory is missing in %s", folder, repofs.Root())
}
}

Expand Down
77 changes: 76 additions & 1 deletion cmd/commands/repo_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ func Test_validateRepo(t *testing.T) {
tt.preFn(t, repofs)
}

if err := validateRepo(repofs); err != nil {
if err := validateRepo(repofs, false); err != nil {
if tt.wantErr != "" {
assert.EqualError(t, err, tt.wantErr)
} else {
Expand Down Expand Up @@ -414,6 +414,81 @@ func TestRunRepoBootstrap(t *testing.T) {
}
}

func TestRunRepoBootstrapRecovery(t *testing.T) {
exitCalled := false
tests := map[string]struct {
opts *RepoBootstrapOptions
beforeFn func(*gitmocks.MockRepository, *kubemocks.MockFactory)
assertFn func(*testing.T, fs.FS, error)
}{
"Recovery installation": {
opts: &RepoBootstrapOptions{
InstallationMode: installationModeNormal,
Namespace: "bar",
Recover: true,
CloneOptions: &git.CloneOptions{
Repo: "https://github.com/foo/bar/installation1?ref=main",
Auth: git.Auth{Password: "test"},
},
},
beforeFn: func(r *gitmocks.MockRepository, f *kubemocks.MockFactory) {
mockCS := fake.NewSimpleClientset(&v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "argocd-initial-admin-secret",
Namespace: "bar",
},
Data: map[string][]byte{
"password": []byte("foo"),
},
})
r.EXPECT().Persist(gomock.Any(), &git.PushOptions{CommitMsg: "Autopilot Bootstrap"}).Times(0)
f.EXPECT().Apply(gomock.Any(), gomock.Any()).
Times(2).
Return(nil)
f.EXPECT().Wait(gomock.Any(), gomock.Any()).Return(nil)
f.EXPECT().KubernetesClientSetOrDie().Return(mockCS)
},
assertFn: func(t *testing.T, repofs fs.FS, ret error) {
assert.NoError(t, ret)
assert.False(t, exitCalled)
},
},
}

origExit, origGetRepo, origRunKustomizeBuild, origArgoLogin := exit, getRepo, runKustomizeBuild, argocdLogin
defer func() {
exit = origExit
getRepo = origGetRepo
runKustomizeBuild = origRunKustomizeBuild
argocdLogin = origArgoLogin
}()
exit = func(_ int) { exitCalled = true }
runKustomizeBuild = func(k *kusttypes.Kustomization) ([]byte, error) { return []byte("test"), nil }
argocdLogin = func(opts *argocd.LoginOptions) error { return nil }

for tname, tt := range tests {
t.Run(tname, func(t *testing.T) {
ctrl := gomock.NewController(t)
r := gitmocks.NewMockRepository(ctrl)
f := kubemocks.NewMockFactory(ctrl)
repofs := fs.Create(memfs.New())
_ = repofs.MkdirAll("bootstrap", 0666)
_ = repofs.MkdirAll("projects", 0666)

exitCalled = false

tt.beforeFn(r, f)
tt.opts.KubeFactory = f
getRepo = func(_ context.Context, _ *git.CloneOptions) (git.Repository, fs.FS, error) {
return r, repofs, nil
}

err := RunRepoBootstrap(context.Background(), tt.opts)
tt.assertFn(t, repofs, err)
})
}
}

func Test_setUninstallOptsDefaults(t *testing.T) {
tests := map[string]struct {
opts RepoUninstallOptions
Expand Down
1 change: 1 addition & 0 deletions docs/commands/argocd-autopilot_repo_bootstrap.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ argocd-autopilot repo bootstrap [flags]
-n, --namespace string If present, the namespace scope for this CLI request
--namespace-labels stringToString Optional labels that will be set on the namespace resource. (e.g. "key1=value1,key2=value2" (default [])
--provider string The git provider, one of: azure|gitea|github|gitlab
--recover Installs Argo-CD on a cluster without pushing installation manifests to the git repository. This is meant to be used together with --app flag to use the same Argo-CD manifests that exists in the git repository (e.g. --app https://github.com/git-user/repo-name/bootstrap/argo-cd)
--repo string Repository URL [GIT_REPO]
--request-timeout string The length of time to wait before giving up on a single server request. Non-zero values should contain a corresponding time unit (e.g. 1s, 2m, 3h). A value of zero means don't timeout requests. (default "0")
-b, --upsert-branch If true will try to checkout the specified branch and create it if it doesn't exist
Expand Down

0 comments on commit 1b9637a

Please sign in to comment.