Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: runbook snapshot list #340

Merged
merged 3 commits into from
Mar 13, 2024
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: 2 additions & 0 deletions pkg/cmd/runbook/runbook.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"github.com/MakeNowJust/heredoc/v2"
cmdList "github.com/OctopusDeploy/cli/pkg/cmd/runbook/list"
cmdRun "github.com/OctopusDeploy/cli/pkg/cmd/runbook/run"
cmdSnapshot "github.com/OctopusDeploy/cli/pkg/cmd/runbook/snapshot"
"github.com/OctopusDeploy/cli/pkg/constants"
"github.com/OctopusDeploy/cli/pkg/constants/annotations"
"github.com/OctopusDeploy/cli/pkg/factory"
Expand All @@ -26,5 +27,6 @@ func NewCmdRunbook(f factory.Factory) *cobra.Command {

cmd.AddCommand(cmdList.NewCmdList(f))
cmd.AddCommand(cmdRun.NewCmdRun(f))
cmd.AddCommand(cmdSnapshot.NewCmdSnapshot(f))
return cmd
}
156 changes: 156 additions & 0 deletions pkg/cmd/runbook/snapshot/list/list.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
package list

import (
"errors"
"github.com/MakeNowJust/heredoc/v2"
"github.com/OctopusDeploy/cli/pkg/apiclient"
"github.com/OctopusDeploy/cli/pkg/constants"
"github.com/OctopusDeploy/cli/pkg/factory"
"github.com/OctopusDeploy/cli/pkg/output"
"github.com/OctopusDeploy/cli/pkg/question/selectors"
"github.com/OctopusDeploy/cli/pkg/util/flag"
"github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/projects"
"github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/runbooks"
"github.com/spf13/cobra"
"math"
"time"
)

const (
FlagProject = "project"
FlagRunbook = "runbook"
FlagLimit = "limit"
)

type ListFlags struct {
Project *flag.Flag[string]
Runbook *flag.Flag[string]
Limit *flag.Flag[int32]
}

func NewListFlags() *ListFlags {
return &ListFlags{
Project: flag.New[string](FlagProject, false),
Runbook: flag.New[string](FlagRunbook, false),
Limit: flag.New[int32](FlagLimit, false),
}
}

type SnapshotsAsJson struct {
Id string `json:"Id"`
Name string `json:"Name"`
Assembled *time.Time `json:"Assembled"`
}

func NewCmdList(f factory.Factory) *cobra.Command {
listFlags := NewListFlags()
cmd := &cobra.Command{
Use: "list",
Short: "List runbook snapshots",
Long: "List runbook snapshots in Octopus Deploy",
Example: heredoc.Docf(`
$ %[1]s runbook snapshot list --project "Deploy Web App" --runbook "Run maintenance"
$ %[1]s runbook snapshot ls
`, constants.ExecutableName),
Aliases: []string{"ls"},
RunE: func(cmd *cobra.Command, args []string) error {
return listRun(cmd, f, listFlags)
},
}

flags := cmd.Flags()
flags.StringVarP(&listFlags.Project.Value, listFlags.Project.Name, "p", "", "Name or ID of the project to list runbook snapshots for")
flags.StringVarP(&listFlags.Runbook.Value, listFlags.Runbook.Name, "r", "", "Name or ID of the runbook to list snapshots for")
flags.Int32Var(&listFlags.Limit.Value, listFlags.Limit.Name, math.MaxInt32, "Limit the maximum number of results that will be returned")

return cmd
}

func listRun(cmd *cobra.Command, f factory.Factory, flags *ListFlags) error {
client, err := f.GetSpacedClient(apiclient.NewRequester(cmd))
if err != nil {
return err
}

outputFormat, err := cmd.Flags().GetString(constants.FlagOutputFormat)
if err != nil { // should never happen, but fallback if it does
outputFormat = constants.OutputFormatTable
}

projectNameOrID := flags.Project.Value
runbookNameOrID := flags.Runbook.Value

var selectedProject *projects.Project
var selectedRunbook *runbooks.Runbook
if f.IsPromptEnabled() { // this would be AskQuestions if it were bigger
if projectNameOrID == "" {
selectedProject, err = selectors.Project("Select the project to list runbook snapshots for", client, f.Ask)
if err != nil {
return err
}
} else { // project name is already provided, fetch the object because it's needed for further questions
selectedProject, err = selectors.FindProject(client, projectNameOrID)
if err != nil {
return err
}
if !constants.IsProgrammaticOutputFormat(outputFormat) {
cmd.Printf("Project %s\n", output.Cyan(selectedProject.Name))
}
}

if runbookNameOrID == "" {
selectedRunbook, err = selectors.Runbook("Select the runbook to list snapshots for", client, f.Ask, selectedProject.GetID())
if err != nil {
return err
}
} else { // project name is already provided, fetch the object because it's needed for further questions
selectedRunbook, err = selectors.FindRunbook(client, runbookNameOrID, selectedProject.GetID())
if err != nil {
return err
}
if !constants.IsProgrammaticOutputFormat(outputFormat) {
cmd.Printf("Runbook %s\n", output.Cyan(selectedRunbook.Name))
}
}
} else { // we don't have the executions API backing us and allowing NameOrID; we need to do the lookup ourselves
if projectNameOrID == "" {
return errors.New("project must be specified")
}
selectedProject, err = selectors.FindProject(client, projectNameOrID)
if err != nil {
return err
}

if runbookNameOrID == "" {
return errors.New("runbook must be specified")
}
selectedRunbook, err = selectors.FindRunbook(client, runbookNameOrID, selectedProject.GetID())
if err != nil {
return err
}
}

allSnapshots, err := runbooks.ListSnapshots(client, client.GetSpaceID(), selectedProject.GetID(), selectedRunbook.GetID(), int(flags.Limit.Value))
if err != nil {
return err
}

return output.PrintArray(allSnapshots.Items, cmd, output.Mappers[*runbooks.RunbookSnapshot]{
Json: func(s *runbooks.RunbookSnapshot) any {
return SnapshotsAsJson{
Id: s.GetID(),
Name: s.Name,
Assembled: s.Assembled,
}
},
Table: output.TableDefinition[*runbooks.RunbookSnapshot]{
Header: []string{"ID", "NAME", "ASSEMBLED"},
Row: func(s *runbooks.RunbookSnapshot) []string {
return []string{s.GetID(), output.Bold(s.Name), s.Assembled.Format(time.RFC1123Z)}
},
},
Basic: func(s *runbooks.RunbookSnapshot) string {
return s.Name
},
})
}
24 changes: 24 additions & 0 deletions pkg/cmd/runbook/snapshot/snapshot.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package snapshot

import (
"github.com/MakeNowJust/heredoc/v2"
cmdList "github.com/OctopusDeploy/cli/pkg/cmd/runbook/snapshot/list"
"github.com/OctopusDeploy/cli/pkg/constants"
"github.com/OctopusDeploy/cli/pkg/factory"
"github.com/spf13/cobra"
)

func NewCmdSnapshot(f factory.Factory) *cobra.Command {
cmd := &cobra.Command{
Use: "snapshot <command>",
Short: "Manage runbook snapshots",
Long: "Manage runbook snapshots in Octopus Deploy",
Example: heredoc.Docf(`
$ %[1]s runbook snapshot create
$ %[1]s runbook snapshot list
`, constants.ExecutableName),
}

cmd.AddCommand(cmdList.NewCmdList(f))
return cmd
}
40 changes: 40 additions & 0 deletions pkg/question/selectors/runbooks.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package selectors

import (
"errors"
"github.com/OctopusDeploy/cli/pkg/question"
octopusApiClient "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/client"
"github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/runbooks"
"math"
)

func Runbook(questionText string, client *octopusApiClient.Client, ask question.Asker, projectID string) (*runbooks.Runbook, error) {
existingRunbooks, err := runbooks.List(client, client.GetSpaceID(), projectID, "", math.MaxInt32)
if err != nil {
return nil, err
}

if len(existingRunbooks.Items) == 0 {
return nil, errors.New("no runbooks found")
}

if len(existingRunbooks.Items) == 1 {
return existingRunbooks.Items[0], nil
}

return question.SelectMap(ask, questionText, existingRunbooks.Items, func(r *runbooks.Runbook) string {
return r.Name
})
}

func FindRunbook(client *octopusApiClient.Client, runbookIdentifier string, projectID string) (*runbooks.Runbook, error) {
runbook, err := runbooks.GetByID(client, client.GetSpaceID(), runbookIdentifier)
if err != nil {
runbook, err = runbooks.GetByName(client, client.GetSpaceID(), projectID, runbookIdentifier)
if err != nil {
return nil, err
}
}

return runbook, nil
}
Loading