Skip to content

Commit

Permalink
Add project list command (#34)
Browse files Browse the repository at this point in the history
* project list
  • Loading branch information
oren-codefresh authored May 5, 2021
1 parent 1a372fb commit 611c58f
Show file tree
Hide file tree
Showing 8 changed files with 322 additions and 7 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ codegen: $(GOBIN)/mockery
pre-commit: lint

.PHONY: pre-push
pre-push: lint test codegen check-worktree
pre-push: tidy lint test codegen check-worktree

.PHONY: build-docs
build-docs:
Expand Down
6 changes: 6 additions & 0 deletions cmd/commands/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"github.com/argoproj/argocd-autopilot/pkg/util"

memfs "github.com/go-git/go-billy/v5/memfs"
billyUtils "github.com/go-git/go-billy/v5/util"
"github.com/spf13/cobra"
)

Expand Down Expand Up @@ -68,8 +69,13 @@ var (
}

log.G().Debug("repository is ok")

return r, repofs, nil
}

glob = func(fs fs.FS, pattern string) ([]string, error) {
return billyUtils.Glob(fs, pattern)
}
)

func addFlags(cmd *cobra.Command) (*BaseOptions, error) {
Expand Down
100 changes: 100 additions & 0 deletions cmd/commands/project.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@ package commands
import (
"context"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"text/tabwriter"

"github.com/argoproj/argocd-autopilot/pkg/argocd"
"github.com/argoproj/argocd-autopilot/pkg/fs"
Expand Down Expand Up @@ -46,7 +49,10 @@ func NewProjectCommand() *cobra.Command {
},
}

opts, err := addFlags(cmd)
die(err)
cmd.AddCommand(NewProjectCreateCommand())
cmd.AddCommand(NewProjectListCommand(opts))

return cmd
}
Expand Down Expand Up @@ -323,3 +329,97 @@ var getInstallationNamespace = func(repofs fs.FS) (string, error) {

return a.Spec.Destination.Namespace, nil
}

type (
ProjectListOptions struct {
BaseOptions
Out io.Writer
}
)

func NewProjectListCommand(opts *BaseOptions) *cobra.Command {

cmd := &cobra.Command{
Use: "list ",
Short: "Lists all the projects on a git repository",
Example: util.Doc(`
# 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=<token>
export GIT_REPO=<repo_url>
# or with the flags:
--token <token> --repo <repo_url>
# Lists projects
<BIN> project list
`),
RunE: func(cmd *cobra.Command, args []string) error {

return RunProjectList(cmd.Context(), &ProjectListOptions{
BaseOptions: *opts,
Out: os.Stdout,
})
},
}

return cmd
}

func RunProjectList(ctx context.Context, opts *ProjectListOptions) error {

_, repofs, err := prepareRepo(ctx, &opts.BaseOptions)
if err != nil {
return err
}

matches, err := glob(repofs, repofs.Join(store.Default.ProjectsDir, "*.yaml"))
if err != nil {
return err
}
w := tabwriter.NewWriter(opts.Out, 0, 0, 2, ' ', 0)
_, _ = fmt.Fprintf(w, "NAME\tNAMESPACE\tCLUSTER\t\n")

for _, name := range matches {
proj, _, err := getProjectInfoFromFile(repofs, name)
if err != nil {
return err
}
fmt.Fprintf(w, "%s\t%s\t%s\n", proj.Name, proj.Namespace, proj.ClusterName)

}
w.Flush()
return nil
}

var getProjectInfoFromFile = func(fs fs.FS, name string) (*argocdv1alpha1.AppProject, *appsetv1alpha1.ApplicationSpec, error) {
file, err := fs.Open(name)
if err != nil {
return nil, nil, fmt.Errorf("%s not found", name)
}
b, err := ioutil.ReadAll(file)
if err != nil {
return nil, nil, fmt.Errorf("failed to read file %s", name)
}
yamls := util.SplitManifests(b)
if len(yamls) != 2 {
return nil, nil, fmt.Errorf("expected 2 files when splitting %s", name)
}
proj := argocdv1alpha1.AppProject{}
err = yaml.Unmarshal(yamls[0], &proj)

if err != nil {
return nil, nil, fmt.Errorf("failed to unmarshal %s", name)
}
appSet := appsetv1alpha1.ApplicationSpec{}
err = yaml.Unmarshal(yamls[1], &proj)
if err != nil {
return nil, nil, fmt.Errorf("failed to unmarshal %s", name)
}

return &proj, &appSet, nil

}
141 changes: 140 additions & 1 deletion cmd/commands/project_test.go
Original file line number Diff line number Diff line change
@@ -1,23 +1,29 @@
package commands

import (
"bytes"
"context"
"fmt"
"io"
"os"
"reflect"
"strings"

"testing"

appsetv1alpha1 "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
argocdv1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
"github.com/argoproj/argocd-autopilot/pkg/fs"
fsmocks "github.com/argoproj/argocd-autopilot/pkg/fs/mocks"
"github.com/argoproj/argocd-autopilot/pkg/git"
gitmocks "github.com/argoproj/argocd-autopilot/pkg/git/mocks"
"github.com/argoproj/argocd-autopilot/pkg/store"
"github.com/argoproj/argocd-autopilot/pkg/util"

"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/apimachinery/pkg/apis/meta/v1"
)

func TestRunProjectCreate(t *testing.T) {
Expand Down Expand Up @@ -283,3 +289,136 @@ spec:
})
}
}

func Test_getProjectInfoFromFile(t *testing.T) {
tests := map[string]struct {
name string
want *argocdv1alpha1.AppProject
wantErr string
beforeFn func(fs.FS) (fs.FS, error)
}{
"should return error if project file doesn't exist": {
name: "prod.yaml",
wantErr: "prod.yaml not found",
},
"should failed when 2 files not found": {
name: "prod.yaml",
wantErr: "expected 2 files when splitting prod.yaml",
beforeFn: func(f fs.FS) (fs.FS, error) {
_, err := f.WriteFile("prod.yaml", []byte("content"))
if err != nil {
return nil, err
}
return f, nil
},
},
"should return AppProject": {
name: "prod.yaml",
beforeFn: func(f fs.FS) (fs.FS, error) {
appProj := argocdv1alpha1.AppProject{
ObjectMeta: v1.ObjectMeta{
Name: "prod",
Namespace: "ns",
},
}
appSet := appsetv1alpha1.ApplicationSpec{}
projectYAML, _ := yaml.Marshal(&appProj)
appsetYAML, _ := yaml.Marshal(&appSet)
joinedYAML := util.JoinManifests(projectYAML, appsetYAML)
_, err := f.WriteFile("prod.yaml", joinedYAML)
if err != nil {
return nil, err
}
return f, nil
},
want: &argocdv1alpha1.AppProject{
ObjectMeta: v1.ObjectMeta{
Name: "prod",
Namespace: "ns",
},
},
},
}
for tName, tt := range tests {
t.Run(tName, func(t *testing.T) {
repofs := fs.Create(memfs.New())
if tt.beforeFn != nil {
_, err := tt.beforeFn(repofs)
assert.NoError(t, err)
}
got, _, err := getProjectInfoFromFile(repofs, tt.name)
if (err != nil) && tt.wantErr != "" {
assert.EqualError(t, err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("getProjectInfoFromFile() = %v, want %v", got, tt.want)
}
})
}
}

func TestRunProjectList(t *testing.T) {
type args struct {
ctx context.Context
opts *ProjectListOptions
}
tests := map[string]struct {
args args
wantErr bool
prepareRepo func(ctx context.Context, o *BaseOptions) (git.Repository, fs.FS, error)
glob func(fs fs.FS, pattern string) ([]string, error)
getProjectInfoFromFile func(fs fs.FS, name string) (*argocdv1alpha1.AppProject, *appsetv1alpha1.ApplicationSpec, error)
assertFn func(t *testing.T, str string)
}{
"should print to table": {
args: args{
opts: &ProjectListOptions{
BaseOptions: BaseOptions{},
Out: &bytes.Buffer{},
},
},
glob: func(fs fs.FS, pattern string) ([]string, error) {
res := make([]string, 0, 1)
res = append(res, "prod.yaml")
return res, nil
},
prepareRepo: func(ctx context.Context, o *BaseOptions) (git.Repository, fs.FS, error) {
memFS := fs.Create(memfs.New())
return nil, memFS, nil
},
getProjectInfoFromFile: func(fs fs.FS, name string) (*argocdv1alpha1.AppProject, *appsetv1alpha1.ApplicationSpec, error) {
appProj := &argocdv1alpha1.AppProject{
ObjectMeta: v1.ObjectMeta{
Name: "prod",
Namespace: "ns",
},
}
return appProj, nil, nil
},
assertFn: func(t *testing.T, str string) {
assert.Contains(t, str, "NAME NAMESPACE CLUSTER \n")
assert.Contains(t, str, "prod ns ")

},
},
}
origPrepareRepo := prepareRepo
origGlob := glob
origGetProjectInfoFromFile := getProjectInfoFromFile
for tName, tt := range tests {
t.Run(tName, func(t *testing.T) {
prepareRepo = tt.prepareRepo
glob = tt.glob
getProjectInfoFromFile = tt.getProjectInfoFromFile
if err := RunProjectList(tt.args.ctx, tt.args.opts); (err != nil) != tt.wantErr {
t.Errorf("RunProjectList() error = %v, wantErr %v", err, tt.wantErr)
}
b := tt.args.opts.Out.(*bytes.Buffer)
tt.assertFn(t, b.String())
prepareRepo = origPrepareRepo
glob = origGlob
getProjectInfoFromFile = origGetProjectInfoFromFile
})
}
}
8 changes: 7 additions & 1 deletion docs/commands/argocd-autopilot_project.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,18 @@ argocd-autopilot project [flags]
### Options

```
-h, --help help for project
-t, --git-token string Your git provider api token [GIT_TOKEN]
-h, --help help for project
--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](argocd-autopilot.md) - argocd-autopilot is used for installing and managing argo-cd installations and argo-cd
applications using gitops
* [argocd-autopilot project create](argocd-autopilot_project_create.md) - Create a new project
* [argocd-autopilot project list](argocd-autopilot_project_list.md) - Lists all the projects on a git repository

14 changes: 10 additions & 4 deletions docs/commands/argocd-autopilot_project_create.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,20 +46,16 @@ argocd-autopilot project create [PROJECT] [flags]
--exec-command-args stringArray Arguments to supply to the --exec-command executable
--exec-command-env stringToString Environment vars to set when running the --exec-command executable (default [])
--exec-command-install-hint string Text shown to the user when the --exec-command executable doesn't seem to be present
-t, --git-token string Your git provider api token [GIT_TOKEN]
--grpc-web Enables gRPC-web protocol. Useful if Argo CD server is behind proxy which does not support HTTP2.
--grpc-web-root-path string Enables gRPC-web protocol. Useful if Argo CD server is behind proxy which does not support HTTP2. Set web root.
-H, --header strings Sets additional header to all requests made by Argo CD CLI. (Can be repeated multiple times to add multiple headers, also supports comma separated headers)
-h, --help help for create
--in-cluster Indicates Argo CD resides inside this cluster and should connect using the internal k8s hostname (kubernetes.default.svc)
--insecure Skip server certificate and domain verification
--installation-path string The path where we of the installation files (defaults to the root of the repository [GIT_INSTALLATION_PATH]
--name string Overwrite the cluster name
--plaintext Disable TLS
--port-forward Connect to a random argocd-server port using port forwarding
--port-forward-namespace string Namespace name which should be used for port forwarding
--repo string Repository URL [GIT_REPO]
--revision string Repository branch, tag or commit hash (defaults to HEAD)
--server string Argo CD server address
--server-crt string Server certificate file
--service-account string System namespace service account to use for kubernetes resource management. If not set then default "argocd-manager" SA will be created
Expand All @@ -68,6 +64,16 @@ argocd-autopilot project create [PROJECT] [flags]
--upsert Override an existing cluster with the same name even if the spec differs
```

### 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 project](argocd-autopilot_project.md) - Manage projects
Expand Down
Loading

0 comments on commit 611c58f

Please sign in to comment.