@@ -2,16 +2,43 @@ package cluster
22
33import (
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+
1542var _ = 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