diff --git a/pkg/cmd/environment/delete/delete.go b/pkg/cmd/environment/delete/delete.go index 7ed76eae..12f6306d 100644 --- a/pkg/cmd/environment/delete/delete.go +++ b/pkg/cmd/environment/delete/delete.go @@ -2,8 +2,10 @@ package delete import ( "fmt" + "github.com/MakeNowJust/heredoc/v2" "github.com/OctopusDeploy/cli/pkg/apiclient" + "github.com/OctopusDeploy/cli/pkg/cmd/environment/helper" "github.com/OctopusDeploy/cli/pkg/constants" "github.com/OctopusDeploy/cli/pkg/factory" "github.com/OctopusDeploy/cli/pkg/question" @@ -36,22 +38,7 @@ func NewCmdDelete(f factory.Factory) *cobra.Command { return err } - // SDK doesn't have accounts.GetByIDOrName so we emulate it here - foundEnvironments, err := client.Environments.Get(environments.EnvironmentsQuery{ - // TODO we can't lookup by ID here because the server will AND it with the ItemName and produce no results - PartialName: itemIDOrName, - }) - if err != nil { - return err - } - // need exact match - var itemToDelete *environments.Environment - for _, item := range foundEnvironments.Items { - if item.Name == itemIDOrName { - itemToDelete = item - break - } - } + itemToDelete, err := helper.GetByIDOrName(client.Environments, itemIDOrName) if itemToDelete == nil { return fmt.Errorf("cannot find an environment with name or ID of '%s'", itemIDOrName) } diff --git a/pkg/cmd/environment/environment.go b/pkg/cmd/environment/environment.go index 8c26e851..d18d599a 100644 --- a/pkg/cmd/environment/environment.go +++ b/pkg/cmd/environment/environment.go @@ -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" + cmdView "github.com/OctopusDeploy/cli/pkg/cmd/environment/view" "github.com/OctopusDeploy/cli/pkg/constants" "github.com/OctopusDeploy/cli/pkg/constants/annotations" "github.com/OctopusDeploy/cli/pkg/factory" @@ -28,5 +29,7 @@ func NewCmdEnvironment(f factory.Factory) *cobra.Command { cmd.AddCommand(cmdList.NewCmdList(f)) cmd.AddCommand(cmdDelete.NewCmdDelete(f)) cmd.AddCommand(cmdCreate.NewCmdCreate(f)) + cmd.AddCommand(cmdView.NewCmdView(f)) + return cmd } diff --git a/pkg/cmd/environment/helper/helper.go b/pkg/cmd/environment/helper/helper.go new file mode 100644 index 00000000..d4b2ef22 --- /dev/null +++ b/pkg/cmd/environment/helper/helper.go @@ -0,0 +1,24 @@ +package helper + +import "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/environments" + +func GetByIDOrName(service *environments.EnvironmentService, idOrName string) (*environments.Environment, error) { + // SDK doesn't have accounts.GetByIDOrName so we emulate it here + foundEnvironments, err := service.Get(environments.EnvironmentsQuery{ + // TODO we can't lookup by ID here because the server will AND it with the ItemName and produce no results + PartialName: idOrName, + }) + if err != nil { + return nil, err + } + // need exact match + var matchedItem *environments.Environment + for _, item := range foundEnvironments.Items { + if item.Name == idOrName { + matchedItem = item + break + } + } + + return matchedItem, nil +} diff --git a/pkg/cmd/environment/view/view.go b/pkg/cmd/environment/view/view.go new file mode 100644 index 00000000..68f80d47 --- /dev/null +++ b/pkg/cmd/environment/view/view.go @@ -0,0 +1,161 @@ +package view + +import ( + "fmt" + "strings" + + "github.com/MakeNowJust/heredoc/v2" + "github.com/OctopusDeploy/cli/pkg/apiclient" + "github.com/OctopusDeploy/cli/pkg/cmd/environment/helper" + "github.com/OctopusDeploy/cli/pkg/constants" + "github.com/OctopusDeploy/cli/pkg/factory" + "github.com/OctopusDeploy/cli/pkg/output" + "github.com/OctopusDeploy/cli/pkg/usage" + "github.com/OctopusDeploy/cli/pkg/util" + "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/pkg/browser" + "github.com/spf13/cobra" +) + +const ( + FlagWeb = "web" +) + +type ViewFlags struct { + Web *flag.Flag[bool] +} + +func NewViewFlags() *ViewFlags { + return &ViewFlags{ + Web: flag.New[bool](FlagWeb, false), + } +} + +type ViewOptions struct { + Client *client.Client + Host string + idOrName string + flags *ViewFlags + Command *cobra.Command +} + +func NewCmdView(f factory.Factory) *cobra.Command { + viewFlags := NewViewFlags() + + cmd := &cobra.Command{ + Args: usage.ExactArgs(1), + Use: "view { | }", + Short: "View an environment", + Long: "View an environment in Octopus Deploy", + Example: heredoc.Docf(` + $ %[1]s environment view 'Production' + $ %[1]s environment view Environments-102 + `, constants.ExecutableName), + RunE: func(cmd *cobra.Command, args []string) error { + client, err := f.GetSystemClient(apiclient.NewRequester(cmd)) + if err != nil { + return err + } + + opts := &ViewOptions{ + client, + f.GetCurrentHost(), + args[0], + viewFlags, + cmd, + } + + return viewRun(opts) + }, + } + + flags := cmd.Flags() + flags.BoolVarP(&viewFlags.Web.Value, viewFlags.Web.Name, "w", false, "Open in web browser") + + return cmd +} + +func viewRun(opts *ViewOptions) error { + environment, err := helper.GetByIDOrName(opts.Client.Environments, opts.idOrName) + if err != nil { + return err + } + + return output.PrintResource(environment, opts.Command, output.Mappers[*environments.Environment]{ + Json: func(env *environments.Environment) any { + return EnvironmentAsJson{ + Id: env.GetID(), + Slug: env.Slug, + Name: env.Name, + Description: env.Description, + UseGuidedFailure: env.UseGuidedFailure, + AllowDynamicInfrastructure: env.AllowDynamicInfrastructure, + WebUrl: generateWebUrl(opts.Host, env), + } + }, + Table: output.TableDefinition[*environments.Environment]{ + Header: []string{"NAME", "SLUG", "DESCRIPTION", "GUIDED FAILURE", "DYNAMIC INFRASTRUCTURE", "WEB URL"}, + Row: func(env *environments.Environment) []string { + description := env.Description + if description == "" { + description = constants.NoDescription + } + + return []string{ + output.Bold(env.Name), + env.Slug, + description, + getBoolToString(env.UseGuidedFailure, "Enabled", "Disabled"), + getBoolToString(env.UseGuidedFailure, "Allowed", "Disallowed"), + output.Blue(generateWebUrl(opts.Host, env)), + } + }, + }, + Basic: func(env *environments.Environment) string { + var result strings.Builder + + // header + result.WriteString(fmt.Sprintf("%s %s\n", output.Bold(env.Name), output.Dimf("(%s)", env.GetID()))) + + // metadata + if len(env.Description) == 0 { + result.WriteString(fmt.Sprintf("%s\n", output.Dim(constants.NoDescription))) + } else { + result.WriteString(fmt.Sprintf("%s\n", output.Dim(env.Description))) + } + + url := generateWebUrl(opts.Host, env) + result.WriteString(fmt.Sprintf("View this environment in Octopus Deploy: %s\n", output.Blue(url))) + + if opts.flags.Web.Value { + browser.OpenURL(url) + } + + return result.String() + }, + }) +} + +type EnvironmentAsJson struct { + Id string `json:"Id"` + Slug string `json:"Slug"` + Name string `json:"Name"` + Description string `json:"Description"` + UseGuidedFailure bool `json:"UseGuidedFailure"` + AllowDynamicInfrastructure bool `json:"AllowDynamicInfrastructure"` + WebUrl string `json:"WebUrl"` +} + +func generateWebUrl(host string, env *environments.Environment) string { + return util.GenerateWebURL(host, env.SpaceID, fmt.Sprintf("infrastructure/environments/%s", env.GetID())) +} + +func getBoolToString(value bool, trueString string, falseString string) string { + if value { + return trueString + } else { + return falseString + } +}