Skip to content

Commit 4b5db6b

Browse files
committed
feat(resources): implement PostgresFlexInstance list + delete
List via postgresflex/v2api ListInstances; Remove soft-deletes active instances and force-deletes ones already in Deleted state so nuke runs converge past the retention window.
1 parent 606c0e5 commit 4b5db6b

6 files changed

Lines changed: 157 additions & 19 deletions

File tree

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,15 +37,15 @@ Legend: ✅ list + delete via real STACKIT SDK · 🟡 registered, lister return
3737
| Object Storage | `ObjectStorageBucket` | 🟡 | `services/objectstorage` |
3838
| Object Storage | `ObjectStorageObject` | 🟡 | `services/objectstorage` |
3939
| SKE | `SKECluster` | 🟡 | `services/ske` |
40-
| PostgresFlex | `PostgresFlexInstance` | 🟡 | `services/postgresflex` |
40+
| PostgresFlex | `PostgresFlexInstance` | | `services/postgresflex/v2api` |
4141
| MongoDBFlex | `MongoDBFlexInstance` | 🟡 | `services/mongodbflex` |
4242
| Redis | `RedisInstance` | 🟡 | `services/redis` |
4343
| OpenSearch | `OpenSearchInstance` | 🟡 | `services/opensearch` |
4444
| RabbitMQ | `RabbitMQInstance` | 🟡 | `services/rabbitmq` |
4545
| LoadBalancer | `LoadBalancer` | 🟡 | `services/loadbalancer` |
4646
| DNS | `DNSZone` | 🟡 | `services/dns` |
4747

48-
**5 of 20 resources fully working.** The CLI / config / auth / libnuke engine are functional; the per-resource SDK wiring lands incrementally. Pick one above and follow [`resources/compute-server.go`](resources/compute-server.go) as the reference pattern — see [Contributing](docs/contributing.md).
48+
**6 of 20 resources fully working.** The CLI / config / auth / libnuke engine are functional; the per-resource SDK wiring lands incrementally. Pick one above and follow [`resources/compute-server.go`](resources/compute-server.go) as the reference pattern — see [Contributing](docs/contributing.md).
4949

5050
## Features
5151

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ require (
77
github.com/sirupsen/logrus v1.9.4
88
github.com/stackitcloud/stackit-sdk-go/core v0.26.0
99
github.com/stackitcloud/stackit-sdk-go/services/iaas v1.12.2
10+
github.com/stackitcloud/stackit-sdk-go/services/postgresflex v1.11.0
1011
github.com/urfave/cli/v3 v3.10.1
1112
gopkg.in/yaml.v3 v3.0.1
1213
)

go.sum

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,16 +31,12 @@ github.com/stackitcloud/stackit-sdk-go/core v0.26.0 h1:jQEb9gkehfp6VCP6TcYk7BI10
3131
github.com/stackitcloud/stackit-sdk-go/core v0.26.0/go.mod h1:WU1hhxnjXw2EV7CYa1nlEvNpMiRY6CvmIOaHuL3pOaA=
3232
github.com/stackitcloud/stackit-sdk-go/services/iaas v1.12.2 h1:hWtbUy0UOhw1cE1riCNjvtmbF3zDl717DIcpEEMF9ps=
3333
github.com/stackitcloud/stackit-sdk-go/services/iaas v1.12.2/go.mod h1:AbPN9BGkdjc+tVsXEX9Vr8BPDjdlDmG26K1FwCKZQVU=
34+
github.com/stackitcloud/stackit-sdk-go/services/postgresflex v1.11.0 h1:cuI4NhuFhaZ3tTkBpUM7nt2odKFJkyCcphT/3gGb9CE=
35+
github.com/stackitcloud/stackit-sdk-go/services/postgresflex v1.11.0/go.mod h1:yzlakB+f8ur4yAHR6lyCABO+HcEtZG3G2Faj6m5/uW8=
3436
github.com/stevenle/topsort v0.2.0 h1:LLWgtp34HPX6/RBDRS0kElVxGOTzGBLI1lSAa5Lb46k=
3537
github.com/stevenle/topsort v0.2.0/go.mod h1:ck2WG2/ZrOr6dLApQ/5Xrqy5wv3T0qhKYWE7r9tkibc=
3638
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
3739
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
38-
github.com/urfave/cli/v3 v3.9.0 h1:AV9lIiPv3ukYnxunaCUsHnEozptYmDN2F0+yWqLMn/c=
39-
github.com/urfave/cli/v3 v3.9.0/go.mod h1:ysVLtOEmg2tOy6PknnYVhDoouyC/6N42TMeoMzskhso=
40-
github.com/urfave/cli/v3 v3.9.1 h1:OLU13atWZ0M+a4xmyBuBNOLZsSRYXyPeMeNjOvgYP54=
41-
github.com/urfave/cli/v3 v3.9.1/go.mod h1:ysVLtOEmg2tOy6PknnYVhDoouyC/6N42TMeoMzskhso=
42-
github.com/urfave/cli/v3 v3.10.0 h1:0aU8yOObVDMkM13Cj4G+zb4P0PdeJMec65f81Ak1ioM=
43-
github.com/urfave/cli/v3 v3.10.0/go.mod h1:ysVLtOEmg2tOy6PknnYVhDoouyC/6N42TMeoMzskhso=
4440
github.com/urfave/cli/v3 v3.10.1 h1:7Kx9H50hrHbRbyxgO1KP6/BcbiGRz0uYh5YyQ30JEEY=
4541
github.com/urfave/cli/v3 v3.10.1/go.mod h1:ysVLtOEmg2tOy6PknnYVhDoouyC/6N42TMeoMzskhso=
4642
golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ=

resources/postgresflex-instance.go

Lines changed: 79 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,14 @@ import (
44
"context"
55
"fmt"
66

7+
"github.com/sirupsen/logrus"
8+
79
"github.com/ekristen/libnuke/pkg/registry"
810
"github.com/ekristen/libnuke/pkg/resource"
911
"github.com/ekristen/libnuke/pkg/types"
1012

13+
postgresflexv2 "github.com/stackitcloud/stackit-sdk-go/services/postgresflex/v2api"
14+
1115
"github.com/qaiser42/stackit-nuke/pkg/stackit"
1216
)
1317

@@ -22,20 +26,89 @@ func init() {
2226
})
2327
}
2428

29+
// PostgresFlexInstance is a STACKIT PostgreSQL Flex instance.
30+
//
31+
// API: github.com/stackitcloud/stackit-sdk-go/services/postgresflex/v2api
32+
//
33+
// Endpoints used:
34+
// - GET /v2/projects/{projectId}/regions/{region}/instances
35+
// - DELETE /v2/projects/{projectId}/regions/{region}/instances/{instanceId}
36+
// - DELETE /v2/projects/{projectId}/regions/{region}/instances/{instanceId}/force
37+
//
38+
// Deleting an instance is a soft delete: it stays listed with status
39+
// "Deleted" during the retention window. Remove therefore force-deletes
40+
// instances already in that state so a nuke run converges instead of
41+
// re-deleting the same instance forever.
2542
type PostgresFlexInstance struct {
2643
*BaseResource `property:",inline"`
27-
ID string
28-
Name string
44+
45+
api postgresflexv2.DefaultAPI
46+
47+
ID string
48+
Name string
49+
Status string
2950
}
3051

31-
func (r *PostgresFlexInstance) Remove(_ context.Context) error {
32-
return fmt.Errorf("PostgresFlexInstance.Remove not yet implemented")
52+
func (r *PostgresFlexInstance) Remove(ctx context.Context) error {
53+
if r.api == nil {
54+
return fmt.Errorf("PostgresFlexInstance.Remove: api client not set")
55+
}
56+
if r.Status == "Deleted" {
57+
return r.api.ForceDeleteInstance(ctx, r.ProjectID, r.Region, r.ID).Execute()
58+
}
59+
return r.api.DeleteInstance(ctx, r.ProjectID, r.Region, r.ID).Execute()
3360
}
61+
3462
func (r *PostgresFlexInstance) Properties() types.Properties { return PropsFromStruct(r) }
3563
func (r *PostgresFlexInstance) String() string { return r.Name }
3664

3765
type PostgresFlexInstanceLister struct{}
3866

39-
func (l *PostgresFlexInstanceLister) List(_ context.Context, _ any) ([]resource.Resource, error) {
40-
return []resource.Resource{}, nil
67+
func (l *PostgresFlexInstanceLister) List(ctx context.Context, o any) ([]resource.Resource, error) {
68+
opts := o.(*stackit.ListerOpts)
69+
logger := logrus.WithFields(logrus.Fields{
70+
"resource": PostgresFlexInstanceResource,
71+
"project": opts.ProjectID,
72+
"region": opts.Region,
73+
})
74+
75+
if opts.Credentials == nil {
76+
return nil, fmt.Errorf("missing STACKIT credentials")
77+
}
78+
79+
client, err := postgresflexv2.NewAPIClient(stackitConfigOpts(opts)...)
80+
if err != nil {
81+
return nil, fmt.Errorf("build postgresflex client: %w", err)
82+
}
83+
84+
logger.Trace("listing postgresflex instances")
85+
resp, err := client.DefaultAPI.ListInstances(ctx, opts.ProjectID, opts.Region).Execute()
86+
if err != nil {
87+
return nil, fmt.Errorf("list postgresflex instances: %w", err)
88+
}
89+
90+
out := make([]resource.Resource, 0, len(resp.GetItems()))
91+
for _, i := range resp.GetItems() {
92+
id, ok := i.GetIdOk()
93+
if !ok || id == nil {
94+
continue
95+
}
96+
name, _ := i.GetNameOk()
97+
status, _ := i.GetStatusOk()
98+
99+
out = append(out, &PostgresFlexInstance{
100+
BaseResource: &BaseResource{
101+
OrganizationID: opts.OrganizationID,
102+
ProjectID: opts.ProjectID,
103+
Region: opts.Region,
104+
},
105+
api: client.DefaultAPI,
106+
ID: *id,
107+
Name: stringDeref(name),
108+
Status: stringDeref(status),
109+
})
110+
}
111+
112+
logger.WithField("count", len(out)).Trace("list complete")
113+
return out, nil
41114
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package resources
2+
3+
import (
4+
"context"
5+
"strings"
6+
"testing"
7+
8+
"github.com/ekristen/libnuke/pkg/registry"
9+
10+
"github.com/qaiser42/stackit-nuke/pkg/stackit"
11+
)
12+
13+
func TestPostgresFlexInstance_StringAndProperties(t *testing.T) {
14+
r := &PostgresFlexInstance{
15+
BaseResource: &BaseResource{ProjectID: "proj-1", Region: "eu01"},
16+
ID: "pg-abc",
17+
Name: "orders-db",
18+
Status: "Ready",
19+
}
20+
21+
if got := r.String(); got != "orders-db" {
22+
t.Errorf("String() = %q, want orders-db", got)
23+
}
24+
25+
props := r.Properties()
26+
rendered := props.String()
27+
for _, want := range []string{"orders-db", "pg-abc", "proj-1", "eu01", "Ready"} {
28+
if !strings.Contains(rendered, want) {
29+
t.Errorf("Properties() = %q; missing %q", rendered, want)
30+
}
31+
}
32+
}
33+
34+
func TestPostgresFlexInstance_RemoveWithoutAPIErrors(t *testing.T) {
35+
r := &PostgresFlexInstance{
36+
BaseResource: &BaseResource{ProjectID: "p", Region: "r"},
37+
ID: "id",
38+
}
39+
err := r.Remove(context.Background())
40+
if err == nil {
41+
t.Fatal("expected error when api is unset")
42+
}
43+
if !strings.Contains(err.Error(), "api client not set") {
44+
t.Errorf("unexpected error: %v", err)
45+
}
46+
}
47+
48+
func TestPostgresFlexInstanceLister_RequiresCredentials(t *testing.T) {
49+
l := &PostgresFlexInstanceLister{}
50+
_, err := l.List(context.Background(), &stackit.ListerOpts{ProjectID: "p", Region: "r"})
51+
if err == nil {
52+
t.Fatal("expected error when credentials missing")
53+
}
54+
}
55+
56+
func TestPostgresFlexInstanceRegistration(t *testing.T) {
57+
regs := registry.GetRegistration(PostgresFlexInstanceResource)
58+
if regs == nil {
59+
t.Fatal("PostgresFlexInstance not registered")
60+
}
61+
if regs.Scope != stackit.ProjectScope {
62+
t.Errorf("scope = %q, want %q", regs.Scope, stackit.ProjectScope)
63+
}
64+
if regs.Lister == nil || regs.Resource == nil {
65+
t.Error("lister or resource nil in registration")
66+
}
67+
}

resources/registry_test.go

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -72,11 +72,12 @@ func TestAllResourcesUseProjectScope(t *testing.T) {
7272
// They are excluded from the stub-emptiness check below; they get their own
7373
// per-resource tests instead.
7474
var realImpls = map[string]bool{
75-
"ComputeServer": true,
76-
"ComputeVolume": true,
77-
"NetworkInterface": true,
78-
"Network": true,
79-
"SecurityGroup": true,
75+
"ComputeServer": true,
76+
"ComputeVolume": true,
77+
"NetworkInterface": true,
78+
"Network": true,
79+
"SecurityGroup": true,
80+
"PostgresFlexInstance": true,
8081
}
8182

8283
func TestListersReturnEmpty(t *testing.T) {

0 commit comments

Comments
 (0)