Skip to content

Commit

Permalink
feat: add project list command (#1295)
Browse files Browse the repository at this point in the history
  • Loading branch information
Yangyang96 authored Sep 24, 2024
1 parent 67d4ab5 commit 74acaa7
Show file tree
Hide file tree
Showing 23 changed files with 1,054 additions and 6 deletions.
3 changes: 3 additions & 0 deletions pkg/backend/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ type Backend interface {

// StateStorageWithPath returns the state storage with the specified path.
StateStorageWithPath(path string) (release.Storage, error)

// ProjectStorage returns the project directory under release folder.
ProjectStorage() (map[string][]string, error)
}

// NewBackend creates the Backend with the configuration set in the Kusion configuration file, where the input
Expand Down
5 changes: 5 additions & 0 deletions pkg/backend/storages/local.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
v1 "kusionstack.io/kusion/pkg/apis/api.kusion.io/v1"
"kusionstack.io/kusion/pkg/engine/release"
releasestorages "kusionstack.io/kusion/pkg/engine/release/storages"
projectstorages "kusionstack.io/kusion/pkg/project/storages"
"kusionstack.io/kusion/pkg/workspace"
workspacestorages "kusionstack.io/kusion/pkg/workspace/storages"
)
Expand All @@ -30,3 +31,7 @@ func (s *LocalStorage) ReleaseStorage(project, workspace string) (release.Storag
func (s *LocalStorage) StateStorageWithPath(path string) (release.Storage, error) {
return releasestorages.NewLocalStorage(releasestorages.GenReleasePrefixKeyWithPath(s.path, path))
}

func (s *LocalStorage) ProjectStorage() (map[string][]string, error) {
return projectstorages.NewLocalStorage(projectstorages.GenProjectDirPath(s.path)).Get()
}
5 changes: 5 additions & 0 deletions pkg/backend/storages/oss.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
v1 "kusionstack.io/kusion/pkg/apis/api.kusion.io/v1"
"kusionstack.io/kusion/pkg/engine/release"
releasestorages "kusionstack.io/kusion/pkg/engine/release/storages"
projectstorages "kusionstack.io/kusion/pkg/project/storages"
"kusionstack.io/kusion/pkg/workspace"
workspacestorages "kusionstack.io/kusion/pkg/workspace/storages"
)
Expand Down Expand Up @@ -42,3 +43,7 @@ func (s *OssStorage) ReleaseStorage(project, workspace string) (release.Storage,
func (s *OssStorage) StateStorageWithPath(path string) (release.Storage, error) {
return releasestorages.NewOssStorage(s.bucket, releasestorages.GenReleasePrefixKeyWithPath(s.prefix, path))
}

func (s *OssStorage) ProjectStorage() (map[string][]string, error) {
return projectstorages.NewOssStorage(s.bucket, projectstorages.GenGenericOssReleasePrefixKey(s.prefix)).Get()
}
5 changes: 5 additions & 0 deletions pkg/backend/storages/s3.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
v1 "kusionstack.io/kusion/pkg/apis/api.kusion.io/v1"
"kusionstack.io/kusion/pkg/engine/release"
releasestorages "kusionstack.io/kusion/pkg/engine/release/storages"
projectstorages "kusionstack.io/kusion/pkg/project/storages"
"kusionstack.io/kusion/pkg/workspace"
workspacestorages "kusionstack.io/kusion/pkg/workspace/storages"
)
Expand Down Expand Up @@ -55,3 +56,7 @@ func (s *S3Storage) ReleaseStorage(project, workspace string) (release.Storage,
func (s *S3Storage) StateStorageWithPath(path string) (release.Storage, error) {
return releasestorages.NewS3Storage(s.s3, s.bucket, releasestorages.GenReleasePrefixKeyWithPath(s.prefix, path))
}

func (s *S3Storage) ProjectStorage() (map[string][]string, error) {
return projectstorages.NewS3Storage(s.s3, s.bucket, projectstorages.GenGenericOssReleasePrefixKey(s.prefix)).Get()
}
4 changes: 3 additions & 1 deletion pkg/cmd/project/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"github.com/spf13/cobra"
"k8s.io/kubectl/pkg/util/templates"
"kusionstack.io/kusion/pkg/cmd/project/create"
"kusionstack.io/kusion/pkg/cmd/project/list"
"kusionstack.io/kusion/pkg/util/i18n"
)

Expand All @@ -28,7 +29,8 @@ func NewCmd() *cobra.Command {
}

createCmd := create.NewCmd()
cmd.AddCommand(createCmd)
listCmd := list.NewCmd()
cmd.AddCommand(createCmd, listCmd)

return cmd
}
53 changes: 53 additions & 0 deletions pkg/cmd/project/list/cmd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package list

import (
"github.com/spf13/cobra"
"k8s.io/kubectl/pkg/util/templates"
cmdutil "kusionstack.io/kusion/pkg/cmd/util"
"kusionstack.io/kusion/pkg/util/i18n"
)

// NewCmd creates the `list` command.
func NewCmd() *cobra.Command {
var (
short = i18n.T(`List the applied projects`)

long = i18n.T(`
This command lists all the applied projects in the target backend and target workspace.
By default list the projects in the current backend and current workspace.`)

example = i18n.T(`
# List the applied project in the current backend and current workspace
kusion project list
# List the applied project in a specified backend and current workspace
kusion project list --backend default
# List the applied project in a specified backend and specified workspaces
kusion project list --backend default --workspace dev,default
# List the applied project in a specified backend and all the workspaces
kusion project list --backend default --all`)
)

flags := NewFlags()
cmd := &cobra.Command{
Use: "list",
Short: short,
Long: templates.LongDesc(long),
Example: templates.Examples(example),
DisableFlagsInUseLine: true,
RunE: func(cmd *cobra.Command, args []string) (err error) {
o, err := flags.ToOptions()
defer cmdutil.RecoverErr(&err)
cmdutil.CheckErr(err)
cmdutil.CheckErr(o.Validate(cmd, args))
cmdutil.CheckErr(o.Run())

return
},
}
flags.AddFlags(cmd)

return cmd
}
28 changes: 28 additions & 0 deletions pkg/cmd/project/list/cmd_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package list

import (
"testing"

"github.com/spf13/cobra"
)

func TestNewCmd(t *testing.T) {
tests := []struct {
name string
want *cobra.Command
}{
{
name: "Create new command Successfully",
want: &cobra.Command{Use: "list", Short: "List the applied projects"},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Call the actual NewCmd function
cmd := NewCmd()

cmd.Execute()
})
}
}
187 changes: 187 additions & 0 deletions pkg/cmd/project/list/options.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
package list

import (
"errors"
"fmt"
"strings"

"github.com/spf13/cobra"
"kusionstack.io/kusion/pkg/backend"
cmdutil "kusionstack.io/kusion/pkg/cmd/util"
"kusionstack.io/kusion/pkg/util/i18n"
"kusionstack.io/kusion/pkg/util/pretty"
)

var ErrNotEmptyArgs = errors.New("no args accepted")

// Options defines the configurations for the `list` command.
// Options reflects the runtime requirements for the `list` command.
type Options struct {
projects map[string][]string
Workspace []string
CurrentWorkspace string
}

// Flags defines the flags for the `list` command.
// Flags reflects the information gathered by the `list` command.
type Flags struct {
Backend *string
Workspace *[]string
All bool
}

// NewFlags returns a new Flags with default values.
func NewFlags() *Flags {
backend := ""
workspace := []string{}
all := false

return &Flags{
Backend: &backend,
Workspace: &workspace,
All: all,
}
}

// AddFlags registers flags for the `list` command.
func (f *Flags) AddFlags(cmd *cobra.Command) {
if f.Workspace != nil {
cmd.Flags().StringSliceVarP(f.Workspace, "workspace", "", []string{}, i18n.T("The name of the target workspace"))
}
if f.Backend != nil {
cmd.Flags().StringVarP(f.Backend, "backend", "", "", i18n.T("The backend to use, supports 'local', 'oss' and 's3'"))
}

cmd.Flags().BoolVarP(&f.All, "all", "a", false, i18n.T("List all the projects in all the workspaces"))
}

// ToOptions converts the Flags to the Options.
func (f *Flags) ToOptions() (*Options, error) {
var storageBackend backend.Backend
var err error
// Get backend storage.
if f.Backend != nil && *f.Backend != "" {
storageBackend, err = backend.NewBackend(*f.Backend)
if err != nil {
return nil, err
}
} else {
storageBackend, err = backend.NewBackend("")
if err != nil {
return nil, err
}
}

workspaceName := ""
workspaces := []string{}
projects, err := storageBackend.ProjectStorage()
if err != nil {
return nil, err
}
workspaceStorage, err := storageBackend.WorkspaceStorage()
if err != nil {
return nil, err
}
currentWorkspaceName, err := workspaceStorage.GetCurrent()
if err != nil {
return nil, err
}

// Get all the available workspaces if needed.
if f.All {
workspaceNames, err := workspaceStorage.GetNames()
if err != nil {
return nil, err
}
workspaces = append(workspaces, workspaceNames...)
} else {
// Get the specified workspaces.
if len(*f.Workspace) != 0 {
for _, workspace := range *f.Workspace {
if workspace != "" {
refWorkspace, err := workspaceStorage.Get(workspace)
if err != nil {
return nil, err
}
workspaceName = refWorkspace.Name
workspaces = append(workspaces, workspaceName)
}
}
// No workspace specified, use the current workspace.
} else {
workspaceName = currentWorkspaceName
workspaces = append(workspaces, workspaceName)
}
}

return &Options{
projects: projects,
Workspace: workspaces,
CurrentWorkspace: currentWorkspaceName,
}, err
}

// Validate checks the options to see if they are valid.
func (o *Options) Validate(cmd *cobra.Command, args []string) error {
if len(args) != 0 {
return cmdutil.UsageErrorf(cmd, "Unexpected args: %v", args)
}

return nil
}

// Run executes the `list` command.
func (o *Options) Run() error {
if o.Workspace == nil || len(o.Workspace) == 0 {
return fmt.Errorf("workspace is empty")
}

if len(o.projects) == 0 {
fmt.Println("No projects found")
return nil
}

// Function to print each workspace and its projects.
printProjects := func(workspace string, isCurrent bool) {
if isCurrent {
fmt.Println(pretty.GreenBold("Current Workspace: %s", workspace))
} else {
fmt.Printf("Workspace: %s\n", workspace)
}

projects, exists := o.projects[workspace]
if !exists || len(projects) == 0 {
fmt.Println(" No projects found")
} else {
// Print each project
for _, project := range projects {
fmt.Printf(" - %s\n", project)
}
}

fmt.Println(strings.Repeat("-", 40))
}

// Check if CurrentWorkspace exists in the list.
currentWorkspaceExists := false
for _, workspace := range o.Workspace {
if workspace == o.CurrentWorkspace {
currentWorkspaceExists = true
break
}
}

// If CurrentWorkspace exists, print it first.
if currentWorkspaceExists {
printProjects(o.CurrentWorkspace, true)
}

// Print the other workspaces.
for _, workspace := range o.Workspace {
if workspace != o.CurrentWorkspace {
printProjects(workspace, false)
}
}

return nil
}
Loading

0 comments on commit 74acaa7

Please sign in to comment.