Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions cloud/cleanup.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import (
"fmt"
"maps"
"strings"

"github.com/stackitcloud/cluster-api-provider-stackit/cloud/services/publicip"
)

// CleanupByTags deletes provider-managed cloud resources matching tags without
Expand All @@ -29,6 +31,25 @@ func CleanupByTags(ctx context.Context, client Client, tags map[string]string) e
bastionTags := make(map[string]string, len(tags)+1)
maps.Copy(bastionTags, tags)
bastionTags["cluster-api-provider-stackit/resource-role"] = "bastion"
bastionServers, err := client.ListServersByTags(ctx, bastionTags)
if err != nil {
errs = append(errs, err.Error())
}
publicIPs, err := publicip.ListByTags(ctx, client.IaaSClient(), client.ProjectID(), client.Region(), tags)
if err != nil {
errs = append(errs, err.Error())
} else {
for _, pubIP := range publicIPs {
for _, server := range bastionServers {
if err := publicip.DetachFromServer(ctx, client.IaaSClient(), client.ProjectID(), client.Region(), server.ID, pubIP.ID); err != nil {
errs = append(errs, fmt.Sprintf("detach public IP %q from server %q: %v", pubIP.ID, server.ID, err))
}
}
if err := publicip.DeleteByID(ctx, client.IaaSClient(), client.ProjectID(), client.Region(), pubIP.ID); err != nil {
errs = append(errs, fmt.Sprintf("delete public IP %q: %v", pubIP.ID, err))
}
}
}
if err := client.DeleteBastion(ctx, BastionInput{Tags: bastionTags}, Bastion{}); err != nil && !IsNotFound(err) {
errs = append(errs, fmt.Sprintf("delete bastion resources: %v", err))
}
Expand Down
20 changes: 20 additions & 0 deletions cloud/cleanup_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,12 @@ func TestCleanupByTagsDeletesMatchingCloudResources(t *testing.T) {
if _, err := client.CreateServer(ctx, cloud.CreateServerInput{Name: "other", Tags: otherTags}); err != nil {
t.Fatalf("CreateServer() error = %v", err)
}
if _, err := client.CreatePublicIP(ctx, tags); err != nil {
t.Fatalf("CreatePublicIP() error = %v", err)
}
if _, err := client.CreatePublicIP(ctx, otherTags); err != nil {
t.Fatalf("CreatePublicIP() error = %v", err)
}
if _, err := client.EnsureAPIServerLoadBalancer(ctx, cloud.LoadBalancerInput{
Name: "matched",
Tags: tags,
Expand Down Expand Up @@ -86,4 +92,18 @@ func TestCleanupByTagsDeletesMatchingCloudResources(t *testing.T) {
if len(matchedLoadBalancers) != 0 {
t.Fatalf("matched load balancer count = %d, want 0", len(matchedLoadBalancers))
}
matchedPublicIPs, err := client.ListPublicIPsByTags(ctx, tags)
if err != nil {
t.Fatalf("ListPublicIPsByTags() error = %v", err)
}
if len(matchedPublicIPs) != 0 {
t.Fatalf("matched public IP count = %d, want 0", len(matchedPublicIPs))
}
otherPublicIPs, err := client.ListPublicIPsByTags(ctx, otherTags)
if err != nil {
t.Fatalf("ListPublicIPsByTags(other) error = %v", err)
}
if len(otherPublicIPs) != 1 {
t.Fatalf("other public IP count = %d, want 1", len(otherPublicIPs))
}
}
20 changes: 17 additions & 3 deletions cloud/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,17 @@ You may obtain a copy of the License at

package cloud

import "context"
import (
"context"

iaas "github.com/stackitcloud/stackit-sdk-go/services/iaas/v2api"
)

// Reconciler is a generic interface used by components offering a type of service.
type Reconciler interface {
Reconcile(ctx context.Context) error
Delete(ctx context.Context) error
}

// Client is the abstraction over STACKIT cloud APIs used by the controllers.
//
Expand All @@ -23,6 +33,12 @@ import "context"
// - be idempotent (CreateServer/EnsureAPIServerLoadBalancer must not produce
// duplicates when a resource with matching tags already exists)
type Client interface {
ProjectID() string

Region() string

IaaSClient() *iaas.APIClient

GetServer(ctx context.Context, id string) (*Server, error)

FindServerByTags(ctx context.Context, tags map[string]string) (*Server, error)
Expand All @@ -43,8 +59,6 @@ type Client interface {

DeleteNodeSSHAccess(ctx context.Context, tags map[string]string) error

ListPublicIPsByTags(ctx context.Context, tags map[string]string) ([]*PublicIP, error)

ListSecurityGroupsByTags(ctx context.Context, tags map[string]string) ([]*SecurityGroup, error)

EnsureAPIServerLoadBalancer(ctx context.Context, input LoadBalancerInput) (*LoadBalancer, error)
Expand Down
33 changes: 15 additions & 18 deletions cloud/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,35 +10,32 @@ You may obtain a copy of the License at

package cloud

import "errors"
import "github.com/stackitcloud/cluster-api-provider-stackit/cloud/services/shared"

// Sentinel errors returned by Client implementations. Controllers use
// errors.Is to classify them into requeue / condition-update behavior.
// Sentinel errors returned by Client implementations, re-exported from shared.
// Controllers use errors.Is to classify them into requeue / condition-update behavior.
var (
ErrNotFound = errors.New("not found")
ErrUnauthorized = errors.New("unauthorized")
ErrInvalidInput = errors.New("invalid input")
ErrConflict = errors.New("conflict")
ErrTransient = errors.New("transient")
ErrNotFound = shared.ErrNotFound
ErrUnauthorized = shared.ErrUnauthorized
ErrInvalidInput = shared.ErrInvalidInput
ErrConflict = shared.ErrConflict
ErrTransient = shared.ErrTransient
)

// IsNotFound reports whether err wraps ErrNotFound.
func IsNotFound(err error) bool { return errors.Is(err, ErrNotFound) }
func IsNotFound(err error) bool { return shared.IsNotFound(err) }

// IsUnauthorized reports whether err wraps ErrUnauthorized.
func IsUnauthorized(err error) bool { return errors.Is(err, ErrUnauthorized) }
func IsUnauthorized(err error) bool { return shared.IsUnauthorized(err) }

// IsInvalidInput reports whether err wraps ErrInvalidInput.
func IsInvalidInput(err error) bool { return errors.Is(err, ErrInvalidInput) }
func IsInvalidInput(err error) bool { return shared.IsInvalidInput(err) }

// IsConflict reports whether err wraps ErrConflict.
func IsConflict(err error) bool { return errors.Is(err, ErrConflict) }
func IsConflict(err error) bool { return shared.IsConflict(err) }

// IsTransient reports whether err wraps ErrTransient.
func IsTransient(err error) bool { return errors.Is(err, ErrTransient) }
func IsTransient(err error) bool { return shared.IsTransient(err) }

// IsRetryable reports whether err should trigger a requeue (conflict or
// transient).
func IsRetryable(err error) bool {
return IsConflict(err) || IsTransient(err)
}
// IsRetryable reports whether err should trigger a requeue (conflict or transient).
func IsRetryable(err error) bool { return shared.IsRetryable(err) }
Comment on lines 25 to +41

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why even keep these wrappers instead o using the shared package instead? or is this just a transient step while you are refactoring?

Loading
Loading