@@ -27,6 +27,7 @@ import (
2727 "github.com/google/uuid"
2828 "go.uber.org/multierr"
2929 "kpt.dev/configsync/e2e"
30+ "kpt.dev/configsync/e2e/nomostest/retry"
3031 "kpt.dev/configsync/e2e/nomostest/testlogger"
3132)
3233
@@ -140,31 +141,69 @@ func (b *BitbucketClient) DeleteRepositories(names ...string) error {
140141 return deleteRepos (accessToken , names ... )
141142}
142143
144+ // TODO: This is a temporary workaround for the known issue
145+ // go/acm-e2e-testing#bitbucket-repository-deletion-failure
146+ // Until this is fixed, we are skipping removal of repos stuck in bad state
143147func deleteRepos (accessToken string , names ... string ) error {
144148 var errs error
145149 for _ , name := range names {
146- out , err := exec .Command ("curl" , "-sX" , "DELETE" ,
147- "-H" , fmt .Sprintf ("Authorization:Bearer %s" , accessToken ),
148- fmt .Sprintf ("https://api.bitbucket.org/2.0/repositories/%s/%s" ,
149- bitbucketWorkspace , name )).CombinedOutput ()
150-
150+ _ , err := retry .Retry (30 * time .Second , func () error {
151+ return deleteSingleRepo (accessToken , name )
152+ })
151153 if err != nil {
152- errs = multierr .Append (errs , fmt .Errorf ("%s: %w" , string (out ), err ))
153- }
154- if len (out ) != 0 {
155- errs = multierr .Append (errs , errors .New (string (out )))
154+ // Skip repositories that are currently unavailable
155+ if strings .Contains (err .Error (), "Repository currently not available" ) {
156+ fmt .Printf ("[BITBUCKET] Skipping repository %s: %v\n " , name , err )
157+ continue
158+ }
159+ errs = multierr .Append (errs , err )
156160 }
157161 }
158162 return errs
159163}
160164
165+ // deleteSingleRepo deletes a single repository
166+ func deleteSingleRepo (accessToken , name string ) error {
167+ out , err := exec .Command ("curl" , "-sX" , "DELETE" ,
168+ "-H" , fmt .Sprintf ("Authorization:Bearer %s" , accessToken ),
169+ fmt .Sprintf ("https://api.bitbucket.org/2.0/repositories/%s/%s" ,
170+ bitbucketWorkspace , name )).CombinedOutput ()
171+
172+ if err != nil {
173+ return fmt .Errorf ("%s: %w" , string (out ), err )
174+ }
175+ if len (out ) != 0 {
176+ return errors .New (string (out ))
177+ }
178+ return nil
179+ }
180+
161181// DeleteObsoleteRepos deletes all repos that were created more than 24 hours ago.
162182func (b * BitbucketClient ) DeleteObsoleteRepos () error {
163183 accessToken , err := b .refreshAccessToken ()
164184 if err != nil {
165185 return err
166186 }
167187
188+ // Count total obsolete repos first
189+ totalObsoleteRepos , err := b .countObsoleteRepos (accessToken )
190+ if err != nil {
191+ return err
192+ }
193+
194+ // TODO: This is a temporary workaround for the known issue
195+ // go/acm-e2e-testing#bitbucket-repository-deletion-failure
196+ // Until this is fixed, we are skipping removal of repos stuck in bad state.
197+ //
198+ // The 1000 limit is a guestimation that does not exceed the 1GB limit of
199+ // workspace for a free account. If that number is exceeded, engineer
200+ // will need to replace the workspace until the root cause of unresponsive
201+ // repo is known.
202+ if totalObsoleteRepos > 1000 {
203+ return fmt .Errorf ("total number of obsolete repositories (%d) exceeds 1000, manual cleanup required" , totalObsoleteRepos )
204+ }
205+
206+ // Now delete the obsolete repos
168207 page := 1
169208 for page != - 1 {
170209 page , err = b .deleteObsoleteReposByPage (accessToken , page )
@@ -214,21 +253,41 @@ func FetchCloudSecret(name string) (string, error) {
214253}
215254
216255func (b * BitbucketClient ) deleteObsoleteReposByPage (accessToken string , page int ) (int , error ) {
217- out , err := exec .Command ("curl" , "-sX" , "GET" ,
218- "-H" , fmt .Sprintf ("Authorization:Bearer %s" , accessToken ),
219- fmt .Sprintf (`https://api.bitbucket.org/2.0/repositories/%s?q=project.key="%s"&page=%d` ,
220- bitbucketWorkspace , bitbucketProject , page )).CombinedOutput ()
221- if err != nil {
222- return - 1 , fmt .Errorf ("%s: %w" , string (out ), err )
223- }
224- repos , page , err := b .filterObsoleteRepos (out )
256+ repos , nextPage , err := b .getObsoleteReposByPage (accessToken , page )
225257 if err != nil {
226258 return - 1 , err
227259 }
228260
229261 b .logger .Infof ("Deleting the following repos: %s" , strings .Join (repos , ", " ))
230262 err = deleteRepos (accessToken , repos ... )
231- return page , err
263+ return nextPage , err
264+ }
265+
266+ // countObsoleteRepos counts all obsolete repositories across all pages
267+ func (b * BitbucketClient ) countObsoleteRepos (accessToken string ) (int , error ) {
268+ totalObsoleteRepos := 0
269+ page := 1
270+ for page != - 1 {
271+ repos , nextPage , err := b .getObsoleteReposByPage (accessToken , page )
272+ if err != nil {
273+ return 0 , err
274+ }
275+ totalObsoleteRepos += len (repos )
276+ page = nextPage
277+ }
278+ return totalObsoleteRepos , nil
279+ }
280+
281+ // getObsoleteReposByPage fetches obsolete repos for a given page
282+ func (b * BitbucketClient ) getObsoleteReposByPage (accessToken string , page int ) ([]string , int , error ) {
283+ out , err := exec .Command ("curl" , "-sX" , "GET" ,
284+ "-H" , fmt .Sprintf ("Authorization:Bearer %s" , accessToken ),
285+ fmt .Sprintf (`https://api.bitbucket.org/2.0/repositories/%s?q=project.key="%s"&page=%d` ,
286+ bitbucketWorkspace , bitbucketProject , page )).CombinedOutput ()
287+ if err != nil {
288+ return nil , - 1 , fmt .Errorf ("%s: %w" , string (out ), err )
289+ }
290+ return b .filterObsoleteRepos (out )
232291}
233292
234293// filterObsoleteRepos extracts the names of the repos that were created more than 24 hours ago.
0 commit comments