Skip to content

Commit

Permalink
CR-5155 - gracefully handle repo-not-exist during clone (#116)
Browse files Browse the repository at this point in the history
  • Loading branch information
ATGardner authored Jun 28, 2021
1 parent 0a7eff5 commit 3c8be61
Show file tree
Hide file tree
Showing 18 changed files with 529 additions and 466 deletions.
22 changes: 11 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,30 +70,30 @@ docker run \

## Getting Started
```bash
# Most of the commands need your git token, you can provide with --git-token to each command
# or export it beforehand:
# All of the commands need your git token with the --git-token flag,
# or the GIT_TOKEN env variable:

export GIT_TOKEN=<YOUR_TOKEN>

# 1. Create a new git repository

argocd-autopilot repo create --owner <owner> --name <name>

# At this point you can specify the gitops repo in each command with --repo
# or you can export it as well:
# The commands will also need your repo clone URL with the --repo flag,
# or the GIT_REPO env variable:

export GIT_REPO=<REPO_URL>

# 2. Run the bootstrap installation on your current kubernetes context.
# 1. Run the bootstrap installation on your current kubernetes context.
# This will install argo-cd as well as the application-set controller.

argocd-autopilot repo bootstrap

# 3. Create your first project
# Please note that this will automatically attempt to create a private repository,
# if the clone URL references a non-existing one. If the repository already exists,
# the command will just clone it.

# 2. Create your first project

argocd-autopilot project create my-project

# 4. Install your first application on your project
# 3. Install your first application on your project

argocd-autopilot app create demoapp --app github.com/argoproj-labs/argocd-autopilot/examples/demo-app/ -p my-project
```
Expand Down
16 changes: 11 additions & 5 deletions cmd/commands/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,10 @@ func NewAppCommand() *cobra.Command {
exit(1)
},
}
cloneOpts = git.AddFlags(cmd, memfs.New(), "")
cloneOpts = git.AddFlags(cmd, &git.AddFlagsOptions{
FS: memfs.New(),
Required: true,
})

cmd.AddCommand(NewAppCreateCommand(cloneOpts))
cmd.AddCommand(NewAppListCommand(cloneOpts))
Expand Down Expand Up @@ -124,7 +127,10 @@ func NewAppCreateCommand(cloneOpts *git.CloneOptions) *cobra.Command {
}

cmd.Flags().StringVarP(&projectName, "project", "p", "", "Project name")
appsCloneOpts = git.AddFlags(cmd, memfs.New(), "apps")
appsCloneOpts = git.AddFlags(cmd, &git.AddFlagsOptions{
FS: memfs.New(),
Prefix: "apps",
})
appOpts = application.AddFlags(cmd)

die(cmd.MarkFlagRequired("app"))
Expand All @@ -149,7 +155,7 @@ func RunAppCreate(ctx context.Context, opts *AppCreateOptions) error {
opts.AppsCloneOpts.Auth.Password = opts.CloneOpts.Auth.Password
}

appsRepo, appsfs, err = clone(ctx, opts.AppsCloneOpts)
appsRepo, appsfs, err = getRepo(ctx, opts.AppsCloneOpts)
if err != nil {
return err
}
Expand All @@ -164,7 +170,7 @@ func RunAppCreate(ctx context.Context, opts *AppCreateOptions) error {

app, err := parseApp(opts.AppOpts, opts.ProjectName, opts.CloneOpts.URL(), opts.CloneOpts.Revision(), opts.CloneOpts.Path())
if err != nil {
return fmt.Errorf("failed to parse application from flags: %v", err)
return fmt.Errorf("failed to parse application from flags: %w", err)
}

if err = app.CreateFiles(repofs, appsfs, opts.ProjectName); err != nil {
Expand Down Expand Up @@ -224,7 +230,7 @@ var setAppOptsDefaults = func(ctx context.Context, repofs fs.FS, opts *AppCreate
FS: fs.Create(memfs.New()),
}
cloneOpts.Parse()
_, fsys, err = clone(ctx, cloneOpts)
_, fsys, err = getRepo(ctx, cloneOpts)
if err != nil {
return err
}
Expand Down
14 changes: 7 additions & 7 deletions cmd/commands/app_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -203,10 +203,10 @@ func TestRunAppCreate(t *testing.T) {
},
},
}
origPrepareRepo, origClone, origSetAppOptsDefault, origAppParse := prepareRepo, clone, setAppOptsDefaults, parseApp
origPrepareRepo, origGetRepo, origSetAppOptsDefault, origAppParse := prepareRepo, getRepo, setAppOptsDefaults, parseApp
defer func() {
prepareRepo = origPrepareRepo
clone = origClone
getRepo = origGetRepo
setAppOptsDefaults = origSetAppOptsDefault
parseApp = origAppParse
}()
Expand All @@ -226,7 +226,7 @@ func TestRunAppCreate(t *testing.T) {
gitopsRepo, repofs, err = tt.prepareRepo()
return gitopsRepo, repofs, err
}
clone = func(_ context.Context, cloneOpts *git.CloneOptions) (git.Repository, fs.FS, error) {
getRepo = func(_ context.Context, cloneOpts *git.CloneOptions) (git.Repository, fs.FS, error) {
var (
repofs fs.FS
err error
Expand Down Expand Up @@ -739,7 +739,7 @@ func Test_setAppOptsDefaults(t *testing.T) {
},
},
beforeFn: func() fs.FS {
clone = func(_ context.Context, _ *git.CloneOptions) (git.Repository, fs.FS, error) {
getRepo = func(_ context.Context, _ *git.CloneOptions) (git.Repository, fs.FS, error) {
return nil, fs.Create(memfs.New()), nil
}

Expand Down Expand Up @@ -775,16 +775,16 @@ func Test_setAppOptsDefaults(t *testing.T) {
},
wantErr: "some error",
beforeFn: func() fs.FS {
clone = func(_ context.Context, _ *git.CloneOptions) (git.Repository, fs.FS, error) {
getRepo = func(_ context.Context, _ *git.CloneOptions) (git.Repository, fs.FS, error) {
return nil, nil, fmt.Errorf("some error")
}

return nil
},
},
}
origClone := clone
defer func() { clone = origClone }()
origGetRepo := getRepo
defer func() { getRepo = origGetRepo }()
for name, tt := range tests {
t.Run(name, func(t *testing.T) {
var repofs fs.FS
Expand Down
13 changes: 4 additions & 9 deletions cmd/commands/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ var (
//go:embed assets/apps_readme.md
appsReadme []byte

clone = func(ctx context.Context, cloneOpts *git.CloneOptions) (git.Repository, fs.FS, error) {
return cloneOpts.Clone(ctx)
getRepo = func(ctx context.Context, cloneOpts *git.CloneOptions) (git.Repository, fs.FS, error) {
return cloneOpts.GetRepo(ctx)
}

prepareRepo = func(ctx context.Context, cloneOpts *git.CloneOptions, projectName string) (git.Repository, fs.FS, error) {
Expand All @@ -47,20 +47,15 @@ var (

// clone repo
log.G().Infof("cloning git repository: %s", cloneOpts.URL())
r, repofs, err := clone(ctx, cloneOpts)
r, repofs, err := getRepo(ctx, cloneOpts)
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\"", cloneOpts.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)
return nil, nil, fmt.Errorf("Bootstrap directory not found, please execute `repo bootstrap` command")
}

if projectName != "" {
Expand Down
109 changes: 46 additions & 63 deletions cmd/commands/common_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,95 +2,84 @@ package commands

import (
"context"
"fmt"
"reflect"
"errors"
"testing"

"github.com/argoproj-labs/argocd-autopilot/pkg/fs"
fsmocks "github.com/argoproj-labs/argocd-autopilot/pkg/fs/mocks"
"github.com/argoproj-labs/argocd-autopilot/pkg/git"
gitmocks "github.com/argoproj-labs/argocd-autopilot/pkg/git/mocks"
"github.com/argoproj-labs/argocd-autopilot/pkg/store"

"github.com/go-git/go-billy/v5/memfs"
billyUtils "github.com/go-git/go-billy/v5/util"
"github.com/stretchr/testify/assert"
)

func TestBaseOptions_prepareRepo(t *testing.T) {
func Test_prepareRepo(t *testing.T) {
tests := map[string]struct {
projectName string
cloneErr string
wantErr string
beforeFn func(m *fsmocks.FS)
getRepo func() (git.Repository, fs.FS, error)
assertFn func(*testing.T, git.Repository, fs.FS)
}{
"Should complete when no errors are returned": {
projectName: "",
cloneErr: "",
wantErr: "",
beforeFn: func(m *fsmocks.FS) {
m.On("Root").Return("/")
m.On("ExistsOrDie", "bootstrap").Return(true)
getRepo: func() (git.Repository, fs.FS, error) {
repofs := fs.Create(memfs.New())
_ = repofs.MkdirAll(store.Default.BootsrtrapDir, 0666)
return &gitmocks.Repository{}, repofs, nil
},
assertFn: func(t *testing.T, r git.Repository, fs fs.FS) {
assert.NotNil(t, r)
assert.NotNil(t, fs)
},
},
"Should fail when clone fails": {
projectName: "project",
cloneErr: "some error",
wantErr: "Failed cloning the repository: some error",
beforeFn: func(m *fsmocks.FS) {},
wantErr: "Failed cloning the repository: some error",
getRepo: func() (git.Repository, fs.FS, error) {
return nil, nil, errors.New("some error")
},
},
"Should fail when there is no bootstrap at repo root": {
projectName: "",
cloneErr: "",
wantErr: "Bootstrap directory not found, please execute `repo bootstrap` command",
beforeFn: func(m *fsmocks.FS) {
m.On("Root").Return("/")
m.On("ExistsOrDie", "bootstrap").Return(false)
wantErr: "Bootstrap directory not found, please execute `repo bootstrap` command",
getRepo: func() (git.Repository, fs.FS, error) {
return &gitmocks.Repository{}, fs.Create(memfs.New()), nil
},
},
"Should fail when there is no bootstrap at instllation path": {
projectName: "",
cloneErr: "",
wantErr: "Bootstrap directory not found, please execute `repo bootstrap --installation-path /some/path` command",
beforeFn: func(m *fsmocks.FS) {
m.On("Root").Return("/some/path")
m.On("ExistsOrDie", "bootstrap").Return(false)
assertFn: func(t *testing.T, r git.Repository, fs fs.FS) {
assert.NotNil(t, r)
assert.NotNil(t, fs)
},
},
"Should not validate project existence, if no projectName is supplied": {
projectName: "",
cloneErr: "",
wantErr: "",
beforeFn: func(m *fsmocks.FS) {
m.On("Root").Return("/")
m.On("ExistsOrDie", "bootstrap").Return(true)
"Should validate project existence if a projectName is supplied": {
projectName: "project",
getRepo: func() (git.Repository, fs.FS, error) {
repofs := fs.Create(memfs.New())
_ = repofs.MkdirAll(store.Default.BootsrtrapDir, 0666)
_ = billyUtils.WriteFile(repofs, repofs.Join(store.Default.ProjectsDir, "project.yaml"), []byte{}, 0666)
return &gitmocks.Repository{}, repofs, nil
},
assertFn: func(t *testing.T, r git.Repository, fs fs.FS) {
assert.NotNil(t, r)
assert.NotNil(t, fs)
},
},
"Should fail when project does not exist": {
projectName: "project",
cloneErr: "",
wantErr: "project 'project' not found, please execute `argocd-autopilot project create project`",
beforeFn: func(m *fsmocks.FS) {
m.On("Root").Return("/")
m.On("ExistsOrDie", "bootstrap").Return(true)
m.On("Join", "projects", "project.yaml").Return("projects/project.yaml")
m.On("ExistsOrDie", "projects/project.yaml").Return(false)
getRepo: func() (git.Repository, fs.FS, error) {
repofs := fs.Create(memfs.New())
_ = repofs.MkdirAll(store.Default.BootsrtrapDir, 0666)
return &gitmocks.Repository{}, repofs, nil
},
},
}
origClone := clone
defer func() { clone = origClone }()
origGetRepo := getRepo
defer func() { getRepo = origGetRepo }()
for name, tt := range tests {
t.Run(name, func(t *testing.T) {
mockRepo := &gitmocks.Repository{}
mockFS := &fsmocks.FS{}
tt.beforeFn(mockFS)
clone = func(_ context.Context, _ *git.CloneOptions) (git.Repository, fs.FS, error) {
var err error
if tt.cloneErr != "" {
err = fmt.Errorf(tt.cloneErr)
}

return mockRepo, mockFS, err
getRepo = func(_ context.Context, _ *git.CloneOptions) (git.Repository, fs.FS, error) {
return tt.getRepo()
}
gotRepo, gotFS, err := prepareRepo(context.Background(), &git.CloneOptions{}, tt.projectName)
r, fs, err := prepareRepo(context.Background(), &git.CloneOptions{}, tt.projectName)
if err != nil {
if tt.wantErr != "" {
assert.EqualError(t, err, tt.wantErr)
Expand All @@ -101,13 +90,7 @@ func TestBaseOptions_prepareRepo(t *testing.T) {
return
}

if !reflect.DeepEqual(gotRepo, mockRepo) {
t.Errorf("BaseOptions.clone() got = %v, want %v", gotRepo, mockRepo)
}

if !reflect.DeepEqual(gotFS, mockFS) {
t.Errorf("BaseOptions.clone() got1 = %v, want %v", gotFS, mockFS)
}
tt.assertFn(t, r, fs)
})
}
}
5 changes: 4 additions & 1 deletion cmd/commands/project.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,10 @@ func NewProjectCommand() *cobra.Command {
exit(1)
},
}
cloneOpts = git.AddFlags(cmd, memfs.New(), "")
cloneOpts = git.AddFlags(cmd, &git.AddFlagsOptions{
FS: memfs.New(),
Required: true,
})

cmd.AddCommand(NewProjectCreateCommand(cloneOpts))
cmd.AddCommand(NewProjectListCommand(cloneOpts))
Expand Down
Loading

0 comments on commit 3c8be61

Please sign in to comment.