Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CR-5155 - gracefully handle repo-not-exist during clone #116

Merged
merged 7 commits into from
Jun 28, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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