Skip to content
Open
Show file tree
Hide file tree
Changes from all 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.82.0
github.com/OctopusDeploy/go-octopusdeploy/v2 v2.84.1
github.com/bmatcuk/doublestar/v4 v4.4.0
github.com/briandowns/spinner v1.19.0
github.com/google/uuid v1.3.0
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@ github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 h1:+vx7roKuyA63n
github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDeC1lPdgDeDbhX8XFpy1jqjK0IBG8W5K+xYqA0w=
github.com/OctopusDeploy/go-octodiff v1.0.0 h1:U+ORg6azniwwYo+O44giOw6TiD5USk8S4VDhOQ0Ven0=
github.com/OctopusDeploy/go-octodiff v1.0.0/go.mod h1:Mze0+EkOWTgTmi8++fyUc6r0aLZT7qD9gX+31t8MmIU=
github.com/OctopusDeploy/go-octopusdeploy/v2 v2.82.0 h1:4Pc2W74VKp7Qm0uV0Dv99QKqRWg8WriVikdZPBpIZgY=
github.com/OctopusDeploy/go-octopusdeploy/v2 v2.82.0/go.mod h1:J1UdIilp41MRuFl+5xZm88ywFqJGYCCqxqod+/ZH8ko=
github.com/OctopusDeploy/go-octopusdeploy/v2 v2.84.1 h1:7BfRa64Jxb6XNqnMiAXMza5K9C4nt8fcYGFEVQ/MfuI=
github.com/OctopusDeploy/go-octopusdeploy/v2 v2.84.1/go.mod h1:J1UdIilp41MRuFl+5xZm88ywFqJGYCCqxqod+/ZH8ko=
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
53 changes: 50 additions & 3 deletions pkg/cmd/environment/create/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,11 @@ import (
"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/client"
"github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/environments"
"github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/tagsets"
"github.com/spf13/cobra"
)

Expand All @@ -19,13 +22,15 @@ const (
FlagDescription = "description"
FlagUseGuidedFailure = "use-guided-failure"
FlagDynamicInfrastructure = "allow-dynamic-infrastructure"
FlagTag = "tag"
)

type CreateFlags struct {
Name *flag.Flag[string]
Description *flag.Flag[string]
GuidedFailureMode *flag.Flag[bool]
DynamicInfrastructure *flag.Flag[bool]
Tag *flag.Flag[[]string]
}

func NewCreateFlags() *CreateFlags {
Expand All @@ -34,18 +39,23 @@ func NewCreateFlags() *CreateFlags {
Description: flag.New[string](FlagDescription, false),
GuidedFailureMode: flag.New[bool](FlagUseGuidedFailure, false),
DynamicInfrastructure: flag.New[bool](FlagDynamicInfrastructure, false),
Tag: flag.New[[]string](FlagTag, false),
}
}

type GetAllTagSetsCallback func() ([]*tagsets.TagSet, error)

type CreateOptions struct {
*CreateFlags
*cmd.Dependencies
GetAllTagsCallback GetAllTagSetsCallback
}

func NewCreateOptions(createFlags *CreateFlags, dependencies *cmd.Dependencies) *CreateOptions {
return &CreateOptions{
CreateFlags: createFlags,
Dependencies: dependencies,
CreateFlags: createFlags,
Dependencies: dependencies,
GetAllTagsCallback: getAllTagSetsCallback(dependencies.Client),
}
}

Expand All @@ -70,6 +80,7 @@ func NewCmdCreate(f factory.Factory) *cobra.Command {
flags.StringVarP(&createFlags.Description.Value, createFlags.Description.Name, "d", "", "Description of the environment")
flags.BoolVar(&createFlags.GuidedFailureMode.Value, createFlags.GuidedFailureMode.Name, false, "Use guided failure mode by default")
flags.BoolVar(&createFlags.DynamicInfrastructure.Value, createFlags.DynamicInfrastructure.Name, false, "Allow dynamic infrastructure")
flags.StringArrayVarP(&createFlags.Tag.Value, createFlags.Tag.Name, "t", []string{}, "Tag to apply to environment, must use canonical name: <tag_set>/<tag_name>")

return cmd
}
Expand All @@ -80,12 +91,24 @@ func createRun(opts *CreateOptions) error {
if err != nil {
return err
}
} else {
// Validate tags when running with --no-prompt
if len(opts.Tag.Value) > 0 {
tagSets, err := opts.GetAllTagsCallback()
if err != nil {
return err
}
if err := selectors.ValidateTags(opts.Tag.Value, tagSets); err != nil {
return err
}
}
}

env := environments.NewEnvironment(opts.Name.Value)
env.Description = opts.Description.Value
env.AllowDynamicInfrastructure = opts.DynamicInfrastructure.Value
env.UseGuidedFailure = opts.GuidedFailureMode.Value
env.EnvironmentTags = opts.Tag.Value

createEnv, err := opts.Client.Environments.Add(env)
if err != nil {
Expand All @@ -101,7 +124,7 @@ func createRun(opts *CreateOptions) error {
fmt.Fprintf(opts.Out, "View this environment on Octopus Deploy: %s\n", link)

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

Expand All @@ -122,6 +145,17 @@ func PromptMissing(opts *CreateOptions) error {
_, err = promptBool(opts, &opts.GuidedFailureMode.Value, false, "Use guided failure", "If guided failure is enabled for an environment, Octopus Deploy will prompt for user intervention if a deployment fails in the environment.")
_, err = promptBool(opts, &opts.DynamicInfrastructure.Value, false, "Allow dynamic infrastructure", "If dynamic infrastructure is enabled for an environment, deployments to this environment are allowed to create infrastructure, such as targets and accounts.")

tagSets, err := opts.GetAllTagsCallback()
if err != nil {
return err
}

tags, err := selectors.Tags(opts.Ask, []string{}, opts.Tag.Value, tagSets)
if err != nil {
return err
}
opts.Tag.Value = tags

return nil
}

Expand All @@ -136,3 +170,16 @@ func promptBool(opts *CreateOptions, value *bool, defaultValue bool, message str
}, value)
return *value, err
}

func getAllTagSetsCallback(client *client.Client) GetAllTagSetsCallback {
return func() ([]*tagsets.TagSet, error) {
query := tagsets.TagSetsQuery{
Scopes: []string{string(tagsets.TagSetScopeEnvironment)},
}
result, err := tagsets.Get(client, client.GetSpaceID(), query)
if err != nil {
return nil, err
}
return result.Items, nil
}
}
2 changes: 2 additions & 0 deletions pkg/cmd/environment/environment.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
cmdCreate "github.com/OctopusDeploy/cli/pkg/cmd/environment/create"
cmdDelete "github.com/OctopusDeploy/cli/pkg/cmd/environment/delete"
cmdList "github.com/OctopusDeploy/cli/pkg/cmd/environment/list"
cmdTag "github.com/OctopusDeploy/cli/pkg/cmd/environment/tag"
"github.com/OctopusDeploy/cli/pkg/constants"
"github.com/OctopusDeploy/cli/pkg/constants/annotations"
"github.com/OctopusDeploy/cli/pkg/factory"
Expand All @@ -28,5 +29,6 @@ func NewCmdEnvironment(f factory.Factory) *cobra.Command {
cmd.AddCommand(cmdList.NewCmdList(f))
cmd.AddCommand(cmdDelete.NewCmdDelete(f))
cmd.AddCommand(cmdCreate.NewCmdCreate(f))
cmd.AddCommand(cmdTag.NewCmdTag(f))
return cmd
}
14 changes: 11 additions & 3 deletions pkg/cmd/environment/list/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,21 @@ func NewCmdList(f factory.Factory) *cobra.Command {

return output.PrintArray(allEnvs, cmd, output.Mappers[*environments.Environment]{
Json: func(item *environments.Environment) any {
return output.IdAndName{Id: item.GetID(), Name: item.Name}
return struct {
Id string `json:"Id"`
Name string `json:"Name"`
EnvironmentTags []string `json:"EnvironmentTags,omitempty"`
}{
Id: item.GetID(),
Name: item.Name,
EnvironmentTags: item.EnvironmentTags,
}
},
Table: output.TableDefinition[*environments.Environment]{
Header: []string{"NAME", "GUIDED FAILURE"},
Header: []string{"NAME", "GUIDED FAILURE", "TAGS"},
Row: func(item *environments.Environment) []string {

return []string{output.Bold(item.Name), strconv.FormatBool(item.UseGuidedFailure)}
return []string{output.Bold(item.Name), strconv.FormatBool(item.UseGuidedFailure), output.FormatAsList(item.EnvironmentTags)}
},
},
Basic: func(item *environments.Environment) string {
Expand Down
149 changes: 149 additions & 0 deletions pkg/cmd/environment/tag/tag.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
package tag

import (
"fmt"
"io"

"github.com/MakeNowJust/heredoc/v2"
"github.com/OctopusDeploy/cli/pkg/cmd"
"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"
"github.com/spf13/cobra"
)

const (
FlagTag = "tag"
FlagEnvironment = "environment"
)

type TagFlags struct {
Tag *flag.Flag[[]string]
Environment *flag.Flag[string]
}

func NewTagFlags() *TagFlags {
return &TagFlags{
Tag: flag.New[[]string](FlagTag, false),
Environment: flag.New[string](FlagEnvironment, false),
}
}

func NewCmdTag(f factory.Factory) *cobra.Command {
createFlags := NewTagFlags()

cmd := &cobra.Command{
Use: "tag",
Short: "Override tags for an environment",
Long: "Override tags for an environment in Octopus Deploy",
Example: heredoc.Docf("$ %s environment tag Environment-1", constants.ExecutableName),
RunE: func(c *cobra.Command, _ []string) error {
opts := NewTagOptions(createFlags, cmd.NewDependencies(f, c))

return createRun(opts)
},
}

flags := cmd.Flags()
flags.StringArrayVarP(&createFlags.Tag.Value, createFlags.Tag.Name, "t", []string{}, "Tag to apply to environment, must use canonical name: <tag_set>/<tag_name>")
flags.StringVar(&createFlags.Environment.Value, createFlags.Environment.Name, "", "Name or ID of the environment you wish to update")

return cmd
}

func createRun(opts *TagOptions) error {
var optsArray []cmd.Dependable
var err error
if !opts.NoPrompt {
optsArray, err = PromptMissing(opts)
if err != nil {
return err
}
} else {
// Validate tags when running with --no-prompt
if len(opts.Tag.Value) > 0 {
tagSets, err := opts.GetAllTagsCallback()
if err != nil {
return err
}
if err := selectors.ValidateTags(opts.Tag.Value, tagSets); err != nil {
return err
}
}
optsArray = append(optsArray, opts)
}

for _, o := range optsArray {
if err := o.Commit(); err != nil {
return err
}
}

if !opts.NoPrompt {
fmt.Fprintln(opts.Out, "\nAutomation Commands:")
for _, o := range optsArray {
o.GenerateAutomationCmd()
}
}

return nil
}

func PromptMissing(opts *TagOptions) ([]cmd.Dependable, error) {
nestedOpts := []cmd.Dependable{}

environment, err := AskEnvironments(opts.Ask, opts.Out, opts.Environment.Value, opts.GetEnvironmentsCallback, opts.GetEnvironmentCallback)
if err != nil {
return nil, err
}
opts.environment = environment
opts.Environment.Value = environment.Name

tagSets, err := opts.GetAllTagsCallback()
if err != nil {
return nil, err
}

tags, err := selectors.Tags(opts.Ask, opts.environment.EnvironmentTags, opts.Tag.Value, tagSets)
if err != nil {
return nil, err
}
opts.Tag.Value = tags

nestedOpts = append(nestedOpts, opts)
return nestedOpts, nil
}

func AskEnvironments(ask question.Asker, out io.Writer, value string, getEnvironmentsCallback GetEnvironmentsCallback, getEnvironmentCallback GetEnvironmentCallback) (*environments.Environment, error) {
if value != "" {
environment, err := getEnvironmentCallback(value)
if err != nil {
return nil, err
}
return environment, nil
}

// Check if there's only one environment
envs, err := getEnvironmentsCallback()
if err != nil {
return nil, err
}
if len(envs) == 1 {
fmt.Fprintf(out, "Selecting only available environment '%s'.\n", output.Cyan(envs[0].Name))
return envs[0], nil
}

environment, err := selectors.Select(ask, "Select the Environment you would like to update", getEnvironmentsCallback, func(item *environments.Environment) string {
return item.Name
})
if err != nil {
return nil, err
}

return environment, nil
}

Loading