Skip to content

Commit c5fae1d

Browse files
committed
WIP
1 parent 03ec83b commit c5fae1d

3 files changed

Lines changed: 628 additions & 0 deletions

File tree

Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
package deprovision_project
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"slices"
7+
8+
"github.com/MakeNowJust/heredoc/v2"
9+
"github.com/OctopusDeploy/cli/pkg/cmd"
10+
"github.com/OctopusDeploy/cli/pkg/cmd/ephemeralenvironment/util"
11+
"github.com/OctopusDeploy/cli/pkg/cmd/runbook/shared"
12+
"github.com/OctopusDeploy/cli/pkg/constants"
13+
"github.com/OctopusDeploy/cli/pkg/factory"
14+
"github.com/OctopusDeploy/cli/pkg/output"
15+
"github.com/OctopusDeploy/cli/pkg/question/selectors"
16+
"github.com/OctopusDeploy/cli/pkg/util/flag"
17+
"github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/channels"
18+
"github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/environments/v2/ephemeralenvironments"
19+
"github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/projects"
20+
"github.com/spf13/cobra"
21+
)
22+
23+
const (
24+
FlagName = "name"
25+
FlagProject = "project"
26+
)
27+
28+
type DeprovisionProjectFlags struct {
29+
Name *flag.Flag[string]
30+
Project *flag.Flag[string]
31+
}
32+
33+
func NewDeprovisionProjectFlags() *DeprovisionProjectFlags {
34+
return &DeprovisionProjectFlags{
35+
Name: flag.New[string](FlagName, false),
36+
Project: flag.New[string](FlagProject, false),
37+
}
38+
}
39+
40+
type DeprovisionProjectOptions struct {
41+
*DeprovisionProjectFlags
42+
*cmd.Dependencies
43+
Cmd *cobra.Command
44+
GetConfiguredProjectsCallback func() ([]*projects.Project, error)
45+
GetAllEphemeralEnvironments func() ([]*ephemeralenvironments.EphemeralEnvironment, error)
46+
}
47+
48+
func NewDeprovisionProjectOptions(deprovisionFlags *DeprovisionProjectFlags, dependencies *cmd.Dependencies, command *cobra.Command) *DeprovisionProjectOptions {
49+
return &DeprovisionProjectOptions{
50+
DeprovisionProjectFlags: deprovisionFlags,
51+
Dependencies: dependencies,
52+
Cmd: command,
53+
GetConfiguredProjectsCallback: func() ([]*projects.Project, error) {
54+
return getConfiguredProjects(dependencies)
55+
},
56+
GetAllEphemeralEnvironments: func() ([]*ephemeralenvironments.EphemeralEnvironment, error) {
57+
return getEphemeralEnvironments(dependencies)
58+
},
59+
}
60+
}
61+
62+
func getConfiguredProjects(dependencies *cmd.Dependencies) ([]*projects.Project, error) {
63+
allProjects, err := shared.GetAllProjects(dependencies.Client)
64+
if err != nil {
65+
return nil, err
66+
}
67+
68+
var filteredProjects []*projects.Project
69+
70+
for _, project := range allProjects {
71+
projectChannels, err := dependencies.Client.Projects.GetChannels(project)
72+
if err != nil {
73+
return nil, fmt.Errorf("failed to get channels for project '%s': %w", project.GetName(), err)
74+
}
75+
76+
if slices.ContainsFunc(projectChannels, func(channel *channels.Channel) bool {
77+
return channel.Type == "EphemeralEnvironment"
78+
}) {
79+
filteredProjects = append(filteredProjects, project)
80+
}
81+
}
82+
83+
if len(filteredProjects) == 0 {
84+
return nil, fmt.Errorf("no configured projects - configure a project with an ephemeral environment channel before creating an ephemeral environment")
85+
}
86+
87+
return filteredProjects, nil
88+
}
89+
90+
func getEphemeralEnvironments(dependencies *cmd.Dependencies) ([]*ephemeralenvironments.EphemeralEnvironment, error) {
91+
allEnvironments, err := ephemeralenvironments.GetAll(dependencies.Client, dependencies.Space.ID)
92+
if err != nil {
93+
return nil, err
94+
}
95+
96+
if len(allEnvironments.Items) == 0 {
97+
return nil, fmt.Errorf("no ephemeral environments found")
98+
}
99+
100+
return allEnvironments.Items, nil
101+
}
102+
103+
func NewCmdDeprovisionProject(f factory.Factory) *cobra.Command {
104+
createFlags := NewDeprovisionProjectFlags()
105+
106+
cmd := &cobra.Command{
107+
Use: "deprovision-project",
108+
Short: "Deprovision an ephemeral environment for a project",
109+
Long: "Deprovision an ephemeral environment in Octopus Deploy for a specific project",
110+
Example: heredoc.Docf("$ %s ephemeral-environment deprovision-project", constants.ExecutableName),
111+
Aliases: []string{"new"},
112+
RunE: func(c *cobra.Command, _ []string) error {
113+
opts := NewDeprovisionProjectOptions(createFlags, cmd.NewDependencies(f, c), c)
114+
115+
return DeprovisionEphemeralEnvironmentProject(c, opts)
116+
},
117+
}
118+
119+
flags := cmd.Flags()
120+
flags.StringVarP(&createFlags.Name.Value, createFlags.Name.Name, "n", "", "Name of the environment")
121+
flags.StringVarP(&createFlags.Project.Value, createFlags.Project.Name, "p", "", "Name of the project")
122+
123+
return cmd
124+
}
125+
126+
func DeprovisionEphemeralEnvironmentProject(cmd *cobra.Command, opts *DeprovisionProjectOptions) error {
127+
if !opts.NoPrompt {
128+
err := PromptMissing(opts)
129+
if err != nil {
130+
return err
131+
}
132+
}
133+
134+
if opts.Name.Value == "" {
135+
return fmt.Errorf("environment name is required")
136+
}
137+
138+
if opts.Project.Value == "" {
139+
return fmt.Errorf("project name is required")
140+
}
141+
142+
projectResource, err := projects.GetByName(opts.Client, opts.Space.ID, opts.Project.Value)
143+
if err != nil {
144+
return fmt.Errorf("failed to find project '%s': %w", opts.Project.Value, err)
145+
}
146+
147+
environmentResource, err := util.GetByName(opts.Client, opts.Name.Value, opts.Space.ID)
148+
if err != nil {
149+
return fmt.Errorf("failed to retrieve ephemeral environments: %w", err)
150+
}
151+
152+
if environmentResource == nil {
153+
return fmt.Errorf("no ephemeral environment found with name '%s'", opts.Name.Value)
154+
}
155+
156+
environmentId := environmentResource.ID
157+
158+
cmd.Printf("Deprovisioning ephemeral environment '%s' with id '%s' for project '%s'...\n", opts.Name.Value, environmentId, opts.Project.Value)
159+
160+
projectId := projectResource.GetID()
161+
162+
deprovisionedEnv, err := ephemeralenvironments.DeprovisionForProject(opts.Client, opts.Space.ID, environmentId, projectId)
163+
if err != nil {
164+
return err
165+
}
166+
167+
cmd.Printf("\nSuccessfully deprovisioned ephemeral environment for project '%s' with id '%s'.\n", opts.Project.Value, opts.Name.Value)
168+
169+
outputResult(opts, cmd, deprovisionedEnv)
170+
171+
return nil
172+
}
173+
174+
func outputResult(deprovisionEnvironmentOptions *DeprovisionProjectOptions, command *cobra.Command, response *ephemeralenvironments.DeprovisionEphemeralEnvironmentProjectResponse) {
175+
outputFormat, err := command.Flags().GetString(constants.FlagOutputFormat)
176+
177+
if err != nil {
178+
outputFormat = constants.OutputFormatTable
179+
}
180+
181+
switch outputFormat {
182+
case constants.OutputFormatBasic:
183+
command.Printf("%s \t %s \n", response.DeprovisioningRun.RunbookRunID, response.DeprovisioningRun.TaskId)
184+
case constants.OutputFormatJson:
185+
data, err := json.Marshal(response)
186+
if err != nil { // shouldn't happen but fallback in case
187+
command.PrintErrln(err)
188+
} else {
189+
_, _ = command.OutOrStdout().Write(data)
190+
command.Println()
191+
}
192+
default: // table
193+
t := output.NewTable(command.OutOrStdout())
194+
t.AddRow(output.Bold("Runbook Run ID"), output.Bold("Server Task ID"))
195+
t.AddRow(response.DeprovisioningRun.RunbookRunID, response.DeprovisioningRun.TaskId)
196+
197+
err := t.Print()
198+
if err != nil {
199+
command.PrintErrln(err)
200+
}
201+
}
202+
}
203+
204+
func PromptMissing(opts *DeprovisionProjectOptions) error {
205+
if opts.Project.Value == "" {
206+
opts.Cmd.Printf(" Choose from projects configured with an ephemeral environment channel.\n")
207+
project, err := selectors.Select(opts.Ask, "Select a project:", opts.GetConfiguredProjectsCallback, func(project *projects.Project) string { return project.GetName() })
208+
if err != nil {
209+
return err
210+
}
211+
opts.Project.Value = project.GetName()
212+
}
213+
214+
if opts.Name.Value == "" {
215+
environment, err := selectors.Select(opts.Ask, "Please select the name of the environment you wish to deprovision:", opts.GetAllEphemeralEnvironments, func(env *ephemeralenvironments.EphemeralEnvironment) string { return env.Name })
216+
if err != nil {
217+
return err
218+
}
219+
opts.Name.Value = environment.Name
220+
}
221+
return nil
222+
}

0 commit comments

Comments
 (0)