Skip to content

Commit d8be409

Browse files
gustavobertoiclaude
andcommitted
feat(cli): db + s3 + aws resource verbs (spec 29 stages 3-5)
db group (graduates the reserved db stub): create/user create/grant/list/drop/ gc — tenant-scoped Postgres databases + roles/grants through the resource_ops lock->overlay->provisioner->ledger->event flow, --json + confirm-gated drop/gc. s3 group: mb/rb/ls/lifecycle set|get|rm/versioning/policy set|get/cors set|get on the MinIO provisioner, project-prefixed buckets with --no-prefix. aws -- shim: argv passthrough over the host aws binary with --endpoint-url/--region prepended and dev creds injected via child env (never argv), clear error when absent. Table-driven CLI tests: registration, flags, --json, arg passthrough, absent- binary error, prefix + transition + cors parsing. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent cad4576 commit d8be409

8 files changed

Lines changed: 1276 additions & 14 deletions

File tree

‎go.mod‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ require (
1010
charm.land/lipgloss/v2 v2.0.1
1111
filippo.io/age v1.3.1
1212
github.com/adrg/xdg v0.5.3
13+
github.com/aws/aws-sdk-go-v2 v1.42.0
14+
github.com/aws/aws-sdk-go-v2/credentials v1.19.25
15+
github.com/aws/aws-sdk-go-v2/service/s3 v1.104.1
16+
github.com/aws/smithy-go v1.27.1
1317
github.com/compose-spec/compose-go/v2 v2.12.1
1418
github.com/go-playground/validator/v10 v10.30.3
1519
github.com/goccy/go-yaml v1.19.2
@@ -31,18 +35,14 @@ require (
3135
filippo.io/hpke v0.4.0 // indirect
3236
github.com/Microsoft/go-winio v0.6.2 // indirect
3337
github.com/atotto/clipboard v0.1.4 // indirect
34-
github.com/aws/aws-sdk-go-v2 v1.42.0 // indirect
3538
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.13 // indirect
36-
github.com/aws/aws-sdk-go-v2/credentials v1.19.25 // indirect
3739
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.29 // indirect
3840
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.29 // indirect
3941
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.30 // indirect
4042
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.12 // indirect
4143
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.22 // indirect
4244
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.29 // indirect
4345
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.30 // indirect
44-
github.com/aws/aws-sdk-go-v2/service/s3 v1.104.1 // indirect
45-
github.com/aws/smithy-go v1.27.1 // indirect
4646
github.com/catppuccin/go v0.2.0 // indirect
4747
github.com/charmbracelet/colorprofile v0.4.2 // indirect
4848
github.com/charmbracelet/ultraviolet v0.0.0-20260205113103-524a6607adb8 // indirect

‎internal/cli/aws.go‎

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
package cli
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
"os"
7+
"os/exec"
8+
9+
"github.com/spf13/cobra"
10+
11+
"github.com/open-source-cloud/devstack/internal/orchestrate"
12+
)
13+
14+
// newAwsCmd wires the thin `devstack aws -- <args...>` shim (spec 29 §aws): a pure
15+
// argv passthrough over the user's own `aws` binary with --endpoint-url +
16+
// --region prepended and dev creds injected via the child ENV (never argv). It
17+
// never reimplements any AWS call; it just removes the endpoint/port lookup. The
18+
// aws binary is an external dependency — absence is a clear error, never a panic.
19+
func newAwsCmd(g *GlobalOpts) *cobra.Command {
20+
return &cobra.Command{
21+
Use: "aws -- <args...>",
22+
Short: "Run the host `aws` CLI against the local LocalStack/MinIO endpoint",
23+
Long: "A thin argv shim over your own `aws` binary: devstack resolves the\n" +
24+
"LocalStack/MinIO host port and prepends --endpoint-url/--region plus dev\n" +
25+
"credentials (via the child environment). It does not reimplement any AWS call.\n\n" +
26+
"Example: devstack aws -- s3 ls",
27+
Args: cobra.MinimumNArgs(1),
28+
DisableFlagParsing: true,
29+
RunE: func(cmd *cobra.Command, args []string) error {
30+
args = stripLeadingDashDash(args)
31+
if len(args) == 0 {
32+
return fmt.Errorf("usage: devstack aws -- <args...> (e.g. devstack aws -- s3 ls)")
33+
}
34+
awsPath, err := lookupAws()
35+
if err != nil {
36+
return err
37+
}
38+
d, closeFn, err := buildUpDeps(cmd)
39+
if err != nil {
40+
return err
41+
}
42+
defer closeFn()
43+
endpoint, region, access, secret, err := orchestrate.ResolveAwsEndpoint(cmd.Context(), d)
44+
if err != nil {
45+
return err
46+
}
47+
argv := awsArgs(endpoint, region, args)
48+
child := exec.CommandContext(cmd.Context(), awsPath, argv...)
49+
child.Env = awsEnv(os.Environ(), access, secret, region)
50+
child.Stdin = cmd.InOrStdin()
51+
child.Stdout = cmd.OutOrStdout()
52+
child.Stderr = cmd.ErrOrStderr()
53+
if err := child.Run(); err != nil {
54+
var ee *exec.ExitError
55+
if errors.As(err, &ee) {
56+
return fmt.Errorf("aws exited with code %d", ee.ExitCode())
57+
}
58+
return fmt.Errorf("run aws: %w", err)
59+
}
60+
return nil
61+
},
62+
}
63+
}
64+
65+
// lookupAws resolves the host `aws` binary or returns an install remediation.
66+
func lookupAws() (string, error) {
67+
p, err := exec.LookPath("aws")
68+
if err != nil {
69+
return "", fmt.Errorf("the `aws` CLI is not installed or not on PATH — install it (https://aws.amazon.com/cli/) to use `devstack aws --`")
70+
}
71+
return p, nil
72+
}
73+
74+
// stripLeadingDashDash drops a leading "--" separator (cobra with
75+
// DisableFlagParsing keeps it in args).
76+
func stripLeadingDashDash(args []string) []string {
77+
if len(args) > 0 && args[0] == "--" {
78+
return args[1:]
79+
}
80+
return args
81+
}
82+
83+
// awsArgs prepends the endpoint + region flags to the user's args (creds go in the
84+
// env, never argv, per spec 29 / §7.5 secret hygiene).
85+
func awsArgs(endpoint, region string, args []string) []string {
86+
out := []string{"--endpoint-url=" + endpoint, "--region=" + region}
87+
return append(out, args...)
88+
}
89+
90+
// awsEnv layers the LocalStack/MinIO dev credentials over the parent environment.
91+
func awsEnv(parent []string, access, secret, region string) []string {
92+
env := append([]string{}, parent...)
93+
env = append(env,
94+
"AWS_ACCESS_KEY_ID="+access,
95+
"AWS_SECRET_ACCESS_KEY="+secret,
96+
"AWS_DEFAULT_REGION="+region,
97+
"AWS_REGION="+region,
98+
)
99+
return env
100+
}

0 commit comments

Comments
 (0)