Skip to content

Commit 3c4c631

Browse files
committed
ROSAENG-61173 | fix: complete delete cluster command coverage
Signed-off-by: lufreita <lufreita@redhat.com>
1 parent 6ef37e7 commit 3c4c631

2 files changed

Lines changed: 317 additions & 5 deletions

File tree

cmd/dlt/cluster/cmd.go

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -80,23 +80,36 @@ func run(_ *cobra.Command, _ []string) {
8080
r := rosa.NewRuntime().WithAWS().WithOCM()
8181
defer r.Cleanup()
8282

83+
err := runWithRuntime(r, confirm.Confirm, func(clusterKey string) {
84+
uninstallLogs.Cmd.Run(uninstallLogs.Cmd, []string{clusterKey})
85+
})
86+
if err != nil {
87+
r.Reporter.Errorf("%s", err)
88+
os.Exit(1)
89+
}
90+
}
91+
92+
func runWithRuntime(
93+
r *rosa.Runtime,
94+
confirmFn func(string, ...interface{}) bool,
95+
uninstallLogsFn func(string),
96+
) error {
8397
clusterKey := r.GetClusterKey()
8498

8599
if args.bestEffort {
86100
r.Reporter.Warnf("Deleting cluster '%s' with 'best effort' means that certain resources may be left behind"+
87101
" in AWS account '%s'. These resources will need to be deleted manually.", clusterKey, r.Creator.AccountID)
88102
}
89103

90-
if !confirm.Confirm("delete cluster %s", clusterKey) {
91-
os.Exit(0)
104+
if !confirmFn("delete cluster %s", clusterKey) {
105+
return nil
92106
}
93107

94108
cluster := r.FetchCluster()
95109

96110
err := handleClusterDelete(r, cluster, clusterKey, args.bestEffort)
97111
if err != nil {
98-
r.Reporter.Errorf("%s", err)
99-
os.Exit(1)
112+
return fmt.Errorf("failed to delete cluster '%s': %w", clusterKey, err)
100113
}
101114

102115
if cluster.AWS().STS().RoleARN() != "" {
@@ -122,13 +135,15 @@ func run(_ *cobra.Command, _ []string) {
122135
}
123136
if args.watch {
124137
arguments.DisableRegionDeprecationWarning = true // disable region deprecation warning
125-
uninstallLogs.Cmd.Run(uninstallLogs.Cmd, []string{clusterKey})
138+
uninstallLogsFn(clusterKey)
126139
arguments.DisableRegionDeprecationWarning = false // enable region deprecation again
127140
} else {
128141
r.Reporter.Infof("To watch your cluster uninstallation logs, run 'rosa logs uninstall -c %s --watch'",
129142
clusterKey,
130143
)
131144
}
145+
146+
return nil
132147
}
133148

134149
func handleClusterDelete(r *rosa.Runtime, cluster *cmv1.Cluster, clusterKey string, bestEffort bool) error {

cmd/dlt/cluster/cmd_test.go

Lines changed: 297 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,43 @@ package cluster
22

33
import (
44
"fmt"
5+
"io"
56
"net/http"
7+
"os"
68

79
. "github.com/onsi/ginkgo/v2"
810
. "github.com/onsi/gomega"
911
cmv1 "github.com/openshift-online/ocm-sdk-go/clustersmgmt/v1"
1012
. "github.com/openshift-online/ocm-sdk-go/testing"
1113

14+
"github.com/openshift/rosa/pkg/arguments"
15+
"github.com/openshift/rosa/pkg/interactive"
1216
"github.com/openshift/rosa/pkg/test"
1317
)
1418

19+
func captureRun(fn func() error) (string, string, error) {
20+
rout, wout, _ := os.Pipe()
21+
rerr, werr, _ := os.Pipe()
22+
origOut := os.Stdout
23+
origErr := os.Stderr
24+
defer func() {
25+
os.Stdout = origOut
26+
os.Stderr = origErr
27+
}()
28+
os.Stdout = wout
29+
os.Stderr = werr
30+
31+
var err error
32+
go func() {
33+
err = fn()
34+
wout.Close()
35+
werr.Close()
36+
}()
37+
stdout, _ := io.ReadAll(rout)
38+
stderr, _ := io.ReadAll(rerr)
39+
return string(stdout), string(stderr), err
40+
}
41+
1542
var _ = Describe("Delete cluster", func() {
1643
var (
1744
t *test.TestingRuntime
@@ -21,6 +48,276 @@ var _ = Describe("Delete cluster", func() {
2148
BeforeEach(func() {
2249
t = test.NewTestRuntime()
2350
clusterId = test.MockClusterID
51+
args.bestEffort = false
52+
args.watch = false
53+
interactive.SetEnabled(false)
54+
arguments.DisableRegionDeprecationWarning = false
55+
})
56+
57+
Context("runWithRuntime", func() {
58+
stubConfirm := func(string, ...interface{}) bool { return true }
59+
stubNoConfirm := func(string, ...interface{}) bool { return false }
60+
stubLogs := func(string) {}
61+
62+
BeforeEach(func() {
63+
args.bestEffort = false
64+
args.watch = false
65+
interactive.SetEnabled(false)
66+
arguments.DisableRegionDeprecationWarning = false
67+
})
68+
69+
It("runs the non-STS happy path and prints the uninstall log hint", func() {
70+
clusterReady := test.MockCluster(func(c *cmv1.ClusterBuilder) {
71+
c.State(cmv1.ClusterStateReady)
72+
c.AWS(cmv1.NewAWS().STS(cmv1.NewSTS()))
73+
})
74+
t.SetCluster(clusterId, clusterReady)
75+
76+
statusBody := fmt.Sprintf(`{
77+
"kind": "ClusterStatus",
78+
"id": "%s",
79+
"state": "ready"
80+
}`, clusterId)
81+
t.ApiServer.AppendHandlers(RespondWithJSON(http.StatusOK, statusBody))
82+
t.ApiServer.AppendHandlers(RespondWithJSON(
83+
http.StatusOK, test.FormatClusterList([]*cmv1.Cluster{clusterReady})))
84+
t.ApiServer.AppendHandlers(RespondWithJSON(http.StatusOK, ""))
85+
86+
stdout, stderr, err := captureRun(func() error {
87+
return runWithRuntime(t.RosaRuntime, stubConfirm, stubLogs)
88+
})
89+
Expect(err).NotTo(HaveOccurred())
90+
Expect(stderr).To(BeEmpty())
91+
Expect(stdout).To(ContainSubstring("will start uninstalling"))
92+
Expect(stdout).To(ContainSubstring("rosa logs uninstall -c"))
93+
Expect(stdout).To(ContainSubstring("--watch"))
94+
})
95+
96+
It("returns cleanly when deletion is not confirmed", func() {
97+
clusterReady := test.MockCluster(func(c *cmv1.ClusterBuilder) {
98+
c.State(cmv1.ClusterStateReady)
99+
c.AWS(cmv1.NewAWS().STS(cmv1.NewSTS()))
100+
})
101+
t.SetCluster(clusterId, clusterReady)
102+
103+
stdout, stderr, err := captureRun(func() error {
104+
return runWithRuntime(t.RosaRuntime, stubNoConfirm, stubLogs)
105+
})
106+
Expect(err).NotTo(HaveOccurred())
107+
Expect(stderr).To(BeEmpty())
108+
Expect(stdout).To(BeEmpty())
109+
})
110+
111+
It("prints the best-effort warning and passes the flag through", func() {
112+
clusterReady := test.MockCluster(func(c *cmv1.ClusterBuilder) {
113+
c.State(cmv1.ClusterStateReady)
114+
c.AWS(cmv1.NewAWS().STS(cmv1.NewSTS()))
115+
})
116+
t.SetCluster(clusterId, clusterReady)
117+
args.bestEffort = true
118+
119+
statusBody := fmt.Sprintf(`{
120+
"kind": "ClusterStatus",
121+
"id": "%s",
122+
"state": "ready"
123+
}`, clusterId)
124+
t.ApiServer.AppendHandlers(RespondWithJSON(http.StatusOK, statusBody))
125+
t.ApiServer.AppendHandlers(RespondWithJSON(
126+
http.StatusOK, test.FormatClusterList([]*cmv1.Cluster{clusterReady})))
127+
t.ApiServer.AppendHandlers(RespondWithJSON(http.StatusOK, ""))
128+
129+
stdout, stderr, err := captureRun(func() error {
130+
return runWithRuntime(t.RosaRuntime, stubConfirm, stubLogs)
131+
})
132+
Expect(err).NotTo(HaveOccurred())
133+
Expect(stderr).To(ContainSubstring("best effort"))
134+
Expect(stderr).To(ContainSubstring("certain resources may be left behind"))
135+
Expect(stdout).To(ContainSubstring("will start uninstalling"))
136+
})
137+
138+
It("prints STS cleanup guidance for clusters with operator roles", func() {
139+
clusterReady := test.MockCluster(func(c *cmv1.ClusterBuilder) {
140+
c.State(cmv1.ClusterStateReady)
141+
c.AWS(cmv1.NewAWS().STS(
142+
cmv1.NewSTS().
143+
RoleARN("arn:aws:iam::123456789012:role/Installer").
144+
OIDCEndpointURL("https://oidc.example.com").
145+
OperatorRolePrefix("my-prefix").
146+
OperatorIAMRoles(
147+
cmv1.NewOperatorIAMRole().
148+
Name("ebs-cloud-credentials").
149+
Namespace("openshift-cluster-csi-drivers").
150+
RoleARN("arn:aws:iam::123456789012:role/op-role"),
151+
),
152+
))
153+
})
154+
t.SetCluster(clusterId, clusterReady)
155+
156+
statusBody := fmt.Sprintf(`{
157+
"kind": "ClusterStatus",
158+
"id": "%s",
159+
"state": "ready"
160+
}`, clusterId)
161+
t.ApiServer.AppendHandlers(RespondWithJSON(http.StatusOK, statusBody))
162+
t.ApiServer.AppendHandlers(RespondWithJSON(
163+
http.StatusOK, test.FormatClusterList([]*cmv1.Cluster{clusterReady})))
164+
t.ApiServer.AppendHandlers(RespondWithJSON(http.StatusOK, ""))
165+
166+
stdout, stderr, err := captureRun(func() error {
167+
return runWithRuntime(t.RosaRuntime, stubConfirm, stubLogs)
168+
})
169+
Expect(err).NotTo(HaveOccurred())
170+
Expect(stderr).To(BeEmpty())
171+
Expect(stdout).To(ContainSubstring("Operator IAM Roles:"))
172+
Expect(stdout).To(ContainSubstring("arn:aws:iam::123456789012:role/op-role"))
173+
Expect(stdout).To(ContainSubstring("OIDC Provider : https://oidc.example.com"))
174+
Expect(stdout).To(ContainSubstring("rosa delete operator-roles -c"))
175+
Expect(stdout).To(ContainSubstring("rosa delete oidc-provider -c"))
176+
})
177+
178+
It("prints STS cleanup guidance without operator role output when none remain", func() {
179+
clusterReady := test.MockCluster(func(c *cmv1.ClusterBuilder) {
180+
c.State(cmv1.ClusterStateReady)
181+
c.AWS(cmv1.NewAWS().STS(
182+
cmv1.NewSTS().
183+
RoleARN("arn:aws:iam::123456789012:role/Installer").
184+
OIDCEndpointURL("https://oidc-no-roles.example.com"),
185+
))
186+
})
187+
t.SetCluster(clusterId, clusterReady)
188+
189+
statusBody := fmt.Sprintf(`{
190+
"kind": "ClusterStatus",
191+
"id": "%s",
192+
"state": "ready"
193+
}`, clusterId)
194+
t.ApiServer.AppendHandlers(RespondWithJSON(http.StatusOK, statusBody))
195+
t.ApiServer.AppendHandlers(RespondWithJSON(
196+
http.StatusOK, test.FormatClusterList([]*cmv1.Cluster{clusterReady})))
197+
t.ApiServer.AppendHandlers(RespondWithJSON(http.StatusOK, ""))
198+
199+
stdout, stderr, err := captureRun(func() error {
200+
return runWithRuntime(t.RosaRuntime, stubConfirm, stubLogs)
201+
})
202+
Expect(err).NotTo(HaveOccurred())
203+
Expect(stderr).To(BeEmpty())
204+
Expect(stdout).NotTo(ContainSubstring("Operator IAM Roles:"))
205+
Expect(stdout).To(ContainSubstring("OIDC Provider : https://oidc-no-roles.example.com"))
206+
Expect(stdout).To(ContainSubstring("rosa delete operator-roles -c"))
207+
Expect(stdout).To(ContainSubstring("rosa delete oidc-provider -c"))
208+
})
209+
210+
It("runs uninstall logs when watch is enabled and restores the deprecation warning flag", func() {
211+
clusterReady := test.MockCluster(func(c *cmv1.ClusterBuilder) {
212+
c.State(cmv1.ClusterStateReady)
213+
c.AWS(cmv1.NewAWS().STS(cmv1.NewSTS()))
214+
})
215+
t.SetCluster(clusterId, clusterReady)
216+
args.watch = true
217+
218+
statusBody := fmt.Sprintf(`{
219+
"kind": "ClusterStatus",
220+
"id": "%s",
221+
"state": "ready"
222+
}`, clusterId)
223+
t.ApiServer.AppendHandlers(RespondWithJSON(http.StatusOK, statusBody))
224+
t.ApiServer.AppendHandlers(RespondWithJSON(
225+
http.StatusOK, test.FormatClusterList([]*cmv1.Cluster{clusterReady})))
226+
t.ApiServer.AppendHandlers(RespondWithJSON(http.StatusOK, ""))
227+
228+
var watchedClusterKey string
229+
var sawDisabledWarning bool
230+
watchStub := func(clusterKey string) {
231+
watchedClusterKey = clusterKey
232+
sawDisabledWarning = arguments.DisableRegionDeprecationWarning
233+
}
234+
235+
stdout, stderr, err := captureRun(func() error {
236+
return runWithRuntime(t.RosaRuntime, stubConfirm, watchStub)
237+
})
238+
Expect(err).NotTo(HaveOccurred())
239+
Expect(stderr).To(BeEmpty())
240+
Expect(stdout).To(ContainSubstring("will start uninstalling"))
241+
Expect(watchedClusterKey).To(Equal(clusterId))
242+
Expect(sawDisabledWarning).To(BeTrue())
243+
Expect(arguments.DisableRegionDeprecationWarning).To(BeFalse())
244+
Expect(stdout).NotTo(ContainSubstring("rosa logs uninstall -c"))
245+
})
246+
247+
It("returns the already-uninstalling path through the command wrapper", func() {
248+
clusterReady := test.MockCluster(func(c *cmv1.ClusterBuilder) {
249+
c.State(cmv1.ClusterStateReady)
250+
c.AWS(cmv1.NewAWS().STS(cmv1.NewSTS()))
251+
})
252+
t.SetCluster(clusterId, clusterReady)
253+
254+
statusBody := fmt.Sprintf(`{
255+
"kind": "ClusterStatus",
256+
"id": "%s",
257+
"state": "uninstalling"
258+
}`, clusterId)
259+
t.ApiServer.AppendHandlers(RespondWithJSON(http.StatusOK, statusBody))
260+
261+
stdout, stderr, err := captureRun(func() error {
262+
return runWithRuntime(t.RosaRuntime, stubConfirm, stubLogs)
263+
})
264+
Expect(err).NotTo(HaveOccurred())
265+
Expect(stderr).To(BeEmpty())
266+
Expect(stdout).To(ContainSubstring("already uninstalling"))
267+
Expect(stdout).To(ContainSubstring("rosa logs uninstall -c"))
268+
})
269+
270+
It("returns an error from GetClusterState through the command wrapper", func() {
271+
clusterReady := test.MockCluster(func(c *cmv1.ClusterBuilder) {
272+
c.State(cmv1.ClusterStateReady)
273+
c.AWS(cmv1.NewAWS().STS(cmv1.NewSTS()))
274+
})
275+
t.SetCluster(clusterId, clusterReady)
276+
277+
t.ApiServer.AppendHandlers(RespondWithJSON(http.StatusInternalServerError, ""))
278+
279+
stdout, stderr, err := captureRun(func() error {
280+
return runWithRuntime(t.RosaRuntime, stubConfirm, stubLogs)
281+
})
282+
Expect(err).To(HaveOccurred())
283+
Expect(stderr).To(BeEmpty())
284+
Expect(stdout).To(BeEmpty())
285+
Expect(err.Error()).To(ContainSubstring("failed to delete cluster"))
286+
Expect(err.Error()).To(ContainSubstring("expected response content type"))
287+
})
288+
289+
It("returns a delete error through the command wrapper", func() {
290+
clusterReady := test.MockCluster(func(c *cmv1.ClusterBuilder) {
291+
c.State(cmv1.ClusterStateReady)
292+
c.AWS(cmv1.NewAWS().STS(cmv1.NewSTS()))
293+
})
294+
t.SetCluster(clusterId, clusterReady)
295+
296+
statusBody := fmt.Sprintf(`{
297+
"kind": "ClusterStatus",
298+
"id": "%s",
299+
"state": "ready"
300+
}`, clusterId)
301+
t.ApiServer.AppendHandlers(RespondWithJSON(http.StatusOK, statusBody))
302+
t.ApiServer.AppendHandlers(RespondWithJSON(
303+
http.StatusOK, test.FormatClusterList([]*cmv1.Cluster{clusterReady})))
304+
t.ApiServer.AppendHandlers(RespondWithJSON(http.StatusForbidden, `{
305+
"kind": "Error",
306+
"id": "403",
307+
"href": "/api/clusters_mgmt/v1/errors/403",
308+
"code": "CLUSTERS-MGMT-403",
309+
"reason": "forbidden"
310+
}`))
311+
312+
stdout, stderr, err := captureRun(func() error {
313+
return runWithRuntime(t.RosaRuntime, stubConfirm, stubLogs)
314+
})
315+
Expect(err).To(HaveOccurred())
316+
Expect(stderr).To(BeEmpty())
317+
Expect(stdout).To(BeEmpty())
318+
Expect(err.Error()).To(ContainSubstring("failed to delete cluster"))
319+
Expect(err.Error()).To(ContainSubstring("forbidden"))
320+
})
24321
})
25322

26323
Context("handleClusterDelete", func() {

0 commit comments

Comments
 (0)