diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index acc306b..5657267 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -29,7 +29,9 @@ jobs: run: sudo apt-get install -y libsqlite3-dev - name: Run Go tests - run: CGO_ENABLED=1 go test ./internal/... -v + # ./... rather than ./internal/... so cmd/devcloud's ServicePlugin + # conformance test runs in CI too. + run: CGO_ENABLED=1 go test ./... -v env: CGO_ENABLED: "1" diff --git a/.github/workflows/smithy-sync.yml b/.github/workflows/smithy-sync.yml index b2dc733..4de28b6 100644 --- a/.github/workflows/smithy-sync.yml +++ b/.github/workflows/smithy-sync.yml @@ -24,7 +24,9 @@ jobs: run: sudo apt-get install -y libsqlite3-dev - name: Download latest Smithy models - run: bash scripts/download-smithy-models.sh + # --refresh re-downloads the committed models; without it every model + # already in the tree is skipped and this job can never find a change. + run: bash scripts/download-smithy-models.sh --refresh - name: Run code generation run: | @@ -39,8 +41,11 @@ jobs: - name: Check for changes id: changes + # --porcelain rather than `git diff --quiet` so a newly generated + # package (an untracked directory) counts as a change instead of being + # silently dropped on the runner. run: | - if git diff --quiet; then + if [ -z "$(git status --porcelain)" ]; then echo "changed=false" >> $GITHUB_OUTPUT else echo "changed=true" >> $GITHUB_OUTPUT @@ -48,7 +53,7 @@ jobs: - name: Run tests if: steps.changes.outputs.changed == 'true' - run: CGO_ENABLED=1 go test ./internal/... -v + run: CGO_ENABLED=1 go test ./... -v - name: Create Pull Request if: steps.changes.outputs.changed == 'true' diff --git a/Makefile b/Makefile index d60abab..4d773a8 100644 --- a/Makefile +++ b/Makefile @@ -36,7 +36,7 @@ changelog: @changie batch $(VERSION) && changie merge stats: - @svcs=$$(awk '/^services:/{found=1;next} /^[^ \t]/{found=0} found && /^[ \t]+[a-z][a-z0-9_]+:$$/' internal/config/default.yaml | wc -l | tr -d ' '); \ + @svcs=$$(grep -rho 'DefaultRegistry.Register(' internal/services | wc -l | tr -d ' '); \ ops=$$(grep -r 'case "' internal/services/*/provider.go 2>/dev/null | wc -l | tr -d ' '); \ echo "Services: $$svcs"; \ echo "Operations: $$ops" diff --git a/README.md b/README.md index ae12d9a..a6643fc 100644 --- a/README.md +++ b/README.md @@ -26,12 +26,12 @@ Today DevCloud targets **AWS**. Our long-term goal is to support the full range - **104 AWS services** across all major categories (run `make stats` for current counts; see [services-matrix.md](docs/services-matrix.md)) - **boto3-compatible** — a 729-test compatibility suite runs in CI (`make test-compat`); works with most boto3 apps, and unsupported operations return a clean AWS error, never a false success - **Cross-service integration** — CFN provisioning, DDB Streams → Lambda, EventBridge targets, S3 → Lambda -- **Smithy-driven codegen** — auto-generate Go interfaces, types, and serializers from Smithy models +- **Smithy-driven codegen** — auto-generate Go types, routers, and error catalogues from Smithy models - **Weekly auto-sync** — GitHub Actions keeps generated code up to date with upstream AWS API changes - **Single binary, zero-config** — one Docker image, one port (4747), no config file required (embedded defaults) - **Environment variable overrides** — `DEVCLOUD_SERVICES`, `DEVCLOUD_DATA_DIR`, `DEVCLOUD_PORT` for quick configuration without YAML - **SDK/CLI compatible** — works with the AWS SDK, CLI, Terraform, CDK out of the box -- **Admin API** — opt-in REST + WebSocket at `/devcloud/api/*` for service status, resource listing, and live request logs (`admin.enabled: true`). The web dashboard UI lives in a separate repository. +- **Admin API** — opt-in REST at `/devcloud/api/*` for service status, resource listing, and recent request logs (`admin.enabled: true`). The web dashboard UI lives in a separate repository. ## Quick Start diff --git a/changes/unreleased/Added-20260718-081112.yaml b/changes/unreleased/Added-20260718-081112.yaml index a852ff8..0e60ccb 100644 --- a/changes/unreleased/Added-20260718-081112.yaml +++ b/changes/unreleased/Added-20260718-081112.yaml @@ -1,5 +1,5 @@ kind: Added -body: Add SQS dead-letter redrive operations (ListDeadLetterSourceQueues, StartMessageMoveTask, ListMessageMoveTasks, CancelMessageMoveTask) — SQS now covers all 23 operations +body: Add the four SQS dead-letter redrive operations (ListDeadLetterSourceQueues, Start/List/CancelMessageMoveTask), completing SQS at 23 operations time: 2026-07-18T08:11:12.483907+09:00 custom: Issue: "94" diff --git a/changes/unreleased/Added-20260718-222651.yaml b/changes/unreleased/Added-20260718-222651.yaml index d05c891..566d789 100644 --- a/changes/unreleased/Added-20260718-222651.yaml +++ b/changes/unreleased/Added-20260718-222651.yaml @@ -1,5 +1,5 @@ kind: Added -body: Document the stable `ServicePlugin` plugin API in docs/plugin-api.md (method contracts, config keys, error convention, and the v1.x stability policy), enforced by a conformance test over every registered service +body: Document the `ServicePlugin` API and its v1.x stability policy in docs/plugin-api.md, enforced by a conformance test over every registered service time: 2026-07-18T22:26:51.495862+09:00 custom: Issue: "96" diff --git a/changes/unreleased/Added-20260718-222652.yaml b/changes/unreleased/Added-20260718-222652.yaml index bc82f9d..16c55c5 100644 --- a/changes/unreleased/Added-20260718-222652.yaml +++ b/changes/unreleased/Added-20260718-222652.yaml @@ -1,5 +1,5 @@ kind: Added -body: Enable and add boto3 compatibility tests for CodeConnections, DMS, and Verified Permissions (previously scaffolded but not enabled) +body: Enable boto3 compatibility tests for CodeConnections, DMS, and Verified Permissions time: 2026-07-18T22:26:52.000000+09:00 custom: Issue: "96" diff --git a/changes/unreleased/Added-20260718-233026.yaml b/changes/unreleased/Added-20260718-233026.yaml index a968c0a..da7e8cb 100644 --- a/changes/unreleased/Added-20260718-233026.yaml +++ b/changes/unreleased/Added-20260718-233026.yaml @@ -1,5 +1,5 @@ kind: Added -body: 'Add 24 operations to core services: EC2 instance lifecycle (Start/Stop/Reboot), DescribeAvailabilityZones/DescribeRegions, security group rules (Authorize/Revoke ingress+egress), Elastic IP association, DeleteSecurityGroup/DeleteSubnet/DescribeTags/DeleteTags, and IAM group policy management (attach/detach/put/get/delete/list)' +body: 'Add 24 core-service operations: EC2 instance lifecycle, region/AZ and tag queries, security group rules, Elastic IP association, and IAM group policy management' time: 2026-07-18T23:30:26.666352+09:00 custom: Issue: "97" diff --git a/changes/unreleased/Added-20260719-001541.yaml b/changes/unreleased/Added-20260719-001541.yaml index 5aed721..247c268 100644 --- a/changes/unreleased/Added-20260719-001541.yaml +++ b/changes/unreleased/Added-20260719-001541.yaml @@ -1,5 +1,5 @@ kind: Added -body: 'Add a generic Smithy-driven CRUD fallback engine (internal/shared/crud) that auto-serves ~2,200 standard CRUD-shaped operations across all 46 JSON-protocol services with plausible, store-backed responses; hand-implemented operations always take precedence and unclassifiable operations still return an honest error. Documented in docs/crud-engine.md' +body: Serve ~2,200 standard CRUD operations across the 46 JSON-protocol services from a Smithy-driven fallback engine (`internal/shared/crud`); hand-written operations take precedence and unclassifiable ones still return an error. See docs/crud-engine.md time: 2026-07-19T00:15:41.279904+09:00 custom: Issue: "98" diff --git a/changes/unreleased/Changed-20260724-000001.yaml b/changes/unreleased/Changed-20260724-000001.yaml index 43ff339..7d5967b 100644 --- a/changes/unreleased/Changed-20260724-000001.yaml +++ b/changes/unreleased/Changed-20260724-000001.yaml @@ -1,5 +1,5 @@ kind: Changed -body: Rename the `internal/dashboard` package to `internal/admin` and the `dashboard.enabled` config key to `admin.enabled`, reflecting its role as a general-purpose admin/introspection API rather than a UI-bound dashboard. The old `dashboard.enabled` key is still honoured for one release with a deprecation warning. +body: Rename `internal/dashboard` to `internal/admin` and the `dashboard.enabled` config key to `admin.enabled`. The old key is honoured for one release with a deprecation warning. time: 2026-07-24T00:00:01.000000+09:00 custom: Issue: "111" diff --git a/changes/unreleased/Changed-20260725-000001.yaml b/changes/unreleased/Changed-20260725-000001.yaml index 4e9aff5..501059c 100644 --- a/changes/unreleased/Changed-20260725-000001.yaml +++ b/changes/unreleased/Changed-20260725-000001.yaml @@ -1,5 +1,5 @@ kind: Changed -body: Skip request log collection on the AWS request hot path when the admin API is disabled (the default). The log collector is now built only when `admin.enabled` is true, avoiding a per-request mutex lock and ring-buffer write that no consumer could read. +body: Build the request log collector only when `admin.enabled` is set, dropping a per-request mutex lock and ring-buffer write from the AWS request hot path time: 2026-07-25T00:00:01.000000+09:00 custom: Issue: "112" diff --git a/changes/unreleased/Changed-20260730-000001.yaml b/changes/unreleased/Changed-20260730-000001.yaml new file mode 100644 index 0000000..98a17c6 --- /dev/null +++ b/changes/unreleased/Changed-20260730-000001.yaml @@ -0,0 +1,5 @@ +kind: Changed +body: 'The `services` config block is now optional: omit it and every registered service starts with `data_dir ./data/`; list services and only those start. Replaces 325 lines of identical boilerplate.' +time: 2026-07-30T00:00:01.000000+09:00 +custom: + Issue: "120" diff --git a/changes/unreleased/Fixed-20260718-222651.yaml b/changes/unreleased/Fixed-20260718-222651.yaml index 057f479..802045a 100644 --- a/changes/unreleased/Fixed-20260718-222651.yaml +++ b/changes/unreleased/Fixed-20260718-222651.yaml @@ -1,5 +1,5 @@ kind: Fixed -body: Correct `X-Amz-Target` service routing for AWS services whose target prefix is dotted (e.g. CodeConnections, DMS); they previously misrouted to CloudTrail via an overbroad `com` prefix match +body: Route `X-Amz-Target` prefixes that contain dots (CodeConnections, DMS) to the right service; they misrouted to CloudTrail time: 2026-07-18T22:26:51.484673+09:00 custom: Issue: "96" diff --git a/changes/unreleased/Fixed-20260718-222652.yaml b/changes/unreleased/Fixed-20260718-222652.yaml index 17b385e..00e1416 100644 --- a/changes/unreleased/Fixed-20260718-222652.yaml +++ b/changes/unreleased/Fixed-20260718-222652.yaml @@ -1,5 +1,5 @@ kind: Fixed -body: Unimplemented operations now return a `NotImplemented` error (HTTP 501) instead of a misleading empty `200` success, so SDKs no longer see a false success and can tell a not-yet-emulated real operation apart from a genuinely invalid action; applied uniformly across all service providers (the ChangeResourceRecordSets record-action validation path keeps `InvalidChangeBatch`) +body: Unimplemented operations return `NotImplemented` (HTTP 501) instead of an empty `200`, so SDKs can tell a not-yet-emulated operation from an invalid one time: 2026-07-18T22:26:52.000000+09:00 custom: Issue: "96" diff --git a/changes/unreleased/Fixed-20260720-054448.yaml b/changes/unreleased/Fixed-20260720-054448.yaml index 44fc386..a052384 100644 --- a/changes/unreleased/Fixed-20260720-054448.yaml +++ b/changes/unreleased/Fixed-20260720-054448.yaml @@ -1,5 +1,5 @@ kind: Fixed -body: 'EC2: Describe{SecurityGroups,Tags,Addresses} now honor GroupId/AllocationId/PublicIp selectors and Filters (and raise NotFound for unknown IDs) instead of returning every resource; delete-by-name removes a single security group; batch Start/Stop is atomic; AssociateAddress validates the instance; duplicate/absent security-group rules are rejected; Authorize/Revoke accept GroupName; all-traffic rules omit ports' +body: 'EC2: Describe{SecurityGroups,Tags,Addresses} honor their selectors and Filters instead of returning every resource, unknown IDs raise NotFound, batch Start/Stop is atomic, and duplicate or absent security group rules are rejected' time: 2026-07-20T05:44:48.649862+09:00 custom: Issue: "97" diff --git a/changes/unreleased/Fixed-20260720-055500.yaml b/changes/unreleased/Fixed-20260720-055500.yaml index 229e699..9603c17 100644 --- a/changes/unreleased/Fixed-20260720-055500.yaml +++ b/changes/unreleased/Fixed-20260720-055500.yaml @@ -1,5 +1,5 @@ kind: Fixed -body: 'DynamoDB: numeric (''N'') sort/range keys now order by true numeric value across the full 38-digit precision via a NUMTEXT SQLite collation, instead of a float CAST that lost precision past 2^53' +body: 'DynamoDB: numeric sort keys order by true value over the full 38 digits via a NUMTEXT collation, instead of a float CAST that lost precision past 2^53' time: 2026-07-20T05:55:00.000000+09:00 custom: Issue: "97" diff --git a/changes/unreleased/Fixed-20260730-000002.yaml b/changes/unreleased/Fixed-20260730-000002.yaml new file mode 100644 index 0000000..371d57a --- /dev/null +++ b/changes/unreleased/Fixed-20260730-000002.yaml @@ -0,0 +1,5 @@ +kind: Fixed +body: 'The weekly Smithy model sync could never report an update: it skipped every model already in the tree (all of them), and 14 entries of its hand-maintained service list 404''d upstream. The list is now derived from the tree, the workflow re-downloads with `--refresh`, and downloads are atomic so a failed fetch cannot delete a committed model.' +time: 2026-07-30T00:00:02.000000+09:00 +custom: + Issue: "120" diff --git a/changes/unreleased/Removed-20260724-000000.yaml b/changes/unreleased/Removed-20260724-000000.yaml index 0207a11..e7030d9 100644 --- a/changes/unreleased/Removed-20260724-000000.yaml +++ b/changes/unreleased/Removed-20260724-000000.yaml @@ -1,5 +1,5 @@ kind: Removed -body: Extract the Next.js web dashboard frontend to a separate repository. The Go binary no longer builds, bundles, or serves a web UI; it exposes only the opt-in admin API at `/devcloud/api/*`. +body: Extract the web dashboard to a separate repository. The binary builds and serves no UI, only the opt-in admin API at `/devcloud/api/*`. time: 2026-07-24T00:00:00.000000+09:00 custom: Issue: "111" diff --git a/changes/unreleased/Removed-20260730-000000.yaml b/changes/unreleased/Removed-20260730-000000.yaml new file mode 100644 index 0000000..fe3a192 --- /dev/null +++ b/changes/unreleased/Removed-20260730-000000.yaml @@ -0,0 +1,5 @@ +kind: Removed +body: 'Remove dead surface found by a repo-wide over-engineering audit: the event bus and admin WebSocket, the `GetMetrics` plugin API and `/devcloud/api/metrics` (resource counts remain on `/devcloud/api/services`), `shared.ResourceStore`, `gateway.ExtractAccountID`, and the `auth.enabled` key — still read for one release so enabling it warns that credentials are accepted regardless. Drops the `gorilla/websocket` dependency.' +time: 2026-07-30T00:00:00.000000+09:00 +custom: + Issue: "120" diff --git a/cmd/codegen/main.go b/cmd/codegen/main.go index 2c560f6..b2aa4d8 100644 --- a/cmd/codegen/main.go +++ b/cmd/codegen/main.go @@ -13,6 +13,14 @@ import ( "github.com/skyoo2003/devcloud/internal/codegen" ) +// handWritten services get no generated package: their provider is implemented +// by hand elsewhere (sts lives in internal/services/iam alongside its store). +// Their generated stubs were deleted in #91 and #96; without this skip every +// `make codegen` run resurrects them as untracked files that the sync +// workflow's diff check cannot see. sts is Query-protocol, so it contributes +// nothing to the JSON-only CRUD registry either. +var handWritten = map[string]bool{"sts": true} + func main() { modelsDir := flag.String("models", "./smithy-models", "Directory containing Smithy JSON model files") outputDir := flag.String("output", "./internal/generated", "Output directory for generated code") @@ -59,6 +67,11 @@ func main() { continue } + if handWritten[model.ServiceID] { + fmt.Printf("Skipping %s (%s): hand-written provider\n", model.ServiceName, model.ServiceID) + continue + } + fmt.Printf("Generating %s (%s)...\n", model.ServiceName, model.ServiceID) if err := gen.GenerateAll(model, *outputDir, *scaffoldDir); err != nil { fmt.Fprintf(os.Stderr, "Error generating %s: %v\n", model.ServiceID, err) @@ -86,7 +99,7 @@ func main() { fmt.Fprintf(os.Stderr, "Error creating crudregistry dir: %v\n", err) os.Exit(1) } - if err := os.WriteFile(filepath.Join(regDir, "registry_gen.go"), []byte(content), 0644); err != nil { + if err := codegen.WriteGo(filepath.Join(regDir, "registry_gen.go"), content); err != nil { fmt.Fprintf(os.Stderr, "Error writing CRUD registry: %v\n", err) os.Exit(1) } diff --git a/cmd/devcloud/buffer.go b/cmd/devcloud/buffer.go deleted file mode 100644 index 2399c32..0000000 --- a/cmd/devcloud/buffer.go +++ /dev/null @@ -1,43 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -package main - -import ( - "context" - "log/slog" -) - -// bufferHandler is a slog.Handler that captures every record instead of -// emitting it, so log calls made before the operator-configured handler is -// installed (notably config.parse() warnings) can be replayed afterwards and -// honor logging.format / logging.level. See flushTo. -// -// ponytail: single-threaded startup, add a mutex if buffered logging ever -// goes concurrent. -type bufferHandler struct { - records []slog.Record -} - -// Enabled buffers all levels; the real handler filters on replay in flushTo. -func (h *bufferHandler) Enabled(context.Context, slog.Level) bool { return true } - -func (h *bufferHandler) Handle(_ context.Context, r slog.Record) error { - h.records = append(h.records, r.Clone()) - return nil -} - -// WithAttrs/WithGroup return the handler unchanged: config-time logging uses -// plain slog.Warn/Info without attr or group chaining, so nothing is lost. -func (h *bufferHandler) WithAttrs([]slog.Attr) slog.Handler { return h } -func (h *bufferHandler) WithGroup(string) slog.Handler { return h } - -// flushTo replays buffered records through dst, honoring dst's level filter, -// then clears the buffer. -func (h *bufferHandler) flushTo(dst slog.Handler) { - for _, r := range h.records { - if dst.Enabled(context.Background(), r.Level) { - _ = dst.Handle(context.Background(), r) - } - } - h.records = nil -} diff --git a/cmd/devcloud/buffer_test.go b/cmd/devcloud/buffer_test.go deleted file mode 100644 index ee5e036..0000000 --- a/cmd/devcloud/buffer_test.go +++ /dev/null @@ -1,49 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -package main - -import ( - "bytes" - "context" - "log/slog" - "strings" - "testing" -) - -// TestBufferHandler_FlushHonorsFormatAndLevel verifies that records buffered -// before the real handler is installed are replayed as JSON and filtered by -// the destination handler's level — the two acceptance criteria from #113. -func TestBufferHandler_FlushHonorsFormatAndLevel(t *testing.T) { - buf := &bufferHandler{} - log := slog.New(buf) - log.Info("config file not found") - log.Warn("dashboard key deprecated", "token", "tierX") - - var out bytes.Buffer - dst := slog.NewJSONHandler(&out, &slog.HandlerOptions{Level: slog.LevelWarn}) - buf.flushTo(dst) - - got := out.String() - if strings.Contains(got, "config file not found") { - t.Errorf("info record should be dropped at level=warn, got: %s", got) - } - if !strings.Contains(got, `"dashboard key deprecated"`) || !strings.Contains(got, `"token":"tierX"`) { - t.Errorf("warn record (with attrs) should be replayed as JSON, got: %s", got) - } - - // flush clears the buffer so a second flush is a no-op. - out.Reset() - buf.flushTo(dst) - if out.Len() != 0 { - t.Errorf("second flush should emit nothing, got: %s", out.String()) - } -} - -// TestBufferHandler_EnabledBuffersAllLevels confirms buffering does not drop -// records below the eventual threshold; the level filter applies only on flush. -func TestBufferHandler_EnabledBuffersAllLevels(t *testing.T) { - buf := &bufferHandler{} - if !buf.Enabled(context.Background(), slog.LevelDebug) { - t.Fatal("bufferHandler must buffer all levels, including debug") - } -} diff --git a/cmd/devcloud/main.go b/cmd/devcloud/main.go index 69dc908..0719dab 100644 --- a/cmd/devcloud/main.go +++ b/cmd/devcloud/main.go @@ -10,18 +10,19 @@ import ( "net/http" "os" "os/signal" - "sort" "syscall" "time" "github.com/skyoo2003/devcloud/internal/admin" "github.com/skyoo2003/devcloud/internal/config" - "github.com/skyoo2003/devcloud/internal/eventbus" "github.com/skyoo2003/devcloud/internal/gateway" "github.com/skyoo2003/devcloud/internal/plugin" iamsvc "github.com/skyoo2003/devcloud/internal/services/iam" ) +// initOrder lists the services that must come up for DevCloud to be useful, in +// dependency order — sts borrows iam's store, so iam precedes it. A failure +// here is fatal; every other registered service only warns. var initOrder = []string{ "s3", "sqs", "dynamodb", "iam", "sts", "lambda", "kms", "sns", "secretsmanager", "ssm", "cloudwatchlogs", "cloudwatch", @@ -32,89 +33,78 @@ func main() { cfgPath := flag.String("config", "", "Path to config file (optional; uses ./devcloud.yaml if present, else embedded defaults)") flag.Parse() - // Buffer log records emitted while loading config (parse() deprecation and - // unknown-tier warnings, etc.) so they can be replayed through the - // operator-configured handler below and honor logging.format / level. - buf := &bufferHandler{} - slog.SetDefault(slog.New(buf)) - var ( - cfg *config.Config - err error + cfg *config.Config + warnings []string + err error ) if *cfgPath != "" { // Explicit --config flag: the file must exist. - cfg, err = config.Load(*cfgPath) + cfg, warnings, err = config.Load(*cfgPath) } else { // No flag: prefer ./devcloud.yaml in the working directory; fall back to embedded defaults. - cfg, err = config.LoadOrDefault("devcloud.yaml") + cfg, warnings, err = config.LoadOrDefault("devcloud.yaml") } if err != nil { // Load failed, so there is no logging config to honor; fall back to the - // default handler, flush any buffered warnings, then surface the error. + // default handler and surface the error. setupLogging(config.LoggingConfig{}) - buf.flushTo(slog.Default().Handler()) slog.Error("failed to load config", "error", err) os.Exit(1) } + // Config warnings are returned rather than logged by the config package so + // they honor logging.format / logging.level. setupLogging(cfg.Logging) - buf.flushTo(slog.Default().Handler()) + for _, w := range warnings { + slog.Warn(w) + } registry := plugin.DefaultRegistry - // Initialize services in dependency order. - for _, name := range initOrder { - svcCfg, exists := cfg.Services[name] - if !exists || !svcCfg.Enabled { - continue + initService := func(name string, fatal bool) { + svcCfg := cfg.Service(name) + if !svcCfg.Enabled { + return } pluginCfg := plugin.PluginConfig{ DataDir: svcCfg.DataDir, Options: buildOptions(name, cfg, registry), } if _, err := registry.Init(name, pluginCfg); err != nil { - slog.Error("failed to init service", "service", name, "error", err) - os.Exit(1) + if fatal { + slog.Error("failed to init service", "service", name, "error", err) + os.Exit(1) + } + slog.Warn("service init failed", "service", name, "error", err) + return } slog.Info("service initialized", "service", name) } - // Initialize any remaining enabled services not in initOrder, in - // deterministic alphabetical order so startup logs and init sequencing - // are reproducible across runs. - remaining := make([]string, 0, len(cfg.Services)) - for name := range cfg.Services { - remaining = append(remaining, name) + for _, name := range initOrder { + initService(name, true) } - sort.Strings(remaining) - for _, name := range remaining { - svcCfg := cfg.Services[name] - if !svcCfg.Enabled { - continue - } + // RegisteredServices() is sorted, so the long tail starts in a reproducible + // order. Services already brought up above are skipped. + for _, name := range registry.RegisteredServices() { if _, ok := registry.Get(name); ok { continue } - pluginCfg := plugin.PluginConfig{ - DataDir: svcCfg.DataDir, - Options: buildOptions(name, cfg, registry), - } - if _, err := registry.Init(name, pluginCfg); err != nil { - slog.Warn("service init failed", "service", name, "error", err) - continue - } - slog.Info("service initialized", "service", name) + initService(name, false) } - if cfg.Auth.Enabled { - slog.Warn("auth.enabled=true but SigV4 enforcement is not yet implemented; requests are accepted regardless of signature validity") + // A services block is authoritative and `enabled` defaults to Go's false, + // so `services:\n s3:\n` (or a typo'd DEVCLOUD_SERVICES) silently brings up + // nothing. Serving zero services is never what an operator wanted. + if len(registry.ActiveServices()) == 0 { + slog.Warn("no services enabled; a 'services' block only starts what it lists, and each entry still needs 'enabled: true' — also check DEVCLOUD_SERVICES") } - // Admin API: build the REST + WebSocket handler only when the operator - // opted in via admin.enabled. Otherwise expose a 404 handler so the admin - // routes don't leak service internals. This binary serves no web UI; the - // dashboard frontend lives in a separate repository and talks to this API. + // Admin API: build the REST handler only when the operator opted in via + // admin.enabled. Otherwise expose a 404 handler so the admin routes don't + // leak service internals. This binary serves no web UI; the dashboard + // frontend lives in a separate repository and talks to this API. // The log collector is only built when admin is enabled; otherwise no // consumer can ever read it, so a nil collector keeps Add off the request // hot path (see gateway.New). @@ -122,14 +112,7 @@ func main() { adminHandler := http.NotFoundHandler() if cfg.Admin.Enabled { logCollector = admin.NewLogCollector(1000) - adminAPI := admin.NewAPI(registry, logCollector) - hub := admin.NewHub(eventbus.New()) - go hub.Start() - - adminMux := http.NewServeMux() - adminMux.Handle("/devcloud/api/", adminAPI.Handler()) - adminMux.HandleFunc("/devcloud/api/ws", hub.ServeWS) - adminHandler = adminMux + adminHandler = admin.NewAPI(registry, logCollector).Handler() slog.Info("admin API enabled") } gw := gateway.New(cfg.Server.Port, registry, adminHandler, logCollector) diff --git a/docs/architecture.md b/docs/architecture.md index 8acf0a4..0376f4d 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -65,7 +65,6 @@ type ServicePlugin interface { Shutdown(ctx context.Context) error HandleRequest(ctx context.Context, op string, req *http.Request) (*Response, error) ListResources(ctx context.Context) ([]Resource, error) - GetMetrics(ctx context.Context) (*ServiceMetrics, error) } ``` @@ -102,12 +101,16 @@ smithy-models/*.json (AWS Smithy model files) Uses Go templates to produce: │ ├─ types.go — Request/response structs - ├─ interface.go — Service interface (all operations) - ├─ serializer.go — Request marshaling - ├─ deserializer.go — Response unmarshaling - ├─ router.go — Operation routing + ├─ router.go — Method+URI → operation routing (REST services) ├─ errors.go — Service-specific error types └─ base_provider.go — Stub implementation (NotImplementedError) + + There is no generated serializer: providers receive the raw + *http.Request and parse it themselves (map[string]any for JSON + protocols), so types.go/base_provider.go have no wire glue and only + router.go is consumed today — by the REST services that need + MatchOperation. See docs/crud-engine.md for how the long tail of + unimplemented operations is actually served. │ ▼ internal/generated/{service}/ (DO NOT EDIT) @@ -126,24 +129,20 @@ make codegen-s3 ### Weekly Auto-Sync A GitHub Actions workflow runs weekly to: -1. Fetch the latest Smithy models from AWS +1. Re-download the Smithy models (`scripts/download-smithy-models.sh --refresh`) 2. Run codegen -3. Open a PR if any generated code changed - -## Event Bus +3. Open a PR if the models or the generated code changed -The event bus (`internal/eventbus/`) provides in-memory pub/sub for internal communication: - -- Services publish events (resource created, deleted, etc.) -- The admin API subscribes to stream real-time updates via WebSocket -- Loose coupling between services and the admin API +The models under `smithy-models/` are committed on purpose: the download URL +tracks `aws-sdk-go-v2` *main*, so they are the pin that makes `make codegen` +reproducible and offline. Only the weekly job passes `--refresh`, which is what +makes an upstream API change show up as a reviewable model diff next to the +regenerated code. ## Admin API -The admin API (`internal/admin/`) provides: - -- **REST API** at `/devcloud/api/` — service status, resource listing, request logs -- **WebSocket** at `/devcloud/api/ws` — real-time event streaming +The admin API (`internal/admin/`) provides a **REST API** at `/devcloud/api/` — +service status, resource listing, and recent request logs. It is disabled by default (`admin.enabled: false`). The web dashboard UI is a separate project (its own repository) that consumes this API; the Go server @@ -165,15 +164,13 @@ devcloud/ │ ├── config/ # YAML config loading, env overrides │ ├── generated/ # Auto-generated code (DO NOT EDIT; run `make stats` for count) │ ├── services/ # Service implementations (run `make stats` for count) -│ ├── admin/ # Admin REST API + WebSocket -│ ├── eventbus/ # In-memory event pub/sub +│ ├── admin/ # Admin REST API │ └── storage/ # Shared storage abstractions ├── docker/ # Dockerfile, docker-compose.yml ├── smithy-models/ # AWS Smithy JSON model files ├── tests/compatibility/ # Python/boto3 compatibility tests ├── docs/ # Documentation ├── Makefile -├── devcloud.yaml # Default configuration └── go.mod ``` diff --git a/docs/configuration.md b/docs/configuration.md index a955f7f..5cddca2 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -1,6 +1,6 @@ # Configuration -**Configuration is optional.** DevCloud ships with built-in defaults: running `devcloud` with no flags enables all 101 services on port 4747, storing data under `./data//`. The embedded defaults are compiled into the binary from [`internal/config/default.yaml`](https://github.com/skyoo2003/devcloud/blob/main/internal/config/default.yaml). +**Configuration is optional.** DevCloud ships with built-in defaults: running `devcloud` with no flags enables every registered service on port 4747, storing data under `./data//` (run `make stats` for the current service count). The embedded defaults are compiled into the binary from [`internal/config/default.yaml`](https://github.com/skyoo2003/devcloud/blob/main/internal/config/default.yaml). To override defaults, provide a YAML file. DevCloud looks for config in this order: @@ -26,12 +26,14 @@ Environment variables override YAML values for selected keys (see [Environment V ### Services -Each service has the following options: +The `services` block is **optional and authoritative**: omit it (as the embedded +default does) and every registered service starts with `data_dir +./data/`; list any service and *only* the services you list start. | Key | Default | Description | |-----|---------|-------------| -| `services..enabled` | `true` | Enable or disable the service | -| `services..data_dir` | varies | Data directory for persistent storage | +| `services..enabled` | `false` | Enable the service. **Required per entry** — listing a service is not enough, `enabled: true` still has to be set. (With no `services` block at all, every service is enabled.) | +| `services..data_dir` | `./data/` | Data directory for persistent storage | Service-specific options: @@ -41,20 +43,17 @@ Service-specific options: | `services.lambda.warm_containers` | `0` | Number of warm containers to keep | | `services.iam.enforce_policies` | `false` | Enforce IAM policies (experimental) | -### Auth - -| Key | Default | Description | -|-----|---------|-------------| -| `auth.enabled` | `false` | Enable SigV4 signature validation | - ### Admin API | Key | Default | Description | |-----|---------|-------------| -| `admin.enabled` | `false` | Enable the admin REST + WebSocket API at `/devcloud/api/*` | +| `admin.enabled` | `false` | Enable the admin REST API at `/devcloud/api/*` | > The `dashboard` key was renamed to `admin`. The old `dashboard.enabled` key is still honoured for one release (with a deprecation warning); migrate to `admin.enabled`. +> SigV4 signature validation is not implemented, so there is no `auth` key. Any +> credentials are accepted. + ### Logging | Key | Default | Description | @@ -68,6 +67,8 @@ Service-specific options: server: port: 4747 +# Optional: listing services restricts startup to exactly this set. +# Omit the whole block to run every registered service. services: s3: enabled: true @@ -87,9 +88,6 @@ services: enabled: true data_dir: ./data/lambda -auth: - enabled: false - admin: enabled: false @@ -114,7 +112,7 @@ docker run -p 8080:8080 -e DEVCLOUD_PORT=8080 ghcr.io/skyoo2003/devcloud:latest ### `DEVCLOUD_SERVICES` -Comma-separated list of services to enable. When set, **only** the listed services are enabled — all others are disabled regardless of their `enabled` setting in YAML. When not set, each service uses its YAML `enabled` value (or the embedded default of `true`). +Comma-separated list of services to enable. When set, **only** the listed services are enabled — all others are disabled regardless of their `enabled` setting in YAML. When not set, each service uses its YAML `enabled` value (or the embedded default of `true`). An unknown `tierN` token is treated as a literal service name and logged as a warning. **Tier shortcuts** (expand to predefined service groups — see [`internal/config/config.go`](https://github.com/skyoo2003/devcloud/blob/main/internal/config/config.go) for the exact list): @@ -143,7 +141,7 @@ docker run -p 4747:4747 -e DEVCLOUD_SERVICES=tier1 ghcr.io/skyoo2003/devcloud:la ### `DEVCLOUD_DATA_DIR` -Overrides the base data directory for **all** services. When set, every service uses `/` — per-service `data_dir` values in YAML are ignored. When not set, each service falls back to its YAML `data_dir` value (or the embedded default). +Overrides the base data directory for **all** services. When set, every service uses `/` — per-service `data_dir` values in YAML are ignored. When not set, each service falls back to its YAML `data_dir` value, or `./data/`. ```bash # Put all service data under /tmp/devcloud-local diff --git a/docs/getting-started.md b/docs/getting-started.md index 064030e..114709b 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -94,9 +94,7 @@ is exposed under `/devcloud/api/*`: - `GET /devcloud/api/services` — service status overview - `GET /devcloud/api/services/{id}/resources` — resource browser (buckets, queues, tables, functions) -- `GET /devcloud/api/metrics` — aggregate request/error counters -- `GET /devcloud/api/logs` — recent API call logs -- `GET /devcloud/api/ws` — WebSocket live updates +- `GET /devcloud/api/logs` — recent API call logs (`?limit=`) The web dashboard UI that consumes this API lives in a separate repository. diff --git a/docs/plugin-api.md b/docs/plugin-api.md index f35c6f6..05db2e6 100644 --- a/docs/plugin-api.md +++ b/docs/plugin-api.md @@ -20,7 +20,6 @@ type ServicePlugin interface { Shutdown(ctx context.Context) error HandleRequest(ctx context.Context, op string, req *http.Request) (*Response, error) ListResources(ctx context.Context) ([]Resource, error) - GetMetrics(ctx context.Context) (*ServiceMetrics, error) } ``` @@ -35,7 +34,6 @@ type ServicePlugin interface { | `Shutdown(ctx)` | Called once at graceful shutdown (15s budget). Flush/close resources. Idempotent-friendly. | | `HandleRequest(ctx, op, req)` | Handle one API call. `op` is the operation name pre-extracted by the gateway (see [operation names](#operation-names)); it may be `""` for REST protocols, in which case derive it from method+path or `X-Amz-Target`. Return a `*Response`; return an `error` only for unexpected internal failures (the gateway wraps those as a `500 InternalError`). **Model AWS errors as a normal `*Response`** with the right status and error body, not as a Go `error`. | | `ListResources(ctx)` | Return the resources this service currently holds (admin API). May be empty. | -| `GetMetrics(ctx)` | Return request/error/resource counters (admin API). May be zero-valued. | These invariants are enforced for every registered service by [`TestServicePluginConformance`](../cmd/devcloud/conformance_test.go). @@ -130,8 +128,7 @@ The registry also exposes `RegisteredServices()` (all registered IDs) and ## API stability Starting at **v1.0**, `ServicePlugin`, `PluginConfig`, `Response`, `Resource`, -`ServiceMetrics`, and the `ProtocolType` constants are stable within the `v1.x` -series: +and the `ProtocolType` constants are stable within the `v1.x` series: - No method will be **removed** from `ServicePlugin` and no existing method **signature** will change in a `v1.x` release. diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index 5025708..da387d6 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -67,7 +67,6 @@ By default, DevCloud accepts any signature — it checks the SigV4 *format* but - Your client is configured with dummy but **non-empty** credentials (`aws_access_key_id="test"`, `aws_secret_access_key="test"`). - The `endpoint_url` points to DevCloud, not real AWS. -- If you enabled `auth.enabled: true`, either disable it or provide the credentials you registered. ### `NoSuchBucket` / `ResourceNotFoundException` after restart diff --git a/go.mod b/go.mod index 125e0ff..6284185 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,6 @@ go 1.26 toolchain go1.26.1 require ( - github.com/gorilla/websocket v1.5.3 github.com/mattn/go-sqlite3 v1.14.48 github.com/stretchr/testify v1.11.1 gopkg.in/yaml.v3 v3.0.1 diff --git a/go.sum b/go.sum index 087a508..7844577 100644 --- a/go.sum +++ b/go.sum @@ -1,8 +1,6 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= -github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= diff --git a/internal/admin/api.go b/internal/admin/api.go index f00ce6b..9ab31dc 100644 --- a/internal/admin/api.go +++ b/internal/admin/api.go @@ -32,8 +32,6 @@ func (d *API) Handler() http.Handler { mux.HandleFunc("/devcloud/api/services", d.handleServices) mux.HandleFunc("/devcloud/api/services/", d.handleServiceResources) - mux.HandleFunc("/devcloud/api/metrics", d.handleMetrics) - mux.HandleFunc("/devcloud/api/metrics/", d.handleServiceMetrics) mux.HandleFunc("/devcloud/api/logs", d.handleLogs) return mux @@ -73,10 +71,6 @@ func (d *API) handleServices(w http.ResponseWriter, r *http.Request) { } resources, _ := p.ListResources(ctx) - // GetMetrics is called but we only use the resource count from - // ListResources here; stats are available via /devcloud/api/metrics. - _, _ = p.GetMetrics(ctx) - services = append(services, serviceInfo{ ID: p.ServiceID(), Name: p.ServiceName(), @@ -118,71 +112,6 @@ func (d *API) handleServiceResources(w http.ResponseWriter, r *http.Request) { writeJSON(w, http.StatusOK, resources) } -// aggregateMetrics is the payload for GET /devcloud/api/metrics. -type aggregateMetrics struct { - TotalRequests int64 `json:"totalRequests"` - ErrorCount int64 `json:"errorCount"` - Services int `json:"services"` -} - -// handleMetrics handles GET /devcloud/api/metrics. -func (d *API) handleMetrics(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodGet { - http.Error(w, "method not allowed", http.StatusMethodNotAllowed) - return - } - - ids := d.registry.ActiveServices() - ctx := context.Background() - - var totalRequests, errorCount int64 - for _, id := range ids { - p, ok := d.registry.Get(id) - if !ok { - continue - } - m, err := p.GetMetrics(ctx) - if err != nil || m == nil { - continue - } - totalRequests += m.TotalRequests - errorCount += m.ErrorCount - } - - writeJSON(w, http.StatusOK, aggregateMetrics{ - TotalRequests: totalRequests, - ErrorCount: errorCount, - Services: len(ids), - }) -} - -// handleServiceMetrics handles GET /devcloud/api/metrics/{service}. -func (d *API) handleServiceMetrics(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodGet { - http.Error(w, "method not allowed", http.StatusMethodNotAllowed) - return - } - - serviceID := strings.TrimPrefix(r.URL.Path, "/devcloud/api/metrics/") - - p, ok := d.registry.Get(serviceID) - if !ok { - http.Error(w, "service not found", http.StatusNotFound) - return - } - - m, err := p.GetMetrics(context.Background()) - if err != nil { - http.Error(w, "failed to get metrics", http.StatusInternalServerError) - return - } - if m == nil { - m = &plugin.ServiceMetrics{} - } - - writeJSON(w, http.StatusOK, m) -} - // handleLogs handles GET /devcloud/api/logs. func (d *API) handleLogs(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodGet { diff --git a/internal/admin/api_test.go b/internal/admin/api_test.go index 0990b51..0ebd2ea 100644 --- a/internal/admin/api_test.go +++ b/internal/admin/api_test.go @@ -21,7 +21,6 @@ type mockServicePlugin struct { id string name string resources []plugin.Resource - metrics *plugin.ServiceMetrics } func (m *mockServicePlugin) ServiceID() string { return m.id } @@ -37,9 +36,6 @@ func (m *mockServicePlugin) HandleRequest(ctx context.Context, op string, req *h func (m *mockServicePlugin) ListResources(ctx context.Context) ([]plugin.Resource, error) { return m.resources, nil } -func (m *mockServicePlugin) GetMetrics(ctx context.Context) (*plugin.ServiceMetrics, error) { - return m.metrics, nil -} // newTestRegistry creates a Registry with a single mock plugin already active. func newTestRegistry(p *mockServicePlugin) *plugin.Registry { @@ -63,7 +59,6 @@ func TestAPI_Services(t *testing.T) { {Type: "bucket", ID: "my-bucket", Name: "my-bucket"}, {Type: "bucket", ID: "other-bucket", Name: "other-bucket"}, }, - metrics: &plugin.ServiceMetrics{TotalRequests: 10, ErrorCount: 1}, } reg := newTestRegistry(p) lc := NewLogCollector(10) diff --git a/internal/admin/logger.go b/internal/admin/logger.go index 59c4a38..8ffa962 100644 --- a/internal/admin/logger.go +++ b/internal/admin/logger.go @@ -7,11 +7,6 @@ import ( "time" ) -const ( - defaultLogCollectorSize = 1000 - maxLogCollectorSize = 10000 -) - // RequestLog holds details about a single API request. type RequestLog struct { Method string `json:"method"` @@ -32,14 +27,11 @@ type LogCollector struct { } // NewLogCollector creates a LogCollector that holds at most maxSize entries. +// A non-positive maxSize would make Add divide by zero, so it is floored at 1. func NewLogCollector(maxSize int) *LogCollector { - if maxSize <= 0 { - maxSize = defaultLogCollectorSize - } - if maxSize > maxLogCollectorSize { - maxSize = maxLogCollectorSize + if maxSize < 1 { + maxSize = 1 } - return &LogCollector{ entries: make([]RequestLog, maxSize), maxSize: maxSize, @@ -67,9 +59,6 @@ func (c *LogCollector) Recent(n int) []RequestLog { if n <= 0 || c.count == 0 { return []RequestLog{} } - if n > maxLogCollectorSize { - n = maxLogCollectorSize - } if n > c.count { n = c.count } diff --git a/internal/admin/websocket.go b/internal/admin/websocket.go deleted file mode 100644 index c494961..0000000 --- a/internal/admin/websocket.go +++ /dev/null @@ -1,142 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -package admin - -import ( - "encoding/json" - "log" - "net/http" - "sync" - "time" - - "github.com/gorilla/websocket" - "github.com/skyoo2003/devcloud/internal/eventbus" -) - -var upgrader = websocket.Upgrader{ - CheckOrigin: func(r *http.Request) bool { - return true - }, -} - -// eventMessage is the JSON format sent to admin clients. -type eventMessage struct { - Source string `json:"source"` - Type string `json:"type"` - Detail map[string]any `json:"detail"` - Timestamp time.Time `json:"timestamp"` -} - -// Client represents a single connected WebSocket client. -type Client struct { - conn *websocket.Conn - send chan []byte -} - -// Hub maintains connected clients and broadcasts EventBus events to them. -type Hub struct { - mu sync.RWMutex - clients map[*Client]bool - bus eventbus.EventBus -} - -// NewHub creates a new Hub with a reference to the provided EventBus. -func NewHub(bus eventbus.EventBus) *Hub { - return &Hub{ - clients: make(map[*Client]bool), - bus: bus, - } -} - -// Start subscribes to all events on the EventBus and begins broadcasting them -// to connected WebSocket clients. -func (h *Hub) Start() { - h.bus.Subscribe("*", func(e eventbus.Event) { - msg := eventMessage{ - Source: e.Source, - Type: e.Type, - Detail: e.Detail, - Timestamp: e.Timestamp, - } - data, err := json.Marshal(msg) - if err != nil { - log.Printf("admin: failed to marshal event: %v", err) - return - } - h.broadcast(data) - }) -} - -// ServeWS upgrades an HTTP connection to WebSocket, registers the client with -// the hub, and starts its write pump. -func (h *Hub) ServeWS(w http.ResponseWriter, r *http.Request) { - conn, err := upgrader.Upgrade(w, r, nil) - if err != nil { - log.Printf("admin: websocket upgrade failed: %v", err) - return - } - - client := &Client{ - conn: conn, - send: make(chan []byte, 256), - } - - h.mu.Lock() - h.clients[client] = true - h.mu.Unlock() - - // cleanup ensures the client is unregistered and the connection is closed - // exactly once, regardless of which pump exits first. - var once sync.Once - cleanup := func() { - once.Do(func() { - h.mu.Lock() - delete(h.clients, client) - h.mu.Unlock() - _ = conn.Close() - }) - } - - // Write pump: forwards messages from the send channel to the WebSocket connection. - go func() { - defer cleanup() - for data := range client.send { - if err := conn.WriteMessage(websocket.TextMessage, data); err != nil { - log.Printf("admin: write error: %v", err) - return - } - } - }() - - // Read pump: keeps the connection alive and detects disconnects. - go func() { - defer func() { - close(client.send) - cleanup() - }() - for { - if _, _, err := conn.ReadMessage(); err != nil { - break - } - } - }() -} - -// broadcast sends data to every connected client. -func (h *Hub) broadcast(data []byte) { - h.mu.RLock() - clients := make([]*Client, 0, len(h.clients)) - for c := range h.clients { - clients = append(clients, c) - } - h.mu.RUnlock() - - for _, c := range clients { - select { - case c.send <- data: - default: - // Drop message if the client's send buffer is full. - log.Printf("admin: client send buffer full, dropping message") - } - } -} diff --git a/internal/admin/websocket_test.go b/internal/admin/websocket_test.go deleted file mode 100644 index a870b8a..0000000 --- a/internal/admin/websocket_test.go +++ /dev/null @@ -1,96 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -package admin - -import ( - "context" - "encoding/json" - "net/http" - "net/http/httptest" - "strings" - "testing" - "time" - - "github.com/gorilla/websocket" - "github.com/skyoo2003/devcloud/internal/eventbus" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -// wsURL converts an http:// URL to ws://. -func wsURL(serverURL string) string { - return "ws" + strings.TrimPrefix(serverURL, "http") -} - -// dialWS connects a gorilla WebSocket client to the given URL. -func dialWS(t *testing.T, url string) *websocket.Conn { - t.Helper() - dialer := websocket.Dialer{} - conn, _, err := dialer.Dial(url, nil) - require.NoError(t, err) - return conn -} - -// TestHub_ClientConnects verifies that a client can successfully upgrade to -// WebSocket and that the hub registers it. -func TestHub_ClientConnects(t *testing.T) { - bus := eventbus.New() - hub := NewHub(bus) - hub.Start() - - server := httptest.NewServer(http.HandlerFunc(hub.ServeWS)) - defer server.Close() - - conn := dialWS(t, wsURL(server.URL)) - defer func() { _ = conn.Close() }() - - // Give the hub a moment to register the client. - time.Sleep(50 * time.Millisecond) - - hub.mu.RLock() - clientCount := len(hub.clients) - hub.mu.RUnlock() - - assert.Equal(t, 1, clientCount, "expected 1 registered client after connection") -} - -// TestHub_BroadcastEvent verifies that an event published to the EventBus is -// delivered to a connected WebSocket client as the expected JSON payload. -func TestHub_BroadcastEvent(t *testing.T) { - bus := eventbus.New() - hub := NewHub(bus) - hub.Start() - - server := httptest.NewServer(http.HandlerFunc(hub.ServeWS)) - defer server.Close() - - conn := dialWS(t, wsURL(server.URL)) - defer func() { _ = conn.Close() }() - - // Allow the hub to register the client before publishing. - time.Sleep(50 * time.Millisecond) - - ts := time.Now().UTC().Truncate(time.Second) - evt := eventbus.Event{ - Source: "s3", - Type: "s3:ObjectCreated", - Detail: map[string]any{"key": "my-file.txt", "size": float64(1024)}, - Timestamp: ts, - } - err := bus.Publish(context.Background(), evt) - require.NoError(t, err) - - // Read message with a deadline to avoid hanging on failure. - _ = conn.SetReadDeadline(time.Now().Add(2 * time.Second)) - _, raw, err := conn.ReadMessage() - require.NoError(t, err) - - var got eventMessage - require.NoError(t, json.Unmarshal(raw, &got)) - - assert.Equal(t, "s3", got.Source) - assert.Equal(t, "s3:ObjectCreated", got.Type) - assert.Equal(t, "my-file.txt", got.Detail["key"]) - assert.Equal(t, float64(1024), got.Detail["size"]) - assert.WithinDuration(t, ts, got.Timestamp, time.Second) -} diff --git a/internal/codegen/generator.go b/internal/codegen/generator.go index 711e6b7..66f5730 100644 --- a/internal/codegen/generator.go +++ b/internal/codegen/generator.go @@ -5,10 +5,22 @@ package codegen import ( "fmt" + "go/format" "os" "path/filepath" ) +// WriteGo gofmts content and writes it to path. Templates emit close-enough Go; +// formatting here keeps a fresh `make codegen` byte-identical to what is +// committed, so a regeneration diff shows real model changes and nothing else. +func WriteGo(path, content string) error { + formatted, err := format.Source([]byte(content)) + if err != nil { + return fmt.Errorf("gofmt %s: %w", path, err) + } + return os.WriteFile(path, formatted, 0644) +} + func (g *Generator) GenerateAll(model *SmithyModel, outputDir string, scaffoldDir string) error { pkgName := model.ServiceID serviceDir := filepath.Join(outputDir, pkgName) @@ -16,49 +28,47 @@ func (g *Generator) GenerateAll(model *SmithyModel, outputDir string, scaffoldDi return fmt.Errorf("mkdir %s: %w", serviceDir, err) } - generators := map[string]func(string, *SmithyModel) (string, error){ - "types.go": g.GenerateTypes, - "base_provider.go": g.GenerateBaseProvider, - "router.go": g.GenerateRouter, - "errors.go": g.GenerateErrors, + generators := []struct { + fileName string + generate func(string, *SmithyModel) (string, error) + }{ + {"types.go", g.GenerateTypes}, + {"base_provider.go", g.GenerateBaseProvider}, + {"router.go", g.GenerateRouter}, + {"errors.go", g.GenerateErrors}, } - for fileName, genFunc := range generators { - content, err := genFunc(pkgName, model) + for _, gen := range generators { + content, err := gen.generate(pkgName, model) if err != nil { - return fmt.Errorf("generate %s: %w", fileName, err) + return fmt.Errorf("generate %s: %w", gen.fileName, err) } - outPath := filepath.Join(serviceDir, fileName) - if err := os.WriteFile(outPath, []byte(content), 0644); err != nil { + outPath := filepath.Join(serviceDir, gen.fileName) + if err := WriteGo(outPath, content); err != nil { return fmt.Errorf("write %s: %w", outPath, err) } } - // Scaffold files — only written if they don't already exist. - if scaffoldDir != "" { - scaffoldServiceDir := filepath.Join(scaffoldDir, pkgName) - if err := os.MkdirAll(scaffoldServiceDir, 0755); err != nil { - return fmt.Errorf("mkdir scaffold %s: %w", scaffoldServiceDir, err) - } - - scaffolds := map[string]func(string, *SmithyModel) (string, error){ - "provider.go": g.GenerateScaffold, - } - - for fileName, genFunc := range scaffolds { - outPath := filepath.Join(scaffoldServiceDir, fileName) - if _, err := os.Stat(outPath); err == nil { - continue // file exists, skip - } - content, err := genFunc(pkgName, model) - if err != nil { - return fmt.Errorf("generate scaffold %s: %w", fileName, err) - } - if err := os.WriteFile(outPath, []byte(content), 0644); err != nil { - return fmt.Errorf("write scaffold %s: %w", outPath, err) - } - } + if scaffoldDir == "" { + return nil } + // Scaffold provider — only written if it doesn't already exist, so a + // hand-written provider is never clobbered by a regeneration. + scaffoldServiceDir := filepath.Join(scaffoldDir, pkgName) + if err := os.MkdirAll(scaffoldServiceDir, 0755); err != nil { + return fmt.Errorf("mkdir scaffold %s: %w", scaffoldServiceDir, err) + } + outPath := filepath.Join(scaffoldServiceDir, "provider.go") + if _, err := os.Stat(outPath); err == nil { + return nil + } + content, err := g.GenerateScaffold(pkgName, model) + if err != nil { + return fmt.Errorf("generate scaffold %s: %w", outPath, err) + } + if err := WriteGo(outPath, content); err != nil { + return fmt.Errorf("write scaffold %s: %w", outPath, err) + } return nil } diff --git a/internal/codegen/templates/scaffold_provider.go.tmpl b/internal/codegen/templates/scaffold_provider.go.tmpl index ea17310..3caceaf 100644 --- a/internal/codegen/templates/scaffold_provider.go.tmpl +++ b/internal/codegen/templates/scaffold_provider.go.tmpl @@ -35,10 +35,6 @@ func (p *Provider) ListResources(ctx context.Context) ([]plugin.Resource, error) return []plugin.Resource{}, nil } -func (p *Provider) GetMetrics(ctx context.Context) (*plugin.ServiceMetrics, error) { - return &plugin.ServiceMetrics{}, nil -} - func init() { plugin.DefaultRegistry.Register("{{ .ServiceID }}", func() plugin.ServicePlugin { return &Provider{} diff --git a/internal/config/config.go b/internal/config/config.go index d096fd2..8a58fa8 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -4,7 +4,7 @@ package config import ( _ "embed" - "log/slog" + "fmt" "os" "path/filepath" "strconv" @@ -14,16 +14,19 @@ import ( ) // defaultConfigYAML is the built-in configuration used when no YAML file is -// provided. It enables all services with standard data directories so DevCloud -// runs out of the box with zero setup. +// provided. It carries no services block, which means "every registered +// service, enabled, under ./data/" — see Config.Service. // //go:embed default.yaml var defaultConfigYAML []byte +// defaultDataDir is the base directory services store data under when neither +// the YAML nor DEVCLOUD_DATA_DIR names one. +const defaultDataDir = "./data" + type Config struct { Server ServerConfig `yaml:"server"` Services map[string]ServiceConfig `yaml:"services"` - Auth AuthConfig `yaml:"auth"` Admin *AdminConfig `yaml:"admin"` Logging LoggingConfig `yaml:"logging"` @@ -32,6 +35,18 @@ type Config struct { // Both Admin and Dashboard are pointers so parse can tell an explicit // block from an absent one and apply the correct precedence. Dashboard *AdminConfig `yaml:"dashboard"` + + // Auth is the removed pre-1.0 auth block. SigV4 enforcement was never + // implemented, so the key is gone — but yaml.Unmarshal ignores unknown keys + // silently, and an operator who wrote auth.enabled: true to require + // signature validation must not be left believing it took effect. Parsed + // only to warn (see parse); remove with the dashboard key. + Auth *AuthConfig `yaml:"auth"` + + // allowed is the DEVCLOUD_SERVICES filter; nil means no filtering. + allowed map[string]bool + // baseDir is the DEVCLOUD_DATA_DIR override; "" means honour data_dir. + baseDir string } type ServerConfig struct { @@ -43,11 +58,13 @@ type ServiceConfig struct { DataDir string `yaml:"data_dir"` } -type AuthConfig struct { +type AdminConfig struct { Enabled bool `yaml:"enabled"` } -type AdminConfig struct { +// AuthConfig is the shape of the removed auth block, kept only so parse can +// warn about it instead of ignoring it. See Config.Auth. +type AuthConfig struct { Enabled bool `yaml:"enabled"` } @@ -56,12 +73,41 @@ type LoggingConfig struct { Format string `yaml:"format"` } -// Load reads and parses a YAML config file from the given path. -// Returns an error if the file cannot be read or parsed. -func Load(path string) (*Config, error) { +// Service returns the effective configuration for serviceID. +// +// A YAML services block is authoritative: only the services it lists can run. +// When there is no block (the embedded default), every service is enabled with +// data_dir /. DEVCLOUD_SERVICES and DEVCLOUD_DATA_DIR are applied +// here so both paths agree. +func (c *Config) Service(serviceID string) ServiceConfig { + if c.allowed != nil && !c.allowed[serviceID] { + return ServiceConfig{} + } + svc, listed := c.Services[serviceID] + if !listed { + if len(c.Services) > 0 { + return ServiceConfig{} + } + svc = ServiceConfig{Enabled: true} + } + if c.baseDir == "" && svc.DataDir != "" { + return svc + } + base := c.baseDir + if base == "" { + base = defaultDataDir + } + svc.DataDir = filepath.Join(base, serviceID) + return svc +} + +// Load reads and parses a YAML config file from the given path. It returns any +// configuration warnings alongside the config so the caller can log them +// through the operator-configured handler (see Logging). +func Load(path string) (*Config, []string, error) { data, err := os.ReadFile(path) if err != nil { - return nil, err + return nil, nil, err } return parse(data) } @@ -70,36 +116,52 @@ func Load(path string) (*Config, error) { // missing, it returns the embedded default configuration instead. Any other // read or parse error is returned as-is. This is the recommended entry point // for CLI usage so the server runs with zero setup. -func LoadOrDefault(fallbackPath string) (*Config, error) { +func LoadOrDefault(fallbackPath string) (*Config, []string, error) { if fallbackPath != "" { if _, err := os.Stat(fallbackPath); err == nil { return Load(fallbackPath) } else if !os.IsNotExist(err) { - return nil, err + return nil, nil, err } - slog.Info("config file not found, using embedded defaults", "tried", fallbackPath) } return parse(defaultConfigYAML) } -func parse(data []byte) (*Config, error) { +func parse(data []byte) (*Config, []string, error) { cfg := &Config{} if err := yaml.Unmarshal(data, cfg); err != nil { - return nil, err + return nil, nil, err } + var warnings []string + // Back-compat: 'dashboard' was renamed to 'admin'. Honour the old key for // one release so existing configs don't silently lose the admin API. An // explicit 'admin' block always wins; the deprecated key is used only when // no 'admin' block is present (so an explicit admin.enabled: false is not // overridden by a leftover dashboard.enabled: true). if cfg.Dashboard != nil { - slog.Warn("config: 'dashboard' key is deprecated and will be removed; rename it to 'admin'") + warnings = append(warnings, + "config: 'dashboard' key is deprecated and will be removed; rename it to 'admin'") if cfg.Admin == nil { cfg.Admin = cfg.Dashboard } cfg.Dashboard = nil } + + // The auth block is gone, and an ignored auth.enabled: true is a security + // surprise: the operator thinks signatures are checked when nothing is. + if cfg.Auth != nil { + if cfg.Auth.Enabled { + warnings = append(warnings, + "config: 'auth.enabled: true' has no effect — SigV4 enforcement is not implemented and the 'auth' key was removed; any credentials are accepted") + } else { + warnings = append(warnings, + "config: the 'auth' key was removed and is ignored; delete it") + } + cfg.Auth = nil + } + if cfg.Admin == nil { cfg.Admin = &AdminConfig{} } @@ -108,8 +170,7 @@ func parse(data []byte) (*Config, error) { cfg.Server.Port = 4747 } - applyEnvOverrides(cfg) - return cfg, nil + return cfg, append(warnings, applyEnvOverrides(cfg)...), nil } var serviceTiers = map[string][]string{ @@ -130,10 +191,14 @@ var serviceTiers = map[string][]string{ }, } -func expandTiers(value string) map[string]bool { +// expandTiers resolves a DEVCLOUD_SERVICES value into the set of allowed +// service names, returning nil for "all" (no filtering). Tokens that look like +// a mistyped tier shortcut are reported as warnings. +func expandTiers(value string) (map[string]bool, []string) { if value == "all" { - return nil + return nil, nil } + var warnings []string allowed := make(map[string]bool) for _, token := range strings.Split(value, ",") { token = strings.TrimSpace(token) @@ -150,41 +215,25 @@ func expandTiers(value string) map[string]bool { // name. If it looks like it was *meant* to be a tier ("tierXXX"), // warn the operator so a typo surfaces in logs. if token != "all" && strings.HasPrefix(token, "tier") { - slog.Warn("DEVCLOUD_SERVICES: unknown tier shortcut; treating as literal service name", - "token", token) + warnings = append(warnings, fmt.Sprintf( + "DEVCLOUD_SERVICES: unknown tier shortcut %q; treating as a literal service name", token)) } allowed[token] = true } - return allowed + return allowed, warnings } -func applyEnvOverrides(cfg *Config) { +func applyEnvOverrides(cfg *Config) []string { if p := os.Getenv("DEVCLOUD_PORT"); p != "" { if v, err := strconv.Atoi(p); err == nil { cfg.Server.Port = v } } + var warnings []string if envServices := os.Getenv("DEVCLOUD_SERVICES"); envServices != "" { - allowed := expandTiers(envServices) - if allowed == nil { - return - } - for name, svc := range cfg.Services { - if !allowed[name] { - svc.Enabled = false - cfg.Services[name] = svc - } - } - } - - // DEVCLOUD_DATA_DIR overrides the base data directory for all services. - // Each service's data_dir is rewritten to / using - // the host's path separator. - if baseDir := os.Getenv("DEVCLOUD_DATA_DIR"); baseDir != "" { - for name, svc := range cfg.Services { - svc.DataDir = filepath.Join(baseDir, name) - cfg.Services[name] = svc - } + cfg.allowed, warnings = expandTiers(envServices) } + cfg.baseDir = os.Getenv("DEVCLOUD_DATA_DIR") + return warnings } diff --git a/internal/config/config_test.go b/internal/config/config_test.go index 880f742..93c2b0d 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -12,12 +12,13 @@ import ( ) func TestExpandTiers_All(t *testing.T) { - result := expandTiers("all") + result, warnings := expandTiers("all") assert.Nil(t, result, "all should return nil (no filtering)") + assert.Empty(t, warnings) } func TestExpandTiers_Tier1(t *testing.T) { - result := expandTiers("tier1") + result, _ := expandTiers("tier1") assert.Contains(t, result, "s3") assert.Contains(t, result, "sns") assert.Contains(t, result, "kms") @@ -26,13 +27,39 @@ func TestExpandTiers_Tier1(t *testing.T) { } func TestExpandTiers_Mixed(t *testing.T) { - result := expandTiers("tier1,cognito,rds") + result, _ := expandTiers("tier1,cognito,rds") assert.Contains(t, result, "s3") assert.Contains(t, result, "cognito") assert.Contains(t, result, "rds") } -func TestApplyEnvOverrides_TierBased(t *testing.T) { +// TestExpandTiers_UnknownToken_TreatedAsService documents the current +// behavior: unknown tokens are passed through as service names, allowing +// users to mix tier shortcuts with individual service names. A token that +// looks like a mistyped tier is reported as a warning. +func TestExpandTiers_UnknownToken_TreatedAsService(t *testing.T) { + result, warnings := expandTiers("tier1,kinesis,some-custom-service,tier9") + assert.Contains(t, result, "s3", "tier1 expanded") + assert.Contains(t, result, "kinesis", "explicit service included") + assert.Contains(t, result, "some-custom-service", + "unknown tokens pass through as service names (current contract)") + require.Len(t, warnings, 1, "only the tier-looking typo warns") + assert.Contains(t, warnings[0], "tier9") +} + +// isolateEnv clears the env overrides parse() reads, so a developer or CI runner +// that exports DEVCLOUD_DATA_DIR / DEVCLOUD_SERVICES / DEVCLOUD_PORT does not +// fail every assertion about ports, data dirs, and enabled services. +// t.Setenv restores the previous value when the test ends. +func isolateEnv(t *testing.T) { + t.Helper() + for _, k := range []string{"DEVCLOUD_DATA_DIR", "DEVCLOUD_SERVICES", "DEVCLOUD_PORT"} { + t.Setenv(k, "") + } +} + +func TestService_EnvServiceFilter(t *testing.T) { + isolateEnv(t) cfg := &Config{ Services: map[string]ServiceConfig{ "s3": {Enabled: true}, @@ -41,11 +68,53 @@ func TestApplyEnvOverrides_TierBased(t *testing.T) { } t.Setenv("DEVCLOUD_SERVICES", "s3") applyEnvOverrides(cfg) - assert.True(t, cfg.Services["s3"].Enabled) - assert.False(t, cfg.Services["sqs"].Enabled) + assert.True(t, cfg.Service("s3").Enabled) + assert.False(t, cfg.Service("sqs").Enabled) +} + +// TestService_NoServicesBlock_EnablesEverything covers the zero-config +// contract: with no services block every service is enabled and lands under +// ./data/. +func TestService_NoServicesBlock_EnablesEverything(t *testing.T) { + isolateEnv(t) + cfg, _, err := parse([]byte("server:\n port: 4747\n")) + require.NoError(t, err) + + svc := cfg.Service("s3") + assert.True(t, svc.Enabled) + assert.Equal(t, filepath.Join("./data", "s3"), svc.DataDir) + assert.True(t, cfg.Service("some-new-service").Enabled, + "a service absent from the config still runs when there is no services block") +} + +// TestService_ExplicitBlockIsAuthoritative verifies that listing any service +// restricts startup to that list — otherwise a minimal config would silently +// start all 100+ services. +func TestService_ExplicitBlockIsAuthoritative(t *testing.T) { + isolateEnv(t) + cfg, _, err := parse([]byte("services:\n s3:\n enabled: true\n")) + require.NoError(t, err) + assert.True(t, cfg.Service("s3").Enabled) + assert.False(t, cfg.Service("sqs").Enabled, "unlisted service must not start") +} + +func TestService_DataDirOverride(t *testing.T) { + isolateEnv(t) + yaml := []byte("services:\n s3:\n enabled: true\n data_dir: ./custom/s3\n") + + cfg, _, err := parse(yaml) + require.NoError(t, err) + assert.Equal(t, "./custom/s3", cfg.Service("s3").DataDir) + + t.Setenv("DEVCLOUD_DATA_DIR", "/tmp/dc") + cfg, _, err = parse(yaml) + require.NoError(t, err) + assert.Equal(t, filepath.Join("/tmp/dc", "s3"), cfg.Service("s3").DataDir, + "DEVCLOUD_DATA_DIR overrides data_dir") } func TestLoadConfig_DefaultFile(t *testing.T) { + isolateEnv(t) dir := t.TempDir() cfgPath := filepath.Join(dir, "devcloud.yaml") err := os.WriteFile(cfgPath, []byte(` @@ -55,8 +124,6 @@ services: s3: enabled: true data_dir: ./data/s3 -auth: - enabled: false admin: enabled: false logging: @@ -65,51 +132,33 @@ logging: `), 0644) require.NoError(t, err) - cfg, err := Load(cfgPath) + cfg, warnings, err := Load(cfgPath) require.NoError(t, err) + assert.Empty(t, warnings) assert.Equal(t, 4747, cfg.Server.Port) - assert.True(t, cfg.Services["s3"].Enabled) - assert.Equal(t, "./data/s3", cfg.Services["s3"].DataDir) - assert.False(t, cfg.Auth.Enabled) + assert.True(t, cfg.Service("s3").Enabled) + assert.Equal(t, "./data/s3", cfg.Service("s3").DataDir) + assert.False(t, cfg.Admin.Enabled) assert.Equal(t, "info", cfg.Logging.Level) } -func TestLoadConfig_EnvOverride(t *testing.T) { - dir := t.TempDir() - cfgPath := filepath.Join(dir, "devcloud.yaml") - err := os.WriteFile(cfgPath, []byte(` -server: - port: 4747 -services: - s3: - enabled: true - sqs: - enabled: true -`), 0644) - require.NoError(t, err) - - t.Setenv("DEVCLOUD_SERVICES", "s3") - cfg, err := Load(cfgPath) - require.NoError(t, err) - assert.True(t, cfg.Services["s3"].Enabled) - assert.False(t, cfg.Services["sqs"].Enabled) -} - // TestLoadOrDefault_FileMissing_UsesEmbedded verifies zero-config startup: // when the fallback path doesn't exist, LoadOrDefault returns the embedded -// default configuration (which has all services enabled and port 4747). +// default configuration (port 4747, every service enabled). func TestLoadOrDefault_FileMissing_UsesEmbedded(t *testing.T) { - cfg, err := LoadOrDefault(filepath.Join(t.TempDir(), "nonexistent.yaml")) + isolateEnv(t) + cfg, _, err := LoadOrDefault(filepath.Join(t.TempDir(), "nonexistent.yaml")) require.NoError(t, err) require.NotNil(t, cfg) assert.Equal(t, 4747, cfg.Server.Port, "embedded default port should be 4747") - assert.NotEmpty(t, cfg.Services, "embedded default should have services") - assert.True(t, cfg.Services["s3"].Enabled, "s3 should be enabled in embedded default") + assert.Empty(t, cfg.Services, "embedded default carries no services block") + assert.True(t, cfg.Service("s3").Enabled, "s3 should be enabled in embedded default") } // TestLoadOrDefault_FileExists_UsesFile verifies that when the fallback path // exists, its contents are loaded (not the embedded default). func TestLoadOrDefault_FileExists_UsesFile(t *testing.T) { + isolateEnv(t) dir := t.TempDir() cfgPath := filepath.Join(dir, "devcloud.yaml") require.NoError(t, os.WriteFile(cfgPath, []byte(` @@ -121,69 +170,83 @@ services: data_dir: ./custom/s3 `), 0644)) - cfg, err := LoadOrDefault(cfgPath) + cfg, _, err := LoadOrDefault(cfgPath) require.NoError(t, err) assert.Equal(t, 5858, cfg.Server.Port, "should use file's port, not embedded default") - assert.Equal(t, "./custom/s3", cfg.Services["s3"].DataDir) + assert.Equal(t, "./custom/s3", cfg.Service("s3").DataDir) } // TestLoadOrDefault_EmptyPath_UsesEmbedded verifies that an empty fallback // path skips the file check entirely and uses embedded defaults. func TestLoadOrDefault_EmptyPath_UsesEmbedded(t *testing.T) { - cfg, err := LoadOrDefault("") + isolateEnv(t) + cfg, _, err := LoadOrDefault("") require.NoError(t, err) assert.Equal(t, 4747, cfg.Server.Port) - assert.NotEmpty(t, cfg.Services) + assert.True(t, cfg.Service("dynamodb").Enabled) } // TestLoad_ExplicitPathMissing_ReturnsError verifies that Load (strict) // surfaces an error when the explicit path does not exist — unlike // LoadOrDefault, which falls back to embedded defaults. func TestLoad_ExplicitPathMissing_ReturnsError(t *testing.T) { - _, err := Load(filepath.Join(t.TempDir(), "nope.yaml")) + _, _, err := Load(filepath.Join(t.TempDir(), "nope.yaml")) require.Error(t, err, "Load should error on missing file; use LoadOrDefault for graceful fallback") } -// TestParse_EmptyData_FillsDefaults verifies that parsing an empty YAML -// payload yields a Config with at least the default server port populated, -// so downstream code sees a usable config rather than a zero-value one. -func TestParse_EmptyData_FillsDefaults(t *testing.T) { - cfg, err := parse([]byte("")) - require.NoError(t, err) - assert.Equal(t, 4747, cfg.Server.Port, "empty data should still yield default port 4747") -} - // TestParse_DeprecatedDashboardKey verifies the legacy 'dashboard' config key // still enables the admin API after the rename (with a deprecation warning), // so existing deployments don't silently lose it. func TestParse_DeprecatedDashboardKey(t *testing.T) { - cfg, err := parse([]byte("dashboard:\n enabled: true\n")) + cfg, warnings, err := parse([]byte("dashboard:\n enabled: true\n")) require.NoError(t, err) assert.True(t, cfg.Admin.Enabled, "deprecated dashboard.enabled=true should enable admin") assert.Nil(t, cfg.Dashboard, "deprecated key should be cleared after folding into admin") + require.Len(t, warnings, 1) + assert.Contains(t, warnings[0], "deprecated") +} + +// TestParse_RemovedAuthKeyWarns verifies the removed 'auth' key is reported +// rather than silently ignored: yaml.Unmarshal drops unknown keys, so an +// operator who set auth.enabled: true to require SigV4 would otherwise get no +// hint that nothing validates signatures. +func TestParse_RemovedAuthKeyWarns(t *testing.T) { + _, warnings, err := parse([]byte("auth:\n enabled: true\n")) + require.NoError(t, err) + require.Len(t, warnings, 1) + assert.Contains(t, warnings[0], "auth.enabled") + assert.Contains(t, warnings[0], "not implemented") + + // An explicit auth.enabled: false is still stale config worth flagging, but + // it must not claim credentials are being accepted contrary to intent. + _, warnings, err = parse([]byte("auth:\n enabled: false\n")) + require.NoError(t, err) + require.Len(t, warnings, 1) + assert.Contains(t, warnings[0], "removed") } // TestParse_AdminKeyWinsOverDeprecated verifies an explicit 'admin' block takes // precedence over the deprecated 'dashboard' key when both are present. func TestParse_AdminKeyWinsOverDeprecated(t *testing.T) { - cfg, err := parse([]byte("admin:\n enabled: true\ndashboard:\n enabled: false\n")) + cfg, _, err := parse([]byte("admin:\n enabled: true\ndashboard:\n enabled: false\n")) require.NoError(t, err) assert.True(t, cfg.Admin.Enabled, "explicit admin.enabled=true should win") // The reverse precedence must also hold: an explicit admin.enabled=false // must not be re-enabled by a leftover deprecated dashboard.enabled=true. - cfg, err = parse([]byte("admin:\n enabled: false\ndashboard:\n enabled: true\n")) + cfg, _, err = parse([]byte("admin:\n enabled: false\ndashboard:\n enabled: true\n")) require.NoError(t, err) assert.False(t, cfg.Admin.Enabled, "explicit admin.enabled=false should win over deprecated dashboard.enabled=true") } -// TestExpandTiers_UnknownToken_TreatedAsService documents the current -// behavior: unknown tokens are passed through as service names, allowing -// users to mix tier shortcuts with individual service names. -func TestExpandTiers_UnknownToken_TreatedAsService(t *testing.T) { - result := expandTiers("tier1,kinesis,some-custom-service") - assert.Contains(t, result, "s3", "tier1 expanded") - assert.Contains(t, result, "kinesis", "explicit service included") - assert.Contains(t, result, "some-custom-service", - "unknown tokens pass through as service names (current contract)") +// TestParse_EmptyData_FillsDefaults verifies that parsing an empty YAML +// payload yields a Config with at least the default server port populated, +// so downstream code sees a usable config rather than a zero-value one. +func TestParse_EmptyData_FillsDefaults(t *testing.T) { + isolateEnv(t) + cfg, _, err := parse([]byte("")) + require.NoError(t, err) + assert.Equal(t, 4747, cfg.Server.Port, "empty data should still yield default port 4747") + require.NotNil(t, cfg.Admin) + assert.False(t, cfg.Admin.Enabled) } diff --git a/internal/config/default.yaml b/internal/config/default.yaml index 2caf3c7..15fc1b1 100644 --- a/internal/config/default.yaml +++ b/internal/config/default.yaml @@ -1,322 +1,13 @@ +# Zero-config defaults. +# +# There is deliberately no `services:` block: every registered service starts +# with data_dir ./data/. Add a services block to run a subset — it is +# authoritative, so only the services it lists start. Use DEVCLOUD_SERVICES for +# a quick subset without a file, and DEVCLOUD_DATA_DIR to relocate the data +# directories. server: port: 4747 -services: - s3: - enabled: true - data_dir: ./data/s3 - sqs: - enabled: true - dynamodb: - enabled: true - data_dir: ./data/dynamodb - iam: - enabled: true - data_dir: ./data/iam - sts: - enabled: true - data_dir: ./data/sts - lambda: - enabled: true - data_dir: ./data/lambda - sns: - enabled: true - data_dir: ./data/sns - kms: - enabled: true - data_dir: ./data/kms - secretsmanager: - enabled: true - data_dir: ./data/secretsmanager - ssm: - enabled: true - data_dir: ./data/ssm - cloudwatchlogs: - enabled: true - data_dir: ./data/cloudwatchlogs - cloudwatch: - enabled: true - data_dir: ./data/cloudwatch - eventbridge: - enabled: true - data_dir: ./data/eventbridge - ec2: - enabled: true - data_dir: ./data/ec2 - ecs: - enabled: true - data_dir: ./data/ecs - ecr: - enabled: true - data_dir: ./data/ecr - route53: - enabled: true - data_dir: ./data/route53 - acm: - enabled: true - data_dir: ./data/acm - rds: - enabled: true - data_dir: ./data/rds - cloudformation: - enabled: true - data_dir: ./data/cloudformation - elasticache: - enabled: true - data_dir: ./data/elasticache - elasticloadbalancingv2: - enabled: true - data_dir: ./data/elasticloadbalancingv2 - redshift: - enabled: true - data_dir: ./data/redshift - ses: - enabled: true - data_dir: ./data/ses - autoscaling: - enabled: true - data_dir: ./data/autoscaling - docdb: - enabled: true - data_dir: ./data/docdb - neptune: - enabled: true - data_dir: ./data/neptune - elasticbeanstalk: - enabled: true - data_dir: ./data/elasticbeanstalk - cloudsearch: - enabled: true - data_dir: ./data/cloudsearch - apigatewayv2: - enabled: true - data_dir: ./data/apigatewayv2 - opensearch: - enabled: true - data_dir: ./data/opensearch - waf: - enabled: true - data_dir: ./data/waf - backup: - enabled: true - data_dir: ./data/backup - glue: - enabled: true - data_dir: ./data/glue - sagemaker: - enabled: true - data_dir: ./data/sagemaker - iot: - enabled: true - data_dir: ./data/iot - route53resolver: - enabled: true - data_dir: ./data/route53resolver - sesv2: - enabled: true - data_dir: ./data/sesv2 - pinpoint: - enabled: true - data_dir: ./data/pinpoint - acmpca: - enabled: true - data_dir: ./data/acmpca - amplify: - enabled: true - data_dir: ./data/amplify - appconfig: - enabled: true - data_dir: ./data/appconfig - applicationautoscaling: - enabled: true - data_dir: ./data/applicationautoscaling - appsync: - enabled: true - data_dir: ./data/appsync - athena: - enabled: true - data_dir: ./data/athena - batch: - enabled: true - data_dir: ./data/batch - bedrock: - enabled: true - data_dir: ./data/bedrock - cloudfront: - enabled: true - data_dir: ./data/cloudfront - cloudtrail: - enabled: true - data_dir: ./data/cloudtrail - codeartifact: - enabled: true - data_dir: ./data/codeartifact - codebuild: - enabled: true - data_dir: ./data/codebuild - codecommit: - enabled: true - data_dir: ./data/codecommit - codedeploy: - enabled: true - data_dir: ./data/codedeploy - codepipeline: - enabled: true - data_dir: ./data/codepipeline - cognitoidentity: - enabled: true - data_dir: ./data/cognitoidentity - cognitoidentityprovider: - enabled: true - data_dir: ./data/cognitoidentityprovider - configservice: - enabled: true - data_dir: ./data/configservice - costexplorer: - enabled: true - data_dir: ./data/costexplorer - dynamodbstreams: - enabled: true - data_dir: ./data/dynamodbstreams - ebs: - enabled: true - data_dir: ./data/ebs - efs: - enabled: true - data_dir: ./data/efs - eks: - enabled: true - data_dir: ./data/eks - elasticsearchservice: - enabled: true - data_dir: ./data/elasticsearchservice - emr: - enabled: true - data_dir: ./data/emr - firehose: - enabled: true - data_dir: ./data/firehose - fis: - enabled: true - data_dir: ./data/fis - glacier: - enabled: true - data_dir: ./data/glacier - iotdataplane: - enabled: true - data_dir: ./data/iotdataplane - iotwireless: - enabled: true - data_dir: ./data/iotwireless - kafka: - enabled: true - data_dir: ./data/kafka - kinesis: - enabled: true - data_dir: ./data/kinesis - kinesisanalyticsv2: - enabled: true - data_dir: ./data/kinesisanalyticsv2 - lakeformation: - enabled: true - data_dir: ./data/lakeformation - managedblockchain: - enabled: true - data_dir: ./data/managedblockchain - memorydb: - enabled: true - data_dir: ./data/memorydb - mq: - enabled: true - data_dir: ./data/mq - mwaa: - enabled: true - data_dir: ./data/mwaa - organizations: - enabled: true - data_dir: ./data/organizations - ram: - enabled: true - data_dir: ./data/ram - resourcegroups: - enabled: true - data_dir: ./data/resourcegroups - resourcegroupstaggingapi: - enabled: true - data_dir: ./data/resourcegroupstaggingapi - servicediscovery: - enabled: true - data_dir: ./data/servicediscovery - sfn: - enabled: true - data_dir: ./data/sfn - shield: - enabled: true - data_dir: ./data/shield - ssoadmin: - enabled: true - data_dir: ./data/ssoadmin - support: - enabled: true - data_dir: ./data/support - swf: - enabled: true - data_dir: ./data/swf - textract: - enabled: true - data_dir: ./data/textract - timestreamwrite: - enabled: true - data_dir: ./data/timestreamwrite - transcribe: - enabled: true - data_dir: ./data/transcribe - transfer: - enabled: true - data_dir: ./data/transfer - wafv2: - enabled: true - data_dir: ./data/wafv2 - xray: - enabled: true - data_dir: ./data/xray - account: - enabled: true - data_dir: ./data/account - cloudcontrol: - enabled: true - data_dir: ./data/cloudcontrol - pipes: - enabled: true - data_dir: ./data/pipes - mediaconvert: - enabled: true - data_dir: ./data/mediaconvert - s3tables: - enabled: true - data_dir: ./data/s3tables - scheduler: - enabled: true - data_dir: ./data/scheduler - identitystore: - enabled: true - data_dir: ./data/identitystore - serverlessrepo: - enabled: true - data_dir: ./data/serverlessrepo - codeconnections: - enabled: true - data_dir: ./data/codeconnections - dms: - enabled: true - data_dir: ./data/dms - verifiedpermissions: - enabled: true - data_dir: ./data/verifiedpermissions - -auth: - enabled: false - admin: enabled: false diff --git a/internal/eventbus/eventbus.go b/internal/eventbus/eventbus.go deleted file mode 100644 index 9baa572..0000000 --- a/internal/eventbus/eventbus.go +++ /dev/null @@ -1,81 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -package eventbus - -import ( - "context" - "log/slog" - "sync" - "time" -) - -type Event struct { - Source string - Type string - Detail map[string]any - Timestamp time.Time -} - -type EventHandler func(Event) - -type EventBus interface { - Publish(ctx context.Context, event Event) error - Subscribe(eventType string, handler EventHandler) (unsubscribe func()) -} - -type subscriber struct { - eventType string - handler EventHandler -} - -type InMemoryEventBus struct { - mu sync.RWMutex - subscribers map[uint64]*subscriber - nextID uint64 -} - -func New() *InMemoryEventBus { - return &InMemoryEventBus{ - subscribers: make(map[uint64]*subscriber), - } -} - -func (b *InMemoryEventBus) Publish(_ context.Context, event Event) error { - if event.Timestamp.IsZero() { - event.Timestamp = time.Now() - } - b.mu.RLock() - subs := make([]*subscriber, 0, len(b.subscribers)) - for _, s := range b.subscribers { - subs = append(subs, s) - } - b.mu.RUnlock() - - for _, s := range subs { - if s.eventType == "*" || s.eventType == event.Type { - go func(h EventHandler, e Event) { - defer func() { - if r := recover(); r != nil { - slog.Error("event handler panic", "event", e.Type, "panic", r) - } - }() - h(e) - }(s.handler, event) - } - } - return nil -} - -func (b *InMemoryEventBus) Subscribe(eventType string, handler EventHandler) func() { - b.mu.Lock() - id := b.nextID - b.nextID++ - b.subscribers[id] = &subscriber{eventType: eventType, handler: handler} - b.mu.Unlock() - - return func() { - b.mu.Lock() - delete(b.subscribers, id) - b.mu.Unlock() - } -} diff --git a/internal/eventbus/eventbus_test.go b/internal/eventbus/eventbus_test.go deleted file mode 100644 index 62a4b6e..0000000 --- a/internal/eventbus/eventbus_test.go +++ /dev/null @@ -1,84 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -package eventbus - -import ( - "context" - "sync" - "testing" - "time" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestPublishAndSubscribe(t *testing.T) { - bus := New() - var received []Event - var mu sync.Mutex - - unsub := bus.Subscribe("s3:ObjectCreated", func(e Event) { - mu.Lock() - received = append(received, e) - mu.Unlock() - }) - defer unsub() - - require.NoError(t, bus.Publish(context.Background(), Event{ - Source: "s3", - Type: "s3:ObjectCreated", - Detail: map[string]any{"bucket": "test", "key": "hello.txt"}, - })) - - time.Sleep(50 * time.Millisecond) - mu.Lock() - defer mu.Unlock() - require.Len(t, received, 1) - assert.Equal(t, "s3", received[0].Source) - assert.Equal(t, "test", received[0].Detail["bucket"]) -} - -func TestSubscribeWildcard(t *testing.T) { - bus := New() - var received []Event - var mu sync.Mutex - - unsub := bus.Subscribe("*", func(e Event) { - mu.Lock() - received = append(received, e) - mu.Unlock() - }) - defer unsub() - - require.NoError(t, bus.Publish(context.Background(), Event{Source: "s3", Type: "s3:ObjectCreated"})) - require.NoError(t, bus.Publish(context.Background(), Event{Source: "sqs", Type: "sqs:MessageSent"})) - - time.Sleep(50 * time.Millisecond) - mu.Lock() - defer mu.Unlock() - assert.Len(t, received, 2) -} - -func TestUnsubscribe(t *testing.T) { - bus := New() - callCount := 0 - var mu sync.Mutex - - unsub := bus.Subscribe("test", func(e Event) { - mu.Lock() - callCount++ - mu.Unlock() - }) - - require.NoError(t, bus.Publish(context.Background(), Event{Type: "test"})) - time.Sleep(50 * time.Millisecond) - - unsub() - - require.NoError(t, bus.Publish(context.Background(), Event{Type: "test"})) - time.Sleep(50 * time.Millisecond) - - mu.Lock() - defer mu.Unlock() - assert.Equal(t, 1, callCount) -} diff --git a/internal/gateway/auth.go b/internal/gateway/auth.go deleted file mode 100644 index 31875cb..0000000 --- a/internal/gateway/auth.go +++ /dev/null @@ -1,23 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -package gateway - -import ( - "net/http" - - "github.com/skyoo2003/devcloud/internal/plugin" -) - -// defaultAccountID is the account ID used for all requests until real -// account-to-credential mapping is implemented. -const defaultAccountID = plugin.DefaultAccountID - -// ExtractAccountID attempts to derive an AWS account ID from the incoming -// request (e.g. by inspecting the Authorization header). Real account mapping -// is deferred; the function always returns the default account ID for now. -func ExtractAccountID(r *http.Request) string { - // The Authorization header is available for future implementation of - // access-key → account-ID resolution. - _ = r.Header.Get("Authorization") - return defaultAccountID -} diff --git a/internal/gateway/auth_test.go b/internal/gateway/auth_test.go deleted file mode 100644 index 48c256e..0000000 --- a/internal/gateway/auth_test.go +++ /dev/null @@ -1,25 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -package gateway - -import ( - "net/http/httptest" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestExtractAccountID(t *testing.T) { - req := httptest.NewRequest("GET", "/", nil) - req.Header.Set("Authorization", - "AWS4-HMAC-SHA256 Credential=AKIAIOSFODNN7EXAMPLE/20130524/us-east-1/s3/aws4_request, "+ - "SignedHeaders=host;range;x-amz-date, Signature=abc123") - accountID := ExtractAccountID(req) - assert.Equal(t, "000000000000", accountID) -} - -func TestExtractAccountID_NoAuth(t *testing.T) { - req := httptest.NewRequest("GET", "/", nil) - accountID := ExtractAccountID(req) - assert.Equal(t, "000000000000", accountID) -} diff --git a/internal/gateway/protocol.go b/internal/gateway/protocol.go index 593443d..3d6a04e 100644 --- a/internal/gateway/protocol.go +++ b/internal/gateway/protocol.go @@ -34,9 +34,8 @@ func DetectProtocol(r *http.Request) (protocol string, serviceID string) { // Restore the body so downstream handlers can read it again. r.Body = io.NopCloser(bytes.NewReader(bodyBytes)) - if strings.Contains(string(bodyBytes), "Action=") { - service := serviceFromQueryRequest(r, string(bodyBytes)) - return "query", service + if bytes.Contains(bodyBytes, []byte("Action=")) { + return "query", serviceFromQueryRequest(r, string(bodyBytes)) } } } @@ -134,24 +133,12 @@ func serviceFromSigV4(r *http.Request) string { // normalizeServiceID maps SigV4 signing names, X-Amz-Target prefixes, and // other AWS identifiers to DevCloud internal service IDs. func normalizeServiceID(svc string) string { - switch strings.ToLower(svc) { - // Core services - case "s3": - return "s3" - case "sqs", "amazonsqs": + svc = strings.ToLower(svc) + switch svc { + case "amazonsqs": return "sqs" - case "dynamodb", "dynamodb_20120810": + case "dynamodb_20120810": return "dynamodb" - case "iam": - return "iam" - case "sts": - return "sts" - case "lambda": - return "lambda" - case "sns": - return "sns" - - // JSON-protocol services (matched by X-Amz-Target prefix, lowercased) case "amazonssm": return "ssm" case "trentservice": @@ -166,8 +153,6 @@ func normalizeServiceID(svc string) string { return "ecs" case "amazonec2containerregistry", "amazonec2containerregistry_v20150921": return "ecr" - case "secretsmanager": - return "secretsmanager" case "certificatemanager": return "acm" case "awswaf", "awswaf_20150824": @@ -176,10 +161,6 @@ func normalizeServiceID(svc string) string { return "wafv2" case "awsglue": return "glue" - case "sagemaker": - return "sagemaker" - case "route53resolver": - return "route53resolver" case "codepipeline_20150709": return "codepipeline" case "codebuild_20161006": @@ -192,9 +173,9 @@ func normalizeServiceID(svc string) string { return "codeartifact" case "amazonkinesis": return "kinesis" - case "kinesisanalytics", "kinesisanalytics_v2", "kinesisanalyticsv2": + case "kinesisanalytics", "kinesisanalytics_v2": return "kinesisanalyticsv2" - case "firehose", "firehose_20150804": + case "firehose_20150804": return "firehose" case "amazonathena": return "athena" @@ -206,7 +187,7 @@ func normalizeServiceID(svc string) string { return "mwaa" case "awssfn", "awsstepfunctions": return "sfn" - case "swf", "simpleWorkflowService", "simpleworkflowservice": + case "simpleworkflowservice": return "swf" case "swbexternalservice": return "ssoadmin" @@ -222,56 +203,34 @@ func normalizeServiceID(svc string) string { return "organizations" case "awsshield", "awsshield_20160616": return "shield" - case "ssoadmin", "sso": + case "sso": return "ssoadmin" case "awssupport", "awssupport_20130415": return "support" - case "awsfaultinjectionservice", "fis": + case "awsfaultinjectionservice": return "fis" - case "xray", "awsxray": + case "awsxray": return "xray" - case "timestreamwrite", "timestream_20181101", "timestream": + case "timestream_20181101", "timestream": return "timestreamwrite" - case "transcribe": - return "transcribe" - case "textract": - return "textract" - case "bedrock": - return "bedrock" - case "costexplorer", "awscostexplorer", "awsinsightsindexservice": + case "awscostexplorer", "awsinsightsindexservice": return "costexplorer" - case "batch", "awsbatch", "awsbatch_v20160810": + case "awsbatch", "awsbatch_v20160810": return "batch" - case "kafka", "msk": + case "msk": return "kafka" - case "lakeformation": - return "lakeformation" case "amazondmsv20160101": return "dms" - case "configservice", "config", "starlingdoveservice": + case "config", "starlingdoveservice": return "configservice" - case "applicationautoscaling", "application-autoscaling", "anyupfront", "anyscalefrontendservice": + case "application-autoscaling", "anyupfront", "anyscalefrontendservice": return "applicationautoscaling" - case "appconfig": - return "appconfig" - case "awsresourcegroups", "resourcegroups", "resource-groups": + case "awsresourcegroups", "resource-groups": return "resourcegroups" - case "resourcegroupstaggingapi", "resourcegroupstagging": + case "resourcegroupstagging": return "resourcegroupstaggingapi" - case "ram": - return "ram" - case "cloudcontrolapi", "cloudapiservice", "cloudcontrol": + case "cloudcontrolapi", "cloudapiservice": return "cloudcontrol" - case "pipes": - return "pipes" - case "account": - return "account" - - // SigV4 signing names for REST/Query services - case "ec2": - return "ec2" - case "route53": - return "route53" case "elasticloadbalancing": return "elasticloadbalancingv2" case "es": @@ -282,169 +241,70 @@ func normalizeServiceID(svc string) string { return "apigatewayv2" case "mobiletargeting": return "pinpoint" - case "backup": - return "backup" - case "iot": - return "iot" case "data.iot", "iotdata", "iot-data": return "iotdataplane" - case "iotwireless": - return "iotwireless" - case "amplify": - return "amplify" - case "appsync": - return "appsync" - case "cloudfront": - return "cloudfront" - case "acm-pca", "acmpca", "acmprivateca": + case "acm-pca", "acmprivateca": return "acmpca" - case "servicediscovery", "route53autonaming": + case "route53autonaming": return "servicediscovery" - case "eks": - return "eks" - case "efs", "elasticfilesystem": + case "elasticfilesystem": return "efs" - case "ebs": - return "ebs" - case "glacier": - return "glacier" - case "managedblockchain": - return "managedblockchain" - case "mediaconvert": - return "mediaconvert" - case "transfer", "transferservice": + case "transferservice": return "transfer" - case "codecommit": - return "codecommit" - case "codedeploy": - return "codedeploy" - case "codebuild": - return "codebuild" - case "codepipeline": - return "codepipeline" - case "codeartifact": - return "codeartifact" - case "cloudtrail", "cloudtrail_20131101": + case "cloudtrail_20131101": return "cloudtrail" - case "opensearch", "opensearchservice": + case "opensearchservice": return "opensearch" - case "s3tables": - return "s3tables" - case "identitystore", "awsidentitystore", "swbexternaluserservice": + case "awsidentitystore", "swbexternaluserservice": return "identitystore" - case "serverlessrepo", "serverlessapplicationrepository": + case "serverlessapplicationrepository": return "serverlessrepo" - case "scheduler": - return "scheduler" - - // Query-protocol services (signing name = service name) - case "rds": - return "rds" - case "cloudformation": - return "cloudformation" - case "elasticache": - return "elasticache" - case "redshift": - return "redshift" - case "ses": - return "ses" - case "autoscaling": - return "autoscaling" - case "elasticbeanstalk": - return "elasticbeanstalk" - case "cloudsearch": - return "cloudsearch" - case "ssm": - return "ssm" - case "kms": - return "kms" - case "ecs": - return "ecs" - case "ecr": - return "ecr" case "events": return "eventbridge" - case "acm": - return "acm" - case "cloudwatchlogs": - return "cloudwatchlogs" - case "cloudwatch": - return "cloudwatch" - case "eventbridge": - return "eventbridge" - case "glue": - return "glue" - case "waf": - return "waf" - default: return svc } } // serviceFromQueryRequest determines the service for a Query-protocol request -// by examining the SigV4 credential scope, Host header prefix, and the Action parameter. +// from the SigV4 credential scope, then the Host header prefix, then the Action +// parameter. SDKs, the CLI, and Terraform all sign, so the credential scope +// almost always decides it — but an unsigned client posting to a bare endpoint +// has neither a scope nor a host prefix, and defaulting those to sqs sends +// every IAM and STS call to the wrong provider. sqs remains the final default. func serviceFromQueryRequest(r *http.Request, body string) string { - // Most reliable: extract service from SigV4 Authorization header if svc := serviceFromSigV4(r); svc != "" { return svc } - host := r.Host - - // Check the host prefix. - hostPrefix := strings.ToLower(strings.SplitN(host, ".", 2)[0]) - switch hostPrefix { + switch strings.ToLower(strings.SplitN(r.Host, ".", 2)[0]) { case "iam": return "iam" case "sts": return "sts" - case "sqs": - return "sqs" } - // Fall back to Action name inspection. values, err := url.ParseQuery(body) - if err == nil { - action := values.Get("Action") - switch { - case strings.HasPrefix(action, "CreateUser"), - strings.HasPrefix(action, "DeleteUser"), - strings.HasPrefix(action, "ListUsers"), - strings.HasPrefix(action, "AttachRole"), - strings.HasPrefix(action, "CreateRole"), - strings.HasPrefix(action, "CreatePolicy"), - strings.HasPrefix(action, "ListAttachedRole"), - strings.HasPrefix(action, "CreateAccessKey"), - action == "CreateGroup", - action == "DeleteGroup": - return "iam" - case action == "GetCallerIdentity", - action == "AssumeRole", - action == "GetSessionToken": - return "sts" - case action == "SendMessage", - action == "ReceiveMessage", - action == "DeleteMessage", - action == "CreateQueue", - action == "GetQueueUrl", - action == "ListQueues", - action == "DeleteQueue", - action == "GetQueueAttributes", - action == "SetQueueAttributes", - action == "PurgeQueue", - action == "ChangeMessageVisibility", - action == "TagQueue", - action == "UntagQueue": - return "sqs" - } - - // Detect SQS by presence of QueueUrl parameter. - if values.Get("QueueUrl") != "" { - return "sqs" + if err != nil { + return "sqs" + } + action := values.Get("Action") + switch { + case action == "GetCallerIdentity", action == "GetSessionToken", + action == "GetFederationToken", strings.HasPrefix(action, "AssumeRole"): + return "sts" + case values.Get("QueueUrl") != "", values.Get("QueueName") != "", + action == "ListQueues": + return "sqs" + case action != "": + // Every SQS operation names a queue, so it is fully covered above. + // What is left that talks about IAM entities is IAM. "Polic" rather than + // "Policy" so the plural in ListPolicies/DeletePolicies still matches. + for _, entity := range []string{"User", "Role", "Polic", "Group", "AccessKey"} { + if strings.Contains(action, entity) { + return "iam" + } } } - - // Default Query service. return "sqs" } diff --git a/internal/gateway/protocol_test.go b/internal/gateway/protocol_test.go index 4e18597..fd905f4 100644 --- a/internal/gateway/protocol_test.go +++ b/internal/gateway/protocol_test.go @@ -63,6 +63,50 @@ func TestDetectProtocol_Query_IAM(t *testing.T) { assert.Equal(t, "iam", service) } +// TestDetectProtocol_Query_Unsigned_BareHost covers the client that has neither +// a SigV4 credential scope nor a service host prefix (curl, an SDK configured +// unsigned, a test harness). Without the Action fallback every one of these +// routes to sqs and gets the wrong provider's error. +func TestDetectProtocol_Query_Unsigned_BareHost(t *testing.T) { + cases := []struct{ name, body, want string }{ + {"sts_caller_identity", "Action=GetCallerIdentity", "sts"}, + {"sts_assume_role", "Action=AssumeRoleWithWebIdentity&RoleArn=x", "sts"}, + {"sts_session_token", "Action=GetSessionToken", "sts"}, + {"iam_create_user", "Action=CreateUser&UserName=test", "iam"}, + {"iam_delete_role", "Action=DeleteRole&RoleName=r", "iam"}, + {"iam_list_policies", "Action=ListPolicies", "iam"}, + {"iam_create_access_key", "Action=CreateAccessKey&UserName=u", "iam"}, + {"sqs_send", "Action=SendMessage&QueueUrl=http://x/q&MessageBody=hi", "sqs"}, + {"sqs_create", "Action=CreateQueue&QueueName=q", "sqs"}, + {"sqs_list", "Action=ListQueues", "sqs"}, + // An unrecognised Query action still defaults to sqs, as before. + {"unknown_defaults_sqs", "Action=DescribeDBInstances", "sqs"}, + } + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + req := httptest.NewRequest("POST", "/", strings.NewReader(c.body)) + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + req.Host = "localhost:4747" + proto, service := DetectProtocol(req) + assert.Equal(t, "query", proto) + assert.Equal(t, c.want, service) + }) + } +} + +// TestDetectProtocol_Query_SigV4WinsOverAction confirms the credential scope +// still decides for signed clients, so the Action fallback cannot misroute the +// SDK/CLI/Terraform traffic that makes up every real request. +func TestDetectProtocol_Query_SigV4WinsOverAction(t *testing.T) { + req := httptest.NewRequest("POST", "/", strings.NewReader("Action=CreateUser&UserName=test")) + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + req.Header.Set("Authorization", + "AWS4-HMAC-SHA256 Credential=AKIA/20130524/us-east-1/sts/aws4_request, Signature=abc") + req.Host = "localhost:4747" + _, service := DetectProtocol(req) + assert.Equal(t, "sts", service, "credential scope must outrank the Action name") +} + func TestDetectProtocol_Query_STS(t *testing.T) { req := httptest.NewRequest("POST", "/", strings.NewReader("Action=GetCallerIdentity")) req.Header.Set("Content-Type", "application/x-www-form-urlencoded") diff --git a/internal/gateway/router_test.go b/internal/gateway/router_test.go index 30ebdc1..fd28320 100644 --- a/internal/gateway/router_test.go +++ b/internal/gateway/router_test.go @@ -28,9 +28,6 @@ func (s *stubPlugin) HandleRequest(_ context.Context, _ string, _ *http.Request) return s.response, nil } func (s *stubPlugin) ListResources(_ context.Context) ([]plugin.Resource, error) { return nil, nil } -func (s *stubPlugin) GetMetrics(_ context.Context) (*plugin.ServiceMetrics, error) { - return &plugin.ServiceMetrics{}, nil -} // registerStub injects a stub plugin directly into the registry's active map // by going through Init with a pre-registered factory. diff --git a/internal/plugin/plugin.go b/internal/plugin/plugin.go index 3f1eda9..017cf05 100644 --- a/internal/plugin/plugin.go +++ b/internal/plugin/plugin.go @@ -45,12 +45,6 @@ type Resource struct { Name string `json:"name"` } -type ServiceMetrics struct { - TotalRequests int64 `json:"total_requests"` - ErrorCount int64 `json:"error_count"` - ResourceCount int `json:"resource_count"` -} - type ServicePlugin interface { ServiceID() string ServiceName() string @@ -59,5 +53,4 @@ type ServicePlugin interface { Shutdown(ctx context.Context) error HandleRequest(ctx context.Context, op string, req *http.Request) (*Response, error) ListResources(ctx context.Context) ([]Resource, error) - GetMetrics(ctx context.Context) (*ServiceMetrics, error) } diff --git a/internal/plugin/registry_test.go b/internal/plugin/registry_test.go index 25bb3d5..d80091c 100644 --- a/internal/plugin/registry_test.go +++ b/internal/plugin/registry_test.go @@ -21,8 +21,7 @@ func (m *mockPlugin) Shutdown(ctx context.Context) error { return nil } func (m *mockPlugin) HandleRequest(ctx context.Context, op string, req *http.Request) (*Response, error) { return &Response{StatusCode: 200, Body: []byte("ok")}, nil } -func (m *mockPlugin) ListResources(ctx context.Context) ([]Resource, error) { return nil, nil } -func (m *mockPlugin) GetMetrics(ctx context.Context) (*ServiceMetrics, error) { return nil, nil } +func (m *mockPlugin) ListResources(ctx context.Context) ([]Resource, error) { return nil, nil } func TestRegistryRegisterAndGet(t *testing.T) { reg := NewRegistry() diff --git a/internal/services/account/provider.go b/internal/services/account/provider.go index 72742ed..1560e69 100644 --- a/internal/services/account/provider.go +++ b/internal/services/account/provider.go @@ -13,6 +13,7 @@ import ( "strings" "github.com/skyoo2003/devcloud/internal/plugin" + "github.com/skyoo2003/devcloud/internal/shared" ) const defaultAccountID = plugin.DefaultAccountID @@ -74,7 +75,7 @@ func (p *AccountProvider) HandleRequest(_ context.Context, op string, req *http. // --- Alternate Contact --- case "getalternatecontact": - contactType := strVal(bodyMap, "AlternateContactType") + contactType := shared.StrParam(bodyMap, "AlternateContactType") if contactType == "" { contactType = q.Get("AlternateContactType") } @@ -82,7 +83,7 @@ func (p *AccountProvider) HandleRequest(_ context.Context, op string, req *http. case "putalternatecontact": return p.putAlternateContact(bodyMap) case "deletealternatecontact": - contactType := strVal(bodyMap, "AlternateContactType") + contactType := shared.StrParam(bodyMap, "AlternateContactType") if contactType == "" { contactType = q.Get("AlternateContactType") } @@ -92,7 +93,7 @@ func (p *AccountProvider) HandleRequest(_ context.Context, op string, req *http. case "listregions": return p.listRegions() case "getregionoptstatus": - regionName := strVal(bodyMap, "RegionName") + regionName := shared.StrParam(bodyMap, "RegionName") if regionName == "" { regionName = q.Get("RegionName") } @@ -123,7 +124,7 @@ func (p *AccountProvider) HandleRequest(_ context.Context, op string, req *http. case "alternatecontact": switch method { case http.MethodGet: - contactType := strVal(bodyMap, "AlternateContactType") + contactType := shared.StrParam(bodyMap, "AlternateContactType") if contactType == "" { contactType = q.Get("AlternateContactType") } @@ -131,7 +132,7 @@ func (p *AccountProvider) HandleRequest(_ context.Context, op string, req *http. case http.MethodPut: return p.putAlternateContact(bodyMap) case http.MethodDelete: - contactType := strVal(bodyMap, "AlternateContactType") + contactType := shared.StrParam(bodyMap, "AlternateContactType") if contactType == "" { contactType = q.Get("AlternateContactType") } @@ -140,7 +141,7 @@ func (p *AccountProvider) HandleRequest(_ context.Context, op string, req *http. case "regions": return p.listRegions() case "regionoptstatus": - regionName := strVal(bodyMap, "RegionName") + regionName := shared.StrParam(bodyMap, "RegionName") if regionName == "" { regionName = q.Get("RegionName") } @@ -162,7 +163,7 @@ func (p *AccountProvider) HandleRequest(_ context.Context, op string, req *http. case "DeleteContactInformation": return p.deleteContactInformation() case "GetAlternateContact": - contactType := strVal(bodyMap, "AlternateContactType") + contactType := shared.StrParam(bodyMap, "AlternateContactType") if contactType == "" { contactType = q.Get("AlternateContactType") } @@ -170,7 +171,7 @@ func (p *AccountProvider) HandleRequest(_ context.Context, op string, req *http. case "PutAlternateContact": return p.putAlternateContact(bodyMap) case "DeleteAlternateContact": - contactType := strVal(bodyMap, "AlternateContactType") + contactType := shared.StrParam(bodyMap, "AlternateContactType") if contactType == "" { contactType = q.Get("AlternateContactType") } @@ -178,7 +179,7 @@ func (p *AccountProvider) HandleRequest(_ context.Context, op string, req *http. case "ListRegions": return p.listRegions() case "GetRegionOptStatus": - regionName := strVal(bodyMap, "RegionName") + regionName := shared.StrParam(bodyMap, "RegionName") if regionName == "" { regionName = q.Get("RegionName") } @@ -208,10 +209,6 @@ func (p *AccountProvider) ListResources(_ context.Context) ([]plugin.Resource, e }, nil } -func (p *AccountProvider) GetMetrics(_ context.Context) (*plugin.ServiceMetrics, error) { - return &plugin.ServiceMetrics{}, nil -} - // --- helpers --- func jsonError(code, message string, status int) *plugin.Response { @@ -235,15 +232,6 @@ func jsonResponse(status int, v any) (*plugin.Response, error) { }, nil } -func strVal(m map[string]any, key string) string { - if v, ok := m[key]; ok { - if s, ok := v.(string); ok { - return s - } - } - return "" -} - // --- Contact Information --- func (p *AccountProvider) getContactInformation() (*plugin.Response, error) { @@ -271,24 +259,24 @@ func (p *AccountProvider) getContactInformation() (*plugin.Response, error) { func (p *AccountProvider) putContactInformation(body map[string]any) (*plugin.Response, error) { ci := &ContactInfo{} if contactRaw, ok := body["ContactInformation"].(map[string]any); ok { - ci.FullName = strVal(contactRaw, "FullName") - ci.CompanyName = strVal(contactRaw, "CompanyName") - ci.PhoneNumber = strVal(contactRaw, "PhoneNumber") - ci.AddressLine1 = strVal(contactRaw, "AddressLine1") - ci.City = strVal(contactRaw, "City") - ci.PostalCode = strVal(contactRaw, "PostalCode") - ci.CountryCode = strVal(contactRaw, "CountryCode") - ci.WebsiteURL = strVal(contactRaw, "WebsiteUrl") + ci.FullName = shared.StrParam(contactRaw, "FullName") + ci.CompanyName = shared.StrParam(contactRaw, "CompanyName") + ci.PhoneNumber = shared.StrParam(contactRaw, "PhoneNumber") + ci.AddressLine1 = shared.StrParam(contactRaw, "AddressLine1") + ci.City = shared.StrParam(contactRaw, "City") + ci.PostalCode = shared.StrParam(contactRaw, "PostalCode") + ci.CountryCode = shared.StrParam(contactRaw, "CountryCode") + ci.WebsiteURL = shared.StrParam(contactRaw, "WebsiteUrl") } else { // Flat body - ci.FullName = strVal(body, "FullName") - ci.CompanyName = strVal(body, "CompanyName") - ci.PhoneNumber = strVal(body, "PhoneNumber") - ci.AddressLine1 = strVal(body, "AddressLine1") - ci.City = strVal(body, "City") - ci.PostalCode = strVal(body, "PostalCode") - ci.CountryCode = strVal(body, "CountryCode") - ci.WebsiteURL = strVal(body, "WebsiteUrl") + ci.FullName = shared.StrParam(body, "FullName") + ci.CompanyName = shared.StrParam(body, "CompanyName") + ci.PhoneNumber = shared.StrParam(body, "PhoneNumber") + ci.AddressLine1 = shared.StrParam(body, "AddressLine1") + ci.City = shared.StrParam(body, "City") + ci.PostalCode = shared.StrParam(body, "PostalCode") + ci.CountryCode = shared.StrParam(body, "CountryCode") + ci.WebsiteURL = shared.StrParam(body, "WebsiteUrl") } if err := p.store.PutContactInfo(defaultAccountID, ci); err != nil { return jsonError("InternalError", err.Error(), http.StatusInternalServerError), nil @@ -327,17 +315,17 @@ func (p *AccountProvider) getAlternateContact(contactType string) (*plugin.Respo func (p *AccountProvider) putAlternateContact(body map[string]any) (*plugin.Response, error) { c := &AlternateContact{} if acRaw, ok := body["AlternateContact"].(map[string]any); ok { - c.ContactType = strVal(acRaw, "AlternateContactType") - c.Name = strVal(acRaw, "Name") - c.Title = strVal(acRaw, "Title") - c.Email = strVal(acRaw, "EmailAddress") - c.PhoneNumber = strVal(acRaw, "PhoneNumber") + c.ContactType = shared.StrParam(acRaw, "AlternateContactType") + c.Name = shared.StrParam(acRaw, "Name") + c.Title = shared.StrParam(acRaw, "Title") + c.Email = shared.StrParam(acRaw, "EmailAddress") + c.PhoneNumber = shared.StrParam(acRaw, "PhoneNumber") } else { - c.ContactType = strVal(body, "AlternateContactType") - c.Name = strVal(body, "Name") - c.Title = strVal(body, "Title") - c.Email = strVal(body, "EmailAddress") - c.PhoneNumber = strVal(body, "PhoneNumber") + c.ContactType = shared.StrParam(body, "AlternateContactType") + c.Name = shared.StrParam(body, "Name") + c.Title = shared.StrParam(body, "Title") + c.Email = shared.StrParam(body, "EmailAddress") + c.PhoneNumber = shared.StrParam(body, "PhoneNumber") } if err := p.store.PutAlternateContact(defaultAccountID, c); err != nil { return jsonError("InternalError", err.Error(), http.StatusInternalServerError), nil @@ -384,7 +372,7 @@ func (p *AccountProvider) getRegionOptStatus(regionName string) (*plugin.Respons } func (p *AccountProvider) enableRegion(body map[string]any) (*plugin.Response, error) { - regionName := strVal(body, "RegionName") + regionName := shared.StrParam(body, "RegionName") if err := p.store.SetRegionOptStatus(defaultAccountID, regionName, "ENABLED"); err != nil { return jsonError("InternalError", err.Error(), http.StatusInternalServerError), nil } @@ -392,7 +380,7 @@ func (p *AccountProvider) enableRegion(body map[string]any) (*plugin.Response, e } func (p *AccountProvider) disableRegion(body map[string]any) (*plugin.Response, error) { - regionName := strVal(body, "RegionName") + regionName := shared.StrParam(body, "RegionName") if err := p.store.SetRegionOptStatus(defaultAccountID, regionName, "DISABLED"); err != nil { return jsonError("InternalError", err.Error(), http.StatusInternalServerError), nil } @@ -410,7 +398,7 @@ func (p *AccountProvider) getPrimaryEmail() (*plugin.Response, error) { } func (p *AccountProvider) startPrimaryEmailUpdate(body map[string]any) (*plugin.Response, error) { - pendingEmail := strVal(body, "PrimaryEmail") + pendingEmail := shared.StrParam(body, "PrimaryEmail") if err := p.store.StartPrimaryEmailUpdate(defaultAccountID, pendingEmail); err != nil { return jsonError("InternalError", err.Error(), http.StatusInternalServerError), nil } diff --git a/internal/services/acm/provider.go b/internal/services/acm/provider.go index 9226d0d..41d5638 100644 --- a/internal/services/acm/provider.go +++ b/internal/services/acm/provider.go @@ -20,10 +20,11 @@ import ( "time" "github.com/skyoo2003/devcloud/internal/plugin" + "github.com/skyoo2003/devcloud/internal/shared" ) const defaultAccountID = plugin.DefaultAccountID -const defaultRegion = "us-east-1" +const defaultRegion = shared.DefaultRegion // Provider implements ACM using the JSON 1.1 protocol. // Operations are dispatched via X-Amz-Target header (value passed as op). @@ -150,10 +151,6 @@ func (p *Provider) ListResources(_ context.Context) ([]plugin.Resource, error) { return out, nil } -func (p *Provider) GetMetrics(_ context.Context) (*plugin.ServiceMetrics, error) { - return &plugin.ServiceMetrics{}, nil -} - // --- helpers --- func arnForCert(accountID, region, id string) string { diff --git a/internal/services/acmpca/provider.go b/internal/services/acmpca/provider.go index 3cbbc18..9fdb627 100644 --- a/internal/services/acmpca/provider.go +++ b/internal/services/acmpca/provider.go @@ -19,10 +19,11 @@ import ( "time" "github.com/skyoo2003/devcloud/internal/plugin" + "github.com/skyoo2003/devcloud/internal/shared" ) const defaultAccountID = plugin.DefaultAccountID -const defaultRegion = "us-east-1" +const defaultRegion = shared.DefaultRegion // Provider implements the ACMPrivateCA service using JSON 1.1 protocol. type Provider struct { @@ -120,10 +121,6 @@ func (p *Provider) ListResources(_ context.Context) ([]plugin.Resource, error) { return out, nil } -func (p *Provider) GetMetrics(_ context.Context) (*plugin.ServiceMetrics, error) { - return &plugin.ServiceMetrics{}, nil -} - // --- helpers --- func newID() string { diff --git a/internal/services/amplify/provider.go b/internal/services/amplify/provider.go index 5f89826..26626d1 100644 --- a/internal/services/amplify/provider.go +++ b/internal/services/amplify/provider.go @@ -403,10 +403,6 @@ func (p *Provider) ListResources(_ context.Context) ([]plugin.Resource, error) { return res, nil } -func (p *Provider) GetMetrics(_ context.Context) (*plugin.ServiceMetrics, error) { - return &plugin.ServiceMetrics{}, nil -} - // --- App CRUD --- func (p *Provider) createApp(params map[string]any) (*plugin.Response, error) { @@ -422,10 +418,10 @@ func (p *Provider) createApp(params map[string]any) (*plugin.Response, error) { ID: id, ARN: arn, Name: name, - Description: strParam(params, "description"), - Repository: strParam(params, "repository"), - Platform: strParamDefault(params, "platform", "WEB"), - IAMRole: strParam(params, "iamServiceRoleArn"), + Description: shared.StrParam(params, "description"), + Repository: shared.StrParam(params, "repository"), + Platform: shared.StrParamDefault(params, "platform", "WEB"), + IAMRole: shared.StrParam(params, "iamServiceRoleArn"), DefaultDomain: defaultDomain, } @@ -509,10 +505,10 @@ func (p *Provider) createBranch(appID string, params map[string]any) (*plugin.Re AppID: appID, Name: branchName, ARN: arn, - DisplayName: strParamDefault(params, "displayName", branchName), - Description: strParam(params, "description"), - Stage: strParamDefault(params, "stage", "NONE"), - Framework: strParam(params, "framework"), + DisplayName: shared.StrParamDefault(params, "displayName", branchName), + Description: shared.StrParam(params, "description"), + Stage: shared.StrParamDefault(params, "stage", "NONE"), + Framework: shared.StrParam(params, "framework"), EnableAutoBuild: boolParamDefault(params, "enableAutoBuild", true), } if err := p.store.CreateBranch(b); err != nil { @@ -658,7 +654,7 @@ func (p *Provider) createWebhook(appID string, params map[string]any) (*plugin.R ID: id, ARN: arn, AppID: appID, - BranchName: strParam(params, "branchName"), + BranchName: shared.StrParam(params, "branchName"), URL: fmt.Sprintf("https://webhooks.amplify.us-east-1.io/%s", id), } if err := p.store.CreateWebhook(w); err != nil { @@ -727,7 +723,7 @@ func (p *Provider) createBackendEnvironment(appID string, params map[string]any) AppID: appID, Name: envName, ARN: arn, - StackName: strParam(params, "stackName"), + StackName: shared.StrParam(params, "stackName"), } if err := p.store.CreateBackendEnvironment(be); err != nil { if isUniqueErr(err) { @@ -785,10 +781,10 @@ func (p *Provider) startJob(appID, branchName string, params map[string]any) (*p AppID: appID, BranchName: branchName, ARN: arn, - JobType: strParamDefault(params, "jobType", "RELEASE"), + JobType: shared.StrParamDefault(params, "jobType", "RELEASE"), Status: "SUCCEED", - CommitID: strParam(params, "commitId"), - CommitMsg: strParam(params, "commitMessage"), + CommitID: shared.StrParam(params, "commitId"), + CommitMsg: shared.StrParam(params, "commitMessage"), } if err := p.store.CreateJob(j); err != nil { return nil, err @@ -988,18 +984,6 @@ func extractPathParam(path, key string) string { return "" } -func strParam(params map[string]any, key string) string { - v, _ := params[key].(string) - return v -} - -func strParamDefault(params map[string]any, key, def string) string { - if v, ok := params[key].(string); ok && v != "" { - return v - } - return def -} - func boolParamDefault(params map[string]any, key string, def bool) bool { if v, ok := params[key].(bool); ok { return v diff --git a/internal/services/apigatewayv2/provider.go b/internal/services/apigatewayv2/provider.go index 7c0d295..ebf7b1c 100644 --- a/internal/services/apigatewayv2/provider.go +++ b/internal/services/apigatewayv2/provider.go @@ -341,10 +341,6 @@ func (p *Provider) ListResources(_ context.Context) ([]plugin.Resource, error) { return res, nil } -func (p *Provider) GetMetrics(_ context.Context) (*plugin.ServiceMetrics, error) { - return &plugin.ServiceMetrics{}, nil -} - // --- API CRUD --- func (p *Provider) createAPI(params map[string]any) (*plugin.Response, error) { @@ -360,9 +356,9 @@ func (p *Provider) createAPI(params map[string]any) (*plugin.Response, error) { ID: id, ARN: arn, Name: name, - ProtocolType: strParamDefault(params, "protocolType", "HTTP"), - Description: strParam(params, "description"), - RouteSelection: strParamDefault(params, "routeSelectionExpression", "$request.method $request.path"), + ProtocolType: shared.StrParamDefault(params, "protocolType", "HTTP"), + Description: shared.StrParam(params, "description"), + RouteSelection: shared.StrParamDefault(params, "routeSelectionExpression", "$request.method $request.path"), APIEndpoint: apiEndpoint, } @@ -479,9 +475,9 @@ func (p *Provider) createRoute(apiID string, params map[string]any) (*plugin.Res ID: shared.GenerateID("", 10), APIID: apiID, RouteKey: routeKey, - Target: strParam(params, "target"), - AuthorizationType: strParamDefault(params, "authorizationType", "NONE"), - AuthorizerID: strParam(params, "authorizerId"), + Target: shared.StrParam(params, "target"), + AuthorizationType: shared.StrParamDefault(params, "authorizationType", "NONE"), + AuthorizerID: shared.StrParam(params, "authorizerId"), } if err := p.store.CreateRoute(r); err != nil { return nil, err @@ -543,8 +539,8 @@ func (p *Provider) createRouteResponse(apiID, routeID string, params map[string] ID: shared.GenerateID("", 10), APIID: apiID, RouteID: routeID, - RouteResponseKey: strParamDefault(params, "routeResponseKey", "default"), - ModelSelectionExpr: strParam(params, "modelSelectionExpression"), + RouteResponseKey: shared.StrParamDefault(params, "routeResponseKey", "default"), + ModelSelectionExpr: shared.StrParam(params, "modelSelectionExpression"), } if err := p.store.CreateRouteResponse(rr); err != nil { return nil, err @@ -588,10 +584,10 @@ func (p *Provider) createIntegration(apiID string, params map[string]any) (*plug i := &Integration{ ID: shared.GenerateID("", 10), APIID: apiID, - Type: strParamDefault(params, "integrationType", "HTTP_PROXY"), - IntegrationURI: strParam(params, "integrationUri"), - IntegrationMethod: strParam(params, "integrationMethod"), - PayloadFormat: strParamDefault(params, "payloadFormatVersion", "2.0"), + Type: shared.StrParamDefault(params, "integrationType", "HTTP_PROXY"), + IntegrationURI: shared.StrParam(params, "integrationUri"), + IntegrationMethod: shared.StrParam(params, "integrationMethod"), + PayloadFormat: shared.StrParamDefault(params, "payloadFormatVersion", "2.0"), } if err := p.store.CreateIntegration(i); err != nil { return nil, err @@ -641,8 +637,8 @@ func (p *Provider) createIntegrationResponse(apiID, integrationID string, params ID: shared.GenerateID("", 10), APIID: apiID, IntegrationID: integrationID, - ResponseKey: strParamDefault(params, "integrationResponseKey", "default"), - TemplateSelection: strParam(params, "templateSelectionExpression"), + ResponseKey: shared.StrParamDefault(params, "integrationResponseKey", "default"), + TemplateSelection: shared.StrParam(params, "templateSelectionExpression"), } if err := p.store.CreateIntegrationResponse(ir); err != nil { return nil, err @@ -705,8 +701,8 @@ func (p *Provider) createAuthorizer(apiID string, params map[string]any) (*plugi ID: shared.GenerateID("", 10), APIID: apiID, Name: name, - Type: strParamDefault(params, "authorizerType", "JWT"), - IdentitySource: strParam(params, "identitySource"), + Type: shared.StrParamDefault(params, "authorizerType", "JWT"), + IdentitySource: shared.StrParam(params, "identitySource"), JWTConfig: jwtConfig, } if err := p.store.CreateAuthorizer(a); err != nil { @@ -759,7 +755,7 @@ func (p *Provider) createDeployment(apiID string, params map[string]any) (*plugi d := &Deployment{ ID: shared.GenerateID("", 10), APIID: apiID, - Description: strParam(params, "description"), + Description: shared.StrParam(params, "description"), } if err := p.store.CreateDeployment(d); err != nil { return nil, err @@ -821,8 +817,8 @@ func (p *Provider) createStage(apiID string, params map[string]any) (*plugin.Res st := &Stage{ Name: stageName, APIID: apiID, - Description: strParam(params, "description"), - DeploymentID: strParam(params, "deploymentId"), + Description: shared.StrParam(params, "description"), + DeploymentID: shared.StrParam(params, "deploymentId"), AutoDeploy: boolParamDefault(params, "autoDeploy", false), StageVariables: stageVars, AccessLog: "{}", @@ -889,7 +885,7 @@ func (p *Provider) createModel(apiID string, params map[string]any) (*plugin.Res ID: shared.GenerateID("", 10), APIID: apiID, Name: name, - ContentType: strParamDefault(params, "contentType", "application/json"), + ContentType: shared.StrParamDefault(params, "contentType", "application/json"), SchemaDef: schemaDef, } if err := p.store.CreateModel(m); err != nil { @@ -1013,8 +1009,8 @@ func (p *Provider) createAPIMapping(domainName string, params map[string]any) (* ID: shared.GenerateID("", 10), APIID: apiID, DomainName: domainName, - Stage: strParam(params, "stage"), - APIMappingKey: strParam(params, "apiMappingKey"), + Stage: shared.StrParam(params, "stage"), + APIMappingKey: shared.StrParam(params, "apiMappingKey"), } if err := p.store.CreateAPIMapping(m); err != nil { return nil, err @@ -1610,18 +1606,6 @@ func extractPathParam(path, key string) string { return "" } -func strParam(params map[string]any, key string) string { - v, _ := params[key].(string) - return v -} - -func strParamDefault(params map[string]any, key, def string) string { - if v, ok := params[key].(string); ok && v != "" { - return v - } - return def -} - func boolParamDefault(params map[string]any, key string, def bool) bool { if v, ok := params[key].(bool); ok { return v diff --git a/internal/services/appconfig/provider.go b/internal/services/appconfig/provider.go index 6df70a4..94c13f1 100644 --- a/internal/services/appconfig/provider.go +++ b/internal/services/appconfig/provider.go @@ -224,10 +224,6 @@ func (p *Provider) ListResources(_ context.Context) ([]plugin.Resource, error) { return res, nil } -func (p *Provider) GetMetrics(_ context.Context) (*plugin.ServiceMetrics, error) { - return &plugin.ServiceMetrics{}, nil -} - // --- Applications --- func (p *Provider) createApplication(params map[string]any) (*plugin.Response, error) { diff --git a/internal/services/applicationautoscaling/provider.go b/internal/services/applicationautoscaling/provider.go index 59d04d1..dfe2c84 100644 --- a/internal/services/applicationautoscaling/provider.go +++ b/internal/services/applicationautoscaling/provider.go @@ -167,10 +167,6 @@ func (p *Provider) ListResources(_ context.Context) ([]plugin.Resource, error) { return res, nil } -func (p *Provider) GetMetrics(_ context.Context) (*plugin.ServiceMetrics, error) { - return &plugin.ServiceMetrics{}, nil -} - func (p *Provider) registerScalableTarget(params map[string]any) (*plugin.Response, error) { ns, _ := params["ServiceNamespace"].(string) resourceID, _ := params["ResourceId"].(string) diff --git a/internal/services/appsync/provider.go b/internal/services/appsync/provider.go index 907cc85..d5b3114 100644 --- a/internal/services/appsync/provider.go +++ b/internal/services/appsync/provider.go @@ -439,10 +439,6 @@ func (p *Provider) ListResources(_ context.Context) ([]plugin.Resource, error) { return res, nil } -func (p *Provider) GetMetrics(_ context.Context) (*plugin.ServiceMetrics, error) { - return &plugin.ServiceMetrics{}, nil -} - // --- GraphqlApi CRUD --- func (p *Provider) createGraphqlApi(params map[string]any) (*plugin.Response, error) { @@ -461,7 +457,7 @@ func (p *Provider) createGraphqlApi(params map[string]any) (*plugin.Response, er ID: id, ARN: arn, Name: name, - AuthType: strParamDefault(params, "authenticationType", "API_KEY"), + AuthType: shared.StrParamDefault(params, "authenticationType", "API_KEY"), LogConfig: "{}", Uris: string(urisJSON), } @@ -542,9 +538,9 @@ func (p *Provider) createDataSource(apiID string, params map[string]any) (*plugi ApiID: apiID, Name: name, ARN: arn, - Type: strParamDefault(params, "type", "NONE"), + Type: shared.StrParamDefault(params, "type", "NONE"), Config: "{}", - ServiceRole: strParam(params, "serviceRoleArn"), + ServiceRole: shared.StrParam(params, "serviceRoleArn"), } if err := p.store.CreateDataSource(ds); err != nil { if isUniqueErr(err) { @@ -616,10 +612,10 @@ func (p *Provider) createResolver(apiID, typeName string, params map[string]any) TypeName: typeName, FieldName: fieldName, ARN: arn, - DataSource: strParam(params, "dataSourceName"), - RequestTemplate: strParam(params, "requestMappingTemplate"), - ResponseTemplate: strParam(params, "responseMappingTemplate"), - Kind: strParamDefault(params, "kind", "UNIT"), + DataSource: shared.StrParam(params, "dataSourceName"), + RequestTemplate: shared.StrParam(params, "requestMappingTemplate"), + ResponseTemplate: shared.StrParam(params, "responseMappingTemplate"), + Kind: shared.StrParamDefault(params, "kind", "UNIT"), } if err := p.store.CreateResolver(r); err != nil { if isUniqueErr(err) { @@ -704,9 +700,9 @@ func (p *Provider) createFunction(apiID string, params map[string]any) (*plugin. ID: id, ARN: arn, Name: name, - DataSource: strParam(params, "dataSourceName"), - RequestTemplate: strParam(params, "requestMappingTemplate"), - ResponseTemplate: strParam(params, "responseMappingTemplate"), + DataSource: shared.StrParam(params, "dataSourceName"), + RequestTemplate: shared.StrParam(params, "requestMappingTemplate"), + ResponseTemplate: shared.StrParam(params, "responseMappingTemplate"), } if err := p.store.CreateFunction(f); err != nil { return nil, err @@ -774,7 +770,7 @@ func (p *Provider) createApiKey(apiID string, params map[string]any) (*plugin.Re ApiID: apiID, ID: id, Expires: expires, - Description: strParam(params, "description"), + Description: shared.StrParam(params, "description"), } if err := p.store.CreateApiKey(k); err != nil { return nil, err @@ -831,7 +827,7 @@ func (p *Provider) createType(apiID string, params map[string]any) (*plugin.Resp if name == "" { name = shared.GenerateID("Type", 8) } - format := strParamDefault(params, "format", "SDL") + format := shared.StrParamDefault(params, "format", "SDL") tp := &Type{ ApiID: apiID, Name: name, @@ -1026,18 +1022,6 @@ func extractTagARN(path string) string { return path[idx+len("/tags/"):] } -func strParam(params map[string]any, key string) string { - v, _ := params[key].(string) - return v -} - -func strParamDefault(params map[string]any, key, def string) string { - if v, ok := params[key].(string); ok && v != "" { - return v - } - return def -} - func toStringMap(m map[string]any) map[string]string { result := make(map[string]string, len(m)) for k, v := range m { diff --git a/internal/services/athena/provider.go b/internal/services/athena/provider.go index b45e9ce..3a7041e 100644 --- a/internal/services/athena/provider.go +++ b/internal/services/athena/provider.go @@ -137,14 +137,14 @@ func (p *Provider) HandleRequest(_ context.Context, op string, req *http.Request return shared.JSONResponse(http.StatusOK, map[string]any{}) case "GetCapacityReservation": return shared.JSONResponse(http.StatusOK, map[string]any{ - "CapacityReservation": map[string]any{"Name": strParam(params, "Name"), "Status": "ACTIVE", "TargetDpus": 24}, + "CapacityReservation": map[string]any{"Name": shared.StrParam(params, "Name"), "Status": "ACTIVE", "TargetDpus": 24}, }) case "ListCapacityReservations": return shared.JSONResponse(http.StatusOK, map[string]any{"CapacityReservations": []any{}}) case "GetCapacityAssignmentConfiguration": return shared.JSONResponse(http.StatusOK, map[string]any{ "CapacityAssignmentConfiguration": map[string]any{ - "CapacityReservationName": strParam(params, "CapacityReservationName"), + "CapacityReservationName": shared.StrParam(params, "CapacityReservationName"), "CapacityAssignments": []any{}, }, }) @@ -153,7 +153,7 @@ func (p *Provider) HandleRequest(_ context.Context, op string, req *http.Request case "ExportNotebook": return shared.JSONResponse(http.StatusOK, map[string]any{"NotebookMetadata": map[string]any{}, "Payload": ""}) case "GetNotebookMetadata": - return shared.JSONResponse(http.StatusOK, map[string]any{"NotebookMetadata": map[string]any{"NotebookId": strParam(params, "NotebookId")}}) + return shared.JSONResponse(http.StatusOK, map[string]any{"NotebookMetadata": map[string]any{"NotebookId": shared.StrParam(params, "NotebookId")}}) case "ListNotebookMetadata": return shared.JSONResponse(http.StatusOK, map[string]any{"NotebookMetadataList": []any{}}) case "CreatePresignedNotebookUrl": @@ -163,9 +163,9 @@ func (p *Provider) HandleRequest(_ context.Context, op string, req *http.Request case "StartSession": return shared.JSONResponse(http.StatusOK, map[string]any{"SessionId": shared.GenerateUUID(), "State": "CREATING"}) case "GetSession": - return shared.JSONResponse(http.StatusOK, map[string]any{"SessionId": strParam(params, "SessionId"), "Status": map[string]any{"State": "IDLE"}}) + return shared.JSONResponse(http.StatusOK, map[string]any{"SessionId": shared.StrParam(params, "SessionId"), "Status": map[string]any{"State": "IDLE"}}) case "GetSessionStatus": - return shared.JSONResponse(http.StatusOK, map[string]any{"SessionId": strParam(params, "SessionId"), "Status": map[string]any{"State": "IDLE"}}) + return shared.JSONResponse(http.StatusOK, map[string]any{"SessionId": shared.StrParam(params, "SessionId"), "Status": map[string]any{"State": "IDLE"}}) case "GetSessionEndpoint": return shared.JSONResponse(http.StatusOK, map[string]any{"Endpoint": ""}) case "ListSessions": @@ -177,7 +177,7 @@ func (p *Provider) HandleRequest(_ context.Context, op string, req *http.Request case "StopCalculationExecution": return shared.JSONResponse(http.StatusOK, map[string]any{"State": "CANCELING"}) case "GetCalculationExecution": - return shared.JSONResponse(http.StatusOK, map[string]any{"CalculationExecutionId": strParam(params, "CalculationExecutionId"), "Status": map[string]any{"State": "COMPLETED"}}) + return shared.JSONResponse(http.StatusOK, map[string]any{"CalculationExecutionId": shared.StrParam(params, "CalculationExecutionId"), "Status": map[string]any{"State": "COMPLETED"}}) case "GetCalculationExecutionCode": return shared.JSONResponse(http.StatusOK, map[string]any{"CodeBlock": ""}) case "GetCalculationExecutionStatus": @@ -188,13 +188,13 @@ func (p *Provider) HandleRequest(_ context.Context, op string, req *http.Request return shared.JSONResponse(http.StatusOK, map[string]any{"Executors": []any{}}) case "GetDatabase": return shared.JSONResponse(http.StatusOK, map[string]any{ - "Database": map[string]any{"Name": strParam(params, "Database"), "Description": ""}, + "Database": map[string]any{"Name": shared.StrParam(params, "Database"), "Description": ""}, }) case "ListDatabases": return shared.JSONResponse(http.StatusOK, map[string]any{"DatabaseList": []any{}}) case "GetTableMetadata": return shared.JSONResponse(http.StatusOK, map[string]any{ - "TableMetadata": map[string]any{"Name": strParam(params, "TableName"), "Columns": []any{}}, + "TableMetadata": map[string]any{"Name": shared.StrParam(params, "TableName"), "Columns": []any{}}, }) case "ListTableMetadata": return shared.JSONResponse(http.StatusOK, map[string]any{"TableMetadataList": []any{}}) @@ -224,18 +224,14 @@ func (p *Provider) ListResources(_ context.Context) ([]plugin.Resource, error) { return res, nil } -func (p *Provider) GetMetrics(_ context.Context) (*plugin.ServiceMetrics, error) { - return &plugin.ServiceMetrics{}, nil -} - // ---- WorkGroup handlers ---- func (p *Provider) createWorkGroup(params map[string]any) (*plugin.Response, error) { - name := strParam(params, "Name") + name := shared.StrParam(params, "Name") if name == "" { return shared.JSONError("InvalidRequestException", "Name is required", http.StatusBadRequest), nil } - description := strParam(params, "Description") + description := shared.StrParam(params, "Description") configJSON := "{}" if cfg, ok := params["Configuration"]; ok { b, _ := json.Marshal(cfg) @@ -257,7 +253,7 @@ func (p *Provider) createWorkGroup(params map[string]any) (*plugin.Response, err } func (p *Provider) getWorkGroup(params map[string]any) (*plugin.Response, error) { - name := strParam(params, "WorkGroup") + name := shared.StrParam(params, "WorkGroup") if name == "" { return shared.JSONError("InvalidRequestException", "WorkGroup is required", http.StatusBadRequest), nil } @@ -288,7 +284,7 @@ func (p *Provider) listWorkGroups(_ map[string]any) (*plugin.Response, error) { } func (p *Provider) deleteWorkGroup(params map[string]any) (*plugin.Response, error) { - name := strParam(params, "WorkGroup") + name := shared.StrParam(params, "WorkGroup") if name == "" { return shared.JSONError("InvalidRequestException", "WorkGroup is required", http.StatusBadRequest), nil } @@ -304,7 +300,7 @@ func (p *Provider) deleteWorkGroup(params map[string]any) (*plugin.Response, err } func (p *Provider) updateWorkGroup(params map[string]any) (*plugin.Response, error) { - name := strParam(params, "WorkGroup") + name := shared.StrParam(params, "WorkGroup") if name == "" { return shared.JSONError("InvalidRequestException", "WorkGroup is required", http.StatusBadRequest), nil } @@ -312,11 +308,11 @@ func (p *Provider) updateWorkGroup(params map[string]any) (*plugin.Response, err if err != nil { return shared.JSONError("InvalidRequestException", "WorkGroup not found", http.StatusBadRequest), nil } - description := strParam(params, "Description") + description := shared.StrParam(params, "Description") if description == "" { description = wg.Description } - state := strParam(params, "State") + state := shared.StrParam(params, "State") if state == "" { state = wg.State } @@ -346,17 +342,17 @@ func workGroupToMap(wg *WorkGroup) map[string]any { // ---- NamedQuery handlers ---- func (p *Provider) createNamedQuery(params map[string]any) (*plugin.Response, error) { - name := strParam(params, "Name") - queryString := strParam(params, "QueryString") + name := shared.StrParam(params, "Name") + queryString := shared.StrParam(params, "QueryString") if name == "" || queryString == "" { return shared.JSONError("InvalidRequestException", "Name and QueryString are required", http.StatusBadRequest), nil } - workgroup := strParam(params, "WorkGroup") + workgroup := shared.StrParam(params, "WorkGroup") if workgroup == "" { workgroup = "primary" } - database := strParam(params, "Database") - description := strParam(params, "Description") + database := shared.StrParam(params, "Database") + description := shared.StrParam(params, "Description") id := shared.GenerateUUID() nq, err := p.store.CreateNamedQuery(id, name, workgroup, database, queryString, description) if err != nil { @@ -366,7 +362,7 @@ func (p *Provider) createNamedQuery(params map[string]any) (*plugin.Response, er } func (p *Provider) getNamedQuery(params map[string]any) (*plugin.Response, error) { - id := strParam(params, "NamedQueryId") + id := shared.StrParam(params, "NamedQueryId") if id == "" { return shared.JSONError("InvalidRequestException", "NamedQueryId is required", http.StatusBadRequest), nil } @@ -380,7 +376,7 @@ func (p *Provider) getNamedQuery(params map[string]any) (*plugin.Response, error } func (p *Provider) listNamedQueries(params map[string]any) (*plugin.Response, error) { - workgroup := strParam(params, "WorkGroup") + workgroup := shared.StrParam(params, "WorkGroup") queries, err := p.store.ListNamedQueries(workgroup) if err != nil { return nil, err @@ -393,7 +389,7 @@ func (p *Provider) listNamedQueries(params map[string]any) (*plugin.Response, er } func (p *Provider) deleteNamedQuery(params map[string]any) (*plugin.Response, error) { - id := strParam(params, "NamedQueryId") + id := shared.StrParam(params, "NamedQueryId") if id == "" { return shared.JSONError("InvalidRequestException", "NamedQueryId is required", http.StatusBadRequest), nil } @@ -436,11 +432,11 @@ func namedQueryToMap(nq *NamedQuery) map[string]any { // ---- QueryExecution handlers ---- func (p *Provider) startQueryExecution(params map[string]any) (*plugin.Response, error) { - query := strParam(params, "QueryString") + query := shared.StrParam(params, "QueryString") if query == "" { return shared.JSONError("InvalidRequestException", "QueryString is required", http.StatusBadRequest), nil } - workgroup := strParam(params, "WorkGroup") + workgroup := shared.StrParam(params, "WorkGroup") if workgroup == "" { workgroup = "primary" } @@ -457,7 +453,7 @@ func (p *Provider) startQueryExecution(params map[string]any) (*plugin.Response, } func (p *Provider) getQueryExecution(params map[string]any) (*plugin.Response, error) { - id := strParam(params, "QueryExecutionId") + id := shared.StrParam(params, "QueryExecutionId") if id == "" { return shared.JSONError("InvalidRequestException", "QueryExecutionId is required", http.StatusBadRequest), nil } @@ -471,7 +467,7 @@ func (p *Provider) getQueryExecution(params map[string]any) (*plugin.Response, e } func (p *Provider) getQueryResults(params map[string]any) (*plugin.Response, error) { - id := strParam(params, "QueryExecutionId") + id := shared.StrParam(params, "QueryExecutionId") if id == "" { return shared.JSONError("InvalidRequestException", "QueryExecutionId is required", http.StatusBadRequest), nil } @@ -492,7 +488,7 @@ func (p *Provider) getQueryResults(params map[string]any) (*plugin.Response, err } func (p *Provider) listQueryExecutions(params map[string]any) (*plugin.Response, error) { - workgroup := strParam(params, "WorkGroup") + workgroup := shared.StrParam(params, "WorkGroup") execs, err := p.store.ListQueryExecutions(workgroup) if err != nil { return nil, err @@ -505,7 +501,7 @@ func (p *Provider) listQueryExecutions(params map[string]any) (*plugin.Response, } func (p *Provider) stopQueryExecution(params map[string]any) (*plugin.Response, error) { - id := strParam(params, "QueryExecutionId") + id := shared.StrParam(params, "QueryExecutionId") if id == "" { return shared.JSONError("InvalidRequestException", "QueryExecutionId is required", http.StatusBadRequest), nil } @@ -535,7 +531,7 @@ func (p *Provider) batchGetQueryExecution(params map[string]any) (*plugin.Respon } func (p *Provider) getQueryRuntimeStatistics(params map[string]any) (*plugin.Response, error) { - id := strParam(params, "QueryExecutionId") + id := shared.StrParam(params, "QueryExecutionId") if id == "" { return shared.JSONError("InvalidRequestException", "QueryExecutionId is required", http.StatusBadRequest), nil } @@ -577,15 +573,15 @@ func queryExecutionToMap(qe *QueryExecution) map[string]any { // ---- DataCatalog handlers ---- func (p *Provider) createDataCatalog(params map[string]any) (*plugin.Response, error) { - name := strParam(params, "Name") + name := shared.StrParam(params, "Name") if name == "" { return shared.JSONError("InvalidRequestException", "Name is required", http.StatusBadRequest), nil } - catalogType := strParam(params, "Type") + catalogType := shared.StrParam(params, "Type") if catalogType == "" { catalogType = "HIVE" } - description := strParam(params, "Description") + description := shared.StrParam(params, "Description") parametersJSON := "{}" if p2, ok := params["Parameters"]; ok { b, _ := json.Marshal(p2) @@ -602,7 +598,7 @@ func (p *Provider) createDataCatalog(params map[string]any) (*plugin.Response, e } func (p *Provider) getDataCatalog(params map[string]any) (*plugin.Response, error) { - name := strParam(params, "Name") + name := shared.StrParam(params, "Name") if name == "" { return shared.JSONError("InvalidRequestException", "Name is required", http.StatusBadRequest), nil } @@ -631,7 +627,7 @@ func (p *Provider) listDataCatalogs(_ map[string]any) (*plugin.Response, error) } func (p *Provider) updateDataCatalog(params map[string]any) (*plugin.Response, error) { - name := strParam(params, "Name") + name := shared.StrParam(params, "Name") if name == "" { return shared.JSONError("InvalidRequestException", "Name is required", http.StatusBadRequest), nil } @@ -639,7 +635,7 @@ func (p *Provider) updateDataCatalog(params map[string]any) (*plugin.Response, e if err != nil { return shared.JSONError("InvalidRequestException", "DataCatalog not found", http.StatusBadRequest), nil } - description := strParam(params, "Description") + description := shared.StrParam(params, "Description") if description == "" { description = dc.Description } @@ -655,7 +651,7 @@ func (p *Provider) updateDataCatalog(params map[string]any) (*plugin.Response, e } func (p *Provider) deleteDataCatalog(params map[string]any) (*plugin.Response, error) { - name := strParam(params, "Name") + name := shared.StrParam(params, "Name") if name == "" { return shared.JSONError("InvalidRequestException", "Name is required", http.StatusBadRequest), nil } @@ -679,13 +675,13 @@ func dataCatalogToMap(dc *DataCatalog) map[string]any { // ---- PreparedStatement handlers ---- func (p *Provider) createPreparedStatement(params map[string]any) (*plugin.Response, error) { - name := strParam(params, "StatementName") - workgroup := strParam(params, "WorkGroup") - queryStatement := strParam(params, "QueryStatement") + name := shared.StrParam(params, "StatementName") + workgroup := shared.StrParam(params, "WorkGroup") + queryStatement := shared.StrParam(params, "QueryStatement") if name == "" || workgroup == "" || queryStatement == "" { return shared.JSONError("InvalidRequestException", "StatementName, WorkGroup, and QueryStatement are required", http.StatusBadRequest), nil } - description := strParam(params, "Description") + description := shared.StrParam(params, "Description") if _, err := p.store.CreatePreparedStatement(name, workgroup, queryStatement, description); err != nil { if sqliteIsUnique(err) { return shared.JSONError("InvalidRequestException", "PreparedStatement already exists", http.StatusBadRequest), nil @@ -696,8 +692,8 @@ func (p *Provider) createPreparedStatement(params map[string]any) (*plugin.Respo } func (p *Provider) getPreparedStatement(params map[string]any) (*plugin.Response, error) { - name := strParam(params, "StatementName") - workgroup := strParam(params, "WorkGroup") + name := shared.StrParam(params, "StatementName") + workgroup := shared.StrParam(params, "WorkGroup") if name == "" || workgroup == "" { return shared.JSONError("InvalidRequestException", "StatementName and WorkGroup are required", http.StatusBadRequest), nil } @@ -711,7 +707,7 @@ func (p *Provider) getPreparedStatement(params map[string]any) (*plugin.Response } func (p *Provider) listPreparedStatements(params map[string]any) (*plugin.Response, error) { - workgroup := strParam(params, "WorkGroup") + workgroup := shared.StrParam(params, "WorkGroup") if workgroup == "" { return shared.JSONError("InvalidRequestException", "WorkGroup is required", http.StatusBadRequest), nil } @@ -730,13 +726,13 @@ func (p *Provider) listPreparedStatements(params map[string]any) (*plugin.Respon } func (p *Provider) updatePreparedStatement(params map[string]any) (*plugin.Response, error) { - name := strParam(params, "StatementName") - workgroup := strParam(params, "WorkGroup") - queryStatement := strParam(params, "QueryStatement") + name := shared.StrParam(params, "StatementName") + workgroup := shared.StrParam(params, "WorkGroup") + queryStatement := shared.StrParam(params, "QueryStatement") if name == "" || workgroup == "" || queryStatement == "" { return shared.JSONError("InvalidRequestException", "StatementName, WorkGroup, and QueryStatement are required", http.StatusBadRequest), nil } - description := strParam(params, "Description") + description := shared.StrParam(params, "Description") if err := p.store.UpdatePreparedStatement(name, workgroup, queryStatement, description); err != nil { return shared.JSONError("InvalidRequestException", "PreparedStatement not found", http.StatusBadRequest), nil } @@ -744,8 +740,8 @@ func (p *Provider) updatePreparedStatement(params map[string]any) (*plugin.Respo } func (p *Provider) deletePreparedStatement(params map[string]any) (*plugin.Response, error) { - name := strParam(params, "StatementName") - workgroup := strParam(params, "WorkGroup") + name := shared.StrParam(params, "StatementName") + workgroup := shared.StrParam(params, "WorkGroup") if name == "" || workgroup == "" { return shared.JSONError("InvalidRequestException", "StatementName and WorkGroup are required", http.StatusBadRequest), nil } @@ -756,7 +752,7 @@ func (p *Provider) deletePreparedStatement(params map[string]any) (*plugin.Respo } func (p *Provider) batchGetPreparedStatement(params map[string]any) (*plugin.Response, error) { - workgroup := strParam(params, "WorkGroup") + workgroup := shared.StrParam(params, "WorkGroup") rawNames, _ := params["PreparedStatementNames"].([]any) var found []map[string]any var unprocessed []map[string]any @@ -788,7 +784,7 @@ func preparedStatementToMap(ps *PreparedStatement) map[string]any { // ---- Tag handlers ---- func (p *Provider) tagResource(params map[string]any) (*plugin.Response, error) { - arn := strParam(params, "ResourceARN") + arn := shared.StrParam(params, "ResourceARN") if arn == "" { return shared.JSONError("InvalidRequestException", "ResourceARN is required", http.StatusBadRequest), nil } @@ -800,7 +796,7 @@ func (p *Provider) tagResource(params map[string]any) (*plugin.Response, error) } func (p *Provider) untagResource(params map[string]any) (*plugin.Response, error) { - arn := strParam(params, "ResourceARN") + arn := shared.StrParam(params, "ResourceARN") if arn == "" { return shared.JSONError("InvalidRequestException", "ResourceARN is required", http.StatusBadRequest), nil } @@ -818,7 +814,7 @@ func (p *Provider) untagResource(params map[string]any) (*plugin.Response, error } func (p *Provider) listTagsForResource(params map[string]any) (*plugin.Response, error) { - arn := strParam(params, "ResourceARN") + arn := shared.StrParam(params, "ResourceARN") if arn == "" { return shared.JSONError("InvalidRequestException", "ResourceARN is required", http.StatusBadRequest), nil } @@ -835,11 +831,6 @@ func (p *Provider) listTagsForResource(params map[string]any) (*plugin.Response, // ---- helpers ---- -func strParam(params map[string]any, key string) string { - v, _ := params[key].(string) - return v -} - func parseTags(rawTags []any) map[string]string { tags := make(map[string]string) for _, t := range rawTags { diff --git a/internal/services/autoscaling/provider.go b/internal/services/autoscaling/provider.go index 4f24678..2bd4f22 100644 --- a/internal/services/autoscaling/provider.go +++ b/internal/services/autoscaling/provider.go @@ -193,10 +193,6 @@ func (p *Provider) ListResources(ctx context.Context) ([]plugin.Resource, error) return out, nil } -func (p *Provider) GetMetrics(_ context.Context) (*plugin.ServiceMetrics, error) { - return &plugin.ServiceMetrics{}, nil -} - // --- helpers --- func asError(code, msg string, status int) *plugin.Response { diff --git a/internal/services/backup/provider.go b/internal/services/backup/provider.go index 2a7e002..0483fbe 100644 --- a/internal/services/backup/provider.go +++ b/internal/services/backup/provider.go @@ -331,10 +331,6 @@ func (p *Provider) ListResources(_ context.Context) ([]plugin.Resource, error) { return res, nil } -func (p *Provider) GetMetrics(_ context.Context) (*plugin.ServiceMetrics, error) { - return &plugin.ServiceMetrics{}, nil -} - // ========== BackupPlan ========== func (p *Provider) createBackupPlan(params map[string]any) (*plugin.Response, error) { diff --git a/internal/services/batch/provider.go b/internal/services/batch/provider.go index 4ff8a01..adb9f43 100644 --- a/internal/services/batch/provider.go +++ b/internal/services/batch/provider.go @@ -134,8 +134,8 @@ func (p *Provider) HandleRequest(_ context.Context, op string, req *http.Request // Stub operations — ConsumableResource, QuotaShare, ServiceEnvironment, ServiceJob case "CreateConsumableResource": return shared.JSONResponse(http.StatusOK, map[string]any{ - "consumableResourceArn": shared.BuildARN("batch", "consumable-resource", strParam(params, "consumableResourceName")), - "consumableResourceName": strParam(params, "consumableResourceName"), + "consumableResourceArn": shared.BuildARN("batch", "consumable-resource", shared.StrParam(params, "consumableResourceName")), + "consumableResourceName": shared.StrParam(params, "consumableResourceName"), }) case "DeleteConsumableResource": return shared.JSONResponse(http.StatusOK, map[string]any{}) @@ -162,8 +162,8 @@ func (p *Provider) HandleRequest(_ context.Context, op string, req *http.Request return shared.JSONResponse(http.StatusOK, map[string]any{"jobs": []any{}, "nextToken": ""}) case "CreateQuotaShare": return shared.JSONResponse(http.StatusOK, map[string]any{ - "quotaShareArn": shared.BuildARN("batch", "quota-share", strParam(params, "quotaShareName")), - "quotaShareName": strParam(params, "quotaShareName"), + "quotaShareArn": shared.BuildARN("batch", "quota-share", shared.StrParam(params, "quotaShareName")), + "quotaShareName": shared.StrParam(params, "quotaShareName"), }) case "DeleteQuotaShare": return shared.JSONResponse(http.StatusOK, map[string]any{}) @@ -175,8 +175,8 @@ func (p *Provider) HandleRequest(_ context.Context, op string, req *http.Request return shared.JSONResponse(http.StatusOK, map[string]any{"quotaShares": []any{}, "nextToken": ""}) case "CreateServiceEnvironment": return shared.JSONResponse(http.StatusOK, map[string]any{ - "serviceEnvironmentArn": shared.BuildARN("batch", "service-environment", strParam(params, "serviceEnvironmentName")), - "serviceEnvironmentName": strParam(params, "serviceEnvironmentName"), + "serviceEnvironmentArn": shared.BuildARN("batch", "service-environment", shared.StrParam(params, "serviceEnvironmentName")), + "serviceEnvironmentName": shared.StrParam(params, "serviceEnvironmentName"), }) case "DeleteServiceEnvironment": return shared.JSONResponse(http.StatusOK, map[string]any{}) @@ -189,7 +189,7 @@ func (p *Provider) HandleRequest(_ context.Context, op string, req *http.Request return shared.JSONResponse(http.StatusOK, map[string]any{ "jobArn": shared.BuildARN("batch", "job", jobID), "jobId": jobID, - "jobName": strParam(params, "jobName"), + "jobName": shared.StrParam(params, "jobName"), }) case "TerminateServiceJob": return shared.JSONResponse(http.StatusOK, map[string]any{}) @@ -291,20 +291,16 @@ func (p *Provider) ListResources(_ context.Context) ([]plugin.Resource, error) { return res, nil } -func (p *Provider) GetMetrics(_ context.Context) (*plugin.ServiceMetrics, error) { - return &plugin.ServiceMetrics{}, nil -} - // --- ComputeEnvironment CRUD --- func (p *Provider) createComputeEnvironment(params map[string]any) (*plugin.Response, error) { - name := strParam(params, "computeEnvironmentName") + name := shared.StrParam(params, "computeEnvironmentName") if name == "" { return shared.JSONError("ClientException", "computeEnvironmentName is required", http.StatusBadRequest), nil } - ceType := strParamDefault(params, "type", "MANAGED") - state := strParamDefault(params, "state", "ENABLED") - serviceRole := strParam(params, "serviceRole") + ceType := shared.StrParamDefault(params, "type", "MANAGED") + state := shared.StrParamDefault(params, "state", "ENABLED") + serviceRole := shared.StrParam(params, "serviceRole") computeResources := "{}" if cr, ok := params["computeResources"]; ok { @@ -368,7 +364,7 @@ func (p *Provider) describeComputeEnvironments(params map[string]any) (*plugin.R } func (p *Provider) updateComputeEnvironment(params map[string]any) (*plugin.Response, error) { - nameOrARN := strParam(params, "computeEnvironment") + nameOrARN := shared.StrParam(params, "computeEnvironment") if nameOrARN == "" { return shared.JSONError("ClientException", "computeEnvironment is required", http.StatusBadRequest), nil } @@ -376,8 +372,8 @@ func (p *Provider) updateComputeEnvironment(params map[string]any) (*plugin.Resp if err != nil { return shared.JSONError("ClientException", "compute environment not found", http.StatusBadRequest), nil } - state := strParamDefault(params, "state", ce.State) - serviceRole := strParamDefault(params, "serviceRole", ce.ServiceRole) + state := shared.StrParamDefault(params, "state", ce.State) + serviceRole := shared.StrParamDefault(params, "serviceRole", ce.ServiceRole) if err := p.store.UpdateComputeEnvironment(nameOrARN, state, serviceRole); err != nil { return shared.JSONError("ClientException", "compute environment not found", http.StatusBadRequest), nil @@ -389,7 +385,7 @@ func (p *Provider) updateComputeEnvironment(params map[string]any) (*plugin.Resp } func (p *Provider) deleteComputeEnvironment(params map[string]any) (*plugin.Response, error) { - nameOrARN := strParam(params, "computeEnvironment") + nameOrARN := shared.StrParam(params, "computeEnvironment") if nameOrARN == "" { return shared.JSONError("ClientException", "computeEnvironment is required", http.StatusBadRequest), nil } @@ -407,16 +403,16 @@ func (p *Provider) deleteComputeEnvironment(params map[string]any) (*plugin.Resp // --- JobQueue CRUD --- func (p *Provider) createJobQueue(params map[string]any) (*plugin.Response, error) { - name := strParam(params, "jobQueueName") + name := shared.StrParam(params, "jobQueueName") if name == "" { return shared.JSONError("ClientException", "jobQueueName is required", http.StatusBadRequest), nil } - state := strParamDefault(params, "state", "ENABLED") + state := shared.StrParamDefault(params, "state", "ENABLED") priority := int32(0) if v, ok := params["priority"].(float64); ok { priority = int32(v) } - schedulingPolicy := strParam(params, "schedulingPolicyArn") + schedulingPolicy := shared.StrParam(params, "schedulingPolicyArn") computeEnvs := "[]" if ce, ok := params["computeEnvironmentOrder"]; ok { @@ -480,7 +476,7 @@ func (p *Provider) describeJobQueues(params map[string]any) (*plugin.Response, e } func (p *Provider) updateJobQueue(params map[string]any) (*plugin.Response, error) { - nameOrARN := strParam(params, "jobQueue") + nameOrARN := shared.StrParam(params, "jobQueue") if nameOrARN == "" { return shared.JSONError("ClientException", "jobQueue is required", http.StatusBadRequest), nil } @@ -488,12 +484,12 @@ func (p *Provider) updateJobQueue(params map[string]any) (*plugin.Response, erro if err != nil { return shared.JSONError("ClientException", "job queue not found", http.StatusBadRequest), nil } - state := strParamDefault(params, "state", jq.State) + state := shared.StrParamDefault(params, "state", jq.State) priority := jq.Priority if v, ok := params["priority"].(float64); ok { priority = int32(v) } - schedulingPolicy := strParamDefault(params, "schedulingPolicyArn", jq.SchedulingPolicy) + schedulingPolicy := shared.StrParamDefault(params, "schedulingPolicyArn", jq.SchedulingPolicy) if err := p.store.UpdateJobQueue(nameOrARN, state, priority, schedulingPolicy); err != nil { return shared.JSONError("ClientException", "job queue not found", http.StatusBadRequest), nil @@ -505,7 +501,7 @@ func (p *Provider) updateJobQueue(params map[string]any) (*plugin.Response, erro } func (p *Provider) deleteJobQueue(params map[string]any) (*plugin.Response, error) { - nameOrARN := strParam(params, "jobQueue") + nameOrARN := shared.StrParam(params, "jobQueue") if nameOrARN == "" { return shared.JSONError("ClientException", "jobQueue is required", http.StatusBadRequest), nil } @@ -523,11 +519,11 @@ func (p *Provider) deleteJobQueue(params map[string]any) (*plugin.Response, erro // --- JobDefinition CRUD --- func (p *Provider) registerJobDefinition(params map[string]any) (*plugin.Response, error) { - name := strParam(params, "jobDefinitionName") + name := shared.StrParam(params, "jobDefinitionName") if name == "" { return shared.JSONError("ClientException", "jobDefinitionName is required", http.StatusBadRequest), nil } - jdType := strParamDefault(params, "type", "container") + jdType := shared.StrParamDefault(params, "type", "container") containerProps := "{}" if cp, ok := params["containerProperties"]; ok { @@ -575,8 +571,8 @@ func (p *Provider) registerJobDefinition(params map[string]any) (*plugin.Respons } func (p *Provider) describeJobDefinitions(params map[string]any) (*plugin.Response, error) { - name := strParam(params, "jobDefinitionName") - status := strParam(params, "status") + name := shared.StrParam(params, "jobDefinitionName") + status := shared.StrParam(params, "status") arnFilters := toStringSlice(params["jobDefinitions"]) var jds []*JobDefinition @@ -609,7 +605,7 @@ func (p *Provider) describeJobDefinitions(params map[string]any) (*plugin.Respon } func (p *Provider) deregisterJobDefinition(params map[string]any) (*plugin.Response, error) { - nameOrARN := strParam(params, "jobDefinition") + nameOrARN := shared.StrParam(params, "jobDefinition") if nameOrARN == "" { return shared.JSONError("ClientException", "jobDefinition is required", http.StatusBadRequest), nil } @@ -622,12 +618,12 @@ func (p *Provider) deregisterJobDefinition(params map[string]any) (*plugin.Respo // --- Jobs --- func (p *Provider) submitJob(params map[string]any) (*plugin.Response, error) { - jobName := strParam(params, "jobName") + jobName := shared.StrParam(params, "jobName") if jobName == "" { return shared.JSONError("ClientException", "jobName is required", http.StatusBadRequest), nil } - jobQueue := strParam(params, "jobQueue") - jobDefinition := strParam(params, "jobDefinition") + jobQueue := shared.StrParam(params, "jobQueue") + jobDefinition := shared.StrParam(params, "jobDefinition") parameters := "{}" if p2, ok := params["parameters"]; ok { @@ -687,8 +683,8 @@ func (p *Provider) describeJobs(params map[string]any) (*plugin.Response, error) } func (p *Provider) listJobs(params map[string]any) (*plugin.Response, error) { - queue := strParam(params, "jobQueue") - status := strParam(params, "jobStatus") + queue := shared.StrParam(params, "jobQueue") + status := shared.StrParam(params, "jobStatus") jobs, err := p.store.ListJobs(queue, status) if err != nil { @@ -705,8 +701,8 @@ func (p *Provider) listJobs(params map[string]any) (*plugin.Response, error) { } func (p *Provider) cancelJob(params map[string]any) (*plugin.Response, error) { - jobID := strParam(params, "jobId") - reason := strParamDefault(params, "reason", "cancelled") + jobID := shared.StrParam(params, "jobId") + reason := shared.StrParamDefault(params, "reason", "cancelled") if jobID == "" { return shared.JSONError("ClientException", "jobId is required", http.StatusBadRequest), nil } @@ -717,8 +713,8 @@ func (p *Provider) cancelJob(params map[string]any) (*plugin.Response, error) { } func (p *Provider) terminateJob(params map[string]any) (*plugin.Response, error) { - jobID := strParam(params, "jobId") - reason := strParamDefault(params, "reason", "terminated") + jobID := shared.StrParam(params, "jobId") + reason := shared.StrParamDefault(params, "reason", "terminated") if jobID == "" { return shared.JSONError("ClientException", "jobId is required", http.StatusBadRequest), nil } @@ -731,7 +727,7 @@ func (p *Provider) terminateJob(params map[string]any) (*plugin.Response, error) // --- SchedulingPolicy CRUD --- func (p *Provider) createSchedulingPolicy(params map[string]any) (*plugin.Response, error) { - name := strParam(params, "name") + name := shared.StrParam(params, "name") if name == "" { return shared.JSONError("ClientException", "name is required", http.StatusBadRequest), nil } @@ -803,7 +799,7 @@ func (p *Provider) listSchedulingPolicies() (*plugin.Response, error) { } func (p *Provider) updateSchedulingPolicy(params map[string]any) (*plugin.Response, error) { - arn := strParam(params, "arn") + arn := shared.StrParam(params, "arn") if arn == "" { return shared.JSONError("ClientException", "arn is required", http.StatusBadRequest), nil } @@ -818,7 +814,7 @@ func (p *Provider) updateSchedulingPolicy(params map[string]any) (*plugin.Respon } func (p *Provider) deleteSchedulingPolicy(params map[string]any) (*plugin.Response, error) { - arn := strParam(params, "arn") + arn := shared.StrParam(params, "arn") if arn == "" { return shared.JSONError("ClientException", "arn is required", http.StatusBadRequest), nil } @@ -838,7 +834,7 @@ func (p *Provider) deleteSchedulingPolicy(params map[string]any) (*plugin.Respon func (p *Provider) tagResource(req *http.Request, params map[string]any) (*plugin.Response, error) { arn := extractPathParam(req.URL.Path, "tags") if arn == "" { - arn = strParam(params, "resourceArn") + arn = shared.StrParam(params, "resourceArn") } if arn == "" { return shared.JSONError("ClientException", "resourceArn is required", http.StatusBadRequest), nil @@ -995,20 +991,6 @@ func spToDetailMap(sp *SchedulingPolicy, tags map[string]string) map[string]any // --- Util --- -func strParam(params map[string]any, key string) string { - if v, ok := params[key].(string); ok { - return v - } - return "" -} - -func strParamDefault(params map[string]any, key, def string) string { - if v, ok := params[key].(string); ok && v != "" { - return v - } - return def -} - func toStringMap(m map[string]any) map[string]string { result := make(map[string]string) for k, v := range m { diff --git a/internal/services/bedrock/provider.go b/internal/services/bedrock/provider.go index 0e0e105..0ac5056 100644 --- a/internal/services/bedrock/provider.go +++ b/internal/services/bedrock/provider.go @@ -20,7 +20,7 @@ import ( const ( defaultAccountID = plugin.DefaultAccountID - defaultRegion = "us-east-1" + defaultRegion = shared.DefaultRegion ) // hardcoded foundation models @@ -223,10 +223,6 @@ func (p *Provider) ListResources(_ context.Context) ([]plugin.Resource, error) { return out, nil } -func (p *Provider) GetMetrics(_ context.Context) (*plugin.ServiceMetrics, error) { - return &plugin.ServiceMetrics{}, nil -} - // --- Foundation Models --- func (p *Provider) listFoundationModels(_ url.Values) (*plugin.Response, error) { @@ -256,9 +252,9 @@ func (p *Provider) invokeModel(modelID string, _ bool) (*plugin.Response, error) func (p *Provider) createModelCustomizationJob(body map[string]any) (*plugin.Response, error) { jobID := shared.GenerateUUID() - jobName := strVal(body, "jobName") - customModelName := strVal(body, "customModelName") - baseModelID := strVal(body, "baseModelIdentifier") + jobName := shared.StrParam(body, "jobName") + customModelName := shared.StrParam(body, "customModelName") + baseModelID := shared.StrParam(body, "baseModelIdentifier") j := &CustomizationJob{ JobID: jobID, @@ -377,8 +373,8 @@ func (p *Provider) deleteCustomModel(modelID string) (*plugin.Response, error) { // --- Guardrails --- func (p *Provider) createGuardrail(body map[string]any) (*plugin.Response, error) { - name := strVal(body, "name") - description := strVal(body, "description") + name := shared.StrParam(body, "name") + description := shared.StrParam(body, "description") guardrailID := shared.GenerateID("", 10) g := &Guardrail{ @@ -447,11 +443,11 @@ func (p *Provider) updateGuardrail(guardrailID string, body map[string]any) (*pl if err != nil { return shared.JSONError("ResourceNotFoundException", "guardrail not found", http.StatusNotFound), nil } - name := strVal(body, "name") + name := shared.StrParam(body, "name") if name == "" { name = g.Name } - description := strVal(body, "description") + description := shared.StrParam(body, "description") if err := p.store.UpdateGuardrail(guardrailID, name, description); err != nil { return shared.JSONError("InternalError", err.Error(), http.StatusInternalServerError), nil } @@ -520,15 +516,6 @@ func (p *Provider) untagResource(arn string, keys []string) (*plugin.Response, e // --- helpers --- -func strVal(m map[string]any, key string) string { - if v, ok := m[key]; ok { - if s, ok := v.(string); ok { - return s - } - } - return "" -} - func extractSegment(path, prefix string) string { s := strings.TrimPrefix(path, prefix) // Remove trailing slash segments beyond first diff --git a/internal/services/cloudcontrol/provider.go b/internal/services/cloudcontrol/provider.go index e7bc69e..8c194ad 100644 --- a/internal/services/cloudcontrol/provider.go +++ b/internal/services/cloudcontrol/provider.go @@ -153,10 +153,6 @@ func (p *CloudControlProvider) ListResources(_ context.Context) ([]plugin.Resour return []plugin.Resource{}, nil } -func (p *CloudControlProvider) GetMetrics(_ context.Context) (*plugin.ServiceMetrics, error) { - return &plugin.ServiceMetrics{}, nil -} - // --- Operations --- func (p *CloudControlProvider) createResource(params map[string]any) (*plugin.Response, error) { diff --git a/internal/services/cloudformation/provider.go b/internal/services/cloudformation/provider.go index 3470d1e..cb6f330 100644 --- a/internal/services/cloudformation/provider.go +++ b/internal/services/cloudformation/provider.go @@ -223,10 +223,6 @@ func (p *Provider) ListResources(ctx context.Context) ([]plugin.Resource, error) return out, nil } -func (p *Provider) GetMetrics(_ context.Context) (*plugin.ServiceMetrics, error) { - return &plugin.ServiceMetrics{}, nil -} - // ============================================================ // Stack handlers // ============================================================ diff --git a/internal/services/cloudfront/provider.go b/internal/services/cloudfront/provider.go index 9ac793c..738a6c6 100644 --- a/internal/services/cloudfront/provider.go +++ b/internal/services/cloudfront/provider.go @@ -178,10 +178,6 @@ func (p *Provider) ListResources(_ context.Context) ([]plugin.Resource, error) { return out, nil } -func (p *Provider) GetMetrics(_ context.Context) (*plugin.ServiceMetrics, error) { - return &plugin.ServiceMetrics{}, nil -} - // --- helpers --- func cfError(code, msg string, status int) *plugin.Response { diff --git a/internal/services/cloudsearch/provider.go b/internal/services/cloudsearch/provider.go index efc7d51..ff46bf9 100644 --- a/internal/services/cloudsearch/provider.go +++ b/internal/services/cloudsearch/provider.go @@ -141,10 +141,6 @@ func (p *Provider) ListResources(ctx context.Context) ([]plugin.Resource, error) return out, nil } -func (p *Provider) GetMetrics(_ context.Context) (*plugin.ServiceMetrics, error) { - return &plugin.ServiceMetrics{}, nil -} - // --- helpers --- func csError(code, msg string, status int) *plugin.Response { diff --git a/internal/services/cloudtrail/provider.go b/internal/services/cloudtrail/provider.go index e5fbcda..0d69c79 100644 --- a/internal/services/cloudtrail/provider.go +++ b/internal/services/cloudtrail/provider.go @@ -186,10 +186,6 @@ func (p *Provider) ListResources(_ context.Context) ([]plugin.Resource, error) { return res, nil } -func (p *Provider) GetMetrics(_ context.Context) (*plugin.ServiceMetrics, error) { - return &plugin.ServiceMetrics{}, nil -} - // ---- Trail handlers ---- func (p *Provider) createTrail(params map[string]any) (*plugin.Response, error) { diff --git a/internal/services/cloudwatch/provider.go b/internal/services/cloudwatch/provider.go index e6e6782..72b65ec 100644 --- a/internal/services/cloudwatch/provider.go +++ b/internal/services/cloudwatch/provider.go @@ -119,10 +119,6 @@ func (p *Provider) ListResources(_ context.Context) ([]plugin.Resource, error) { return resources, nil } -func (p *Provider) GetMetrics(_ context.Context) (*plugin.ServiceMetrics, error) { - return &plugin.ServiceMetrics{}, nil -} - // putMetricData parses MetricData.member.N.* form fields. func (p *Provider) putMetricData(jm bool, req *http.Request) (*plugin.Response, error) { namespace := req.FormValue("Namespace") diff --git a/internal/services/cloudwatchlogs/provider.go b/internal/services/cloudwatchlogs/provider.go index 4e49d02..993a2d2 100644 --- a/internal/services/cloudwatchlogs/provider.go +++ b/internal/services/cloudwatchlogs/provider.go @@ -137,10 +137,6 @@ func (p *Provider) ListResources(_ context.Context) ([]plugin.Resource, error) { return resources, nil } -func (p *Provider) GetMetrics(_ context.Context) (*plugin.ServiceMetrics, error) { - return &plugin.ServiceMetrics{}, nil -} - func (p *Provider) createLogGroup(params map[string]any) (*plugin.Response, error) { name, _ := params["logGroupName"].(string) if name == "" { diff --git a/internal/services/codeartifact/provider.go b/internal/services/codeartifact/provider.go index d6886f5..3d2176c 100644 --- a/internal/services/codeartifact/provider.go +++ b/internal/services/codeartifact/provider.go @@ -345,10 +345,6 @@ func (p *Provider) ListResources(_ context.Context) ([]plugin.Resource, error) { return res, nil } -func (p *Provider) GetMetrics(_ context.Context) (*plugin.ServiceMetrics, error) { - return &plugin.ServiceMetrics{}, nil -} - // --- Domain --- func (p *Provider) createDomain(req *http.Request, params map[string]any) (*plugin.Response, error) { diff --git a/internal/services/codebuild/provider.go b/internal/services/codebuild/provider.go index f449674..b2eb4dd 100644 --- a/internal/services/codebuild/provider.go +++ b/internal/services/codebuild/provider.go @@ -153,10 +153,6 @@ func (p *Provider) ListResources(_ context.Context) ([]plugin.Resource, error) { return res, nil } -func (p *Provider) GetMetrics(_ context.Context) (*plugin.ServiceMetrics, error) { - return &plugin.ServiceMetrics{}, nil -} - // ---- Project handlers ---- func (p *Provider) createProject(params map[string]any) (*plugin.Response, error) { diff --git a/internal/services/codecommit/provider.go b/internal/services/codecommit/provider.go index b3b57a5..0e13990 100644 --- a/internal/services/codecommit/provider.go +++ b/internal/services/codecommit/provider.go @@ -212,10 +212,6 @@ func (p *Provider) ListResources(_ context.Context) ([]plugin.Resource, error) { return res, nil } -func (p *Provider) GetMetrics(_ context.Context) (*plugin.ServiceMetrics, error) { - return &plugin.ServiceMetrics{}, nil -} - // ---- Repository handlers ---- func (p *Provider) createRepository(params map[string]any) (*plugin.Response, error) { diff --git a/internal/services/codeconnections/provider.go b/internal/services/codeconnections/provider.go index 11402d7..8c4863a 100644 --- a/internal/services/codeconnections/provider.go +++ b/internal/services/codeconnections/provider.go @@ -135,7 +135,7 @@ func (p *CodeConnectionsProvider) HandleRequest(_ context.Context, op string, re case "GetSyncBlockerSummary": return shared.JSONResponse(http.StatusOK, map[string]any{ "SyncBlockerSummary": map[string]any{ - "ResourceName": getString(params, "ResourceName"), + "ResourceName": shared.StrParam(params, "ResourceName"), "ParentResourceName": "", "LatestBlockers": []any{}, }, @@ -149,22 +149,22 @@ func (p *CodeConnectionsProvider) HandleRequest(_ context.Context, op string, re return shared.JSONResponse(http.StatusOK, map[string]any{ "SyncBlocker": map[string]any{ "Id": shared.GenerateID("blocker-", 16), - "Type": getString(params, "Type"), + "Type": shared.StrParam(params, "Type"), "Status": "ACTIVE", }, }) case "UpdateSyncBlocker": return shared.JSONResponse(http.StatusOK, map[string]any{ - "ResourceName": getString(params, "ResourceName"), + "ResourceName": shared.StrParam(params, "ResourceName"), "ParentResourceName": "", "SyncBlocker": map[string]any{ - "Id": getString(params, "Id"), + "Id": shared.StrParam(params, "Id"), "Status": "RESOLVED", }, }) case "StartRepositoryLink": return shared.JSONResponse(http.StatusOK, map[string]any{ - "RepositoryLinkId": getString(params, "RepositoryLinkId"), + "RepositoryLinkId": shared.StrParam(params, "RepositoryLinkId"), }) case "GetRepositorySyncStatus": return shared.JSONResponse(http.StatusOK, map[string]any{ @@ -221,11 +221,6 @@ func (p *CodeConnectionsProvider) ListResources(_ context.Context) ([]plugin.Res return res, nil } -// GetMetrics returns empty metrics. -func (p *CodeConnectionsProvider) GetMetrics(_ context.Context) (*plugin.ServiceMetrics, error) { - return &plugin.ServiceMetrics{}, nil -} - // --- Connection handlers --- func (p *CodeConnectionsProvider) createConnection(params map[string]any) (*plugin.Response, error) { @@ -717,11 +712,6 @@ func parseTagList(raw []any) map[string]string { return tags } -func getString(params map[string]any, key string) string { - s, _ := params[key].(string) - return s -} - func init() { plugin.DefaultRegistry.Register("codeconnections", func() plugin.ServicePlugin { return &CodeConnectionsProvider{} diff --git a/internal/services/codedeploy/provider.go b/internal/services/codedeploy/provider.go index c224ca7..98b27de 100644 --- a/internal/services/codedeploy/provider.go +++ b/internal/services/codedeploy/provider.go @@ -142,10 +142,6 @@ func (p *Provider) ListResources(_ context.Context) ([]plugin.Resource, error) { return res, nil } -func (p *Provider) GetMetrics(_ context.Context) (*plugin.ServiceMetrics, error) { - return &plugin.ServiceMetrics{}, nil -} - // ---- Application handlers ---- func (p *Provider) createApplication(params map[string]any) (*plugin.Response, error) { diff --git a/internal/services/codepipeline/provider.go b/internal/services/codepipeline/provider.go index 8b8c174..3ebabb7 100644 --- a/internal/services/codepipeline/provider.go +++ b/internal/services/codepipeline/provider.go @@ -205,10 +205,6 @@ func (p *Provider) ListResources(_ context.Context) ([]plugin.Resource, error) { return res, nil } -func (p *Provider) GetMetrics(_ context.Context) (*plugin.ServiceMetrics, error) { - return &plugin.ServiceMetrics{}, nil -} - // ---- Pipeline handlers ---- func (p *Provider) createPipeline(params map[string]any) (*plugin.Response, error) { diff --git a/internal/services/cognitoidentity/provider.go b/internal/services/cognitoidentity/provider.go index 66aa744..e4fdccd 100644 --- a/internal/services/cognitoidentity/provider.go +++ b/internal/services/cognitoidentity/provider.go @@ -13,10 +13,11 @@ import ( "time" "github.com/skyoo2003/devcloud/internal/plugin" + "github.com/skyoo2003/devcloud/internal/shared" ) const defaultAccountID = plugin.DefaultAccountID -const defaultRegion = "us-east-1" +const defaultRegion = shared.DefaultRegion // Provider implements the CognitoIdentityService using JSON 1.1 protocol. type Provider struct { @@ -114,10 +115,6 @@ func (p *Provider) ListResources(_ context.Context) ([]plugin.Resource, error) { return out, nil } -func (p *Provider) GetMetrics(_ context.Context) (*plugin.ServiceMetrics, error) { - return &plugin.ServiceMetrics{}, nil -} - // --- helpers --- func arnForPool(accountID, region, id string) string { diff --git a/internal/services/cognitoidentityprovider/provider.go b/internal/services/cognitoidentityprovider/provider.go index 11cc8e9..6ffe7b8 100644 --- a/internal/services/cognitoidentityprovider/provider.go +++ b/internal/services/cognitoidentityprovider/provider.go @@ -19,7 +19,7 @@ import ( ) const defaultAccountID = plugin.DefaultAccountID -const defaultRegion = "us-east-1" +const defaultRegion = shared.DefaultRegion // Provider implements the CognitoIdentityProviderService service. type Provider struct { @@ -199,10 +199,6 @@ func (p *Provider) ListResources(_ context.Context) ([]plugin.Resource, error) { return out, nil } -func (p *Provider) GetMetrics(_ context.Context) (*plugin.ServiceMetrics, error) { - return &plugin.ServiceMetrics{}, nil -} - // --- helpers --- func jsonResp(status int, v any) (*plugin.Response, error) { diff --git a/internal/services/configservice/provider.go b/internal/services/configservice/provider.go index 55d4cdb..8d5f043 100644 --- a/internal/services/configservice/provider.go +++ b/internal/services/configservice/provider.go @@ -203,21 +203,8 @@ func (p *Provider) ListResources(_ context.Context) ([]plugin.Resource, error) { return res, nil } -func (p *Provider) GetMetrics(_ context.Context) (*plugin.ServiceMetrics, error) { - return &plugin.ServiceMetrics{}, nil -} - // ---- helpers ---- -func strParam(params map[string]any, key string) string { - if v, ok := params[key]; ok { - if s, ok := v.(string); ok { - return s - } - } - return "" -} - func intParam(params map[string]any, key string, def int) int { if v, ok := params[key]; ok { switch n := v.(type) { @@ -296,8 +283,8 @@ func tagsFromParams(params map[string]any, key string) map[string]string { out := make(map[string]string, len(arr)) for _, item := range arr { if m, ok := item.(map[string]any); ok { - k := strParam(m, "Key") - val := strParam(m, "Value") + k := shared.StrParam(m, "Key") + val := shared.StrParam(m, "Value") if k != "" { out[k] = val } @@ -341,7 +328,7 @@ func (p *Provider) putConfigRule(params map[string]any) (*plugin.Response, error if input == nil { return shared.JSONError("InvalidParameterValueException", "ConfigRule is required", http.StatusBadRequest), nil } - name := strParam(input, "ConfigRuleName") + name := shared.StrParam(input, "ConfigRuleName") if name == "" { return shared.JSONError("InvalidParameterValueException", "ConfigRuleName is required", http.StatusBadRequest), nil } @@ -350,7 +337,7 @@ func (p *Provider) putConfigRule(params map[string]any) (*plugin.Response, error ARN: buildConfigRuleARN(name), Source: marshalParam(input, "Source"), Scope: marshalParam(input, "Scope"), - InputParams: strParam(input, "InputParameters"), + InputParams: shared.StrParam(input, "InputParameters"), State: "ACTIVE", CreatedAt: now(), } @@ -374,7 +361,7 @@ func (p *Provider) describeConfigRules(params map[string]any) (*plugin.Response, } func (p *Provider) deleteConfigRule(params map[string]any) (*plugin.Response, error) { - name := strParam(params, "ConfigRuleName") + name := shared.StrParam(params, "ConfigRuleName") if name == "" { return shared.JSONError("InvalidParameterValueException", "ConfigRuleName is required", http.StatusBadRequest), nil } @@ -426,22 +413,22 @@ func (p *Provider) putConfigurationRecorder(params map[string]any) (*plugin.Resp if input == nil { return shared.JSONError("InvalidParameterValueException", "ConfigurationRecorder is required", http.StatusBadRequest), nil } - name := strParam(input, "name") + name := shared.StrParam(input, "name") if name == "" { - name = strParam(input, "Name") + name = shared.StrParam(input, "Name") } if name == "" { name = "default" } r := &ConfigurationRecorder{ Name: name, - RoleARN: strParam(input, "roleARN"), + RoleARN: shared.StrParam(input, "roleARN"), RecordingGroup: marshalParam(input, "recordingGroup"), RecordingMode: marshalParam(input, "recordingMode"), Status: "STOPPED", } if r.RoleARN == "" { - r.RoleARN = strParam(input, "RoleARN") + r.RoleARN = shared.StrParam(input, "RoleARN") } if err := p.store.PutConfigurationRecorder(r); err != nil { return nil, err @@ -479,7 +466,7 @@ func (p *Provider) describeConfigurationRecorderStatus(params map[string]any) (* } func (p *Provider) startConfigurationRecorder(params map[string]any) (*plugin.Response, error) { - name := strParam(params, "ConfigurationRecorderName") + name := shared.StrParam(params, "ConfigurationRecorderName") if name == "" { return shared.JSONError("InvalidParameterValueException", "ConfigurationRecorderName is required", http.StatusBadRequest), nil } @@ -493,7 +480,7 @@ func (p *Provider) startConfigurationRecorder(params map[string]any) (*plugin.Re } func (p *Provider) stopConfigurationRecorder(params map[string]any) (*plugin.Response, error) { - name := strParam(params, "ConfigurationRecorderName") + name := shared.StrParam(params, "ConfigurationRecorderName") if name == "" { return shared.JSONError("InvalidParameterValueException", "ConfigurationRecorderName is required", http.StatusBadRequest), nil } @@ -507,7 +494,7 @@ func (p *Provider) stopConfigurationRecorder(params map[string]any) (*plugin.Res } func (p *Provider) deleteConfigurationRecorder(params map[string]any) (*plugin.Response, error) { - name := strParam(params, "ConfigurationRecorderName") + name := shared.StrParam(params, "ConfigurationRecorderName") if name == "" { return shared.JSONError("InvalidParameterValueException", "ConfigurationRecorderName is required", http.StatusBadRequest), nil } @@ -540,22 +527,22 @@ func (p *Provider) putDeliveryChannel(params map[string]any) (*plugin.Response, if input == nil { return shared.JSONError("InvalidParameterValueException", "DeliveryChannel is required", http.StatusBadRequest), nil } - name := strParam(input, "name") + name := shared.StrParam(input, "name") if name == "" { - name = strParam(input, "Name") + name = shared.StrParam(input, "Name") } if name == "" { name = "default" } d := &DeliveryChannel{ Name: name, - S3Bucket: strParam(input, "s3BucketName"), - S3Prefix: strParam(input, "s3KeyPrefix"), - SNSTopic: strParam(input, "snsTopicARN"), + S3Bucket: shared.StrParam(input, "s3BucketName"), + S3Prefix: shared.StrParam(input, "s3KeyPrefix"), + SNSTopic: shared.StrParam(input, "snsTopicARN"), Frequency: "TwentyFour_Hours", } if freq, ok := input["configSnapshotDeliveryProperties"].(map[string]any); ok { - if f := strParam(freq, "deliveryFrequency"); f != "" { + if f := shared.StrParam(freq, "deliveryFrequency"); f != "" { d.Frequency = f } } @@ -600,7 +587,7 @@ func (p *Provider) describeDeliveryChannelStatus(params map[string]any) (*plugin } func (p *Provider) deleteDeliveryChannel(params map[string]any) (*plugin.Response, error) { - name := strParam(params, "DeliveryChannelName") + name := shared.StrParam(params, "DeliveryChannelName") if name == "" { return shared.JSONError("InvalidParameterValueException", "DeliveryChannelName is required", http.StatusBadRequest), nil } @@ -629,15 +616,15 @@ func deliveryChannelToMap(d *DeliveryChannel) map[string]any { // ---- ConformancePack handlers ---- func (p *Provider) putConformancePack(params map[string]any) (*plugin.Response, error) { - name := strParam(params, "ConformancePackName") + name := shared.StrParam(params, "ConformancePackName") if name == "" { return shared.JSONError("InvalidParameterValueException", "ConformancePackName is required", http.StatusBadRequest), nil } c := &ConformancePack{ Name: name, ARN: buildConformancePackARN(name), - TemplateBody: strParam(params, "TemplateBody"), - DeliveryBucket: strParam(params, "DeliveryS3Bucket"), + TemplateBody: shared.StrParam(params, "TemplateBody"), + DeliveryBucket: shared.StrParam(params, "DeliveryS3Bucket"), Status: "CREATE_COMPLETE", CreatedAt: now(), } @@ -679,7 +666,7 @@ func (p *Provider) describeConformancePackStatus(params map[string]any) (*plugin } func (p *Provider) describeConformancePackCompliance(params map[string]any) (*plugin.Response, error) { - name := strParam(params, "ConformancePackName") + name := shared.StrParam(params, "ConformancePackName") _ = name return shared.JSONResponse(http.StatusOK, map[string]any{ "ConformancePackName": name, @@ -688,7 +675,7 @@ func (p *Provider) describeConformancePackCompliance(params map[string]any) (*pl } func (p *Provider) deleteConformancePack(params map[string]any) (*plugin.Response, error) { - name := strParam(params, "ConformancePackName") + name := shared.StrParam(params, "ConformancePackName") if name == "" { return shared.JSONError("InvalidParameterValueException", "ConformancePackName is required", http.StatusBadRequest), nil } @@ -715,7 +702,7 @@ func conformancePackToMap(c *ConformancePack) map[string]any { // ---- ConfigurationAggregator handlers ---- func (p *Provider) putConfigurationAggregator(params map[string]any) (*plugin.Response, error) { - name := strParam(params, "ConfigurationAggregatorName") + name := shared.StrParam(params, "ConfigurationAggregatorName") if name == "" { return shared.JSONError("InvalidParameterValueException", "ConfigurationAggregatorName is required", http.StatusBadRequest), nil } @@ -748,7 +735,7 @@ func (p *Provider) describeConfigurationAggregators(params map[string]any) (*plu } func (p *Provider) describeConfigurationAggregatorSourcesStatus(params map[string]any) (*plugin.Response, error) { - name := strParam(params, "ConfigurationAggregatorName") + name := shared.StrParam(params, "ConfigurationAggregatorName") if name == "" { return shared.JSONError("InvalidParameterValueException", "ConfigurationAggregatorName is required", http.StatusBadRequest), nil } @@ -763,7 +750,7 @@ func (p *Provider) describeConfigurationAggregatorSourcesStatus(params map[strin } func (p *Provider) deleteConfigurationAggregator(params map[string]any) (*plugin.Response, error) { - name := strParam(params, "ConfigurationAggregatorName") + name := shared.StrParam(params, "ConfigurationAggregatorName") if name == "" { return shared.JSONError("InvalidParameterValueException", "ConfigurationAggregatorName is required", http.StatusBadRequest), nil } @@ -796,7 +783,7 @@ func (p *Provider) putStoredQuery(params map[string]any) (*plugin.Response, erro if input == nil { return shared.JSONError("InvalidParameterValueException", "StoredQuery is required", http.StatusBadRequest), nil } - name := strParam(input, "QueryName") + name := shared.StrParam(input, "QueryName") if name == "" { return shared.JSONError("InvalidParameterValueException", "QueryName is required", http.StatusBadRequest), nil } @@ -804,8 +791,8 @@ func (p *Provider) putStoredQuery(params map[string]any) (*plugin.Response, erro Name: name, ID: shared.GenerateUUID(), ARN: buildStoredQueryARN(name), - Expression: strParam(input, "Expression"), - Description: strParam(input, "Description"), + Expression: shared.StrParam(input, "Expression"), + Description: shared.StrParam(input, "Description"), } // Keep existing ID if present existing, _ := p.store.GetStoredQuery(name) @@ -820,7 +807,7 @@ func (p *Provider) putStoredQuery(params map[string]any) (*plugin.Response, erro } func (p *Provider) getStoredQuery(params map[string]any) (*plugin.Response, error) { - name := strParam(params, "QueryName") + name := shared.StrParam(params, "QueryName") if name == "" { return shared.JSONError("InvalidParameterValueException", "QueryName is required", http.StatusBadRequest), nil } @@ -851,7 +838,7 @@ func (p *Provider) listStoredQueries(_ map[string]any) (*plugin.Response, error) } func (p *Provider) deleteStoredQuery(params map[string]any) (*plugin.Response, error) { - name := strParam(params, "QueryName") + name := shared.StrParam(params, "QueryName") if name == "" { return shared.JSONError("InvalidParameterValueException", "QueryName is required", http.StatusBadRequest), nil } @@ -904,7 +891,7 @@ func (p *Provider) describeRetentionConfigurations(_ map[string]any) (*plugin.Re } func (p *Provider) deleteRetentionConfiguration(params map[string]any) (*plugin.Response, error) { - name := strParam(params, "RetentionConfigurationName") + name := shared.StrParam(params, "RetentionConfigurationName") if name == "" { name = "default" } @@ -928,8 +915,8 @@ func retentionConfigToMap(r *RetentionConfig) map[string]any { // ---- AggregationAuthorization handlers ---- func (p *Provider) putAggregationAuthorization(params map[string]any) (*plugin.Response, error) { - account := strParam(params, "AuthorizedAccountId") - region := strParam(params, "AuthorizedAwsRegion") + account := shared.StrParam(params, "AuthorizedAccountId") + region := shared.StrParam(params, "AuthorizedAwsRegion") if account == "" || region == "" { return shared.JSONError("InvalidParameterValueException", "AuthorizedAccountId and AuthorizedAwsRegion are required", http.StatusBadRequest), nil } @@ -960,8 +947,8 @@ func (p *Provider) describeAggregationAuthorizations(_ map[string]any) (*plugin. } func (p *Provider) deleteAggregationAuthorization(params map[string]any) (*plugin.Response, error) { - account := strParam(params, "AuthorizedAccountId") - region := strParam(params, "AuthorizedAwsRegion") + account := shared.StrParam(params, "AuthorizedAccountId") + region := shared.StrParam(params, "AuthorizedAwsRegion") if account == "" || region == "" { return shared.JSONError("InvalidParameterValueException", "AuthorizedAccountId and AuthorizedAwsRegion are required", http.StatusBadRequest), nil } @@ -992,14 +979,14 @@ func (p *Provider) putRemediationConfigurations(params map[string]any) (*plugin. if !ok { continue } - ruleName := strParam(m, "ConfigRuleName") + ruleName := shared.StrParam(m, "ConfigRuleName") if ruleName == "" { continue } r := &RemediationConfig{ ConfigRuleName: ruleName, - TargetType: strParam(m, "TargetType"), - TargetID: strParam(m, "TargetId"), + TargetType: shared.StrParam(m, "TargetType"), + TargetID: shared.StrParam(m, "TargetId"), Parameters: marshalParam(m, "Parameters"), } if r.TargetType == "" { @@ -1029,7 +1016,7 @@ func (p *Provider) describeRemediationConfigurations(params map[string]any) (*pl } func (p *Provider) deleteRemediationConfiguration(params map[string]any) (*plugin.Response, error) { - name := strParam(params, "ConfigRuleName") + name := shared.StrParam(params, "ConfigRuleName") if name == "" { return shared.JSONError("InvalidParameterValueException", "ConfigRuleName is required", http.StatusBadRequest), nil } @@ -1058,7 +1045,7 @@ func remediationConfigToMap(r *RemediationConfig) map[string]any { // ---- Tags handlers ---- func (p *Provider) tagResource(params map[string]any) (*plugin.Response, error) { - arn := strParam(params, "ResourceArn") + arn := shared.StrParam(params, "ResourceArn") if arn == "" { return shared.JSONError("InvalidParameterValueException", "ResourceArn is required", http.StatusBadRequest), nil } @@ -1070,7 +1057,7 @@ func (p *Provider) tagResource(params map[string]any) (*plugin.Response, error) } func (p *Provider) untagResource(params map[string]any) (*plugin.Response, error) { - arn := strParam(params, "ResourceArn") + arn := shared.StrParam(params, "ResourceArn") if arn == "" { return shared.JSONError("InvalidParameterValueException", "ResourceArn is required", http.StatusBadRequest), nil } @@ -1082,7 +1069,7 @@ func (p *Provider) untagResource(params map[string]any) (*plugin.Response, error } func (p *Provider) listTagsForResource(params map[string]any) (*plugin.Response, error) { - arn := strParam(params, "ResourceArn") + arn := shared.StrParam(params, "ResourceArn") if arn == "" { return shared.JSONError("InvalidParameterValueException", "ResourceArn is required", http.StatusBadRequest), nil } diff --git a/internal/services/configservice/store.go b/internal/services/configservice/store.go index a41dfd4..516460c 100644 --- a/internal/services/configservice/store.go +++ b/internal/services/configservice/store.go @@ -205,7 +205,7 @@ func (s *Store) PutConfigRule(r *ConfigRule) error { return err } -func scanConfigRule(row shared.Scanner) (*ConfigRule, error) { +func scanConfigRule(row sqlite.Scanner) (*ConfigRule, error) { r := &ConfigRule{} err := row.Scan(&r.Name, &r.ARN, &r.Source, &r.Scope, &r.InputParams, &r.State, &r.CreatedAt) if err == sql.ErrNoRows { @@ -274,7 +274,7 @@ func (s *Store) PutConfigurationRecorder(r *ConfigurationRecorder) error { return err } -func scanRecorder(row shared.Scanner) (*ConfigurationRecorder, error) { +func scanRecorder(row sqlite.Scanner) (*ConfigurationRecorder, error) { r := &ConfigurationRecorder{} err := row.Scan(&r.Name, &r.RoleARN, &r.RecordingGroup, &r.RecordingMode, &r.Status) if err == sql.ErrNoRows { @@ -355,7 +355,7 @@ func (s *Store) PutDeliveryChannel(d *DeliveryChannel) error { return err } -func scanDeliveryChannel(row shared.Scanner) (*DeliveryChannel, error) { +func scanDeliveryChannel(row sqlite.Scanner) (*DeliveryChannel, error) { d := &DeliveryChannel{} err := row.Scan(&d.Name, &d.S3Bucket, &d.S3Prefix, &d.SNSTopic, &d.Frequency) if err == sql.ErrNoRows { @@ -418,7 +418,7 @@ func (s *Store) PutConformancePack(c *ConformancePack) error { return err } -func scanConformancePack(row shared.Scanner) (*ConformancePack, error) { +func scanConformancePack(row sqlite.Scanner) (*ConformancePack, error) { c := &ConformancePack{} err := row.Scan(&c.Name, &c.ARN, &c.TemplateBody, &c.DeliveryBucket, &c.Status, &c.CreatedAt) if err == sql.ErrNoRows { @@ -480,7 +480,7 @@ func (s *Store) PutAggregator(a *Aggregator) error { return err } -func scanAggregator(row shared.Scanner) (*Aggregator, error) { +func scanAggregator(row sqlite.Scanner) (*Aggregator, error) { a := &Aggregator{} err := row.Scan(&a.Name, &a.ARN, &a.AccountSources, &a.OrgSource, &a.CreatedAt) if err == sql.ErrNoRows { @@ -548,7 +548,7 @@ func (s *Store) PutStoredQuery(q *StoredQuery) error { return err } -func scanStoredQuery(row shared.Scanner) (*StoredQuery, error) { +func scanStoredQuery(row sqlite.Scanner) (*StoredQuery, error) { q := &StoredQuery{} err := row.Scan(&q.Name, &q.ID, &q.ARN, &q.Expression, &q.Description) if err == sql.ErrNoRows { @@ -600,7 +600,7 @@ func (s *Store) PutRetentionConfig(r *RetentionConfig) error { return err } -func scanRetentionConfig(row shared.Scanner) (*RetentionConfig, error) { +func scanRetentionConfig(row sqlite.Scanner) (*RetentionConfig, error) { r := &RetentionConfig{} err := row.Scan(&r.Name, &r.RetentionDays) if err == sql.ErrNoRows { @@ -654,7 +654,7 @@ func (s *Store) PutAggregationAuthorization(a *AggregationAuthorization) error { return err } -func scanAggAuth(row shared.Scanner) (*AggregationAuthorization, error) { +func scanAggAuth(row sqlite.Scanner) (*AggregationAuthorization, error) { a := &AggregationAuthorization{} err := row.Scan(&a.ARN, &a.AuthorizedAccount, &a.AuthorizedRegion, &a.CreatedAt) if err == sql.ErrNoRows { @@ -705,7 +705,7 @@ func (s *Store) PutRemediationConfig(r *RemediationConfig) error { return err } -func scanRemediationConfig(row shared.Scanner) (*RemediationConfig, error) { +func scanRemediationConfig(row sqlite.Scanner) (*RemediationConfig, error) { r := &RemediationConfig{} err := row.Scan(&r.ConfigRuleName, &r.TargetType, &r.TargetID, &r.Parameters, &r.Automatic) if err == sql.ErrNoRows { diff --git a/internal/services/costexplorer/provider.go b/internal/services/costexplorer/provider.go index 6addebe..0ae3edc 100644 --- a/internal/services/costexplorer/provider.go +++ b/internal/services/costexplorer/provider.go @@ -189,10 +189,6 @@ func (p *Provider) ListResources(_ context.Context) ([]plugin.Resource, error) { return res, nil } -func (p *Provider) GetMetrics(_ context.Context) (*plugin.ServiceMetrics, error) { - return &plugin.ServiceMetrics{}, nil -} - // ----- CostCategoryDefinition ----- func (p *Provider) createCostCategoryDefinition(params map[string]any) (*plugin.Response, error) { diff --git a/internal/services/dms/provider.go b/internal/services/dms/provider.go index f8d73ed..33c4f7f 100644 --- a/internal/services/dms/provider.go +++ b/internal/services/dms/provider.go @@ -105,7 +105,7 @@ func (p *DMSProvider) HandleRequest(_ context.Context, op string, req *http.Requ return p.reloadTables(params) case "DescribeTableStatistics": return shared.JSONResponse(http.StatusOK, map[string]any{ - "ReplicationTaskArn": getString(params, "ReplicationTaskArn"), + "ReplicationTaskArn": shared.StrParam(params, "ReplicationTaskArn"), "TableStatistics": []any{}, }) case "DescribeReplicationTaskAssessmentResults": @@ -200,11 +200,6 @@ func (p *DMSProvider) ListResources(_ context.Context) ([]plugin.Resource, error return out, nil } -// GetMetrics returns empty metrics. -func (p *DMSProvider) GetMetrics(_ context.Context) (*plugin.ServiceMetrics, error) { - return &plugin.ServiceMetrics{}, nil -} - // --- Replication Instance handlers --- func (p *DMSProvider) createReplicationInstance(params map[string]any) (*plugin.Response, error) { @@ -780,11 +775,6 @@ func parseDMSTags(raw []any) map[string]string { return tags } -func getString(params map[string]any, key string) string { - s, _ := params[key].(string) - return s -} - func init() { plugin.DefaultRegistry.Register("dms", func() plugin.ServicePlugin { return &DMSProvider{} diff --git a/internal/services/docdb/provider.go b/internal/services/docdb/provider.go index 9e5957c..461054c 100644 --- a/internal/services/docdb/provider.go +++ b/internal/services/docdb/provider.go @@ -206,10 +206,6 @@ func (p *Provider) ListResources(ctx context.Context) ([]plugin.Resource, error) return out, nil } -func (p *Provider) GetMetrics(_ context.Context) (*plugin.ServiceMetrics, error) { - return &plugin.ServiceMetrics{}, nil -} - // --- helpers --- func dbError(code, msg string, status int) *plugin.Response { diff --git a/internal/services/dynamodb/provider.go b/internal/services/dynamodb/provider.go index d6d18cc..0debf61 100644 --- a/internal/services/dynamodb/provider.go +++ b/internal/services/dynamodb/provider.go @@ -119,14 +119,6 @@ func (p *DynamoDBProvider) ListResources(_ context.Context) ([]plugin.Resource, return resources, nil } -// GetMetrics returns basic metrics for the DynamoDB provider. -func (p *DynamoDBProvider) GetMetrics(_ context.Context) (*plugin.ServiceMetrics, error) { - names := p.store.ListTables() - return &plugin.ServiceMetrics{ - ResourceCount: len(names), - }, nil -} - // --- request/response structs --- type keySchemaElement struct { diff --git a/internal/services/dynamodbstreams/provider.go b/internal/services/dynamodbstreams/provider.go index 4d626c8..a962ece 100644 --- a/internal/services/dynamodbstreams/provider.go +++ b/internal/services/dynamodbstreams/provider.go @@ -136,14 +136,6 @@ func (p *Provider) ListResources(_ context.Context) ([]plugin.Resource, error) { return res, nil } -func (p *Provider) GetMetrics(_ context.Context) (*plugin.ServiceMetrics, error) { - streams, err := p.store.ListStreams("") - if err != nil { - return &plugin.ServiceMetrics{}, nil - } - return &plugin.ServiceMetrics{ResourceCount: len(streams)}, nil -} - // ---- handlers ---- func (p *Provider) listStreams(params map[string]any) (*plugin.Response, error) { diff --git a/internal/services/ebs/provider.go b/internal/services/ebs/provider.go index 6de39f7..a5dd5df 100644 --- a/internal/services/ebs/provider.go +++ b/internal/services/ebs/provider.go @@ -95,10 +95,6 @@ func (p *Provider) ListResources(_ context.Context) ([]plugin.Resource, error) { return res, nil } -func (p *Provider) GetMetrics(_ context.Context) (*plugin.ServiceMetrics, error) { - return &plugin.ServiceMetrics{}, nil -} - // --- Operations --- func (p *Provider) startSnapshot(req *http.Request) (*plugin.Response, error) { diff --git a/internal/services/ec2/provider.go b/internal/services/ec2/provider.go index ca17a9c..81f4dfd 100644 --- a/internal/services/ec2/provider.go +++ b/internal/services/ec2/provider.go @@ -15,6 +15,7 @@ import ( "strings" "github.com/skyoo2003/devcloud/internal/plugin" + "github.com/skyoo2003/devcloud/internal/shared" ) const ( @@ -215,10 +216,6 @@ func (p *Provider) ListResources(ctx context.Context) ([]plugin.Resource, error) return out, nil } -func (p *Provider) GetMetrics(_ context.Context) (*plugin.ServiceMetrics, error) { - return &plugin.ServiceMetrics{}, nil -} - // --- operation handlers --- func (p *Provider) handleRunInstances(form url.Values) (*plugin.Response, error) { @@ -889,7 +886,7 @@ func (p *Provider) handleDescribeAvailabilityZones(_ url.Values) (*plugin.Respon XMLName xml.Name `xml:"DescribeAvailabilityZonesResponse"` AvailabilityZoneInfo []azXML `xml:"availabilityZoneInfo>item"` } - const region = "us-east-1" + const region = shared.DefaultRegion suffixes := []string{"a", "b", "c", "d", "f"} items := make([]azXML, 0, len(suffixes)) for i, sfx := range suffixes { diff --git a/internal/services/ecr/provider.go b/internal/services/ecr/provider.go index 3b00cde..41c18f4 100644 --- a/internal/services/ecr/provider.go +++ b/internal/services/ecr/provider.go @@ -15,6 +15,7 @@ import ( "time" "github.com/skyoo2003/devcloud/internal/plugin" + "github.com/skyoo2003/devcloud/internal/shared" ) const defaultAccountID = plugin.DefaultAccountID @@ -145,14 +146,10 @@ func (p *Provider) ListResources(ctx context.Context) ([]plugin.Resource, error) return out, nil } -func (p *Provider) GetMetrics(_ context.Context) (*plugin.ServiceMetrics, error) { - return &plugin.ServiceMetrics{}, nil -} - // --- operation handlers --- func (p *Provider) handleCreateRepository(params map[string]any) (*plugin.Response, error) { - name := strParam(params, "repositoryName") + name := shared.StrParam(params, "repositoryName") if name == "" { return ecrError("InvalidParameterException", "repositoryName is required", http.StatusBadRequest), nil } @@ -167,7 +164,7 @@ func (p *Provider) handleCreateRepository(params map[string]any) (*plugin.Respon } func (p *Provider) handleDeleteRepository(params map[string]any) (*plugin.Response, error) { - name := strParam(params, "repositoryName") + name := shared.StrParam(params, "repositoryName") if name == "" { return ecrError("InvalidParameterException", "repositoryName is required", http.StatusBadRequest), nil } @@ -202,9 +199,9 @@ func (p *Provider) handleDescribeRepositories(params map[string]any) (*plugin.Re } func (p *Provider) handlePutImage(params map[string]any) (*plugin.Response, error) { - repoName := strParam(params, "repositoryName") - manifest := strParam(params, "imageManifest") - tag := strParam(params, "imageTag") + repoName := shared.StrParam(params, "repositoryName") + manifest := shared.StrParam(params, "imageManifest") + tag := shared.StrParam(params, "imageTag") if repoName == "" || manifest == "" { return ecrError("InvalidParameterException", "repositoryName and imageManifest are required", http.StatusBadRequest), nil } @@ -216,7 +213,7 @@ func (p *Provider) handlePutImage(params map[string]any) (*plugin.Response, erro } func (p *Provider) handleBatchGetImage(params map[string]any) (*plugin.Response, error) { - repoName := strParam(params, "repositoryName") + repoName := shared.StrParam(params, "repositoryName") imageIDs := imageIDsParam(params, "imageIds") if repoName == "" { return ecrError("InvalidParameterException", "repositoryName is required", http.StatusBadRequest), nil @@ -233,7 +230,7 @@ func (p *Provider) handleBatchGetImage(params map[string]any) (*plugin.Response, } func (p *Provider) handleBatchDeleteImage(params map[string]any) (*plugin.Response, error) { - repoName := strParam(params, "repositoryName") + repoName := shared.StrParam(params, "repositoryName") imageIDs := imageIDsParam(params, "imageIds") if repoName == "" { return ecrError("InvalidParameterException", "repositoryName is required", http.StatusBadRequest), nil @@ -250,7 +247,7 @@ func (p *Provider) handleBatchDeleteImage(params map[string]any) (*plugin.Respon } func (p *Provider) handleDescribeImages(params map[string]any) (*plugin.Response, error) { - repoName := strParam(params, "repositoryName") + repoName := shared.StrParam(params, "repositoryName") if repoName == "" { return ecrError("InvalidParameterException", "repositoryName is required", http.StatusBadRequest), nil } @@ -266,7 +263,7 @@ func (p *Provider) handleDescribeImages(params map[string]any) (*plugin.Response } func (p *Provider) handleListImages(params map[string]any) (*plugin.Response, error) { - repoName := strParam(params, "repositoryName") + repoName := shared.StrParam(params, "repositoryName") if repoName == "" { return ecrError("InvalidParameterException", "repositoryName is required", http.StatusBadRequest), nil } @@ -300,8 +297,8 @@ func (p *Provider) handleGetAuthorizationToken(_ map[string]any) (*plugin.Respon } func (p *Provider) handleSetRepositoryPolicy(params map[string]any) (*plugin.Response, error) { - repoName := strParam(params, "repositoryName") - policyText := strParam(params, "policyText") + repoName := shared.StrParam(params, "repositoryName") + policyText := shared.StrParam(params, "policyText") if repoName == "" || policyText == "" { return ecrError("InvalidParameterException", "repositoryName and policyText are required", http.StatusBadRequest), nil } @@ -319,7 +316,7 @@ func (p *Provider) handleSetRepositoryPolicy(params map[string]any) (*plugin.Res } func (p *Provider) handleGetRepositoryPolicy(params map[string]any) (*plugin.Response, error) { - repoName := strParam(params, "repositoryName") + repoName := shared.StrParam(params, "repositoryName") if repoName == "" { return ecrError("InvalidParameterException", "repositoryName is required", http.StatusBadRequest), nil } @@ -343,7 +340,7 @@ func (p *Provider) handleGetRepositoryPolicy(params map[string]any) (*plugin.Res // --- layer operation handlers --- func (p *Provider) handleInitiateLayerUpload(params map[string]any) (*plugin.Response, error) { - repoName := strParam(params, "repositoryName") + repoName := shared.StrParam(params, "repositoryName") if repoName == "" { return ecrError("InvalidParameterException", "repositoryName is required", http.StatusBadRequest), nil } @@ -358,8 +355,8 @@ func (p *Provider) handleInitiateLayerUpload(params map[string]any) (*plugin.Res } func (p *Provider) handleUploadLayerPart(params map[string]any) (*plugin.Response, error) { - repoName := strParam(params, "repositoryName") - uploadID := strParam(params, "uploadId") + repoName := shared.StrParam(params, "repositoryName") + uploadID := shared.StrParam(params, "uploadId") if repoName == "" || uploadID == "" { return ecrError("InvalidParameterException", "repositoryName and uploadId are required", http.StatusBadRequest), nil } @@ -397,8 +394,8 @@ func (p *Provider) handleUploadLayerPart(params map[string]any) (*plugin.Respons } func (p *Provider) handleCompleteLayerUpload(params map[string]any) (*plugin.Response, error) { - repoName := strParam(params, "repositoryName") - uploadID := strParam(params, "uploadId") + repoName := shared.StrParam(params, "repositoryName") + uploadID := shared.StrParam(params, "uploadId") if repoName == "" || uploadID == "" { return ecrError("InvalidParameterException", "repositoryName and uploadId are required", http.StatusBadRequest), nil } @@ -422,7 +419,7 @@ func (p *Provider) handleCompleteLayerUpload(params map[string]any) (*plugin.Res } func (p *Provider) handleBatchCheckLayerAvailability(params map[string]any) (*plugin.Response, error) { - repoName := strParam(params, "repositoryName") + repoName := shared.StrParam(params, "repositoryName") if repoName == "" { return ecrError("InvalidParameterException", "repositoryName is required", http.StatusBadRequest), nil } @@ -439,8 +436,8 @@ func (p *Provider) handleBatchCheckLayerAvailability(params map[string]any) (*pl } func (p *Provider) handleGetDownloadUrlForLayer(params map[string]any) (*plugin.Response, error) { - repoName := strParam(params, "repositoryName") - digest := strParam(params, "layerDigest") + repoName := shared.StrParam(params, "repositoryName") + digest := shared.StrParam(params, "layerDigest") if repoName == "" || digest == "" { return ecrError("InvalidParameterException", "repositoryName and layerDigest are required", http.StatusBadRequest), nil } @@ -462,8 +459,8 @@ func (p *Provider) handleGetDownloadUrlForLayer(params map[string]any) (*plugin. // --- lifecycle policy handlers --- func (p *Provider) handlePutLifecyclePolicy(params map[string]any) (*plugin.Response, error) { - repoName := strParam(params, "repositoryName") - policyText := strParam(params, "lifecyclePolicyText") + repoName := shared.StrParam(params, "repositoryName") + policyText := shared.StrParam(params, "lifecyclePolicyText") if repoName == "" || policyText == "" { return ecrError("InvalidParameterException", "repositoryName and lifecyclePolicyText are required", http.StatusBadRequest), nil } @@ -481,7 +478,7 @@ func (p *Provider) handlePutLifecyclePolicy(params map[string]any) (*plugin.Resp } func (p *Provider) handleGetLifecyclePolicy(params map[string]any) (*plugin.Response, error) { - repoName := strParam(params, "repositoryName") + repoName := shared.StrParam(params, "repositoryName") if repoName == "" { return ecrError("InvalidParameterException", "repositoryName is required", http.StatusBadRequest), nil } @@ -503,7 +500,7 @@ func (p *Provider) handleGetLifecyclePolicy(params map[string]any) (*plugin.Resp } func (p *Provider) handleDeleteLifecyclePolicy(params map[string]any) (*plugin.Response, error) { - repoName := strParam(params, "repositoryName") + repoName := shared.StrParam(params, "repositoryName") if repoName == "" { return ecrError("InvalidParameterException", "repositoryName is required", http.StatusBadRequest), nil } @@ -530,7 +527,7 @@ func (p *Provider) handleDeleteLifecyclePolicy(params map[string]any) (*plugin.R // --- tag handlers --- func (p *Provider) handleTagResource(params map[string]any) (*plugin.Response, error) { - resourceARN := strParam(params, "resourceArn") + resourceARN := shared.StrParam(params, "resourceArn") if resourceARN == "" { return ecrError("InvalidParameterException", "resourceArn is required", http.StatusBadRequest), nil } @@ -542,7 +539,7 @@ func (p *Provider) handleTagResource(params map[string]any) (*plugin.Response, e } func (p *Provider) handleUntagResource(params map[string]any) (*plugin.Response, error) { - resourceARN := strParam(params, "resourceArn") + resourceARN := shared.StrParam(params, "resourceArn") if resourceARN == "" { return ecrError("InvalidParameterException", "resourceArn is required", http.StatusBadRequest), nil } @@ -554,7 +551,7 @@ func (p *Provider) handleUntagResource(params map[string]any) (*plugin.Response, } func (p *Provider) handleListTagsForResource(params map[string]any) (*plugin.Response, error) { - resourceARN := strParam(params, "resourceArn") + resourceARN := shared.StrParam(params, "resourceArn") if resourceARN == "" { return ecrError("InvalidParameterException", "resourceArn is required", http.StatusBadRequest), nil } @@ -572,7 +569,7 @@ func (p *Provider) handleListTagsForResource(params map[string]any) (*plugin.Res // --- image scan handlers --- func (p *Provider) handleStartImageScan(params map[string]any) (*plugin.Response, error) { - repoName := strParam(params, "repositoryName") + repoName := shared.StrParam(params, "repositoryName") if repoName == "" { return ecrError("InvalidParameterException", "repositoryName is required", http.StatusBadRequest), nil } @@ -607,7 +604,7 @@ func (p *Provider) handleStartImageScan(params map[string]any) (*plugin.Response } func (p *Provider) handleDescribeImageScanFindings(params map[string]any) (*plugin.Response, error) { - repoName := strParam(params, "repositoryName") + repoName := shared.StrParam(params, "repositoryName") if repoName == "" { return ecrError("InvalidParameterException", "repositoryName is required", http.StatusBadRequest), nil } @@ -653,7 +650,7 @@ func (p *Provider) handleDescribeImageScanFindings(params map[string]any) (*plug } func (p *Provider) handlePutImageScanningConfiguration(params map[string]any) (*plugin.Response, error) { - repoName := strParam(params, "repositoryName") + repoName := shared.StrParam(params, "repositoryName") if repoName == "" { return ecrError("InvalidParameterException", "repositoryName is required", http.StatusBadRequest), nil } @@ -693,15 +690,6 @@ func ecrJSON(status int, v any) (*plugin.Response, error) { return &plugin.Response{StatusCode: status, ContentType: "application/x-amz-json-1.1", Body: body}, nil } -func strParam(params map[string]any, key string) string { - if v, ok := params[key]; ok { - if s, ok := v.(string); ok { - return s - } - } - return "" -} - func strSliceParam(params map[string]any, key string) []string { v, ok := params[key] if !ok { diff --git a/internal/services/ecr/store.go b/internal/services/ecr/store.go index 45007a4..0dec954 100644 --- a/internal/services/ecr/store.go +++ b/internal/services/ecr/store.go @@ -27,7 +27,7 @@ var ( ErrLayerUploadNotFound = errors.New("layer upload not found") ) -const region = "us-east-1" +const region = shared.DefaultRegion var migrations = []sqlite.Migration{ {Version: 1, SQL: ` diff --git a/internal/services/ecs/provider.go b/internal/services/ecs/provider.go index b797997..5bc575c 100644 --- a/internal/services/ecs/provider.go +++ b/internal/services/ecs/provider.go @@ -13,6 +13,7 @@ import ( "strings" "github.com/skyoo2003/devcloud/internal/plugin" + "github.com/skyoo2003/devcloud/internal/shared" ) const defaultAccountID = plugin.DefaultAccountID @@ -209,14 +210,10 @@ func (p *Provider) ListResources(ctx context.Context) ([]plugin.Resource, error) return out, nil } -func (p *Provider) GetMetrics(_ context.Context) (*plugin.ServiceMetrics, error) { - return &plugin.ServiceMetrics{}, nil -} - // --- operation handlers --- func (p *Provider) handleCreateCluster(params map[string]any) (*plugin.Response, error) { - name := strParam(params, "clusterName") + name := shared.StrParam(params, "clusterName") if name == "" { name = "default" } @@ -275,7 +272,7 @@ func (p *Provider) handleDescribeClusters(params map[string]any) (*plugin.Respon } func (p *Provider) handleDeleteCluster(params map[string]any) (*plugin.Response, error) { - arn := strParam(params, "cluster") + arn := shared.StrParam(params, "cluster") if arn == "" { return ecsError("MissingParameter", "cluster is required", http.StatusBadRequest), nil } @@ -286,7 +283,7 @@ func (p *Provider) handleDeleteCluster(params map[string]any) (*plugin.Response, } func (p *Provider) handleRegisterTaskDefinition(params map[string]any) (*plugin.Response, error) { - family := strParam(params, "family") + family := shared.StrParam(params, "family") if family == "" { return ecsError("MissingParameter", "family is required", http.StatusBadRequest), nil } @@ -304,7 +301,7 @@ func (p *Provider) handleRegisterTaskDefinition(params map[string]any) (*plugin. } func (p *Provider) handleDescribeTaskDefinition(params map[string]any) (*plugin.Response, error) { - arn := strParam(params, "taskDefinition") + arn := shared.StrParam(params, "taskDefinition") if arn == "" { return ecsError("MissingParameter", "taskDefinition is required", http.StatusBadRequest), nil } @@ -327,7 +324,7 @@ func (p *Provider) handleListTaskDefinitions(_ map[string]any) (*plugin.Response } func (p *Provider) handleDeregisterTaskDefinition(params map[string]any) (*plugin.Response, error) { - arn := strParam(params, "taskDefinition") + arn := shared.StrParam(params, "taskDefinition") if arn == "" { return ecsError("MissingParameter", "taskDefinition is required", http.StatusBadRequest), nil } @@ -339,8 +336,8 @@ func (p *Provider) handleDeregisterTaskDefinition(params map[string]any) (*plugi } func (p *Provider) handleRunTask(params map[string]any) (*plugin.Response, error) { - clusterArn := strParam(params, "cluster") - taskDefArn := strParam(params, "taskDefinition") + clusterArn := shared.StrParam(params, "cluster") + taskDefArn := shared.StrParam(params, "taskDefinition") if clusterArn == "" || taskDefArn == "" { return ecsError("MissingParameter", "cluster and taskDefinition are required", http.StatusBadRequest), nil } @@ -352,7 +349,7 @@ func (p *Provider) handleRunTask(params map[string]any) (*plugin.Response, error } func (p *Provider) handleStopTask(params map[string]any) (*plugin.Response, error) { - taskArn := strParam(params, "task") + taskArn := shared.StrParam(params, "task") if taskArn == "" { return ecsError("MissingParameter", "task is required", http.StatusBadRequest), nil } @@ -376,9 +373,9 @@ func (p *Provider) handleDescribeTasks(params map[string]any) (*plugin.Response, } func (p *Provider) handleCreateService(params map[string]any) (*plugin.Response, error) { - name := strParam(params, "serviceName") - clusterArn := strParam(params, "cluster") - taskDefArn := strParam(params, "taskDefinition") + name := shared.StrParam(params, "serviceName") + clusterArn := shared.StrParam(params, "cluster") + taskDefArn := shared.StrParam(params, "taskDefinition") if name == "" || clusterArn == "" || taskDefArn == "" { return ecsError("MissingParameter", "serviceName, cluster, and taskDefinition are required", http.StatusBadRequest), nil } @@ -394,9 +391,9 @@ func (p *Provider) handleCreateService(params map[string]any) (*plugin.Response, } func (p *Provider) handleUpdateService(params map[string]any) (*plugin.Response, error) { - serviceRef := strParam(params, "service") - clusterArn := strParam(params, "cluster") - taskDefArn := strParam(params, "taskDefinition") + serviceRef := shared.StrParam(params, "service") + clusterArn := shared.StrParam(params, "cluster") + taskDefArn := shared.StrParam(params, "taskDefinition") desiredCount := intParam(params, "desiredCount", -1) if serviceRef == "" { return ecsError("MissingParameter", "service is required", http.StatusBadRequest), nil @@ -418,7 +415,7 @@ func (p *Provider) handleUpdateService(params map[string]any) (*plugin.Response, } func (p *Provider) handleDeleteService(params map[string]any) (*plugin.Response, error) { - serviceArn := strParam(params, "service") + serviceArn := shared.StrParam(params, "service") if serviceArn == "" { return ecsError("MissingParameter", "service is required", http.StatusBadRequest), nil } @@ -429,7 +426,7 @@ func (p *Provider) handleDeleteService(params map[string]any) (*plugin.Response, } func (p *Provider) handleListServices(params map[string]any) (*plugin.Response, error) { - clusterArn := strParam(params, "cluster") + clusterArn := shared.StrParam(params, "cluster") if clusterArn == "" { return ecsError("MissingParameter", "cluster is required", http.StatusBadRequest), nil } @@ -446,7 +443,7 @@ func (p *Provider) handleListServices(params map[string]any) (*plugin.Response, // --- Capacity Provider handlers --- func (p *Provider) handleCreateCapacityProvider(params map[string]any) (*plugin.Response, error) { - name := strParam(params, "name") + name := shared.StrParam(params, "name") if name == "" { return ecsError("MissingParameter", "name is required", http.StatusBadRequest), nil } @@ -475,7 +472,7 @@ func (p *Provider) handleDescribeCapacityProviders(params map[string]any) (*plug } func (p *Provider) handleDeleteCapacityProvider(params map[string]any) (*plugin.Response, error) { - nameOrARN := strParam(params, "capacityProvider") + nameOrARN := shared.StrParam(params, "capacityProvider") if nameOrARN == "" { return ecsError("MissingParameter", "capacityProvider is required", http.StatusBadRequest), nil } @@ -486,7 +483,7 @@ func (p *Provider) handleDeleteCapacityProvider(params map[string]any) (*plugin. } func (p *Provider) handlePutClusterCapacityProviders(params map[string]any) (*plugin.Response, error) { - clusterRef := strParam(params, "cluster") + clusterRef := shared.StrParam(params, "cluster") if clusterRef == "" { return ecsError("MissingParameter", "cluster is required", http.StatusBadRequest), nil } @@ -505,7 +502,7 @@ func (p *Provider) handlePutClusterCapacityProviders(params map[string]any) (*pl // --- Container Instance handlers --- func (p *Provider) handleListContainerInstances(params map[string]any) (*plugin.Response, error) { - clusterRef := strParam(params, "cluster") + clusterRef := shared.StrParam(params, "cluster") if clusterRef == "" { return ecsError("MissingParameter", "cluster is required", http.StatusBadRequest), nil } @@ -513,7 +510,7 @@ func (p *Provider) handleListContainerInstances(params map[string]any) (*plugin. if !strings.Contains(clusterRef, ":") { clusterARN = fmt.Sprintf("arn:aws:ecs:%s:%s:cluster/%s", region, defaultAccountID, clusterRef) } - status := strParam(params, "status") + status := shared.StrParam(params, "status") arns, err := p.store.ListContainerInstances(defaultAccountID, clusterARN, status) if err != nil { return nil, err @@ -538,7 +535,7 @@ func (p *Provider) handleDescribeContainerInstances(params map[string]any) (*plu } func (p *Provider) handleDeregisterContainerInstance(params map[string]any) (*plugin.Response, error) { - arn := strParam(params, "containerInstance") + arn := shared.StrParam(params, "containerInstance") if arn == "" { return ecsError("MissingParameter", "containerInstance is required", http.StatusBadRequest), nil } @@ -555,7 +552,7 @@ func (p *Provider) handleDeregisterContainerInstance(params map[string]any) (*pl // --- Attribute handlers --- func (p *Provider) handlePutAttributes(params map[string]any) (*plugin.Response, error) { - clusterRef := strParam(params, "cluster") + clusterRef := shared.StrParam(params, "cluster") clusterARN := clusterRef if clusterRef != "" && !strings.Contains(clusterRef, ":") { clusterARN = fmt.Sprintf("arn:aws:ecs:%s:%s:cluster/%s", region, defaultAccountID, clusterRef) @@ -573,7 +570,7 @@ func (p *Provider) handlePutAttributes(params map[string]any) (*plugin.Response, } func (p *Provider) handleDeleteAttributes(params map[string]any) (*plugin.Response, error) { - clusterRef := strParam(params, "cluster") + clusterRef := shared.StrParam(params, "cluster") clusterARN := clusterRef if clusterRef != "" && !strings.Contains(clusterRef, ":") { clusterARN = fmt.Sprintf("arn:aws:ecs:%s:%s:cluster/%s", region, defaultAccountID, clusterRef) @@ -587,14 +584,14 @@ func (p *Provider) handleDeleteAttributes(params map[string]any) (*plugin.Respon } func (p *Provider) handleListAttributes(params map[string]any) (*plugin.Response, error) { - clusterRef := strParam(params, "cluster") + clusterRef := shared.StrParam(params, "cluster") clusterARN := clusterRef if clusterRef != "" && !strings.Contains(clusterRef, ":") { clusterARN = fmt.Sprintf("arn:aws:ecs:%s:%s:cluster/%s", region, defaultAccountID, clusterRef) } - targetType := strParam(params, "targetType") - attrName := strParam(params, "attributeName") - attrValue := strParam(params, "attributeValue") + targetType := shared.StrParam(params, "targetType") + attrName := shared.StrParam(params, "attributeName") + attrValue := shared.StrParam(params, "attributeValue") attrs, err := p.store.ListAttributes(defaultAccountID, clusterARN, targetType, attrName, attrValue) if err != nil { return nil, err @@ -610,7 +607,7 @@ func (p *Provider) handleListAttributes(params map[string]any) (*plugin.Response func (p *Provider) handleUpdateContainerInstancesState(params map[string]any) (*plugin.Response, error) { arns := strSliceParam(params, "containerInstances") - status := strParam(params, "status") + status := shared.StrParam(params, "status") if status == "" { return ecsError("MissingParameter", "status is required", http.StatusBadRequest), nil } @@ -671,15 +668,6 @@ func ecsJSON(status int, v any) (*plugin.Response, error) { return &plugin.Response{StatusCode: status, ContentType: "application/x-amz-json-1.1", Body: body}, nil } -func strParam(params map[string]any, key string) string { - if v, ok := params[key]; ok { - if s, ok := v.(string); ok { - return s - } - } - return "" -} - func intParam(params map[string]any, key string, defaultVal int) int { if v, ok := params[key]; ok { switch n := v.(type) { @@ -820,7 +808,7 @@ func parseAttributes(raw []any) []ECSAttribute { // --- Cluster extras --- func (p *Provider) handleUpdateCluster(params map[string]any) (*plugin.Response, error) { - clusterRef := strParam(params, "cluster") + clusterRef := shared.StrParam(params, "cluster") if clusterRef == "" { return ecsError("MissingParameter", "cluster is required", http.StatusBadRequest), nil } @@ -869,8 +857,8 @@ func (p *Provider) handleListTaskDefinitionFamilies(_ map[string]any) (*plugin.R // --- Task extras --- func (p *Provider) handleStartTask(params map[string]any) (*plugin.Response, error) { - clusterArn := strParam(params, "cluster") - taskDefArn := strParam(params, "taskDefinition") + clusterArn := shared.StrParam(params, "cluster") + taskDefArn := shared.StrParam(params, "taskDefinition") if clusterArn == "" || taskDefArn == "" { return ecsError("MissingParameter", "cluster and taskDefinition are required", http.StatusBadRequest), nil } @@ -882,7 +870,7 @@ func (p *Provider) handleStartTask(params map[string]any) (*plugin.Response, err } func (p *Provider) handleListTasks(params map[string]any) (*plugin.Response, error) { - clusterArn := strParam(params, "cluster") + clusterArn := shared.StrParam(params, "cluster") // ListTasks for a given cluster just returns all RUNNING tasks in that cluster. tasks, err := p.store.DescribeTasks(defaultAccountID, nil) if err != nil { @@ -901,7 +889,7 @@ func (p *Provider) handleListTasks(params map[string]any) (*plugin.Response, err func (p *Provider) handleDescribeServices(params map[string]any) (*plugin.Response, error) { arns := strSliceParam(params, "services") - clusterRef := strParam(params, "cluster") + clusterRef := shared.StrParam(params, "cluster") // Resolve service names to ARNs within cluster. resolved := make([]string, 0, len(arns)) for _, a := range arns { @@ -931,7 +919,7 @@ func (p *Provider) handleDescribeServices(params map[string]any) (*plugin.Respon // --- Capacity Provider extras --- func (p *Provider) handleUpdateCapacityProvider(params map[string]any) (*plugin.Response, error) { - name := strParam(params, "name") + name := shared.StrParam(params, "name") if name == "" { return ecsError("MissingParameter", "name is required", http.StatusBadRequest), nil } @@ -945,7 +933,7 @@ func (p *Provider) handleUpdateCapacityProvider(params map[string]any) (*plugin. // --- Container Instance extras --- func (p *Provider) handleRegisterContainerInstance(params map[string]any) (*plugin.Response, error) { - clusterRef := strParam(params, "cluster") + clusterRef := shared.StrParam(params, "cluster") if clusterRef == "" { clusterRef = "default" } @@ -957,7 +945,7 @@ func (p *Provider) handleRegisterContainerInstance(params map[string]any) (*plug } else { clusterARN = fmt.Sprintf("arn:aws:ecs:%s:%s:cluster/%s", region, defaultAccountID, clusterRef) } - ec2ID := strParam(params, "instanceIdentityDocument") + ec2ID := shared.StrParam(params, "instanceIdentityDocument") ci, err := p.store.RegisterContainerInstance(defaultAccountID, clusterARN, clusterName, ec2ID) if err != nil { return nil, err @@ -968,11 +956,11 @@ func (p *Provider) handleRegisterContainerInstance(params map[string]any) (*plug // --- Task Sets --- func (p *Provider) handleCreateTaskSet(params map[string]any) (*plugin.Response, error) { - serviceARN := strParam(params, "service") - clusterARN := strParam(params, "cluster") - taskDefARN := strParam(params, "taskDefinition") - externalID := strParam(params, "externalId") - launchType := strParam(params, "launchType") + serviceARN := shared.StrParam(params, "service") + clusterARN := shared.StrParam(params, "cluster") + taskDefARN := shared.StrParam(params, "taskDefinition") + externalID := shared.StrParam(params, "externalId") + launchType := shared.StrParam(params, "launchType") if serviceARN == "" || clusterARN == "" || taskDefARN == "" { return ecsError("MissingParameter", "service, cluster, and taskDefinition are required", http.StatusBadRequest), nil } @@ -984,7 +972,7 @@ func (p *Provider) handleCreateTaskSet(params map[string]any) (*plugin.Response, } func (p *Provider) handleDeleteTaskSet(params map[string]any) (*plugin.Response, error) { - id := strParam(params, "taskSet") + id := shared.StrParam(params, "taskSet") if id == "" { return ecsError("MissingParameter", "taskSet is required", http.StatusBadRequest), nil } @@ -995,7 +983,7 @@ func (p *Provider) handleDeleteTaskSet(params map[string]any) (*plugin.Response, } func (p *Provider) handleUpdateTaskSet(params map[string]any) (*plugin.Response, error) { - id := strParam(params, "taskSet") + id := shared.StrParam(params, "taskSet") if id == "" { return ecsError("MissingParameter", "taskSet is required", http.StatusBadRequest), nil } @@ -1017,7 +1005,7 @@ func (p *Provider) handleUpdateTaskSet(params map[string]any) (*plugin.Response, } func (p *Provider) handleDescribeTaskSets(params map[string]any) (*plugin.Response, error) { - serviceARN := strParam(params, "service") + serviceARN := shared.StrParam(params, "service") sets, err := p.store.ListTaskSets(defaultAccountID, serviceARN) if err != nil { return nil, err @@ -1030,7 +1018,7 @@ func (p *Provider) handleDescribeTaskSets(params map[string]any) (*plugin.Respon } func (p *Provider) handleUpdateServicePrimaryTaskSet(params map[string]any) (*plugin.Response, error) { - id := strParam(params, "primaryTaskSet") + id := shared.StrParam(params, "primaryTaskSet") if id == "" { return ecsError("MissingParameter", "primaryTaskSet is required", http.StatusBadRequest), nil } @@ -1044,17 +1032,17 @@ func (p *Provider) handleUpdateServicePrimaryTaskSet(params map[string]any) (*pl // --- Service Auto-Scaling --- func (p *Provider) handleRegisterScalableTarget(params map[string]any) (*plugin.Response, error) { - serviceARN := strParam(params, "serviceARN") + serviceARN := shared.StrParam(params, "serviceARN") if serviceARN == "" { - serviceARN = strParam(params, "resourceId") + serviceARN = shared.StrParam(params, "resourceId") } - dimension := strParam(params, "scalableDimension") + dimension := shared.StrParam(params, "scalableDimension") if serviceARN == "" || dimension == "" { return ecsError("InvalidParameterException", "serviceARN and scalableDimension are required", http.StatusBadRequest), nil } minCap := intParam(params, "minCapacity", 0) maxCap := intParam(params, "maxCapacity", 10) - roleARN := strParam(params, "roleARN") + roleARN := shared.StrParam(params, "roleARN") if _, err := p.store.RegisterScalableTarget(defaultAccountID, serviceARN, dimension, roleARN, minCap, maxCap); err != nil { return nil, err } @@ -1062,11 +1050,11 @@ func (p *Provider) handleRegisterScalableTarget(params map[string]any) (*plugin. } func (p *Provider) handleDeregisterScalableTarget(params map[string]any) (*plugin.Response, error) { - serviceARN := strParam(params, "serviceARN") + serviceARN := shared.StrParam(params, "serviceARN") if serviceARN == "" { - serviceARN = strParam(params, "resourceId") + serviceARN = shared.StrParam(params, "resourceId") } - dimension := strParam(params, "scalableDimension") + dimension := shared.StrParam(params, "scalableDimension") if err := p.store.DeregisterScalableTarget(defaultAccountID, serviceARN, dimension); err != nil { return ecsError("ObjectNotFoundException", "scalable target not found", http.StatusBadRequest), nil } @@ -1074,11 +1062,11 @@ func (p *Provider) handleDeregisterScalableTarget(params map[string]any) (*plugi } func (p *Provider) handleDescribeScalableTargets(params map[string]any) (*plugin.Response, error) { - serviceARN := strParam(params, "serviceARN") + serviceARN := shared.StrParam(params, "serviceARN") if serviceARN == "" { - serviceARN = strParam(params, "resourceId") + serviceARN = shared.StrParam(params, "resourceId") } - dimension := strParam(params, "scalableDimension") + dimension := shared.StrParam(params, "scalableDimension") targets, err := p.store.ListScalableTargets(defaultAccountID, serviceARN, dimension) if err != nil { return nil, err @@ -1091,16 +1079,16 @@ func (p *Provider) handleDescribeScalableTargets(params map[string]any) (*plugin } func (p *Provider) handlePutScalingPolicy(params map[string]any) (*plugin.Response, error) { - serviceARN := strParam(params, "serviceARN") + serviceARN := shared.StrParam(params, "serviceARN") if serviceARN == "" { - serviceARN = strParam(params, "resourceId") + serviceARN = shared.StrParam(params, "resourceId") } - policyName := strParam(params, "policyName") - dimension := strParam(params, "scalableDimension") + policyName := shared.StrParam(params, "policyName") + dimension := shared.StrParam(params, "scalableDimension") if serviceARN == "" || policyName == "" || dimension == "" { return ecsError("InvalidParameterException", "serviceARN, policyName, and scalableDimension are required", http.StatusBadRequest), nil } - policyType := strParam(params, "policyType") + policyType := shared.StrParam(params, "policyType") if policyType == "" { policyType = "TargetTrackingScaling" } @@ -1120,11 +1108,11 @@ func (p *Provider) handlePutScalingPolicy(params map[string]any) (*plugin.Respon } func (p *Provider) handleDeleteScalingPolicy(params map[string]any) (*plugin.Response, error) { - serviceARN := strParam(params, "serviceARN") + serviceARN := shared.StrParam(params, "serviceARN") if serviceARN == "" { - serviceARN = strParam(params, "resourceId") + serviceARN = shared.StrParam(params, "resourceId") } - policyName := strParam(params, "policyName") + policyName := shared.StrParam(params, "policyName") if err := p.store.DeleteServiceScalingPolicy(defaultAccountID, serviceARN, policyName); err != nil { return ecsError("ObjectNotFoundException", "scaling policy not found", http.StatusBadRequest), nil } @@ -1132,9 +1120,9 @@ func (p *Provider) handleDeleteScalingPolicy(params map[string]any) (*plugin.Res } func (p *Provider) handleDescribeScalingPolicies(params map[string]any) (*plugin.Response, error) { - serviceARN := strParam(params, "serviceARN") + serviceARN := shared.StrParam(params, "serviceARN") if serviceARN == "" { - serviceARN = strParam(params, "resourceId") + serviceARN = shared.StrParam(params, "resourceId") } policies, err := p.store.ListServiceScalingPolicies(defaultAccountID, serviceARN) if err != nil { @@ -1150,7 +1138,7 @@ func (p *Provider) handleDescribeScalingPolicies(params map[string]any) (*plugin // --- Tags --- func (p *Provider) handleTagResource(params map[string]any) (*plugin.Response, error) { - arn := strParam(params, "resourceArn") + arn := shared.StrParam(params, "resourceArn") if arn == "" { return ecsError("MissingParameter", "resourceArn is required", http.StatusBadRequest), nil } @@ -1174,7 +1162,7 @@ func (p *Provider) handleTagResource(params map[string]any) (*plugin.Response, e } func (p *Provider) handleUntagResource(params map[string]any) (*plugin.Response, error) { - arn := strParam(params, "resourceArn") + arn := shared.StrParam(params, "resourceArn") if arn == "" { return ecsError("MissingParameter", "resourceArn is required", http.StatusBadRequest), nil } @@ -1186,7 +1174,7 @@ func (p *Provider) handleUntagResource(params map[string]any) (*plugin.Response, } func (p *Provider) handleListTagsForResource(params map[string]any) (*plugin.Response, error) { - arn := strParam(params, "resourceArn") + arn := shared.StrParam(params, "resourceArn") if arn == "" { return ecsError("MissingParameter", "resourceArn is required", http.StatusBadRequest), nil } @@ -1204,8 +1192,8 @@ func (p *Provider) handleListTagsForResource(params map[string]any) (*plugin.Res // --- Account Settings --- func (p *Provider) handleListAccountSettings(params map[string]any) (*plugin.Response, error) { - name := strParam(params, "name") - principal := strParam(params, "principalArn") + name := shared.StrParam(params, "name") + principal := shared.StrParam(params, "principalArn") settings, err := p.store.ListAccountSettings(defaultAccountID, name, principal) if err != nil { return nil, err @@ -1222,9 +1210,9 @@ func (p *Provider) handleListAccountSettings(params map[string]any) (*plugin.Res } func (p *Provider) handlePutAccountSetting(params map[string]any) (*plugin.Response, error) { - name := strParam(params, "name") - value := strParam(params, "value") - principal := strParam(params, "principalArn") + name := shared.StrParam(params, "name") + value := shared.StrParam(params, "value") + principal := shared.StrParam(params, "principalArn") if name == "" || value == "" { return ecsError("MissingParameter", "name and value are required", http.StatusBadRequest), nil } @@ -1244,8 +1232,8 @@ func (p *Provider) handlePutAccountSettingDefault(params map[string]any) (*plugi } func (p *Provider) handleDeleteAccountSetting(params map[string]any) (*plugin.Response, error) { - name := strParam(params, "name") - principal := strParam(params, "principalArn") + name := shared.StrParam(params, "name") + principal := shared.StrParam(params, "principalArn") if err := p.store.DeleteAccountSetting(defaultAccountID, name, principal); err != nil { return nil, err } diff --git a/internal/services/ecs/store.go b/internal/services/ecs/store.go index ea3264a..77b6c4d 100644 --- a/internal/services/ecs/store.go +++ b/internal/services/ecs/store.go @@ -29,7 +29,7 @@ var ( ErrTaskSetNotFound = errors.New("task set not found") ) -const region = "us-east-1" +const region = shared.DefaultRegion var migrations = []sqlite.Migration{ {Version: 1, SQL: ` diff --git a/internal/services/efs/provider.go b/internal/services/efs/provider.go index d2f26cf..798a595 100644 --- a/internal/services/efs/provider.go +++ b/internal/services/efs/provider.go @@ -19,7 +19,7 @@ import ( const ( defaultAccountID = plugin.DefaultAccountID - defaultRegion = "us-east-1" + defaultRegion = shared.DefaultRegion ) // Provider implements the MagnolioAPIService_v20150201 service. @@ -233,10 +233,6 @@ func (p *Provider) ListResources(_ context.Context) ([]plugin.Resource, error) { return out, nil } -func (p *Provider) GetMetrics(_ context.Context) (*plugin.ServiceMetrics, error) { - return &plugin.ServiceMetrics{}, nil -} - // --- helpers --- func efsARN(fsID string) string { @@ -317,7 +313,7 @@ func apToDesc(ap *accessPointRow, tags map[string]string) map[string]any { // --- FileSystem operations --- func (p *Provider) createFileSystem(body map[string]any) (*plugin.Response, error) { - token := strVal(body, "CreationToken") + token := shared.StrParam(body, "CreationToken") if token == "" { token = shared.GenerateUUID() } @@ -328,16 +324,16 @@ func (p *Provider) createFileSystem(body map[string]any) (*plugin.Response, erro } fsID := shared.GenerateID("fs-", 11) - perfMode := strVal(body, "PerformanceMode") + perfMode := shared.StrParam(body, "PerformanceMode") if perfMode == "" { perfMode = "generalPurpose" } - tpMode := strVal(body, "ThroughputMode") + tpMode := shared.StrParam(body, "ThroughputMode") if tpMode == "" { tpMode = "bursting" } encrypted := boolVal(body, "Encrypted") - kmsKey := strVal(body, "KmsKeyId") + kmsKey := shared.StrParam(body, "KmsKeyId") r := &fileSystemRow{ FileSystemID: fsID, @@ -393,7 +389,7 @@ func (p *Provider) updateFileSystem(fsID string, body map[string]any) (*plugin.R if err != nil { return shared.JSONError("FileSystemNotFound", "file system not found", http.StatusNotFound), nil } - tpMode := strVal(body, "ThroughputMode") + tpMode := shared.StrParam(body, "ThroughputMode") if tpMode == "" { tpMode = fs.ThroughputMode } @@ -416,7 +412,7 @@ func (p *Provider) putFileSystemPolicy(fsID string, body map[string]any) (*plugi if _, err := p.store.GetFileSystem(fsID); err != nil { return shared.JSONError("FileSystemNotFound", "file system not found", http.StatusNotFound), nil } - policy := strVal(body, "Policy") + policy := shared.StrParam(body, "Policy") if err := p.store.PutFileSystemPolicy(fsID, policy); err != nil { return shared.JSONError("InternalError", err.Error(), http.StatusInternalServerError), nil } @@ -444,7 +440,7 @@ func (p *Provider) putBackupPolicy(fsID string, body map[string]any) (*plugin.Re } status := "DISABLED" if bp, ok := body["BackupPolicy"].(map[string]any); ok { - status = strVal(bp, "Status") + status = shared.StrParam(bp, "Status") } if err := p.store.PutBackupPolicy(fsID, status); err != nil { return shared.JSONError("InternalError", err.Error(), http.StatusInternalServerError), nil @@ -496,12 +492,12 @@ func (p *Provider) describeLifecycleConfiguration(fsID string) (*plugin.Response // --- MountTarget operations --- func (p *Provider) createMountTarget(body map[string]any) (*plugin.Response, error) { - fsID := strVal(body, "FileSystemId") + fsID := shared.StrParam(body, "FileSystemId") if _, err := p.store.GetFileSystem(fsID); err != nil { return shared.JSONError("FileSystemNotFound", "file system not found", http.StatusNotFound), nil } - subnetID := strVal(body, "SubnetId") - ipAddr := strVal(body, "IpAddress") + subnetID := shared.StrParam(body, "SubnetId") + ipAddr := shared.StrParam(body, "IpAddress") if ipAddr == "" { ipAddr = "10.0.1.100" } @@ -591,12 +587,12 @@ func (p *Provider) modifyMountTargetSecurityGroups(mtID string, body map[string] // --- AccessPoint operations --- func (p *Provider) createAccessPoint(body map[string]any) (*plugin.Response, error) { - fsID := strVal(body, "FileSystemId") + fsID := shared.StrParam(body, "FileSystemId") if _, err := p.store.GetFileSystem(fsID); err != nil { return shared.JSONError("FileSystemNotFound", "file system not found", http.StatusNotFound), nil } apID := shared.GenerateID("fsap-", 13) - clientToken := strVal(body, "ClientToken") + clientToken := shared.StrParam(body, "ClientToken") var posixUser, rootDir string if pu, ok := body["PosixUser"]; ok { b, _ := json.Marshal(pu) @@ -700,15 +696,6 @@ func (p *Provider) untagResource(resourceID string, keys []string) (*plugin.Resp // --- helpers --- -func strVal(m map[string]any, key string) string { - if v, ok := m[key]; ok { - if s, ok := v.(string); ok { - return s - } - } - return "" -} - func boolVal(m map[string]any, key string) bool { if v, ok := m[key]; ok { if b, ok := v.(bool); ok { diff --git a/internal/services/eks/provider.go b/internal/services/eks/provider.go index 6c86669..6688b85 100644 --- a/internal/services/eks/provider.go +++ b/internal/services/eks/provider.go @@ -446,10 +446,6 @@ func (p *Provider) ListResources(_ context.Context) ([]plugin.Resource, error) { return res, nil } -func (p *Provider) GetMetrics(_ context.Context) (*plugin.ServiceMetrics, error) { - return &plugin.ServiceMetrics{}, nil -} - // ── Cluster operations ────────────────────────────────────────────────────── func (p *Provider) createCluster(params map[string]any) (*plugin.Response, error) { diff --git a/internal/services/elasticache/provider.go b/internal/services/elasticache/provider.go index d259b64..32e1108 100644 --- a/internal/services/elasticache/provider.go +++ b/internal/services/elasticache/provider.go @@ -194,10 +194,6 @@ func (p *Provider) ListResources(ctx context.Context) ([]plugin.Resource, error) return out, nil } -func (p *Provider) GetMetrics(_ context.Context) (*plugin.ServiceMetrics, error) { - return &plugin.ServiceMetrics{}, nil -} - // --- helpers --- func ecError(code, msg string, status int) *plugin.Response { diff --git a/internal/services/elasticbeanstalk/provider.go b/internal/services/elasticbeanstalk/provider.go index 53160fa..f052d06 100644 --- a/internal/services/elasticbeanstalk/provider.go +++ b/internal/services/elasticbeanstalk/provider.go @@ -168,10 +168,6 @@ func (p *Provider) ListResources(ctx context.Context) ([]plugin.Resource, error) return out, nil } -func (p *Provider) GetMetrics(_ context.Context) (*plugin.ServiceMetrics, error) { - return &plugin.ServiceMetrics{}, nil -} - // --- helpers --- func ebError(code, msg string, status int) *plugin.Response { diff --git a/internal/services/elasticloadbalancingv2/provider.go b/internal/services/elasticloadbalancingv2/provider.go index 3f8c73e..bbd5ca0 100644 --- a/internal/services/elasticloadbalancingv2/provider.go +++ b/internal/services/elasticloadbalancingv2/provider.go @@ -164,10 +164,6 @@ func (p *Provider) ListResources(ctx context.Context) ([]plugin.Resource, error) return out, nil } -func (p *Provider) GetMetrics(_ context.Context) (*plugin.ServiceMetrics, error) { - return &plugin.ServiceMetrics{}, nil -} - // --- helpers --- func elbError(code, msg string, status int) *plugin.Response { diff --git a/internal/services/elasticsearchservice/provider.go b/internal/services/elasticsearchservice/provider.go index dc9532a..6cb56f9 100644 --- a/internal/services/elasticsearchservice/provider.go +++ b/internal/services/elasticsearchservice/provider.go @@ -191,10 +191,6 @@ func (p *Provider) ListResources(_ context.Context) ([]plugin.Resource, error) { return res, nil } -func (p *Provider) GetMetrics(_ context.Context) (*plugin.ServiceMetrics, error) { - return &plugin.ServiceMetrics{}, nil -} - // --- Core CRUD --- func (p *Provider) createDomain(params map[string]any) (*plugin.Response, error) { diff --git a/internal/services/emr/provider.go b/internal/services/emr/provider.go index 1c06dcd..3d05c4a 100644 --- a/internal/services/emr/provider.go +++ b/internal/services/emr/provider.go @@ -127,11 +127,11 @@ func (p *Provider) HandleRequest(_ context.Context, op string, req *http.Request // Stub operations — return success/empty case "AddInstanceFleet": return shared.JSONResponse(http.StatusOK, map[string]any{ - "ClusterId": strParam(params, "ClusterId"), "ClusterArn": "", "InstanceFleetId": "", + "ClusterId": shared.StrParam(params, "ClusterId"), "ClusterArn": "", "InstanceFleetId": "", }) case "AddInstanceGroups": return shared.JSONResponse(http.StatusOK, map[string]any{ - "JobFlowId": strParam(params, "JobFlowId"), "ClusterArn": "", "InstanceGroupIds": []string{}, + "JobFlowId": shared.StrParam(params, "JobFlowId"), "ClusterArn": "", "InstanceGroupIds": []string{}, }) case "CreateStudioSessionMapping": return shared.JSONResponse(http.StatusOK, map[string]any{}) @@ -221,10 +221,6 @@ func (p *Provider) ListResources(_ context.Context) ([]plugin.Resource, error) { return res, nil } -func (p *Provider) GetMetrics(_ context.Context) (*plugin.ServiceMetrics, error) { - return &plugin.ServiceMetrics{}, nil -} - // ---- Cluster operations ---- func (p *Provider) runJobFlow(params map[string]any) (*plugin.Response, error) { @@ -853,11 +849,6 @@ func parseTags(rawTags []any) map[string]string { return tags } -func strParam(params map[string]any, key string) string { - v, _ := params[key].(string) - return v -} - func boolParam(params map[string]any, key string) bool { v, _ := params[key].(bool) return v diff --git a/internal/services/eventbridge/provider.go b/internal/services/eventbridge/provider.go index 4818e19..82a871e 100644 --- a/internal/services/eventbridge/provider.go +++ b/internal/services/eventbridge/provider.go @@ -142,10 +142,6 @@ func (p *Provider) ListResources(_ context.Context) ([]plugin.Resource, error) { return resources, nil } -func (p *Provider) GetMetrics(_ context.Context) (*plugin.ServiceMetrics, error) { - return &plugin.ServiceMetrics{}, nil -} - func (p *Provider) createEventBus(params map[string]any) (*plugin.Response, error) { name, _ := params["Name"].(string) if name == "" { diff --git a/internal/services/eventbridge/store.go b/internal/services/eventbridge/store.go index 8188874..feeb953 100644 --- a/internal/services/eventbridge/store.go +++ b/internal/services/eventbridge/store.go @@ -13,6 +13,8 @@ import ( "time" "github.com/skyoo2003/devcloud/internal/storage/sqlite" + + "github.com/skyoo2003/devcloud/internal/plugin" ) var ( @@ -112,7 +114,7 @@ func NewEBStore(dataDir string) (*EBStore, error) { } eb := &EBStore{store: s} // Seed the default event bus. - _ = eb.CreateEventBus("default", "000000000000") + _ = eb.CreateEventBus("default", plugin.DefaultAccountID) return eb, nil } diff --git a/internal/services/firehose/provider.go b/internal/services/firehose/provider.go index 4f9d70f..2a1fbf7 100644 --- a/internal/services/firehose/provider.go +++ b/internal/services/firehose/provider.go @@ -172,10 +172,6 @@ func (p *Provider) ListResources(_ context.Context) ([]plugin.Resource, error) { return res, nil } -func (p *Provider) GetMetrics(_ context.Context) (*plugin.ServiceMetrics, error) { - return &plugin.ServiceMetrics{}, nil -} - func (p *Provider) createDeliveryStream(params map[string]any) (*plugin.Response, error) { name, _ := params["DeliveryStreamName"].(string) if name == "" { diff --git a/internal/services/fis/provider.go b/internal/services/fis/provider.go index 7b1f244..c182ffa 100644 --- a/internal/services/fis/provider.go +++ b/internal/services/fis/provider.go @@ -242,10 +242,6 @@ func (p *Provider) ListResources(_ context.Context) ([]plugin.Resource, error) { return res, nil } -func (p *Provider) GetMetrics(_ context.Context) (*plugin.ServiceMetrics, error) { - return &plugin.ServiceMetrics{}, nil -} - // --- ExperimentTemplate CRUD --- func (p *Provider) createExperimentTemplate(params map[string]any) (*plugin.Response, error) { diff --git a/internal/services/glacier/provider.go b/internal/services/glacier/provider.go index 98a068c..5313344 100644 --- a/internal/services/glacier/provider.go +++ b/internal/services/glacier/provider.go @@ -333,10 +333,6 @@ func (p *Provider) ListResources(_ context.Context) ([]plugin.Resource, error) { return res, nil } -func (p *Provider) GetMetrics(_ context.Context) (*plugin.ServiceMetrics, error) { - return &plugin.ServiceMetrics{}, nil -} - // --- Vault CRUD --- func (p *Provider) createVault(name string) (*plugin.Response, error) { diff --git a/internal/services/glue/provider.go b/internal/services/glue/provider.go index 8c3600b..5b45d33 100644 --- a/internal/services/glue/provider.go +++ b/internal/services/glue/provider.go @@ -230,21 +230,8 @@ func (p *Provider) ListResources(_ context.Context) ([]plugin.Resource, error) { return res, nil } -func (p *Provider) GetMetrics(_ context.Context) (*plugin.ServiceMetrics, error) { - return &plugin.ServiceMetrics{}, nil -} - // ---- helpers ---- -func strParam(params map[string]any, key string) string { - if v, ok := params[key]; ok { - if s, ok := v.(string); ok { - return s - } - } - return "" -} - func intParam(params map[string]any, key string, def int) int { if v, ok := params[key]; ok { switch n := v.(type) { @@ -274,7 +261,7 @@ func marshalParamArray(params map[string]any, key string) string { } func catalogID(params map[string]any) string { - if id := strParam(params, "CatalogId"); id != "" { + if id := shared.StrParam(params, "CatalogId"); id != "" { return id } return shared.DefaultAccountID @@ -309,13 +296,13 @@ func (p *Provider) createDatabase(params map[string]any) (*plugin.Response, erro if input == nil { return shared.JSONError("InvalidInputException", "DatabaseInput is required", http.StatusBadRequest), nil } - name := strParam(input, "Name") + name := shared.StrParam(input, "Name") if name == "" { return shared.JSONError("InvalidInputException", "Name is required", http.StatusBadRequest), nil } catID := catalogID(params) - description := strParam(input, "Description") - locationURI := strParam(input, "LocationUri") + description := shared.StrParam(input, "Description") + locationURI := shared.StrParam(input, "LocationUri") parameters := marshalParam(input, "Parameters") _, err := p.store.CreateDatabase(catID, name, description, locationURI, parameters) if err != nil { @@ -328,7 +315,7 @@ func (p *Provider) createDatabase(params map[string]any) (*plugin.Response, erro } func (p *Provider) getDatabase(params map[string]any) (*plugin.Response, error) { - name := strParam(params, "Name") + name := shared.StrParam(params, "Name") if name == "" { return shared.JSONError("InvalidInputException", "Name is required", http.StatusBadRequest), nil } @@ -357,7 +344,7 @@ func (p *Provider) getDatabases(params map[string]any) (*plugin.Response, error) } func (p *Provider) updateDatabase(params map[string]any) (*plugin.Response, error) { - name := strParam(params, "Name") + name := shared.StrParam(params, "Name") if name == "" { return shared.JSONError("InvalidInputException", "Name is required", http.StatusBadRequest), nil } @@ -366,8 +353,8 @@ func (p *Provider) updateDatabase(params map[string]any) (*plugin.Response, erro if input == nil { input = map[string]any{} } - description := strParam(input, "Description") - locationURI := strParam(input, "LocationUri") + description := shared.StrParam(input, "Description") + locationURI := shared.StrParam(input, "LocationUri") parameters := marshalParam(input, "Parameters") if err := p.store.UpdateDatabase(catID, name, description, locationURI, parameters); err != nil { if err == errDatabaseNotFound { @@ -379,7 +366,7 @@ func (p *Provider) updateDatabase(params map[string]any) (*plugin.Response, erro } func (p *Provider) deleteDatabase(params map[string]any) (*plugin.Response, error) { - name := strParam(params, "Name") + name := shared.StrParam(params, "Name") if name == "" { return shared.JSONError("InvalidInputException", "Name is required", http.StatusBadRequest), nil } @@ -409,7 +396,7 @@ func databaseToMap(db *Database) map[string]any { // ---- Table handlers ---- func (p *Provider) createTable(params map[string]any) (*plugin.Response, error) { - dbName := strParam(params, "DatabaseName") + dbName := shared.StrParam(params, "DatabaseName") if dbName == "" { return shared.JSONError("InvalidInputException", "DatabaseName is required", http.StatusBadRequest), nil } @@ -417,13 +404,13 @@ func (p *Provider) createTable(params map[string]any) (*plugin.Response, error) if input == nil { return shared.JSONError("InvalidInputException", "TableInput is required", http.StatusBadRequest), nil } - name := strParam(input, "Name") + name := shared.StrParam(input, "Name") if name == "" { return shared.JSONError("InvalidInputException", "Name is required", http.StatusBadRequest), nil } catID := catalogID(params) - description := strParam(input, "Description") - tableType := strParam(input, "TableType") + description := shared.StrParam(input, "Description") + tableType := shared.StrParam(input, "TableType") if tableType == "" { tableType = "EXTERNAL_TABLE" } @@ -445,8 +432,8 @@ func (p *Provider) createTable(params map[string]any) (*plugin.Response, error) } func (p *Provider) getTable(params map[string]any) (*plugin.Response, error) { - dbName := strParam(params, "DatabaseName") - name := strParam(params, "Name") + dbName := shared.StrParam(params, "DatabaseName") + name := shared.StrParam(params, "Name") if dbName == "" || name == "" { return shared.JSONError("InvalidInputException", "DatabaseName and Name are required", http.StatusBadRequest), nil } @@ -462,7 +449,7 @@ func (p *Provider) getTable(params map[string]any) (*plugin.Response, error) { } func (p *Provider) getTables(params map[string]any) (*plugin.Response, error) { - dbName := strParam(params, "DatabaseName") + dbName := shared.StrParam(params, "DatabaseName") if dbName == "" { return shared.JSONError("InvalidInputException", "DatabaseName is required", http.StatusBadRequest), nil } @@ -479,18 +466,18 @@ func (p *Provider) getTables(params map[string]any) (*plugin.Response, error) { } func (p *Provider) updateTable(params map[string]any) (*plugin.Response, error) { - dbName := strParam(params, "DatabaseName") + dbName := shared.StrParam(params, "DatabaseName") input, _ := params["TableInput"].(map[string]any) if dbName == "" || input == nil { return shared.JSONError("InvalidInputException", "DatabaseName and TableInput are required", http.StatusBadRequest), nil } - name := strParam(input, "Name") + name := shared.StrParam(input, "Name") if name == "" { return shared.JSONError("InvalidInputException", "Name is required", http.StatusBadRequest), nil } catID := catalogID(params) - description := strParam(input, "Description") - tableType := strParam(input, "TableType") + description := shared.StrParam(input, "Description") + tableType := shared.StrParam(input, "TableType") if tableType == "" { tableType = "EXTERNAL_TABLE" } @@ -511,8 +498,8 @@ func (p *Provider) updateTable(params map[string]any) (*plugin.Response, error) } func (p *Provider) deleteTable(params map[string]any) (*plugin.Response, error) { - dbName := strParam(params, "DatabaseName") - name := strParam(params, "Name") + dbName := shared.StrParam(params, "DatabaseName") + name := shared.StrParam(params, "Name") if dbName == "" || name == "" { return shared.JSONError("InvalidInputException", "DatabaseName and Name are required", http.StatusBadRequest), nil } @@ -527,7 +514,7 @@ func (p *Provider) deleteTable(params map[string]any) (*plugin.Response, error) } func (p *Provider) batchDeleteTable(params map[string]any) (*plugin.Response, error) { - dbName := strParam(params, "DatabaseName") + dbName := shared.StrParam(params, "DatabaseName") names := stringsParam(params, "TablesToDelete") catID := catalogID(params) var errs []any @@ -543,8 +530,8 @@ func (p *Provider) batchDeleteTable(params map[string]any) (*plugin.Response, er } func (p *Provider) getTableVersion(params map[string]any) (*plugin.Response, error) { - dbName := strParam(params, "DatabaseName") - tableName := strParam(params, "TableName") + dbName := shared.StrParam(params, "DatabaseName") + tableName := shared.StrParam(params, "TableName") t, err := p.store.GetTable(catalogID(params), dbName, tableName) if err != nil { if err == errTableNotFound { @@ -561,8 +548,8 @@ func (p *Provider) getTableVersion(params map[string]any) (*plugin.Response, err } func (p *Provider) getTableVersions(params map[string]any) (*plugin.Response, error) { - dbName := strParam(params, "DatabaseName") - tableName := strParam(params, "TableName") + dbName := shared.StrParam(params, "DatabaseName") + tableName := shared.StrParam(params, "TableName") t, err := p.store.GetTable(catalogID(params), dbName, tableName) if err != nil { if err == errTableNotFound { @@ -600,8 +587,8 @@ func tableToMap(t *Table) map[string]any { // ---- Partition handlers ---- func (p *Provider) createPartition(params map[string]any) (*plugin.Response, error) { - dbName := strParam(params, "DatabaseName") - tableName := strParam(params, "TableName") + dbName := shared.StrParam(params, "DatabaseName") + tableName := shared.StrParam(params, "TableName") if dbName == "" || tableName == "" { return shared.JSONError("InvalidInputException", "DatabaseName and TableName are required", http.StatusBadRequest), nil } @@ -624,8 +611,8 @@ func (p *Provider) createPartition(params map[string]any) (*plugin.Response, err } func (p *Provider) getPartition(params map[string]any) (*plugin.Response, error) { - dbName := strParam(params, "DatabaseName") - tableName := strParam(params, "TableName") + dbName := shared.StrParam(params, "DatabaseName") + tableName := shared.StrParam(params, "TableName") values := stringsParam(params, "PartitionValues") catID := catalogID(params) part, err := p.store.GetPartition(catID, dbName, tableName, values) @@ -639,8 +626,8 @@ func (p *Provider) getPartition(params map[string]any) (*plugin.Response, error) } func (p *Provider) getPartitions(params map[string]any) (*plugin.Response, error) { - dbName := strParam(params, "DatabaseName") - tableName := strParam(params, "TableName") + dbName := shared.StrParam(params, "DatabaseName") + tableName := shared.StrParam(params, "TableName") catID := catalogID(params) parts, err := p.store.ListPartitions(catID, dbName, tableName) if err != nil { @@ -654,8 +641,8 @@ func (p *Provider) getPartitions(params map[string]any) (*plugin.Response, error } func (p *Provider) updatePartition(params map[string]any) (*plugin.Response, error) { - dbName := strParam(params, "DatabaseName") - tableName := strParam(params, "TableName") + dbName := shared.StrParam(params, "DatabaseName") + tableName := shared.StrParam(params, "TableName") values := stringsParam(params, "PartitionValueList") input, _ := params["PartitionInput"].(map[string]any) if input == nil { @@ -674,8 +661,8 @@ func (p *Provider) updatePartition(params map[string]any) (*plugin.Response, err } func (p *Provider) batchCreatePartition(params map[string]any) (*plugin.Response, error) { - dbName := strParam(params, "DatabaseName") - tableName := strParam(params, "TableName") + dbName := shared.StrParam(params, "DatabaseName") + tableName := shared.StrParam(params, "TableName") catID := catalogID(params) inputs, _ := params["PartitionInputList"].([]any) var errs []any @@ -701,8 +688,8 @@ func (p *Provider) batchCreatePartition(params map[string]any) (*plugin.Response } func (p *Provider) batchDeletePartition(params map[string]any) (*plugin.Response, error) { - dbName := strParam(params, "DatabaseName") - tableName := strParam(params, "TableName") + dbName := shared.StrParam(params, "DatabaseName") + tableName := shared.StrParam(params, "TableName") catID := catalogID(params) parts, _ := params["PartitionsToDelete"].([]any) var errs []any @@ -726,8 +713,8 @@ func (p *Provider) batchDeletePartition(params map[string]any) (*plugin.Response } func (p *Provider) batchGetPartition(params map[string]any) (*plugin.Response, error) { - dbName := strParam(params, "DatabaseName") - tableName := strParam(params, "TableName") + dbName := shared.StrParam(params, "DatabaseName") + tableName := shared.StrParam(params, "TableName") catID := catalogID(params) partitionsToGet, _ := params["PartitionsToGet"].([]any) var found []any @@ -755,8 +742,8 @@ func (p *Provider) batchGetPartition(params map[string]any) (*plugin.Response, e } func (p *Provider) batchUpdatePartition(params map[string]any) (*plugin.Response, error) { - dbName := strParam(params, "DatabaseName") - tableName := strParam(params, "TableName") + dbName := shared.StrParam(params, "DatabaseName") + tableName := shared.StrParam(params, "TableName") catID := catalogID(params) entries, _ := params["Entries"].([]any) var errs []any @@ -807,16 +794,16 @@ func partitionToMap(p *Partition) map[string]any { // ---- Crawler handlers ---- func (p *Provider) createCrawler(params map[string]any) (*plugin.Response, error) { - name := strParam(params, "Name") + name := shared.StrParam(params, "Name") if name == "" { return shared.JSONError("InvalidInputException", "Name is required", http.StatusBadRequest), nil } - role := strParam(params, "Role") - dbName := strParam(params, "DatabaseName") + role := shared.StrParam(params, "Role") + dbName := shared.StrParam(params, "DatabaseName") targets := marshalParam(params, "Targets") schedule := "" if s, ok := params["Schedule"].(map[string]any); ok { - schedule = strParam(s, "ScheduleExpression") + schedule = shared.StrParam(s, "ScheduleExpression") } else if s, ok := params["Schedule"].(string); ok { schedule = s } @@ -832,7 +819,7 @@ func (p *Provider) createCrawler(params map[string]any) (*plugin.Response, error } func (p *Provider) getCrawler(params map[string]any) (*plugin.Response, error) { - name := strParam(params, "Name") + name := shared.StrParam(params, "Name") c, err := p.store.GetCrawler(name) if err != nil { if err == errCrawlerNotFound { @@ -856,12 +843,12 @@ func (p *Provider) getCrawlers(params map[string]any) (*plugin.Response, error) } func (p *Provider) updateCrawler(params map[string]any) (*plugin.Response, error) { - name := strParam(params, "Name") + name := shared.StrParam(params, "Name") if name == "" { return shared.JSONError("InvalidInputException", "Name is required", http.StatusBadRequest), nil } - role := strParam(params, "Role") - dbName := strParam(params, "DatabaseName") + role := shared.StrParam(params, "Role") + dbName := shared.StrParam(params, "DatabaseName") targets := marshalParam(params, "Targets") schedule := "" if s, ok := params["Schedule"].(string); ok { @@ -878,7 +865,7 @@ func (p *Provider) updateCrawler(params map[string]any) (*plugin.Response, error } func (p *Provider) deleteCrawler(params map[string]any) (*plugin.Response, error) { - name := strParam(params, "Name") + name := shared.StrParam(params, "Name") if err := p.store.DeleteCrawler(name); err != nil { if err == errCrawlerNotFound { return shared.JSONError("EntityNotFoundException", "Crawler not found", http.StatusBadRequest), nil @@ -889,7 +876,7 @@ func (p *Provider) deleteCrawler(params map[string]any) (*plugin.Response, error } func (p *Provider) startCrawler(params map[string]any) (*plugin.Response, error) { - name := strParam(params, "Name") + name := shared.StrParam(params, "Name") if err := p.store.UpdateCrawlerStatus(name, "RUNNING"); err != nil { if err == errCrawlerNotFound { return shared.JSONError("EntityNotFoundException", "Crawler not found", http.StatusBadRequest), nil @@ -900,7 +887,7 @@ func (p *Provider) startCrawler(params map[string]any) (*plugin.Response, error) } func (p *Provider) stopCrawler(params map[string]any) (*plugin.Response, error) { - name := strParam(params, "Name") + name := shared.StrParam(params, "Name") if err := p.store.UpdateCrawlerStatus(name, "READY"); err != nil { if err == errCrawlerNotFound { return shared.JSONError("EntityNotFoundException", "Crawler not found", http.StatusBadRequest), nil @@ -951,11 +938,11 @@ func crawlerToMap(c *Crawler) map[string]any { // ---- Job handlers ---- func (p *Provider) createJob(params map[string]any) (*plugin.Response, error) { - name := strParam(params, "Name") + name := shared.StrParam(params, "Name") if name == "" { return shared.JSONError("InvalidInputException", "Name is required", http.StatusBadRequest), nil } - role := strParam(params, "Role") + role := shared.StrParam(params, "Role") command := marshalParam(params, "Command") maxRetries := intParam(params, "MaxRetries", 0) timeout := intParam(params, "Timeout", 2880) @@ -971,7 +958,7 @@ func (p *Provider) createJob(params map[string]any) (*plugin.Response, error) { } func (p *Provider) getJob(params map[string]any) (*plugin.Response, error) { - name := strParam(params, "JobName") + name := shared.StrParam(params, "JobName") j, err := p.store.GetJob(name) if err != nil { if err == errJobNotFound { @@ -995,7 +982,7 @@ func (p *Provider) getJobs(params map[string]any) (*plugin.Response, error) { } func (p *Provider) updateJob(params map[string]any) (*plugin.Response, error) { - name := strParam(params, "JobName") + name := shared.StrParam(params, "JobName") if name == "" { return shared.JSONError("InvalidInputException", "JobName is required", http.StatusBadRequest), nil } @@ -1003,7 +990,7 @@ func (p *Provider) updateJob(params map[string]any) (*plugin.Response, error) { if input == nil { input = map[string]any{} } - role := strParam(input, "Role") + role := shared.StrParam(input, "Role") command := marshalParam(input, "Command") maxRetries := intParam(input, "MaxRetries", 0) timeout := intParam(input, "Timeout", 2880) @@ -1018,7 +1005,7 @@ func (p *Provider) updateJob(params map[string]any) (*plugin.Response, error) { } func (p *Provider) deleteJob(params map[string]any) (*plugin.Response, error) { - name := strParam(params, "JobName") + name := shared.StrParam(params, "JobName") if err := p.store.DeleteJob(name); err != nil { if err == errJobNotFound { return shared.JSONError("EntityNotFoundException", "Job not found", http.StatusBadRequest), nil @@ -1050,7 +1037,7 @@ func (p *Provider) batchGetJobs(params map[string]any) (*plugin.Response, error) } func (p *Provider) startJobRun(params map[string]any) (*plugin.Response, error) { - jobName := strParam(params, "JobName") + jobName := shared.StrParam(params, "JobName") if jobName == "" { return shared.JSONError("InvalidInputException", "JobName is required", http.StatusBadRequest), nil } @@ -1063,7 +1050,7 @@ func (p *Provider) startJobRun(params map[string]any) (*plugin.Response, error) } func (p *Provider) getJobRun(params map[string]any) (*plugin.Response, error) { - runID := strParam(params, "RunId") + runID := shared.StrParam(params, "RunId") r, err := p.store.GetJobRun(runID) if err != nil { if err == errJobRunNotFound { @@ -1075,7 +1062,7 @@ func (p *Provider) getJobRun(params map[string]any) (*plugin.Response, error) { } func (p *Provider) getJobRuns(params map[string]any) (*plugin.Response, error) { - jobName := strParam(params, "JobName") + jobName := shared.StrParam(params, "JobName") runs, err := p.store.ListJobRuns(jobName) if err != nil { return nil, err @@ -1088,7 +1075,7 @@ func (p *Provider) getJobRuns(params map[string]any) (*plugin.Response, error) { } func (p *Provider) batchStopJobRun(params map[string]any) (*plugin.Response, error) { - jobName := strParam(params, "JobName") + jobName := shared.StrParam(params, "JobName") runIDs := stringsParam(params, "JobRunIds") var errs []any for _, id := range runIDs { @@ -1138,12 +1125,12 @@ func (p *Provider) createConnection(params map[string]any) (*plugin.Response, er if input == nil { return shared.JSONError("InvalidInputException", "ConnectionInput is required", http.StatusBadRequest), nil } - name := strParam(input, "Name") + name := shared.StrParam(input, "Name") if name == "" { return shared.JSONError("InvalidInputException", "Name is required", http.StatusBadRequest), nil } catID := catalogID(params) - connType := strParam(input, "ConnectionType") + connType := shared.StrParam(input, "ConnectionType") if connType == "" { connType = "JDBC" } @@ -1159,7 +1146,7 @@ func (p *Provider) createConnection(params map[string]any) (*plugin.Response, er } func (p *Provider) getConnection(params map[string]any) (*plugin.Response, error) { - name := strParam(params, "Name") + name := shared.StrParam(params, "Name") catID := catalogID(params) c, err := p.store.GetConnection(catID, name) if err != nil { @@ -1185,13 +1172,13 @@ func (p *Provider) getConnections(params map[string]any) (*plugin.Response, erro } func (p *Provider) updateConnection(params map[string]any) (*plugin.Response, error) { - name := strParam(params, "Name") + name := shared.StrParam(params, "Name") catID := catalogID(params) input, _ := params["ConnectionInput"].(map[string]any) if input == nil { input = map[string]any{} } - connType := strParam(input, "ConnectionType") + connType := shared.StrParam(input, "ConnectionType") if connType == "" { connType = "JDBC" } @@ -1206,7 +1193,7 @@ func (p *Provider) updateConnection(params map[string]any) (*plugin.Response, er } func (p *Provider) deleteConnection(params map[string]any) (*plugin.Response, error) { - name := strParam(params, "ConnectionName") + name := shared.StrParam(params, "ConnectionName") catID := catalogID(params) if err := p.store.DeleteConnection(catID, name); err != nil { if err == errConnectionNotFound { @@ -1250,17 +1237,17 @@ func connectionToMap(c *Connection) map[string]any { // ---- Trigger handlers ---- func (p *Provider) createTrigger(params map[string]any) (*plugin.Response, error) { - name := strParam(params, "Name") + name := shared.StrParam(params, "Name") if name == "" { return shared.JSONError("InvalidInputException", "Name is required", http.StatusBadRequest), nil } - trigType := strParam(params, "Type") + trigType := shared.StrParam(params, "Type") if trigType == "" { trigType = "ON_DEMAND" } actions := marshalParamArray(params, "Actions") predicate := marshalParam(params, "Predicate") - schedule := strParam(params, "Schedule") + schedule := shared.StrParam(params, "Schedule") _, err := p.store.CreateTrigger(name, trigType, actions, predicate, schedule) if err != nil { if sqliteIsUnique(err) { @@ -1272,7 +1259,7 @@ func (p *Provider) createTrigger(params map[string]any) (*plugin.Response, error } func (p *Provider) getTrigger(params map[string]any) (*plugin.Response, error) { - name := strParam(params, "Name") + name := shared.StrParam(params, "Name") t, err := p.store.GetTrigger(name) if err != nil { if err == errTriggerNotFound { @@ -1296,7 +1283,7 @@ func (p *Provider) getTriggers(params map[string]any) (*plugin.Response, error) } func (p *Provider) updateTrigger(params map[string]any) (*plugin.Response, error) { - name := strParam(params, "Name") + name := shared.StrParam(params, "Name") if name == "" { return shared.JSONError("InvalidInputException", "Name is required", http.StatusBadRequest), nil } @@ -1304,13 +1291,13 @@ func (p *Provider) updateTrigger(params map[string]any) (*plugin.Response, error if input == nil { input = map[string]any{} } - trigType := strParam(input, "Type") + trigType := shared.StrParam(input, "Type") if trigType == "" { trigType = "ON_DEMAND" } actions := marshalParamArray(input, "Actions") predicate := marshalParam(input, "Predicate") - schedule := strParam(input, "Schedule") + schedule := shared.StrParam(input, "Schedule") if err := p.store.UpdateTrigger(name, trigType, actions, predicate, schedule); err != nil { if err == errTriggerNotFound { return shared.JSONError("EntityNotFoundException", "Trigger not found", http.StatusBadRequest), nil @@ -1321,7 +1308,7 @@ func (p *Provider) updateTrigger(params map[string]any) (*plugin.Response, error } func (p *Provider) deleteTrigger(params map[string]any) (*plugin.Response, error) { - name := strParam(params, "Name") + name := shared.StrParam(params, "Name") if err := p.store.DeleteTrigger(name); err != nil { if err == errTriggerNotFound { return shared.JSONError("EntityNotFoundException", "Trigger not found", http.StatusBadRequest), nil @@ -1332,7 +1319,7 @@ func (p *Provider) deleteTrigger(params map[string]any) (*plugin.Response, error } func (p *Provider) startTrigger(params map[string]any) (*plugin.Response, error) { - name := strParam(params, "Name") + name := shared.StrParam(params, "Name") if err := p.store.UpdateTriggerState(name, "ACTIVATED"); err != nil { if err == errTriggerNotFound { return shared.JSONError("EntityNotFoundException", "Trigger not found", http.StatusBadRequest), nil @@ -1343,7 +1330,7 @@ func (p *Provider) startTrigger(params map[string]any) (*plugin.Response, error) } func (p *Provider) stopTrigger(params map[string]any) (*plugin.Response, error) { - name := strParam(params, "Name") + name := shared.StrParam(params, "Name") if err := p.store.UpdateTriggerState(name, "DEACTIVATED"); err != nil { if err == errTriggerNotFound { return shared.JSONError("EntityNotFoundException", "Trigger not found", http.StatusBadRequest), nil @@ -1391,7 +1378,7 @@ func triggerToMap(t *Trigger) map[string]any { // ---- SecurityConfiguration handlers ---- func (p *Provider) createSecurityConfiguration(params map[string]any) (*plugin.Response, error) { - name := strParam(params, "Name") + name := shared.StrParam(params, "Name") if name == "" { return shared.JSONError("InvalidInputException", "Name is required", http.StatusBadRequest), nil } @@ -1407,7 +1394,7 @@ func (p *Provider) createSecurityConfiguration(params map[string]any) (*plugin.R } func (p *Provider) getSecurityConfiguration(params map[string]any) (*plugin.Response, error) { - name := strParam(params, "Name") + name := shared.StrParam(params, "Name") c, err := p.store.GetSecurityConfig(name) if err != nil { if err == errSecurityConfigNotFound { @@ -1431,7 +1418,7 @@ func (p *Provider) getSecurityConfigurations(params map[string]any) (*plugin.Res } func (p *Provider) deleteSecurityConfiguration(params map[string]any) (*plugin.Response, error) { - name := strParam(params, "Name") + name := shared.StrParam(params, "Name") if err := p.store.DeleteSecurityConfig(name); err != nil { if err == errSecurityConfigNotFound { return shared.JSONError("EntityNotFoundException", "SecurityConfiguration not found", http.StatusBadRequest), nil @@ -1454,7 +1441,7 @@ func securityConfigToMap(c *SecurityConfig) map[string]any { // ---- Tags handlers ---- func (p *Provider) tagResource(params map[string]any) (*plugin.Response, error) { - arn := strParam(params, "ResourceArn") + arn := shared.StrParam(params, "ResourceArn") tagsRaw, _ := params["TagsToAdd"].(map[string]any) tags := make(map[string]string) for k, v := range tagsRaw { @@ -1469,7 +1456,7 @@ func (p *Provider) tagResource(params map[string]any) (*plugin.Response, error) } func (p *Provider) untagResource(params map[string]any) (*plugin.Response, error) { - arn := strParam(params, "ResourceArn") + arn := shared.StrParam(params, "ResourceArn") keys := stringsParam(params, "TagsToRemove") if err := p.store.tags.RemoveTags(arn, keys); err != nil { return nil, err @@ -1478,7 +1465,7 @@ func (p *Provider) untagResource(params map[string]any) (*plugin.Response, error } func (p *Provider) getTags(params map[string]any) (*plugin.Response, error) { - arn := strParam(params, "ResourceArn") + arn := shared.StrParam(params, "ResourceArn") tags, err := p.store.tags.ListTags(arn) if err != nil { return nil, err diff --git a/internal/services/iam/provider.go b/internal/services/iam/provider.go index 8bd9a15..13e3bfe 100644 --- a/internal/services/iam/provider.go +++ b/internal/services/iam/provider.go @@ -259,11 +259,6 @@ func (p *IAMProvider) ListResources(ctx context.Context) ([]plugin.Resource, err return resources, nil } -// GetMetrics returns empty metrics. -func (p *IAMProvider) GetMetrics(_ context.Context) (*plugin.ServiceMetrics, error) { - return &plugin.ServiceMetrics{}, nil -} - // Store returns the underlying IAMStore, allowing other providers (e.g. STS) // to share the same database instance. func (p *IAMProvider) Store() *IAMStore { diff --git a/internal/services/iam/sts_provider.go b/internal/services/iam/sts_provider.go index dcd9415..7b70149 100644 --- a/internal/services/iam/sts_provider.go +++ b/internal/services/iam/sts_provider.go @@ -106,11 +106,6 @@ func (p *STSProvider) ListResources(_ context.Context) ([]plugin.Resource, error return nil, nil } -// GetMetrics returns empty metrics. -func (p *STSProvider) GetMetrics(_ context.Context) (*plugin.ServiceMetrics, error) { - return &plugin.ServiceMetrics{}, nil -} - // --- XML response structs --- type getCallerIdentityResponse struct { diff --git a/internal/services/identitystore/provider.go b/internal/services/identitystore/provider.go index 20d671e..1cdaf57 100644 --- a/internal/services/identitystore/provider.go +++ b/internal/services/identitystore/provider.go @@ -166,10 +166,6 @@ func (p *IdentityStoreProvider) ListResources(_ context.Context) ([]plugin.Resou return res, nil } -func (p *IdentityStoreProvider) GetMetrics(_ context.Context) (*plugin.ServiceMetrics, error) { - return &plugin.ServiceMetrics{}, nil -} - // --- User handlers --- func (p *IdentityStoreProvider) createUser(params map[string]any) (*plugin.Response, error) { diff --git a/internal/services/iot/provider.go b/internal/services/iot/provider.go index f896f88..f1666e9 100644 --- a/internal/services/iot/provider.go +++ b/internal/services/iot/provider.go @@ -552,10 +552,6 @@ func (p *Provider) ListResources(_ context.Context) ([]plugin.Resource, error) { return res, nil } -func (p *Provider) GetMetrics(_ context.Context) (*plugin.ServiceMetrics, error) { - return &plugin.ServiceMetrics{}, nil -} - // --- Thing operations --- func (p *Provider) createThing(params map[string]any) (*plugin.Response, error) { @@ -575,7 +571,7 @@ func (p *Provider) createThing(params map[string]any) (*plugin.Response, error) t := &Thing{ Name: name, ARN: arn, - TypeName: strParam(params, "thingTypeName"), + TypeName: shared.StrParam(params, "thingTypeName"), Attributes: attrsJSON, Version: 1, CreatedAt: time.Now().Unix(), @@ -1443,7 +1439,7 @@ func (p *Provider) createJob(id string, params map[string]any) (*plugin.Response Status: "IN_PROGRESS", Targets: targetsJSON, Document: docJSON, - Description: strParam(params, "description"), + Description: shared.StrParam(params, "description"), CreatedAt: time.Now().Unix(), } if err := p.store.CreateJob(j); err != nil { @@ -1828,11 +1824,6 @@ func extractPathParam(path, segment string) string { return "" } -func strParam(params map[string]any, key string) string { - v, _ := params[key].(string) - return v -} - func isUniqueErr(err error) bool { return err != nil && strings.Contains(err.Error(), "UNIQUE constraint failed") } diff --git a/internal/services/iotdataplane/provider.go b/internal/services/iotdataplane/provider.go index dbc9be3..0f11ca1 100644 --- a/internal/services/iotdataplane/provider.go +++ b/internal/services/iotdataplane/provider.go @@ -119,10 +119,6 @@ func (p *Provider) ListResources(_ context.Context) ([]plugin.Resource, error) { return []plugin.Resource{}, nil } -func (p *Provider) GetMetrics(_ context.Context) (*plugin.ServiceMetrics, error) { - return &plugin.ServiceMetrics{}, nil -} - // --- ThingShadow --- func (p *Provider) getThingShadow(thingName, shadowName string) (*plugin.Response, error) { diff --git a/internal/services/iotwireless/provider.go b/internal/services/iotwireless/provider.go index b43abf5..3b359e8 100644 --- a/internal/services/iotwireless/provider.go +++ b/internal/services/iotwireless/provider.go @@ -210,10 +210,6 @@ func (p *Provider) ListResources(_ context.Context) ([]plugin.Resource, error) { return res, nil } -func (p *Provider) GetMetrics(_ context.Context) (*plugin.ServiceMetrics, error) { - return &plugin.ServiceMetrics{}, nil -} - // --- Destination operations --- func (p *Provider) createDestination(params map[string]any) (*plugin.Response, error) { @@ -225,10 +221,10 @@ func (p *Provider) createDestination(params map[string]any) (*plugin.Response, e d := &Destination{ Name: name, ARN: arn, - Expression: strParam(params, "Expression"), - ExpressionType: strParamDefault(params, "ExpressionType", "RuleName"), - RoleARN: strParam(params, "RoleArn"), - Description: strParam(params, "Description"), + Expression: shared.StrParam(params, "Expression"), + ExpressionType: shared.StrParamDefault(params, "ExpressionType", "RuleName"), + RoleARN: shared.StrParam(params, "RoleArn"), + Description: shared.StrParam(params, "Description"), } if err := p.store.CreateDestination(d); err != nil { if isUniqueErr(err) { @@ -302,7 +298,7 @@ func (p *Provider) createDeviceProfile(params map[string]any) (*plugin.Response, dp := &DeviceProfile{ ID: id, ARN: arn, - Name: strParam(params, "Name"), + Name: shared.StrParam(params, "Name"), Config: configJSON, } if err := p.store.CreateDeviceProfile(dp); err != nil { @@ -363,7 +359,7 @@ func (p *Provider) createServiceProfile(params map[string]any) (*plugin.Response sp := &ServiceProfile{ ID: id, ARN: arn, - Name: strParam(params, "Name"), + Name: shared.StrParam(params, "Name"), Config: configJSON, } if err := p.store.CreateServiceProfile(sp); err != nil { @@ -424,10 +420,10 @@ func (p *Provider) createWirelessDevice(params map[string]any) (*plugin.Response wd := &WirelessDevice{ ID: id, ARN: arn, - Name: strParam(params, "Name"), - Type: strParamDefault(params, "Type", "LoRaWAN"), - Destination: strParam(params, "DestinationName"), - Description: strParam(params, "Description"), + Name: shared.StrParam(params, "Name"), + Type: shared.StrParamDefault(params, "Type", "LoRaWAN"), + Destination: shared.StrParam(params, "DestinationName"), + Description: shared.StrParam(params, "Description"), Config: configJSON, } if err := p.store.CreateWirelessDevice(wd); err != nil { @@ -504,8 +500,8 @@ func (p *Provider) createWirelessGateway(params map[string]any) (*plugin.Respons wg := &WirelessGateway{ ID: id, ARN: arn, - Name: strParam(params, "Name"), - Description: strParam(params, "Description"), + Name: shared.StrParam(params, "Name"), + Description: shared.StrParam(params, "Description"), Config: configJSON, } if err := p.store.CreateWirelessGateway(wg); err != nil { @@ -580,7 +576,7 @@ func (p *Provider) createFuotaTask(params map[string]any) (*plugin.Response, err ft := &FuotaTask{ ID: id, ARN: arn, - Name: strParam(params, "Name"), + Name: shared.StrParam(params, "Name"), Status: "Pending", Config: configJSON, } @@ -667,7 +663,7 @@ func (p *Provider) createMulticastGroup(params map[string]any) (*plugin.Response mg := &MulticastGroup{ ID: id, ARN: arn, - Name: strParam(params, "Name"), + Name: shared.StrParam(params, "Name"), Status: "Active", Config: configJSON, } @@ -1002,19 +998,6 @@ func extractPathParam(path, segment string) string { return "" } -func strParam(params map[string]any, key string) string { - v, _ := params[key].(string) - return v -} - -func strParamDefault(params map[string]any, key, def string) string { - v, _ := params[key].(string) - if v == "" { - return def - } - return v -} - func isUniqueErr(err error) bool { return err != nil && strings.Contains(err.Error(), "UNIQUE constraint failed") } diff --git a/internal/services/kafka/provider.go b/internal/services/kafka/provider.go index d45fc46..14a9b7e 100644 --- a/internal/services/kafka/provider.go +++ b/internal/services/kafka/provider.go @@ -12,6 +12,7 @@ import ( "net/http" "path/filepath" "strings" + "unicode" "github.com/skyoo2003/devcloud/internal/plugin" "github.com/skyoo2003/devcloud/internal/shared" @@ -52,8 +53,7 @@ func (p *Provider) HandleRequest(ctx context.Context, op string, req *http.Reque if resp != nil && resp.StatusCode < 300 && len(resp.Body) > 0 { var data any if json.Unmarshal(resp.Body, &data) == nil { - converted := shared.CamelCaseKeys(data) - if b, err := json.Marshal(converted); err == nil { + if b, err := json.Marshal(mapKeys(data, toCamelCase)); err == nil { resp.Body = b } } @@ -76,7 +76,7 @@ func (p *Provider) handleRequest(_ context.Context, op string, req *http.Request } // Normalize camelCase input params to PascalCase for internal consistency. - params = shared.PascalCaseKeys(params) + params, _ = mapKeys(params, toPascalCase).(map[string]any) // Extract path parameters from URL path path := req.URL.Path @@ -443,10 +443,6 @@ func (p *Provider) ListResources(_ context.Context) ([]plugin.Resource, error) { return res, nil } -func (p *Provider) GetMetrics(_ context.Context) (*plugin.ServiceMetrics, error) { - return &plugin.ServiceMetrics{}, nil -} - // ── Cluster operations ───────────────────────────────────────────────────── func (p *Provider) createCluster(params map[string]any) (*plugin.Response, error) { @@ -1343,6 +1339,63 @@ func isUniqueErr(err error) bool { return err != nil && strings.Contains(err.Error(), "UNIQUE constraint failed") } +// mapKeys recursively rewrites every map key in v with f. Kafka is the only +// service with a camelCase wire format, so the casing walk lives here. +func mapKeys(v any, f func(string) string) any { + switch val := v.(type) { + case map[string]any: + out := make(map[string]any, len(val)) + for k, item := range val { + out[f(k)] = mapKeys(item, f) + } + return out + case []any: + out := make([]any, len(val)) + for i, item := range val { + out[i] = mapKeys(item, f) + } + return out + case []map[string]any: + out := make([]any, len(val)) + for i, item := range val { + out[i] = mapKeys(item, f) + } + return out + default: + return v + } +} + +// toCamelCase lowercases the leading uppercase run of a PascalCase name: +// "ClusterArn" → "clusterArn", "ARN" → "arn", "ARNPrefix" → "arnPrefix". +func toCamelCase(s string) string { + runes := []rune(s) + i := 0 + for i < len(runes) && unicode.IsUpper(runes[i]) { + i++ + } + switch { + case i == 0: // already camelCase + return s + case i == len(runes): // all caps + return strings.ToLower(s) + case i == 1: + return strings.ToLower(string(runes[0])) + string(runes[1:]) + default: + // Last uppercase letter starts the next word: "ARNPrefix" → "arnPrefix". + return strings.ToLower(string(runes[:i-1])) + string(runes[i-1:]) + } +} + +func toPascalCase(s string) string { + if s == "" { + return s + } + runes := []rune(s) + runes[0] = unicode.ToUpper(runes[0]) + return string(runes) +} + func init() { plugin.DefaultRegistry.Register("kafka", func() plugin.ServicePlugin { return &Provider{} diff --git a/internal/services/kafka/provider_test.go b/internal/services/kafka/provider_test.go index 55b0f5a..43790ae 100644 --- a/internal/services/kafka/provider_test.go +++ b/internal/services/kafka/provider_test.go @@ -300,3 +300,32 @@ func TestGetBootstrapBrokers(t *testing.T) { resp2 := callOP(t, p, "GET", "/v1/clusters/nonexistent/bootstrap-brokers", "GetBootstrapBrokers", "") assert.Equal(t, 404, resp2.StatusCode) } + +func TestMapKeys_RoundTrip(t *testing.T) { + in := map[string]any{ + "ClusterArn": "arn", + "ARN": "x", + "ARNPrefix": "y", + "BrokerNodeGroupInfo": map[string]any{ + "ClientSubnets": []any{"a", "b"}, + }, + "ClusterInfoList": []any{ + map[string]any{"ClusterName": "c1"}, + }, + } + + camel, ok := mapKeys(in, toCamelCase).(map[string]any) + require.True(t, ok) + assert.Equal(t, "arn", camel["clusterArn"]) + assert.Equal(t, "x", camel["arn"]) + assert.Equal(t, "y", camel["arnPrefix"]) + assert.Contains(t, camel["brokerNodeGroupInfo"], "clientSubnets") + assert.Equal(t, "c1", camel["clusterInfoList"].([]any)[0].(map[string]any)["clusterName"]) + + // PascalCase only touches the first rune, so it is the inverse for keys that + // start with a single uppercase letter. + back, ok := mapKeys(camel, toPascalCase).(map[string]any) + require.True(t, ok) + assert.Equal(t, "arn", back["ClusterArn"]) + assert.Equal(t, "c1", back["ClusterInfoList"].([]any)[0].(map[string]any)["ClusterName"]) +} diff --git a/internal/services/kinesis/provider.go b/internal/services/kinesis/provider.go index b0f0f7f..72f4f27 100644 --- a/internal/services/kinesis/provider.go +++ b/internal/services/kinesis/provider.go @@ -188,10 +188,6 @@ func (p *Provider) ListResources(_ context.Context) ([]plugin.Resource, error) { return res, nil } -func (p *Provider) GetMetrics(_ context.Context) (*plugin.ServiceMetrics, error) { - return &plugin.ServiceMetrics{}, nil -} - // --- Stream CRUD --- func (p *Provider) createStream(params map[string]any) (*plugin.Response, error) { diff --git a/internal/services/kinesisanalyticsv2/provider.go b/internal/services/kinesisanalyticsv2/provider.go index 2609a13..b5c9f51 100644 --- a/internal/services/kinesisanalyticsv2/provider.go +++ b/internal/services/kinesisanalyticsv2/provider.go @@ -138,10 +138,6 @@ func (p *Provider) ListResources(_ context.Context) ([]plugin.Resource, error) { return res, nil } -func (p *Provider) GetMetrics(_ context.Context) (*plugin.ServiceMetrics, error) { - return &plugin.ServiceMetrics{}, nil -} - // --- helpers --- func appToMap(app *Application) map[string]any { diff --git a/internal/services/kms/provider.go b/internal/services/kms/provider.go index e9fb3be..ef119b6 100644 --- a/internal/services/kms/provider.go +++ b/internal/services/kms/provider.go @@ -15,10 +15,11 @@ import ( "time" "github.com/skyoo2003/devcloud/internal/plugin" + "github.com/skyoo2003/devcloud/internal/shared" ) const defaultAccountID = plugin.DefaultAccountID -const defaultRegion = "us-east-1" +const defaultRegion = shared.DefaultRegion type Provider struct { store *KMSStore @@ -144,10 +145,6 @@ func (p *Provider) ListResources(_ context.Context) ([]plugin.Resource, error) { return resources, nil } -func (p *Provider) GetMetrics(_ context.Context) (*plugin.ServiceMetrics, error) { - return &plugin.ServiceMetrics{}, nil -} - func (p *Provider) createKey(params map[string]any) (*plugin.Response, error) { description, _ := params["Description"].(string) keySpec, _ := params["KeySpec"].(string) diff --git a/internal/services/lakeformation/provider.go b/internal/services/lakeformation/provider.go index 130b875..6fa9773 100644 --- a/internal/services/lakeformation/provider.go +++ b/internal/services/lakeformation/provider.go @@ -229,14 +229,10 @@ func (p *Provider) ListResources(_ context.Context) ([]plugin.Resource, error) { return res, nil } -func (p *Provider) GetMetrics(_ context.Context) (*plugin.ServiceMetrics, error) { - return &plugin.ServiceMetrics{}, nil -} - // --- LFTag --- func (p *Provider) createLFTag(params map[string]any) (*plugin.Response, error) { - catalogID := strParam(params, "CatalogId", "000000000000") + catalogID := strParam(params, "CatalogId", plugin.DefaultAccountID) key, _ := params["TagKey"].(string) if key == "" { return shared.JSONError("ValidationException", "TagKey is required", http.StatusBadRequest), nil @@ -254,7 +250,7 @@ func (p *Provider) createLFTag(params map[string]any) (*plugin.Response, error) } func (p *Provider) getLFTag(params map[string]any) (*plugin.Response, error) { - catalogID := strParam(params, "CatalogId", "000000000000") + catalogID := strParam(params, "CatalogId", plugin.DefaultAccountID) key, _ := params["TagKey"].(string) if key == "" { return shared.JSONError("ValidationException", "TagKey is required", http.StatusBadRequest), nil @@ -273,7 +269,7 @@ func (p *Provider) getLFTag(params map[string]any) (*plugin.Response, error) { } func (p *Provider) updateLFTag(params map[string]any) (*plugin.Response, error) { - catalogID := strParam(params, "CatalogId", "000000000000") + catalogID := strParam(params, "CatalogId", plugin.DefaultAccountID) key, _ := params["TagKey"].(string) if key == "" { return shared.JSONError("ValidationException", "TagKey is required", http.StatusBadRequest), nil @@ -311,7 +307,7 @@ func (p *Provider) updateLFTag(params map[string]any) (*plugin.Response, error) } func (p *Provider) deleteLFTag(params map[string]any) (*plugin.Response, error) { - catalogID := strParam(params, "CatalogId", "000000000000") + catalogID := strParam(params, "CatalogId", plugin.DefaultAccountID) key, _ := params["TagKey"].(string) if key == "" { return shared.JSONError("ValidationException", "TagKey is required", http.StatusBadRequest), nil @@ -323,7 +319,7 @@ func (p *Provider) deleteLFTag(params map[string]any) (*plugin.Response, error) } func (p *Provider) listLFTags(params map[string]any) (*plugin.Response, error) { - catalogID := strParam(params, "CatalogId", "000000000000") + catalogID := strParam(params, "CatalogId", plugin.DefaultAccountID) tags, err := p.store.ListLFTags(catalogID) if err != nil { return nil, err @@ -351,7 +347,7 @@ func (p *Provider) createLFTagExpression(params map[string]any) (*plugin.Respons if name == "" { return shared.JSONError("ValidationException", "Name is required", http.StatusBadRequest), nil } - catalogID := strParam(params, "CatalogId", "000000000000") + catalogID := strParam(params, "CatalogId", plugin.DefaultAccountID) description, _ := params["Description"].(string) rawExpr, _ := params["Expression"].([]any) exprJSON := marshalJSON(rawExpr) @@ -414,7 +410,7 @@ func (p *Provider) deleteLFTagExpression(params map[string]any) (*plugin.Respons } func (p *Provider) listLFTagExpressions(params map[string]any) (*plugin.Response, error) { - catalogID := strParam(params, "CatalogId", "000000000000") + catalogID := strParam(params, "CatalogId", plugin.DefaultAccountID) exprs, err := p.store.ListLFTagExpressions(catalogID) if err != nil { return nil, err @@ -653,7 +649,7 @@ func (p *Provider) listResources(_ map[string]any) (*plugin.Response, error) { // --- DataLakeSettings --- func (p *Provider) getDataLakeSettings(params map[string]any) (*plugin.Response, error) { - catalogID := strParam(params, "CatalogId", "000000000000") + catalogID := strParam(params, "CatalogId", plugin.DefaultAccountID) st, err := p.store.GetSettings(catalogID) if err != nil { return nil, err @@ -677,7 +673,7 @@ func (p *Provider) getDataLakeSettings(params map[string]any) (*plugin.Response, } func (p *Provider) putDataLakeSettings(params map[string]any) (*plugin.Response, error) { - catalogID := strParam(params, "CatalogId", "000000000000") + catalogID := strParam(params, "CatalogId", plugin.DefaultAccountID) settingsRaw, _ := params["DataLakeSettings"].(map[string]any) if settingsRaw == nil { settingsRaw = map[string]any{} diff --git a/internal/services/lambda/provider.go b/internal/services/lambda/provider.go index 1895c5c..74f80b6 100644 --- a/internal/services/lambda/provider.go +++ b/internal/services/lambda/provider.go @@ -17,11 +17,12 @@ import ( "strings" "github.com/skyoo2003/devcloud/internal/plugin" + "github.com/skyoo2003/devcloud/internal/shared" ) const ( defaultAccountID = plugin.DefaultAccountID - defaultRegion = "us-east-1" + defaultRegion = shared.DefaultRegion lambdaAPIPrefix = "/2015-03-31/functions" lambdaTagsPrefix = "/2017-03-31/tags/" lambdaESMPrefix = "/2015-03-31/event-source-mappings" @@ -245,15 +246,6 @@ func (p *LambdaProvider) ListResources(_ context.Context) ([]plugin.Resource, er return resources, nil } -// GetMetrics returns basic metrics for the Lambda service. -func (p *LambdaProvider) GetMetrics(_ context.Context) (*plugin.ServiceMetrics, error) { - functions, err := p.store.ListFunctions(defaultAccountID) - if err != nil { - return nil, err - } - return &plugin.ServiceMetrics{ResourceCount: len(functions)}, nil -} - // --- operation implementations --- // createFunctionRequest is the JSON body sent by boto3 for CreateFunction. diff --git a/internal/services/managedblockchain/provider.go b/internal/services/managedblockchain/provider.go index 0ea4545..1eaaf33 100644 --- a/internal/services/managedblockchain/provider.go +++ b/internal/services/managedblockchain/provider.go @@ -294,10 +294,6 @@ func (p *Provider) ListResources(_ context.Context) ([]plugin.Resource, error) { return res, nil } -func (p *Provider) GetMetrics(_ context.Context) (*plugin.ServiceMetrics, error) { - return &plugin.ServiceMetrics{}, nil -} - // --- Networks --- func (p *Provider) createNetwork(params map[string]any) (*plugin.Response, error) { diff --git a/internal/services/mediaconvert/provider.go b/internal/services/mediaconvert/provider.go index 6514799..a4f7f57 100644 --- a/internal/services/mediaconvert/provider.go +++ b/internal/services/mediaconvert/provider.go @@ -188,11 +188,6 @@ func (p *MediaConvertProvider) ListResources(_ context.Context) ([]plugin.Resour return res, nil } -// GetMetrics returns empty metrics. -func (p *MediaConvertProvider) GetMetrics(_ context.Context) (*plugin.ServiceMetrics, error) { - return &plugin.ServiceMetrics{}, nil -} - // --- Job handlers --- func (p *MediaConvertProvider) handleCreateJob(params map[string]any) (*plugin.Response, error) { diff --git a/internal/services/mediaconvert/store.go b/internal/services/mediaconvert/store.go index 281d178..b33f7e3 100644 --- a/internal/services/mediaconvert/store.go +++ b/internal/services/mediaconvert/store.go @@ -11,9 +11,11 @@ import ( "time" "github.com/skyoo2003/devcloud/internal/storage/sqlite" + + "github.com/skyoo2003/devcloud/internal/shared" ) -const region = "us-east-1" +const region = shared.DefaultRegion var ( errJobTemplateNotFound = errors.New("job template not found") diff --git a/internal/services/memorydb/provider.go b/internal/services/memorydb/provider.go index b503af6..740234c 100644 --- a/internal/services/memorydb/provider.go +++ b/internal/services/memorydb/provider.go @@ -188,10 +188,6 @@ func (p *Provider) ListResources(_ context.Context) ([]plugin.Resource, error) { return res, nil } -func (p *Provider) GetMetrics(_ context.Context) (*plugin.ServiceMetrics, error) { - return &plugin.ServiceMetrics{}, nil -} - // ---- Cluster handlers ---- func (p *Provider) createCluster(params map[string]any) (*plugin.Response, error) { diff --git a/internal/services/mq/provider.go b/internal/services/mq/provider.go index e3a4aea..16b1b3a 100644 --- a/internal/services/mq/provider.go +++ b/internal/services/mq/provider.go @@ -263,10 +263,6 @@ func (p *Provider) ListResources(_ context.Context) ([]plugin.Resource, error) { return res, nil } -func (p *Provider) GetMetrics(_ context.Context) (*plugin.ServiceMetrics, error) { - return &plugin.ServiceMetrics{}, nil -} - // ---- Broker ---- func (p *Provider) createBroker(params map[string]any) (*plugin.Response, error) { diff --git a/internal/services/mwaa/provider.go b/internal/services/mwaa/provider.go index e0a41e4..a2a4b13 100644 --- a/internal/services/mwaa/provider.go +++ b/internal/services/mwaa/provider.go @@ -326,10 +326,6 @@ func (p *Provider) ListResources(_ context.Context) ([]plugin.Resource, error) { return res, nil } -func (p *Provider) GetMetrics(_ context.Context) (*plugin.ServiceMetrics, error) { - return &plugin.ServiceMetrics{}, nil -} - // --- Environment CRUD --- func (p *Provider) createEnvironment(name string, params map[string]any) (*plugin.Response, error) { diff --git a/internal/services/neptune/provider.go b/internal/services/neptune/provider.go index 0bdafeb..1427d5e 100644 --- a/internal/services/neptune/provider.go +++ b/internal/services/neptune/provider.go @@ -244,10 +244,6 @@ func (p *Provider) ListResources(ctx context.Context) ([]plugin.Resource, error) return out, nil } -func (p *Provider) GetMetrics(_ context.Context) (*plugin.ServiceMetrics, error) { - return &plugin.ServiceMetrics{}, nil -} - // --- helpers --- func dbError(code, msg string, status int) *plugin.Response { diff --git a/internal/services/opensearch/provider.go b/internal/services/opensearch/provider.go index 1f0f76c..f9fda23 100644 --- a/internal/services/opensearch/provider.go +++ b/internal/services/opensearch/provider.go @@ -269,10 +269,6 @@ func (p *Provider) ListResources(_ context.Context) ([]plugin.Resource, error) { return res, nil } -func (p *Provider) GetMetrics(_ context.Context) (*plugin.ServiceMetrics, error) { - return &plugin.ServiceMetrics{}, nil -} - // --- Domain CRUD --- func (p *Provider) createDomain(params map[string]any) (*plugin.Response, error) { diff --git a/internal/services/organizations/provider.go b/internal/services/organizations/provider.go index 64f22cb..a97f71c 100644 --- a/internal/services/organizations/provider.go +++ b/internal/services/organizations/provider.go @@ -168,10 +168,6 @@ func (p *Provider) ListResources(_ context.Context) ([]plugin.Resource, error) { return res, nil } -func (p *Provider) GetMetrics(_ context.Context) (*plugin.ServiceMetrics, error) { - return &plugin.ServiceMetrics{}, nil -} - // ---- Organization ---- func (p *Provider) createOrganization(params map[string]any) (*plugin.Response, error) { diff --git a/internal/services/pinpoint/provider.go b/internal/services/pinpoint/provider.go index 34a680f..9321cf4 100644 --- a/internal/services/pinpoint/provider.go +++ b/internal/services/pinpoint/provider.go @@ -47,10 +47,6 @@ func (p *Provider) ListResources(_ context.Context) ([]plugin.Resource, error) { return []plugin.Resource{}, nil } -func (p *Provider) GetMetrics(_ context.Context) (*plugin.ServiceMetrics, error) { - return &plugin.ServiceMetrics{}, nil -} - func (p *Provider) HandleRequest(_ context.Context, op string, req *http.Request) (*plugin.Response, error) { body, err := io.ReadAll(req.Body) if err != nil { @@ -361,9 +357,9 @@ func (p *Provider) HandleRequest(_ context.Context, op string, req *http.Request // ── App handlers ────────────────────────────────────────────────────────────── func (p *Provider) createApp(params map[string]any) (*plugin.Response, error) { - name := strParam(params, "Name") + name := shared.StrParam(params, "Name") if name == "" { - name = strParam(params, "name") + name = shared.StrParam(params, "name") } if name == "" { return shared.JSONError("BadRequestException", "Name is required", http.StatusBadRequest), nil @@ -420,12 +416,12 @@ func appToMap(a *App) map[string]any { // ── Campaign handlers ───────────────────────────────────────────────────────── func (p *Provider) createCampaign(appID string, params map[string]any) (*plugin.Response, error) { - name := strParam(params, "Name") + name := shared.StrParam(params, "Name") if name == "" { return shared.JSONError("BadRequestException", "Name is required", http.StatusBadRequest), nil } - description := strParam(params, "Description") - segmentID := strParam(params, "SegmentId") + description := shared.StrParam(params, "Description") + segmentID := shared.StrParam(params, "SegmentId") id := generateID() arn := fmt.Sprintf("arn:aws:mobiletargeting:us-east-1:000000000000:apps/%s/campaigns/%s", appID, id) c, err := p.store.CreateCampaign(id, appID, arn, name, description, segmentID) @@ -456,9 +452,9 @@ func (p *Provider) getCampaigns(appID string) (*plugin.Response, error) { } func (p *Provider) updateCampaign(appID, campaignID string, params map[string]any) (*plugin.Response, error) { - name := strParam(params, "Name") - description := strParam(params, "Description") - segmentID := strParam(params, "SegmentId") + name := shared.StrParam(params, "Name") + description := shared.StrParam(params, "Description") + segmentID := shared.StrParam(params, "SegmentId") c, err := p.store.UpdateCampaign(appID, campaignID, name, description, segmentID) if err != nil { return shared.JSONError("NotFoundException", "Campaign not found", http.StatusNotFound), nil @@ -517,11 +513,11 @@ func campaignToMap(c *Campaign) map[string]any { // ── Segment handlers ────────────────────────────────────────────────────────── func (p *Provider) createSegment(appID string, params map[string]any) (*plugin.Response, error) { - name := strParam(params, "Name") + name := shared.StrParam(params, "Name") if name == "" { return shared.JSONError("BadRequestException", "Name is required", http.StatusBadRequest), nil } - segType := strParam(params, "SegmentType") + segType := shared.StrParam(params, "SegmentType") if segType == "" { segType = "DIMENSIONAL" } @@ -555,7 +551,7 @@ func (p *Provider) getSegments(appID string) (*plugin.Response, error) { } func (p *Provider) updateSegment(appID, segmentID string, params map[string]any) (*plugin.Response, error) { - name := strParam(params, "Name") + name := shared.StrParam(params, "Name") seg, err := p.store.UpdateSegment(appID, segmentID, name) if err != nil { return shared.JSONError("NotFoundException", "Segment not found", http.StatusNotFound), nil @@ -605,7 +601,7 @@ func segmentToMap(seg *Segment) map[string]any { // ── Journey handlers ────────────────────────────────────────────────────────── func (p *Provider) createJourney(appID string, params map[string]any) (*plugin.Response, error) { - name := strParam(params, "Name") + name := shared.StrParam(params, "Name") if name == "" { return shared.JSONError("BadRequestException", "Name is required", http.StatusBadRequest), nil } @@ -639,7 +635,7 @@ func (p *Provider) listJourneys(appID string) (*plugin.Response, error) { } func (p *Provider) updateJourney(appID, journeyID string, params map[string]any) (*plugin.Response, error) { - name := strParam(params, "Name") + name := shared.StrParam(params, "Name") j, err := p.store.UpdateJourney(appID, journeyID, name) if err != nil { return shared.JSONError("NotFoundException", "Journey not found", http.StatusNotFound), nil @@ -648,7 +644,7 @@ func (p *Provider) updateJourney(appID, journeyID string, params map[string]any) } func (p *Provider) updateJourneyState(appID, journeyID string, params map[string]any) (*plugin.Response, error) { - state := strParam(params, "State") + state := shared.StrParam(params, "State") if state == "" { state = "ACTIVE" } @@ -685,16 +681,16 @@ func journeyToMap(j *Journey) map[string]any { // ── Template handlers ───────────────────────────────────────────────────────── func (p *Provider) createTemplate(name, tType string, params map[string]any) (*plugin.Response, error) { - subject := strParam(params, "Subject") - htmlBody := strParam(params, "HtmlPart") + subject := shared.StrParam(params, "Subject") + htmlBody := shared.StrParam(params, "HtmlPart") if htmlBody == "" { - htmlBody = strParam(params, "HtmlBody") + htmlBody = shared.StrParam(params, "HtmlBody") } - textBody := strParam(params, "TextPart") + textBody := shared.StrParam(params, "TextPart") if textBody == "" { - textBody = strParam(params, "TextBody") + textBody = shared.StrParam(params, "TextBody") } - body := strParam(params, "Body") + body := shared.StrParam(params, "Body") _, err := p.store.CreateTemplate(name, tType, subject, htmlBody, textBody, body) if err != nil { return shared.JSONError("InternalServerErrorException", err.Error(), http.StatusInternalServerError), nil @@ -714,16 +710,16 @@ func (p *Provider) getTemplate(name, tType string) (*plugin.Response, error) { } func (p *Provider) updateTemplate(name, tType string, params map[string]any) (*plugin.Response, error) { - subject := strParam(params, "Subject") - htmlBody := strParam(params, "HtmlPart") + subject := shared.StrParam(params, "Subject") + htmlBody := shared.StrParam(params, "HtmlPart") if htmlBody == "" { - htmlBody = strParam(params, "HtmlBody") + htmlBody = shared.StrParam(params, "HtmlBody") } - textBody := strParam(params, "TextPart") + textBody := shared.StrParam(params, "TextPart") if textBody == "" { - textBody = strParam(params, "TextBody") + textBody = shared.StrParam(params, "TextBody") } - body := strParam(params, "Body") + body := shared.StrParam(params, "Body") _, err := p.store.UpdateTemplate(name, tType, subject, htmlBody, textBody, body) if err != nil { return shared.JSONError("NotFoundException", "Template not found", http.StatusNotFound), nil @@ -1014,15 +1010,6 @@ func tagARN(path string) string { // ── Misc helpers ────────────────────────────────────────────────────────────── -func strParam(params map[string]any, key string) string { - if v, ok := params[key]; ok { - if s, ok := v.(string); ok { - return s - } - } - return "" -} - func extractTags(params map[string]any) map[string]string { tags := map[string]string{} for _, key := range []string{"tags", "Tags"} { diff --git a/internal/services/pipes/provider.go b/internal/services/pipes/provider.go index 0657499..c2d2c82 100644 --- a/internal/services/pipes/provider.go +++ b/internal/services/pipes/provider.go @@ -140,10 +140,6 @@ func (p *PipesProvider) ListResources(_ context.Context) ([]plugin.Resource, err return res, nil } -func (p *PipesProvider) GetMetrics(_ context.Context) (*plugin.ServiceMetrics, error) { - return &plugin.ServiceMetrics{}, nil -} - // --- Operations --- func (p *PipesProvider) handleCreatePipe(req *http.Request) (*plugin.Response, error) { diff --git a/internal/services/ram/provider.go b/internal/services/ram/provider.go index 678cab5..3631a83 100644 --- a/internal/services/ram/provider.go +++ b/internal/services/ram/provider.go @@ -216,10 +216,6 @@ func (p *Provider) ListResources(_ context.Context) ([]plugin.Resource, error) { return res, nil } -func (p *Provider) GetMetrics(_ context.Context) (*plugin.ServiceMetrics, error) { - return &plugin.ServiceMetrics{}, nil -} - // --- ResourceShare CRUD --- func (p *Provider) createResourceShare(params map[string]any) (*plugin.Response, error) { diff --git a/internal/services/rds/provider.go b/internal/services/rds/provider.go index 3259b56..21c3103 100644 --- a/internal/services/rds/provider.go +++ b/internal/services/rds/provider.go @@ -220,10 +220,6 @@ func (p *Provider) ListResources(ctx context.Context) ([]plugin.Resource, error) return out, nil } -func (p *Provider) GetMetrics(_ context.Context) (*plugin.ServiceMetrics, error) { - return &plugin.ServiceMetrics{}, nil -} - // --- helpers --- func rdsError(code, msg string, status int) *plugin.Response { diff --git a/internal/services/redshift/provider.go b/internal/services/redshift/provider.go index 8351e4f..d91a76a 100644 --- a/internal/services/redshift/provider.go +++ b/internal/services/redshift/provider.go @@ -154,10 +154,6 @@ func (p *Provider) ListResources(ctx context.Context) ([]plugin.Resource, error) return out, nil } -func (p *Provider) GetMetrics(_ context.Context) (*plugin.ServiceMetrics, error) { - return &plugin.ServiceMetrics{}, nil -} - // --- helpers --- func rsError(code, msg string, status int) *plugin.Response { diff --git a/internal/services/resourcegroups/provider.go b/internal/services/resourcegroups/provider.go index a8a38b8..b8c6394 100644 --- a/internal/services/resourcegroups/provider.go +++ b/internal/services/resourcegroups/provider.go @@ -124,10 +124,6 @@ func (p *Provider) ListResources(_ context.Context) ([]plugin.Resource, error) { return res, nil } -func (p *Provider) GetMetrics(_ context.Context) (*plugin.ServiceMetrics, error) { - return &plugin.ServiceMetrics{}, nil -} - // --- CRUD --- func (p *Provider) createGroup(params map[string]any) (*plugin.Response, error) { diff --git a/internal/services/resourcegroupstaggingapi/provider.go b/internal/services/resourcegroupstaggingapi/provider.go index 086ece1..6cde419 100644 --- a/internal/services/resourcegroupstaggingapi/provider.go +++ b/internal/services/resourcegroupstaggingapi/provider.go @@ -144,10 +144,6 @@ func (p *Provider) ListResources(_ context.Context) ([]plugin.Resource, error) { return []plugin.Resource{}, nil } -func (p *Provider) GetMetrics(_ context.Context) (*plugin.ServiceMetrics, error) { - return &plugin.ServiceMetrics{}, nil -} - func (p *Provider) getResources(params map[string]any) (*plugin.Response, error) { // Parse TagFilters var tagFilters []TagFilter diff --git a/internal/services/route53/provider.go b/internal/services/route53/provider.go index 732d553..ee6ebd5 100644 --- a/internal/services/route53/provider.go +++ b/internal/services/route53/provider.go @@ -179,10 +179,6 @@ func (p *Provider) ListResources(_ context.Context) ([]plugin.Resource, error) { return out, nil } -func (p *Provider) GetMetrics(_ context.Context) (*plugin.ServiceMetrics, error) { - return &plugin.ServiceMetrics{}, nil -} - // --- helpers --- func zoneIDFromPath(path string) string { diff --git a/internal/services/route53resolver/provider.go b/internal/services/route53resolver/provider.go index 6055784..d654048 100644 --- a/internal/services/route53resolver/provider.go +++ b/internal/services/route53resolver/provider.go @@ -235,17 +235,8 @@ func (p *Provider) ListResources(_ context.Context) ([]plugin.Resource, error) { return res, nil } -func (p *Provider) GetMetrics(_ context.Context) (*plugin.ServiceMetrics, error) { - return &plugin.ServiceMetrics{}, nil -} - // ==================== helpers ==================== -func str(params map[string]any, key string) string { - v, _ := params[key].(string) - return v -} - func intParam(params map[string]any, key string) int { switch v := params[key].(type) { case float64: @@ -399,8 +390,8 @@ func fwRuleGroupAssocToMap(r *fwRuleGroupAssocRow) map[string]any { // ==================== ResolverEndpoint ==================== func (p *Provider) createResolverEndpoint(params map[string]any) (*plugin.Response, error) { - name := str(params, "Name") - direction := str(params, "Direction") + name := shared.StrParam(params, "Name") + direction := shared.StrParam(params, "Direction") if direction == "" { direction = "INBOUND" } @@ -449,7 +440,7 @@ func (p *Provider) createResolverEndpoint(params map[string]any) (*plugin.Respon } func (p *Provider) getResolverEndpoint(params map[string]any) (*plugin.Response, error) { - id := str(params, "ResolverEndpointId") + id := shared.StrParam(params, "ResolverEndpointId") if id == "" { return shared.JSONError("ValidationException", "ResolverEndpointId is required", http.StatusBadRequest), nil } @@ -476,8 +467,8 @@ func (p *Provider) listResolverEndpoints(_ map[string]any) (*plugin.Response, er } func (p *Provider) updateResolverEndpoint(params map[string]any) (*plugin.Response, error) { - id := str(params, "ResolverEndpointId") - name := str(params, "Name") + id := shared.StrParam(params, "ResolverEndpointId") + name := shared.StrParam(params, "Name") if err := p.store.UpdateEndpoint(id, name); err != nil { return shared.JSONError("ResourceNotFoundException", "resolver endpoint not found", http.StatusBadRequest), nil } @@ -486,7 +477,7 @@ func (p *Provider) updateResolverEndpoint(params map[string]any) (*plugin.Respon } func (p *Provider) deleteResolverEndpoint(params map[string]any) (*plugin.Response, error) { - id := str(params, "ResolverEndpointId") + id := shared.StrParam(params, "ResolverEndpointId") ep, err := p.store.GetEndpoint(id) if err != nil { return shared.JSONError("ResourceNotFoundException", "resolver endpoint not found", http.StatusBadRequest), nil @@ -499,7 +490,7 @@ func (p *Provider) deleteResolverEndpoint(params map[string]any) (*plugin.Respon } func (p *Provider) associateResolverEndpointIpAddress(params map[string]any) (*plugin.Response, error) { - id := str(params, "ResolverEndpointId") + id := shared.StrParam(params, "ResolverEndpointId") ipMap, _ := params["IpAddress"].(map[string]any) subnet, _ := ipMap["SubnetId"].(string) ip, _ := ipMap["Ip"].(string) @@ -517,7 +508,7 @@ func (p *Provider) associateResolverEndpointIpAddress(params map[string]any) (*p } func (p *Provider) disassociateResolverEndpointIpAddress(params map[string]any) (*plugin.Response, error) { - id := str(params, "ResolverEndpointId") + id := shared.StrParam(params, "ResolverEndpointId") ipMap, _ := params["IpAddress"].(map[string]any) ipID, _ := ipMap["IpId"].(string) ep, err := p.store.DisassociateEndpointIP(id, ipID) @@ -528,7 +519,7 @@ func (p *Provider) disassociateResolverEndpointIpAddress(params map[string]any) } func (p *Provider) listResolverEndpointIpAddresses(params map[string]any) (*plugin.Response, error) { - id := str(params, "ResolverEndpointId") + id := shared.StrParam(params, "ResolverEndpointId") ips, err := p.store.ListEndpointIPs(id) if err != nil { return shared.JSONError("ResourceNotFoundException", "resolver endpoint not found", http.StatusBadRequest), nil @@ -551,16 +542,16 @@ func (p *Provider) listResolverEndpointIpAddresses(params map[string]any) (*plug // ==================== ResolverRule ==================== func (p *Provider) createResolverRule(params map[string]any) (*plugin.Response, error) { - name := str(params, "Name") - domainName := str(params, "DomainName") + name := shared.StrParam(params, "Name") + domainName := shared.StrParam(params, "DomainName") if domainName != "" && !strings.HasSuffix(domainName, ".") { domainName += "." } - ruleType := str(params, "RuleType") + ruleType := shared.StrParam(params, "RuleType") if ruleType == "" { ruleType = "FORWARD" } - endpointID := str(params, "ResolverEndpointId") + endpointID := shared.StrParam(params, "ResolverEndpointId") var targetIPs []map[string]any if raw, ok := params["TargetIps"].([]any); ok { @@ -585,7 +576,7 @@ func (p *Provider) createResolverRule(params map[string]any) (*plugin.Response, } func (p *Provider) getResolverRule(params map[string]any) (*plugin.Response, error) { - id := str(params, "ResolverRuleId") + id := shared.StrParam(params, "ResolverRuleId") rule, err := p.store.GetRule(id) if err != nil { return shared.JSONError("ResourceNotFoundException", "resolver rule not found", http.StatusBadRequest), nil @@ -609,7 +600,7 @@ func (p *Provider) listResolverRules(_ map[string]any) (*plugin.Response, error) } func (p *Provider) updateResolverRule(params map[string]any) (*plugin.Response, error) { - id := str(params, "ResolverRuleId") + id := shared.StrParam(params, "ResolverRuleId") cfg, _ := params["Config"].(map[string]any) name, _ := cfg["Name"].(string) endpointID, _ := cfg["ResolverEndpointId"].(string) @@ -642,7 +633,7 @@ func (p *Provider) updateResolverRule(params map[string]any) (*plugin.Response, } func (p *Provider) deleteResolverRule(params map[string]any) (*plugin.Response, error) { - id := str(params, "ResolverRuleId") + id := shared.StrParam(params, "ResolverRuleId") rule, err := p.store.GetRule(id) if err != nil { return shared.JSONError("ResourceNotFoundException", "resolver rule not found", http.StatusBadRequest), nil @@ -655,9 +646,9 @@ func (p *Provider) deleteResolverRule(params map[string]any) (*plugin.Response, } func (p *Provider) associateResolverRule(params map[string]any) (*plugin.Response, error) { - ruleID := str(params, "ResolverRuleId") - vpcID := str(params, "VPCId") - name := str(params, "Name") + ruleID := shared.StrParam(params, "ResolverRuleId") + vpcID := shared.StrParam(params, "VPCId") + name := shared.StrParam(params, "Name") id := shared.GenerateID("rslvr-ra-", 24) assoc, err := p.store.AssociateRule(id, ruleID, vpcID, name) if err != nil { @@ -667,8 +658,8 @@ func (p *Provider) associateResolverRule(params map[string]any) (*plugin.Respons } func (p *Provider) disassociateResolverRule(params map[string]any) (*plugin.Response, error) { - ruleID := str(params, "ResolverRuleId") - vpcID := str(params, "VPCId") + ruleID := shared.StrParam(params, "ResolverRuleId") + vpcID := shared.StrParam(params, "VPCId") assoc, err := p.store.DisassociateRule(ruleID, vpcID) if err != nil { return shared.JSONError("ResourceNotFoundException", "rule association not found", http.StatusBadRequest), nil @@ -677,7 +668,7 @@ func (p *Provider) disassociateResolverRule(params map[string]any) (*plugin.Resp } func (p *Provider) getResolverRuleAssociation(params map[string]any) (*plugin.Response, error) { - id := str(params, "ResolverRuleAssociationId") + id := shared.StrParam(params, "ResolverRuleAssociationId") assoc, err := p.store.GetRuleAssociation(id) if err != nil { return shared.JSONError("ResourceNotFoundException", "rule association not found", http.StatusBadRequest), nil @@ -711,8 +702,8 @@ func (p *Provider) putResolverRulePolicy(_ map[string]any) (*plugin.Response, er // ==================== QueryLogConfig ==================== func (p *Provider) createResolverQueryLogConfig(params map[string]any) (*plugin.Response, error) { - name := str(params, "Name") - dest := str(params, "DestinationArn") + name := shared.StrParam(params, "Name") + dest := shared.StrParam(params, "DestinationArn") if name == "" { return shared.JSONError("ValidationException", "Name is required", http.StatusBadRequest), nil } @@ -729,7 +720,7 @@ func (p *Provider) createResolverQueryLogConfig(params map[string]any) (*plugin. } func (p *Provider) getResolverQueryLogConfig(params map[string]any) (*plugin.Response, error) { - id := str(params, "ResolverQueryLogConfigId") + id := shared.StrParam(params, "ResolverQueryLogConfigId") cfg, err := p.store.GetQueryLogConfig(id) if err != nil { return shared.JSONError("ResourceNotFoundException", "query log config not found", http.StatusBadRequest), nil @@ -754,7 +745,7 @@ func (p *Provider) listResolverQueryLogConfigs(_ map[string]any) (*plugin.Respon } func (p *Provider) deleteResolverQueryLogConfig(params map[string]any) (*plugin.Response, error) { - id := str(params, "ResolverQueryLogConfigId") + id := shared.StrParam(params, "ResolverQueryLogConfigId") cfg, err := p.store.GetQueryLogConfig(id) if err != nil { return shared.JSONError("ResourceNotFoundException", "query log config not found", http.StatusBadRequest), nil @@ -767,8 +758,8 @@ func (p *Provider) deleteResolverQueryLogConfig(params map[string]any) (*plugin. } func (p *Provider) associateResolverQueryLogConfig(params map[string]any) (*plugin.Response, error) { - configID := str(params, "ResolverQueryLogConfigId") - resourceID := str(params, "ResourceId") + configID := shared.StrParam(params, "ResolverQueryLogConfigId") + resourceID := shared.StrParam(params, "ResourceId") id := shared.GenerateID("rqlca-", 28) assoc, err := p.store.AssociateQueryLogConfig(id, configID, resourceID) if err != nil { @@ -778,8 +769,8 @@ func (p *Provider) associateResolverQueryLogConfig(params map[string]any) (*plug } func (p *Provider) disassociateResolverQueryLogConfig(params map[string]any) (*plugin.Response, error) { - configID := str(params, "ResolverQueryLogConfigId") - resourceID := str(params, "ResourceId") + configID := shared.StrParam(params, "ResolverQueryLogConfigId") + resourceID := shared.StrParam(params, "ResourceId") assoc, err := p.store.DisassociateQueryLogConfig(configID, resourceID) if err != nil { return shared.JSONError("ResourceNotFoundException", "query log association not found", http.StatusBadRequest), nil @@ -788,7 +779,7 @@ func (p *Provider) disassociateResolverQueryLogConfig(params map[string]any) (*p } func (p *Provider) getResolverQueryLogConfigAssociation(params map[string]any) (*plugin.Response, error) { - id := str(params, "ResolverQueryLogConfigAssociationId") + id := shared.StrParam(params, "ResolverQueryLogConfigAssociationId") assoc, err := p.store.GetQueryLogConfigAssociation(id) if err != nil { return shared.JSONError("ResourceNotFoundException", "query log association not found", http.StatusBadRequest), nil @@ -823,7 +814,7 @@ func (p *Provider) putResolverQueryLogConfigPolicy(_ map[string]any) (*plugin.Re // ==================== FirewallRuleGroup ==================== func (p *Provider) createFirewallRuleGroup(params map[string]any) (*plugin.Response, error) { - name := str(params, "Name") + name := shared.StrParam(params, "Name") if name == "" { return shared.JSONError("ValidationException", "Name is required", http.StatusBadRequest), nil } @@ -840,7 +831,7 @@ func (p *Provider) createFirewallRuleGroup(params map[string]any) (*plugin.Respo } func (p *Provider) getFirewallRuleGroup(params map[string]any) (*plugin.Response, error) { - id := str(params, "FirewallRuleGroupId") + id := shared.StrParam(params, "FirewallRuleGroupId") grp, err := p.store.GetFirewallRuleGroup(id) if err != nil { return shared.JSONError("ResourceNotFoundException", "firewall rule group not found", http.StatusBadRequest), nil @@ -865,7 +856,7 @@ func (p *Provider) listFirewallRuleGroups(_ map[string]any) (*plugin.Response, e } func (p *Provider) deleteFirewallRuleGroup(params map[string]any) (*plugin.Response, error) { - id := str(params, "FirewallRuleGroupId") + id := shared.StrParam(params, "FirewallRuleGroupId") grp, err := p.store.GetFirewallRuleGroup(id) if err != nil { return shared.JSONError("ResourceNotFoundException", "firewall rule group not found", http.StatusBadRequest), nil @@ -880,15 +871,15 @@ func (p *Provider) deleteFirewallRuleGroup(params map[string]any) (*plugin.Respo // ==================== FirewallRule ==================== func (p *Provider) createFirewallRule(params map[string]any) (*plugin.Response, error) { - groupID := str(params, "FirewallRuleGroupId") - domainListID := str(params, "FirewallDomainListId") - name := str(params, "Name") + groupID := shared.StrParam(params, "FirewallRuleGroupId") + domainListID := shared.StrParam(params, "FirewallDomainListId") + name := shared.StrParam(params, "Name") priority := intParam(params, "Priority") - action := str(params, "Action") + action := shared.StrParam(params, "Action") if action == "" { action = "BLOCK" } - blockResponse := str(params, "BlockResponse") + blockResponse := shared.StrParam(params, "BlockResponse") if blockResponse == "" { blockResponse = "NODATA" } @@ -903,7 +894,7 @@ func (p *Provider) createFirewallRule(params map[string]any) (*plugin.Response, } func (p *Provider) listFirewallRules(params map[string]any) (*plugin.Response, error) { - groupID := str(params, "FirewallRuleGroupId") + groupID := shared.StrParam(params, "FirewallRuleGroupId") rules, err := p.store.ListFirewallRules(groupID) if err != nil { return nil, err @@ -916,12 +907,12 @@ func (p *Provider) listFirewallRules(params map[string]any) (*plugin.Response, e } func (p *Provider) updateFirewallRule(params map[string]any) (*plugin.Response, error) { - groupID := str(params, "FirewallRuleGroupId") - domainListID := str(params, "FirewallDomainListId") - name := str(params, "Name") + groupID := shared.StrParam(params, "FirewallRuleGroupId") + domainListID := shared.StrParam(params, "FirewallDomainListId") + name := shared.StrParam(params, "Name") priority := intParam(params, "Priority") - action := str(params, "Action") - blockResponse := str(params, "BlockResponse") + action := shared.StrParam(params, "Action") + blockResponse := shared.StrParam(params, "BlockResponse") rule, err := p.store.UpdateFirewallRule(groupID, domainListID, name, priority, action, blockResponse) if err != nil { return shared.JSONError("ResourceNotFoundException", "firewall rule not found", http.StatusBadRequest), nil @@ -930,8 +921,8 @@ func (p *Provider) updateFirewallRule(params map[string]any) (*plugin.Response, } func (p *Provider) deleteFirewallRule(params map[string]any) (*plugin.Response, error) { - groupID := str(params, "FirewallRuleGroupId") - domainListID := str(params, "FirewallDomainListId") + groupID := shared.StrParam(params, "FirewallRuleGroupId") + domainListID := shared.StrParam(params, "FirewallDomainListId") // get before delete for response rules, _ := p.store.ListFirewallRules(groupID) var found *fwRuleRow @@ -953,7 +944,7 @@ func (p *Provider) deleteFirewallRule(params map[string]any) (*plugin.Response, // ==================== FirewallDomainList ==================== func (p *Provider) createFirewallDomainList(params map[string]any) (*plugin.Response, error) { - name := str(params, "Name") + name := shared.StrParam(params, "Name") if name == "" { return shared.JSONError("ValidationException", "Name is required", http.StatusBadRequest), nil } @@ -970,7 +961,7 @@ func (p *Provider) createFirewallDomainList(params map[string]any) (*plugin.Resp } func (p *Provider) getFirewallDomainList(params map[string]any) (*plugin.Response, error) { - id := str(params, "FirewallDomainListId") + id := shared.StrParam(params, "FirewallDomainListId") dl, err := p.store.GetFirewallDomainList(id) if err != nil { return shared.JSONError("ResourceNotFoundException", "firewall domain list not found", http.StatusBadRequest), nil @@ -995,7 +986,7 @@ func (p *Provider) listFirewallDomainLists(_ map[string]any) (*plugin.Response, } func (p *Provider) deleteFirewallDomainList(params map[string]any) (*plugin.Response, error) { - id := str(params, "FirewallDomainListId") + id := shared.StrParam(params, "FirewallDomainListId") dl, err := p.store.GetFirewallDomainList(id) if err != nil { return shared.JSONError("ResourceNotFoundException", "firewall domain list not found", http.StatusBadRequest), nil @@ -1008,7 +999,7 @@ func (p *Provider) deleteFirewallDomainList(params map[string]any) (*plugin.Resp } func (p *Provider) importFirewallDomains(params map[string]any) (*plugin.Response, error) { - id := str(params, "FirewallDomainListId") + id := shared.StrParam(params, "FirewallDomainListId") dl, err := p.store.GetFirewallDomainList(id) if err != nil { return shared.JSONError("ResourceNotFoundException", "firewall domain list not found", http.StatusBadRequest), nil @@ -1022,7 +1013,7 @@ func (p *Provider) importFirewallDomains(params map[string]any) (*plugin.Respons } func (p *Provider) listFirewallDomains(params map[string]any) (*plugin.Response, error) { - id := str(params, "FirewallDomainListId") + id := shared.StrParam(params, "FirewallDomainListId") domains, err := p.store.ListFirewallDomains(id) if err != nil { return nil, err @@ -1034,8 +1025,8 @@ func (p *Provider) listFirewallDomains(params map[string]any) (*plugin.Response, } func (p *Provider) updateFirewallDomains(params map[string]any) (*plugin.Response, error) { - id := str(params, "FirewallDomainListId") - op := str(params, "Operation") + id := shared.StrParam(params, "FirewallDomainListId") + op := shared.StrParam(params, "Operation") if op == "" { op = "ADD" } @@ -1065,9 +1056,9 @@ func (p *Provider) updateFirewallDomains(params map[string]any) (*plugin.Respons // ==================== FirewallRuleGroupAssociation ==================== func (p *Provider) associateFirewallRuleGroup(params map[string]any) (*plugin.Response, error) { - groupID := str(params, "FirewallRuleGroupId") - vpcID := str(params, "VpcId") - name := str(params, "Name") + groupID := shared.StrParam(params, "FirewallRuleGroupId") + vpcID := shared.StrParam(params, "VpcId") + name := shared.StrParam(params, "Name") priority := intParam(params, "Priority") id := shared.GenerateID("rslvr-frgassoc-", 28) assoc, err := p.store.AssociateFirewallRuleGroup(id, groupID, vpcID, name, priority) @@ -1078,7 +1069,7 @@ func (p *Provider) associateFirewallRuleGroup(params map[string]any) (*plugin.Re } func (p *Provider) getFirewallRuleGroupAssociation(params map[string]any) (*plugin.Response, error) { - id := str(params, "FirewallRuleGroupAssociationId") + id := shared.StrParam(params, "FirewallRuleGroupAssociationId") assoc, err := p.store.GetFirewallRuleGroupAssociation(id) if err != nil { return shared.JSONError("ResourceNotFoundException", "firewall rule group association not found", http.StatusBadRequest), nil @@ -1087,7 +1078,7 @@ func (p *Provider) getFirewallRuleGroupAssociation(params map[string]any) (*plug } func (p *Provider) listFirewallRuleGroupAssociations(params map[string]any) (*plugin.Response, error) { - vpcID := str(params, "VpcId") + vpcID := shared.StrParam(params, "VpcId") assocs, err := p.store.ListFirewallRuleGroupAssociations(vpcID) if err != nil { return nil, err @@ -1100,7 +1091,7 @@ func (p *Provider) listFirewallRuleGroupAssociations(params map[string]any) (*pl } func (p *Provider) disassociateFirewallRuleGroup(params map[string]any) (*plugin.Response, error) { - id := str(params, "FirewallRuleGroupAssociationId") + id := shared.StrParam(params, "FirewallRuleGroupAssociationId") assoc, err := p.store.DisassociateFirewallRuleGroup(id) if err != nil { return shared.JSONError("ResourceNotFoundException", "firewall rule group association not found", http.StatusBadRequest), nil @@ -1109,8 +1100,8 @@ func (p *Provider) disassociateFirewallRuleGroup(params map[string]any) (*plugin } func (p *Provider) updateFirewallRuleGroupAssociation(params map[string]any) (*plugin.Response, error) { - id := str(params, "FirewallRuleGroupAssociationId") - name := str(params, "Name") + id := shared.StrParam(params, "FirewallRuleGroupAssociationId") + name := shared.StrParam(params, "Name") priority := intParam(params, "Priority") assoc, err := p.store.UpdateFirewallRuleGroupAssociation(id, name, priority) if err != nil { @@ -1130,7 +1121,7 @@ func (p *Provider) putFirewallRuleGroupPolicy(_ map[string]any) (*plugin.Respons // ==================== FirewallConfig (per-VPC, stub) ==================== func (p *Provider) getFirewallConfig(params map[string]any) (*plugin.Response, error) { - resourceID := str(params, "ResourceId") + resourceID := shared.StrParam(params, "ResourceId") return shared.JSONResponse(http.StatusOK, map[string]any{ "FirewallConfig": map[string]any{ "Id": shared.GenerateID("rslvr-fc-", 24), @@ -1142,8 +1133,8 @@ func (p *Provider) getFirewallConfig(params map[string]any) (*plugin.Response, e } func (p *Provider) updateFirewallConfig(params map[string]any) (*plugin.Response, error) { - resourceID := str(params, "ResourceId") - failOpen := str(params, "FirewallFailOpen") + resourceID := shared.StrParam(params, "ResourceId") + failOpen := shared.StrParam(params, "FirewallFailOpen") return shared.JSONResponse(http.StatusOK, map[string]any{ "FirewallConfig": map[string]any{ "Id": shared.GenerateID("rslvr-fc-", 24), @@ -1161,7 +1152,7 @@ func (p *Provider) listFirewallConfigs(_ map[string]any) (*plugin.Response, erro // ==================== ResolverConfig (stub) ==================== func (p *Provider) getResolverConfig(params map[string]any) (*plugin.Response, error) { - resourceID := str(params, "ResourceId") + resourceID := shared.StrParam(params, "ResourceId") return shared.JSONResponse(http.StatusOK, map[string]any{ "ResolverConfig": map[string]any{ "Id": shared.GenerateID("rslvr-rc-", 24), @@ -1173,8 +1164,8 @@ func (p *Provider) getResolverConfig(params map[string]any) (*plugin.Response, e } func (p *Provider) updateResolverConfig(params map[string]any) (*plugin.Response, error) { - resourceID := str(params, "ResourceId") - flag := str(params, "AutodefinedReverseFlag") + resourceID := shared.StrParam(params, "ResourceId") + flag := shared.StrParam(params, "AutodefinedReverseFlag") return shared.JSONResponse(http.StatusOK, map[string]any{ "ResolverConfig": map[string]any{ "Id": shared.GenerateID("rslvr-rc-", 24), @@ -1192,7 +1183,7 @@ func (p *Provider) listResolverConfigs(_ map[string]any) (*plugin.Response, erro // ==================== ResolverDnssecConfig (stub) ==================== func (p *Provider) getResolverDnssecConfig(params map[string]any) (*plugin.Response, error) { - resourceID := str(params, "ResourceId") + resourceID := shared.StrParam(params, "ResourceId") return shared.JSONResponse(http.StatusOK, map[string]any{ "ResolverDNSSECConfig": map[string]any{ "Id": shared.GenerateID("rslvr-dnssec-", 24), @@ -1204,8 +1195,8 @@ func (p *Provider) getResolverDnssecConfig(params map[string]any) (*plugin.Respo } func (p *Provider) updateResolverDnssecConfig(params map[string]any) (*plugin.Response, error) { - resourceID := str(params, "ResourceId") - validation := str(params, "Validation") + resourceID := shared.StrParam(params, "ResourceId") + validation := shared.StrParam(params, "Validation") return shared.JSONResponse(http.StatusOK, map[string]any{ "ResolverDNSSECConfig": map[string]any{ "Id": shared.GenerateID("rslvr-dnssec-", 24), @@ -1223,7 +1214,7 @@ func (p *Provider) listResolverDnssecConfigs(_ map[string]any) (*plugin.Response // ==================== Tags ==================== func (p *Provider) tagResource(params map[string]any) (*plugin.Response, error) { - arn := str(params, "ResourceArn") + arn := shared.StrParam(params, "ResourceArn") rawTags, _ := params["Tags"].([]any) if err := p.store.tags.AddTags(arn, parseTags(rawTags)); err != nil { return nil, err @@ -1232,7 +1223,7 @@ func (p *Provider) tagResource(params map[string]any) (*plugin.Response, error) } func (p *Provider) untagResource(params map[string]any) (*plugin.Response, error) { - arn := str(params, "ResourceArn") + arn := shared.StrParam(params, "ResourceArn") rawKeys, _ := params["TagKeys"].([]any) keys := make([]string, 0, len(rawKeys)) for _, k := range rawKeys { @@ -1247,7 +1238,7 @@ func (p *Provider) untagResource(params map[string]any) (*plugin.Response, error } func (p *Provider) listTagsForResource(params map[string]any) (*plugin.Response, error) { - arn := str(params, "ResourceArn") + arn := shared.StrParam(params, "ResourceArn") tags, err := p.store.tags.ListTags(arn) if err != nil { return nil, err diff --git a/internal/services/s3/provider.go b/internal/services/s3/provider.go index c8e9056..f14818c 100644 --- a/internal/services/s3/provider.go +++ b/internal/services/s3/provider.go @@ -249,11 +249,6 @@ func (p *S3Provider) ListResources(ctx context.Context) ([]plugin.Resource, erro return resources, nil } -// GetMetrics returns empty metrics. -func (p *S3Provider) GetMetrics(_ context.Context) (*plugin.ServiceMetrics, error) { - return &plugin.ServiceMetrics{}, nil -} - // --- XML response structs --- type listAllMyBucketsResult struct { diff --git a/internal/services/s3tables/provider.go b/internal/services/s3tables/provider.go index 55300aa..cc5cc88 100644 --- a/internal/services/s3tables/provider.go +++ b/internal/services/s3tables/provider.go @@ -149,10 +149,6 @@ func (p *S3TablesProvider) ListResources(_ context.Context) ([]plugin.Resource, return res, nil } -func (p *S3TablesProvider) GetMetrics(_ context.Context) (*plugin.ServiceMetrics, error) { - return &plugin.ServiceMetrics{}, nil -} - // --- Bucket operations --- func (p *S3TablesProvider) createTableBucket(params map[string]any) (*plugin.Response, error) { diff --git a/internal/services/sagemaker/provider.go b/internal/services/sagemaker/provider.go index bd9d115..d44fca5 100644 --- a/internal/services/sagemaker/provider.go +++ b/internal/services/sagemaker/provider.go @@ -240,22 +240,18 @@ func (p *Provider) ListResources(_ context.Context) ([]plugin.Resource, error) { return res, nil } -func (p *Provider) GetMetrics(_ context.Context) (*plugin.ServiceMetrics, error) { - return &plugin.ServiceMetrics{}, nil -} - // ---- NotebookInstance handlers ---- func (p *Provider) createNotebookInstance(params map[string]any) (*plugin.Response, error) { - name := strParam(params, "NotebookInstanceName") + name := shared.StrParam(params, "NotebookInstanceName") if name == "" { return shared.JSONError("ValidationException", "NotebookInstanceName is required", http.StatusBadRequest), nil } - instanceType := strParam(params, "InstanceType") + instanceType := shared.StrParam(params, "InstanceType") if instanceType == "" { instanceType = "ml.t3.medium" } - roleARN := strParam(params, "RoleArn") + roleARN := shared.StrParam(params, "RoleArn") arn := shared.BuildARN("sagemaker", "notebook-instance", name) nb, err := p.store.CreateNotebookInstance(name, arn, "Pending", instanceType, roleARN, "") if err != nil { @@ -268,7 +264,7 @@ func (p *Provider) createNotebookInstance(params map[string]any) (*plugin.Respon } func (p *Provider) describeNotebookInstance(params map[string]any) (*plugin.Response, error) { - name := strParam(params, "NotebookInstanceName") + name := shared.StrParam(params, "NotebookInstanceName") nb, err := p.store.GetNotebookInstance(name) if err != nil { return shared.JSONError("ResourceNotFound", "notebook instance not found: "+name, http.StatusBadRequest), nil @@ -303,7 +299,7 @@ func (p *Provider) listNotebookInstances(_ map[string]any) (*plugin.Response, er } func (p *Provider) deleteNotebookInstance(params map[string]any) (*plugin.Response, error) { - name := strParam(params, "NotebookInstanceName") + name := shared.StrParam(params, "NotebookInstanceName") if err := p.store.DeleteNotebookInstance(name); err != nil { return shared.JSONError("ResourceNotFound", "notebook instance not found: "+name, http.StatusBadRequest), nil } @@ -311,7 +307,7 @@ func (p *Provider) deleteNotebookInstance(params map[string]any) (*plugin.Respon } func (p *Provider) startNotebookInstance(params map[string]any) (*plugin.Response, error) { - name := strParam(params, "NotebookInstanceName") + name := shared.StrParam(params, "NotebookInstanceName") if err := p.store.UpdateNotebookInstanceStatus(name, "InService"); err != nil { return shared.JSONError("ResourceNotFound", "notebook instance not found: "+name, http.StatusBadRequest), nil } @@ -319,7 +315,7 @@ func (p *Provider) startNotebookInstance(params map[string]any) (*plugin.Respons } func (p *Provider) stopNotebookInstance(params map[string]any) (*plugin.Response, error) { - name := strParam(params, "NotebookInstanceName") + name := shared.StrParam(params, "NotebookInstanceName") if err := p.store.UpdateNotebookInstanceStatus(name, "Stopped"); err != nil { return shared.JSONError("ResourceNotFound", "notebook instance not found: "+name, http.StatusBadRequest), nil } @@ -327,8 +323,8 @@ func (p *Provider) stopNotebookInstance(params map[string]any) (*plugin.Response } func (p *Provider) updateNotebookInstance(params map[string]any) (*plugin.Response, error) { - name := strParam(params, "NotebookInstanceName") - instanceType := strParam(params, "InstanceType") + name := shared.StrParam(params, "NotebookInstanceName") + instanceType := shared.StrParam(params, "InstanceType") if instanceType == "" { nb, err := p.store.GetNotebookInstance(name) if err != nil { @@ -336,7 +332,7 @@ func (p *Provider) updateNotebookInstance(params map[string]any) (*plugin.Respon } instanceType = nb.InstanceType } - roleARN := strParam(params, "RoleArn") + roleARN := shared.StrParam(params, "RoleArn") if roleARN == "" { nb, err := p.store.GetNotebookInstance(name) if err != nil { @@ -353,11 +349,11 @@ func (p *Provider) updateNotebookInstance(params map[string]any) (*plugin.Respon // ---- Model handlers ---- func (p *Provider) createModel(params map[string]any) (*plugin.Response, error) { - name := strParam(params, "ModelName") + name := shared.StrParam(params, "ModelName") if name == "" { return shared.JSONError("ValidationException", "ModelName is required", http.StatusBadRequest), nil } - executionRole := strParam(params, "ExecutionRoleArn") + executionRole := shared.StrParam(params, "ExecutionRoleArn") primaryContainer := marshalParam(params, "PrimaryContainer") arn := shared.BuildARN("sagemaker", "model", name) m, err := p.store.CreateModel(name, arn, executionRole, primaryContainer) @@ -371,7 +367,7 @@ func (p *Provider) createModel(params map[string]any) (*plugin.Response, error) } func (p *Provider) describeModel(params map[string]any) (*plugin.Response, error) { - name := strParam(params, "ModelName") + name := shared.StrParam(params, "ModelName") m, err := p.store.GetModel(name) if err != nil { return shared.JSONError("ResourceNotFound", "model not found: "+name, http.StatusBadRequest), nil @@ -404,7 +400,7 @@ func (p *Provider) listModels(_ map[string]any) (*plugin.Response, error) { } func (p *Provider) deleteModel(params map[string]any) (*plugin.Response, error) { - name := strParam(params, "ModelName") + name := shared.StrParam(params, "ModelName") if err := p.store.DeleteModel(name); err != nil { return shared.JSONError("ResourceNotFound", "model not found: "+name, http.StatusBadRequest), nil } @@ -414,7 +410,7 @@ func (p *Provider) deleteModel(params map[string]any) (*plugin.Response, error) // ---- EndpointConfig handlers ---- func (p *Provider) createEndpointConfig(params map[string]any) (*plugin.Response, error) { - name := strParam(params, "EndpointConfigName") + name := shared.StrParam(params, "EndpointConfigName") if name == "" { return shared.JSONError("ValidationException", "EndpointConfigName is required", http.StatusBadRequest), nil } @@ -431,7 +427,7 @@ func (p *Provider) createEndpointConfig(params map[string]any) (*plugin.Response } func (p *Provider) describeEndpointConfig(params map[string]any) (*plugin.Response, error) { - name := strParam(params, "EndpointConfigName") + name := shared.StrParam(params, "EndpointConfigName") ec, err := p.store.GetEndpointConfig(name) if err != nil { return shared.JSONError("ResourceNotFound", "endpoint config not found: "+name, http.StatusBadRequest), nil @@ -463,7 +459,7 @@ func (p *Provider) listEndpointConfigs(_ map[string]any) (*plugin.Response, erro } func (p *Provider) deleteEndpointConfig(params map[string]any) (*plugin.Response, error) { - name := strParam(params, "EndpointConfigName") + name := shared.StrParam(params, "EndpointConfigName") if err := p.store.DeleteEndpointConfig(name); err != nil { return shared.JSONError("ResourceNotFound", "endpoint config not found: "+name, http.StatusBadRequest), nil } @@ -473,11 +469,11 @@ func (p *Provider) deleteEndpointConfig(params map[string]any) (*plugin.Response // ---- Endpoint handlers ---- func (p *Provider) createEndpoint(params map[string]any) (*plugin.Response, error) { - name := strParam(params, "EndpointName") + name := shared.StrParam(params, "EndpointName") if name == "" { return shared.JSONError("ValidationException", "EndpointName is required", http.StatusBadRequest), nil } - configName := strParam(params, "EndpointConfigName") + configName := shared.StrParam(params, "EndpointConfigName") arn := shared.BuildARN("sagemaker", "endpoint", name) e, err := p.store.CreateEndpoint(name, arn, configName, "Creating") if err != nil { @@ -492,7 +488,7 @@ func (p *Provider) createEndpoint(params map[string]any) (*plugin.Response, erro } func (p *Provider) describeEndpoint(params map[string]any) (*plugin.Response, error) { - name := strParam(params, "EndpointName") + name := shared.StrParam(params, "EndpointName") e, err := p.store.GetEndpoint(name) if err != nil { return shared.JSONError("ResourceNotFound", "endpoint not found: "+name, http.StatusBadRequest), nil @@ -525,8 +521,8 @@ func (p *Provider) listEndpoints(_ map[string]any) (*plugin.Response, error) { } func (p *Provider) updateEndpoint(params map[string]any) (*plugin.Response, error) { - name := strParam(params, "EndpointName") - configName := strParam(params, "EndpointConfigName") + name := shared.StrParam(params, "EndpointName") + configName := shared.StrParam(params, "EndpointConfigName") e, err := p.store.GetEndpoint(name) if err != nil { return shared.JSONError("ResourceNotFound", "endpoint not found: "+name, http.StatusBadRequest), nil @@ -538,7 +534,7 @@ func (p *Provider) updateEndpoint(params map[string]any) (*plugin.Response, erro } func (p *Provider) deleteEndpoint(params map[string]any) (*plugin.Response, error) { - name := strParam(params, "EndpointName") + name := shared.StrParam(params, "EndpointName") if err := p.store.DeleteEndpoint(name); err != nil { return shared.JSONError("ResourceNotFound", "endpoint not found: "+name, http.StatusBadRequest), nil } @@ -548,11 +544,11 @@ func (p *Provider) deleteEndpoint(params map[string]any) (*plugin.Response, erro // ---- TrainingJob handlers ---- func (p *Provider) createTrainingJob(params map[string]any) (*plugin.Response, error) { - name := strParam(params, "TrainingJobName") + name := shared.StrParam(params, "TrainingJobName") if name == "" { return shared.JSONError("ValidationException", "TrainingJobName is required", http.StatusBadRequest), nil } - roleARN := strParam(params, "RoleArn") + roleARN := shared.StrParam(params, "RoleArn") algorithm := marshalParam(params, "AlgorithmSpecification") inputConfig := marshalParamArray(params, "InputDataConfig") outputConfig := marshalParam(params, "OutputDataConfig") @@ -569,7 +565,7 @@ func (p *Provider) createTrainingJob(params map[string]any) (*plugin.Response, e } func (p *Provider) describeTrainingJob(params map[string]any) (*plugin.Response, error) { - name := strParam(params, "TrainingJobName") + name := shared.StrParam(params, "TrainingJobName") tj, err := p.store.GetTrainingJob(name) if err != nil { return shared.JSONError("ResourceNotFound", "training job not found: "+name, http.StatusBadRequest), nil @@ -610,7 +606,7 @@ func (p *Provider) listTrainingJobs(_ map[string]any) (*plugin.Response, error) } func (p *Provider) stopTrainingJob(params map[string]any) (*plugin.Response, error) { - name := strParam(params, "TrainingJobName") + name := shared.StrParam(params, "TrainingJobName") if err := p.store.UpdateTrainingJobStatus(name, "Stopped"); err != nil { return shared.JSONError("ResourceNotFound", "training job not found: "+name, http.StatusBadRequest), nil } @@ -620,11 +616,11 @@ func (p *Provider) stopTrainingJob(params map[string]any) (*plugin.Response, err // ---- ProcessingJob handlers ---- func (p *Provider) createProcessingJob(params map[string]any) (*plugin.Response, error) { - name := strParam(params, "ProcessingJobName") + name := shared.StrParam(params, "ProcessingJobName") if name == "" { return shared.JSONError("ValidationException", "ProcessingJobName is required", http.StatusBadRequest), nil } - roleARN := strParam(params, "RoleArn") + roleARN := shared.StrParam(params, "RoleArn") appSpec := marshalParam(params, "AppSpecification") inputs := marshalParamArray(params, "ProcessingInputs") outputs := marshalParamArray(params, "ProcessingOutputConfig") @@ -641,7 +637,7 @@ func (p *Provider) createProcessingJob(params map[string]any) (*plugin.Response, } func (p *Provider) describeProcessingJob(params map[string]any) (*plugin.Response, error) { - name := strParam(params, "ProcessingJobName") + name := shared.StrParam(params, "ProcessingJobName") pj, err := p.store.GetProcessingJob(name) if err != nil { return shared.JSONError("ResourceNotFound", "processing job not found: "+name, http.StatusBadRequest), nil @@ -682,7 +678,7 @@ func (p *Provider) listProcessingJobs(_ map[string]any) (*plugin.Response, error } func (p *Provider) stopProcessingJob(params map[string]any) (*plugin.Response, error) { - name := strParam(params, "ProcessingJobName") + name := shared.StrParam(params, "ProcessingJobName") if err := p.store.UpdateProcessingJobStatus(name, "Stopped"); err != nil { return shared.JSONError("ResourceNotFound", "processing job not found: "+name, http.StatusBadRequest), nil } @@ -692,11 +688,11 @@ func (p *Provider) stopProcessingJob(params map[string]any) (*plugin.Response, e // ---- TransformJob handlers ---- func (p *Provider) createTransformJob(params map[string]any) (*plugin.Response, error) { - name := strParam(params, "TransformJobName") + name := shared.StrParam(params, "TransformJobName") if name == "" { return shared.JSONError("ValidationException", "TransformJobName is required", http.StatusBadRequest), nil } - modelName := strParam(params, "ModelName") + modelName := shared.StrParam(params, "ModelName") input := marshalParam(params, "TransformInput") output := marshalParam(params, "TransformOutput") resources := marshalParam(params, "TransformResources") @@ -712,7 +708,7 @@ func (p *Provider) createTransformJob(params map[string]any) (*plugin.Response, } func (p *Provider) describeTransformJob(params map[string]any) (*plugin.Response, error) { - name := strParam(params, "TransformJobName") + name := shared.StrParam(params, "TransformJobName") tj, err := p.store.GetTransformJob(name) if err != nil { return shared.JSONError("ResourceNotFound", "transform job not found: "+name, http.StatusBadRequest), nil @@ -751,7 +747,7 @@ func (p *Provider) listTransformJobs(_ map[string]any) (*plugin.Response, error) } func (p *Provider) stopTransformJob(params map[string]any) (*plugin.Response, error) { - name := strParam(params, "TransformJobName") + name := shared.StrParam(params, "TransformJobName") if err := p.store.UpdateTransformJobStatus(name, "Stopped"); err != nil { return shared.JSONError("ResourceNotFound", "transform job not found: "+name, http.StatusBadRequest), nil } @@ -761,13 +757,13 @@ func (p *Provider) stopTransformJob(params map[string]any) (*plugin.Response, er // ---- Pipeline handlers ---- func (p *Provider) createPipeline(params map[string]any) (*plugin.Response, error) { - name := strParam(params, "PipelineName") + name := shared.StrParam(params, "PipelineName") if name == "" { return shared.JSONError("ValidationException", "PipelineName is required", http.StatusBadRequest), nil } - roleARN := strParam(params, "RoleArn") - definition := strParam(params, "PipelineDefinition") - description := strParam(params, "PipelineDescription") + roleARN := shared.StrParam(params, "RoleArn") + definition := shared.StrParam(params, "PipelineDefinition") + description := shared.StrParam(params, "PipelineDescription") arn := shared.BuildARN("sagemaker", "pipeline", name) pl, err := p.store.CreatePipeline(name, arn, roleARN, definition, description) if err != nil { @@ -780,7 +776,7 @@ func (p *Provider) createPipeline(params map[string]any) (*plugin.Response, erro } func (p *Provider) describePipeline(params map[string]any) (*plugin.Response, error) { - name := strParam(params, "PipelineName") + name := shared.StrParam(params, "PipelineName") pl, err := p.store.GetPipeline(name) if err != nil { return shared.JSONError("ResourceNotFound", "pipeline not found: "+name, http.StatusBadRequest), nil @@ -816,20 +812,20 @@ func (p *Provider) listPipelines(_ map[string]any) (*plugin.Response, error) { } func (p *Provider) updatePipeline(params map[string]any) (*plugin.Response, error) { - name := strParam(params, "PipelineName") + name := shared.StrParam(params, "PipelineName") pl, err := p.store.GetPipeline(name) if err != nil { return shared.JSONError("ResourceNotFound", "pipeline not found: "+name, http.StatusBadRequest), nil } - roleARN := strParam(params, "RoleArn") + roleARN := shared.StrParam(params, "RoleArn") if roleARN == "" { roleARN = pl.RoleARN } - definition := strParam(params, "PipelineDefinition") + definition := shared.StrParam(params, "PipelineDefinition") if definition == "" { definition = pl.Definition } - description := strParam(params, "PipelineDescription") + description := shared.StrParam(params, "PipelineDescription") if description == "" { description = pl.Description } @@ -840,7 +836,7 @@ func (p *Provider) updatePipeline(params map[string]any) (*plugin.Response, erro } func (p *Provider) deletePipeline(params map[string]any) (*plugin.Response, error) { - name := strParam(params, "PipelineName") + name := shared.StrParam(params, "PipelineName") pl, err := p.store.GetPipeline(name) if err != nil { return shared.JSONError("ResourceNotFound", "pipeline not found: "+name, http.StatusBadRequest), nil @@ -852,7 +848,7 @@ func (p *Provider) deletePipeline(params map[string]any) (*plugin.Response, erro } func (p *Provider) startPipelineExecution(params map[string]any) (*plugin.Response, error) { - name := strParam(params, "PipelineName") + name := shared.StrParam(params, "PipelineName") if _, err := p.store.GetPipeline(name); err != nil { return shared.JSONError("ResourceNotFound", "pipeline not found: "+name, http.StatusBadRequest), nil } @@ -865,7 +861,7 @@ func (p *Provider) startPipelineExecution(params map[string]any) (*plugin.Respon } func (p *Provider) describePipelineExecution(params map[string]any) (*plugin.Response, error) { - arn := strParam(params, "PipelineExecutionArn") + arn := shared.StrParam(params, "PipelineExecutionArn") pe, err := p.store.GetPipelineExecution(arn) if err != nil { return shared.JSONError("ResourceNotFound", "pipeline execution not found: "+arn, http.StatusBadRequest), nil @@ -879,7 +875,7 @@ func (p *Provider) describePipelineExecution(params map[string]any) (*plugin.Res } func (p *Provider) listPipelineExecutions(params map[string]any) (*plugin.Response, error) { - name := strParam(params, "PipelineName") + name := shared.StrParam(params, "PipelineName") executions, err := p.store.ListPipelineExecutions(name) if err != nil { return nil, err @@ -896,7 +892,7 @@ func (p *Provider) listPipelineExecutions(params map[string]any) (*plugin.Respon } func (p *Provider) stopPipelineExecution(params map[string]any) (*plugin.Response, error) { - arn := strParam(params, "PipelineExecutionArn") + arn := shared.StrParam(params, "PipelineExecutionArn") if err := p.store.UpdatePipelineExecutionStatus(arn, "Stopped"); err != nil { return shared.JSONError("ResourceNotFound", "pipeline execution not found: "+arn, http.StatusBadRequest), nil } @@ -906,11 +902,11 @@ func (p *Provider) stopPipelineExecution(params map[string]any) (*plugin.Respons // ---- Experiment handlers ---- func (p *Provider) createExperiment(params map[string]any) (*plugin.Response, error) { - name := strParam(params, "ExperimentName") + name := shared.StrParam(params, "ExperimentName") if name == "" { return shared.JSONError("ValidationException", "ExperimentName is required", http.StatusBadRequest), nil } - description := strParam(params, "Description") + description := shared.StrParam(params, "Description") arn := shared.BuildARN("sagemaker", "experiment", name) e, err := p.store.CreateExperiment(name, arn, description) if err != nil { @@ -923,7 +919,7 @@ func (p *Provider) createExperiment(params map[string]any) (*plugin.Response, er } func (p *Provider) describeExperiment(params map[string]any) (*plugin.Response, error) { - name := strParam(params, "ExperimentName") + name := shared.StrParam(params, "ExperimentName") e, err := p.store.GetExperiment(name) if err != nil { return shared.JSONError("ResourceNotFound", "experiment not found: "+name, http.StatusBadRequest), nil @@ -953,8 +949,8 @@ func (p *Provider) listExperiments(_ map[string]any) (*plugin.Response, error) { } func (p *Provider) updateExperiment(params map[string]any) (*plugin.Response, error) { - name := strParam(params, "ExperimentName") - description := strParam(params, "Description") + name := shared.StrParam(params, "ExperimentName") + description := shared.StrParam(params, "Description") e, err := p.store.GetExperiment(name) if err != nil { return shared.JSONError("ResourceNotFound", "experiment not found: "+name, http.StatusBadRequest), nil @@ -966,7 +962,7 @@ func (p *Provider) updateExperiment(params map[string]any) (*plugin.Response, er } func (p *Provider) deleteExperiment(params map[string]any) (*plugin.Response, error) { - name := strParam(params, "ExperimentName") + name := shared.StrParam(params, "ExperimentName") e, err := p.store.GetExperiment(name) if err != nil { return shared.JSONError("ResourceNotFound", "experiment not found: "+name, http.StatusBadRequest), nil @@ -980,11 +976,11 @@ func (p *Provider) deleteExperiment(params map[string]any) (*plugin.Response, er // ---- Trial handlers ---- func (p *Provider) createTrial(params map[string]any) (*plugin.Response, error) { - name := strParam(params, "TrialName") + name := shared.StrParam(params, "TrialName") if name == "" { return shared.JSONError("ValidationException", "TrialName is required", http.StatusBadRequest), nil } - experimentName := strParam(params, "ExperimentName") + experimentName := shared.StrParam(params, "ExperimentName") arn := shared.BuildARN("sagemaker", "experiment-trial", name) tr, err := p.store.CreateTrial(name, arn, experimentName) if err != nil { @@ -997,7 +993,7 @@ func (p *Provider) createTrial(params map[string]any) (*plugin.Response, error) } func (p *Provider) describeTrial(params map[string]any) (*plugin.Response, error) { - name := strParam(params, "TrialName") + name := shared.StrParam(params, "TrialName") tr, err := p.store.GetTrial(name) if err != nil { return shared.JSONError("ResourceNotFound", "trial not found: "+name, http.StatusBadRequest), nil @@ -1011,7 +1007,7 @@ func (p *Provider) describeTrial(params map[string]any) (*plugin.Response, error } func (p *Provider) listTrials(params map[string]any) (*plugin.Response, error) { - experimentName := strParam(params, "ExperimentName") + experimentName := shared.StrParam(params, "ExperimentName") trials, err := p.store.ListTrials(experimentName) if err != nil { return nil, err @@ -1029,8 +1025,8 @@ func (p *Provider) listTrials(params map[string]any) (*plugin.Response, error) { } func (p *Provider) updateTrial(params map[string]any) (*plugin.Response, error) { - name := strParam(params, "TrialName") - experimentName := strParam(params, "ExperimentName") + name := shared.StrParam(params, "TrialName") + experimentName := shared.StrParam(params, "ExperimentName") tr, err := p.store.GetTrial(name) if err != nil { return shared.JSONError("ResourceNotFound", "trial not found: "+name, http.StatusBadRequest), nil @@ -1045,7 +1041,7 @@ func (p *Provider) updateTrial(params map[string]any) (*plugin.Response, error) } func (p *Provider) deleteTrial(params map[string]any) (*plugin.Response, error) { - name := strParam(params, "TrialName") + name := shared.StrParam(params, "TrialName") tr, err := p.store.GetTrial(name) if err != nil { return shared.JSONError("ResourceNotFound", "trial not found: "+name, http.StatusBadRequest), nil @@ -1059,15 +1055,15 @@ func (p *Provider) deleteTrial(params map[string]any) (*plugin.Response, error) // ---- Domain handlers ---- func (p *Provider) createDomain(params map[string]any) (*plugin.Response, error) { - name := strParam(params, "DomainName") + name := shared.StrParam(params, "DomainName") if name == "" { return shared.JSONError("ValidationException", "DomainName is required", http.StatusBadRequest), nil } - authMode := strParam(params, "AuthMode") + authMode := shared.StrParam(params, "AuthMode") if authMode == "" { authMode = "IAM" } - vpcID := strParam(params, "VpcId") + vpcID := shared.StrParam(params, "VpcId") id := shared.GenerateID("d-", 16) arn := shared.BuildARN("sagemaker", "domain", id) d, err := p.store.CreateDomain(id, arn, name, "InService", authMode, vpcID) @@ -1081,7 +1077,7 @@ func (p *Provider) createDomain(params map[string]any) (*plugin.Response, error) } func (p *Provider) describeDomain(params map[string]any) (*plugin.Response, error) { - id := strParam(params, "DomainId") + id := shared.StrParam(params, "DomainId") d, err := p.store.GetDomain(id) if err != nil { return shared.JSONError("ResourceNotFound", "domain not found: "+id, http.StatusBadRequest), nil @@ -1116,12 +1112,12 @@ func (p *Provider) listDomains(_ map[string]any) (*plugin.Response, error) { } func (p *Provider) updateDomain(params map[string]any) (*plugin.Response, error) { - id := strParam(params, "DomainId") + id := shared.StrParam(params, "DomainId") d, err := p.store.GetDomain(id) if err != nil { return shared.JSONError("ResourceNotFound", "domain not found: "+id, http.StatusBadRequest), nil } - vpcID := strParam(params, "VpcId") + vpcID := shared.StrParam(params, "VpcId") if vpcID == "" { vpcID = d.VpcID } @@ -1132,7 +1128,7 @@ func (p *Provider) updateDomain(params map[string]any) (*plugin.Response, error) } func (p *Provider) deleteDomain(params map[string]any) (*plugin.Response, error) { - id := strParam(params, "DomainId") + id := shared.StrParam(params, "DomainId") if err := p.store.DeleteDomain(id); err != nil { return shared.JSONError("ResourceNotFound", "domain not found: "+id, http.StatusBadRequest), nil } @@ -1142,8 +1138,8 @@ func (p *Provider) deleteDomain(params map[string]any) (*plugin.Response, error) // ---- UserProfile handlers ---- func (p *Provider) createUserProfile(params map[string]any) (*plugin.Response, error) { - domainID := strParam(params, "DomainId") - name := strParam(params, "UserProfileName") + domainID := shared.StrParam(params, "DomainId") + name := shared.StrParam(params, "UserProfileName") if domainID == "" || name == "" { return shared.JSONError("ValidationException", "DomainId and UserProfileName are required", http.StatusBadRequest), nil } @@ -1159,8 +1155,8 @@ func (p *Provider) createUserProfile(params map[string]any) (*plugin.Response, e } func (p *Provider) describeUserProfile(params map[string]any) (*plugin.Response, error) { - domainID := strParam(params, "DomainId") - name := strParam(params, "UserProfileName") + domainID := shared.StrParam(params, "DomainId") + name := shared.StrParam(params, "UserProfileName") up, err := p.store.GetUserProfile(domainID, name) if err != nil { return shared.JSONError("ResourceNotFound", "user profile not found: "+name, http.StatusBadRequest), nil @@ -1175,7 +1171,7 @@ func (p *Provider) describeUserProfile(params map[string]any) (*plugin.Response, } func (p *Provider) listUserProfiles(params map[string]any) (*plugin.Response, error) { - domainID := strParam(params, "DomainIdEquals") + domainID := shared.StrParam(params, "DomainIdEquals") profiles, err := p.store.ListUserProfiles(domainID) if err != nil { return nil, err @@ -1194,8 +1190,8 @@ func (p *Provider) listUserProfiles(params map[string]any) (*plugin.Response, er } func (p *Provider) updateUserProfile(params map[string]any) (*plugin.Response, error) { - domainID := strParam(params, "DomainId") - name := strParam(params, "UserProfileName") + domainID := shared.StrParam(params, "DomainId") + name := shared.StrParam(params, "UserProfileName") up, err := p.store.GetUserProfile(domainID, name) if err != nil { return shared.JSONError("ResourceNotFound", "user profile not found: "+name, http.StatusBadRequest), nil @@ -1207,8 +1203,8 @@ func (p *Provider) updateUserProfile(params map[string]any) (*plugin.Response, e } func (p *Provider) deleteUserProfile(params map[string]any) (*plugin.Response, error) { - domainID := strParam(params, "DomainId") - name := strParam(params, "UserProfileName") + domainID := shared.StrParam(params, "DomainId") + name := shared.StrParam(params, "UserProfileName") if err := p.store.DeleteUserProfile(domainID, name); err != nil { return shared.JSONError("ResourceNotFound", "user profile not found: "+name, http.StatusBadRequest), nil } @@ -1218,7 +1214,7 @@ func (p *Provider) deleteUserProfile(params map[string]any) (*plugin.Response, e // ---- Tags handlers ---- func (p *Provider) addTags(params map[string]any) (*plugin.Response, error) { - resourceARN := strParam(params, "ResourceArn") + resourceARN := shared.StrParam(params, "ResourceArn") tagsRaw, _ := params["Tags"].([]any) tags := make(map[string]string, len(tagsRaw)) for _, t := range tagsRaw { @@ -1237,7 +1233,7 @@ func (p *Provider) addTags(params map[string]any) (*plugin.Response, error) { } func (p *Provider) deleteTags(params map[string]any) (*plugin.Response, error) { - resourceARN := strParam(params, "ResourceArn") + resourceARN := shared.StrParam(params, "ResourceArn") keys := stringsParam(params, "TagKeys") if err := p.store.RemoveTags(resourceARN, keys); err != nil { return nil, err @@ -1246,7 +1242,7 @@ func (p *Provider) deleteTags(params map[string]any) (*plugin.Response, error) { } func (p *Provider) listTags(params map[string]any) (*plugin.Response, error) { - resourceARN := strParam(params, "ResourceArn") + resourceARN := shared.StrParam(params, "ResourceArn") tags, err := p.store.ListTagsForResource(resourceARN) if err != nil { return nil, err @@ -1260,15 +1256,6 @@ func (p *Provider) listTags(params map[string]any) (*plugin.Response, error) { // ---- helpers ---- -func strParam(params map[string]any, key string) string { - if v, ok := params[key]; ok { - if s, ok := v.(string); ok { - return s - } - } - return "" -} - func marshalParam(params map[string]any, key string) string { if v, ok := params[key]; ok { b, _ := json.Marshal(v) diff --git a/internal/services/scheduler/provider.go b/internal/services/scheduler/provider.go index 183d567..22bfcdb 100644 --- a/internal/services/scheduler/provider.go +++ b/internal/services/scheduler/provider.go @@ -173,10 +173,6 @@ func (p *SchedulerProvider) ListResources(_ context.Context) ([]plugin.Resource, return res, nil } -func (p *SchedulerProvider) GetMetrics(_ context.Context) (*plugin.ServiceMetrics, error) { - return &plugin.ServiceMetrics{}, nil -} - // --- Schedule handlers --- func (p *SchedulerProvider) createSchedule(req *http.Request, params map[string]any) (*plugin.Response, error) { diff --git a/internal/services/secretsmanager/provider.go b/internal/services/secretsmanager/provider.go index c763669..d2cca64 100644 --- a/internal/services/secretsmanager/provider.go +++ b/internal/services/secretsmanager/provider.go @@ -14,10 +14,11 @@ import ( "strings" "github.com/skyoo2003/devcloud/internal/plugin" + "github.com/skyoo2003/devcloud/internal/shared" ) const defaultAccountID = plugin.DefaultAccountID -const defaultRegion = "us-east-1" +const defaultRegion = shared.DefaultRegion type Provider struct { store *SecretStore @@ -167,10 +168,6 @@ func (p *Provider) ListResources(_ context.Context) ([]plugin.Resource, error) { return resources, nil } -func (p *Provider) GetMetrics(_ context.Context) (*plugin.ServiceMetrics, error) { - return &plugin.ServiceMetrics{}, nil -} - func (p *Provider) createSecret(params map[string]any) (*plugin.Response, error) { name, _ := params["Name"].(string) if name == "" { diff --git a/internal/services/serverlessrepo/provider.go b/internal/services/serverlessrepo/provider.go index d3a3da3..9212e97 100644 --- a/internal/services/serverlessrepo/provider.go +++ b/internal/services/serverlessrepo/provider.go @@ -184,10 +184,6 @@ func (p *ServerlessRepoProvider) ListResources(_ context.Context) ([]plugin.Reso return res, nil } -func (p *ServerlessRepoProvider) GetMetrics(_ context.Context) (*plugin.ServiceMetrics, error) { - return &plugin.ServiceMetrics{}, nil -} - // --- Application handlers --- func (p *ServerlessRepoProvider) createApplication(params map[string]any) (*plugin.Response, error) { diff --git a/internal/services/servicediscovery/provider.go b/internal/services/servicediscovery/provider.go index 5e26fdc..4f55f6f 100644 --- a/internal/services/servicediscovery/provider.go +++ b/internal/services/servicediscovery/provider.go @@ -142,10 +142,6 @@ func (p *Provider) ListResources(_ context.Context) ([]plugin.Resource, error) { return res, nil } -func (p *Provider) GetMetrics(_ context.Context) (*plugin.ServiceMetrics, error) { - return &plugin.ServiceMetrics{}, nil -} - // Namespace operations func (p *Provider) createHttpNamespace(params map[string]any) (*plugin.Response, error) { diff --git a/internal/services/ses/provider.go b/internal/services/ses/provider.go index 5535f6b..cb5efa7 100644 --- a/internal/services/ses/provider.go +++ b/internal/services/ses/provider.go @@ -212,10 +212,6 @@ func (p *Provider) ListResources(ctx context.Context) ([]plugin.Resource, error) return out, nil } -func (p *Provider) GetMetrics(_ context.Context) (*plugin.ServiceMetrics, error) { - return &plugin.ServiceMetrics{}, nil -} - // --- helpers --- func sesError(code, msg string, status int) *plugin.Response { diff --git a/internal/services/sesv2/provider.go b/internal/services/sesv2/provider.go index 4fc9438..436bc37 100644 --- a/internal/services/sesv2/provider.go +++ b/internal/services/sesv2/provider.go @@ -321,10 +321,6 @@ func (p *Provider) ListResources(_ context.Context) ([]plugin.Resource, error) { return res, nil } -func (p *Provider) GetMetrics(_ context.Context) (*plugin.ServiceMetrics, error) { - return &plugin.ServiceMetrics{}, nil -} - // --- EmailIdentity --- func (p *Provider) createEmailIdentity(params map[string]any) (*plugin.Response, error) { diff --git a/internal/services/sfn/provider.go b/internal/services/sfn/provider.go index e55941b..1ff77f9 100644 --- a/internal/services/sfn/provider.go +++ b/internal/services/sfn/provider.go @@ -167,10 +167,6 @@ func (p *Provider) ListResources(_ context.Context) ([]plugin.Resource, error) { return res, nil } -func (p *Provider) GetMetrics(_ context.Context) (*plugin.ServiceMetrics, error) { - return &plugin.ServiceMetrics{}, nil -} - // ---- Helpers ---- func json10Resp(status int, v any) (*plugin.Response, error) { diff --git a/internal/services/shield/provider.go b/internal/services/shield/provider.go index 2d25625..123fe7c 100644 --- a/internal/services/shield/provider.go +++ b/internal/services/shield/provider.go @@ -155,10 +155,6 @@ func (p *Provider) ListResources(_ context.Context) ([]plugin.Resource, error) { return res, nil } -func (p *Provider) GetMetrics(_ context.Context) (*plugin.ServiceMetrics, error) { - return &plugin.ServiceMetrics{}, nil -} - // --- Protection --- func (p *Provider) createProtection(params map[string]any) (*plugin.Response, error) { diff --git a/internal/services/sns/provider.go b/internal/services/sns/provider.go index 4df65cd..2aa93f7 100644 --- a/internal/services/sns/provider.go +++ b/internal/services/sns/provider.go @@ -14,10 +14,11 @@ import ( "strings" "github.com/skyoo2003/devcloud/internal/plugin" + "github.com/skyoo2003/devcloud/internal/shared" ) const defaultAccountID = plugin.DefaultAccountID -const defaultRegion = "us-east-1" +const defaultRegion = shared.DefaultRegion type Provider struct { store *SNSStore @@ -114,10 +115,6 @@ func (p *Provider) ListResources(_ context.Context) ([]plugin.Resource, error) { return resources, nil } -func (p *Provider) GetMetrics(_ context.Context) (*plugin.ServiceMetrics, error) { - return &plugin.ServiceMetrics{}, nil -} - // --- operation handlers --- func (p *Provider) createTopic(req *http.Request) (*plugin.Response, error) { diff --git a/internal/services/sqs/provider.go b/internal/services/sqs/provider.go index b222d1d..fe18aad 100644 --- a/internal/services/sqs/provider.go +++ b/internal/services/sqs/provider.go @@ -14,6 +14,7 @@ import ( "strings" "github.com/skyoo2003/devcloud/internal/plugin" + "github.com/skyoo2003/devcloud/internal/shared" ) const defaultAccountID = plugin.DefaultAccountID @@ -209,11 +210,6 @@ func (p *SQSProvider) ListResources(_ context.Context) ([]plugin.Resource, error return resources, nil } -// GetMetrics returns empty metrics. -func (p *SQSProvider) GetMetrics(_ context.Context) (*plugin.ServiceMetrics, error) { - return &plugin.ServiceMetrics{}, nil -} - // --- XML response helpers --- type sqsErrorResponse struct { @@ -398,16 +394,6 @@ func jsonResp(status int, v any) (*plugin.Response, error) { }, nil } -// strParam extracts a string value from the JSON params map. -func strParam(params map[string]any, key string) string { - if v, ok := params[key]; ok { - if s, ok := v.(string); ok { - return s - } - } - return "" -} - // intParam extracts an int value from the JSON params map. func intParam(params map[string]any, key string, defaultVal int) int { if v, ok := params[key]; ok { @@ -424,7 +410,7 @@ func intParam(params map[string]any, key string, defaultVal int) int { // --- JSON protocol operation implementations --- func (p *SQSProvider) createQueueJSON(params map[string]any) (*plugin.Response, error) { - name := strParam(params, "QueueName") + name := shared.StrParam(params, "QueueName") if name == "" { return jsonError("MissingParameter", "QueueName is required", http.StatusBadRequest), nil } @@ -450,7 +436,7 @@ func (p *SQSProvider) createQueueJSON(params map[string]any) (*plugin.Response, } func (p *SQSProvider) listQueuesJSON(params map[string]any) (*plugin.Response, error) { - prefix := strParam(params, "QueueNamePrefix") + prefix := shared.StrParam(params, "QueueNamePrefix") queues := p.store.ListQueues(defaultAccountID, prefix) urls := make([]string, 0, len(queues)) @@ -461,7 +447,7 @@ func (p *SQSProvider) listQueuesJSON(params map[string]any) (*plugin.Response, e } func (p *SQSProvider) getQueueUrlJSON(params map[string]any) (*plugin.Response, error) { - name := strParam(params, "QueueName") + name := shared.StrParam(params, "QueueName") if name == "" { return jsonError("MissingParameter", "QueueName is required", http.StatusBadRequest), nil } @@ -476,8 +462,8 @@ func (p *SQSProvider) getQueueUrlJSON(params map[string]any) (*plugin.Response, } func (p *SQSProvider) sendMessageJSON(params map[string]any) (*plugin.Response, error) { - queueURL := strParam(params, "QueueUrl") - body := strParam(params, "MessageBody") + queueURL := shared.StrParam(params, "QueueUrl") + body := shared.StrParam(params, "MessageBody") if queueURL == "" { return jsonError("MissingParameter", "QueueUrl is required", http.StatusBadRequest), nil } @@ -487,8 +473,8 @@ func (p *SQSProvider) sendMessageJSON(params map[string]any) (*plugin.Response, attrs := parseJSONMessageAttributes(params) fifoOpts := SendMessageFIFOOptions{ - MessageGroupID: strParam(params, "MessageGroupId"), - MessageDeduplicationID: strParam(params, "MessageDeduplicationId"), + MessageGroupID: shared.StrParam(params, "MessageGroupId"), + MessageDeduplicationID: shared.StrParam(params, "MessageDeduplicationId"), } name := queueNameFromURL(queueURL) @@ -504,7 +490,7 @@ func (p *SQSProvider) sendMessageJSON(params map[string]any) (*plugin.Response, } func (p *SQSProvider) receiveMessageJSON(params map[string]any) (*plugin.Response, error) { - queueURL := strParam(params, "QueueUrl") + queueURL := shared.StrParam(params, "QueueUrl") if queueURL == "" { return jsonError("MissingParameter", "QueueUrl is required", http.StatusBadRequest), nil } @@ -556,8 +542,8 @@ func (p *SQSProvider) receiveMessageJSON(params map[string]any) (*plugin.Respons } func (p *SQSProvider) deleteMessageJSON(params map[string]any) (*plugin.Response, error) { - queueURL := strParam(params, "QueueUrl") - receiptHandle := strParam(params, "ReceiptHandle") + queueURL := shared.StrParam(params, "QueueUrl") + receiptHandle := shared.StrParam(params, "ReceiptHandle") if queueURL == "" { return jsonError("MissingParameter", "QueueUrl is required", http.StatusBadRequest), nil } @@ -575,7 +561,7 @@ func (p *SQSProvider) deleteMessageJSON(params map[string]any) (*plugin.Response } func (p *SQSProvider) deleteQueueJSON(params map[string]any) (*plugin.Response, error) { - queueURL := strParam(params, "QueueUrl") + queueURL := shared.StrParam(params, "QueueUrl") if queueURL == "" { return jsonError("MissingParameter", "QueueUrl is required", http.StatusBadRequest), nil } @@ -590,7 +576,7 @@ func (p *SQSProvider) deleteQueueJSON(params map[string]any) (*plugin.Response, } func (p *SQSProvider) getQueueAttributesJSON(params map[string]any) (*plugin.Response, error) { - queueURL := strParam(params, "QueueUrl") + queueURL := shared.StrParam(params, "QueueUrl") if queueURL == "" { return jsonError("MissingParameter", "QueueUrl is required", http.StatusBadRequest), nil } @@ -619,7 +605,7 @@ func (p *SQSProvider) getQueueAttributesJSON(params map[string]any) (*plugin.Res } func (p *SQSProvider) setQueueAttributesJSON(params map[string]any) (*plugin.Response, error) { - queueURL := strParam(params, "QueueUrl") + queueURL := shared.StrParam(params, "QueueUrl") if queueURL == "" { return jsonError("MissingParameter", "QueueUrl is required", http.StatusBadRequest), nil } @@ -645,7 +631,7 @@ func (p *SQSProvider) setQueueAttributesJSON(params map[string]any) (*plugin.Res } func (p *SQSProvider) purgeQueueJSON(params map[string]any) (*plugin.Response, error) { - queueURL := strParam(params, "QueueUrl") + queueURL := shared.StrParam(params, "QueueUrl") if queueURL == "" { return jsonError("MissingParameter", "QueueUrl is required", http.StatusBadRequest), nil } @@ -660,7 +646,7 @@ func (p *SQSProvider) purgeQueueJSON(params map[string]any) (*plugin.Response, e } func (p *SQSProvider) listDeadLetterSourceQueuesJSON(params map[string]any) (*plugin.Response, error) { - queueURL := strParam(params, "QueueUrl") + queueURL := shared.StrParam(params, "QueueUrl") if queueURL == "" { return jsonError("MissingParameter", "QueueUrl is required", http.StatusBadRequest), nil } @@ -677,11 +663,11 @@ func (p *SQSProvider) listDeadLetterSourceQueuesJSON(params map[string]any) (*pl } func (p *SQSProvider) startMessageMoveTaskJSON(params map[string]any) (*plugin.Response, error) { - sourceArn := strParam(params, "SourceArn") + sourceArn := shared.StrParam(params, "SourceArn") if sourceArn == "" { return jsonError("MissingParameter", "SourceArn is required", http.StatusBadRequest), nil } - destArn := strParam(params, "DestinationArn") + destArn := shared.StrParam(params, "DestinationArn") maxRate := intParam(params, "MaxNumberOfMessagesPerSecond", 0) handle, err := p.store.StartMessageMoveTask(sourceArn, destArn, maxRate, defaultAccountID) @@ -697,7 +683,7 @@ func (p *SQSProvider) startMessageMoveTaskJSON(params map[string]any) (*plugin.R } func (p *SQSProvider) listMessageMoveTasksJSON(params map[string]any) (*plugin.Response, error) { - sourceArn := strParam(params, "SourceArn") + sourceArn := shared.StrParam(params, "SourceArn") if sourceArn == "" { return jsonError("MissingParameter", "SourceArn is required", http.StatusBadRequest), nil } @@ -724,7 +710,7 @@ func (p *SQSProvider) listMessageMoveTasksJSON(params map[string]any) (*plugin.R } func (p *SQSProvider) cancelMessageMoveTaskJSON(params map[string]any) (*plugin.Response, error) { - handle := strParam(params, "TaskHandle") + handle := shared.StrParam(params, "TaskHandle") if handle == "" { return jsonError("MissingParameter", "TaskHandle is required", http.StatusBadRequest), nil } @@ -1159,7 +1145,7 @@ func parseFormMessageAttributes(req *http.Request) map[string]MessageAttribute { // --- Batch operations --- func (p *SQSProvider) sendMessageBatchJSON(params map[string]any) (*plugin.Response, error) { - queueURL := strParam(params, "QueueUrl") + queueURL := shared.StrParam(params, "QueueUrl") if queueURL == "" { return jsonError("MissingParameter", "QueueUrl is required", http.StatusBadRequest), nil } @@ -1191,8 +1177,8 @@ func (p *SQSProvider) sendMessageBatchJSON(params map[string]any) (*plugin.Respo if !ok { continue } - id := strParam(entry, "Id") - body := strParam(entry, "MessageBody") + id := shared.StrParam(entry, "Id") + body := shared.StrParam(entry, "MessageBody") if body == "" { failed = append(failed, failEntry{ Id: id, @@ -1204,8 +1190,8 @@ func (p *SQSProvider) sendMessageBatchJSON(params map[string]any) (*plugin.Respo } attrs := parseJSONMessageAttributes(entry) batchFIFOOpts := SendMessageFIFOOptions{ - MessageGroupID: strParam(entry, "MessageGroupId"), - MessageDeduplicationID: strParam(entry, "MessageDeduplicationId"), + MessageGroupID: shared.StrParam(entry, "MessageGroupId"), + MessageDeduplicationID: shared.StrParam(entry, "MessageDeduplicationId"), } msgID, err := p.store.SendMessageFull(name, defaultAccountID, body, attrs, batchFIFOOpts) if err != nil { @@ -1238,7 +1224,7 @@ func (p *SQSProvider) sendMessageBatchJSON(params map[string]any) (*plugin.Respo } func (p *SQSProvider) deleteMessageBatchJSON(params map[string]any) (*plugin.Response, error) { - queueURL := strParam(params, "QueueUrl") + queueURL := shared.StrParam(params, "QueueUrl") if queueURL == "" { return jsonError("MissingParameter", "QueueUrl is required", http.StatusBadRequest), nil } @@ -1268,8 +1254,8 @@ func (p *SQSProvider) deleteMessageBatchJSON(params map[string]any) (*plugin.Res if !ok { continue } - id := strParam(entry, "Id") - receiptHandle := strParam(entry, "ReceiptHandle") + id := shared.StrParam(entry, "Id") + receiptHandle := shared.StrParam(entry, "ReceiptHandle") err := p.store.DeleteMessage(name, defaultAccountID, receiptHandle) if err != nil { failed = append(failed, failEntry{ @@ -1297,7 +1283,7 @@ func (p *SQSProvider) deleteMessageBatchJSON(params map[string]any) (*plugin.Res } func (p *SQSProvider) changeMessageVisibilityBatchJSON(params map[string]any) (*plugin.Response, error) { - queueURL := strParam(params, "QueueUrl") + queueURL := shared.StrParam(params, "QueueUrl") if queueURL == "" { return jsonError("MissingParameter", "QueueUrl is required", http.StatusBadRequest), nil } @@ -1327,8 +1313,8 @@ func (p *SQSProvider) changeMessageVisibilityBatchJSON(params map[string]any) (* if !ok { continue } - id := strParam(entry, "Id") - receiptHandle := strParam(entry, "ReceiptHandle") + id := shared.StrParam(entry, "Id") + receiptHandle := shared.StrParam(entry, "ReceiptHandle") vt := intParam(entry, "VisibilityTimeout", 30) err := p.store.ChangeMessageVisibility(defaultAccountID, name, receiptHandle, vt) if err != nil { @@ -1357,8 +1343,8 @@ func (p *SQSProvider) changeMessageVisibilityBatchJSON(params map[string]any) (* } func (p *SQSProvider) changeMessageVisibilityJSON(params map[string]any) (*plugin.Response, error) { - queueURL := strParam(params, "QueueUrl") - receiptHandle := strParam(params, "ReceiptHandle") + queueURL := shared.StrParam(params, "QueueUrl") + receiptHandle := shared.StrParam(params, "ReceiptHandle") if queueURL == "" { return jsonError("MissingParameter", "QueueUrl is required", http.StatusBadRequest), nil } @@ -1379,7 +1365,7 @@ func (p *SQSProvider) changeMessageVisibilityJSON(params map[string]any) (*plugi // --- Tags --- func (p *SQSProvider) tagQueueJSON(params map[string]any) (*plugin.Response, error) { - queueURL := strParam(params, "QueueUrl") + queueURL := shared.StrParam(params, "QueueUrl") if queueURL == "" { return jsonError("MissingParameter", "QueueUrl is required", http.StatusBadRequest), nil } @@ -1405,7 +1391,7 @@ func (p *SQSProvider) tagQueueJSON(params map[string]any) (*plugin.Response, err } func (p *SQSProvider) untagQueueJSON(params map[string]any) (*plugin.Response, error) { - queueURL := strParam(params, "QueueUrl") + queueURL := shared.StrParam(params, "QueueUrl") if queueURL == "" { return jsonError("MissingParameter", "QueueUrl is required", http.StatusBadRequest), nil } @@ -1431,7 +1417,7 @@ func (p *SQSProvider) untagQueueJSON(params map[string]any) (*plugin.Response, e } func (p *SQSProvider) listQueueTagsJSON(params map[string]any) (*plugin.Response, error) { - queueURL := strParam(params, "QueueUrl") + queueURL := shared.StrParam(params, "QueueUrl") if queueURL == "" { return jsonError("MissingParameter", "QueueUrl is required", http.StatusBadRequest), nil } diff --git a/internal/services/ssm/provider.go b/internal/services/ssm/provider.go index c306f32..b1d9146 100644 --- a/internal/services/ssm/provider.go +++ b/internal/services/ssm/provider.go @@ -129,10 +129,6 @@ func (p *Provider) ListResources(_ context.Context) ([]plugin.Resource, error) { return resources, nil } -func (p *Provider) GetMetrics(_ context.Context) (*plugin.ServiceMetrics, error) { - return &plugin.ServiceMetrics{}, nil -} - func (p *Provider) putParameter(params map[string]any) (*plugin.Response, error) { name, _ := params["Name"].(string) value, _ := params["Value"].(string) diff --git a/internal/services/ssoadmin/provider.go b/internal/services/ssoadmin/provider.go index 419190e..1d6ea56 100644 --- a/internal/services/ssoadmin/provider.go +++ b/internal/services/ssoadmin/provider.go @@ -240,10 +240,6 @@ func (p *Provider) ListResources(_ context.Context) ([]plugin.Resource, error) { return res, nil } -func (p *Provider) GetMetrics(_ context.Context) (*plugin.ServiceMetrics, error) { - return &plugin.ServiceMetrics{}, nil -} - // --- Instance --- func (p *Provider) createInstance(params map[string]any) (*plugin.Response, error) { diff --git a/internal/services/support/provider.go b/internal/services/support/provider.go index 9f75d31..1c18a94 100644 --- a/internal/services/support/provider.go +++ b/internal/services/support/provider.go @@ -169,10 +169,6 @@ func (p *Provider) ListResources(_ context.Context) ([]plugin.Resource, error) { return res, nil } -func (p *Provider) GetMetrics(_ context.Context) (*plugin.ServiceMetrics, error) { - return &plugin.ServiceMetrics{}, nil -} - func (p *Provider) createCase(params map[string]any) (*plugin.Response, error) { subject, _ := params["subject"].(string) if subject == "" { diff --git a/internal/services/swf/provider.go b/internal/services/swf/provider.go index 29107df..7a3c4e2 100644 --- a/internal/services/swf/provider.go +++ b/internal/services/swf/provider.go @@ -170,10 +170,6 @@ func (p *Provider) ListResources(_ context.Context) ([]plugin.Resource, error) { return res, nil } -func (p *Provider) GetMetrics(_ context.Context) (*plugin.ServiceMetrics, error) { - return &plugin.ServiceMetrics{}, nil -} - // ---- Helpers ---- func json10Resp(status int, v any) (*plugin.Response, error) { @@ -213,11 +209,6 @@ func parseTags(rawTags []any) map[string]string { return tags } -func strParam(params map[string]any, key string) string { - v, _ := params[key].(string) - return v -} - func typeInfoParam(params map[string]any, key string) (name, version string) { m, _ := params[key].(map[string]any) if m == nil { @@ -231,12 +222,12 @@ func typeInfoParam(params map[string]any, key string) (name, version string) { // ---- Domain handlers ---- func (p *Provider) registerDomain(params map[string]any) (*plugin.Response, error) { - name := strParam(params, "name") + name := shared.StrParam(params, "name") if name == "" { return json10Err("ValidationException", "name is required", http.StatusBadRequest), nil } - description := strParam(params, "description") - retention := strParam(params, "workflowExecutionRetentionPeriodInDays") + description := shared.StrParam(params, "description") + retention := shared.StrParam(params, "workflowExecutionRetentionPeriodInDays") if retention == "" { retention = "30" } @@ -262,7 +253,7 @@ func (p *Provider) registerDomain(params map[string]any) (*plugin.Response, erro } func (p *Provider) describeDomain(params map[string]any) (*plugin.Response, error) { - name := strParam(params, "name") + name := shared.StrParam(params, "name") if name == "" { return json10Err("ValidationException", "name is required", http.StatusBadRequest), nil } @@ -274,7 +265,7 @@ func (p *Provider) describeDomain(params map[string]any) (*plugin.Response, erro } func (p *Provider) listDomains(params map[string]any) (*plugin.Response, error) { - statusFilter := strParam(params, "registrationStatus") + statusFilter := shared.StrParam(params, "registrationStatus") domains, err := p.store.ListDomains(statusFilter) if err != nil { return nil, err @@ -291,7 +282,7 @@ func (p *Provider) listDomains(params map[string]any) (*plugin.Response, error) } func (p *Provider) deprecateDomain(params map[string]any) (*plugin.Response, error) { - name := strParam(params, "name") + name := shared.StrParam(params, "name") if name == "" { return json10Err("ValidationException", "name is required", http.StatusBadRequest), nil } @@ -309,7 +300,7 @@ func (p *Provider) deprecateDomain(params map[string]any) (*plugin.Response, err } func (p *Provider) undeprecateDomain(params map[string]any) (*plugin.Response, error) { - name := strParam(params, "name") + name := shared.StrParam(params, "name") if name == "" { return json10Err("ValidationException", "name is required", http.StatusBadRequest), nil } @@ -325,9 +316,9 @@ func (p *Provider) undeprecateDomain(params map[string]any) (*plugin.Response, e // ---- WorkflowType handlers ---- func (p *Provider) registerWorkflowType(params map[string]any) (*plugin.Response, error) { - domain := strParam(params, "domain") - name := strParam(params, "name") - version := strParam(params, "version") + domain := shared.StrParam(params, "domain") + name := shared.StrParam(params, "name") + version := shared.StrParam(params, "version") if domain == "" || name == "" || version == "" { return json10Err("ValidationException", "domain, name, version are required", http.StatusBadRequest), nil } @@ -337,7 +328,7 @@ func (p *Provider) registerWorkflowType(params map[string]any) (*plugin.Response Name: name, Version: version, Status: "REGISTERED", - Description: strParam(params, "description"), + Description: shared.StrParam(params, "description"), DefaultTimeout: "NONE", CreatedAt: now, } @@ -351,7 +342,7 @@ func (p *Provider) registerWorkflowType(params map[string]any) (*plugin.Response } func (p *Provider) describeWorkflowType(params map[string]any) (*plugin.Response, error) { - domain := strParam(params, "domain") + domain := shared.StrParam(params, "domain") name, version := typeInfoParam(params, "workflowType") if domain == "" || name == "" || version == "" { return json10Err("ValidationException", "domain and workflowType are required", http.StatusBadRequest), nil @@ -364,11 +355,11 @@ func (p *Provider) describeWorkflowType(params map[string]any) (*plugin.Response } func (p *Provider) listWorkflowTypes(params map[string]any) (*plugin.Response, error) { - domain := strParam(params, "domain") + domain := shared.StrParam(params, "domain") if domain == "" { return json10Err("ValidationException", "domain is required", http.StatusBadRequest), nil } - statusFilter := strParam(params, "registrationStatus") + statusFilter := shared.StrParam(params, "registrationStatus") wts, err := p.store.ListWorkflowTypes(domain, statusFilter) if err != nil { return nil, err @@ -383,7 +374,7 @@ func (p *Provider) listWorkflowTypes(params map[string]any) (*plugin.Response, e } func (p *Provider) deprecateWorkflowType(params map[string]any) (*plugin.Response, error) { - domain := strParam(params, "domain") + domain := shared.StrParam(params, "domain") name, version := typeInfoParam(params, "workflowType") if domain == "" || name == "" || version == "" { return json10Err("ValidationException", "domain and workflowType are required", http.StatusBadRequest), nil @@ -402,7 +393,7 @@ func (p *Provider) deprecateWorkflowType(params map[string]any) (*plugin.Respons } func (p *Provider) undeprecateWorkflowType(params map[string]any) (*plugin.Response, error) { - domain := strParam(params, "domain") + domain := shared.StrParam(params, "domain") name, version := typeInfoParam(params, "workflowType") if domain == "" || name == "" || version == "" { return json10Err("ValidationException", "domain and workflowType are required", http.StatusBadRequest), nil @@ -417,7 +408,7 @@ func (p *Provider) undeprecateWorkflowType(params map[string]any) (*plugin.Respo } func (p *Provider) deleteWorkflowType(params map[string]any) (*plugin.Response, error) { - domain := strParam(params, "domain") + domain := shared.StrParam(params, "domain") name, version := typeInfoParam(params, "workflowType") if domain == "" || name == "" || version == "" { return json10Err("ValidationException", "domain and workflowType are required", http.StatusBadRequest), nil @@ -438,9 +429,9 @@ func (p *Provider) deleteWorkflowType(params map[string]any) (*plugin.Response, // ---- ActivityType handlers ---- func (p *Provider) registerActivityType(params map[string]any) (*plugin.Response, error) { - domain := strParam(params, "domain") - name := strParam(params, "name") - version := strParam(params, "version") + domain := shared.StrParam(params, "domain") + name := shared.StrParam(params, "name") + version := shared.StrParam(params, "version") if domain == "" || name == "" || version == "" { return json10Err("ValidationException", "domain, name, version are required", http.StatusBadRequest), nil } @@ -450,7 +441,7 @@ func (p *Provider) registerActivityType(params map[string]any) (*plugin.Response Name: name, Version: version, Status: "REGISTERED", - Description: strParam(params, "description"), + Description: shared.StrParam(params, "description"), DefaultTimeout: "NONE", CreatedAt: now, } @@ -464,7 +455,7 @@ func (p *Provider) registerActivityType(params map[string]any) (*plugin.Response } func (p *Provider) describeActivityType(params map[string]any) (*plugin.Response, error) { - domain := strParam(params, "domain") + domain := shared.StrParam(params, "domain") name, version := typeInfoParam(params, "activityType") if domain == "" || name == "" || version == "" { return json10Err("ValidationException", "domain and activityType are required", http.StatusBadRequest), nil @@ -477,11 +468,11 @@ func (p *Provider) describeActivityType(params map[string]any) (*plugin.Response } func (p *Provider) listActivityTypes(params map[string]any) (*plugin.Response, error) { - domain := strParam(params, "domain") + domain := shared.StrParam(params, "domain") if domain == "" { return json10Err("ValidationException", "domain is required", http.StatusBadRequest), nil } - statusFilter := strParam(params, "registrationStatus") + statusFilter := shared.StrParam(params, "registrationStatus") ats, err := p.store.ListActivityTypes(domain, statusFilter) if err != nil { return nil, err @@ -496,7 +487,7 @@ func (p *Provider) listActivityTypes(params map[string]any) (*plugin.Response, e } func (p *Provider) deprecateActivityType(params map[string]any) (*plugin.Response, error) { - domain := strParam(params, "domain") + domain := shared.StrParam(params, "domain") name, version := typeInfoParam(params, "activityType") if domain == "" || name == "" || version == "" { return json10Err("ValidationException", "domain and activityType are required", http.StatusBadRequest), nil @@ -515,7 +506,7 @@ func (p *Provider) deprecateActivityType(params map[string]any) (*plugin.Respons } func (p *Provider) undeprecateActivityType(params map[string]any) (*plugin.Response, error) { - domain := strParam(params, "domain") + domain := shared.StrParam(params, "domain") name, version := typeInfoParam(params, "activityType") if domain == "" || name == "" || version == "" { return json10Err("ValidationException", "domain and activityType are required", http.StatusBadRequest), nil @@ -530,7 +521,7 @@ func (p *Provider) undeprecateActivityType(params map[string]any) (*plugin.Respo } func (p *Provider) deleteActivityType(params map[string]any) (*plugin.Response, error) { - domain := strParam(params, "domain") + domain := shared.StrParam(params, "domain") name, version := typeInfoParam(params, "activityType") if domain == "" || name == "" || version == "" { return json10Err("ValidationException", "domain and activityType are required", http.StatusBadRequest), nil @@ -551,13 +542,13 @@ func (p *Provider) deleteActivityType(params map[string]any) (*plugin.Response, // ---- WorkflowExecution handlers ---- func (p *Provider) startWorkflowExecution(params map[string]any) (*plugin.Response, error) { - domain := strParam(params, "domain") - workflowID := strParam(params, "workflowId") + domain := shared.StrParam(params, "domain") + workflowID := shared.StrParam(params, "workflowId") if domain == "" || workflowID == "" { return json10Err("ValidationException", "domain and workflowId are required", http.StatusBadRequest), nil } wfName, wfVersion := typeInfoParam(params, "workflowType") - input := strParam(params, "input") + input := shared.StrParam(params, "input") runID := shared.GenerateUUID() now := time.Now() @@ -590,7 +581,7 @@ func (p *Provider) startWorkflowExecution(params map[string]any) (*plugin.Respon } func (p *Provider) describeWorkflowExecution(params map[string]any) (*plugin.Response, error) { - domain := strParam(params, "domain") + domain := shared.StrParam(params, "domain") execMap, _ := params["execution"].(map[string]any) if domain == "" || execMap == nil { return json10Err("ValidationException", "domain and execution are required", http.StatusBadRequest), nil @@ -608,7 +599,7 @@ func (p *Provider) describeWorkflowExecution(params map[string]any) (*plugin.Res } func (p *Provider) listOpenWorkflowExecutions(params map[string]any) (*plugin.Response, error) { - domain := strParam(params, "domain") + domain := shared.StrParam(params, "domain") if domain == "" { return json10Err("ValidationException", "domain is required", http.StatusBadRequest), nil } @@ -626,7 +617,7 @@ func (p *Provider) listOpenWorkflowExecutions(params map[string]any) (*plugin.Re } func (p *Provider) listClosedWorkflowExecutions(params map[string]any) (*plugin.Response, error) { - domain := strParam(params, "domain") + domain := shared.StrParam(params, "domain") if domain == "" { return json10Err("ValidationException", "domain is required", http.StatusBadRequest), nil } @@ -644,12 +635,12 @@ func (p *Provider) listClosedWorkflowExecutions(params map[string]any) (*plugin. } func (p *Provider) terminateWorkflowExecution(params map[string]any) (*plugin.Response, error) { - domain := strParam(params, "domain") - workflowID := strParam(params, "workflowId") + domain := shared.StrParam(params, "domain") + workflowID := shared.StrParam(params, "workflowId") if domain == "" || workflowID == "" { return json10Err("ValidationException", "domain and workflowId are required", http.StatusBadRequest), nil } - runID := strParam(params, "runId") + runID := shared.StrParam(params, "runId") var we *WorkflowExecution var err error if runID != "" { @@ -667,12 +658,12 @@ func (p *Provider) terminateWorkflowExecution(params map[string]any) (*plugin.Re } func (p *Provider) requestCancelWorkflowExecution(params map[string]any) (*plugin.Response, error) { - domain := strParam(params, "domain") - workflowID := strParam(params, "workflowId") + domain := shared.StrParam(params, "domain") + workflowID := shared.StrParam(params, "workflowId") if domain == "" || workflowID == "" { return json10Err("ValidationException", "domain and workflowId are required", http.StatusBadRequest), nil } - runID := strParam(params, "runId") + runID := shared.StrParam(params, "runId") var we *WorkflowExecution var err error if runID != "" { @@ -694,7 +685,7 @@ func (p *Provider) signalWorkflowExecution(_ map[string]any) (*plugin.Response, } func (p *Provider) getWorkflowExecutionHistory(params map[string]any) (*plugin.Response, error) { - domain := strParam(params, "domain") + domain := shared.StrParam(params, "domain") execMap, _ := params["execution"].(map[string]any) if domain == "" || execMap == nil { return json10Err("ValidationException", "domain and execution are required", http.StatusBadRequest), nil @@ -725,7 +716,7 @@ func (p *Provider) getWorkflowExecutionHistory(params map[string]any) (*plugin.R // ---- Count handlers ---- func (p *Provider) countOpenWorkflowExecutions(params map[string]any) (*plugin.Response, error) { - domain := strParam(params, "domain") + domain := shared.StrParam(params, "domain") if domain == "" { return json10Err("ValidationException", "domain is required", http.StatusBadRequest), nil } @@ -737,7 +728,7 @@ func (p *Provider) countOpenWorkflowExecutions(params map[string]any) (*plugin.R } func (p *Provider) countClosedWorkflowExecutions(params map[string]any) (*plugin.Response, error) { - domain := strParam(params, "domain") + domain := shared.StrParam(params, "domain") if domain == "" { return json10Err("ValidationException", "domain is required", http.StatusBadRequest), nil } @@ -796,7 +787,7 @@ func (p *Provider) recordActivityTaskHeartbeat(_ map[string]any) (*plugin.Respon // ---- Tag handlers ---- func (p *Provider) tagResource(params map[string]any) (*plugin.Response, error) { - arn := strParam(params, "resourceArn") + arn := shared.StrParam(params, "resourceArn") if arn == "" { return json10Err("ValidationException", "resourceArn is required", http.StatusBadRequest), nil } @@ -807,7 +798,7 @@ func (p *Provider) tagResource(params map[string]any) (*plugin.Response, error) } func (p *Provider) untagResource(params map[string]any) (*plugin.Response, error) { - arn := strParam(params, "resourceArn") + arn := shared.StrParam(params, "resourceArn") if arn == "" { return json10Err("ValidationException", "resourceArn is required", http.StatusBadRequest), nil } @@ -824,7 +815,7 @@ func (p *Provider) untagResource(params map[string]any) (*plugin.Response, error } func (p *Provider) listTagsForResource(params map[string]any) (*plugin.Response, error) { - arn := strParam(params, "resourceArn") + arn := shared.StrParam(params, "resourceArn") if arn == "" { return json10Err("ValidationException", "resourceArn is required", http.StatusBadRequest), nil } diff --git a/internal/services/textract/provider.go b/internal/services/textract/provider.go index d2cdc88..a15c6db 100644 --- a/internal/services/textract/provider.go +++ b/internal/services/textract/provider.go @@ -185,10 +185,6 @@ func (p *Provider) ListResources(_ context.Context) ([]plugin.Resource, error) { return res, nil } -func (p *Provider) GetMetrics(_ context.Context) (*plugin.ServiceMetrics, error) { - return &plugin.ServiceMetrics{}, nil -} - // --- Adapter operations --- func (p *Provider) createAdapter(params map[string]any) (*plugin.Response, error) { diff --git a/internal/services/timestreamwrite/provider.go b/internal/services/timestreamwrite/provider.go index a4c700b..248d17c 100644 --- a/internal/services/timestreamwrite/provider.go +++ b/internal/services/timestreamwrite/provider.go @@ -121,10 +121,6 @@ func (p *Provider) ListResources(_ context.Context) ([]plugin.Resource, error) { return res, nil } -func (p *Provider) GetMetrics(_ context.Context) (*plugin.ServiceMetrics, error) { - return &plugin.ServiceMetrics{}, nil -} - // ---- Database handlers ---- func (p *Provider) createDatabase(params map[string]any) (*plugin.Response, error) { diff --git a/internal/services/transcribe/provider.go b/internal/services/transcribe/provider.go index 7f1e7d1..c115dac 100644 --- a/internal/services/transcribe/provider.go +++ b/internal/services/transcribe/provider.go @@ -178,10 +178,6 @@ func (p *Provider) ListResources(_ context.Context) ([]plugin.Resource, error) { return res, nil } -func (p *Provider) GetMetrics(_ context.Context) (*plugin.ServiceMetrics, error) { - return &plugin.ServiceMetrics{}, nil -} - // ---- TranscriptionJob ---- func (p *Provider) startTranscriptionJob(params map[string]any) (*plugin.Response, error) { diff --git a/internal/services/transfer/provider.go b/internal/services/transfer/provider.go index 9fc40dd..07801af 100644 --- a/internal/services/transfer/provider.go +++ b/internal/services/transfer/provider.go @@ -166,10 +166,6 @@ func (p *Provider) ListResources(_ context.Context) ([]plugin.Resource, error) { return res, nil } -func (p *Provider) GetMetrics(_ context.Context) (*plugin.ServiceMetrics, error) { - return &plugin.ServiceMetrics{}, nil -} - // Server operations func (p *Provider) createServer(params map[string]any) (*plugin.Response, error) { diff --git a/internal/services/verifiedpermissions/provider.go b/internal/services/verifiedpermissions/provider.go index 18584f4..f54fd6e 100644 --- a/internal/services/verifiedpermissions/provider.go +++ b/internal/services/verifiedpermissions/provider.go @@ -123,11 +123,6 @@ func (p *VerifiedPermissionsProvider) ListResources(ctx context.Context) ([]plug return []plugin.Resource{}, nil } -// GetMetrics returns empty metrics. -func (p *VerifiedPermissionsProvider) GetMetrics(_ context.Context) (*plugin.ServiceMetrics, error) { - return &plugin.ServiceMetrics{}, nil -} - // --- helpers --- func jsonError(code, message string, status int) *plugin.Response { diff --git a/internal/services/waf/provider.go b/internal/services/waf/provider.go index afcbf40..2657495 100644 --- a/internal/services/waf/provider.go +++ b/internal/services/waf/provider.go @@ -274,10 +274,6 @@ func (p *Provider) ListResources(_ context.Context) ([]plugin.Resource, error) { return out, nil } -func (p *Provider) GetMetrics(_ context.Context) (*plugin.ServiceMetrics, error) { - return &plugin.ServiceMetrics{}, nil -} - // --- helpers --- func wafError(code, msg string, status int) *plugin.Response { @@ -292,15 +288,6 @@ func wafARN(resourceType, id string) string { return fmt.Sprintf("arn:aws:waf::%s:%s/%s", accountID, resourceType, id) } -func strParam(params map[string]any, key string) string { - if v, ok := params[key]; ok { - if s, ok := v.(string); ok { - return s - } - } - return "" -} - func marshalJSON(v any) string { b, _ := json.Marshal(v) return string(b) @@ -320,11 +307,11 @@ func (p *Provider) getChangeTokenStatus(_ map[string]any) (*plugin.Response, err // --- WebACL --- func (p *Provider) createWebACL(params map[string]any) (*plugin.Response, error) { - name := strParam(params, "Name") + name := shared.StrParam(params, "Name") if name == "" { return wafError("WAFInvalidParameterException", "Name is required", http.StatusBadRequest), nil } - changeToken := strParam(params, "ChangeToken") + changeToken := shared.StrParam(params, "ChangeToken") id := shared.GenerateUUID() arn := wafARN("webacl", id) @@ -363,7 +350,7 @@ func (p *Provider) createWebACL(params map[string]any) (*plugin.Response, error) } func (p *Provider) getWebACL(params map[string]any) (*plugin.Response, error) { - id := strParam(params, "WebACLId") + id := shared.StrParam(params, "WebACLId") if id == "" { return wafError("WAFInvalidParameterException", "WebACLId is required", http.StatusBadRequest), nil } @@ -407,7 +394,7 @@ func (p *Provider) listWebACLs(_ map[string]any) (*plugin.Response, error) { } func (p *Provider) updateWebACL(params map[string]any) (*plugin.Response, error) { - id := strParam(params, "WebACLId") + id := shared.StrParam(params, "WebACLId") if id == "" { return wafError("WAFInvalidParameterException", "WebACLId is required", http.StatusBadRequest), nil } @@ -441,7 +428,7 @@ func (p *Provider) updateWebACL(params map[string]any) (*plugin.Response, error) } w.Rules = marshalJSON(rules) } - w.ChangeToken = strParam(params, "ChangeToken") + w.ChangeToken = shared.StrParam(params, "ChangeToken") if err := p.store.UpdateWebACL(w); err != nil { return nil, err } @@ -449,7 +436,7 @@ func (p *Provider) updateWebACL(params map[string]any) (*plugin.Response, error) } func (p *Provider) deleteWebACL(params map[string]any) (*plugin.Response, error) { - id := strParam(params, "WebACLId") + id := shared.StrParam(params, "WebACLId") if id == "" { return wafError("WAFInvalidParameterException", "WebACLId is required", http.StatusBadRequest), nil } @@ -465,7 +452,7 @@ func (p *Provider) deleteWebACL(params map[string]any) (*plugin.Response, error) // --- IPSet --- func (p *Provider) createIPSet(params map[string]any) (*plugin.Response, error) { - name := strParam(params, "Name") + name := shared.StrParam(params, "Name") if name == "" { return wafError("WAFInvalidParameterException", "Name is required", http.StatusBadRequest), nil } @@ -476,7 +463,7 @@ func (p *Provider) createIPSet(params map[string]any) (*plugin.Response, error) Name: name, ARN: arn, Descriptors: "[]", - ChangeToken: strParam(params, "ChangeToken"), + ChangeToken: shared.StrParam(params, "ChangeToken"), } if err := p.store.CreateIPSet(ip); err != nil { return nil, err @@ -492,7 +479,7 @@ func (p *Provider) createIPSet(params map[string]any) (*plugin.Response, error) } func (p *Provider) getIPSet(params map[string]any) (*plugin.Response, error) { - id := strParam(params, "IPSetId") + id := shared.StrParam(params, "IPSetId") if id == "" { return wafError("WAFInvalidParameterException", "IPSetId is required", http.StatusBadRequest), nil } @@ -530,7 +517,7 @@ func (p *Provider) listIPSets(_ map[string]any) (*plugin.Response, error) { } func (p *Provider) updateIPSet(params map[string]any) (*plugin.Response, error) { - id := strParam(params, "IPSetId") + id := shared.StrParam(params, "IPSetId") if id == "" { return wafError("WAFInvalidParameterException", "IPSetId is required", http.StatusBadRequest), nil } @@ -559,7 +546,7 @@ func (p *Provider) updateIPSet(params map[string]any) (*plugin.Response, error) } ip.Descriptors = marshalJSON(descs) } - ip.ChangeToken = strParam(params, "ChangeToken") + ip.ChangeToken = shared.StrParam(params, "ChangeToken") if err := p.store.UpdateIPSet(ip); err != nil { return nil, err } @@ -567,7 +554,7 @@ func (p *Provider) updateIPSet(params map[string]any) (*plugin.Response, error) } func (p *Provider) deleteIPSet(params map[string]any) (*plugin.Response, error) { - id := strParam(params, "IPSetId") + id := shared.StrParam(params, "IPSetId") if id == "" { return wafError("WAFInvalidParameterException", "IPSetId is required", http.StatusBadRequest), nil } @@ -583,11 +570,11 @@ func (p *Provider) deleteIPSet(params map[string]any) (*plugin.Response, error) // --- Rule --- func (p *Provider) createRule(params map[string]any) (*plugin.Response, error) { - name := strParam(params, "Name") + name := shared.StrParam(params, "Name") if name == "" { return wafError("WAFInvalidParameterException", "Name is required", http.StatusBadRequest), nil } - metricName := strParam(params, "MetricName") + metricName := shared.StrParam(params, "MetricName") if metricName == "" { metricName = name } @@ -599,7 +586,7 @@ func (p *Provider) createRule(params map[string]any) (*plugin.Response, error) { ARN: arn, MetricName: metricName, Predicates: "[]", - ChangeToken: strParam(params, "ChangeToken"), + ChangeToken: shared.StrParam(params, "ChangeToken"), } if err := p.store.CreateRule(r); err != nil { return nil, err @@ -616,7 +603,7 @@ func (p *Provider) createRule(params map[string]any) (*plugin.Response, error) { } func (p *Provider) getRule(params map[string]any) (*plugin.Response, error) { - id := strParam(params, "RuleId") + id := shared.StrParam(params, "RuleId") if id == "" { return wafError("WAFInvalidParameterException", "RuleId is required", http.StatusBadRequest), nil } @@ -655,7 +642,7 @@ func (p *Provider) listRules(_ map[string]any) (*plugin.Response, error) { } func (p *Provider) updateRule(params map[string]any) (*plugin.Response, error) { - id := strParam(params, "RuleId") + id := shared.StrParam(params, "RuleId") if id == "" { return wafError("WAFInvalidParameterException", "RuleId is required", http.StatusBadRequest), nil } @@ -684,7 +671,7 @@ func (p *Provider) updateRule(params map[string]any) (*plugin.Response, error) { } r.Predicates = marshalJSON(preds) } - r.ChangeToken = strParam(params, "ChangeToken") + r.ChangeToken = shared.StrParam(params, "ChangeToken") if err := p.store.UpdateRule(r); err != nil { return nil, err } @@ -692,7 +679,7 @@ func (p *Provider) updateRule(params map[string]any) (*plugin.Response, error) { } func (p *Provider) deleteRule(params map[string]any) (*plugin.Response, error) { - id := strParam(params, "RuleId") + id := shared.StrParam(params, "RuleId") if id == "" { return wafError("WAFInvalidParameterException", "RuleId is required", http.StatusBadRequest), nil } @@ -708,11 +695,11 @@ func (p *Provider) deleteRule(params map[string]any) (*plugin.Response, error) { // --- RuleGroup --- func (p *Provider) createRuleGroup(params map[string]any) (*plugin.Response, error) { - name := strParam(params, "Name") + name := shared.StrParam(params, "Name") if name == "" { return wafError("WAFInvalidParameterException", "Name is required", http.StatusBadRequest), nil } - metricName := strParam(params, "MetricName") + metricName := shared.StrParam(params, "MetricName") if metricName == "" { metricName = name } @@ -723,7 +710,7 @@ func (p *Provider) createRuleGroup(params map[string]any) (*plugin.Response, err Name: name, ARN: arn, MetricName: metricName, - ChangeToken: strParam(params, "ChangeToken"), + ChangeToken: shared.StrParam(params, "ChangeToken"), } if err := p.store.CreateRuleGroup(rg); err != nil { return nil, err @@ -739,7 +726,7 @@ func (p *Provider) createRuleGroup(params map[string]any) (*plugin.Response, err } func (p *Provider) getRuleGroup(params map[string]any) (*plugin.Response, error) { - id := strParam(params, "RuleGroupId") + id := shared.StrParam(params, "RuleGroupId") if id == "" { return wafError("WAFInvalidParameterException", "RuleGroupId is required", http.StatusBadRequest), nil } @@ -772,7 +759,7 @@ func (p *Provider) listRuleGroups(_ map[string]any) (*plugin.Response, error) { } func (p *Provider) updateRuleGroup(params map[string]any) (*plugin.Response, error) { - id := strParam(params, "RuleGroupId") + id := shared.StrParam(params, "RuleGroupId") if id == "" { return wafError("WAFInvalidParameterException", "RuleGroupId is required", http.StatusBadRequest), nil } @@ -786,7 +773,7 @@ func (p *Provider) updateRuleGroup(params map[string]any) (*plugin.Response, err } func (p *Provider) deleteRuleGroup(params map[string]any) (*plugin.Response, error) { - id := strParam(params, "RuleGroupId") + id := shared.StrParam(params, "RuleGroupId") if id == "" { return wafError("WAFInvalidParameterException", "RuleGroupId is required", http.StatusBadRequest), nil } @@ -802,11 +789,11 @@ func (p *Provider) deleteRuleGroup(params map[string]any) (*plugin.Response, err // --- RateBasedRule --- func (p *Provider) createRateBasedRule(params map[string]any) (*plugin.Response, error) { - name := strParam(params, "Name") + name := shared.StrParam(params, "Name") if name == "" { return wafError("WAFInvalidParameterException", "Name is required", http.StatusBadRequest), nil } - metricName := strParam(params, "MetricName") + metricName := shared.StrParam(params, "MetricName") if metricName == "" { metricName = name } @@ -823,7 +810,7 @@ func (p *Provider) createRateBasedRule(params map[string]any) (*plugin.Response, MetricName: metricName, RateLimit: rateLimit, Predicates: "[]", - ChangeToken: strParam(params, "ChangeToken"), + ChangeToken: shared.StrParam(params, "ChangeToken"), } if err := p.store.CreateRateBasedRule(r); err != nil { return nil, err @@ -842,7 +829,7 @@ func (p *Provider) createRateBasedRule(params map[string]any) (*plugin.Response, } func (p *Provider) getRateBasedRule(params map[string]any) (*plugin.Response, error) { - id := strParam(params, "RuleId") + id := shared.StrParam(params, "RuleId") if id == "" { return wafError("WAFInvalidParameterException", "RuleId is required", http.StatusBadRequest), nil } @@ -883,7 +870,7 @@ func (p *Provider) listRateBasedRules(_ map[string]any) (*plugin.Response, error } func (p *Provider) updateRateBasedRule(params map[string]any) (*plugin.Response, error) { - id := strParam(params, "RuleId") + id := shared.StrParam(params, "RuleId") if id == "" { return wafError("WAFInvalidParameterException", "RuleId is required", http.StatusBadRequest), nil } @@ -897,7 +884,7 @@ func (p *Provider) updateRateBasedRule(params map[string]any) (*plugin.Response, if rl, ok := params["RateLimit"].(float64); ok { r.RateLimit = int64(rl) } - r.ChangeToken = strParam(params, "ChangeToken") + r.ChangeToken = shared.StrParam(params, "ChangeToken") if err := p.store.UpdateRateBasedRule(r); err != nil { return nil, err } @@ -905,7 +892,7 @@ func (p *Provider) updateRateBasedRule(params map[string]any) (*plugin.Response, } func (p *Provider) deleteRateBasedRule(params map[string]any) (*plugin.Response, error) { - id := strParam(params, "RuleId") + id := shared.StrParam(params, "RuleId") if id == "" { return wafError("WAFInvalidParameterException", "RuleId is required", http.StatusBadRequest), nil } @@ -921,7 +908,7 @@ func (p *Provider) deleteRateBasedRule(params map[string]any) (*plugin.Response, // --- ByteMatchSet --- func (p *Provider) createByteMatchSet(params map[string]any) (*plugin.Response, error) { - name := strParam(params, "Name") + name := shared.StrParam(params, "Name") if name == "" { return wafError("WAFInvalidParameterException", "Name is required", http.StatusBadRequest), nil } @@ -929,7 +916,7 @@ func (p *Provider) createByteMatchSet(params map[string]any) (*plugin.Response, b := &ByteMatchSet{ ID: id, Name: name, - ChangeToken: strParam(params, "ChangeToken"), + ChangeToken: shared.StrParam(params, "ChangeToken"), Tuples: "[]", } if err := p.store.CreateByteMatchSet(b); err != nil { @@ -946,7 +933,7 @@ func (p *Provider) createByteMatchSet(params map[string]any) (*plugin.Response, } func (p *Provider) getByteMatchSet(params map[string]any) (*plugin.Response, error) { - id := strParam(params, "ByteMatchSetId") + id := shared.StrParam(params, "ByteMatchSetId") if id == "" { return wafError("WAFInvalidParameterException", "ByteMatchSetId is required", http.StatusBadRequest), nil } @@ -984,7 +971,7 @@ func (p *Provider) listByteMatchSets(_ map[string]any) (*plugin.Response, error) } func (p *Provider) updateByteMatchSet(params map[string]any) (*plugin.Response, error) { - id := strParam(params, "ByteMatchSetId") + id := shared.StrParam(params, "ByteMatchSetId") if id == "" { return wafError("WAFInvalidParameterException", "ByteMatchSetId is required", http.StatusBadRequest), nil } @@ -1013,7 +1000,7 @@ func (p *Provider) updateByteMatchSet(params map[string]any) (*plugin.Response, } b.Tuples = marshalJSON(tuples) } - b.ChangeToken = strParam(params, "ChangeToken") + b.ChangeToken = shared.StrParam(params, "ChangeToken") if err := p.store.UpdateByteMatchSet(b); err != nil { return nil, err } @@ -1021,7 +1008,7 @@ func (p *Provider) updateByteMatchSet(params map[string]any) (*plugin.Response, } func (p *Provider) deleteByteMatchSet(params map[string]any) (*plugin.Response, error) { - id := strParam(params, "ByteMatchSetId") + id := shared.StrParam(params, "ByteMatchSetId") if id == "" { return wafError("WAFInvalidParameterException", "ByteMatchSetId is required", http.StatusBadRequest), nil } @@ -1037,7 +1024,7 @@ func (p *Provider) deleteByteMatchSet(params map[string]any) (*plugin.Response, // --- RegexPatternSet --- func (p *Provider) createRegexPatternSet(params map[string]any) (*plugin.Response, error) { - name := strParam(params, "Name") + name := shared.StrParam(params, "Name") if name == "" { return wafError("WAFInvalidParameterException", "Name is required", http.StatusBadRequest), nil } @@ -1046,7 +1033,7 @@ func (p *Provider) createRegexPatternSet(params map[string]any) (*plugin.Respons ID: id, Name: name, Patterns: "[]", - ChangeToken: strParam(params, "ChangeToken"), + ChangeToken: shared.StrParam(params, "ChangeToken"), } if err := p.store.CreateRegexPatternSet(r); err != nil { return nil, err @@ -1062,7 +1049,7 @@ func (p *Provider) createRegexPatternSet(params map[string]any) (*plugin.Respons } func (p *Provider) getRegexPatternSet(params map[string]any) (*plugin.Response, error) { - id := strParam(params, "RegexPatternSetId") + id := shared.StrParam(params, "RegexPatternSetId") if id == "" { return wafError("WAFInvalidParameterException", "RegexPatternSetId is required", http.StatusBadRequest), nil } @@ -1100,7 +1087,7 @@ func (p *Provider) listRegexPatternSets(_ map[string]any) (*plugin.Response, err } func (p *Provider) updateRegexPatternSet(params map[string]any) (*plugin.Response, error) { - id := strParam(params, "RegexPatternSetId") + id := shared.StrParam(params, "RegexPatternSetId") if id == "" { return wafError("WAFInvalidParameterException", "RegexPatternSetId is required", http.StatusBadRequest), nil } @@ -1129,7 +1116,7 @@ func (p *Provider) updateRegexPatternSet(params map[string]any) (*plugin.Respons } r.Patterns = marshalJSON(patterns) } - r.ChangeToken = strParam(params, "ChangeToken") + r.ChangeToken = shared.StrParam(params, "ChangeToken") if err := p.store.UpdateRegexPatternSet(r); err != nil { return nil, err } @@ -1137,7 +1124,7 @@ func (p *Provider) updateRegexPatternSet(params map[string]any) (*plugin.Respons } func (p *Provider) deleteRegexPatternSet(params map[string]any) (*plugin.Response, error) { - id := strParam(params, "RegexPatternSetId") + id := shared.StrParam(params, "RegexPatternSetId") if id == "" { return wafError("WAFInvalidParameterException", "RegexPatternSetId is required", http.StatusBadRequest), nil } @@ -1153,7 +1140,7 @@ func (p *Provider) deleteRegexPatternSet(params map[string]any) (*plugin.Respons // --- SizeConstraintSet --- func (p *Provider) createSizeConstraintSet(params map[string]any) (*plugin.Response, error) { - name := strParam(params, "Name") + name := shared.StrParam(params, "Name") if name == "" { return wafError("WAFInvalidParameterException", "Name is required", http.StatusBadRequest), nil } @@ -1162,7 +1149,7 @@ func (p *Provider) createSizeConstraintSet(params map[string]any) (*plugin.Respo ID: id, Name: name, Constraints: "[]", - ChangeToken: strParam(params, "ChangeToken"), + ChangeToken: shared.StrParam(params, "ChangeToken"), } if err := p.store.CreateSizeConstraintSet(sc); err != nil { return nil, err @@ -1178,7 +1165,7 @@ func (p *Provider) createSizeConstraintSet(params map[string]any) (*plugin.Respo } func (p *Provider) getSizeConstraintSet(params map[string]any) (*plugin.Response, error) { - id := strParam(params, "SizeConstraintSetId") + id := shared.StrParam(params, "SizeConstraintSetId") if id == "" { return wafError("WAFInvalidParameterException", "SizeConstraintSetId is required", http.StatusBadRequest), nil } @@ -1216,7 +1203,7 @@ func (p *Provider) listSizeConstraintSets(_ map[string]any) (*plugin.Response, e } func (p *Provider) updateSizeConstraintSet(params map[string]any) (*plugin.Response, error) { - id := strParam(params, "SizeConstraintSetId") + id := shared.StrParam(params, "SizeConstraintSetId") if id == "" { return wafError("WAFInvalidParameterException", "SizeConstraintSetId is required", http.StatusBadRequest), nil } @@ -1245,7 +1232,7 @@ func (p *Provider) updateSizeConstraintSet(params map[string]any) (*plugin.Respo } sc.Constraints = marshalJSON(constraints) } - sc.ChangeToken = strParam(params, "ChangeToken") + sc.ChangeToken = shared.StrParam(params, "ChangeToken") if err := p.store.UpdateSizeConstraintSet(sc); err != nil { return nil, err } @@ -1253,7 +1240,7 @@ func (p *Provider) updateSizeConstraintSet(params map[string]any) (*plugin.Respo } func (p *Provider) deleteSizeConstraintSet(params map[string]any) (*plugin.Response, error) { - id := strParam(params, "SizeConstraintSetId") + id := shared.StrParam(params, "SizeConstraintSetId") if id == "" { return wafError("WAFInvalidParameterException", "SizeConstraintSetId is required", http.StatusBadRequest), nil } @@ -1269,7 +1256,7 @@ func (p *Provider) deleteSizeConstraintSet(params map[string]any) (*plugin.Respo // --- SqlInjectionMatchSet --- func (p *Provider) createSqlInjectionMatchSet(params map[string]any) (*plugin.Response, error) { - name := strParam(params, "Name") + name := shared.StrParam(params, "Name") if name == "" { return wafError("WAFInvalidParameterException", "Name is required", http.StatusBadRequest), nil } @@ -1278,7 +1265,7 @@ func (p *Provider) createSqlInjectionMatchSet(params map[string]any) (*plugin.Re ID: id, Name: name, Tuples: "[]", - ChangeToken: strParam(params, "ChangeToken"), + ChangeToken: shared.StrParam(params, "ChangeToken"), } if err := p.store.CreateSqlInjectionMatchSet(si); err != nil { return nil, err @@ -1294,7 +1281,7 @@ func (p *Provider) createSqlInjectionMatchSet(params map[string]any) (*plugin.Re } func (p *Provider) getSqlInjectionMatchSet(params map[string]any) (*plugin.Response, error) { - id := strParam(params, "SqlInjectionMatchSetId") + id := shared.StrParam(params, "SqlInjectionMatchSetId") if id == "" { return wafError("WAFInvalidParameterException", "SqlInjectionMatchSetId is required", http.StatusBadRequest), nil } @@ -1332,7 +1319,7 @@ func (p *Provider) listSqlInjectionMatchSets(_ map[string]any) (*plugin.Response } func (p *Provider) updateSqlInjectionMatchSet(params map[string]any) (*plugin.Response, error) { - id := strParam(params, "SqlInjectionMatchSetId") + id := shared.StrParam(params, "SqlInjectionMatchSetId") if id == "" { return wafError("WAFInvalidParameterException", "SqlInjectionMatchSetId is required", http.StatusBadRequest), nil } @@ -1361,7 +1348,7 @@ func (p *Provider) updateSqlInjectionMatchSet(params map[string]any) (*plugin.Re } si.Tuples = marshalJSON(tuples) } - si.ChangeToken = strParam(params, "ChangeToken") + si.ChangeToken = shared.StrParam(params, "ChangeToken") if err := p.store.UpdateSqlInjectionMatchSet(si); err != nil { return nil, err } @@ -1369,7 +1356,7 @@ func (p *Provider) updateSqlInjectionMatchSet(params map[string]any) (*plugin.Re } func (p *Provider) deleteSqlInjectionMatchSet(params map[string]any) (*plugin.Response, error) { - id := strParam(params, "SqlInjectionMatchSetId") + id := shared.StrParam(params, "SqlInjectionMatchSetId") if id == "" { return wafError("WAFInvalidParameterException", "SqlInjectionMatchSetId is required", http.StatusBadRequest), nil } @@ -1385,7 +1372,7 @@ func (p *Provider) deleteSqlInjectionMatchSet(params map[string]any) (*plugin.Re // --- XssMatchSet --- func (p *Provider) createXssMatchSet(params map[string]any) (*plugin.Response, error) { - name := strParam(params, "Name") + name := shared.StrParam(params, "Name") if name == "" { return wafError("WAFInvalidParameterException", "Name is required", http.StatusBadRequest), nil } @@ -1394,7 +1381,7 @@ func (p *Provider) createXssMatchSet(params map[string]any) (*plugin.Response, e ID: id, Name: name, Tuples: "[]", - ChangeToken: strParam(params, "ChangeToken"), + ChangeToken: shared.StrParam(params, "ChangeToken"), } if err := p.store.CreateXssMatchSet(x); err != nil { return nil, err @@ -1410,7 +1397,7 @@ func (p *Provider) createXssMatchSet(params map[string]any) (*plugin.Response, e } func (p *Provider) getXssMatchSet(params map[string]any) (*plugin.Response, error) { - id := strParam(params, "XssMatchSetId") + id := shared.StrParam(params, "XssMatchSetId") if id == "" { return wafError("WAFInvalidParameterException", "XssMatchSetId is required", http.StatusBadRequest), nil } @@ -1448,7 +1435,7 @@ func (p *Provider) listXssMatchSets(_ map[string]any) (*plugin.Response, error) } func (p *Provider) updateXssMatchSet(params map[string]any) (*plugin.Response, error) { - id := strParam(params, "XssMatchSetId") + id := shared.StrParam(params, "XssMatchSetId") if id == "" { return wafError("WAFInvalidParameterException", "XssMatchSetId is required", http.StatusBadRequest), nil } @@ -1477,7 +1464,7 @@ func (p *Provider) updateXssMatchSet(params map[string]any) (*plugin.Response, e } x.Tuples = marshalJSON(tuples) } - x.ChangeToken = strParam(params, "ChangeToken") + x.ChangeToken = shared.StrParam(params, "ChangeToken") if err := p.store.UpdateXssMatchSet(x); err != nil { return nil, err } @@ -1485,7 +1472,7 @@ func (p *Provider) updateXssMatchSet(params map[string]any) (*plugin.Response, e } func (p *Provider) deleteXssMatchSet(params map[string]any) (*plugin.Response, error) { - id := strParam(params, "XssMatchSetId") + id := shared.StrParam(params, "XssMatchSetId") if id == "" { return wafError("WAFInvalidParameterException", "XssMatchSetId is required", http.StatusBadRequest), nil } @@ -1501,7 +1488,7 @@ func (p *Provider) deleteXssMatchSet(params map[string]any) (*plugin.Response, e // --- GeoMatchSet --- func (p *Provider) createGeoMatchSet(params map[string]any) (*plugin.Response, error) { - name := strParam(params, "Name") + name := shared.StrParam(params, "Name") if name == "" { return wafError("WAFInvalidParameterException", "Name is required", http.StatusBadRequest), nil } @@ -1510,7 +1497,7 @@ func (p *Provider) createGeoMatchSet(params map[string]any) (*plugin.Response, e ID: id, Name: name, Constraints: "[]", - ChangeToken: strParam(params, "ChangeToken"), + ChangeToken: shared.StrParam(params, "ChangeToken"), } if err := p.store.CreateGeoMatchSet(g); err != nil { return nil, err @@ -1526,7 +1513,7 @@ func (p *Provider) createGeoMatchSet(params map[string]any) (*plugin.Response, e } func (p *Provider) getGeoMatchSet(params map[string]any) (*plugin.Response, error) { - id := strParam(params, "GeoMatchSetId") + id := shared.StrParam(params, "GeoMatchSetId") if id == "" { return wafError("WAFInvalidParameterException", "GeoMatchSetId is required", http.StatusBadRequest), nil } @@ -1564,7 +1551,7 @@ func (p *Provider) listGeoMatchSets(_ map[string]any) (*plugin.Response, error) } func (p *Provider) updateGeoMatchSet(params map[string]any) (*plugin.Response, error) { - id := strParam(params, "GeoMatchSetId") + id := shared.StrParam(params, "GeoMatchSetId") if id == "" { return wafError("WAFInvalidParameterException", "GeoMatchSetId is required", http.StatusBadRequest), nil } @@ -1593,7 +1580,7 @@ func (p *Provider) updateGeoMatchSet(params map[string]any) (*plugin.Response, e } g.Constraints = marshalJSON(constraints) } - g.ChangeToken = strParam(params, "ChangeToken") + g.ChangeToken = shared.StrParam(params, "ChangeToken") if err := p.store.UpdateGeoMatchSet(g); err != nil { return nil, err } @@ -1601,7 +1588,7 @@ func (p *Provider) updateGeoMatchSet(params map[string]any) (*plugin.Response, e } func (p *Provider) deleteGeoMatchSet(params map[string]any) (*plugin.Response, error) { - id := strParam(params, "GeoMatchSetId") + id := shared.StrParam(params, "GeoMatchSetId") if id == "" { return wafError("WAFInvalidParameterException", "GeoMatchSetId is required", http.StatusBadRequest), nil } @@ -1617,7 +1604,7 @@ func (p *Provider) deleteGeoMatchSet(params map[string]any) (*plugin.Response, e // --- RegexMatchSet --- func (p *Provider) createRegexMatchSet(params map[string]any) (*plugin.Response, error) { - name := strParam(params, "Name") + name := shared.StrParam(params, "Name") if name == "" { return wafError("WAFInvalidParameterException", "Name is required", http.StatusBadRequest), nil } @@ -1626,7 +1613,7 @@ func (p *Provider) createRegexMatchSet(params map[string]any) (*plugin.Response, ID: id, Name: name, Tuples: "[]", - ChangeToken: strParam(params, "ChangeToken"), + ChangeToken: shared.StrParam(params, "ChangeToken"), } if err := p.store.CreateRegexMatchSet(r); err != nil { return nil, err @@ -1642,7 +1629,7 @@ func (p *Provider) createRegexMatchSet(params map[string]any) (*plugin.Response, } func (p *Provider) getRegexMatchSet(params map[string]any) (*plugin.Response, error) { - id := strParam(params, "RegexMatchSetId") + id := shared.StrParam(params, "RegexMatchSetId") if id == "" { return wafError("WAFInvalidParameterException", "RegexMatchSetId is required", http.StatusBadRequest), nil } @@ -1680,7 +1667,7 @@ func (p *Provider) listRegexMatchSets(_ map[string]any) (*plugin.Response, error } func (p *Provider) updateRegexMatchSet(params map[string]any) (*plugin.Response, error) { - id := strParam(params, "RegexMatchSetId") + id := shared.StrParam(params, "RegexMatchSetId") if id == "" { return wafError("WAFInvalidParameterException", "RegexMatchSetId is required", http.StatusBadRequest), nil } @@ -1709,7 +1696,7 @@ func (p *Provider) updateRegexMatchSet(params map[string]any) (*plugin.Response, } r.Tuples = marshalJSON(tuples) } - r.ChangeToken = strParam(params, "ChangeToken") + r.ChangeToken = shared.StrParam(params, "ChangeToken") if err := p.store.UpdateRegexMatchSet(r); err != nil { return nil, err } @@ -1717,7 +1704,7 @@ func (p *Provider) updateRegexMatchSet(params map[string]any) (*plugin.Response, } func (p *Provider) deleteRegexMatchSet(params map[string]any) (*plugin.Response, error) { - id := strParam(params, "RegexMatchSetId") + id := shared.StrParam(params, "RegexMatchSetId") if id == "" { return wafError("WAFInvalidParameterException", "RegexMatchSetId is required", http.StatusBadRequest), nil } @@ -1749,7 +1736,7 @@ func (p *Provider) putLoggingConfiguration(params map[string]any) (*plugin.Respo } func (p *Provider) getLoggingConfiguration(params map[string]any) (*plugin.Response, error) { - arn := strParam(params, "ResourceArn") + arn := shared.StrParam(params, "ResourceArn") if arn == "" { return wafError("WAFInvalidParameterException", "ResourceArn is required", http.StatusBadRequest), nil } @@ -1766,7 +1753,7 @@ func (p *Provider) getLoggingConfiguration(params map[string]any) (*plugin.Respo } func (p *Provider) deleteLoggingConfiguration(params map[string]any) (*plugin.Response, error) { - arn := strParam(params, "ResourceArn") + arn := shared.StrParam(params, "ResourceArn") if arn == "" { return wafError("WAFInvalidParameterException", "ResourceArn is required", http.StatusBadRequest), nil } @@ -1796,8 +1783,8 @@ func (p *Provider) listLoggingConfigurations(_ map[string]any) (*plugin.Response // --- Permission Policy --- func (p *Provider) putPermissionPolicy(params map[string]any) (*plugin.Response, error) { - arn := strParam(params, "ResourceArn") - policy := strParam(params, "Policy") + arn := shared.StrParam(params, "ResourceArn") + policy := shared.StrParam(params, "Policy") if arn == "" { return wafError("WAFInvalidParameterException", "ResourceArn is required", http.StatusBadRequest), nil } @@ -1808,7 +1795,7 @@ func (p *Provider) putPermissionPolicy(params map[string]any) (*plugin.Response, } func (p *Provider) getPermissionPolicy(params map[string]any) (*plugin.Response, error) { - arn := strParam(params, "ResourceArn") + arn := shared.StrParam(params, "ResourceArn") if arn == "" { return wafError("WAFInvalidParameterException", "ResourceArn is required", http.StatusBadRequest), nil } @@ -1823,7 +1810,7 @@ func (p *Provider) getPermissionPolicy(params map[string]any) (*plugin.Response, } func (p *Provider) deletePermissionPolicy(params map[string]any) (*plugin.Response, error) { - arn := strParam(params, "ResourceArn") + arn := shared.StrParam(params, "ResourceArn") if arn == "" { return wafError("WAFInvalidParameterException", "ResourceArn is required", http.StatusBadRequest), nil } @@ -1839,7 +1826,7 @@ func (p *Provider) deletePermissionPolicy(params map[string]any) (*plugin.Respon // --- Tags --- func (p *Provider) tagResource(params map[string]any) (*plugin.Response, error) { - arn := strParam(params, "ResourceARN") + arn := shared.StrParam(params, "ResourceARN") if arn == "" { return wafError("WAFInvalidParameterException", "ResourceARN is required", http.StatusBadRequest), nil } @@ -1851,7 +1838,7 @@ func (p *Provider) tagResource(params map[string]any) (*plugin.Response, error) } func (p *Provider) untagResource(params map[string]any) (*plugin.Response, error) { - arn := strParam(params, "ResourceARN") + arn := shared.StrParam(params, "ResourceARN") if arn == "" { return wafError("WAFInvalidParameterException", "ResourceARN is required", http.StatusBadRequest), nil } @@ -1863,7 +1850,7 @@ func (p *Provider) untagResource(params map[string]any) (*plugin.Response, error } func (p *Provider) listTagsForResource(params map[string]any) (*plugin.Response, error) { - arn := strParam(params, "ResourceARN") + arn := shared.StrParam(params, "ResourceARN") if arn == "" { return wafError("WAFInvalidParameterException", "ResourceARN is required", http.StatusBadRequest), nil } diff --git a/internal/services/wafv2/provider.go b/internal/services/wafv2/provider.go index e5d9ea5..4457c8e 100644 --- a/internal/services/wafv2/provider.go +++ b/internal/services/wafv2/provider.go @@ -214,10 +214,6 @@ func (p *Provider) ListResources(_ context.Context) ([]plugin.Resource, error) { return res, nil } -func (p *Provider) GetMetrics(_ context.Context) (*plugin.ServiceMetrics, error) { - return &plugin.ServiceMetrics{}, nil -} - // --- WebACL --- func (p *Provider) createWebACL(params map[string]any) (*plugin.Response, error) { diff --git a/internal/services/xray/provider.go b/internal/services/xray/provider.go index 4e7bc79..1abf243 100644 --- a/internal/services/xray/provider.go +++ b/internal/services/xray/provider.go @@ -264,10 +264,6 @@ func (p *Provider) ListResources(_ context.Context) ([]plugin.Resource, error) { return res, nil } -func (p *Provider) GetMetrics(_ context.Context) (*plugin.ServiceMetrics, error) { - return &plugin.ServiceMetrics{}, nil -} - // --- Group --- func (p *Provider) createGroup(params map[string]any) (*plugin.Response, error) { diff --git a/internal/shared/arn.go b/internal/shared/arn.go index 081196d..ffdc96c 100644 --- a/internal/shared/arn.go +++ b/internal/shared/arn.go @@ -2,11 +2,17 @@ package shared -import "fmt" +import ( + "fmt" + + "github.com/skyoo2003/devcloud/internal/plugin" +) const ( - DefaultRegion = "us-east-1" - DefaultAccountID = "000000000000" + DefaultRegion = "us-east-1" + // DefaultAccountID mirrors plugin.DefaultAccountID so ARN builders and + // providers cannot drift apart. + DefaultAccountID = plugin.DefaultAccountID ) func BuildARN(service, resourceType, resourceID string) string { diff --git a/internal/shared/params.go b/internal/shared/params.go new file mode 100644 index 0000000..097d448 --- /dev/null +++ b/internal/shared/params.go @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: Apache-2.0 + +package shared + +// StrParam returns params[key] as a string, or "" when the key is absent or +// holds another type. Providers parse request bodies into map[string]any, so +// this is the standard accessor for a string field. +func StrParam(params map[string]any, key string) string { + s, _ := params[key].(string) + return s +} + +// StrParamDefault returns params[key] as a string, or def when the key is +// absent, holds another type, or holds the empty string. +func StrParamDefault(params map[string]any, key, def string) string { + if s, _ := params[key].(string); s != "" { + return s + } + return def +} diff --git a/internal/shared/response.go b/internal/shared/response.go index 6c2a893..ee5fc00 100644 --- a/internal/shared/response.go +++ b/internal/shared/response.go @@ -5,8 +5,6 @@ package shared import ( "encoding/json" "encoding/xml" - "strings" - "unicode" "github.com/skyoo2003/devcloud/internal/plugin" ) @@ -49,94 +47,3 @@ func QueryXMLError(code, message string, status int) *plugin.Response { b, _ := xml.Marshal(e) return &plugin.Response{StatusCode: status, Body: b, ContentType: "text/xml"} } - -// ToCamelCase converts a PascalCase string to camelCase. -// E.g. "ClusterArn" -> "clusterArn", "ARN" -> "arn", "HTTPStatusCode" -> "httpStatusCode". -func ToCamelCase(s string) string { - if s == "" { - return s - } - // Find the first lowercase letter or end of leading uppercase run - runes := []rune(s) - i := 0 - for i < len(runes) && unicode.IsUpper(runes[i]) { - i++ - } - if i == 0 { - return s // already camelCase - } - if i == 1 { - // Single uppercase letter at start - return strings.ToLower(string(runes[0])) + string(runes[1:]) - } - // Multiple uppercase: e.g. "ARN" -> "arn", "ARNPrefix" -> "arnPrefix" - if i == len(runes) { - return strings.ToLower(s) - } - return strings.ToLower(string(runes[:i-1])) + string(runes[i-1:]) -} - -// CamelCaseKeys recursively converts all map keys from PascalCase to camelCase. -func CamelCaseKeys(v any) any { - switch val := v.(type) { - case map[string]any: - result := make(map[string]any, len(val)) - for k, v := range val { - result[ToCamelCase(k)] = CamelCaseKeys(v) - } - return result - case []any: - result := make([]any, len(val)) - for i, item := range val { - result[i] = CamelCaseKeys(item) - } - return result - case []map[string]any: - result := make([]any, len(val)) - for i, item := range val { - result[i] = CamelCaseKeys(item) - } - return result - default: - return v - } -} - -// ToPascalCase converts a camelCase string to PascalCase. -func ToPascalCase(s string) string { - if s == "" { - return s - } - runes := []rune(s) - runes[0] = unicode.ToUpper(runes[0]) - return string(runes) -} - -// PascalCaseKeys recursively converts all map keys from camelCase to PascalCase. -func PascalCaseKeys(v any) map[string]any { - m, ok := v.(map[string]any) - if !ok { - return nil - } - result := make(map[string]any, len(m)) - for k, val := range m { - newKey := ToPascalCase(k) - switch typedVal := val.(type) { - case map[string]any: - result[newKey] = PascalCaseKeys(typedVal) - case []any: - newSlice := make([]any, len(typedVal)) - for i, item := range typedVal { - if subMap, ok := item.(map[string]any); ok { - newSlice[i] = PascalCaseKeys(subMap) - } else { - newSlice[i] = item - } - } - result[newKey] = newSlice - default: - result[newKey] = val - } - } - return result -} diff --git a/internal/shared/store.go b/internal/shared/store.go deleted file mode 100644 index 3692fb7..0000000 --- a/internal/shared/store.go +++ /dev/null @@ -1,114 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -package shared - -import ( - "fmt" - "strings" - - "github.com/skyoo2003/devcloud/internal/storage/sqlite" -) - -type Scanner interface { - Scan(dest ...any) error -} - -type ResourceStore[T any] struct { - db *sqlite.Store - table string - idCol string - cols string - scanner func(Scanner) (T, error) -} - -func NewResourceStore[T any](db *sqlite.Store, table, idCol, cols string, scanner func(Scanner) (T, error)) (*ResourceStore[T], error) { - table = strings.TrimSpace(table) - idCol = strings.TrimSpace(idCol) - if err := validateIdentifier(table, "table"); err != nil { - return nil, err - } - if err := validateIdentifier(idCol, "idCol"); err != nil { - return nil, err - } - var validCols []string - for _, c := range strings.Split(cols, ",") { - c = strings.TrimSpace(c) - if c == "" { - continue - } - if err := validateIdentifier(c, "col"); err != nil { - return nil, err - } - validCols = append(validCols, c) - } - normalizedCols := strings.Join(validCols, ", ") - return &ResourceStore[T]{db: db, table: table, idCol: idCol, cols: normalizedCols, scanner: scanner}, nil -} - -func validateIdentifier(s, kind string) error { - if len(s) == 0 { - return fmt.Errorf("shared: empty %s identifier", kind) - } - for _, r := range s { - isLower := r >= 'a' && r <= 'z' - isUpper := r >= 'A' && r <= 'Z' - isDigit := r >= '0' && r <= '9' - isUnderscore := r == '_' - if !isLower && !isUpper && !isDigit && !isUnderscore { - return fmt.Errorf("shared: invalid %s identifier: %q", kind, s) - } - } - return nil -} - -func (s *ResourceStore[T]) DB() *sqlite.Store { return s.db } - -func (s *ResourceStore[T]) Get(id string) (T, error) { - row := s.db.DB().QueryRow("SELECT "+s.cols+" FROM "+s.table+" WHERE "+s.idCol+" = ?", id) - return s.scanner(row) -} - -func (s *ResourceStore[T]) List(where string, args []any) ([]T, error) { - query := "SELECT " + s.cols + " FROM " + s.table - if where != "" { - query += " WHERE " + where - } - rows, err := s.db.DB().Query(query, args...) - if err != nil { - return nil, err - } - defer func() { _ = rows.Close() }() - var items []T - for rows.Next() { - item, err := s.scanner(rows) - if err != nil { - return nil, err - } - items = append(items, item) - } - return items, rows.Err() -} - -func (s *ResourceStore[T]) Delete(id string) (bool, error) { - res, err := s.db.DB().Exec("DELETE FROM "+s.table+" WHERE "+s.idCol+" = ?", id) - if err != nil { - return false, err - } - n, _ := res.RowsAffected() - return n > 0, nil -} - -func (s *ResourceStore[T]) Count(where string, args []any) (int, error) { - query := "SELECT COUNT(*) FROM " + s.table - if where != "" { - query += " WHERE " + where - } - var n int - err := s.db.DB().QueryRow(query, args...).Scan(&n) - return n, err -} - -func (s *ResourceStore[T]) Exec(query string, args ...any) error { - _, err := s.db.DB().Exec(query, args...) - return err -} diff --git a/internal/shared/store_test.go b/internal/shared/store_test.go deleted file mode 100644 index 31b9d78..0000000 --- a/internal/shared/store_test.go +++ /dev/null @@ -1,204 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -package shared - -import ( - "database/sql" - "errors" - "testing" - - "github.com/skyoo2003/devcloud/internal/storage/sqlite" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -type testItem struct { - ID string - Name string -} - -var testMigrations = []sqlite.Migration{ - {Version: 1, SQL: ` - CREATE TABLE items ( - id TEXT PRIMARY KEY, - name TEXT NOT NULL - ); - `}, -} - -func testScanner(s Scanner) (testItem, error) { - var item testItem - err := s.Scan(&item.ID, &item.Name) - return item, err -} - -func newTestResourceStore(t *testing.T) *ResourceStore[testItem] { - t.Helper() - dbPath := t.TempDir() + "/test.db" - db, err := sqlite.Open(dbPath, testMigrations) - require.NoError(t, err) - t.Cleanup(func() { _ = db.Close() }) - rs, err := NewResourceStore[testItem](db, "items", "id", "id, name", testScanner) - require.NoError(t, err) - return rs -} - -func TestResourceStore_GetNotFound(t *testing.T) { - rs := newTestResourceStore(t) - _, err := rs.Get("nonexistent") - assert.True(t, errors.Is(err, sql.ErrNoRows)) -} - -func TestResourceStore_InsertAndGet(t *testing.T) { - rs := newTestResourceStore(t) - require.NoError(t, rs.Exec("INSERT INTO items (id, name) VALUES (?, ?)", "1", "alpha")) - - item, err := rs.Get("1") - require.NoError(t, err) - assert.Equal(t, "1", item.ID) - assert.Equal(t, "alpha", item.Name) -} - -func TestResourceStore_List(t *testing.T) { - rs := newTestResourceStore(t) - require.NoError(t, rs.Exec("INSERT INTO items (id, name) VALUES (?, ?)", "1", "alpha")) - require.NoError(t, rs.Exec("INSERT INTO items (id, name) VALUES (?, ?)", "2", "beta")) - - items, err := rs.List("", nil) - require.NoError(t, err) - assert.Len(t, items, 2) -} - -func TestResourceStore_ListWithFilter(t *testing.T) { - rs := newTestResourceStore(t) - require.NoError(t, rs.Exec("INSERT INTO items (id, name) VALUES (?, ?)", "1", "alpha")) - require.NoError(t, rs.Exec("INSERT INTO items (id, name) VALUES (?, ?)", "2", "beta")) - - items, err := rs.List("name = ?", []any{"beta"}) - require.NoError(t, err) - require.Len(t, items, 1) - assert.Equal(t, "beta", items[0].Name) -} - -func TestResourceStore_Delete(t *testing.T) { - rs := newTestResourceStore(t) - require.NoError(t, rs.Exec("INSERT INTO items (id, name) VALUES (?, ?)", "1", "alpha")) - - deleted, err := rs.Delete("1") - require.NoError(t, err) - assert.True(t, deleted) - - _, err = rs.Get("1") - assert.True(t, errors.Is(err, sql.ErrNoRows)) -} - -func TestResourceStore_DeleteNotFound(t *testing.T) { - rs := newTestResourceStore(t) - deleted, err := rs.Delete("nonexistent") - require.NoError(t, err) - assert.False(t, deleted) -} - -func TestResourceStore_Count(t *testing.T) { - rs := newTestResourceStore(t) - require.NoError(t, rs.Exec("INSERT INTO items (id, name) VALUES (?, ?)", "1", "alpha")) - require.NoError(t, rs.Exec("INSERT INTO items (id, name) VALUES (?, ?)", "2", "beta")) - - n, err := rs.Count("", nil) - require.NoError(t, err) - assert.Equal(t, 2, n) - - n, err = rs.Count("name = ?", []any{"alpha"}) - require.NoError(t, err) - assert.Equal(t, 1, n) -} - -func newTestDB(t *testing.T) *sqlite.Store { - t.Helper() - dbPath := t.TempDir() + "/test.db" - db, err := sqlite.Open(dbPath, testMigrations) - require.NoError(t, err) - t.Cleanup(func() { _ = db.Close() }) - return db -} - -func TestNewResourceStore_InvalidTable(t *testing.T) { - db := newTestDB(t) - _, err := NewResourceStore[testItem](db, "DROP TABLE items; --", "id", "id", testScanner) - assert.ErrorContains(t, err, "invalid table identifier") -} - -func TestNewResourceStore_InvalidIdCol(t *testing.T) { - db := newTestDB(t) - _, err := NewResourceStore[testItem](db, "items", "id; --", "id", testScanner) - assert.ErrorContains(t, err, "invalid idCol identifier") -} - -func TestNewResourceStore_InvalidCol(t *testing.T) { - db := newTestDB(t) - _, err := NewResourceStore[testItem](db, "items", "id", "id, name; --", testScanner) - assert.ErrorContains(t, err, "invalid col identifier") -} - -func TestNewResourceStore_EmptyTable(t *testing.T) { - db := newTestDB(t) - _, err := NewResourceStore[testItem](db, "", "id", "id", testScanner) - assert.ErrorContains(t, err, "empty table identifier") -} - -func TestNewResourceStore_TrailingComma(t *testing.T) { - db := newTestDB(t) - rs, err := NewResourceStore[testItem](db, "items", "id", "id, name,", testScanner) - require.NoError(t, err) - require.NotNil(t, rs) -} - -func TestNewResourceStore_ValidIdentifiers(t *testing.T) { - db := newTestDB(t) - - tests := []struct { - name string - tableName string - primary string - cols string - }{ - { - name: "underscores", - tableName: "items", - primary: "id", - cols: "id, item_name, created_at", - }, - { - name: "digits_in_identifiers", - tableName: "items", - primary: "id", - cols: "id, name2, col3_v1", - }, - { - name: "trailing_comma", - tableName: "items", - primary: "id", - cols: "id, name,", - }, - { - name: "whitespace_in_identifiers", - tableName: " items ", - primary: " id ", - cols: " id, name ", - }, - { - name: "leading_comma_in_cols", - tableName: "items", - primary: "id", - cols: ", id, name", - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - rs, err := NewResourceStore[testItem](db, tc.tableName, tc.primary, tc.cols, testScanner) - require.NoError(t, err) - require.NotNil(t, rs) - }) - } -} diff --git a/scripts/download-smithy-models.sh b/scripts/download-smithy-models.sh index d10bdbe..9dc6ca5 100755 --- a/scripts/download-smithy-models.sh +++ b/scripts/download-smithy-models.sh @@ -1,55 +1,86 @@ #!/usr/bin/env bash # scripts/download-smithy-models.sh -# Downloads AWS Smithy JSON models from aws-sdk-go-v2 repository. +# Downloads AWS Smithy JSON models from the aws-sdk-go-v2 repository. +# +# The models committed under smithy-models/ are the pin for `make codegen`: +# BASE_URL tracks aws-sdk-go-v2 *main*, so without them codegen output would +# depend on whatever upstream published that day. By default this script only +# fills in missing files, which keeps a local codegen run reproducible and +# offline. The weekly smithy-sync workflow passes --refresh so upstream changes +# land as a reviewable model diff next to the regenerated code. +# +# usage: download-smithy-models.sh [--refresh] [models-dir] [service...] +# Service names are upstream model filenames without .json. With none given, +# every model already in models-dir is processed — name a service explicitly +# to add a new one. set -euo pipefail +REFRESH=0 +if [ "${1:-}" = "--refresh" ]; then + REFRESH=1 + shift +fi + MODELS_DIR="${1:-./smithy-models}" +shift || true BASE_URL="https://raw.githubusercontent.com/aws/aws-sdk-go-v2/main/codegen/sdk-codegen/aws-models" -# Complete list of services to download, organized by DevCloud tiers. -SERVICES=( - # Existing - s3 sqs dynamodb lambda iam sts - # Tier 1 - sns kms secretsmanager ssm logs monitoring events ec2 ecs ecr route53 acm - # Tier 2 - cognito-identity cognito-identity-provider elasticloadbalancingv2 ebs efs - sfn apigateway apigatewayv2 kinesis firehose ses sesv2 rds cloudformation - # Tier 3 - elasticache cloudfront wafv2 glue athena organizations cloudtrail eks - autoscaling appsync emr batch - # Tier 4 (extended coverage) - amplify appconfig application-auto-scaling backup bedrock - cloud-control cloudsearch - codeartifact codebuild codecommit codedeploy codepipeline - config-service cost-explorer dms docdb dynamodb-streams - elastic-beanstalk elasticsearch-service - eventbridge-pipes fis glacier identity-store - iot iot-data-plane iot-wireless - kafka kinesis-analytics-v2 lakeformation - managedblockchain memorydb mq mwaa neptune - opensearch pinpoint acm-pca redshift - ram resource-groups resource-groups-tagging-api - route53resolver sagemaker - serverlessrepo servicediscovery shield - sso-admin support swf textract - timestream-write transcribe transfer - verified-permissions waf xray -) - mkdir -p "$MODELS_DIR" +# Deriving the service list from the directory keeps it from drifting out of +# sync with the models we actually generate from — a hand-maintained list grows +# entries whose upstream filename has since changed, and those 404 silently. +if [ "$#" -gt 0 ]; then + SERVICES=("$@") +else + SERVICES=() + for f in "$MODELS_DIR"/*.json; do + [ -e "$f" ] || continue + SERVICES+=("$(basename "$f" .json)") + done +fi + +if [ "${#SERVICES[@]}" -eq 0 ]; then + echo "No models in $MODELS_DIR and no service named; pass service names to seed it." >&2 + exit 1 +fi + +updated=0 +failed=0 for service in "${SERVICES[@]}"; do dest="${MODELS_DIR}/${service}.json" - if [ -f "$dest" ]; then + if [ -f "$dest" ] && [ "$REFRESH" -eq 0 ]; then echo "SKIP $service (exists)" continue fi - echo "Downloading $service..." - curl -sfL "${BASE_URL}/${service}.json" -o "$dest" 2>/dev/null || { - echo "WARN: failed to download $service, skipping" - rm -f "$dest" - } + # Download to a temp file first: a failed refresh must never delete or + # truncate a model that is already committed. + tmp="${dest}.tmp" + if curl -sfL "${BASE_URL}/${service}.json" -o "$tmp"; then + if [ -f "$dest" ] && cmp -s "$tmp" "$dest"; then + rm -f "$tmp" + else + mv "$tmp" "$dest" + updated=$((updated + 1)) + echo "UPDATED $service" + fi + else + rm -f "$tmp" + failed=$((failed + 1)) + echo "WARN: failed to download $service, keeping any existing copy" >&2 + fi done -echo "Done. $(ls "$MODELS_DIR"/*.json 2>/dev/null | wc -l | tr -d ' ') models downloaded." +# find, not `ls *.json`: under `set -e` + pipefail a glob that matches nothing +# makes ls exit 2 and kills the script right before it reports what happened. +total=$(find "$MODELS_DIR" -maxdepth 1 -name '*.json' | wc -l | tr -d ' ') +echo "Done. ${updated} updated, ${failed} failed, ${total} models total." + +# Exit non-zero on any download failure. Keeping the existing copy is the right +# recovery, but staying green is not: the caller (the weekly sync workflow) would +# regenerate from stale models, find no diff, and report success having synced +# nothing — the exact silent no-op this script was fixed to stop doing. +if [ "$failed" -gt 0 ]; then + echo "ERROR: ${failed} model(s) failed to download; models on disk are unchanged for those." >&2 + exit 1 +fi