2022-09-05 03:06:08 -04:00
|
|
|
/*
|
|
|
|
Copyright (c) Edgeless Systems GmbH
|
|
|
|
|
|
|
|
SPDX-License-Identifier: AGPL-3.0-only
|
|
|
|
*/
|
|
|
|
|
2022-08-25 09:12:08 -04:00
|
|
|
package client
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"fmt"
|
|
|
|
"regexp"
|
|
|
|
"strings"
|
|
|
|
"sync"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime"
|
|
|
|
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources"
|
|
|
|
)
|
|
|
|
|
|
|
|
// TerminateResourceGroupResources deletes all resources from the resource group.
|
|
|
|
func (c *Client) TerminateResourceGroupResources(ctx context.Context) error {
|
|
|
|
const timeOut = 10 * time.Minute
|
|
|
|
ctx, cancel := context.WithTimeout(ctx, timeOut)
|
|
|
|
defer cancel()
|
|
|
|
|
|
|
|
pollers := make(chan *runtime.Poller[armresources.ClientDeleteByIDResponse], 20)
|
|
|
|
delete := make(chan struct{}, 1)
|
|
|
|
wg := &sync.WaitGroup{}
|
|
|
|
wg.Add(2)
|
|
|
|
|
|
|
|
go func() { // This routine lists resources and starts their deletion, where possible.
|
|
|
|
defer wg.Done()
|
|
|
|
defer func() {
|
|
|
|
close(pollers)
|
|
|
|
for range delete { // drain channel
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
|
|
|
for {
|
|
|
|
ids, err := c.getResourceIDList(ctx)
|
|
|
|
if err != nil {
|
|
|
|
time.Sleep(3 * time.Second)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(ids) == 0 {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, id := range ids {
|
|
|
|
poller, err := c.deleteResourceByID(ctx, id)
|
|
|
|
if err != nil {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
pollers <- poller
|
|
|
|
}
|
|
|
|
|
|
|
|
select {
|
|
|
|
case <-ctx.Done():
|
|
|
|
return
|
|
|
|
case _, ok := <-delete:
|
|
|
|
if !ok { // channel was closed
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
|
|
|
go func() { // This routine polls for for the deletions to complete.
|
|
|
|
defer wg.Done()
|
|
|
|
defer close(delete)
|
|
|
|
|
|
|
|
for poller := range pollers {
|
|
|
|
_, err := poller.PollUntilDone(ctx, nil)
|
|
|
|
if err != nil {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
select {
|
|
|
|
case delete <- struct{}{}:
|
|
|
|
default:
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
|
|
|
wg.Wait()
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Client) getResourceIDList(ctx context.Context) ([]string, error) {
|
|
|
|
var ids []string
|
|
|
|
pager := c.resourceAPI.NewListByResourceGroupPager(c.resourceGroup, nil)
|
|
|
|
for pager.More() {
|
|
|
|
page, err := pager.NextPage(ctx)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("getting next page of ListByResourceGroup: %w", err)
|
|
|
|
}
|
|
|
|
for _, resource := range page.Value {
|
|
|
|
if resource.ID == nil {
|
|
|
|
return nil, fmt.Errorf("resource %v has no ID", resource)
|
|
|
|
}
|
|
|
|
ids = append(ids, *resource.ID)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return ids, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Client) deleteResourceByID(ctx context.Context, id string,
|
|
|
|
) (*runtime.Poller[armresources.ClientDeleteByIDResponse], error) {
|
|
|
|
apiVersion := "2020-02-02"
|
|
|
|
|
|
|
|
// First try, API version unknown, will fail.
|
|
|
|
poller, err := c.resourceAPI.BeginDeleteByID(ctx, id, apiVersion, nil)
|
|
|
|
if isVersionWrongErr(err) {
|
|
|
|
// bad hack, but easiest way to get the right API version
|
|
|
|
apiVersion = parseAPIVersionFromErr(err)
|
|
|
|
poller, err = c.resourceAPI.BeginDeleteByID(ctx, id, apiVersion, nil)
|
|
|
|
}
|
|
|
|
return poller, err
|
|
|
|
}
|
|
|
|
|
|
|
|
func isVersionWrongErr(err error) bool {
|
|
|
|
if err == nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
return strings.Contains(err.Error(), "NoRegisteredProviderFound") &&
|
|
|
|
strings.Contains(err.Error(), "The supported api-versions are")
|
|
|
|
}
|
|
|
|
|
|
|
|
var apiVersionRegex = regexp.MustCompile(` (\d\d\d\d-\d\d-\d\d)'`)
|
|
|
|
|
|
|
|
func parseAPIVersionFromErr(err error) string {
|
|
|
|
if err == nil {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
matches := apiVersionRegex.FindStringSubmatch(err.Error())
|
|
|
|
return matches[1]
|
|
|
|
}
|