Skip to content

Commit 39c556c

Browse files
committed
feat: real list+delete for Volume/Network/NIC/SecurityGroup + post-run summary
- ComputeVolume, Network, SecurityGroup wired against iaas/v2api (previously stubs) - New NetworkInterface resource type (ListNics scoped per Network) - DependsOn graph: NIC after Server; Network after NIC; SG after NIC - Compact log formatter strips libnuke property dump; --log-verbose restores the full output - Post-run summary prints a grouped list of nuked resources with an ASCII art banner (only on --no-dry-run) - dev-infra/nuke.yaml(.example) companion config covering the 5 implemented resource types; filters out the project default SG - README: Concepts section, sample summary output, updated coverage table (5/20 working) - CHANGELOG: 0.0.2 entry
1 parent ee3e4a8 commit 39c556c

14 files changed

Lines changed: 734 additions & 34 deletions

CHANGELOG.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,28 @@ to [Semantic Versioning](https://semver.org/).
66

77
## [Unreleased]
88

9+
## [0.0.2] - 2026-05-14
10+
11+
### Added
12+
- Real list + delete via STACKIT IaaS v2 SDK for: ComputeVolume,
13+
Network, NetworkInterface, SecurityGroup
14+
- New `NetworkInterface` resource type
15+
- `dev-infra/` Pulumi (Go) stack for round-trip create + nuke testing,
16+
plus `dev-infra/nuke.yaml(.example)` companion config
17+
- Post-run summary: grouped list of nuked resources + ASCII art banner
18+
- Compact log formatter (strips libnuke property dump);
19+
`--log-verbose` restores full output
20+
21+
## [0.0.1]
22+
923
### Added
1024
- Initial scaffold: CLI, libnuke wiring, STACKIT auth, config loader
1125
- Resource registrations (placeholder Listers): ComputeServer, ComputeVolume,
1226
ComputeSnapshot, ComputeKeypair, Network, Subnet, Router, SecurityGroup,
1327
FloatingIP, ObjectStorageBucket, ObjectStorageObject, SKECluster,
1428
PostgresFlexInstance, MongoDBFlexInstance, RedisInstance,
1529
OpenSearchInstance, RabbitMQInstance, LoadBalancer, DNSZone
30+
- Real list + delete via STACKIT IaaS v2 SDK for ComputeServer
1631
- CI: lint, test, build (GitHub Actions, Go 1.25)
1732
- Release: GoReleaser cross-compile, ko distroless multi-arch, Cosign signing
1833
- Docs: MkDocs Material site published to GitHub Pages

README.md

Lines changed: 51 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,51 @@ stackit-nuke run --config config.yaml # dry run
6060
stackit-nuke run --config config.yaml --no-dry-run # real
6161
```
6262

63+
After a real run, you get a summary of everything deleted:
64+
65+
```
66+
_ _.-'`-._
67+
.-'` '-.
68+
.' _ . - = - . _ '.
69+
/ .-' '-. \
70+
/ .' '. \
71+
| / \ |
72+
| | BOOM | |
73+
| | | |
74+
\ '. .' /
75+
\ '-. .-' /
76+
'. '-._ _.-' .'
77+
'-. `'----'` .-'
78+
`'-..__________..-'`
79+
80+
9 resource(s) nuked:
81+
82+
ComputeServer
83+
- dev-server-0 (2bd1a04d)
84+
- dev-server-1 (6adb84a9)
85+
ComputeVolume
86+
- stackit-nuke-dev-extra (bb668c22)
87+
Network
88+
- dev-net (6f452480)
89+
NetworkInterface
90+
- dev-nic-0-b7856b3 (605f8edd)
91+
- dev-nic-1-cf66680 (3958de73)
92+
```
93+
6394
Full docs: <https://qaiser42.io/stackit-nuke>
6495

96+
## Concepts
97+
98+
- **Project allow-list**`project-ids` is the universe of what may be nuked. The CLI may *narrow* it (`--project-id`); it cannot widen. Without it, the tool refuses to start.
99+
- **Blocklist**`blocklist` is a hard veto. If any blocked ID also appears in `project-ids`, startup fails. Belt-and-suspenders against fat-fingering.
100+
- **Dry-run default**`run` lists what would be deleted. Real deletion requires `--no-dry-run` *and* (unless `--force`) typing the project ID back at a prompt.
101+
- **Filters mark resources as ineligible** — counter-intuitive: a filter is a *keep-list*, not a kill-list. Anything matched by a filter survives; everything else gets deleted. Filter by `Name`, `tag:*`, etc.
102+
- **Presets** — reusable named filter sets, attached to accounts via `presets: [...]`. Same shape as filters; just deduped at one site.
103+
- **Resource type include/exclude**`resource-types.includes` narrows the registered set; `excludes` always wins. Empty `includes` means all registered types.
104+
- **Scopes** — every resource is `ProjectScope` today (region-aware, scoped to one STACKIT project). Engine also supports an `OrganizationScope` we have not yet used.
105+
- **Dependency order** — each `Resource` declares `DependsOn`. libnuke topologically sorts and only deletes a resource once everything it depends on has finished. E.g. `Network` depends on `NetworkInterface` so NICs detach first.
106+
- **Settings & feature-flags** — per-resource toggles (e.g. `EmptyBeforeDelete: true` for buckets) live under `settings:`. Engine behaviors (`wait-on-dependencies`, `filter-groups`) live under `feature-flags:`.
107+
65108
## How it works (101)
66109

67110
`stackit-nuke` is a thin CLI shell over [`libnuke`](https://github.com/ekristen/libnuke). We write the STACKIT-specific bits; libnuke does the engine work.
@@ -71,7 +114,7 @@ Full docs: <https://qaiser42.io/stackit-nuke>
71114
`main.go` blank-imports `pkg/commands/...` and `resources/...`. Their `init()` functions:
72115

73116
- register CLI subcommands (`run`, `resource-types`)
74-
- register **19 resource types** with `libnuke/pkg/registry` — each entry pairs a `Lister` (discover) with a `Resource` (delete) and optional `DependsOn` (ordering)
117+
- register **20 resource types** with `libnuke/pkg/registry` — each entry pairs a `Lister` (discover) with a `Resource` (delete) and optional `DependsOn` (ordering)
75118

76119
No reflection, no plugin loader — pure compile-time wiring.
77120

@@ -151,13 +194,14 @@ Legend: ✅ list + delete via real STACKIT SDK · 🟡 registered, lister return
151194
| Service | Resource | Status | SDK package |
152195
|---|---|---|---|
153196
| IaaS / compute | `ComputeServer` || `stackit-sdk-go/services/iaas/v2api` |
154-
| IaaS / compute | `ComputeVolume` | 🟡 | `iaas/v2api` |
197+
| IaaS / compute | `ComputeVolume` | | `iaas/v2api` |
155198
| IaaS / compute | `ComputeSnapshot` | 🟡 | `iaas/v2api` |
156199
| IaaS / compute | `ComputeKeypair` | 🟡 | `iaas/v2api` |
157-
| IaaS / network | `Network` | 🟡 | `iaas/v2api` |
200+
| IaaS / network | `Network` || `iaas/v2api` |
201+
| IaaS / network | `NetworkInterface` || `iaas/v2api` |
158202
| IaaS / network | `Subnet` | 🟡 | `iaas/v2api` |
159203
| IaaS / network | `Router` | 🟡 | `iaas/v2api` |
160-
| IaaS / network | `SecurityGroup` | 🟡 | `iaas/v2api` |
204+
| IaaS / network | `SecurityGroup` | | `iaas/v2api` |
161205
| IaaS / network | `FloatingIP` | 🟡 | `iaas/v2api` |
162206
| Object Storage | `ObjectStorageBucket` | 🟡 | `services/objectstorage` |
163207
| Object Storage | `ObjectStorageObject` | 🟡 | `services/objectstorage` |
@@ -170,7 +214,7 @@ Legend: ✅ list + delete via real STACKIT SDK · 🟡 registered, lister return
170214
| LoadBalancer | `LoadBalancer` | 🟡 | `services/loadbalancer` |
171215
| DNS | `DNSZone` | 🟡 | `services/dns` |
172216

173-
**1 of 19 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).
217+
**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).
174218

175219
## Development
176220

@@ -189,8 +233,8 @@ Requires Go 1.25+.
189233
[`dev-infra/`](dev-infra/) is a Pulumi project ([`@stackitcloud/pulumi-stackit`](https://www.pulumi.com/registry/packages/stackit/)) that spins up a small STACKIT footprint (network, NICs, servers, volume) you can repeatedly create + nuke + recreate while developing new resource implementations.
190234

191235
```bash
192-
cd dev-infra && npm install && pulumi up
193-
cd .. && ./stackit-nuke run --config examples/compute-only.yaml --no-dry-run
236+
cd dev-infra && go mod download && pulumi up
237+
cd .. && ./stackit-nuke run --config dev-infra/nuke.yaml --no-dry-run
194238
```
195239

196240
See [`dev-infra/README.md`](dev-infra/README.md).

dev-infra/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,6 @@ Pulumi.dev.yaml
44
Pulumi.*.yaml
55
!Pulumi.yaml
66
!Pulumi.dev.yaml.example
7+
nuke.yaml
8+
!nuke.yaml.example
79
*.log

dev-infra/README.md

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
Throwaway STACKIT infrastructure for testing `stackit-nuke` locally.
44

5-
Uses the **official Pulumi STACKIT provider** ([`@stackitcloud/pulumi-stackit`](https://www.pulumi.com/registry/packages/stackit/)) — currently alpha, IaaS + Resource Manager only.
5+
Uses the **official Pulumi STACKIT provider** ([`pulumi-stackit`](https://www.pulumi.com/registry/packages/stackit/)) via its Go SDK — currently alpha, IaaS + Resource Manager only.
66

77
## What it deploys
88

@@ -16,18 +16,20 @@ All resources are labelled `managed-by=pulumi`, `purpose=stackit-nuke-dev` so yo
1616
## Prerequisites
1717

1818
- Pulumi CLI (`brew install pulumi`)
19-
- Node.js 20+
19+
- Go 1.23+
2020
- A STACKIT service-account key — same one `stackit-nuke` uses
2121

2222
## One-time setup
2323

2424
```bash
2525
cd dev-infra
26-
npm install
26+
go mod download
2727
pulumi login --local # or your usual backend
2828
pulumi stack init dev
2929
cp Pulumi.dev.yaml.example Pulumi.dev.yaml
3030
$EDITOR Pulumi.dev.yaml # paste your project ID
31+
cp nuke.yaml.example nuke.yaml
32+
$EDITOR nuke.yaml # paste same project ID + sa-key path
3133
export STACKIT_SERVICE_ACCOUNT_KEY_PATH=~/.stackit/sa.json
3234
```
3335

@@ -38,15 +40,15 @@ export STACKIT_SERVICE_ACCOUNT_KEY_PATH=~/.stackit/sa.json
3840
pulumi up
3941

4042
# Confirm stackit-nuke sees it (dry run)
41-
cd .. && ./stackit-nuke run --config examples/compute-only.yaml
43+
cd .. && ./stackit-nuke run --config dev-infra/nuke.yaml
4244

4345
# Nuke it
44-
./stackit-nuke run --config examples/compute-only.yaml --no-dry-run
46+
./stackit-nuke run --config dev-infra/nuke.yaml --no-dry-run
4547

46-
# Verify the nuker did its job — Pulumi will show the servers as "missing"
48+
# Verify the nuker did its job — Pulumi will show resources as "missing"
4749
cd dev-infra && pulumi refresh
4850

49-
# Clean residual state (volumes, network — stubs in stackit-nuke don't delete these yet)
51+
# Clear Pulumi state — the cloud resources are already gone
5052
pulumi destroy
5153
```
5254

dev-infra/nuke.yaml.example

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# stackit-nuke config for the dev-infra Pulumi stack.
2+
#
3+
# Copy to nuke.yaml and fill in your project ID + sa-key path.
4+
#
5+
# Targets every resource type that has a real SDK implementation today —
6+
# the same set dev-infra creates: servers, NICs, volumes.
7+
#
8+
# Round trip:
9+
# pulumi up
10+
# cd .. && ./stackit-nuke run --config dev-infra/nuke.yaml --no-dry-run
11+
# cd dev-infra && pulumi refresh && pulumi destroy
12+
13+
regions:
14+
- eu01
15+
16+
# Match the projectId set in Pulumi.dev.yaml.
17+
project-ids:
18+
- 00000000-0000-0000-0000-000000000000
19+
20+
auth:
21+
service-account-key-path: ~/.stackit/sa.json
22+
23+
resource-types:
24+
includes:
25+
- ComputeServer
26+
- ComputeVolume
27+
- NetworkInterface
28+
- Network
29+
- SecurityGroup
30+
31+
# STACKIT auto-creates a "default" SG per project that resists deletion.
32+
# Skip it so the run doesn't surface a permanent failure.
33+
accounts:
34+
"00000000-0000-0000-0000-000000000000":
35+
filters:
36+
SecurityGroup:
37+
- property: Name
38+
value: default

pkg/commands/global/global.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ func Flags() []cli.Flag {
2121
&cli.BoolFlag{Name: "log-caller", Usage: "log caller (file:line)"},
2222
&cli.BoolFlag{Name: "log-disable-color", Usage: "disable log coloring"},
2323
&cli.BoolFlag{Name: "log-full-timestamp", Usage: "always show full timestamp"},
24+
&cli.BoolFlag{Name: "log-verbose", Usage: "show every libnuke property in log lines (default: compact)"},
2425
}
2526
}
2627

@@ -37,6 +38,10 @@ func Before(c *cli.Context) error {
3738
}
3839
logrus.SetFormatter(formatter)
3940

41+
if !c.Bool("log-verbose") {
42+
logrus.AddHook(compactHook{})
43+
}
44+
4045
switch c.String("log-level") {
4146
case "trace":
4247
logrus.SetLevel(logrus.TraceLevel)

pkg/commands/global/log_compact.go

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package global
2+
3+
import (
4+
"fmt"
5+
"strings"
6+
7+
"github.com/sirupsen/logrus"
8+
)
9+
10+
// compactHook strips libnuke's per-resource property dump from log entries.
11+
// libnuke emits one INFO line per resource per state with every property as
12+
// prop:<Field>=<value>, which gets unreadable fast. We keep only the fields
13+
// that identify the resource (type, state, name, short ID) and drop the rest.
14+
type compactHook struct{}
15+
16+
func (compactHook) Levels() []logrus.Level { return logrus.AllLevels }
17+
18+
func (compactHook) Fire(e *logrus.Entry) error {
19+
if len(e.Data) == 0 {
20+
return nil
21+
}
22+
out := logrus.Fields{}
23+
for k, v := range e.Data {
24+
switch k {
25+
case "_handler", "component", "owner", "state_code":
26+
continue
27+
case "type", "state", "name":
28+
out[k] = v
29+
continue
30+
}
31+
if !strings.HasPrefix(k, "prop:") {
32+
out[k] = v
33+
continue
34+
}
35+
switch strings.TrimPrefix(k, "prop:") {
36+
case "ID":
37+
out["id"] = shortID(fmt.Sprint(v))
38+
case "Name":
39+
if s := fmt.Sprint(v); s != "" {
40+
out["name"] = s
41+
}
42+
}
43+
}
44+
e.Data = out
45+
return nil
46+
}
47+
48+
func shortID(s string) string {
49+
if i := strings.Index(s, "-"); i > 0 {
50+
return s[:i]
51+
}
52+
return s
53+
}

pkg/commands/run/command.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,8 +140,15 @@ func execute(c *cli.Context) error {
140140
}
141141
}
142142

143+
hook := &nukedHook{}
144+
logrus.AddHook(hook)
145+
143146
logger.Debug("running ...")
144-
return n.Run(ctx)
147+
runErr := n.Run(ctx)
148+
if params.NoDryRun {
149+
printSummary(logger, hook.entries)
150+
}
151+
return runErr
145152
}
146153

147154
func firstNonEmpty(vals ...string) string {

0 commit comments

Comments
 (0)