diff --git a/cmd/skpr/main.go b/cmd/skpr/main.go index 1d150f7..3d3a8bf 100644 --- a/cmd/skpr/main.go +++ b/cmd/skpr/main.go @@ -29,6 +29,7 @@ import ( "github.com/skpr/cli/cmd/skpr/logs" "github.com/skpr/cli/cmd/skpr/mysql" pkg "github.com/skpr/cli/cmd/skpr/package" + "github.com/skpr/cli/cmd/skpr/project" "github.com/skpr/cli/cmd/skpr/purge" "github.com/skpr/cli/cmd/skpr/release" "github.com/skpr/cli/cmd/skpr/restore" @@ -98,6 +99,7 @@ func main() { cmd.AddCommand(logs.NewCommand()) cmd.AddCommand(mysql.NewCommand()) cmd.AddCommand(pkg.NewCommand()) + cmd.AddCommand(project.NewCommand()) cmd.AddCommand(purge.NewCommand()) cmd.AddCommand(release.NewCommand()) cmd.AddCommand(restore.NewCommand()) diff --git a/cmd/skpr/project/command.go b/cmd/skpr/project/command.go new file mode 100644 index 0000000..26f5410 --- /dev/null +++ b/cmd/skpr/project/command.go @@ -0,0 +1,26 @@ +package project + +import ( + "github.com/spf13/cobra" + + "github.com/skpr/cli/cmd/skpr/project/get" + "github.com/skpr/cli/cmd/skpr/project/list" + "github.com/skpr/cli/cmd/skpr/project/set" + "github.com/skpr/cli/internal/command" +) + +// NewCommand creates a new cobra.Command for 'config' sub command +func NewCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "project", + DisableFlagsInUseLine: true, + Short: "Manage projects", + GroupID: command.GroupLifecycle, + } + + cmd.AddCommand(get.NewCommand()) + cmd.AddCommand(list.NewCommand()) + cmd.AddCommand(set.NewCommand()) + + return cmd +} diff --git a/cmd/skpr/project/get/command.go b/cmd/skpr/project/get/command.go new file mode 100644 index 0000000..f285235 --- /dev/null +++ b/cmd/skpr/project/get/command.go @@ -0,0 +1,30 @@ +package get + +import ( + "github.com/spf13/cobra" + + v1get "github.com/skpr/cli/internal/command/project/get" +) + +var ( + cmdExample = ` + # Get project details + skpr project get` +) + +// NewCommand creates a new cobra.Command for 'get' sub command +func NewCommand() *cobra.Command { + command := v1get.Command{} + + cmd := &cobra.Command{ + Use: "get", + DisableFlagsInUseLine: true, + Short: "Get contact for the current project.", + Example: cmdExample, + RunE: func(cmd *cobra.Command, args []string) error { + return command.Run(cmd.Context()) + }, + } + + return cmd +} diff --git a/cmd/skpr/project/list/command.go b/cmd/skpr/project/list/command.go new file mode 100644 index 0000000..9cb54e4 --- /dev/null +++ b/cmd/skpr/project/list/command.go @@ -0,0 +1,24 @@ +package list + +import ( + "github.com/spf13/cobra" + + v1list "github.com/skpr/cli/internal/command/project/list" +) + +// NewCommand creates a new cobra.Command for 'list' sub command +func NewCommand() *cobra.Command { + command := v1list.Command{} + + cmd := &cobra.Command{ + Use: "list", + Args: cobra.NoArgs, + DisableFlagsInUseLine: true, + Short: "Overview of all projects", + RunE: func(cmd *cobra.Command, args []string) error { + return command.Run(cmd.Context()) + }, + } + + return cmd +} diff --git a/cmd/skpr/project/set/command.go b/cmd/skpr/project/set/command.go new file mode 100644 index 0000000..a88c0e1 --- /dev/null +++ b/cmd/skpr/project/set/command.go @@ -0,0 +1,36 @@ +package set + +import ( + "github.com/spf13/cobra" + + v1set "github.com/skpr/cli/internal/command/project/set" +) + +var ( + cmdExample = ` + # Set the contact for the project. + skpr project set contact my-new-contact@example.com + + # Set the tags for the project. + skpr project set tags "tag-a tag-b tag-c"` +) + +// NewCommand creates a new cobra.Command for 'set' sub command +func NewCommand() *cobra.Command { + command := v1set.Command{} + + cmd := &cobra.Command{ + Use: "set ", + Args: cobra.ExactArgs(2), + DisableFlagsInUseLine: true, + Short: "Set an attribute for the current project.", + Example: cmdExample, + RunE: func(cmd *cobra.Command, args []string) error { + command.Key = args[0] + command.Value = args[1] + return command.Run(cmd.Context()) + }, + } + + return cmd +} diff --git a/internal/command/project/get/command.go b/internal/command/project/get/command.go new file mode 100644 index 0000000..9250cd4 --- /dev/null +++ b/internal/command/project/get/command.go @@ -0,0 +1,58 @@ +package get + +import ( + "context" + "io" + "os" + "strings" + + "github.com/skpr/api/pb" + + "github.com/skpr/cli/internal/client" + "github.com/skpr/cli/internal/project" + "github.com/skpr/cli/internal/table" +) + +// Command for getting a config. +type Command struct { +} + +// Run the command. +func (cmd *Command) Run(ctx context.Context) error { + ctx, client, err := client.New(ctx) + if err != nil { + return err + } + + resp, err := client.Project().Get(ctx, &pb.ProjectGetRequest{}) + if err != nil { + return err + } + + err = Print(os.Stdout, resp.Project) + if err != nil { + return err + } + + return nil +} + +// Print the table... +func Print(w io.Writer, item *pb.Project) error { + header := []string{ + "Attribute", + "Value", + } + + rows := [][]string{ + {"ID", item.ID}, + {"Name", item.Name}, + {"Contact", item.Contact}, + {"Version", item.Version}, + {"Environments", strings.Join(project.ListEnvironmentsByName(item), ", ")}, + {"Size", item.Size}, + {"Tags", strings.Join(item.Tags, ", ")}, + } + + return table.Print(w, header, rows) +} diff --git a/internal/command/project/list/command.go b/internal/command/project/list/command.go new file mode 100644 index 0000000..c34c858 --- /dev/null +++ b/internal/command/project/list/command.go @@ -0,0 +1,80 @@ +package set + +import ( + "context" + "fmt" + "io" + "os" + "sort" + "strings" + + "github.com/pkg/errors" + "github.com/skpr/api/pb" + + "github.com/skpr/cli/internal/client" + "github.com/skpr/cli/internal/project" + "github.com/skpr/cli/internal/table" +) + +// Command for setting config. +type Command struct { + Key string + Value string +} + +// Run the command. +func (cmd *Command) Run(ctx context.Context) error { + ctx, client, err := client.New(ctx) + if err != nil { + return err + } + + resp, err := client.Project().List(ctx, &pb.ProjectListRequest{}) + if err != nil { + return errors.Wrap(err, "Could not list projects") + } + + return Print(os.Stdout, resp.Projects) +} + +// Print the table... +func Print(w io.Writer, list []*pb.Project) error { + if len(list) == 0 { + fmt.Fprintln(w, "No projects found") + return nil + } + + sortProjects(list) + + header := []string{ + "Name", + "Contact", + "Version", + "Environments", + "Size", + "Tags", + } + + var rows [][]string + + for _, item := range list { + rows = append(rows, []string{ + item.Name, + item.Contact, + item.Version, + strings.Join(project.ListEnvironmentsByName(item), ", "), + item.Size, + strings.Join(item.Tags, ", "), + }) + } + + return table.Print(w, header, rows) +} + +// sortEnvs sorts the list putting the production envs last. +func sortProjects(list []*pb.Project) { + // Ensure prod environments are listed last. + sort.Slice(list, func(i, j int) bool { + return list[i].Name < list[j].Name + }) +} diff --git a/internal/command/project/set/command.go b/internal/command/project/set/command.go new file mode 100644 index 0000000..ce3dd54 --- /dev/null +++ b/internal/command/project/set/command.go @@ -0,0 +1,48 @@ +package set + +import ( + "context" + "fmt" + "strings" + + "github.com/skpr/api/pb" + + "github.com/skpr/cli/internal/client" +) + +// Command for setting config. +type Command struct { + Key string + Value string +} + +// Run the command. +func (cmd *Command) Run(ctx context.Context) error { + ctx, client, err := client.New(ctx) + if err != nil { + return err + } + + switch cmd.Key { + case "contact": + req := &pb.SetContactRequest{ + Contact: cmd.Value, + } + _, err = client.Project().SetContact(ctx, req) + if err != nil { + return err + } + case "tags": + req := &pb.SetTagsRequest{ + Tags: strings.Fields(cmd.Value), + } + _, err = client.Project().SetTags(ctx, req) + if err != nil { + return err + } + default: + return fmt.Errorf("invalid key. Currently supported keys are: contact, tags") + } + + return nil +} diff --git a/internal/project/project.go b/internal/project/project.go new file mode 100644 index 0000000..2dce99f --- /dev/null +++ b/internal/project/project.go @@ -0,0 +1,19 @@ +package project + +import ( + "sort" + + "github.com/skpr/api/pb" +) + +// ListEnvironmentsByName lists them in printable order. +func ListEnvironmentsByName(project *pb.Project) []string { + environments := []string{ + project.Environments.Prod, + } + + sort.Strings(project.Environments.NonProd) + environments = append(environments, project.Environments.NonProd...) + + return environments +} diff --git a/internal/project/project_test.go b/internal/project/project_test.go new file mode 100644 index 0000000..f07795a --- /dev/null +++ b/internal/project/project_test.go @@ -0,0 +1,27 @@ +package project + +import ( + "testing" + + "github.com/skpr/api/pb" + "github.com/stretchr/testify/assert" +) + +func TestSort(t *testing.T) { + item := pb.Project{ + Environments: &pb.ProjectEnvironments{ + Prod: "prod", + NonProd: []string{ + "dev", + "training", + "stg", + "bvt", + }, + }, + } + + out := ListEnvironmentsByName(&item) + expected := []string{"prod", "bvt", "dev", "stg", "training"} + + assert.Equal(t, expected, out) +}