Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ require (
github.com/AlecAivazis/survey/v2 v2.3.7
github.com/MakeNowJust/heredoc/v2 v2.0.1
github.com/OctopusDeploy/go-octodiff v1.0.0
github.com/OctopusDeploy/go-octopusdeploy/v2 v2.84.2
github.com/OctopusDeploy/go-octopusdeploy/v2 v2.85.2-0.20251102214655-01f4f4d03e21
github.com/bmatcuk/doublestar/v4 v4.4.0
github.com/briandowns/spinner v1.19.0
github.com/google/uuid v1.3.0
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ github.com/OctopusDeploy/go-octodiff v1.0.0 h1:U+ORg6azniwwYo+O44giOw6TiD5USk8S4
github.com/OctopusDeploy/go-octodiff v1.0.0/go.mod h1:Mze0+EkOWTgTmi8++fyUc6r0aLZT7qD9gX+31t8MmIU=
github.com/OctopusDeploy/go-octopusdeploy/v2 v2.84.2 h1:dBTkP2Uxxn/gGOC0i0RPQzyJOqmL1Sv/ntja2u6Jhwo=
github.com/OctopusDeploy/go-octopusdeploy/v2 v2.84.2/go.mod h1:VkTXDoIPbwGFi5+goo1VSwFNdMVo784cVtJdKIEvfus=
github.com/OctopusDeploy/go-octopusdeploy/v2 v2.85.2-0.20251102214655-01f4f4d03e21 h1:8YVMZEu/U8sLWuI1M76e1esEr9yz4ctK379o4O8oH2M=
github.com/OctopusDeploy/go-octopusdeploy/v2 v2.85.2-0.20251102214655-01f4f4d03e21/go.mod h1:VkTXDoIPbwGFi5+goo1VSwFNdMVo784cVtJdKIEvfus=
github.com/bmatcuk/doublestar/v4 v4.4.0 h1:LmAwNwhjEbYtyVLzjcP/XeVw4nhuScHGkF/XWXnvIic=
github.com/bmatcuk/doublestar/v4 v4.4.0/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
github.com/briandowns/spinner v1.19.0 h1:s8aq38H+Qju89yhp89b4iIiMzMm8YN3p6vGpwyh/a8E=
Expand Down
126 changes: 126 additions & 0 deletions pkg/cmd/ephemeralenvironment/create/create.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
package create

import (
"fmt"

"github.com/MakeNowJust/heredoc/v2"
"github.com/OctopusDeploy/cli/pkg/cmd"
"github.com/OctopusDeploy/cli/pkg/cmd/runbook/shared"
"github.com/OctopusDeploy/cli/pkg/constants"
"github.com/OctopusDeploy/cli/pkg/factory"
"github.com/OctopusDeploy/cli/pkg/output"
"github.com/OctopusDeploy/cli/pkg/question"
"github.com/OctopusDeploy/cli/pkg/question/selectors"
"github.com/OctopusDeploy/cli/pkg/util/flag"
"github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/environments/v2/ephemeralenvironments"
"github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/projects"
"github.com/spf13/cobra"
)

const (
FlagName = "name"
FlagProject = "project"
)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need for a --space flag as this is automatically added to the command.


type CreateFlags struct {
Name *flag.Flag[string]
Project *flag.Flag[string]
}

func NewCreateFlags() *CreateFlags {
return &CreateFlags{
Name: flag.New[string](FlagName, false),
Project: flag.New[string](FlagProject, false),
}
}

type CreateOptions struct {
*CreateFlags
*cmd.Dependencies
GetAllProjectsCallback func() ([]*projects.Project, error)
}

func NewCreateOptions(createFlags *CreateFlags, dependencies *cmd.Dependencies) *CreateOptions {
return &CreateOptions{
CreateFlags: createFlags,
Dependencies: dependencies,
GetAllProjectsCallback: func() ([]*projects.Project, error) { return shared.GetAllProjects(dependencies.Client) },
}
}

func NewCmdCreate(f factory.Factory) *cobra.Command {
createFlags := NewCreateFlags()

cmd := &cobra.Command{
Use: "create",
Short: "Create an ephemeral environment",
Long: "Create an ephemeral environment in Octopus Deploy",
Example: heredoc.Docf("$ %s ephemeralenvironment create", constants.ExecutableName),
Aliases: []string{"new"},
RunE: func(c *cobra.Command, _ []string) error {
opts := NewCreateOptions(createFlags, cmd.NewDependencies(f, c))

return createRun(opts)
},
}

flags := cmd.Flags()
flags.StringVarP(&createFlags.Name.Value, createFlags.Name.Name, "n", "", "Name of the environment")
flags.StringVarP(&createFlags.Project.Value, createFlags.Project.Name, "p", "", "Name of the project")

return cmd
}

func createRun(opts *CreateOptions) error {

if !opts.NoPrompt {
err := PromptMissing(opts)
if err != nil {
return err
}
}

projectResource, err := projects.GetByName(opts.Client, opts.Space.ID, opts.Project.Value)
if err != nil {
return fmt.Errorf("failed to find project '%s': %w", opts.Project.Value, err)
}
projectId := projectResource.GetID()

createEnv, err := ephemeralenvironments.Add(opts.Client, opts.Space.ID, projectId, opts.Name.Value)
if err != nil {
return err
}

_, err = fmt.Fprintf(opts.Out, "\nSuccessfully created ephemeral environment '%s` with id '%s'.\n", opts.Name.Value, createEnv.Id)
if err != nil {
return err
}

link := output.Bluef("%s/app#/%s/projects/%s/ephemeral-environments", opts.Host, opts.Space.GetID(), projectId)
fmt.Fprintf(opts.Out, "View this ephemeral environments for project `%s` on Octopus Deploy: %s\n", opts.Project.Value, link)

if !opts.NoPrompt {
autoCmd := flag.GenerateAutomationCmd(opts.CmdPath, opts.Name, opts.Project)
fmt.Fprintf(opts.Out, "%s\n", autoCmd)
}

return nil
}

func PromptMissing(opts *CreateOptions) error {

err := question.AskName(opts.Ask, "", "ephemeral environment", &opts.Name.Value)
if err != nil {
return err
}

if opts.Project.Value == "" {
project, err := selectors.Select(opts.Ask, "Select the project to associate the ephemeral environment with:", opts.GetAllProjectsCallback, func(project *projects.Project) string { return project.GetName() })
if err != nil {
return err
}
opts.Project.Value = project.GetName()
}

return nil
}
67 changes: 67 additions & 0 deletions pkg/cmd/ephemeralenvironment/create/create_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package create_test
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are based on the test to create a space.


import (
"testing"

"github.com/OctopusDeploy/cli/pkg/cmd"
"github.com/OctopusDeploy/cli/pkg/cmd/ephemeralenvironment/create"
"github.com/OctopusDeploy/cli/test/fixtures"
"github.com/OctopusDeploy/cli/test/testutil"
"github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/projects"
"github.com/stretchr/testify/assert"
)

func TestPromptMissing_AllOptionsSupplied(t *testing.T) {

project1 := fixtures.NewProject("Spaces-1", "Projects-1", "Test1", "Lifecycles-1", "ProjectGroups-1", "DeploymentProcesses-1")
project2 := fixtures.NewProject("Spaces-1", "Projects-2", "Test2", "Lifecycles-1", "ProjectGroups-1", "DeploymentProcesses-2")

pa := []*testutil.PA{}

asker, checkRemainingPrompts := testutil.NewMockAsker(t, pa)

flags := create.NewCreateFlags()
flags.Name.Value = "Hello Ephemeral Environment"
flags.Project.Value = "Hello Project"

opts := &create.CreateOptions{
CreateFlags: flags,
Dependencies: &cmd.Dependencies{Ask: asker},
}
opts.GetAllProjectsCallback = func() ([]*projects.Project, error) {
return []*projects.Project{project1, project2}, nil
}

// Check that no unexpected prompts were triggered
create.PromptMissing(opts)
checkRemainingPrompts()
}

func TestPromptMissing_NoOptionsSupplied(t *testing.T) {
project1 := fixtures.NewProject("Spaces-1", "Projects-1", "Hello Project 1", "Lifecycles-1", "ProjectGroups-1", "DeploymentProcesses-1")
project2 := fixtures.NewProject("Spaces-1", "Projects-2", "Hello Project 2", "Lifecycles-1", "ProjectGroups-1", "DeploymentProcesses-2")

pa := []*testutil.PA{
testutil.NewInputPrompt("Name", "A short, memorable, unique name for this ephemeral environment.", "Hello Ephemeral Environment"),
testutil.NewSelectPrompt("Select the project to associate the ephemeral environment with:", "", []string{project1.Name, project2.Name}, project1.Name),
}

asker, checkRemainingPrompts := testutil.NewMockAsker(t, pa)

flags := create.NewCreateFlags()

opts := &create.CreateOptions{
CreateFlags: flags,
Dependencies: &cmd.Dependencies{Ask: asker},
GetAllProjectsCallback: func() ([]*projects.Project, error) {
return []*projects.Project{project1, project2}, nil
},
}

create.PromptMissing(opts)

// Check that all expected prompts were called
checkRemainingPrompts()
assert.Equal(t, "Hello Ephemeral Environment", flags.Name.Value)
assert.Equal(t, project1.Name, flags.Project.Value)
}
27 changes: 27 additions & 0 deletions pkg/cmd/ephemeralenvironment/ephemeralenvironment.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package ephemeralenvironment

import (
"github.com/MakeNowJust/heredoc/v2"
cmdCreate "github.com/OctopusDeploy/cli/pkg/cmd/ephemeralenvironment/create"
"github.com/OctopusDeploy/cli/pkg/constants"
"github.com/OctopusDeploy/cli/pkg/constants/annotations"
"github.com/OctopusDeploy/cli/pkg/factory"
"github.com/spf13/cobra"
)

func NewCmdEphemeralEnvironment(f factory.Factory) *cobra.Command {
cmd := &cobra.Command{
Use: "ephemeralenvironment <command>",
Short: "Manage ephemeral environments",
Long: "Manage ephemeral environments in Octopus Deploy",
Example: heredoc.Docf(`
$ %[1]s ephemeralenvironment create --name "EE1" --project "MyProject"
`, constants.ExecutableName),
Annotations: map[string]string{
annotations.IsInfrastructure: "true",
},
}

cmd.AddCommand(cmdCreate.NewCmdCreate(f))
return cmd
}
2 changes: 2 additions & 0 deletions pkg/cmd/root/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
channelCmd "github.com/OctopusDeploy/cli/pkg/cmd/channel"
configCmd "github.com/OctopusDeploy/cli/pkg/cmd/config"
environmentCmd "github.com/OctopusDeploy/cli/pkg/cmd/environment"
ephemeralEnvironmentCmd "github.com/OctopusDeploy/cli/pkg/cmd/ephemeralenvironment"
loginCmd "github.com/OctopusDeploy/cli/pkg/cmd/login"
logoutCmd "github.com/OctopusDeploy/cli/pkg/cmd/logout"
packageCmd "github.com/OctopusDeploy/cli/pkg/cmd/package"
Expand Down Expand Up @@ -51,6 +52,7 @@ func NewCmdRoot(f factory.Factory, clientFactory apiclient.ClientFactory, askPro
// infrastructure
cmd.AddCommand(accountCmd.NewCmdAccount(f))
cmd.AddCommand(environmentCmd.NewCmdEnvironment(f))
cmd.AddCommand(ephemeralEnvironmentCmd.NewCmdEphemeralEnvironment(f))
cmd.AddCommand(packageCmd.NewCmdPackage(f))
cmd.AddCommand(buildInfoCmd.NewCmdBuildInformation(f))
cmd.AddCommand(deploymentTargetCmd.NewCmdDeploymentTarget(f))
Expand Down