Skip to content
4 changes: 4 additions & 0 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ on:
- main
pull_request:

concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true

permissions:
contents: read

Expand Down
4 changes: 4 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ on:
- main
pull_request:

concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true

jobs:
test:
runs-on: ubuntu-latest
Expand Down
2 changes: 2 additions & 0 deletions cmd/skpr/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,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"
Expand Down Expand Up @@ -108,6 +109,7 @@ func main() {
cmd.AddCommand(logs.NewCommand())
cmd.AddCommand(mysql.NewCommand(featureFlags.DockerClient))
cmd.AddCommand(pkg.NewCommand(featureFlags.DockerClient))
cmd.AddCommand(project.NewCommand())
cmd.AddCommand(purge.NewCommand())
cmd.AddCommand(release.NewCommand())
cmd.AddCommand(restore.NewCommand())
Expand Down
26 changes: 26 additions & 0 deletions cmd/skpr/project/command.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package project

import (
"github.com/spf13/cobra"

"github.com/skpr/cli/cmd/skpr/project/info"
"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 the top-level 'project' command
func NewCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "project",
DisableFlagsInUseLine: true,
Short: "Manage projects",
GroupID: command.GroupLifecycle,
}

cmd.AddCommand(info.NewCommand())
cmd.AddCommand(list.NewCommand())
cmd.AddCommand(set.NewCommand())

return cmd
}
31 changes: 31 additions & 0 deletions cmd/skpr/project/info/command.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package info

import (
"github.com/spf13/cobra"

v1get "github.com/skpr/cli/internal/command/project/info"
)

var (
cmdExample = `
# Get project information
skpr project info`
)

// NewCommand creates a new cobra.Command for 'info' sub command
func NewCommand() *cobra.Command {
command := v1get.Command{}

cmd := &cobra.Command{
Use: "info",
Args: cobra.NoArgs,
DisableFlagsInUseLine: true,
Short: "Get full details for the current project.",
Example: cmdExample,
RunE: func(cmd *cobra.Command, args []string) error {
return command.Run(cmd.Context())
},
}
Comment thread
nterbogt marked this conversation as resolved.

return cmd
}
24 changes: 24 additions & 0 deletions cmd/skpr/project/list/command.go
Original file line number Diff line number Diff line change
@@ -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
}
36 changes: 36 additions & 0 deletions cmd/skpr/project/set/command.go
Original file line number Diff line number Diff line change
@@ -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 <key> <value>",
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
}
58 changes: 58 additions & 0 deletions internal/command/project/info/command.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package info

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 fetching and printing project details.
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{
"Property",
"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)
}
77 changes: 77 additions & 0 deletions internal/command/project/list/command.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package list

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 listing projects.
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().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)
}

// sortProjects sorts the list of projects by name in ascending order.
func sortProjects(list []*pb.Project) {
sort.Slice(list, func(i, j int) bool {
return list[i].Name < list[j].Name
})
}
48 changes: 48 additions & 0 deletions internal/command/project/set/command.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package set

import (
"context"
"fmt"
"strings"

"github.com/skpr/api/pb"

"github.com/skpr/cli/internal/client"
)

// Command for setting project info.
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
}
20 changes: 20 additions & 0 deletions internal/project/project.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
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,
}

nonProd := append([]string(nil), project.Environments.NonProd...)
sort.Strings(nonProd)
environments = append(environments, nonProd...)

return environments
}
27 changes: 27 additions & 0 deletions internal/project/project_test.go
Original file line number Diff line number Diff line change
@@ -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)
}