From 7021df6cfc51f18871665b1b95636e51eda7cb3d Mon Sep 17 00:00:00 2001 From: Mark Harrison Date: Tue, 13 Feb 2024 15:59:59 +0000 Subject: [PATCH 1/8] Add output format support for project view --- pkg/cmd/project/view/view.go | 67 +++++++++++++++++++++++++++++------- 1 file changed, 54 insertions(+), 13 deletions(-) diff --git a/pkg/cmd/project/view/view.go b/pkg/cmd/project/view/view.go index e70fa6ff..3abf13c3 100644 --- a/pkg/cmd/project/view/view.go +++ b/pkg/cmd/project/view/view.go @@ -1,9 +1,11 @@ package view import ( + "encoding/json" "fmt" + "strings" + "github.com/OctopusDeploy/cli/pkg/apiclient" - "io" "github.com/MakeNowJust/heredoc/v2" "github.com/OctopusDeploy/cli/pkg/constants" @@ -34,9 +36,9 @@ func NewViewFlags() *ViewFlags { type ViewOptions struct { Client *client.Client Host string - out io.Writer idOrName string flags *ViewFlags + cmd *cobra.Command } func NewCmdView(f factory.Factory) *cobra.Command { @@ -60,9 +62,9 @@ func NewCmdView(f factory.Factory) *cobra.Command { opts := &ViewOptions{ client, f.GetCurrentHost(), - cmd.OutOrStdout(), args[0], viewFlags, + cmd, } return viewRun(opts) @@ -81,23 +83,62 @@ func viewRun(opts *ViewOptions) error { return err } - fmt.Fprintf(opts.out, "%s %s\n", output.Bold(project.Name), output.Dimf("(%s)", project.Slug)) + outputFormat, err := opts.cmd.Flags().GetString(constants.FlagOutputFormat) + + if err != nil { // should never happen, but fallback if it does + outputFormat = constants.OutputFormatTable + } + out := opts.cmd.OutOrStdout() - cacBranch := "Not version controlled" + projectVcsBranch := "N/A" if project.IsVersionControlled { - cacBranch = project.PersistenceSettings.(projects.GitPersistenceSettings).DefaultBranch() + projectVcsBranch = project.PersistenceSettings.(projects.GitPersistenceSettings).DefaultBranch() } - fmt.Fprintf(opts.out, "Version control branch: %s\n", output.Cyan(cacBranch)) - if project.Description == "" { - fmt.Fprintln(opts.out, output.Dim(constants.NoDescription)) - } else { - fmt.Fprintln(opts.out, output.Dim(project.Description)) + + projectDescription := project.Description + if projectDescription == "" { + projectDescription = constants.NoDescription } url := opts.Host + project.Links["Web"] - // footer - fmt.Fprintf(opts.out, "View this project in Octopus Deploy: %s\n", output.Blue(url)) + type ViewData struct { + Name string `json:"name"` + Slug string `json:"slug"` + Description string `json:"description"` + IsVersionControlled bool `json:"isversioncontrolled"` + Branch string `json:"branch"` + Url string `json:"url"` + } + + switch strings.ToLower(outputFormat) { + case constants.OutputFormatBasic: + fmt.Fprintf(out, "Name: %s %s\n", output.Bold(project.Name), output.Dimf("(%s)", project.Slug)) + fmt.Fprintf(out, "Description: %s\n", output.Dim(projectDescription)) + fmt.Fprintf(out, "Is version controlled: %s\n", output.Cyanf("%t", project.IsVersionControlled)) + fmt.Fprintf(out, "Branch: %s\n", output.Cyan(projectVcsBranch)) + fmt.Fprintf(out, "View this project in Octopus Deploy: %s\n", output.Blue(url)) + case constants.OutputFormatTable: + t := output.NewTable(out) + t.AddRow(output.Bold("KEY"), output.Bold("VALUE")) + t.AddRow("Name", project.Name) + t.AddRow("Slug", project.Slug) + t.AddRow("Description", project.Description) + t.AddRow("IsVersionControlled", fmt.Sprintf("%t", project.IsVersionControlled)) + t.AddRow("Branch", projectVcsBranch) + t.AddRow("Url", fmt.Sprintf("%s", output.Blue(url))) + t.Print() + case constants.OutputFormatJson: + viewData := &ViewData{} + viewData.Name = project.Name + viewData.Slug = project.Slug + viewData.Description = project.Description + viewData.IsVersionControlled = project.IsVersionControlled + viewData.Branch = projectVcsBranch + viewData.Url = url + data, _ := json.MarshalIndent(viewData, "", " ") + opts.cmd.Println(string(data)) + } if opts.flags.Web.Value { browser.OpenURL(url) From 8deb497d4a86cb50e4b2f60c0ba1e5a16f482194 Mon Sep 17 00:00:00 2001 From: Mark Harrison Date: Tue, 13 Feb 2024 16:00:06 +0000 Subject: [PATCH 2/8] Fix typo in readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e1470ad2..e9a34edd 100644 --- a/README.md +++ b/README.md @@ -157,7 +157,7 @@ If successful, the go compiler does not output anything. You should now have an **Makefile** -If you are using a sytem that has `make` installed, then you can also simpl run `make` in the cli root folder. +If you are using a system that has `make` installed, then you can also simpl run `make` in the cli root folder. The default action for the `Makefile` is to run `go build`, as above. ## Running the CLI From 51bbe77963b76e869664ec8452d9f4c9495fdfb4 Mon Sep 17 00:00:00 2001 From: Mark Harrison Date: Tue, 13 Feb 2024 16:34:43 +0000 Subject: [PATCH 3/8] Fixed another missed typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e9a34edd..5cf240ac 100644 --- a/README.md +++ b/README.md @@ -157,7 +157,7 @@ If successful, the go compiler does not output anything. You should now have an **Makefile** -If you are using a system that has `make` installed, then you can also simpl run `make` in the cli root folder. +If you are using a system that has `make` installed, then you can also simply run `make` in the cli root folder. The default action for the `Makefile` is to run `go build`, as above. ## Running the CLI From 234ae7cc282c920cf99fd5afe934c214f8d91ea8 Mon Sep 17 00:00:00 2001 From: Mark Harrison Date: Wed, 14 Feb 2024 16:41:09 +0000 Subject: [PATCH 4/8] Added support for output formats for project variable view --- pkg/cmd/project/variables/view/view.go | 216 +++++++++++++++++++------ 1 file changed, 168 insertions(+), 48 deletions(-) diff --git a/pkg/cmd/project/variables/view/view.go b/pkg/cmd/project/variables/view/view.go index 28facde4..efeb2346 100644 --- a/pkg/cmd/project/variables/view/view.go +++ b/pkg/cmd/project/variables/view/view.go @@ -1,7 +1,11 @@ package view import ( + "encoding/json" "fmt" + "strconv" + "strings" + "github.com/MakeNowJust/heredoc/v2" "github.com/OctopusDeploy/cli/pkg/apiclient" variableShared "github.com/OctopusDeploy/cli/pkg/cmd/project/variables/shared" @@ -14,9 +18,6 @@ import ( "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/resources" "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/variables" "github.com/spf13/cobra" - "io" - "strconv" - "strings" ) const ( @@ -34,9 +35,37 @@ type ViewFlags struct { type ViewOptions struct { Client *client.Client Host string - out io.Writer name string *ViewFlags + cmd *cobra.Command +} + +type VarScopedItem struct { + Id string `json:"id"` + Name string `json:"name"` +} + +type VarProcessScopedItem struct { + Id string `json:"id"` + Name string `json:"name"` + ProcessType string `json:"processtype"` +} + +type VariableViewData struct { + Id string `json:"id"` + Name string `json:"name"` + Value string `json:"value"` + Description string `json:"description"` + EnvironmentScope []*VarScopedItem `json:"environmentscope"` + RoleScope []*VarScopedItem `json:"rolescope"` + MachineScope []*VarScopedItem `json:"machinescope"` + ProcessScope []*VarProcessScopedItem `json:"processscope"` + StepScope []*VarScopedItem `json:"stepscope"` + ChannelScope []*VarScopedItem `json:"channelscope"` + Prompted bool `json:"prompted,omitempty"` + PromptLabel string `json:"promptlabel,omitempty"` + PromptLabelDescription string `json:"promptlabeldescription,omitempty"` + PromptRequired string `json:"promptrequired,omitempty"` } func NewViewFlags() *ViewFlags { @@ -71,9 +100,9 @@ func NewCmdView(f factory.Factory) *cobra.Command { opts := &ViewOptions{ client, f.GetCurrentHost(), - cmd.OutOrStdout(), args[0], viewFlags, + cmd, } return viewRun(opts) @@ -113,55 +142,146 @@ func viewRun(opts *ViewOptions) error { return fmt.Errorf("cannot find variable '%s'", opts.name) } - fmt.Fprintln(opts.out, output.Bold(filteredVars[0].Name)) + outputFormat, err := opts.cmd.Flags().GetString(constants.FlagOutputFormat) - for _, v := range filteredVars { - data := []*output.DataRow{} + if err != nil { // should never happen, but fallback if it does + outputFormat = constants.OutputFormatTable + } - data = append(data, output.NewDataRow("Id", output.Dim(v.GetID()))) - if v.IsSensitive { - data = append(data, output.NewDataRow("Value", output.Bold("*** (sensitive)"))) - } else { - data = append(data, output.NewDataRow("Value", output.Bold(v.Value))) - } + out := opts.cmd.OutOrStdout() - if v.Description == "" { - v.Description = constants.NoDescription - } - data = append(data, output.NewDataRow("Description", output.Dim(v.Description))) + switch strings.ToLower(outputFormat) { + case constants.OutputFormatBasic, constants.OutputFormatTable: + fmt.Fprintln(out, output.Bold(filteredVars[0].Name)) + for _, v := range filteredVars { + data := []*output.DataRow{} - scopeValues, err := variableShared.ToScopeValues(v, allVars.ScopeValues) - if err != nil { - return err - } - data = addScope(scopeValues.Environments, "Environment scope", data, nil) - data = addScope(scopeValues.Roles, "Role scope", data, nil) - data = addScope(scopeValues.Channels, "Channel scope", data, nil) - data = addScope(scopeValues.Machines, "Machine scope", data, nil) - data = addScope(scopeValues.TenantTags, "Tenant tag scope", data, func(item *resources.ReferenceDataItem) string { - return item.ID - }) - data = addScope(scopeValues.Actions, "Step scope", data, nil) - data = addScope( - util.SliceTransform(scopeValues.Processes, func(item *resources.ProcessReferenceDataItem) *resources.ReferenceDataItem { - return &resources.ReferenceDataItem{ - ID: item.ID, - Name: item.Name, - } - }), - "Process scope", - data, - nil) - - if v.Prompt != nil { - data = append(data, output.NewDataRow("Prompted", "true")) - data = append(data, output.NewDataRow("Prompt Label", v.Prompt.Label)) - data = append(data, output.NewDataRow("Prompt Description", output.Dim(v.Prompt.Description))) - data = append(data, output.NewDataRow("Prompt Required", strconv.FormatBool(v.Prompt.IsRequired))) + data = append(data, output.NewDataRow("Id", output.Dim(v.GetID()))) + if v.IsSensitive { + data = append(data, output.NewDataRow("Value", output.Bold("*** (sensitive)"))) + } else { + data = append(data, output.NewDataRow("Value", output.Bold(v.Value))) + } + + if v.Description == "" { + v.Description = constants.NoDescription + } + data = append(data, output.NewDataRow("Description", output.Dim(v.Description))) + + scopeValues, err := variableShared.ToScopeValues(v, allVars.ScopeValues) + if err != nil { + return err + } + + data = addScope(scopeValues.Environments, "Environment scope", data, nil) + data = addScope(scopeValues.Roles, "Role scope", data, nil) + data = addScope(scopeValues.Channels, "Channel scope", data, nil) + data = addScope(scopeValues.Machines, "Machine scope", data, nil) + data = addScope(scopeValues.TenantTags, "Tenant tag scope", data, func(item *resources.ReferenceDataItem) string { + return item.ID + }) + data = addScope(scopeValues.Actions, "Step scope", data, nil) + data = addScope( + util.SliceTransform(scopeValues.Processes, func(item *resources.ProcessReferenceDataItem) *resources.ReferenceDataItem { + return &resources.ReferenceDataItem{ + ID: item.ID, + Name: item.Name, + } + }), + "Process scope", + data, + nil) + + if v.Prompt != nil { + data = append(data, output.NewDataRow("Prompted", "true")) + data = append(data, output.NewDataRow("Prompt Label", v.Prompt.Label)) + data = append(data, output.NewDataRow("Prompt Description", output.Dim(v.Prompt.Description))) + data = append(data, output.NewDataRow("Prompt Required", strconv.FormatBool(v.Prompt.IsRequired))) + } + + fmt.Fprintln(out) + output.PrintRows(data, out) } + case constants.OutputFormatJson: + + viewItems := make([]VariableViewData, len(filteredVars)) + for _, v := range filteredVars { + + scopeValues, err := variableShared.ToScopeValues(v, allVars.ScopeValues) + if err != nil { + return err + } - fmt.Fprintln(opts.out) - output.PrintRows(data, opts.out) + vd := VariableViewData{} + vd.Id = v.ID + if v.IsSensitive { + vd.Value = "*** (sensitive)" + } else { + vd.Value = v.Value + } + vd.Description = v.Description + + if util.Any(scopeValues.Environments) { + vd.EnvironmentScope = util.SliceTransform(scopeValues.Environments, func(e *resources.ReferenceDataItem) *VarScopedItem { + return &VarScopedItem{ + Id: e.ID, + Name: e.Name, + } + }) + } + if util.Any(scopeValues.Roles) { + vd.RoleScope = util.SliceTransform(scopeValues.Roles, func(e *resources.ReferenceDataItem) *VarScopedItem { + return &VarScopedItem{ + Id: e.ID, + Name: e.Name, + } + }) + } + if util.Any(scopeValues.Machines) { + vd.MachineScope = util.SliceTransform(scopeValues.Machines, func(e *resources.ReferenceDataItem) *VarScopedItem { + return &VarScopedItem{ + Id: e.ID, + Name: e.Name, + } + }) + } + if util.Any(scopeValues.Processes) { + vd.ProcessScope = util.SliceTransform(scopeValues.Processes, func(e *resources.ProcessReferenceDataItem) *VarProcessScopedItem { + return &VarProcessScopedItem{ + Id: e.ID, + Name: e.Name, + ProcessType: e.ProcessType, + } + }) + } + if util.Any(scopeValues.Actions) { + vd.StepScope = util.SliceTransform(scopeValues.Actions, func(e *resources.ReferenceDataItem) *VarScopedItem { + return &VarScopedItem{ + Id: e.ID, + Name: e.Name, + } + }) + } + if util.Any(scopeValues.Channels) { + vd.ChannelScope = util.SliceTransform(scopeValues.Channels, func(e *resources.ReferenceDataItem) *VarScopedItem { + return &VarScopedItem{ + Id: e.ID, + Name: e.Name, + } + }) + } + if v.Prompt != nil { + vd.Prompted = true + vd.PromptLabel = v.Prompt.Label + vd.PromptLabelDescription = v.Prompt.Description + vd.PromptRequired = strconv.FormatBool(v.Prompt.IsRequired) + } else { + vd.Prompted = false + } + viewItems = append(viewItems, vd) + } + data, _ := json.MarshalIndent(viewItems, "", " ") + opts.cmd.Println(string(data)) } return nil From 7d1a1354922a2a44aad81606f9b47dc68355bb7e Mon Sep 17 00:00:00 2001 From: Mark Harrison Date: Wed, 14 Feb 2024 16:50:09 +0000 Subject: [PATCH 5/8] Remove name and fix viewItems --- pkg/cmd/project/variables/view/view.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pkg/cmd/project/variables/view/view.go b/pkg/cmd/project/variables/view/view.go index efeb2346..7e356ca2 100644 --- a/pkg/cmd/project/variables/view/view.go +++ b/pkg/cmd/project/variables/view/view.go @@ -53,7 +53,6 @@ type VarProcessScopedItem struct { type VariableViewData struct { Id string `json:"id"` - Name string `json:"name"` Value string `json:"value"` Description string `json:"description"` EnvironmentScope []*VarScopedItem `json:"environmentscope"` @@ -204,7 +203,7 @@ func viewRun(opts *ViewOptions) error { } case constants.OutputFormatJson: - viewItems := make([]VariableViewData, len(filteredVars)) + viewItems := make([]VariableViewData, 0, len(filteredVars)) for _, v := range filteredVars { scopeValues, err := variableShared.ToScopeValues(v, allVars.ScopeValues) From f32bc34c7780f3c2cda3f94dbf432a23f20f4935 Mon Sep 17 00:00:00 2001 From: Mark Harrison Date: Wed, 14 Feb 2024 17:22:54 +0000 Subject: [PATCH 6/8] Inherit the scoped item --- pkg/cmd/project/variables/view/view.go | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/pkg/cmd/project/variables/view/view.go b/pkg/cmd/project/variables/view/view.go index 7e356ca2..cd9101e3 100644 --- a/pkg/cmd/project/variables/view/view.go +++ b/pkg/cmd/project/variables/view/view.go @@ -46,8 +46,7 @@ type VarScopedItem struct { } type VarProcessScopedItem struct { - Id string `json:"id"` - Name string `json:"name"` + VarScopedItem ProcessType string `json:"processtype"` } @@ -246,10 +245,13 @@ func viewRun(opts *ViewOptions) error { } if util.Any(scopeValues.Processes) { vd.ProcessScope = util.SliceTransform(scopeValues.Processes, func(e *resources.ProcessReferenceDataItem) *VarProcessScopedItem { + scopedItem := &VarScopedItem{ + Id: e.ID, + Name: e.Name, + } return &VarProcessScopedItem{ - Id: e.ID, - Name: e.Name, - ProcessType: e.ProcessType, + VarScopedItem: *scopedItem, + ProcessType: e.ProcessType, } }) } From 62fb7a93ad5df4c8735946b9d7913155e61adc3a Mon Sep 17 00:00:00 2001 From: Mark Harrison Date: Wed, 14 Feb 2024 17:23:51 +0000 Subject: [PATCH 7/8] Simplify scopedItem to inline --- pkg/cmd/project/variables/view/view.go | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/pkg/cmd/project/variables/view/view.go b/pkg/cmd/project/variables/view/view.go index cd9101e3..a46612f3 100644 --- a/pkg/cmd/project/variables/view/view.go +++ b/pkg/cmd/project/variables/view/view.go @@ -245,13 +245,12 @@ func viewRun(opts *ViewOptions) error { } if util.Any(scopeValues.Processes) { vd.ProcessScope = util.SliceTransform(scopeValues.Processes, func(e *resources.ProcessReferenceDataItem) *VarProcessScopedItem { - scopedItem := &VarScopedItem{ - Id: e.ID, - Name: e.Name, - } return &VarProcessScopedItem{ - VarScopedItem: *scopedItem, - ProcessType: e.ProcessType, + VarScopedItem: VarScopedItem{ + Id: e.ID, + Name: e.Name, + }, + ProcessType: e.ProcessType, } }) } From 98078bf4968bee4e7536552ce1577ddd5a25b7e9 Mon Sep 17 00:00:00 2001 From: Mark Harrison Date: Mon, 4 Mar 2024 13:43:15 +0000 Subject: [PATCH 8/8] Add header if output format is bold --- pkg/cmd/project/variables/view/view.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pkg/cmd/project/variables/view/view.go b/pkg/cmd/project/variables/view/view.go index a46612f3..a655fe75 100644 --- a/pkg/cmd/project/variables/view/view.go +++ b/pkg/cmd/project/variables/view/view.go @@ -153,7 +153,9 @@ func viewRun(opts *ViewOptions) error { fmt.Fprintln(out, output.Bold(filteredVars[0].Name)) for _, v := range filteredVars { data := []*output.DataRow{} - + if outputFormat == constants.OutputFormatTable { + data = append(data, output.NewDataRow(output.Bold("KEY"), output.Bold("VALUE"))) + } data = append(data, output.NewDataRow("Id", output.Dim(v.GetID()))) if v.IsSensitive { data = append(data, output.NewDataRow("Value", output.Bold("*** (sensitive)")))