Skip to content

Commit

Permalink
feat: allow creating a workspace together with a project (#392)
Browse files Browse the repository at this point in the history
  • Loading branch information
zepatrik authored Nov 4, 2024
1 parent 43ed6ec commit 90ba715
Show file tree
Hide file tree
Showing 7 changed files with 136 additions and 35 deletions.
24 changes: 12 additions & 12 deletions cmd/cloudx/client/command_helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"bufio"
"context"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
Expand All @@ -16,7 +17,6 @@ import (

"github.com/gofrs/uuid"
"github.com/pkg/browser"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/tidwall/gjson"

Expand Down Expand Up @@ -209,7 +209,7 @@ func NewCommandHelper(ctx context.Context, opts ...CommandHelperOption) (*Comman
func (h *CommandHelper) determineWorkspaceID(ctx context.Context, config *Config) error {
if h.workspaceAPIKey != nil {
if h.workspaceOverride != nil {
return errors.New("workspace API key is set but workspace flag is also set, please remove one")
return fmt.Errorf("workspace API key is set but workspace flag is also set, please remove one")
}
ws, err := h.ListWorkspaces(ctx)
if err != nil {
Expand All @@ -222,7 +222,7 @@ func (h *CommandHelper) determineWorkspaceID(ctx context.Context, config *Config
}
return nil
}
return errors.New("workspace API key is set but no workspaces were found")
return fmt.Errorf("workspace API key is set but no workspaces were found")
}

workspace := ""
Expand Down Expand Up @@ -260,10 +260,10 @@ func (h *CommandHelper) determineWorkspaceID(ctx context.Context, config *Config
func (h *CommandHelper) determineProjectID(ctx context.Context, config *Config) error {
if h.projectAPIKey != nil {
if h.projectOverride != nil {
return errors.New("project API key is set but project flag is also set, please remove one")
return fmt.Errorf("project API key is set but project flag is also set, please remove one")
}
if h.workspaceID != uuid.Nil {
return errors.New("project API key is set but workspace is also set, please remove one")
return fmt.Errorf("project API key is set but workspace is also set, please remove one")
}
pjs, err := h.ListProjects(ctx, nil)
if err != nil {
Expand All @@ -276,7 +276,7 @@ func (h *CommandHelper) determineProjectID(ctx context.Context, config *Config)
}
return nil
}
return errors.New("project API key is set but no projects were found")
return fmt.Errorf("project API key is set but no projects were found")
}

project := ""
Expand Down Expand Up @@ -346,29 +346,29 @@ func (h *CommandHelper) OpenURL(uri string) error {

func handleError(message string, res *http.Response, err error) error {
if e := new(cloud.GenericOpenAPIError); errors.As(err, &e) {
return errors.Wrapf(err, "%s: %s", message, e.Body())
return fmt.Errorf("%s: %s: %w", message, e.Body(), err)
}

if res == nil {
return errors.Wrapf(err, "%s", message)
return fmt.Errorf("%s: %w", message, err)
}

body, _ := io.ReadAll(res.Body)
return errors.Wrapf(err, "%s: %s", message, body)
return fmt.Errorf("%s: %s: %w", message, body, err)
}

func toPatch(op string, values []string) (patches []cloud.JsonPatch, err error) {
for _, v := range values {
path, value, found := strings.Cut(v, "=")
if !found {
return nil, errors.Errorf("patches must be in format of `/some/config/key=some-value` but got: %s", v)
return nil, fmt.Errorf("patches must be in format of `/some/config/key=some-value` but got: %s", v)
} else if !gjson.Valid(value) {
return nil, errors.Errorf("value for %s must be valid JSON but got: %s", path, value)
return nil, fmt.Errorf("value for %s must be valid JSON but got: %s", path, value)
}

config, err := jsonx.EmbedSources(json.RawMessage(value), jsonx.WithIgnoreKeys("$id", "$schema"), jsonx.WithOnlySchemes("file"))
if err != nil {
return nil, errors.WithStack(err)
return nil, err
}

patches = append(patches, cloud.JsonPatch{Op: op, Path: path, Value: config})
Expand Down
2 changes: 1 addition & 1 deletion cmd/cloudx/client/project.go
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ func (h *CommandHelper) CreateProject(ctx context.Context, name, environment str
WorkspaceId: workspace,
}).Execute()
if err != nil {
return nil, handleError("unable to list projects", res, err)
return nil, handleError("unable to create project", res, err)
}

if setDefault || h.projectID == uuid.Nil {
Expand Down
13 changes: 13 additions & 0 deletions cmd/cloudx/client/workspace.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,3 +69,16 @@ func (h *CommandHelper) CreateWorkspace(ctx context.Context, name string) (*clou
}
return workspace, nil
}

func (h *CommandHelper) GetWorkspace(ctx context.Context, id string) (*cloud.Workspace, error) {
c, err := h.newConsoleAPIClient(ctx)
if err != nil {
return nil, err
}

workspace, res, err := c.WorkspaceAPI.GetWorkspace(ctx, id).Execute()
if err != nil {
return nil, handleError("unable to get workspace", res, err)
}
return workspace, nil
}
6 changes: 4 additions & 2 deletions cmd/cloudx/get.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@ package cloudx
import (
"github.com/spf13/cobra"

"github.com/ory/cli/cmd/cloudx/oauth2"
"github.com/ory/x/cmdx"

"github.com/ory/cli/cmd/cloudx/client"
"github.com/ory/cli/cmd/cloudx/identity"
"github.com/ory/cli/cmd/cloudx/oauth2"
"github.com/ory/cli/cmd/cloudx/project"
"github.com/ory/x/cmdx"
"github.com/ory/cli/cmd/cloudx/workspace"
)

func NewGetCmd() *cobra.Command {
Expand All @@ -25,6 +26,7 @@ func NewGetCmd() *cobra.Command {
project.NewGetKratosConfigCmd(),
project.NewGetKetoConfigCmd(),
project.NewGetOAuth2ConfigCmd(),
workspace.NewGetCmd(),
identity.NewGetIdentityCmd(),
oauth2.NewGetOAuth2Client(),
oauth2.NewGetJWK(),
Expand Down
42 changes: 32 additions & 10 deletions cmd/cloudx/project/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,28 @@ package project
import (
"fmt"

"github.com/ory/cli/cmd/cloudx/client"

"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/pflag"

"github.com/ory/cli/cmd/cloudx/client"

"github.com/ory/x/cmdx"
"github.com/ory/x/flagx"
"github.com/ory/x/pointerx"
"github.com/ory/x/stringsx"
)

const (
nameFlag = "name"
environmentFlag = "environment"
useProjectFlag = "use-project"
nameFlag = "name"
createWorkspaceFlag = "create-workspace"
environmentFlag = "environment"
useProjectFlag = "use-project"
)

func NewCreateProjectCmd() *cobra.Command {
name := ""
createWorkspace := ""
environment := environmentValue("dev")
useProject := false

Expand All @@ -39,17 +42,35 @@ func NewCreateProjectCmd() *cobra.Command {
return err
}

isQuiet := flagx.MustGetBool(cmd, cmdx.FlagQuiet)

wsID := h.WorkspaceID()
if wsID == nil {
return errors.New("a workspace is required to create a project")
if wsID == nil && createWorkspace == "" && isQuiet {
return errors.New("no workspace found, you must specify the --workspace or --create-workspace flag to create a project when using --quiet")
}

if len(name) == 0 && flagx.MustGetBool(cmd, cmdx.FlagQuiet) {
if name == "" && isQuiet {
return errors.New("you must specify the --name flag when using --quiet")
}

for wsID == nil && createWorkspace == "" {
_, _ = fmt.Fprint(cmd.ErrOrStderr(), "It seems like you do not have a workspace yet.\nEnter a name for the workspace: ")
createWorkspace, err = h.Stdin.ReadString('\n')
if err != nil {
return errors.Wrap(err, "failed to read from stdin")
}
}

if createWorkspace != "" {
ws, err := h.CreateWorkspace(ctx, createWorkspace)
if err != nil {
return cmdx.PrintOpenAPIError(cmd, err)
}
wsID = pointerx.Ptr(ws.Id)
}

for name == "" {
_, _ = fmt.Fprintf(cmd.ErrOrStderr(), "Enter a name for your project: ")
_, _ = fmt.Fprint(cmd.ErrOrStderr(), "Enter a name for your project: ")
name, err = h.Stdin.ReadString('\n')
if err != nil {
return errors.Wrap(err, "failed to read from stdin")
Expand All @@ -69,7 +90,8 @@ func NewCreateProjectCmd() *cobra.Command {

cmd.Flags().StringVarP(&name, nameFlag, "n", "", "The name of the project, required when quiet mode is used")
cmd.Flags().VarP(&environment, environmentFlag, "e", "The environment of the project. Valid values are: prod, stage, dev")
cmd.Flags().BoolVar(&useProject, useProjectFlag, false, "Set the created project as the default.")
cmd.Flags().BoolVar(&useProject, useProjectFlag, false, "Set the created project as the default")
cmd.Flags().StringVar(&createWorkspace, createWorkspaceFlag, "", "Create a new workspace with the given name and use it for the project")
client.RegisterWorkspaceFlag(cmd.Flags())
cmdx.RegisterFormatFlags(cmd.Flags())
return cmd
Expand Down
48 changes: 38 additions & 10 deletions cmd/cloudx/project/create_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@ func TestCreateProject(t *testing.T) {
t.Parallel()

parseOutput := func(stdout string) (id string, slug string, name string) {
id = gjson.Get(stdout, "id").String()
slug = gjson.Get(stdout, "slug").String()
name = gjson.Get(stdout, "name").String()
id = gjson.Get(stdout, "id").Str
slug = gjson.Get(stdout, "slug").Str
name = gjson.Get(stdout, "name").Str
return
}
assertResult := func(t *testing.T, configDir string, stdout string, expectedName string) (id, slug, name string) {
Expand All @@ -37,7 +37,7 @@ func TestCreateProject(t *testing.T) {
return
}

t.Run("requires workspace and environment", func(t *testing.T) {
t.Run("requires workspace", func(t *testing.T) {
t.Parallel()

ctx, newConfig := testhelpers.WithDuplicatedConfigFile(ctx, t, defaultConfig)
Expand All @@ -47,18 +47,46 @@ func TestCreateProject(t *testing.T) {
require.NoError(t, err)
require.NoError(t, os.WriteFile(newConfig, rawConfig, 0600))

for _, extraArgs := range [][]string{
{},
{"--environment", "dev"},
for _, tc := range []struct {
expectedErr string
extraArgs []string
}{
{
expectedErr: "no workspace found",
extraArgs: nil,
},
{
expectedErr: "The requested action was forbidden", // because the workspace does not exist we get a permission check error
extraArgs: []string{"--workspace", uuid.Must(uuid.NewV4()).String()},
},
} {
t.Run("args="+strings.Join(extraArgs, " "), func(t *testing.T) {
_, stderr, err := testhelpers.Cmd(ctx).Exec(nil, append([]string{"create", "project", "--name", testhelpers.TestName(), "--format", "json"}, extraArgs...)...)
t.Run("args="+strings.Join(tc.extraArgs, " "), func(t *testing.T) {
_, stderr, err := testhelpers.Cmd(ctx).Exec(nil, append([]string{"create", "project", "--name", testhelpers.TestName(), "--format", "json", "--quiet"}, tc.extraArgs...)...)
require.Error(t, err)
assert.Contains(t, stderr, "a workspace is required to create a project")
assert.Contains(t, stderr, tc.expectedErr)
})
}
})

t.Run("is able to create a project with a workspace", func(t *testing.T) {
t.Parallel()

ctx, _ := testhelpers.WithDuplicatedConfigFile(ctx, t, defaultConfig)
testhelpers.SetDefaultProject(ctx, t, defaultProject.Id)

name := testhelpers.TestName()
pjRaw, stderr, err := testhelpers.Cmd(ctx).Exec(nil, "create", "project", "--name", name, "--create-workspace", "My new workspace", "--environment", "dev", "--format", "json")
require.NoErrorf(t, err, "%s", stderr)
assertResult(t, defaultConfig, pjRaw, name)

wsID := gjson.Get(pjRaw, "workspace_id").Str
require.NotZerof(t, wsID, "%s", pjRaw)

wsRaw := testhelpers.Cmd(ctx).ExecNoErr(t, "get", "workspace", wsID, "--format", "json")
assert.Equalf(t, wsID, gjson.Get(wsRaw, "id").Str, "%s", wsRaw)
assert.Equalf(t, "My new workspace", gjson.Get(wsRaw, "name").Str, "%s", wsRaw)
})

t.Run("is able to create a project", func(t *testing.T) {
t.Parallel()

Expand Down
36 changes: 36 additions & 0 deletions cmd/cloudx/workspace/get.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// Copyright © 2024 Ory Corp
// SPDX-License-Identifier: Apache-2.0

package workspace

import (
"github.com/spf13/cobra"

"github.com/ory/cli/cmd/cloudx/client"
"github.com/ory/x/cmdx"
)

func NewGetCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "workspace <id>",
Aliases: []string{"workspaces", "ws"},
Short: "Get an Ory Network workspaces",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
h, err := client.NewCobraCommandHelper(cmd)
if err != nil {
return err
}

workspace, err := h.GetWorkspace(cmd.Context(), args[0])
if err != nil {
return err
}

cmdx.PrintRow(cmd, (*outputWorkspace)(workspace))
return nil
},
}
cmdx.RegisterFormatFlags(cmd.Flags())
return cmd
}

0 comments on commit 90ba715

Please sign in to comment.