Skip to content

Commit 5d0e58b

Browse files
authored
Merge pull request #563 from OctopusDeploy/sjc/add-deprovision-project
Deprovision a project for an ephemeral environment
2 parents 4dbefbd + 96b9bba commit 5d0e58b

5 files changed

Lines changed: 604 additions & 4 deletions

File tree

pkg/cmd/ephemeralenvironment/deprovision-environment/deprovision_environment.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,9 @@ func DeprovisionEnvironmentRun(deprovisionEnvironmentOptions *DeprovisionEnviron
105105
return err
106106
}
107107

108-
util.OutPutDeprovisionResult(deprovisionEnvironmentOptions.Name.Value, command, response.DeprovisioningRuns)
108+
command.Printf("Deprovisioning ephemeral environment '%s' with id '%s'...\n", deprovisionEnvironmentOptions.Name.Value, environment.ID)
109+
110+
util.OutputDeprovisionResult(command, response.DeprovisioningRuns)
109111

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

0 commit comments

Comments
 (0)