Skip to content
Merged
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.88.0
github.com/OctopusDeploy/go-octopusdeploy/v2 v2.89.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
8 changes: 2 additions & 6 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +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.86.0 h1:yGoohDFkruQ13mxoK21J+U+IUkxoU5jSxGEpknW/E5Y=
github.com/OctopusDeploy/go-octopusdeploy/v2 v2.86.0/go.mod h1:VkTXDoIPbwGFi5+goo1VSwFNdMVo784cVtJdKIEvfus=
github.com/OctopusDeploy/go-octopusdeploy/v2 v2.86.1-0.20251107005245-10b49a98c515 h1:7L4l7zszH5TKP14BXCzgqT86xmHPQ4vQSmrqHc4Bj7E=
github.com/OctopusDeploy/go-octopusdeploy/v2 v2.86.1-0.20251107005245-10b49a98c515/go.mod h1:VkTXDoIPbwGFi5+goo1VSwFNdMVo784cVtJdKIEvfus=
github.com/OctopusDeploy/go-octopusdeploy/v2 v2.88.0 h1:doBP2Xk11l3tNqJfrF0cYqX0df3UHIQiQCts1qAux+w=
github.com/OctopusDeploy/go-octopusdeploy/v2 v2.88.0/go.mod h1:VkTXDoIPbwGFi5+goo1VSwFNdMVo784cVtJdKIEvfus=
github.com/OctopusDeploy/go-octopusdeploy/v2 v2.89.1 h1:EDo4CdA7jYZBHiaKu8nZRf9ndonJXC70OlU29+6KXKc=
github.com/OctopusDeploy/go-octopusdeploy/v2 v2.89.1/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
26 changes: 26 additions & 0 deletions pkg/cmd/project/create/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ const (
FlagDescription = "description"
FlagLifecycle = "lifecycle"
FlagConfigAsCode = "process-vcs"
FlagTag = "tag"
)

type CreateFlags struct {
Expand All @@ -29,6 +30,7 @@ type CreateFlags struct {
Description *flag.Flag[string]
Lifecycle *flag.Flag[string]
ConfigAsCode *flag.Flag[bool]
Tag *flag.Flag[[]string]

ProjectConvertFlags *convert.ConvertFlags
}
Expand All @@ -40,6 +42,7 @@ func NewCreateFlags() *CreateFlags {
Description: flag.New[string](FlagDescription, false),
Lifecycle: flag.New[string](FlagLifecycle, false),
ConfigAsCode: flag.New[bool](FlagConfigAsCode, false),
Tag: flag.New[[]string](FlagTag, false),
ProjectConvertFlags: convert.NewConvertFlags(),
}
}
Expand Down Expand Up @@ -69,6 +72,7 @@ func NewCmdCreate(f factory.Factory) *cobra.Command {
flags.StringVarP(&createFlags.Group.Value, createFlags.Group.Name, "g", "", "Project group of the project")
flags.StringVarP(&createFlags.Lifecycle.Value, createFlags.Lifecycle.Name, "l", "", "Lifecycle of the project")
flags.BoolVar(&createFlags.ConfigAsCode.Value, createFlags.ConfigAsCode.Name, false, "Use Config As Code for the project")
flags.StringArrayVarP(&createFlags.Tag.Value, createFlags.Tag.Name, "t", []string{}, "Tag to apply to project, must use canonical name: <tag_set>/<tag_name>")
convert.RegisterCacFlags(flags, createFlags.ProjectConvertFlags)
flags.SortFlags = false

Expand All @@ -84,6 +88,17 @@ func createRun(opts *CreateOptions) error {
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)
if opts.ConfigAsCode.Value {
opts.ConvertOptions.Project.Value = opts.Name.Value
Expand Down Expand Up @@ -131,6 +146,17 @@ func PromptMissing(opts *CreateOptions) ([]cmd.Dependable, error) {

nestedOpts = append(nestedOpts, opts)

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

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

configAsCodeOpts, err := PromptForConfigAsCode(opts)
opts.ConvertOptions.Project.Value = opts.Name.Value
if err != nil {
Expand Down
22 changes: 21 additions & 1 deletion pkg/cmd/project/create/create_opts.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,26 @@ import (
"github.com/OctopusDeploy/cli/pkg/cmd/project/shared"
"github.com/OctopusDeploy/cli/pkg/output"
"github.com/OctopusDeploy/cli/pkg/util/flag"
"github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/client"
"github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/projectgroups"
"github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/projects"
"github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/tagsets"
)

type ConvertProjectToConfigAsCodeCallback func() (cmd.Dependable, error)

type GetAllGroupsCallback func() ([]*projectgroups.ProjectGroup, error)

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

type CreateOptions struct {
*CreateFlags
*cmd.Dependencies
*projectConvert.ConvertOptions
GetAllGroupsCallback shared.GetAllGroupsCallback
CreateProjectGroupCallback shared.CreateProjectGroupCallback
ConvertProjectCallback ConvertProjectToConfigAsCodeCallback
GetAllTagsCallback GetAllTagSetsCallback
}

func NewCreateOptions(createFlags *CreateFlags, dependencies *cmd.Dependencies) *CreateOptions {
Expand All @@ -34,6 +39,7 @@ func NewCreateOptions(createFlags *CreateFlags, dependencies *cmd.Dependencies)
GetAllGroupsCallback: func() ([]*projectgroups.ProjectGroup, error) { return shared.GetAllGroups(*dependencies.Client) },
CreateProjectGroupCallback: func() (string, cmd.Dependable, error) { return shared.CreateProjectGroup(dependencies) },
ConvertProjectCallback: func() (cmd.Dependable, error) { return convertProjectCallback(convertOptions) },
GetAllTagsCallback: getAllTagSetsCallback(dependencies.Client),
}
}

Expand All @@ -57,6 +63,7 @@ func (co *CreateOptions) Commit() error {

project := projects.NewProject(co.Name.Value, lifecycle.GetID(), projectGroup.GetID())
project.Description = co.Description.Value
project.ProjectTags = co.Tag.Value

createdProject, err := co.Client.Projects.Add(project)
if err != nil {
Expand All @@ -76,7 +83,20 @@ func (co *CreateOptions) Commit() error {

func (co *CreateOptions) GenerateAutomationCmd() {
if !co.NoPrompt {
autoCmd := flag.GenerateAutomationCmd(co.CmdPath, co.Name, co.Description, co.Group, co.Lifecycle)
autoCmd := flag.GenerateAutomationCmd(co.CmdPath, co.Name, co.Description, co.Group, co.Lifecycle, co.Tag)
fmt.Fprintf(co.Out, "%s\n", autoCmd)
}
}

func getAllTagSetsCallback(client *client.Client) GetAllTagSetsCallback {
return func() ([]*tagsets.TagSet, error) {
query := tagsets.TagSetsQuery{
Scopes: []string{string(tagsets.TagSetScopeProject)},
}
result, err := tagsets.Get(client, client.GetSpaceID(), query)
if err != nil {
return nil, err
}
return result.Items, nil
}
}
12 changes: 7 additions & 5 deletions pkg/cmd/project/list/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,10 @@ func NewCmdList(f factory.Factory) *cobra.Command {
}

type ProjectAsJson struct {
Id string `json:"Id"`
Name string `json:"Name"`
Description string `json:"Description"`
Id string `json:"Id"`
Name string `json:"Name"`
Description string `json:"Description"`
ProjectTags []string `json:"ProjectTags,omitempty"`
}

func listRun(cmd *cobra.Command, f factory.Factory) error {
Expand All @@ -51,12 +52,13 @@ func listRun(cmd *cobra.Command, f factory.Factory) error {
Id: p.GetID(),
Name: p.GetName(),
Description: p.Description,
ProjectTags: p.ProjectTags,
}
},
Table: output.TableDefinition[*projects.Project]{
Header: []string{"NAME", "DESCRIPTION"},
Header: []string{"NAME", "DESCRIPTION", "TAGS"},
Row: func(p *projects.Project) []string {
return []string{output.Bold(p.Name), p.Description}
return []string{output.Bold(p.Name), p.Description, output.FormatAsList(p.ProjectTags)}
},
},
Basic: func(p *projects.Project) string {
Expand Down
2 changes: 2 additions & 0 deletions pkg/cmd/project/project.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
cmdDisconnect "github.com/OctopusDeploy/cli/pkg/cmd/project/disconnect"
cmdEnable "github.com/OctopusDeploy/cli/pkg/cmd/project/enable"
cmdList "github.com/OctopusDeploy/cli/pkg/cmd/project/list"
cmdTag "github.com/OctopusDeploy/cli/pkg/cmd/project/tag"
cmdVariables "github.com/OctopusDeploy/cli/pkg/cmd/project/variables"
cmdView "github.com/OctopusDeploy/cli/pkg/cmd/project/view"
"github.com/OctopusDeploy/cli/pkg/constants"
Expand Down Expand Up @@ -47,6 +48,7 @@ func NewCmdProject(f factory.Factory) *cobra.Command {
cmd.AddCommand(cmdVariables.NewCmdVariables(f))
cmd.AddCommand(cmdClone.NewCmdClone(f))
cmd.AddCommand(cmdBranch.NewCmdBranch(f))
cmd.AddCommand(cmdTag.NewCmdTag(f))

return cmd
}
148 changes: 148 additions & 0 deletions pkg/cmd/project/tag/tag.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
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/projects"
"github.com/spf13/cobra"
)

const (
FlagTag = "tag"
FlagProject = "project"
)

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

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

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

cmd := &cobra.Command{
Use: "tag",
Short: "Override tags for a project",
Long: "Override tags for a project in Octopus Deploy",
Example: heredoc.Docf("$ %s project tag Project-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 project, must use canonical name: <tag_set>/<tag_name>")
flags.StringVar(&createFlags.Project.Value, createFlags.Project.Name, "", "Name or ID of the project 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{}

project, err := AskProjects(opts.Ask, opts.Out, opts.Project.Value, opts.GetProjectsCallback, opts.GetProjectCallback)
if err != nil {
return nil, err
}
opts.project = project
opts.Project.Value = project.Name

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

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

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

func AskProjects(ask question.Asker, out io.Writer, value string, getProjectsCallback GetProjectsCallback, getProjectCallback GetProjectCallback) (*projects.Project, error) {
if value != "" {
project, err := getProjectCallback(value)
if err != nil {
return nil, err
}
return project, nil
}

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

project, err := selectors.Select(ask, "Select the Project you would like to update", getProjectsCallback, func(item *projects.Project) string {
return item.Name
})
if err != nil {
return nil, err
}

return project, nil
}
Loading