From 7985b310c5049407e83a5696c8bc7b3b58fbee82 Mon Sep 17 00:00:00 2001 From: allanice001 Date: Mon, 17 Nov 2025 04:59:39 +0000 Subject: [PATCH] feat: Complete AG Loadbalancer & Cluster API Refactor routing logic (Chi can be a pain when you're managing large sets of routes, but its one of the better options when considering a potential gRPC future) Upgrade API Generation to fully support OAS3.1 Update swagger interface to RapiDoc - the old swagger interface doesnt support OAS3.1 yet Docs are now embedded as part of the UI - once logged in they pick up the cookies and org id from what gets set by the UI, but you can override it Other updates include better portability of the db-studio Signed-off-by: allanice001 --- .semgrep.yml | 100 + Makefile | 6 +- README.md | 1 + atlas.hcl | 20 + cmd/serve.go | 2 +- docs/docs.go | 16 +- docs/swagger.json | 13 +- docs/swagger.yaml | 6713 +++++++++++------- go.mod | 62 +- go.sum | 1697 ++++- internal/api/mount_api_routes.go | 2 + internal/api/mount_cluster_routes.go | 39 + internal/api/mount_db_studio.go | 2 +- internal/api/mount_load_balancer_routes.go | 20 + internal/api/mount_swagger_routes.go | 80 +- internal/api/mw_security.go | 8 +- internal/api/routes.go | 14 +- internal/api/utils.go | 1 + internal/app/runtime.go | 3 +- internal/bg/bastion.go | 3 - internal/handlers/annotations.go | 9 +- internal/handlers/auth.go | 98 +- internal/handlers/clusters.go | 1340 +++- internal/handlers/credentials.go | 183 +- internal/handlers/dns.go | 193 +- internal/handlers/dto/clusters.go | 66 +- internal/handlers/dto/load_balancers.go | 32 + internal/handlers/health.go | 1 - internal/handlers/jobs.go | 2 - internal/handlers/labels.go | 9 +- internal/handlers/load_balancers.go | 283 + internal/handlers/node_pools.go | 289 +- internal/handlers/node_pools_test.go | 381 + internal/handlers/servers.go | 9 +- internal/handlers/servers_test.go | 78 + internal/handlers/ssh_keys.go | 9 +- internal/handlers/taints.go | 9 +- internal/handlers/version.go | 1 - internal/models/cluster.go | 61 +- internal/models/load_balancer.go | 19 + internal/testutil/pgtest/pgtest.go | 119 + internal/web/static.go | 8 +- main.go | 15 +- postgres/Dockerfile | 2 +- schema.sql | 430 ++ tools/pgweb_fetch.go | 137 - ui/eslint.config.js | 31 +- ui/package.json | 15 +- ui/src/App.tsx | 3 + ui/src/api/archer_admin.ts | 8 - ui/src/layouts/app-shell.tsx | 5 + ui/src/layouts/footer.tsx | 2 +- ui/src/layouts/nav-config.ts | 3 + ui/src/layouts/theme-switcher.tsx | 78 + ui/src/layouts/topbar.tsx | 2 + ui/src/pages/annotations/annotation-page.tsx | 1 - ui/src/pages/credentials/credential-page.tsx | 90 +- ui/src/pages/dns/dns-page.tsx | 7 +- ui/src/pages/docs/docs-page.tsx | 171 + ui/src/pages/org/settings.tsx | 11 +- ui/src/pages/servers/server-page.tsx | 30 +- ui/src/pages/ssh/ssh-page.tsx | 11 +- ui/src/providers/theme-provider.tsx | 2 +- ui/src/types/rapidoc.d.ts | 30 + ui/tsconfig.app.json | 2 +- ui/tsconfig.json | 6 +- ui/yarn.lock | 935 ++- 67 files changed, 10745 insertions(+), 3283 deletions(-) create mode 100644 .semgrep.yml create mode 100644 atlas.hcl create mode 100644 internal/api/mount_cluster_routes.go create mode 100644 internal/api/mount_load_balancer_routes.go create mode 100644 internal/handlers/dto/load_balancers.go create mode 100644 internal/handlers/load_balancers.go create mode 100644 internal/handlers/node_pools_test.go create mode 100644 internal/handlers/servers_test.go create mode 100644 internal/models/load_balancer.go create mode 100644 internal/testutil/pgtest/pgtest.go create mode 100644 schema.sql delete mode 100644 tools/pgweb_fetch.go create mode 100644 ui/src/layouts/theme-switcher.tsx create mode 100644 ui/src/pages/docs/docs-page.tsx create mode 100644 ui/src/types/rapidoc.d.ts diff --git a/.semgrep.yml b/.semgrep.yml new file mode 100644 index 0000000..f43f269 --- /dev/null +++ b/.semgrep.yml @@ -0,0 +1,100 @@ +# AutoGlue Semgrep configuration +# Use with: opengrep scan --config .semgrep.yml . + +rules: + + # + # 1. Suppress known benign “direct write to ResponseWriter” warnings + # + - id: autoglue.ignore.direct-write-static + message: Ignore direct writes for static or binary responses + languages: [go] + severity: INFO + metadata: + category: suppression + project: autoglue + patterns: + - pattern: | + _, _ = $W.Write($DATA) + pattern-inside: | + func $F($X...) { + ... + } + paths: + include: + - internal/api/utils.go + - internal/handlers/ssh_keys.go + + + # + # 2. Enforce Allowed Origins checking in writePostMessageHTML + # + - id: autoglue.auth.require-origin-validation + message: > + writePostMessageHTML must validate `origin` against known allowed origins + to prevent token exfiltration via crafted state/redirect parameters. + languages: [go] + severity: ERROR + metadata: + category: security + project: autoglue + + # Look for the JS snippet inside the Go string literal using a regex. + # This is NOT Go code, so we must use pattern-regex, not pattern. + pattern-regex: | + window\.opener\.postMessage\(\{ type: 'autoglue:auth', payload: data \}, .*?\); + + paths: + include: + - internal/handlers/auth.go + + + # + # 3. Require httpOnly+Secure cookies for JWT cookies + # + - id: autoglue.cookies.ensure-secure-jwt + message: > + JWT cookies must always have a Secure field (true in prod, false only for localhost dev). + languages: [go] + severity: WARNING + metadata: + category: security + project: autoglue + + patterns: + # 1) Find any SetCookie for ag_jwt + - pattern: | + http.SetCookie($W, &http.Cookie{ + Name: "ag_jwt", + ... + }) + # 2) BUT ignore cases where the Secure field is present + - pattern-not: | + http.SetCookie($W, &http.Cookie{ + Name: "ag_jwt", + Secure: $SECURE, + ... + }) + + paths: + include: + - internal/handlers/auth.go + + + # + # 4. Ban path.Clean for user-controlled paths + # + - id: autoglue.filesystem.no-path-clean + message: Use securejoin instead of path.Clean() for file paths. + languages: [go] + severity: WARNING + metadata: + category: security + project: autoglue + + pattern: | + path.Clean($X) + + paths: + include: + - internal/web/static.go diff --git a/Makefile b/Makefile index 2605c3a..e1573a9 100644 --- a/Makefile +++ b/Makefile @@ -30,6 +30,7 @@ UI_SSG_ROUTES ?= /,/login,/docs,/pricing # Go versioning (go.mod uses major.minor; you’re on 1.25.4) GO_VERSION ?= 1.25.4 +SWAG_FLAGS ?= --v3.1 --outputTypes json,yaml,go # SDK / package settings (TypeScript) SDK_TS_OUTDIR ?= sdk/ts @@ -112,10 +113,11 @@ $(DOCS_JSON) $(DOCS_YAML): $(GO_SRCS) @echo ">> Generating Swagger docs..." @if ! command -v swag >/dev/null 2>&1; then \ echo "Installing swag/v2 CLI @v2.0.0-rc4..."; \ - $(GOINSTALL) github.com/swaggo/swag/v2/cmd/swag@v2.0.0-rc4; \ + $(GOINSTALL) github.com/swaggo/swag/v2/cmd/swag@latest; \ fi @rm -rf docs/swagger.* docs/docs.go - @swag init -g $(MAIN) -o docs + @swag fmt -d . + @swag init $(SWAG_FLAGS) -g $(MAIN) -o docs # --- spec validation + tag guard --- validate-spec: $(DOCS_JSON) ## Validate docs/swagger.json and pin the core OpenAPI Generator version diff --git a/README.md b/README.md index 30841a2..a13911f 100644 --- a/README.md +++ b/README.md @@ -60,6 +60,7 @@ Create your org (http://localhost:8080/me) - you should be redirected here after Once you have an org - create a set of api keys for your org: They will be in the format of: +Example values only; these are not real secrets. ```text Org Key: org_lnJwmyyWH7JC-JgZo5v3Kw Org Secret: fqd9yebGMfK6h5HSgWn4sXrwr9xlFbvbIYtNylRElMQ diff --git a/atlas.hcl b/atlas.hcl new file mode 100644 index 0000000..19e8eab --- /dev/null +++ b/atlas.hcl @@ -0,0 +1,20 @@ +data "external_schema" "gorm" { + program = [ + "go", + "run", + "-mod=mod", + "ariga.io/atlas-provider-gorm", + "load", + "--path", "./internal/models", + "--dialect", "postgres", + ] +} + +env "gorm" { + src = data.external_schema.gorm.url + dev = "postgres://autoglue:autoglue@localhost:5432/autoglue_dev" +} + +env "gorm-src" { + src = data.external_schema.gorm.url +} \ No newline at end of file diff --git a/cmd/serve.go b/cmd/serve.go index 7037e48..0013a57 100644 --- a/cmd/serve.go +++ b/cmd/serve.go @@ -134,7 +134,7 @@ var serveCmd = &cobra.Command{ dbURL = cfg.DbURL } - studio, err := api.PgwebHandler( + studio, err := api.MountDbStudio( dbURL, "db-studio", false, diff --git a/docs/docs.go b/docs/docs.go index 2e04ad8..c92a99b 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -5,14 +5,22 @@ package docs import "github.com/swaggo/swag/v2" const docTemplate = `{ - "schemes": {{ marshal .Schemes }},"swagger":"2.0","info":{"description":"{{escape .Description}}","title":"{{.Title}}","contact":{"name":"GlueOps"},"version":"{{.Version}}"},"host":"{{.Host}}","basePath":"{{.BasePath}}","paths":{"/.well-known/jwks.json":{"get":{"description":"Returns the JSON Web Key Set for token verification","produces":["application/json"],"tags":["Auth"],"summary":"Get JWKS","operationId":"getJWKS","responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/dto.JWKS"}}}}},"/admin/archer/jobs":{"get":{"security":[{"BearerAuth":[]}],"description":"Paginated background jobs with optional filters. Search ` + "`" + `q` + "`" + ` may match id, type, error, payload (implementation-dependent).","consumes":["application/json"],"produces":["application/json"],"tags":["ArcherAdmin"],"summary":"List Archer jobs (admin)","operationId":"AdminListArcherJobs","parameters":[{"enum":["queued","running","succeeded","failed","canceled","retrying","scheduled"],"type":"string","description":"Filter by status","name":"status","in":"query"},{"type":"string","description":"Filter by queue name / worker name","name":"queue","in":"query"},{"type":"string","description":"Free-text search","name":"q","in":"query"},{"type":"integer","default":1,"description":"Page number","name":"page","in":"query"},{"maximum":100,"minimum":1,"type":"integer","default":25,"description":"Items per page","name":"page_size","in":"query"}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/dto.PageJob"}},"401":{"description":"Unauthorized","schema":{"type":"string"}},"403":{"description":"forbidden","schema":{"type":"string"}},"500":{"description":"internal error","schema":{"type":"string"}}}},"post":{"security":[{"BearerAuth":[]}],"description":"Create a job immediately or schedule it for the future via ` + "`" + `run_at` + "`" + `.","consumes":["application/json"],"produces":["application/json"],"tags":["ArcherAdmin"],"summary":"Enqueue a new Archer job (admin)","operationId":"AdminEnqueueArcherJob","parameters":[{"description":"Job parameters","name":"body","in":"body","required":true,"schema":{"$ref":"#/definitions/dto.EnqueueRequest"}}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/dto.Job"}},"400":{"description":"invalid json or missing fields","schema":{"type":"string"}},"401":{"description":"Unauthorized","schema":{"type":"string"}},"403":{"description":"forbidden","schema":{"type":"string"}},"500":{"description":"internal error","schema":{"type":"string"}}}}},"/admin/archer/jobs/{id}/cancel":{"post":{"security":[{"BearerAuth":[]}],"description":"Set job status to canceled if cancellable. For running jobs, this only affects future picks; wire to Archer if you need active kill.","consumes":["application/json"],"produces":["application/json"],"tags":["ArcherAdmin"],"summary":"Cancel an Archer job (admin)","operationId":"AdminCancelArcherJob","parameters":[{"type":"string","description":"Job ID","name":"id","in":"path","required":true}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/dto.Job"}},"400":{"description":"invalid job or not cancellable","schema":{"type":"string"}},"401":{"description":"Unauthorized","schema":{"type":"string"}},"403":{"description":"forbidden","schema":{"type":"string"}},"404":{"description":"not found","schema":{"type":"string"}}}}},"/admin/archer/jobs/{id}/retry":{"post":{"security":[{"BearerAuth":[]}],"description":"Marks the job retriable (DB flip). Swap this for an Archer admin call if you expose one.","consumes":["application/json"],"produces":["application/json"],"tags":["ArcherAdmin"],"summary":"Retry a failed/canceled Archer job (admin)","operationId":"AdminRetryArcherJob","parameters":[{"type":"string","description":"Job ID","name":"id","in":"path","required":true}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/dto.Job"}},"400":{"description":"invalid job or not eligible","schema":{"type":"string"}},"401":{"description":"Unauthorized","schema":{"type":"string"}},"403":{"description":"forbidden","schema":{"type":"string"}},"404":{"description":"not found","schema":{"type":"string"}}}}},"/admin/archer/queues":{"get":{"security":[{"BearerAuth":[]}],"description":"Summary metrics per queue (pending, running, failed, scheduled).","consumes":["application/json"],"produces":["application/json"],"tags":["ArcherAdmin"],"summary":"List Archer queues (admin)","operationId":"AdminListArcherQueues","responses":{"200":{"description":"OK","schema":{"type":"array","items":{"$ref":"#/definitions/dto.QueueInfo"}}},"401":{"description":"Unauthorized","schema":{"type":"string"}},"403":{"description":"forbidden","schema":{"type":"string"}},"500":{"description":"internal error","schema":{"type":"string"}}}}},"/annotations":{"get":{"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"description":"Returns annotations for the organization in X-Org-ID. Filters: ` + "`" + `key` + "`" + `, ` + "`" + `value` + "`" + `, and ` + "`" + `q` + "`" + ` (key contains). Add ` + "`" + `include=node_pools` + "`" + ` to include linked node pools.","consumes":["application/json"],"produces":["application/json"],"tags":["Annotations"],"summary":"List annotations (org scoped)","operationId":"ListAnnotations","parameters":[{"type":"string","description":"Organization UUID","name":"X-Org-ID","in":"header"},{"type":"string","description":"Exact key","name":"key","in":"query"},{"type":"string","description":"Exact value","name":"value","in":"query"},{"type":"string","description":"key contains (case-insensitive)","name":"q","in":"query"}],"responses":{"200":{"description":"OK","schema":{"type":"array","items":{"$ref":"#/definitions/dto.AnnotationResponse"}}},"401":{"description":"Unauthorized","schema":{"type":"string"}},"403":{"description":"organization required","schema":{"type":"string"}},"500":{"description":"failed to list annotations","schema":{"type":"string"}}}},"post":{"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"description":"Creates an annotation.","consumes":["application/json"],"produces":["application/json"],"tags":["Annotations"],"summary":"Create annotation (org scoped)","operationId":"CreateAnnotation","parameters":[{"type":"string","description":"Organization UUID","name":"X-Org-ID","in":"header"},{"description":"Annotation payload","name":"body","in":"body","required":true,"schema":{"$ref":"#/definitions/dto.CreateAnnotationRequest"}}],"responses":{"201":{"description":"Created","schema":{"$ref":"#/definitions/dto.AnnotationResponse"}},"400":{"description":"invalid json / missing fields","schema":{"type":"string"}},"401":{"description":"Unauthorized","schema":{"type":"string"}},"403":{"description":"organization required","schema":{"type":"string"}},"500":{"description":"create failed","schema":{"type":"string"}}}}},"/annotations/{id}":{"get":{"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"description":"Returns one annotation. Add ` + "`" + `include=node_pools` + "`" + ` to include node pools.","consumes":["application/json"],"produces":["application/json"],"tags":["Annotations"],"summary":"Get annotation by ID (org scoped)","operationId":"GetAnnotation","parameters":[{"type":"string","description":"Organization UUID","name":"X-Org-ID","in":"header"},{"type":"string","description":"Annotation ID (UUID)","name":"id","in":"path","required":true}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/dto.AnnotationResponse"}},"400":{"description":"invalid id","schema":{"type":"string"}},"401":{"description":"Unauthorized","schema":{"type":"string"}},"403":{"description":"organization required","schema":{"type":"string"}},"404":{"description":"not found","schema":{"type":"string"}},"500":{"description":"fetch failed","schema":{"type":"string"}}}},"delete":{"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"description":"Permanently deletes the annotation.","consumes":["application/json"],"produces":["application/json"],"tags":["Annotations"],"summary":"Delete annotation (org scoped)","operationId":"DeleteAnnotation","parameters":[{"type":"string","description":"Organization UUID","name":"X-Org-ID","in":"header"},{"type":"string","description":"Annotation ID (UUID)","name":"id","in":"path","required":true}],"responses":{"204":{"description":"No Content","schema":{"type":"string"}},"400":{"description":"invalid id","schema":{"type":"string"}},"401":{"description":"Unauthorized","schema":{"type":"string"}},"403":{"description":"organization required","schema":{"type":"string"}},"500":{"description":"delete failed","schema":{"type":"string"}}}},"patch":{"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"description":"Partially update annotation fields.","consumes":["application/json"],"produces":["application/json"],"tags":["Annotations"],"summary":"Update annotation (org scoped)","operationId":"UpdateAnnotation","parameters":[{"type":"string","description":"Organization UUID","name":"X-Org-ID","in":"header"},{"type":"string","description":"Annotation ID (UUID)","name":"id","in":"path","required":true},{"description":"Fields to update","name":"body","in":"body","required":true,"schema":{"$ref":"#/definitions/dto.UpdateAnnotationRequest"}}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/dto.AnnotationResponse"}},"400":{"description":"invalid id / invalid json","schema":{"type":"string"}},"401":{"description":"Unauthorized","schema":{"type":"string"}},"403":{"description":"organization required","schema":{"type":"string"}},"404":{"description":"not found","schema":{"type":"string"}},"500":{"description":"update failed","schema":{"type":"string"}}}}},"/auth/logout":{"post":{"consumes":["application/json"],"produces":["application/json"],"tags":["Auth"],"summary":"Revoke refresh token family (logout everywhere)","operationId":"Logout","parameters":[{"description":"Refresh token","name":"body","in":"body","required":true,"schema":{"$ref":"#/definitions/dto.LogoutRequest"}}],"responses":{"204":{"description":"No Content"}}}},"/auth/refresh":{"post":{"consumes":["application/json"],"produces":["application/json"],"tags":["Auth"],"summary":"Rotate refresh token","operationId":"Refresh","parameters":[{"description":"Refresh token","name":"body","in":"body","required":true,"schema":{"$ref":"#/definitions/dto.RefreshRequest"}}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/dto.TokenPair"}}}}},"/auth/{provider}/callback":{"get":{"produces":["application/json"],"tags":["Auth"],"summary":"Handle social login callback","operationId":"AuthCallback","parameters":[{"type":"string","description":"google|github","name":"provider","in":"path","required":true}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/dto.TokenPair"}}}}},"/auth/{provider}/start":{"post":{"description":"Returns provider authorization URL for the frontend to redirect","produces":["application/json"],"tags":["Auth"],"summary":"Begin social login","operationId":"AuthStart","parameters":[{"type":"string","description":"google|github","name":"provider","in":"path","required":true}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/dto.AuthStartResponse"}}}}},"/clusters":{"get":{"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"description":"Returns clusters for the organization in X-Org-ID. Filter by ` + "`" + `q` + "`" + ` (name contains).","produces":["application/json"],"tags":["Clusters"],"summary":"List clusters (org scoped)","operationId":"ListClusters","parameters":[{"type":"string","description":"Organization UUID","name":"X-Org-ID","in":"header"},{"type":"string","description":"Name contains (case-insensitive)","name":"q","in":"query"}],"responses":{"200":{"description":"OK","schema":{"type":"array","items":{"$ref":"#/definitions/dto.ClusterResponse"}}},"401":{"description":"Unauthorized","schema":{"type":"string"}},"403":{"description":"organization required","schema":{"type":"string"}},"500":{"description":"failed to list clusters","schema":{"type":"string"}}}},"post":{"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"description":"Creates a cluster. If ` + "`" + `kubeconfig` + "`" + ` is provided, it will be encrypted per-organization and stored securely (never returned).","consumes":["application/json"],"produces":["application/json"],"tags":["Clusters"],"summary":"Create cluster (org scoped)","operationId":"CreateCluster","parameters":[{"type":"string","description":"Organization UUID","name":"X-Org-ID","in":"header"},{"description":"payload","name":"body","in":"body","required":true,"schema":{"$ref":"#/definitions/dto.CreateClusterRequest"}}],"responses":{"201":{"description":"Created","schema":{"$ref":"#/definitions/dto.ClusterResponse"}},"400":{"description":"invalid json","schema":{"type":"string"}},"401":{"description":"Unauthorized","schema":{"type":"string"}},"403":{"description":"organization required","schema":{"type":"string"}},"500":{"description":"create failed","schema":{"type":"string"}}}}},"/credentials":{"get":{"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"description":"Returns credential metadata for the current org. Secrets are never returned.","consumes":["application/json"],"produces":["application/json"],"tags":["Credentials"],"summary":"List credentials (metadata only)","operationId":"ListCredentials","parameters":[{"type":"string","description":"Organization ID (UUID)","name":"X-Org-ID","in":"header"},{"type":"string","description":"Filter by provider (e.g., aws)","name":"provider","in":"query"},{"type":"string","description":"Filter by kind (e.g., aws_access_key)","name":"kind","in":"query"},{"type":"string","description":"Filter by scope kind (provider/service/resource)","name":"scope_kind","in":"query"}],"responses":{"200":{"description":"OK","schema":{"type":"array","items":{"$ref":"#/definitions/dto.CredentialOut"}}},"401":{"description":"Unauthorized","schema":{"type":"string"}},"403":{"description":"organization required","schema":{"type":"string"}},"500":{"description":"internal server error","schema":{"type":"string"}}}},"post":{"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"consumes":["application/json"],"produces":["application/json"],"tags":["Credentials"],"summary":"Create a credential (encrypts secret)","operationId":"CreateCredential","parameters":[{"type":"string","description":"Organization ID (UUID)","name":"X-Org-ID","in":"header"},{"description":"Credential payload","name":"body","in":"body","required":true,"schema":{"$ref":"#/definitions/dto.CreateCredentialRequest"}}],"responses":{"201":{"description":"Created","schema":{"$ref":"#/definitions/dto.CredentialOut"}},"401":{"description":"Unauthorized","schema":{"type":"string"}},"403":{"description":"organization required","schema":{"type":"string"}},"500":{"description":"internal server error","schema":{"type":"string"}}}}},"/credentials/{id}":{"get":{"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"consumes":["application/json"],"produces":["application/json"],"tags":["Credentials"],"summary":"Get credential by ID (metadata only)","operationId":"GetCredential","parameters":[{"type":"string","description":"Organization ID (UUID)","name":"X-Org-ID","in":"header"},{"type":"string","description":"Credential ID (UUID)","name":"id","in":"path","required":true}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/dto.CredentialOut"}},"401":{"description":"Unauthorized","schema":{"type":"string"}},"403":{"description":"organization required","schema":{"type":"string"}},"500":{"description":"internal server error","schema":{"type":"string"}}}},"delete":{"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"consumes":["application/json"],"produces":["application/json"],"tags":["Credentials"],"summary":"Delete credential","operationId":"DeleteCredential","parameters":[{"type":"string","description":"Organization ID (UUID)","name":"X-Org-ID","in":"header"},{"type":"string","description":"Credential ID (UUID)","name":"id","in":"path","required":true}],"responses":{"204":{"description":"No Content"},"404":{"description":"not found","schema":{"type":"string"}}}},"patch":{"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"consumes":["application/json"],"produces":["application/json"],"tags":["Credentials"],"summary":"Update credential metadata and/or rotate secret","operationId":"UpdateCredential","parameters":[{"type":"string","description":"Organization ID (UUID)","name":"X-Org-ID","in":"header"},{"type":"string","description":"Credential ID (UUID)","name":"id","in":"path","required":true},{"description":"Fields to update","name":"body","in":"body","required":true,"schema":{"$ref":"#/definitions/dto.UpdateCredentialRequest"}}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/dto.CredentialOut"}},"403":{"description":"X-Org-ID required","schema":{"type":"string"}},"404":{"description":"not found","schema":{"type":"string"}}}}},"/credentials/{id}/reveal":{"post":{"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"consumes":["application/json"],"produces":["application/json"],"tags":["Credentials"],"summary":"Reveal decrypted secret (one-time read)","operationId":"RevealCredential","parameters":[{"type":"string","description":"Organization ID (UUID)","name":"X-Org-ID","in":"header"},{"type":"string","description":"Credential ID (UUID)","name":"id","in":"path","required":true}],"responses":{"200":{"description":"OK","schema":{"type":"object","additionalProperties":true}},"403":{"description":"organization required","schema":{"type":"string"}},"404":{"description":"not found","schema":{"type":"string"}}}}},"/dns/domains":{"get":{"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"description":"Returns domains for X-Org-ID. Filters: ` + "`" + `domain_name` + "`" + `, ` + "`" + `status` + "`" + `, ` + "`" + `q` + "`" + ` (contains).","consumes":["application/json"],"produces":["application/json"],"tags":["DNS"],"summary":"List domains (org scoped)","operationId":"ListDomains","parameters":[{"type":"string","description":"Organization UUID","name":"X-Org-ID","in":"header"},{"type":"string","description":"Exact domain name (lowercase, no trailing dot)","name":"domain_name","in":"query"},{"type":"string","description":"pending|provisioning|ready|failed","name":"status","in":"query"},{"type":"string","description":"Domain contains (case-insensitive)","name":"q","in":"query"}],"responses":{"200":{"description":"OK","schema":{"type":"array","items":{"$ref":"#/definitions/dto.DomainResponse"}}},"401":{"description":"Unauthorized","schema":{"type":"string"}},"403":{"description":"organization required","schema":{"type":"string"}},"500":{"description":"db error","schema":{"type":"string"}}}},"post":{"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"description":"Creates a domain bound to a Route 53 scoped credential. Archer will backfill ZoneID if omitted.","consumes":["application/json"],"produces":["application/json"],"tags":["DNS"],"summary":"Create a domain (org scoped)","operationId":"CreateDomain","parameters":[{"type":"string","description":"Organization UUID","name":"X-Org-ID","in":"header"},{"description":"Domain payload","name":"body","in":"body","required":true,"schema":{"$ref":"#/definitions/dto.CreateDomainRequest"}}],"responses":{"201":{"description":"Created","schema":{"$ref":"#/definitions/dto.DomainResponse"}},"400":{"description":"validation error","schema":{"type":"string"}},"401":{"description":"Unauthorized","schema":{"type":"string"}},"403":{"description":"organization required","schema":{"type":"string"}},"500":{"description":"db error","schema":{"type":"string"}}}}},"/dns/domains/{domain_id}/records":{"get":{"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"description":"Filters: ` + "`" + `name` + "`" + `, ` + "`" + `type` + "`" + `, ` + "`" + `status` + "`" + `.","consumes":["application/json"],"produces":["application/json"],"tags":["DNS"],"summary":"List record sets for a domain","operationId":"ListRecordSets","parameters":[{"type":"string","description":"Organization UUID","name":"X-Org-ID","in":"header"},{"type":"string","description":"Domain ID (UUID)","name":"domain_id","in":"path","required":true},{"type":"string","description":"Exact relative name or FQDN (server normalizes)","name":"name","in":"query"},{"type":"string","description":"RR type (A, AAAA, CNAME, TXT, MX, NS, SRV, CAA)","name":"type","in":"query"},{"type":"string","description":"pending|provisioning|ready|failed","name":"status","in":"query"}],"responses":{"200":{"description":"OK","schema":{"type":"array","items":{"$ref":"#/definitions/dto.RecordSetResponse"}}},"403":{"description":"organization required","schema":{"type":"string"}},"404":{"description":"domain not found","schema":{"type":"string"}}}},"post":{"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"consumes":["application/json"],"produces":["application/json"],"tags":["DNS"],"summary":"Create a record set (pending; Archer will UPSERT to Route 53)","operationId":"CreateRecordSet","parameters":[{"type":"string","description":"Organization UUID","name":"X-Org-ID","in":"header"},{"type":"string","description":"Domain ID (UUID)","name":"domain_id","in":"path","required":true},{"description":"Record set payload","name":"body","in":"body","required":true,"schema":{"$ref":"#/definitions/dto.CreateRecordSetRequest"}}],"responses":{"201":{"description":"Created","schema":{"$ref":"#/definitions/dto.RecordSetResponse"}},"400":{"description":"validation error","schema":{"type":"string"}},"403":{"description":"organization required","schema":{"type":"string"}},"404":{"description":"domain not found","schema":{"type":"string"}}}}},"/dns/domains/{id}":{"get":{"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"consumes":["application/json"],"produces":["application/json"],"tags":["DNS"],"summary":"Get a domain (org scoped)","operationId":"GetDomain","parameters":[{"type":"string","description":"Organization UUID","name":"X-Org-ID","in":"header"},{"type":"string","description":"Domain ID (UUID)","name":"id","in":"path","required":true}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/dto.DomainResponse"}},"401":{"description":"Unauthorized","schema":{"type":"string"}},"403":{"description":"organization required","schema":{"type":"string"}},"404":{"description":"not found","schema":{"type":"string"}}}},"delete":{"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"consumes":["application/json"],"produces":["application/json"],"tags":["DNS"],"summary":"Delete a domain","operationId":"DeleteDomain","parameters":[{"type":"string","description":"Organization UUID","name":"X-Org-ID","in":"header"},{"type":"string","description":"Domain ID (UUID)","name":"id","in":"path","required":true}],"responses":{"204":{"description":"No Content"},"403":{"description":"organization required","schema":{"type":"string"}},"404":{"description":"not found","schema":{"type":"string"}}}},"patch":{"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"consumes":["application/json"],"produces":["application/json"],"tags":["DNS"],"summary":"Update a domain (org scoped)","operationId":"UpdateDomain","parameters":[{"type":"string","description":"Organization UUID","name":"X-Org-ID","in":"header"},{"type":"string","description":"Domain ID (UUID)","name":"id","in":"path","required":true},{"description":"Fields to update","name":"body","in":"body","required":true,"schema":{"$ref":"#/definitions/dto.UpdateDomainRequest"}}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/dto.DomainResponse"}},"400":{"description":"validation error","schema":{"type":"string"}},"403":{"description":"organization required","schema":{"type":"string"}},"404":{"description":"not found","schema":{"type":"string"}}}}},"/dns/records/{id}":{"delete":{"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"consumes":["application/json"],"produces":["application/json"],"tags":["DNS"],"summary":"Delete a record set (API removes row; worker can optionally handle external deletion policy)","operationId":"DeleteRecordSet","parameters":[{"type":"string","description":"Organization UUID","name":"X-Org-ID","in":"header"},{"type":"string","description":"Record Set ID (UUID)","name":"id","in":"path","required":true}],"responses":{"204":{"description":"No Content"},"403":{"description":"organization required","schema":{"type":"string"}},"404":{"description":"not found","schema":{"type":"string"}}}},"patch":{"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"consumes":["application/json"],"produces":["application/json"],"tags":["DNS"],"summary":"Update a record set (flips to pending for reconciliation)","operationId":"UpdateRecordSet","parameters":[{"type":"string","description":"Organization UUID","name":"X-Org-ID","in":"header"},{"type":"string","description":"Record Set ID (UUID)","name":"id","in":"path","required":true},{"description":"Fields to update","name":"body","in":"body","required":true,"schema":{"$ref":"#/definitions/dto.UpdateRecordSetRequest"}}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/dto.RecordSetResponse"}},"400":{"description":"validation error","schema":{"type":"string"}},"403":{"description":"organization required","schema":{"type":"string"}},"404":{"description":"not found","schema":{"type":"string"}}}}},"/healthz":{"get":{"description":"Returns 200 OK when the service is up","consumes":["application/json"],"produces":["application/json"],"tags":["Health"],"summary":"Basic health check","operationId":"HealthCheck // operationId","responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/handlers.HealthStatus"}}}}},"/labels":{"get":{"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"description":"Returns node labels for the organization in X-Org-ID. Filters: ` + "`" + `key` + "`" + `, ` + "`" + `value` + "`" + `, and ` + "`" + `q` + "`" + ` (key contains). Add ` + "`" + `include=node_pools` + "`" + ` to include linked node groups.","consumes":["application/json"],"produces":["application/json"],"tags":["Labels"],"summary":"List node labels (org scoped)","operationId":"ListLabels","parameters":[{"type":"string","description":"Organization UUID","name":"X-Org-ID","in":"header"},{"type":"string","description":"Exact key","name":"key","in":"query"},{"type":"string","description":"Exact value","name":"value","in":"query"},{"type":"string","description":"Key contains (case-insensitive)","name":"q","in":"query"}],"responses":{"200":{"description":"OK","schema":{"type":"array","items":{"$ref":"#/definitions/dto.LabelResponse"}}},"401":{"description":"Unauthorized","schema":{"type":"string"}},"403":{"description":"organization required","schema":{"type":"string"}},"500":{"description":"failed to list node taints","schema":{"type":"string"}}}},"post":{"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"description":"Creates a label.","consumes":["application/json"],"produces":["application/json"],"tags":["Labels"],"summary":"Create label (org scoped)","operationId":"CreateLabel","parameters":[{"type":"string","description":"Organization UUID","name":"X-Org-ID","in":"header"},{"description":"Label payload","name":"body","in":"body","required":true,"schema":{"$ref":"#/definitions/dto.CreateLabelRequest"}}],"responses":{"201":{"description":"Created","schema":{"$ref":"#/definitions/dto.LabelResponse"}},"400":{"description":"invalid json / missing fields / invalid node_pool_ids","schema":{"type":"string"}},"401":{"description":"Unauthorized","schema":{"type":"string"}},"403":{"description":"organization required","schema":{"type":"string"}},"500":{"description":"create failed","schema":{"type":"string"}}}}},"/labels/{id}":{"get":{"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"description":"Returns one label.","consumes":["application/json"],"produces":["application/json"],"tags":["Labels"],"summary":"Get label by ID (org scoped)","operationId":"GetLabel","parameters":[{"type":"string","description":"Organization UUID","name":"X-Org-ID","in":"header"},{"type":"string","description":"Label ID (UUID)","name":"id","in":"path","required":true}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/dto.LabelResponse"}},"400":{"description":"invalid id","schema":{"type":"string"}},"401":{"description":"Unauthorized","schema":{"type":"string"}},"403":{"description":"organization required","schema":{"type":"string"}},"404":{"description":"not found","schema":{"type":"string"}},"500":{"description":"fetch failed","schema":{"type":"string"}}}},"delete":{"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"description":"Permanently deletes the label.","consumes":["application/json"],"produces":["application/json"],"tags":["Labels"],"summary":"Delete label (org scoped)","operationId":"DeleteLabel","parameters":[{"type":"string","description":"Organization UUID","name":"X-Org-ID","in":"header"},{"type":"string","description":"Label ID (UUID)","name":"id","in":"path","required":true}],"responses":{"204":{"description":"No Content","schema":{"type":"string"}},"400":{"description":"invalid id","schema":{"type":"string"}},"401":{"description":"Unauthorized","schema":{"type":"string"}},"403":{"description":"organization required","schema":{"type":"string"}},"500":{"description":"delete failed","schema":{"type":"string"}}}},"patch":{"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"description":"Partially update label fields.","consumes":["application/json"],"produces":["application/json"],"tags":["Labels"],"summary":"Update label (org scoped)","operationId":"UpdateLabel","parameters":[{"type":"string","description":"Organization UUID","name":"X-Org-ID","in":"header"},{"type":"string","description":"Label ID (UUID)","name":"id","in":"path","required":true},{"description":"Fields to update","name":"body","in":"body","required":true,"schema":{"$ref":"#/definitions/dto.UpdateLabelRequest"}}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/dto.LabelResponse"}},"400":{"description":"invalid id / invalid json","schema":{"type":"string"}},"401":{"description":"Unauthorized","schema":{"type":"string"}},"403":{"description":"organization required","schema":{"type":"string"}},"404":{"description":"not found","schema":{"type":"string"}},"500":{"description":"update failed","schema":{"type":"string"}}}}},"/me":{"get":{"security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"produces":["application/json"],"tags":["Me"],"summary":"Get current user profile","operationId":"GetMe","responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/handlers.meResponse"}}}},"patch":{"security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"consumes":["application/json"],"produces":["application/json"],"tags":["Me"],"summary":"Update current user profile","operationId":"UpdateMe","parameters":[{"description":"Patch profile","name":"body","in":"body","required":true,"schema":{"$ref":"#/definitions/handlers.updateMeRequest"}}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/models.User"}}}}},"/me/api-keys":{"get":{"security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"produces":["application/json"],"tags":["MeAPIKeys"],"summary":"List my API keys","operationId":"ListUserAPIKeys","responses":{"200":{"description":"OK","schema":{"type":"array","items":{"$ref":"#/definitions/handlers.userAPIKeyOut"}}}}},"post":{"security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"description":"Returns the plaintext key once. Store it securely on the client side.","consumes":["application/json"],"produces":["application/json"],"tags":["MeAPIKeys"],"summary":"Create a new user API key","operationId":"CreateUserAPIKey","parameters":[{"description":"Key options","name":"body","in":"body","required":true,"schema":{"$ref":"#/definitions/handlers.createUserKeyRequest"}}],"responses":{"201":{"description":"Created","schema":{"$ref":"#/definitions/handlers.userAPIKeyOut"}}}}},"/me/api-keys/{id}":{"delete":{"security":[{"BearerAuth":[]}],"produces":["application/json"],"tags":["MeAPIKeys"],"summary":"Delete a user API key","operationId":"DeleteUserAPIKey","parameters":[{"type":"string","description":"Key ID (UUID)","name":"id","in":"path","required":true}],"responses":{"204":{"description":"No Content"}}}},"/node-pools":{"get":{"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"description":"Returns node pools for the organization in X-Org-ID.","consumes":["application/json"],"produces":["application/json"],"tags":["NodePools"],"summary":"List node pools (org scoped)","operationId":"ListNodePools","parameters":[{"type":"string","description":"Organization UUID","name":"X-Org-ID","in":"header"},{"type":"string","description":"Name contains (case-insensitive)","name":"q","in":"query"}],"responses":{"200":{"description":"OK","schema":{"type":"array","items":{"$ref":"#/definitions/dto.NodePoolResponse"}}},"401":{"description":"Unauthorized","schema":{"type":"string"}},"403":{"description":"organization required","schema":{"type":"string"}},"500":{"description":"failed to list node pools","schema":{"type":"string"}}}},"post":{"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"description":"Creates a node pool. Optionally attach initial servers.","consumes":["application/json"],"produces":["application/json"],"tags":["NodePools"],"summary":"Create node pool (org scoped)","operationId":"CreateNodePool","parameters":[{"type":"string","description":"Organization UUID","name":"X-Org-ID","in":"header"},{"description":"NodePool payload","name":"body","in":"body","required":true,"schema":{"$ref":"#/definitions/dto.CreateNodePoolRequest"}}],"responses":{"201":{"description":"Created","schema":{"$ref":"#/definitions/dto.NodePoolResponse"}},"400":{"description":"invalid json / missing fields / invalid server_ids","schema":{"type":"string"}},"401":{"description":"Unauthorized","schema":{"type":"string"}},"403":{"description":"organization required","schema":{"type":"string"}},"500":{"description":"create failed","schema":{"type":"string"}}}}},"/node-pools/{id}":{"get":{"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"description":"Returns one node pool. Add ` + "`" + `include=servers` + "`" + ` to include servers.","consumes":["application/json"],"produces":["application/json"],"tags":["NodePools"],"summary":"Get node pool by ID (org scoped)","operationId":"GetNodePool","parameters":[{"type":"string","description":"Organization UUID","name":"X-Org-ID","in":"header"},{"type":"string","description":"Node Pool ID (UUID)","name":"id","in":"path","required":true}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/dto.NodePoolResponse"}},"400":{"description":"invalid id","schema":{"type":"string"}},"401":{"description":"Unauthorized","schema":{"type":"string"}},"403":{"description":"organization required","schema":{"type":"string"}},"404":{"description":"not found","schema":{"type":"string"}},"500":{"description":"fetch failed","schema":{"type":"string"}}}},"delete":{"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"description":"Permanently deletes the node pool.","consumes":["application/json"],"produces":["application/json"],"tags":["NodePools"],"summary":"Delete node pool (org scoped)","operationId":"DeleteNodePool","parameters":[{"type":"string","description":"Organization UUID","name":"X-Org-ID","in":"header"},{"type":"string","description":"Node Pool ID (UUID)","name":"id","in":"path","required":true}],"responses":{"204":{"description":"No Content","schema":{"type":"string"}},"400":{"description":"invalid id","schema":{"type":"string"}},"401":{"description":"Unauthorized","schema":{"type":"string"}},"403":{"description":"organization required","schema":{"type":"string"}},"500":{"description":"delete failed","schema":{"type":"string"}}}},"patch":{"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"description":"Partially update node pool fields.","consumes":["application/json"],"produces":["application/json"],"tags":["NodePools"],"summary":"Update node pool (org scoped)","operationId":"UpdateNodePool","parameters":[{"type":"string","description":"Organization UUID","name":"X-Org-ID","in":"header"},{"type":"string","description":"Node Pool ID (UUID)","name":"id","in":"path","required":true},{"description":"Fields to update","name":"body","in":"body","required":true,"schema":{"$ref":"#/definitions/dto.UpdateNodePoolRequest"}}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/dto.NodePoolResponse"}},"400":{"description":"invalid id / invalid json","schema":{"type":"string"}},"401":{"description":"Unauthorized","schema":{"type":"string"}},"403":{"description":"organization required","schema":{"type":"string"}},"404":{"description":"not found","schema":{"type":"string"}},"500":{"description":"update failed","schema":{"type":"string"}}}}},"/node-pools/{id}/annotations":{"get":{"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"consumes":["application/json"],"produces":["application/json"],"tags":["NodePools"],"summary":"List annotations attached to a node pool (org scoped)","operationId":"ListNodePoolAnnotations","parameters":[{"type":"string","description":"Organization UUID","name":"X-Org-ID","in":"header"},{"type":"string","description":"Node Pool ID (UUID)","name":"id","in":"path","required":true}],"responses":{"200":{"description":"OK","schema":{"type":"array","items":{"$ref":"#/definitions/dto.AnnotationResponse"}}},"400":{"description":"invalid id","schema":{"type":"string"}},"401":{"description":"Unauthorized","schema":{"type":"string"}},"403":{"description":"organization required","schema":{"type":"string"}},"404":{"description":"not found","schema":{"type":"string"}},"500":{"description":"fetch failed","schema":{"type":"string"}}}},"post":{"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"consumes":["application/json"],"produces":["application/json"],"tags":["NodePools"],"summary":"Attach annotation to a node pool (org scoped)","operationId":"AttachNodePoolAnnotations","parameters":[{"type":"string","description":"Organization UUID","name":"X-Org-ID","in":"header"},{"type":"string","description":"Node Group ID (UUID)","name":"id","in":"path","required":true},{"description":"Annotation IDs to attach","name":"body","in":"body","required":true,"schema":{"$ref":"#/definitions/dto.AttachAnnotationsRequest"}}],"responses":{"204":{"description":"No Content","schema":{"type":"string"}},"400":{"description":"invalid id / invalid server_ids","schema":{"type":"string"}},"401":{"description":"Unauthorized","schema":{"type":"string"}},"403":{"description":"organization required","schema":{"type":"string"}},"404":{"description":"not found","schema":{"type":"string"}},"500":{"description":"attach failed","schema":{"type":"string"}}}}},"/node-pools/{id}/annotations/{annotationId}":{"delete":{"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"consumes":["application/json"],"produces":["application/json"],"tags":["NodePools"],"summary":"Detach one annotation from a node pool (org scoped)","operationId":"DetachNodePoolAnnotation","parameters":[{"type":"string","description":"Organization UUID","name":"X-Org-ID","in":"header"},{"type":"string","description":"Node Pool ID (UUID)","name":"id","in":"path","required":true},{"type":"string","description":"Annotation ID (UUID)","name":"annotationId","in":"path","required":true}],"responses":{"204":{"description":"No Content","schema":{"type":"string"}},"400":{"description":"invalid id","schema":{"type":"string"}},"401":{"description":"Unauthorized","schema":{"type":"string"}},"403":{"description":"organization required","schema":{"type":"string"}},"404":{"description":"not found","schema":{"type":"string"}},"500":{"description":"detach failed","schema":{"type":"string"}}}}},"/node-pools/{id}/labels":{"get":{"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"consumes":["application/json"],"produces":["application/json"],"tags":["NodePools"],"summary":"List labels attached to a node pool (org scoped)","operationId":"ListNodePoolLabels","parameters":[{"type":"string","description":"Organization UUID","name":"X-Org-ID","in":"header"},{"type":"string","description":"Label Pool ID (UUID)","name":"id","in":"path","required":true}],"responses":{"200":{"description":"OK","schema":{"type":"array","items":{"$ref":"#/definitions/dto.LabelResponse"}}},"400":{"description":"invalid id","schema":{"type":"string"}},"401":{"description":"Unauthorized","schema":{"type":"string"}},"403":{"description":"organization required","schema":{"type":"string"}},"404":{"description":"not found","schema":{"type":"string"}},"500":{"description":"fetch failed","schema":{"type":"string"}}}},"post":{"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"consumes":["application/json"],"produces":["application/json"],"tags":["NodePools"],"summary":"Attach labels to a node pool (org scoped)","operationId":"AttachNodePoolLabels","parameters":[{"type":"string","description":"Organization UUID","name":"X-Org-ID","in":"header"},{"type":"string","description":"Node Pool ID (UUID)","name":"id","in":"path","required":true},{"description":"Label IDs to attach","name":"body","in":"body","required":true,"schema":{"$ref":"#/definitions/dto.AttachLabelsRequest"}}],"responses":{"204":{"description":"No Content","schema":{"type":"string"}},"400":{"description":"invalid id / invalid server_ids","schema":{"type":"string"}},"401":{"description":"Unauthorized","schema":{"type":"string"}},"403":{"description":"organization required","schema":{"type":"string"}},"404":{"description":"not found","schema":{"type":"string"}},"500":{"description":"attach failed","schema":{"type":"string"}}}}},"/node-pools/{id}/labels/{labelId}":{"delete":{"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"consumes":["application/json"],"produces":["application/json"],"tags":["NodePools"],"summary":"Detach one label from a node pool (org scoped)","operationId":"DetachNodePoolLabel","parameters":[{"type":"string","description":"Organization UUID","name":"X-Org-ID","in":"header"},{"type":"string","description":"Node Pool ID (UUID)","name":"id","in":"path","required":true},{"type":"string","description":"Label ID (UUID)","name":"labelId","in":"path","required":true}],"responses":{"204":{"description":"No Content","schema":{"type":"string"}},"400":{"description":"invalid id","schema":{"type":"string"}},"401":{"description":"Unauthorized","schema":{"type":"string"}},"403":{"description":"organization required","schema":{"type":"string"}},"404":{"description":"not found","schema":{"type":"string"}},"500":{"description":"detach failed","schema":{"type":"string"}}}}},"/node-pools/{id}/servers":{"get":{"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"consumes":["application/json"],"produces":["application/json"],"tags":["NodePools"],"summary":"List servers attached to a node pool (org scoped)","operationId":"ListNodePoolServers","parameters":[{"type":"string","description":"Organization UUID","name":"X-Org-ID","in":"header"},{"type":"string","description":"Node Pool ID (UUID)","name":"id","in":"path","required":true}],"responses":{"200":{"description":"OK","schema":{"type":"array","items":{"$ref":"#/definitions/dto.ServerResponse"}}},"400":{"description":"invalid id","schema":{"type":"string"}},"401":{"description":"Unauthorized","schema":{"type":"string"}},"403":{"description":"organization required","schema":{"type":"string"}},"404":{"description":"not found","schema":{"type":"string"}},"500":{"description":"fetch failed","schema":{"type":"string"}}}},"post":{"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"consumes":["application/json"],"produces":["application/json"],"tags":["NodePools"],"summary":"Attach servers to a node pool (org scoped)","operationId":"AttachNodePoolServers","parameters":[{"type":"string","description":"Organization UUID","name":"X-Org-ID","in":"header"},{"type":"string","description":"Node Pool ID (UUID)","name":"id","in":"path","required":true},{"description":"Server IDs to attach","name":"body","in":"body","required":true,"schema":{"$ref":"#/definitions/dto.AttachServersRequest"}}],"responses":{"204":{"description":"No Content","schema":{"type":"string"}},"400":{"description":"invalid id / invalid server_ids","schema":{"type":"string"}},"401":{"description":"Unauthorized","schema":{"type":"string"}},"403":{"description":"organization required","schema":{"type":"string"}},"404":{"description":"not found","schema":{"type":"string"}},"500":{"description":"attach failed","schema":{"type":"string"}}}}},"/node-pools/{id}/servers/{serverId}":{"delete":{"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"consumes":["application/json"],"produces":["application/json"],"tags":["NodePools"],"summary":"Detach one server from a node pool (org scoped)","operationId":"DetachNodePoolServer","parameters":[{"type":"string","description":"Organization UUID","name":"X-Org-ID","in":"header"},{"type":"string","description":"Node Pool ID (UUID)","name":"id","in":"path","required":true},{"type":"string","description":"Server ID (UUID)","name":"serverId","in":"path","required":true}],"responses":{"204":{"description":"No Content","schema":{"type":"string"}},"400":{"description":"invalid id","schema":{"type":"string"}},"401":{"description":"Unauthorized","schema":{"type":"string"}},"403":{"description":"organization required","schema":{"type":"string"}},"404":{"description":"not found","schema":{"type":"string"}},"500":{"description":"detach failed","schema":{"type":"string"}}}}},"/node-pools/{id}/taints":{"get":{"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"consumes":["application/json"],"produces":["application/json"],"tags":["NodePools"],"summary":"List taints attached to a node pool (org scoped)","operationId":"ListNodePoolTaints","parameters":[{"type":"string","description":"Organization UUID","name":"X-Org-ID","in":"header"},{"type":"string","description":"Node Pool ID (UUID)","name":"id","in":"path","required":true}],"responses":{"200":{"description":"OK","schema":{"type":"array","items":{"$ref":"#/definitions/dto.TaintResponse"}}},"400":{"description":"invalid id","schema":{"type":"string"}},"401":{"description":"Unauthorized","schema":{"type":"string"}},"403":{"description":"organization required","schema":{"type":"string"}},"404":{"description":"not found","schema":{"type":"string"}},"500":{"description":"fetch failed","schema":{"type":"string"}}}},"post":{"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"consumes":["application/json"],"produces":["application/json"],"tags":["NodePools"],"summary":"Attach taints to a node pool (org scoped)","operationId":"AttachNodePoolTaints","parameters":[{"type":"string","description":"Organization UUID","name":"X-Org-ID","in":"header"},{"type":"string","description":"Node Pool ID (UUID)","name":"id","in":"path","required":true},{"description":"Taint IDs to attach","name":"body","in":"body","required":true,"schema":{"$ref":"#/definitions/dto.AttachTaintsRequest"}}],"responses":{"204":{"description":"No Content","schema":{"type":"string"}},"400":{"description":"invalid id / invalid taint_ids","schema":{"type":"string"}},"401":{"description":"Unauthorized","schema":{"type":"string"}},"403":{"description":"organization required","schema":{"type":"string"}},"404":{"description":"not found","schema":{"type":"string"}},"500":{"description":"attach failed","schema":{"type":"string"}}}}},"/node-pools/{id}/taints/{taintId}":{"delete":{"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"consumes":["application/json"],"produces":["application/json"],"tags":["NodePools"],"summary":"Detach one taint from a node pool (org scoped)","operationId":"DetachNodePoolTaint","parameters":[{"type":"string","description":"Organization UUID","name":"X-Org-ID","in":"header"},{"type":"string","description":"Node Pool ID (UUID)","name":"id","in":"path","required":true},{"type":"string","description":"Taint ID (UUID)","name":"taintId","in":"path","required":true}],"responses":{"204":{"description":"No Content","schema":{"type":"string"}},"400":{"description":"invalid id","schema":{"type":"string"}},"401":{"description":"Unauthorized","schema":{"type":"string"}},"403":{"description":"organization required","schema":{"type":"string"}},"404":{"description":"not found","schema":{"type":"string"}},"500":{"description":"detach failed","schema":{"type":"string"}}}}},"/orgs":{"get":{"security":[{"BearerAuth":[]}],"produces":["application/json"],"tags":["Orgs"],"summary":"List organizations I belong to","operationId":"listMyOrgs","responses":{"200":{"description":"OK","schema":{"type":"array","items":{"$ref":"#/definitions/models.Organization"}}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/utils.ErrorResponse"}}}},"post":{"security":[{"BearerAuth":[]}],"consumes":["application/json"],"produces":["application/json"],"tags":["Orgs"],"summary":"Create organization","operationId":"createOrg","parameters":[{"description":"Org payload","name":"body","in":"body","required":true,"schema":{"$ref":"#/definitions/handlers.orgCreateReq"}}],"responses":{"201":{"description":"Created","schema":{"$ref":"#/definitions/models.Organization"}},"400":{"description":"Bad Request","schema":{"$ref":"#/definitions/utils.ErrorResponse"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/utils.ErrorResponse"}},"409":{"description":"Conflict","schema":{"$ref":"#/definitions/utils.ErrorResponse"}}}}},"/orgs/{id}":{"get":{"security":[{"BearerAuth":[]}],"produces":["application/json"],"tags":["Orgs"],"summary":"Get organization","operationId":"getOrg","parameters":[{"type":"string","description":"Org ID (UUID)","name":"id","in":"path","required":true}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/models.Organization"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/utils.ErrorResponse"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/utils.ErrorResponse"}}}},"delete":{"security":[{"BearerAuth":[]}],"produces":["application/json"],"tags":["Orgs"],"summary":"Delete organization (owner)","operationId":"deleteOrg","parameters":[{"type":"string","description":"Org ID (UUID)","name":"id","in":"path","required":true}],"responses":{"204":{"description":"Deleted"},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/utils.ErrorResponse"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/utils.ErrorResponse"}}}},"patch":{"security":[{"BearerAuth":[]}],"consumes":["application/json"],"produces":["application/json"],"tags":["Orgs"],"summary":"Update organization (owner/admin)","operationId":"updateOrg","parameters":[{"type":"string","description":"Org ID (UUID)","name":"id","in":"path","required":true},{"description":"Update payload","name":"body","in":"body","required":true,"schema":{"$ref":"#/definitions/handlers.orgUpdateReq"}}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/models.Organization"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/utils.ErrorResponse"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/utils.ErrorResponse"}}}}},"/orgs/{id}/api-keys":{"get":{"security":[{"BearerAuth":[]}],"produces":["application/json"],"tags":["Orgs"],"summary":"List org-scoped API keys (no secrets)","operationId":"listOrgKeys","parameters":[{"type":"string","description":"Org ID (UUID)","name":"id","in":"path","required":true}],"responses":{"200":{"description":"OK","schema":{"type":"array","items":{"$ref":"#/definitions/models.APIKey"}}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/utils.ErrorResponse"}}}},"post":{"security":[{"BearerAuth":[]}],"consumes":["application/json"],"produces":["application/json"],"tags":["Orgs"],"summary":"Create org key/secret pair (owner/admin)","operationId":"createOrgKey","parameters":[{"type":"string","description":"Org ID (UUID)","name":"id","in":"path","required":true},{"description":"Key name + optional expiry","name":"body","in":"body","required":true,"schema":{"$ref":"#/definitions/handlers.orgKeyCreateReq"}}],"responses":{"201":{"description":"Created","schema":{"$ref":"#/definitions/handlers.orgKeyCreateResp"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/utils.ErrorResponse"}}}}},"/orgs/{id}/api-keys/{key_id}":{"delete":{"security":[{"BearerAuth":[]}],"produces":["application/json"],"tags":["Orgs"],"summary":"Delete org key (owner/admin)","operationId":"deleteOrgKey","parameters":[{"type":"string","description":"Org ID (UUID)","name":"id","in":"path","required":true},{"type":"string","description":"Key ID (UUID)","name":"key_id","in":"path","required":true}],"responses":{"204":{"description":"Deleted"},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/utils.ErrorResponse"}}}}},"/orgs/{id}/members":{"get":{"security":[{"BearerAuth":[]}],"produces":["application/json"],"tags":["Orgs"],"summary":"List members in org","operationId":"listMembers","parameters":[{"type":"string","description":"Org ID (UUID)","name":"id","in":"path","required":true}],"responses":{"200":{"description":"OK","schema":{"type":"array","items":{"$ref":"#/definitions/handlers.memberOut"}}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/utils.ErrorResponse"}}}},"post":{"security":[{"BearerAuth":[]}],"consumes":["application/json"],"produces":["application/json"],"tags":["Orgs"],"summary":"Add or update a member (owner/admin)","operationId":"addOrUpdateMember","parameters":[{"type":"string","description":"Org ID (UUID)","name":"id","in":"path","required":true},{"description":"User \u0026 role","name":"body","in":"body","required":true,"schema":{"$ref":"#/definitions/handlers.memberUpsertReq"}}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/handlers.memberOut"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/utils.ErrorResponse"}}}}},"/orgs/{id}/members/{user_id}":{"delete":{"security":[{"BearerAuth":[]}],"produces":["application/json"],"tags":["Orgs"],"summary":"Remove a member (owner/admin)","operationId":"removeMember","parameters":[{"type":"string","description":"Org ID (UUID)","name":"id","in":"path","required":true},{"type":"string","description":"User ID (UUID)","name":"user_id","in":"path","required":true}],"responses":{"204":{"description":"Removed"},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/utils.ErrorResponse"}}}}},"/servers":{"get":{"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"description":"Returns servers for the organization in X-Org-ID. Optional filters: status, role.","consumes":["application/json"],"produces":["application/json"],"tags":["Servers"],"summary":"List servers (org scoped)","operationId":"ListServers","parameters":[{"type":"string","description":"Organization UUID","name":"X-Org-ID","in":"header"},{"type":"string","description":"Filter by status (pending|provisioning|ready|failed)","name":"status","in":"query"},{"type":"string","description":"Filter by role","name":"role","in":"query"}],"responses":{"200":{"description":"OK","schema":{"type":"array","items":{"$ref":"#/definitions/dto.ServerResponse"}}},"401":{"description":"Unauthorized","schema":{"type":"string"}},"403":{"description":"organization required","schema":{"type":"string"}},"500":{"description":"failed to list servers","schema":{"type":"string"}}}},"post":{"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"description":"Creates a server bound to the org in X-Org-ID. Validates that ssh_key_id belongs to the org.","consumes":["application/json"],"produces":["application/json"],"tags":["Servers"],"summary":"Create server (org scoped)","operationId":"CreateServer","parameters":[{"type":"string","description":"Organization UUID","name":"X-Org-ID","in":"header"},{"description":"Server payload","name":"body","in":"body","required":true,"schema":{"$ref":"#/definitions/dto.CreateServerRequest"}}],"responses":{"201":{"description":"Created","schema":{"$ref":"#/definitions/dto.ServerResponse"}},"400":{"description":"invalid json / missing fields / invalid status / invalid ssh_key_id","schema":{"type":"string"}},"401":{"description":"Unauthorized","schema":{"type":"string"}},"403":{"description":"organization required","schema":{"type":"string"}},"500":{"description":"create failed","schema":{"type":"string"}}}}},"/servers/{id}":{"get":{"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"description":"Returns one server in the given organization.","consumes":["application/json"],"produces":["application/json"],"tags":["Servers"],"summary":"Get server by ID (org scoped)","operationId":"GetServer","parameters":[{"type":"string","description":"Organization UUID","name":"X-Org-ID","in":"header"},{"type":"string","description":"Server ID (UUID)","name":"id","in":"path","required":true}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/dto.ServerResponse"}},"400":{"description":"invalid id","schema":{"type":"string"}},"401":{"description":"Unauthorized","schema":{"type":"string"}},"403":{"description":"organization required","schema":{"type":"string"}},"404":{"description":"not found","schema":{"type":"string"}},"500":{"description":"fetch failed","schema":{"type":"string"}}}},"delete":{"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"description":"Permanently deletes the server.","consumes":["application/json"],"produces":["application/json"],"tags":["Servers"],"summary":"Delete server (org scoped)","operationId":"DeleteServer","parameters":[{"type":"string","description":"Organization UUID","name":"X-Org-ID","in":"header"},{"type":"string","description":"Server ID (UUID)","name":"id","in":"path","required":true}],"responses":{"204":{"description":"No Content","schema":{"type":"string"}},"400":{"description":"invalid id","schema":{"type":"string"}},"401":{"description":"Unauthorized","schema":{"type":"string"}},"403":{"description":"organization required","schema":{"type":"string"}},"500":{"description":"delete failed","schema":{"type":"string"}}}},"patch":{"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"description":"Partially update fields; changing ssh_key_id validates ownership.","consumes":["application/json"],"produces":["application/json"],"tags":["Servers"],"summary":"Update server (org scoped)","operationId":"UpdateServer","parameters":[{"type":"string","description":"Organization UUID","name":"X-Org-ID","in":"header"},{"type":"string","description":"Server ID (UUID)","name":"id","in":"path","required":true},{"description":"Fields to update","name":"body","in":"body","required":true,"schema":{"$ref":"#/definitions/dto.UpdateServerRequest"}}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/dto.ServerResponse"}},"400":{"description":"invalid id / invalid json / invalid status / invalid ssh_key_id","schema":{"type":"string"}},"401":{"description":"Unauthorized","schema":{"type":"string"}},"403":{"description":"organization required","schema":{"type":"string"}},"404":{"description":"not found","schema":{"type":"string"}},"500":{"description":"update failed","schema":{"type":"string"}}}}},"/servers/{id}/reset-hostkey":{"post":{"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"description":"Clears the stored SSH host key for this server. The next SSH connection will re-learn the host key (trust-on-first-use).","consumes":["application/json"],"produces":["application/json"],"tags":["Servers"],"summary":"Reset SSH host key (org scoped)","operationId":"ResetServerHostKey","parameters":[{"type":"string","description":"Organization UUID","name":"X-Org-ID","in":"header"},{"type":"string","description":"Server ID (UUID)","name":"id","in":"path","required":true}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/dto.ServerResponse"}},"400":{"description":"invalid id","schema":{"type":"string"}},"401":{"description":"Unauthorized","schema":{"type":"string"}},"403":{"description":"organization required","schema":{"type":"string"}},"404":{"description":"not found","schema":{"type":"string"}},"500":{"description":"reset failed","schema":{"type":"string"}}}}},"/ssh":{"get":{"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"description":"Returns ssh keys for the organization in X-Org-ID.","consumes":["application/json"],"produces":["application/json"],"tags":["Ssh"],"summary":"List ssh keys (org scoped)","operationId":"ListPublicSshKeys","parameters":[{"type":"string","description":"Organization UUID","name":"X-Org-ID","in":"header"}],"responses":{"200":{"description":"OK","schema":{"type":"array","items":{"$ref":"#/definitions/dto.SshResponse"}}},"401":{"description":"Unauthorized","schema":{"type":"string"}},"403":{"description":"organization required","schema":{"type":"string"}},"500":{"description":"failed to list keys","schema":{"type":"string"}}}},"post":{"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"description":"Generates an RSA or ED25519 keypair, saves it, and returns metadata. For RSA you may set bits (2048/3072/4096). Default is 4096. ED25519 ignores bits.","consumes":["application/json"],"produces":["application/json"],"tags":["Ssh"],"summary":"Create ssh keypair (org scoped)","operationId":"CreateSSHKey","parameters":[{"type":"string","description":"Organization UUID","name":"X-Org-ID","in":"header"},{"description":"Key generation options","name":"body","in":"body","required":true,"schema":{"$ref":"#/definitions/dto.CreateSSHRequest"}}],"responses":{"201":{"description":"Created","schema":{"$ref":"#/definitions/dto.SshResponse"}},"400":{"description":"invalid json / invalid bits","schema":{"type":"string"}},"401":{"description":"Unauthorized","schema":{"type":"string"}},"403":{"description":"organization required","schema":{"type":"string"}},"500":{"description":"generation/create failed","schema":{"type":"string"}}}}},"/ssh/{id}":{"get":{"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"description":"Returns public key fields. Append ` + "`" + `?reveal=true` + "`" + ` to include the private key PEM.","consumes":["application/json"],"produces":["application/json"],"tags":["Ssh"],"summary":"Get ssh key by ID (org scoped)","operationId":"GetSSHKey","parameters":[{"type":"string","description":"Organization UUID","name":"X-Org-ID","in":"header"},{"type":"string","description":"SSH Key ID (UUID)","name":"id","in":"path","required":true},{"type":"boolean","description":"Reveal private key PEM","name":"reveal","in":"query"}],"responses":{"200":{"description":"When reveal=true","schema":{"$ref":"#/definitions/dto.SshRevealResponse"}},"400":{"description":"invalid id","schema":{"type":"string"}},"401":{"description":"Unauthorized","schema":{"type":"string"}},"403":{"description":"organization required","schema":{"type":"string"}},"404":{"description":"not found","schema":{"type":"string"}},"500":{"description":"fetch failed","schema":{"type":"string"}}}},"delete":{"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"description":"Permanently deletes a keypair.","consumes":["application/json"],"produces":["application/json"],"tags":["Ssh"],"summary":"Delete ssh keypair (org scoped)","operationId":"DeleteSSHKey","parameters":[{"type":"string","description":"Organization UUID","name":"X-Org-ID","in":"header"},{"type":"string","description":"SSH Key ID (UUID)","name":"id","in":"path","required":true}],"responses":{"204":{"description":"No Content","schema":{"type":"string"}},"400":{"description":"invalid id","schema":{"type":"string"}},"401":{"description":"Unauthorized","schema":{"type":"string"}},"403":{"description":"organization required","schema":{"type":"string"}},"500":{"description":"delete failed","schema":{"type":"string"}}}}},"/ssh/{id}/download":{"get":{"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"description":"Download ` + "`" + `part=public|private|both` + "`" + ` of the keypair. ` + "`" + `both` + "`" + ` returns a zip file.","produces":["application/json"],"tags":["Ssh"],"summary":"Download ssh key files by ID (org scoped)","operationId":"DownloadSSHKey","parameters":[{"type":"string","description":"Organization UUID","name":"X-Org-ID","in":"header","required":true},{"type":"string","description":"SSH Key ID (UUID)","name":"id","in":"path","required":true},{"enum":["public","private","both"],"type":"string","description":"Which part to download","name":"part","in":"query","required":true}],"responses":{"200":{"description":"file content","schema":{"type":"string"}},"400":{"description":"invalid id / invalid part","schema":{"type":"string"}},"401":{"description":"Unauthorized","schema":{"type":"string"}},"403":{"description":"organization required","schema":{"type":"string"}},"404":{"description":"not found","schema":{"type":"string"}},"500":{"description":"download failed","schema":{"type":"string"}}}}},"/taints":{"get":{"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"description":"Returns node taints for the organization in X-Org-ID. Filters: ` + "`" + `key` + "`" + `, ` + "`" + `value` + "`" + `, and ` + "`" + `q` + "`" + ` (key contains). Add ` + "`" + `include=node_pools` + "`" + ` to include linked node pools.","consumes":["application/json"],"produces":["application/json"],"tags":["Taints"],"summary":"List node pool taints (org scoped)","operationId":"ListTaints","parameters":[{"type":"string","description":"Organization UUID","name":"X-Org-ID","in":"header"},{"type":"string","description":"Exact key","name":"key","in":"query"},{"type":"string","description":"Exact value","name":"value","in":"query"},{"type":"string","description":"key contains (case-insensitive)","name":"q","in":"query"}],"responses":{"200":{"description":"OK","schema":{"type":"array","items":{"$ref":"#/definitions/dto.TaintResponse"}}},"401":{"description":"Unauthorized","schema":{"type":"string"}},"403":{"description":"organization required","schema":{"type":"string"}},"500":{"description":"failed to list node taints","schema":{"type":"string"}}}},"post":{"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"description":"Creates a taint.","consumes":["application/json"],"produces":["application/json"],"tags":["Taints"],"summary":"Create node taint (org scoped)","operationId":"CreateTaint","parameters":[{"type":"string","description":"Organization UUID","name":"X-Org-ID","in":"header"},{"description":"Taint payload","name":"body","in":"body","required":true,"schema":{"$ref":"#/definitions/dto.CreateTaintRequest"}}],"responses":{"201":{"description":"Created","schema":{"$ref":"#/definitions/dto.TaintResponse"}},"400":{"description":"invalid json / missing fields / invalid node_pool_ids","schema":{"type":"string"}},"401":{"description":"Unauthorized","schema":{"type":"string"}},"403":{"description":"organization required","schema":{"type":"string"}},"500":{"description":"create failed","schema":{"type":"string"}}}}},"/taints/{id}":{"get":{"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"consumes":["application/json"],"produces":["application/json"],"tags":["Taints"],"summary":"Get node taint by ID (org scoped)","operationId":"GetTaint","parameters":[{"type":"string","description":"Organization UUID","name":"X-Org-ID","in":"header"},{"type":"string","description":"Node Taint ID (UUID)","name":"id","in":"path","required":true}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/dto.TaintResponse"}},"400":{"description":"invalid id","schema":{"type":"string"}},"401":{"description":"Unauthorized","schema":{"type":"string"}},"403":{"description":"organization required","schema":{"type":"string"}},"404":{"description":"not found","schema":{"type":"string"}},"500":{"description":"fetch failed","schema":{"type":"string"}}}},"delete":{"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"description":"Permanently deletes the taint.","consumes":["application/json"],"produces":["application/json"],"tags":["Taints"],"summary":"Delete taint (org scoped)","operationId":"DeleteTaint","parameters":[{"type":"string","description":"Organization UUID","name":"X-Org-ID","in":"header"},{"type":"string","description":"Node Taint ID (UUID)","name":"id","in":"path","required":true}],"responses":{"204":{"description":"No Content","schema":{"type":"string"}},"400":{"description":"invalid id","schema":{"type":"string"}},"401":{"description":"Unauthorized","schema":{"type":"string"}},"403":{"description":"organization required","schema":{"type":"string"}},"500":{"description":"delete failed","schema":{"type":"string"}}}},"patch":{"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"description":"Partially update taint fields.","consumes":["application/json"],"produces":["application/json"],"tags":["Taints"],"summary":"Update node taint (org scoped)","operationId":"UpdateTaint","parameters":[{"type":"string","description":"Organization UUID","name":"X-Org-ID","in":"header"},{"type":"string","description":"Node Taint ID (UUID)","name":"id","in":"path","required":true},{"description":"Fields to update","name":"body","in":"body","required":true,"schema":{"$ref":"#/definitions/dto.UpdateTaintRequest"}}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/dto.TaintResponse"}},"400":{"description":"invalid id / invalid json","schema":{"type":"string"}},"401":{"description":"Unauthorized","schema":{"type":"string"}},"403":{"description":"organization required","schema":{"type":"string"}},"404":{"description":"not found","schema":{"type":"string"}},"500":{"description":"update failed","schema":{"type":"string"}}}}},"/version":{"get":{"description":"Returns build/runtime metadata for the running service.","consumes":["application/json"],"produces":["application/json"],"tags":["Meta"],"summary":"Service version information","operationId":"Version // operationId","responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/handlers.VersionResponse"}}}}}},"definitions":{"dto.AnnotationResponse":{"type":"object","properties":{"created_at":{"type":"string"},"id":{"type":"string"},"key":{"type":"string"},"organization_id":{"type":"string"},"updated_at":{"type":"string"},"value":{"type":"string"}}},"dto.AttachAnnotationsRequest":{"type":"object","properties":{"annotation_ids":{"type":"array","items":{"type":"string"}}}},"dto.AttachLabelsRequest":{"type":"object","properties":{"label_ids":{"type":"array","items":{"type":"string"}}}},"dto.AttachServersRequest":{"type":"object","properties":{"server_ids":{"type":"array","items":{"type":"string"}}}},"dto.AttachTaintsRequest":{"type":"object","properties":{"taint_ids":{"type":"array","items":{"type":"string"}}}},"dto.AuthStartResponse":{"type":"object","properties":{"auth_url":{"type":"string","example":"https://accounts.google.com/o/oauth2/v2/auth?client_id=..."}}},"dto.ClusterResponse":{"type":"object","properties":{"bastion_server":{"$ref":"#/definitions/dto.ServerResponse"},"captain_domain":{"type":"string"},"certificate_key":{"type":"string"},"cluster_load_balancer":{"type":"string"},"control_load_balancer":{"type":"string"},"created_at":{"type":"string"},"id":{"type":"string"},"name":{"type":"string"},"node_pools":{"type":"array","items":{"$ref":"#/definitions/dto.NodePoolResponse"}},"provider":{"type":"string"},"random_token":{"type":"string"},"region":{"type":"string"},"status":{"type":"string"},"updated_at":{"type":"string"}}},"dto.CreateAnnotationRequest":{"type":"object","properties":{"key":{"type":"string"},"value":{"type":"string"}}},"dto.CreateClusterRequest":{"type":"object","properties":{"captain_domain":{"type":"string"},"cluster_load_balancer":{"type":"string"},"control_load_balancer":{"type":"string"},"name":{"type":"string"},"provider":{"type":"string"},"region":{"type":"string"},"status":{"type":"string"}}},"dto.CreateCredentialRequest":{"type":"object","required":["kind","provider","schema_version","scope","scope_kind","scope_version","secret"],"properties":{"account_id":{"type":"string","maxLength":32},"kind":{"description":"aws_access_key, api_token, basic_auth, oauth2","type":"string"},"name":{"description":"human label","type":"string","maxLength":100},"provider":{"type":"string","enum":["aws","cloudflare","hetzner","digitalocean","generic"]},"region":{"type":"string","maxLength":32},"schema_version":{"description":"secret schema version","type":"integer","minimum":1},"scope":{"description":"{\"service\":\"route53\"} or {\"arn\":\"...\"}","type":"object"},"scope_kind":{"type":"string","enum":["provider","service","resource"]},"scope_version":{"description":"scope schema version","type":"integer","minimum":1},"secret":{"description":"encrypted later","type":"object"}}},"dto.CreateDomainRequest":{"type":"object","required":["credential_id","domain_name"],"properties":{"credential_id":{"type":"string"},"domain_name":{"type":"string"},"zone_id":{"type":"string","maxLength":128}}},"dto.CreateLabelRequest":{"type":"object","properties":{"key":{"type":"string"},"value":{"type":"string"}}},"dto.CreateNodePoolRequest":{"type":"object","properties":{"name":{"type":"string"},"role":{"type":"string","enum":["master","worker"]}}},"dto.CreateRecordSetRequest":{"type":"object","required":["name","type"],"properties":{"name":{"description":"Name relative to domain (\"endpoint\") OR FQDN (\"endpoint.example.com\").\nServer normalizes to relative.","type":"string","maxLength":253},"ttl":{"type":"integer","maximum":86400,"minimum":1},"type":{"type":"string"},"values":{"type":"array","items":{"type":"string"}}}},"dto.CreateSSHRequest":{"type":"object","properties":{"bits":{"description":"Only for RSA","type":"integer"},"comment":{"type":"string","example":"deploy@autoglue"},"name":{"type":"string"},"type":{"description":"\"rsa\" (default) or \"ed25519\"","type":"string"}}},"dto.CreateServerRequest":{"type":"object","properties":{"hostname":{"type":"string"},"private_ip_address":{"type":"string"},"public_ip_address":{"type":"string"},"role":{"type":"string","enum":["master","worker","bastion"],"example":"master|worker|bastion"},"ssh_key_id":{"type":"string"},"ssh_user":{"type":"string"},"status":{"type":"string","enum":["pending","provisioning","ready","failed"],"example":"pending|provisioning|ready|failed"}}},"dto.CreateTaintRequest":{"type":"object","properties":{"effect":{"type":"string"},"key":{"type":"string"},"value":{"type":"string"}}},"dto.CredentialOut":{"type":"object","properties":{"account_id":{"type":"string"},"created_at":{"type":"string"},"id":{"type":"string"},"kind":{"type":"string"},"name":{"type":"string"},"provider":{"type":"string"},"region":{"type":"string"},"schema_version":{"type":"integer"},"scope":{"type":"object"},"scope_kind":{"type":"string"},"scope_version":{"type":"integer"},"updated_at":{"type":"string"}}},"dto.DomainResponse":{"type":"object","properties":{"created_at":{"type":"string"},"credential_id":{"type":"string"},"domain_name":{"type":"string"},"id":{"type":"string"},"last_error":{"type":"string"},"organization_id":{"type":"string"},"status":{"type":"string"},"updated_at":{"type":"string"},"zone_id":{"type":"string"}}},"dto.EnqueueRequest":{"type":"object","properties":{"payload":{"type":"object"},"queue":{"type":"string","example":"default"},"run_at":{"type":"string","example":"2025-11-05T08:00:00Z"},"type":{"type":"string","example":"email.send"}}},"dto.JWK":{"type":"object","properties":{"alg":{"type":"string","example":"RS256"},"e":{"type":"string","example":"AQAB"},"kid":{"type":"string","example":"7c6f1d0a-7a98-4e6a-9dbf-6b1af4b9f345"},"kty":{"type":"string","example":"RSA"},"n":{"type":"string"},"use":{"type":"string","example":"sig"},"x":{"type":"string"}}},"dto.JWKS":{"type":"object","properties":{"keys":{"type":"array","items":{"$ref":"#/definitions/dto.JWK"}}}},"dto.Job":{"type":"object","properties":{"attempts":{"type":"integer","example":0},"created_at":{"type":"string","example":"2025-11-04T09:30:00Z"},"id":{"type":"string","example":"01HF7SZK8Z8WG1M3J7S2Z8M2N6"},"last_error":{"type":"string","example":"error message"},"max_attempts":{"type":"integer","example":3},"payload":{},"queue":{"type":"string","example":"default"},"run_at":{"type":"string","example":"2025-11-04T09:30:00Z"},"status":{"enum":["queued|running|succeeded|failed|canceled|retrying|scheduled"],"allOf":[{"$ref":"#/definitions/dto.JobStatus"}],"example":"queued"},"type":{"type":"string","example":"email.send"},"updated_at":{"type":"string","example":"2025-11-04T09:30:00Z"}}},"dto.JobStatus":{"type":"string","enum":["queued","running","succeeded","failed","canceled","retrying","scheduled"],"x-enum-varnames":["StatusQueued","StatusRunning","StatusSucceeded","StatusFailed","StatusCanceled","StatusRetrying","StatusScheduled"]},"dto.LabelResponse":{"type":"object","properties":{"created_at":{"type":"string"},"id":{"type":"string"},"key":{"type":"string"},"organization_id":{"type":"string"},"updated_at":{"type":"string"},"value":{"type":"string"}}},"dto.LogoutRequest":{"type":"object","properties":{"refresh_token":{"type":"string","example":"m0l9o8rT3t0V8d3eFf..."}}},"dto.NodePoolResponse":{"type":"object","properties":{"annotations":{"type":"array","items":{"$ref":"#/definitions/dto.AnnotationResponse"}},"created_at":{"type":"string"},"id":{"type":"string"},"labels":{"type":"array","items":{"$ref":"#/definitions/dto.LabelResponse"}},"name":{"type":"string"},"organization_id":{"type":"string"},"role":{"type":"string","enum":["master","worker"]},"servers":{"type":"array","items":{"$ref":"#/definitions/dto.ServerResponse"}},"taints":{"type":"array","items":{"$ref":"#/definitions/dto.TaintResponse"}},"updated_at":{"type":"string"}}},"dto.PageJob":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/definitions/dto.Job"}},"page":{"type":"integer","example":1},"page_size":{"type":"integer","example":25},"total":{"type":"integer","example":120}}},"dto.QueueInfo":{"type":"object","properties":{"failed":{"type":"integer","example":5},"name":{"type":"string","example":"default"},"pending":{"type":"integer","example":42},"running":{"type":"integer","example":3},"scheduled":{"type":"integer","example":7}}},"dto.RecordSetResponse":{"type":"object","properties":{"created_at":{"type":"string"},"domain_id":{"type":"string"},"fingerprint":{"type":"string"},"id":{"type":"string"},"last_error":{"type":"string"},"name":{"type":"string"},"owner":{"type":"string"},"status":{"type":"string"},"ttl":{"type":"integer"},"type":{"type":"string"},"updated_at":{"type":"string"},"values":{"description":"[]string JSON","type":"object"}}},"dto.RefreshRequest":{"type":"object","properties":{"refresh_token":{"type":"string","example":"m0l9o8rT3t0V8d3eFf..."}}},"dto.ServerResponse":{"type":"object","properties":{"created_at":{"type":"string"},"hostname":{"type":"string"},"id":{"type":"string"},"organization_id":{"type":"string"},"private_ip_address":{"type":"string"},"public_ip_address":{"type":"string"},"role":{"type":"string","enum":["master","worker","bastion"],"example":"master|worker|bastion"},"ssh_key_id":{"type":"string"},"ssh_user":{"type":"string"},"status":{"type":"string","enum":["pending","provisioning","ready","failed"],"example":"pending|provisioning|ready|failed"},"updated_at":{"type":"string"}}},"dto.SshResponse":{"type":"object","properties":{"created_at":{"type":"string"},"fingerprint":{"type":"string"},"id":{"type":"string"},"name":{"type":"string"},"organization_id":{"type":"string"},"public_key":{"type":"string"},"updated_at":{"type":"string"}}},"dto.SshRevealResponse":{"type":"object","properties":{"created_at":{"type":"string"},"fingerprint":{"type":"string"},"id":{"type":"string"},"name":{"type":"string"},"organization_id":{"type":"string"},"private_key":{"type":"string"},"public_key":{"type":"string"},"updated_at":{"type":"string"}}},"dto.TaintResponse":{"type":"object","properties":{"created_at":{"type":"string"},"effect":{"type":"string"},"id":{"type":"string"},"key":{"type":"string"},"organization_id":{"type":"string"},"updated_at":{"type":"string"},"value":{"type":"string"}}},"dto.TokenPair":{"type":"object","properties":{"access_token":{"type":"string","example":"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6Ij..."},"expires_in":{"type":"integer","example":3600},"refresh_token":{"type":"string","example":"m0l9o8rT3t0V8d3eFf...."},"token_type":{"type":"string","example":"Bearer"}}},"dto.UpdateAnnotationRequest":{"type":"object","properties":{"key":{"type":"string"},"value":{"type":"string"}}},"dto.UpdateCredentialRequest":{"type":"object","properties":{"account_id":{"type":"string"},"name":{"type":"string"},"region":{"type":"string"},"scope":{"type":"object"},"scope_kind":{"type":"string"},"scope_version":{"type":"integer"},"secret":{"description":"set if rotating","type":"object"}}},"dto.UpdateDomainRequest":{"type":"object","properties":{"credential_id":{"type":"string"},"domain_name":{"type":"string"},"status":{"type":"string","enum":["pending","provisioning","ready","failed"]},"zone_id":{"type":"string","maxLength":128}}},"dto.UpdateLabelRequest":{"type":"object","properties":{"key":{"type":"string"},"value":{"type":"string"}}},"dto.UpdateNodePoolRequest":{"type":"object","properties":{"name":{"type":"string"},"role":{"type":"string","enum":["master","worker"]}}},"dto.UpdateRecordSetRequest":{"type":"object","properties":{"name":{"description":"Any change flips status back to pending (worker will UPSERT)","type":"string","maxLength":253},"status":{"type":"string","enum":["pending","provisioning","ready","failed"]},"ttl":{"type":"integer","maximum":86400,"minimum":1},"type":{"type":"string"},"values":{"type":"array","items":{"type":"string"}}}},"dto.UpdateServerRequest":{"type":"object","properties":{"hostname":{"type":"string"},"private_ip_address":{"type":"string"},"public_ip_address":{"type":"string"},"role":{"type":"string","enum":["master","worker","bastion"],"example":"master|worker|bastion"},"ssh_key_id":{"type":"string"},"ssh_user":{"type":"string"},"status":{"type":"string","enum":["pending","provisioning","ready","failed"],"example":"pending|provisioning|ready|failed"}}},"dto.UpdateTaintRequest":{"type":"object","properties":{"effect":{"type":"string"},"key":{"type":"string"},"value":{"type":"string"}}},"handlers.HealthStatus":{"type":"object","properties":{"status":{"type":"string","example":"ok"}}},"handlers.VersionResponse":{"type":"object","properties":{"built":{"type":"string","example":"2025-11-08T12:34:56Z"},"builtBy":{"type":"string","example":"ci"},"commit":{"type":"string","example":"a1b2c3d"},"commitTime":{"type":"string","example":"2025-11-08T12:31:00Z"},"go":{"type":"string","example":"go1.23.3"},"goArch":{"type":"string","example":"amd64"},"goOS":{"type":"string","example":"linux"},"modified":{"type":"boolean","example":false},"revision":{"type":"string","example":"a1b2c3d4e5f6abcdef"},"vcs":{"type":"string","example":"git"},"version":{"type":"string","example":"1.4.2"}}},"handlers.createUserKeyRequest":{"type":"object","properties":{"expires_in_hours":{"description":"optional TTL","type":"integer"},"name":{"type":"string"}}},"handlers.meResponse":{"type":"object","properties":{"avatar_url":{"type":"string"},"created_at":{"type":"string","format":"date-time"},"display_name":{"type":"string"},"emails":{"type":"array","items":{"$ref":"#/definitions/models.UserEmail"}},"id":{"description":"example: 3fa85f64-5717-4562-b3fc-2c963f66afa6","type":"string","format":"uuid"},"is_admin":{"type":"boolean"},"is_disabled":{"type":"boolean"},"organizations":{"type":"array","items":{"$ref":"#/definitions/models.Organization"}},"primary_email":{"type":"string"},"updated_at":{"type":"string","format":"date-time"}}},"handlers.memberOut":{"type":"object","properties":{"email":{"type":"string"},"role":{"description":"owner/admin/member","type":"string"},"user_id":{"type":"string","format":"uuid"}}},"handlers.memberUpsertReq":{"type":"object","properties":{"role":{"type":"string","example":"member"},"user_id":{"type":"string","format":"uuid"}}},"handlers.orgCreateReq":{"type":"object","properties":{"domain":{"type":"string","example":"acme.com"},"name":{"type":"string","example":"Acme Corp"}}},"handlers.orgKeyCreateReq":{"type":"object","properties":{"expires_in_hours":{"type":"integer","example":720},"name":{"type":"string","example":"automation-bot"}}},"handlers.orgKeyCreateResp":{"type":"object","properties":{"created_at":{"type":"string"},"expires_at":{"type":"string"},"id":{"type":"string"},"name":{"type":"string"},"org_key":{"description":"shown once:","type":"string"},"org_secret":{"description":"shown once:","type":"string"},"scope":{"description":"\"org\"","type":"string"}}},"handlers.orgUpdateReq":{"type":"object","properties":{"domain":{"type":"string"},"name":{"type":"string"}}},"handlers.updateMeRequest":{"type":"object","properties":{"display_name":{"type":"string"}}},"handlers.userAPIKeyOut":{"type":"object","properties":{"created_at":{"type":"string"},"expires_at":{"type":"string"},"id":{"type":"string","format":"uuid"},"last_used_at":{"type":"string"},"name":{"type":"string"},"plain":{"description":"Shown only on create:","type":"string"},"scope":{"description":"\"user\"","type":"string"}}},"models.APIKey":{"type":"object","properties":{"created_at":{"type":"string","format":"date-time"},"expires_at":{"type":"string","format":"date-time"},"id":{"type":"string","format":"uuid"},"last_used_at":{"type":"string","format":"date-time"},"name":{"type":"string"},"org_id":{"type":"string","format":"uuid"},"prefix":{"type":"string"},"revoked":{"type":"boolean"},"scope":{"type":"string"},"updated_at":{"type":"string","format":"date-time"},"user_id":{"type":"string","format":"uuid"}}},"models.Organization":{"type":"object","properties":{"created_at":{"type":"string","format":"date-time"},"domain":{"type":"string"},"id":{"description":"example: 3fa85f64-5717-4562-b3fc-2c963f66afa6","type":"string","format":"uuid"},"name":{"type":"string"},"updated_at":{"type":"string","format":"date-time"}}},"models.User":{"type":"object","properties":{"avatar_url":{"type":"string"},"created_at":{"type":"string","format":"date-time"},"display_name":{"type":"string"},"id":{"description":"example: 3fa85f64-5717-4562-b3fc-2c963f66afa6","type":"string","format":"uuid"},"is_admin":{"type":"boolean"},"is_disabled":{"type":"boolean"},"primary_email":{"type":"string"},"updated_at":{"type":"string","format":"date-time"}}},"models.UserEmail":{"type":"object","properties":{"created_at":{"type":"string","format":"date-time"},"email":{"type":"string"},"id":{"description":"example: 3fa85f64-5717-4562-b3fc-2c963f66afa6","type":"string","format":"uuid"},"is_primary":{"type":"boolean"},"is_verified":{"type":"boolean"},"updated_at":{"type":"string","format":"date-time"},"user":{"$ref":"#/definitions/models.User"},"user_id":{"type":"string","format":"uuid"}}},"utils.ErrorResponse":{"type":"object","properties":{"code":{"description":"A machine-readable error code, e.g. \"validation_error\"\nexample: validation_error","type":"string"},"message":{"description":"Human-readable message\nexample: slug is required","type":"string"}}}},"securityDefinitions":{"ApiKeyAuth":{"description":"User API key","type":"apiKey","name":"X-API-KEY","in":"header"},"BearerAuth":{"description":"Bearer token authentication","type":"apiKey","name":"Authorization","in":"header"},"OrgKeyAuth":{"description":"Org-level key/secret authentication","type":"apiKey","name":"X-ORG-KEY","in":"header"},"OrgSecretAuth":{"description":"Org-level secret","type":"apiKey","name":"X-ORG-SECRET","in":"header"}}}` + "schemes": {{ marshal .Schemes }}, + "components": {"schemas":{"dto.AnnotationResponse":{"properties":{"created_at":{"type":"string"},"id":{"type":"string"},"key":{"type":"string"},"organization_id":{"type":"string"},"updated_at":{"type":"string"},"value":{"type":"string"}},"type":"object"},"dto.AttachAnnotationsRequest":{"properties":{"annotation_ids":{"items":{"type":"string"},"type":"array","uniqueItems":false}},"type":"object"},"dto.AttachBastionRequest":{"properties":{"server_id":{"type":"string"}},"type":"object"},"dto.AttachCaptainDomainRequest":{"properties":{"domain_id":{"type":"string"}},"type":"object"},"dto.AttachLabelsRequest":{"properties":{"label_ids":{"items":{"type":"string"},"type":"array","uniqueItems":false}},"type":"object"},"dto.AttachLoadBalancerRequest":{"properties":{"load_balancer_id":{"type":"string"}},"type":"object"},"dto.AttachRecordSetRequest":{"properties":{"record_set_id":{"type":"string"}},"type":"object"},"dto.AttachServersRequest":{"properties":{"server_ids":{"items":{"type":"string"},"type":"array","uniqueItems":false}},"type":"object"},"dto.AttachTaintsRequest":{"properties":{"taint_ids":{"items":{"type":"string"},"type":"array","uniqueItems":false}},"type":"object"},"dto.AuthStartResponse":{"properties":{"auth_url":{"example":"https://accounts.google.com/o/oauth2/v2/auth?client_id=...","type":"string"}},"type":"object"},"dto.ClusterResponse":{"properties":{"apps_load_balancer":{"$ref":"#/components/schemas/dto.LoadBalancerResponse"},"bastion_server":{"$ref":"#/components/schemas/dto.ServerResponse"},"captain_domain":{"$ref":"#/components/schemas/dto.DomainResponse"},"certificate_key":{"type":"string"},"control_plane_record_set":{"$ref":"#/components/schemas/dto.RecordSetResponse"},"created_at":{"type":"string"},"glueops_load_balancer":{"$ref":"#/components/schemas/dto.LoadBalancerResponse"},"id":{"type":"string"},"last_error":{"type":"string"},"name":{"type":"string"},"node_pools":{"items":{"$ref":"#/components/schemas/dto.NodePoolResponse"},"type":"array","uniqueItems":false},"provider":{"type":"string"},"random_token":{"type":"string"},"region":{"type":"string"},"status":{"type":"string"},"updated_at":{"type":"string"}},"type":"object"},"dto.CreateAnnotationRequest":{"properties":{"key":{"type":"string"},"value":{"type":"string"}},"type":"object"},"dto.CreateClusterRequest":{"properties":{"name":{"type":"string"},"provider":{"type":"string"},"region":{"type":"string"}},"type":"object"},"dto.CreateCredentialRequest":{"properties":{"account_id":{"maxLength":32,"type":"string"},"kind":{"description":"aws_access_key, api_token, basic_auth, oauth2","type":"string"},"name":{"description":"human label","maxLength":100,"type":"string"},"provider":{"enum":["aws","cloudflare","hetzner","digitalocean","generic"],"type":"string"},"region":{"maxLength":32,"type":"string"},"schema_version":{"description":"secret schema version","minimum":1,"type":"integer"},"scope":{"description":"{\"service\":\"route53\"} or {\"arn\":\"...\"}","type":"object"},"scope_kind":{"enum":["provider","service","resource"],"type":"string"},"scope_version":{"description":"scope schema version","minimum":1,"type":"integer"},"secret":{"description":"encrypted later","type":"object"}},"required":["kind","provider","schema_version","scope","scope_kind","scope_version","secret"],"type":"object"},"dto.CreateDomainRequest":{"properties":{"credential_id":{"type":"string"},"domain_name":{"type":"string"},"zone_id":{"maxLength":128,"type":"string"}},"required":["credential_id","domain_name"],"type":"object"},"dto.CreateLabelRequest":{"properties":{"key":{"type":"string"},"value":{"type":"string"}},"type":"object"},"dto.CreateLoadBalancerRequest":{"properties":{"kind":{"enum":["glueops|public"],"example":"public","type":"string"},"name":{"example":"glueops","type":"string"},"private_ip_address":{"example":"192.168.0.2","type":"string"},"public_ip_address":{"example":"8.8.8.8","type":"string"}},"type":"object"},"dto.CreateNodePoolRequest":{"properties":{"name":{"type":"string"},"role":{"enum":["master","worker"],"type":"string"}},"type":"object"},"dto.CreateRecordSetRequest":{"properties":{"name":{"description":"Name relative to domain (\"endpoint\") OR FQDN (\"endpoint.example.com\").\nServer normalizes to relative.","maxLength":253,"type":"string"},"ttl":{"maximum":86400,"minimum":1,"type":"integer"},"type":{"type":"string"},"values":{"items":{"type":"string"},"type":"array","uniqueItems":false}},"required":["name","type"],"type":"object"},"dto.CreateSSHRequest":{"properties":{"bits":{"description":"Only for RSA","type":"integer"},"comment":{"example":"deploy@autoglue","type":"string"},"name":{"type":"string"},"type":{"description":"\"rsa\" (default) or \"ed25519\"","type":"string"}},"type":"object"},"dto.CreateServerRequest":{"properties":{"hostname":{"type":"string"},"private_ip_address":{"type":"string"},"public_ip_address":{"type":"string"},"role":{"enum":["master","worker","bastion"],"example":"master|worker|bastion","type":"string"},"ssh_key_id":{"type":"string"},"ssh_user":{"type":"string"},"status":{"enum":["pending","provisioning","ready","failed"],"example":"pending|provisioning|ready|failed","type":"string"}},"type":"object"},"dto.CreateTaintRequest":{"properties":{"effect":{"type":"string"},"key":{"type":"string"},"value":{"type":"string"}},"type":"object"},"dto.CredentialOut":{"properties":{"account_id":{"type":"string"},"created_at":{"type":"string"},"id":{"type":"string"},"kind":{"type":"string"},"name":{"type":"string"},"provider":{"type":"string"},"region":{"type":"string"},"schema_version":{"type":"integer"},"scope":{"type":"object"},"scope_kind":{"type":"string"},"scope_version":{"type":"integer"},"updated_at":{"type":"string"}},"type":"object"},"dto.DomainResponse":{"properties":{"created_at":{"type":"string"},"credential_id":{"type":"string"},"domain_name":{"type":"string"},"id":{"type":"string"},"last_error":{"type":"string"},"organization_id":{"type":"string"},"status":{"type":"string"},"updated_at":{"type":"string"},"zone_id":{"type":"string"}},"type":"object"},"dto.EnqueueRequest":{"properties":{"payload":{"type":"object"},"queue":{"example":"default","type":"string"},"run_at":{"example":"2025-11-05T08:00:00Z","type":"string"},"type":{"example":"email.send","type":"string"}},"type":"object"},"dto.JWK":{"properties":{"alg":{"example":"RS256","type":"string"},"e":{"example":"AQAB","type":"string"},"kid":{"example":"7c6f1d0a-7a98-4e6a-9dbf-6b1af4b9f345","type":"string"},"kty":{"example":"RSA","type":"string"},"n":{"type":"string"},"use":{"example":"sig","type":"string"},"x":{"type":"string"}},"type":"object"},"dto.JWKS":{"properties":{"keys":{"items":{"$ref":"#/components/schemas/dto.JWK"},"type":"array","uniqueItems":false}},"type":"object"},"dto.Job":{"properties":{"attempts":{"example":0,"type":"integer"},"created_at":{"example":"2025-11-04T09:30:00Z","type":"string"},"id":{"example":"01HF7SZK8Z8WG1M3J7S2Z8M2N6","type":"string"},"last_error":{"example":"error message","type":"string"},"max_attempts":{"example":3,"type":"integer"},"payload":{},"queue":{"example":"default","type":"string"},"run_at":{"example":"2025-11-04T09:30:00Z","type":"string"},"status":{"$ref":"#/components/schemas/dto.JobStatus"},"type":{"example":"email.send","type":"string"},"updated_at":{"example":"2025-11-04T09:30:00Z","type":"string"}},"type":"object"},"dto.JobStatus":{"enum":["queued|running|succeeded|failed|canceled|retrying|scheduled"],"example":"queued","type":"string","x-enum-varnames":["StatusQueued","StatusRunning","StatusSucceeded","StatusFailed","StatusCanceled","StatusRetrying","StatusScheduled"]},"dto.LabelResponse":{"properties":{"created_at":{"type":"string"},"id":{"type":"string"},"key":{"type":"string"},"organization_id":{"type":"string"},"updated_at":{"type":"string"},"value":{"type":"string"}},"type":"object"},"dto.LoadBalancerResponse":{"properties":{"created_at":{"type":"string"},"id":{"type":"string"},"kind":{"enum":["glueops|public"],"type":"string"},"name":{"type":"string"},"organization_id":{"type":"string"},"private_ip_address":{"type":"string"},"public_ip_address":{"type":"string"},"updated_at":{"type":"string"}},"type":"object"},"dto.LogoutRequest":{"properties":{"refresh_token":{"example":"m0l9o8rT3t0V8d3eFf...","type":"string"}},"type":"object"},"dto.NodePoolResponse":{"properties":{"annotations":{"items":{"$ref":"#/components/schemas/dto.AnnotationResponse"},"type":"array","uniqueItems":false},"created_at":{"type":"string"},"id":{"type":"string"},"labels":{"items":{"$ref":"#/components/schemas/dto.LabelResponse"},"type":"array","uniqueItems":false},"name":{"type":"string"},"organization_id":{"type":"string"},"role":{"enum":["master","worker"],"type":"string"},"servers":{"items":{"$ref":"#/components/schemas/dto.ServerResponse"},"type":"array","uniqueItems":false},"taints":{"items":{"$ref":"#/components/schemas/dto.TaintResponse"},"type":"array","uniqueItems":false},"updated_at":{"type":"string"}},"type":"object"},"dto.PageJob":{"properties":{"items":{"items":{"$ref":"#/components/schemas/dto.Job"},"type":"array","uniqueItems":false},"page":{"example":1,"type":"integer"},"page_size":{"example":25,"type":"integer"},"total":{"example":120,"type":"integer"}},"type":"object"},"dto.QueueInfo":{"properties":{"failed":{"example":5,"type":"integer"},"name":{"example":"default","type":"string"},"pending":{"example":42,"type":"integer"},"running":{"example":3,"type":"integer"},"scheduled":{"example":7,"type":"integer"}},"type":"object"},"dto.RecordSetResponse":{"properties":{"created_at":{"type":"string"},"domain_id":{"type":"string"},"fingerprint":{"type":"string"},"id":{"type":"string"},"last_error":{"type":"string"},"name":{"type":"string"},"owner":{"type":"string"},"status":{"type":"string"},"ttl":{"type":"integer"},"type":{"type":"string"},"updated_at":{"type":"string"},"values":{"description":"[]string JSON","type":"object"}},"type":"object"},"dto.RefreshRequest":{"properties":{"refresh_token":{"example":"m0l9o8rT3t0V8d3eFf...","type":"string"}},"type":"object"},"dto.ServerResponse":{"properties":{"created_at":{"type":"string"},"hostname":{"type":"string"},"id":{"type":"string"},"organization_id":{"type":"string"},"private_ip_address":{"type":"string"},"public_ip_address":{"type":"string"},"role":{"enum":["master","worker","bastion"],"example":"master|worker|bastion","type":"string"},"ssh_key_id":{"type":"string"},"ssh_user":{"type":"string"},"status":{"enum":["pending","provisioning","ready","failed"],"example":"pending|provisioning|ready|failed","type":"string"},"updated_at":{"type":"string"}},"type":"object"},"dto.SetKubeconfigRequest":{"properties":{"kubeconfig":{"type":"string"}},"type":"object"},"dto.SshResponse":{"properties":{"created_at":{"type":"string"},"fingerprint":{"type":"string"},"id":{"type":"string"},"name":{"type":"string"},"organization_id":{"type":"string"},"public_key":{"type":"string"},"updated_at":{"type":"string"}},"type":"object"},"dto.SshRevealResponse":{"properties":{"created_at":{"type":"string"},"fingerprint":{"type":"string"},"id":{"type":"string"},"name":{"type":"string"},"organization_id":{"type":"string"},"private_key":{"type":"string"},"public_key":{"type":"string"},"updated_at":{"type":"string"}},"type":"object"},"dto.TaintResponse":{"properties":{"created_at":{"type":"string"},"effect":{"type":"string"},"id":{"type":"string"},"key":{"type":"string"},"organization_id":{"type":"string"},"updated_at":{"type":"string"},"value":{"type":"string"}},"type":"object"},"dto.TokenPair":{"properties":{"access_token":{"example":"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6Ij...","type":"string"},"expires_in":{"example":3600,"type":"integer"},"refresh_token":{"example":"m0l9o8rT3t0V8d3eFf....","type":"string"},"token_type":{"example":"Bearer","type":"string"}},"type":"object"},"dto.UpdateAnnotationRequest":{"properties":{"key":{"type":"string"},"value":{"type":"string"}},"type":"object"},"dto.UpdateClusterRequest":{"properties":{"name":{"type":"string"},"provider":{"type":"string"},"region":{"type":"string"}},"type":"object"},"dto.UpdateCredentialRequest":{"properties":{"account_id":{"type":"string"},"name":{"type":"string"},"region":{"type":"string"},"scope":{"type":"object"},"scope_kind":{"type":"string"},"scope_version":{"type":"integer"},"secret":{"description":"set if rotating","type":"object"}},"type":"object"},"dto.UpdateDomainRequest":{"properties":{"credential_id":{"type":"string"},"domain_name":{"type":"string"},"status":{"enum":["pending","provisioning","ready","failed"],"type":"string"},"zone_id":{"maxLength":128,"type":"string"}},"type":"object"},"dto.UpdateLabelRequest":{"properties":{"key":{"type":"string"},"value":{"type":"string"}},"type":"object"},"dto.UpdateLoadBalancerRequest":{"properties":{"kind":{"enum":["glueops|public"],"example":"public","type":"string"},"name":{"example":"glue","type":"string"},"private_ip_address":{"example":"192.168.0.2","type":"string"},"public_ip_address":{"example":"8.8.8.8","type":"string"}},"type":"object"},"dto.UpdateNodePoolRequest":{"properties":{"name":{"type":"string"},"role":{"enum":["master","worker"],"type":"string"}},"type":"object"},"dto.UpdateRecordSetRequest":{"properties":{"name":{"description":"Any change flips status back to pending (worker will UPSERT)","maxLength":253,"type":"string"},"status":{"enum":["pending","provisioning","ready","failed"],"type":"string"},"ttl":{"maximum":86400,"minimum":1,"type":"integer"},"type":{"type":"string"},"values":{"items":{"type":"string"},"type":"array","uniqueItems":false}},"type":"object"},"dto.UpdateServerRequest":{"properties":{"hostname":{"type":"string"},"private_ip_address":{"type":"string"},"public_ip_address":{"type":"string"},"role":{"enum":["master","worker","bastion"],"example":"master|worker|bastion","type":"string"},"ssh_key_id":{"type":"string"},"ssh_user":{"type":"string"},"status":{"enum":["pending","provisioning","ready","failed"],"example":"pending|provisioning|ready|failed","type":"string"}},"type":"object"},"dto.UpdateTaintRequest":{"properties":{"effect":{"type":"string"},"key":{"type":"string"},"value":{"type":"string"}},"type":"object"},"handlers.HealthStatus":{"properties":{"status":{"example":"ok","type":"string"}},"type":"object"},"handlers.VersionResponse":{"properties":{"built":{"example":"2025-11-08T12:34:56Z","type":"string"},"builtBy":{"example":"ci","type":"string"},"commit":{"example":"a1b2c3d","type":"string"},"commitTime":{"example":"2025-11-08T12:31:00Z","type":"string"},"go":{"example":"go1.23.3","type":"string"},"goArch":{"example":"amd64","type":"string"},"goOS":{"example":"linux","type":"string"},"modified":{"example":false,"type":"boolean"},"revision":{"example":"a1b2c3d4e5f6abcdef","type":"string"},"vcs":{"example":"git","type":"string"},"version":{"example":"1.4.2","type":"string"}},"type":"object"},"handlers.createUserKeyRequest":{"properties":{"expires_in_hours":{"description":"optional TTL","type":"integer"},"name":{"type":"string"}},"type":"object"},"handlers.meResponse":{"properties":{"avatar_url":{"type":"string"},"created_at":{"format":"date-time","type":"string"},"display_name":{"type":"string"},"emails":{"items":{"$ref":"#/components/schemas/models.UserEmail"},"type":"array","uniqueItems":false},"id":{"description":"example: 3fa85f64-5717-4562-b3fc-2c963f66afa6","format":"uuid","type":"string"},"is_admin":{"type":"boolean"},"is_disabled":{"type":"boolean"},"organizations":{"items":{"$ref":"#/components/schemas/models.Organization"},"type":"array","uniqueItems":false},"primary_email":{"type":"string"},"updated_at":{"format":"date-time","type":"string"}},"type":"object"},"handlers.memberOut":{"properties":{"email":{"type":"string"},"role":{"description":"owner/admin/member","type":"string"},"user_id":{"format":"uuid","type":"string"}},"type":"object"},"handlers.memberUpsertReq":{"properties":{"role":{"example":"member","type":"string"},"user_id":{"format":"uuid","type":"string"}},"type":"object"},"handlers.orgCreateReq":{"properties":{"domain":{"example":"acme.com","type":"string"},"name":{"example":"Acme Corp","type":"string"}},"type":"object"},"handlers.orgKeyCreateReq":{"properties":{"expires_in_hours":{"example":720,"type":"integer"},"name":{"example":"automation-bot","type":"string"}},"type":"object"},"handlers.orgKeyCreateResp":{"properties":{"created_at":{"type":"string"},"expires_at":{"type":"string"},"id":{"type":"string"},"name":{"type":"string"},"org_key":{"description":"shown once:","type":"string"},"org_secret":{"description":"shown once:","type":"string"},"scope":{"description":"\"org\"","type":"string"}},"type":"object"},"handlers.orgUpdateReq":{"properties":{"domain":{"type":"string"},"name":{"type":"string"}},"type":"object"},"handlers.updateMeRequest":{"properties":{"display_name":{"type":"string"}},"type":"object"},"handlers.userAPIKeyOut":{"properties":{"created_at":{"type":"string"},"expires_at":{"type":"string"},"id":{"format":"uuid","type":"string"},"last_used_at":{"type":"string"},"name":{"type":"string"},"plain":{"description":"Shown only on create:","type":"string"},"scope":{"description":"\"user\"","type":"string"}},"type":"object"},"models.APIKey":{"properties":{"created_at":{"format":"date-time","type":"string"},"expires_at":{"format":"date-time","type":"string"},"id":{"format":"uuid","type":"string"},"last_used_at":{"format":"date-time","type":"string"},"name":{"type":"string"},"org_id":{"format":"uuid","type":"string"},"prefix":{"type":"string"},"revoked":{"type":"boolean"},"scope":{"type":"string"},"updated_at":{"format":"date-time","type":"string"},"user_id":{"format":"uuid","type":"string"}},"type":"object"},"models.Organization":{"properties":{"created_at":{"format":"date-time","type":"string"},"domain":{"type":"string"},"id":{"description":"example: 3fa85f64-5717-4562-b3fc-2c963f66afa6","format":"uuid","type":"string"},"name":{"type":"string"},"updated_at":{"format":"date-time","type":"string"}},"type":"object"},"models.User":{"properties":{"avatar_url":{"type":"string"},"created_at":{"format":"date-time","type":"string"},"display_name":{"type":"string"},"id":{"description":"example: 3fa85f64-5717-4562-b3fc-2c963f66afa6","format":"uuid","type":"string"},"is_admin":{"type":"boolean"},"is_disabled":{"type":"boolean"},"primary_email":{"type":"string"},"updated_at":{"format":"date-time","type":"string"}},"type":"object"},"models.UserEmail":{"properties":{"created_at":{"format":"date-time","type":"string"},"email":{"type":"string"},"id":{"description":"example: 3fa85f64-5717-4562-b3fc-2c963f66afa6","format":"uuid","type":"string"},"is_primary":{"type":"boolean"},"is_verified":{"type":"boolean"},"updated_at":{"format":"date-time","type":"string"},"user":{"$ref":"#/components/schemas/models.User"},"user_id":{"format":"uuid","type":"string"}},"type":"object"},"utils.ErrorResponse":{"properties":{"code":{"description":"A machine-readable error code, e.g. \"validation_error\"\nexample: validation_error","type":"string"},"message":{"description":"Human-readable message\nexample: slug is required","type":"string"}},"type":"object"}},"securitySchemes":{"":{"description":"Org-level secret","in":"header","name":"X-ORG-SECRET","type":"apiKey"}}}, + "info": {"contact":{"name":"GlueOps"},"description":"{{escape .Description}}","title":"{{.Title}}","version":"{{.Version}}"}, + "externalDocs": {"description":"","url":""}, + "paths": {"/.well-known/jwks.json":{"get":{"description":"Returns the JSON Web Key Set for token verification","operationId":"getJWKS","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.JWKS"}}},"description":"OK"}},"summary":"Get JWKS","tags":["Auth"]}},"/admin/archer/jobs":{"get":{"description":"Paginated background jobs with optional filters. Search ` + "`" + `q` + "`" + ` may match id, type, error, payload (implementation-dependent).","operationId":"AdminListArcherJobs","parameters":[{"description":"Filter by status","in":"query","name":"status","schema":{"enum":["queued","running","succeeded","failed","canceled","retrying","scheduled"],"type":"string"}},{"description":"Filter by queue name / worker name","in":"query","name":"queue","schema":{"type":"string"}},{"description":"Free-text search","in":"query","name":"q","schema":{"type":"string"}},{"description":"Page number","in":"query","name":"page","schema":{"default":1,"type":"integer"}},{"description":"Items per page","in":"query","name":"page_size","schema":{"default":25,"maximum":100,"minimum":1,"type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.PageJob"}}},"description":"OK"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"forbidden"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"internal error"}},"security":[{"BearerAuth":[]}],"summary":"List Archer jobs (admin)","tags":["ArcherAdmin"]},"post":{"description":"Create a job immediately or schedule it for the future via ` + "`" + `run_at` + "`" + `.","operationId":"AdminEnqueueArcherJob","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.EnqueueRequest"}}},"description":"Job parameters","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.Job"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"invalid json or missing fields"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"forbidden"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"internal error"}},"security":[{"BearerAuth":[]}],"summary":"Enqueue a new Archer job (admin)","tags":["ArcherAdmin"]}},"/admin/archer/jobs/{id}/cancel":{"post":{"description":"Set job status to canceled if cancellable. For running jobs, this only affects future picks; wire to Archer if you need active kill.","operationId":"AdminCancelArcherJob","parameters":[{"description":"Job ID","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.Job"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"invalid job or not cancellable"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"forbidden"},"404":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"not found"}},"security":[{"BearerAuth":[]}],"summary":"Cancel an Archer job (admin)","tags":["ArcherAdmin"]}},"/admin/archer/jobs/{id}/retry":{"post":{"description":"Marks the job retriable (DB flip). Swap this for an Archer admin call if you expose one.","operationId":"AdminRetryArcherJob","parameters":[{"description":"Job ID","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.Job"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"invalid job or not eligible"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"forbidden"},"404":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"not found"}},"security":[{"BearerAuth":[]}],"summary":"Retry a failed/canceled Archer job (admin)","tags":["ArcherAdmin"]}},"/admin/archer/queues":{"get":{"description":"Summary metrics per queue (pending, running, failed, scheduled).","operationId":"AdminListArcherQueues","responses":{"200":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/dto.QueueInfo"},"type":"array"}}},"description":"OK"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"forbidden"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"internal error"}},"security":[{"BearerAuth":[]}],"summary":"List Archer queues (admin)","tags":["ArcherAdmin"]}},"/annotations":{"get":{"description":"Returns annotations for the organization in X-Org-ID. Filters: ` + "`" + `key` + "`" + `, ` + "`" + `value` + "`" + `, and ` + "`" + `q` + "`" + ` (key contains). Add ` + "`" + `include=node_pools` + "`" + ` to include linked node pools.","operationId":"ListAnnotations","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"Exact key","in":"query","name":"key","schema":{"type":"string"}},{"description":"Exact value","in":"query","name":"value","schema":{"type":"string"}},{"description":"key contains (case-insensitive)","in":"query","name":"q","schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/dto.AnnotationResponse"},"type":"array"}}},"description":"OK"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"failed to list annotations"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"List annotations (org scoped)","tags":["Annotations"]},"post":{"description":"Creates an annotation.","operationId":"CreateAnnotation","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.CreateAnnotationRequest"}}},"description":"Annotation payload","required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.AnnotationResponse"}}},"description":"Created"},"400":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"invalid json / missing fields"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"create failed"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"Create annotation (org scoped)","tags":["Annotations"]}},"/annotations/{id}":{"delete":{"description":"Permanently deletes the annotation.","operationId":"DeleteAnnotation","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"Annotation ID (UUID)","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"No Content"},"400":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"invalid id"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"delete failed"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"Delete annotation (org scoped)","tags":["Annotations"]},"get":{"description":"Returns one annotation. Add ` + "`" + `include=node_pools` + "`" + ` to include node pools.","operationId":"GetAnnotation","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"Annotation ID (UUID)","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.AnnotationResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"invalid id"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"404":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"not found"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"fetch failed"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"Get annotation by ID (org scoped)","tags":["Annotations"]},"patch":{"description":"Partially update annotation fields.","operationId":"UpdateAnnotation","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"Annotation ID (UUID)","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.UpdateAnnotationRequest"}}},"description":"Fields to update","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.AnnotationResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"invalid id / invalid json"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"404":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"not found"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"update failed"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"Update annotation (org scoped)","tags":["Annotations"]}},"/auth/logout":{"post":{"operationId":"Logout","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.LogoutRequest"}}},"description":"Refresh token","required":true},"responses":{"204":{"description":"No Content"}},"summary":"Revoke refresh token family (logout everywhere)","tags":["Auth"]}},"/auth/refresh":{"post":{"operationId":"Refresh","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.RefreshRequest"}}},"description":"Refresh token","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.TokenPair"}}},"description":"OK"}},"summary":"Rotate refresh token","tags":["Auth"]}},"/auth/{provider}/callback":{"get":{"operationId":"AuthCallback","parameters":[{"description":"google|github","in":"path","name":"provider","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.TokenPair"}}},"description":"OK"}},"summary":"Handle social login callback","tags":["Auth"]}},"/auth/{provider}/start":{"post":{"description":"Returns provider authorization URL for the frontend to redirect","operationId":"AuthStart","parameters":[{"description":"google|github","in":"path","name":"provider","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.AuthStartResponse"}}},"description":"OK"}},"summary":"Begin social login","tags":["Auth"]}},"/clusters":{"get":{"description":"Returns clusters for the organization in X-Org-ID. Filter by ` + "`" + `q` + "`" + ` (name contains).","operationId":"ListClusters","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"Name contains (case-insensitive)","in":"query","name":"q","schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/dto.ClusterResponse"},"type":"array"}}},"description":"OK"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"failed to list clusters"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"List clusters (org scoped)","tags":["Clusters"]},"post":{"description":"Creates a cluster. Status is managed by the system and starts as ` + "`" + `pre_pending` + "`" + ` for validation.","operationId":"CreateCluster","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.CreateClusterRequest"}}},"description":"payload","required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.ClusterResponse"}}},"description":"Created"},"400":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"invalid json"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"create failed"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"Create cluster (org scoped)","tags":["Clusters"]}},"/clusters/{clusterID}":{"delete":{"description":"Deletes the cluster. Related resources are cleaned up via DB constraints (e.g. CASCADE).","operationId":"DeleteCluster","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"Cluster ID","in":"path","name":"clusterID","required":true,"schema":{"type":"string"}}],"responses":{"204":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"deleted"},"400":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"bad request"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"404":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"cluster not found"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"db error"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"Delete a cluster (org scoped)","tags":["Clusters"]},"get":{"description":"Returns a cluster with all related resources (domain, record set, load balancers, bastion, node pools).","operationId":"GetCluster","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"Cluster ID","in":"path","name":"clusterID","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.ClusterResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"bad request"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"404":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"cluster not found"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"db error"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"Get a single cluster by ID (org scoped)","tags":["Clusters"]},"patch":{"description":"Updates the cluster name, provider, and/or region. Status is managed by the system.","operationId":"UpdateCluster","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"Cluster ID","in":"path","name":"clusterID","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.UpdateClusterRequest"}}},"description":"payload","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.ClusterResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"bad request"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"404":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"cluster not found"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"db error"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"Update basic cluster details (org scoped)","tags":["Clusters"]}},"/clusters/{clusterID}/apps-load-balancer":{"delete":{"description":"Clears apps_load_balancer_id on the cluster.","operationId":"DetachAppsLoadBalancer","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"Cluster ID","in":"path","name":"clusterID","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.ClusterResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"bad request"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"404":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"cluster not found"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"db error"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"Detach the apps load balancer from a cluster","tags":["Clusters"]},"post":{"description":"Sets apps_load_balancer_id on the cluster.","operationId":"AttachAppsLoadBalancer","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"Cluster ID","in":"path","name":"clusterID","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.AttachLoadBalancerRequest"}}},"description":"payload","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.ClusterResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"bad request"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"404":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"cluster or load balancer not found"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"db error"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"Attach an apps load balancer to a cluster","tags":["Clusters"]}},"/clusters/{clusterID}/bastion":{"delete":{"description":"Clears bastion_server_id on the cluster.","operationId":"DetachBastionServer","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"Cluster ID","in":"path","name":"clusterID","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.ClusterResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"bad request"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"404":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"cluster not found"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"db error"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"Detach the bastion server from a cluster","tags":["Clusters"]},"post":{"description":"Sets bastion_server_id on the cluster.","operationId":"AttachBastionServer","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"Cluster ID","in":"path","name":"clusterID","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.AttachBastionRequest"}}},"description":"payload","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.ClusterResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"bad request"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"404":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"cluster or server not found"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"db error"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"Attach a bastion server to a cluster","tags":["Clusters"]}},"/clusters/{clusterID}/captain-domain":{"delete":{"description":"Clears captain_domain_id on the cluster. This will likely cause the cluster to become incomplete.","operationId":"DetachCaptainDomain","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"Cluster ID","in":"path","name":"clusterID","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.ClusterResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"bad request"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"404":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"cluster not found"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"db error"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"Detach the captain domain from a cluster","tags":["Clusters"]},"post":{"description":"Sets captain_domain_id on the cluster. Validation of shape happens asynchronously.","operationId":"AttachCaptainDomain","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"Cluster ID","in":"path","name":"clusterID","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.AttachCaptainDomainRequest"}}},"description":"payload","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.ClusterResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"bad request"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"404":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"cluster or domain not found"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"db error"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"Attach a captain domain to a cluster","tags":["Clusters"]}},"/clusters/{clusterID}/control-plane-record-set":{"delete":{"description":"Clears control_plane_record_set_id on the cluster.","operationId":"DetachControlPlaneRecordSet","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"Cluster ID","in":"path","name":"clusterID","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.ClusterResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"bad request"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"404":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"cluster not found"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"db error"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"Detach the control plane record set from a cluster","tags":["Clusters"]},"post":{"description":"Sets control_plane_record_set_id on the cluster.","operationId":"AttachControlPlaneRecordSet","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"Cluster ID","in":"path","name":"clusterID","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.AttachRecordSetRequest"}}},"description":"payload","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.ClusterResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"bad request"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"404":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"cluster or record set not found"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"db error"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"Attach a control plane record set to a cluster","tags":["Clusters"]}},"/clusters/{clusterID}/glueops-load-balancer":{"delete":{"description":"Clears glueops_load_balancer_id on the cluster.","operationId":"DetachGlueOpsLoadBalancer","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"Cluster ID","in":"path","name":"clusterID","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.ClusterResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"bad request"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"404":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"cluster not found"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"db error"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"Detach the GlueOps/control-plane load balancer from a cluster","tags":["Clusters"]},"post":{"description":"Sets glueops_load_balancer_id on the cluster.","operationId":"AttachGlueOpsLoadBalancer","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"Cluster ID","in":"path","name":"clusterID","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.AttachLoadBalancerRequest"}}},"description":"payload","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.ClusterResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"bad request"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"404":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"cluster or load balancer not found"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"db error"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"Attach a GlueOps/control-plane load balancer to a cluster","tags":["Clusters"]}},"/clusters/{clusterID}/kubeconfig":{"delete":{"description":"Removes the encrypted kubeconfig, IV, and tag from the cluster record.","operationId":"ClearClusterKubeconfig","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"Cluster ID","in":"path","name":"clusterID","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.ClusterResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"bad request"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"404":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"cluster not found"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"db error"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"Clear the kubeconfig for a cluster","tags":["Clusters"]},"post":{"description":"Stores the kubeconfig encrypted per organization. The kubeconfig is never returned in responses.","operationId":"SetClusterKubeconfig","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"Cluster ID","in":"path","name":"clusterID","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.SetKubeconfigRequest"}}},"description":"payload","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.ClusterResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"bad request"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"404":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"cluster not found"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"db error"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"Set (or replace) the kubeconfig for a cluster","tags":["Clusters"]}},"/credentials":{"get":{"description":"Returns credential metadata for the current org. Secrets are never returned.","operationId":"ListCredentials","parameters":[{"description":"Organization ID (UUID)","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"Filter by provider (e.g., aws)","in":"query","name":"provider","schema":{"type":"string"}},{"description":"Filter by kind (e.g., aws_access_key)","in":"query","name":"kind","schema":{"type":"string"}},{"description":"Filter by scope kind (provider/service/resource)","in":"query","name":"scope_kind","schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/dto.CredentialOut"},"type":"array"}}},"description":"OK"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"internal server error"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"List credentials (metadata only)","tags":["Credentials"]},"post":{"operationId":"CreateCredential","parameters":[{"description":"Organization ID (UUID)","in":"header","name":"X-Org-ID","schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.CreateCredentialRequest"}}},"description":"Credential payload","required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.CredentialOut"}}},"description":"Created"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"internal server error"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"Create a credential (encrypts secret)","tags":["Credentials"]}},"/credentials/{id}":{"delete":{"operationId":"DeleteCredential","parameters":[{"description":"Organization ID (UUID)","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"Credential ID (UUID)","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"No Content"},"404":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"not found"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"Delete credential","tags":["Credentials"]},"get":{"operationId":"GetCredential","parameters":[{"description":"Organization ID (UUID)","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"Credential ID (UUID)","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.CredentialOut"}}},"description":"OK"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"internal server error"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"Get credential by ID (metadata only)","tags":["Credentials"]},"patch":{"operationId":"UpdateCredential","parameters":[{"description":"Organization ID (UUID)","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"Credential ID (UUID)","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.UpdateCredentialRequest"}}},"description":"Fields to update","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.CredentialOut"}}},"description":"OK"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"X-Org-ID required"},"404":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"not found"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"Update credential metadata and/or rotate secret","tags":["Credentials"]}},"/credentials/{id}/reveal":{"post":{"operationId":"RevealCredential","parameters":[{"description":"Organization ID (UUID)","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"Credential ID (UUID)","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"additionalProperties":{},"type":"object"}}},"description":"OK"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"404":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"not found"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"Reveal decrypted secret (one-time read)","tags":["Credentials"]}},"/dns/domains":{"get":{"description":"Returns domains for X-Org-ID. Filters: ` + "`" + `domain_name` + "`" + `, ` + "`" + `status` + "`" + `, ` + "`" + `q` + "`" + ` (contains).","operationId":"ListDomains","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"Exact domain name (lowercase, no trailing dot)","in":"query","name":"domain_name","schema":{"type":"string"}},{"description":"pending|provisioning|ready|failed","in":"query","name":"status","schema":{"type":"string"}},{"description":"Domain contains (case-insensitive)","in":"query","name":"q","schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/dto.DomainResponse"},"type":"array"}}},"description":"OK"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"db error"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"List domains (org scoped)","tags":["DNS"]},"post":{"description":"Creates a domain bound to a Route 53 scoped credential. Archer will backfill ZoneID if omitted.","operationId":"CreateDomain","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.CreateDomainRequest"}}},"description":"Domain payload","required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.DomainResponse"}}},"description":"Created"},"400":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"validation error"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"db error"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"Create a domain (org scoped)","tags":["DNS"]}},"/dns/domains/{domain_id}/records":{"get":{"description":"Filters: ` + "`" + `name` + "`" + `, ` + "`" + `type` + "`" + `, ` + "`" + `status` + "`" + `.","operationId":"ListRecordSets","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"Domain ID (UUID)","in":"path","name":"domain_id","required":true,"schema":{"type":"string"}},{"description":"Exact relative name or FQDN (server normalizes)","in":"query","name":"name","schema":{"type":"string"}},{"description":"RR type (A, AAAA, CNAME, TXT, MX, NS, SRV, CAA)","in":"query","name":"type","schema":{"type":"string"}},{"description":"pending|provisioning|ready|failed","in":"query","name":"status","schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/dto.RecordSetResponse"},"type":"array"}}},"description":"OK"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"404":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"domain not found"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"List record sets for a domain","tags":["DNS"]},"post":{"operationId":"CreateRecordSet","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"Domain ID (UUID)","in":"path","name":"domain_id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.CreateRecordSetRequest"}}},"description":"Record set payload","required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.RecordSetResponse"}}},"description":"Created"},"400":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"validation error"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"404":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"domain not found"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"Create a record set (pending; Archer will UPSERT to Route 53)","tags":["DNS"]}},"/dns/domains/{id}":{"delete":{"operationId":"DeleteDomain","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"Domain ID (UUID)","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"No Content"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"404":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"not found"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"Delete a domain","tags":["DNS"]},"get":{"operationId":"GetDomain","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"Domain ID (UUID)","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.DomainResponse"}}},"description":"OK"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"404":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"not found"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"Get a domain (org scoped)","tags":["DNS"]},"patch":{"operationId":"UpdateDomain","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"Domain ID (UUID)","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.UpdateDomainRequest"}}},"description":"Fields to update","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.DomainResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"validation error"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"404":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"not found"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"Update a domain (org scoped)","tags":["DNS"]}},"/dns/records/{id}":{"delete":{"operationId":"DeleteRecordSet","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"Record Set ID (UUID)","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"No Content"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"404":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"not found"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"Delete a record set (API removes row; worker can optionally handle external deletion policy)","tags":["DNS"]},"patch":{"operationId":"UpdateRecordSet","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"Record Set ID (UUID)","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.UpdateRecordSetRequest"}}},"description":"Fields to update","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.RecordSetResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"validation error"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"404":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"not found"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"Update a record set (flips to pending for reconciliation)","tags":["DNS"]}},"/healthz":{"get":{"description":"Returns 200 OK when the service is up","operationId":"HealthCheck // operationId","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/handlers.HealthStatus"}}},"description":"OK"}},"summary":"Basic health check","tags":["Health"]}},"/labels":{"get":{"description":"Returns node labels for the organization in X-Org-ID. Filters: ` + "`" + `key` + "`" + `, ` + "`" + `value` + "`" + `, and ` + "`" + `q` + "`" + ` (key contains). Add ` + "`" + `include=node_pools` + "`" + ` to include linked node groups.","operationId":"ListLabels","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"Exact key","in":"query","name":"key","schema":{"type":"string"}},{"description":"Exact value","in":"query","name":"value","schema":{"type":"string"}},{"description":"Key contains (case-insensitive)","in":"query","name":"q","schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/dto.LabelResponse"},"type":"array"}}},"description":"OK"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"failed to list node taints"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"List node labels (org scoped)","tags":["Labels"]},"post":{"description":"Creates a label.","operationId":"CreateLabel","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.CreateLabelRequest"}}},"description":"Label payload","required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.LabelResponse"}}},"description":"Created"},"400":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"invalid json / missing fields / invalid node_pool_ids"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"create failed"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"Create label (org scoped)","tags":["Labels"]}},"/labels/{id}":{"delete":{"description":"Permanently deletes the label.","operationId":"DeleteLabel","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"Label ID (UUID)","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"No Content"},"400":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"invalid id"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"delete failed"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"Delete label (org scoped)","tags":["Labels"]},"get":{"description":"Returns one label.","operationId":"GetLabel","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"Label ID (UUID)","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.LabelResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"invalid id"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"404":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"not found"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"fetch failed"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"Get label by ID (org scoped)","tags":["Labels"]},"patch":{"description":"Partially update label fields.","operationId":"UpdateLabel","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"Label ID (UUID)","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.UpdateLabelRequest"}}},"description":"Fields to update","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.LabelResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"invalid id / invalid json"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"404":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"not found"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"update failed"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"Update label (org scoped)","tags":["Labels"]}},"/load-balancers":{"get":{"description":"Returns load balancers for the organization in X-Org-ID.","operationId":"ListLoadBalancers","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/dto.LoadBalancerResponse"},"type":"array"}}},"description":"OK"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"failed to list clusters"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"List load balancers (org scoped)","tags":["LoadBalancers"]},"post":{"operationId":"CreateLoadBalancer","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.CreateLoadBalancerRequest"}}},"description":"Record set payload","required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.LoadBalancerResponse"}}},"description":"Created"},"400":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"validation error"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"404":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"domain not found"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"Create a load balancer","tags":["LoadBalancers"]}},"/load-balancers/{id}":{"delete":{"operationId":"DeleteLoadBalancer","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"Load Balancer ID (UUID)","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"No Content"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"404":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"not found"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"Delete a load balancer","tags":["LoadBalancers"]},"get":{"description":"Returns load balancer for the organization in X-Org-ID.","operationId":"GetLoadBalancers","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"LoadBalancer ID (UUID)","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/dto.LoadBalancerResponse"},"type":"array"}}},"description":"OK"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"failed to list clusters"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"Get a load balancer (org scoped)","tags":["LoadBalancers"]},"patch":{"operationId":"UpdateLoadBalancer","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"Load Balancer ID (UUID)","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.UpdateLoadBalancerRequest"}}},"description":"Fields to update","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.LoadBalancerResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"validation error"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"404":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"not found"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"Update a load balancer (org scoped)","tags":["LoadBalancers"]}},"/me":{"get":{"operationId":"GetMe","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/handlers.meResponse"}}},"description":"OK"}},"security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"summary":"Get current user profile","tags":["Me"]},"patch":{"operationId":"UpdateMe","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/handlers.updateMeRequest"}}},"description":"Patch profile","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/models.User"}}},"description":"OK"}},"security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"summary":"Update current user profile","tags":["Me"]}},"/me/api-keys":{"get":{"operationId":"ListUserAPIKeys","responses":{"200":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/handlers.userAPIKeyOut"},"type":"array"}}},"description":"OK"}},"security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"summary":"List my API keys","tags":["MeAPIKeys"]},"post":{"description":"Returns the plaintext key once. Store it securely on the client side.","operationId":"CreateUserAPIKey","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/handlers.createUserKeyRequest"}}},"description":"Key options","required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/handlers.userAPIKeyOut"}}},"description":"Created"}},"security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"summary":"Create a new user API key","tags":["MeAPIKeys"]}},"/me/api-keys/{id}":{"delete":{"operationId":"DeleteUserAPIKey","parameters":[{"description":"Key ID (UUID)","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"No Content"}},"security":[{"BearerAuth":[]}],"summary":"Delete a user API key","tags":["MeAPIKeys"]}},"/node-pools":{"get":{"description":"Returns node pools for the organization in X-Org-ID.","operationId":"ListNodePools","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"Name contains (case-insensitive)","in":"query","name":"q","schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/dto.NodePoolResponse"},"type":"array"}}},"description":"OK"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"failed to list node pools"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"List node pools (org scoped)","tags":["NodePools"]},"post":{"description":"Creates a node pool. Optionally attach initial servers.","operationId":"CreateNodePool","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.CreateNodePoolRequest"}}},"description":"NodePool payload","required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.NodePoolResponse"}}},"description":"Created"},"400":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"invalid json / missing fields / invalid server_ids"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"create failed"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"Create node pool (org scoped)","tags":["NodePools"]}},"/node-pools/{id}":{"delete":{"description":"Permanently deletes the node pool.","operationId":"DeleteNodePool","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"Node Pool ID (UUID)","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"No Content"},"400":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"invalid id"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"delete failed"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"Delete node pool (org scoped)","tags":["NodePools"]},"get":{"description":"Returns one node pool. Add ` + "`" + `include=servers` + "`" + ` to include servers.","operationId":"GetNodePool","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"Node Pool ID (UUID)","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.NodePoolResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"invalid id"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"404":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"not found"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"fetch failed"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"Get node pool by ID (org scoped)","tags":["NodePools"]},"patch":{"description":"Partially update node pool fields.","operationId":"UpdateNodePool","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"Node Pool ID (UUID)","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.UpdateNodePoolRequest"}}},"description":"Fields to update","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.NodePoolResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"invalid id / invalid json"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"404":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"not found"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"update failed"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"Update node pool (org scoped)","tags":["NodePools"]}},"/node-pools/{id}/annotations":{"get":{"operationId":"ListNodePoolAnnotations","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"Node Pool ID (UUID)","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/dto.AnnotationResponse"},"type":"array"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"invalid id"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"404":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"not found"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"fetch failed"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"List annotations attached to a node pool (org scoped)","tags":["NodePools"]},"post":{"operationId":"AttachNodePoolAnnotations","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"Node Group ID (UUID)","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.AttachAnnotationsRequest"}}},"description":"Annotation IDs to attach","required":true},"responses":{"204":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"No Content"},"400":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"invalid id / invalid server_ids"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"404":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"not found"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"attach failed"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"Attach annotation to a node pool (org scoped)","tags":["NodePools"]}},"/node-pools/{id}/annotations/{annotationId}":{"delete":{"operationId":"DetachNodePoolAnnotation","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"Node Pool ID (UUID)","in":"path","name":"id","required":true,"schema":{"type":"string"}},{"description":"Annotation ID (UUID)","in":"path","name":"annotationId","required":true,"schema":{"type":"string"}}],"responses":{"204":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"No Content"},"400":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"invalid id"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"404":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"not found"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"detach failed"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"Detach one annotation from a node pool (org scoped)","tags":["NodePools"]}},"/node-pools/{id}/labels":{"get":{"operationId":"ListNodePoolLabels","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"Label Pool ID (UUID)","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/dto.LabelResponse"},"type":"array"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"invalid id"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"404":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"not found"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"fetch failed"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"List labels attached to a node pool (org scoped)","tags":["NodePools"]},"post":{"operationId":"AttachNodePoolLabels","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"Node Pool ID (UUID)","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.AttachLabelsRequest"}}},"description":"Label IDs to attach","required":true},"responses":{"204":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"No Content"},"400":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"invalid id / invalid server_ids"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"404":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"not found"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"attach failed"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"Attach labels to a node pool (org scoped)","tags":["NodePools"]}},"/node-pools/{id}/labels/{labelId}":{"delete":{"operationId":"DetachNodePoolLabel","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"Node Pool ID (UUID)","in":"path","name":"id","required":true,"schema":{"type":"string"}},{"description":"Label ID (UUID)","in":"path","name":"labelId","required":true,"schema":{"type":"string"}}],"responses":{"204":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"No Content"},"400":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"invalid id"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"404":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"not found"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"detach failed"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"Detach one label from a node pool (org scoped)","tags":["NodePools"]}},"/node-pools/{id}/servers":{"get":{"operationId":"ListNodePoolServers","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"Node Pool ID (UUID)","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/dto.ServerResponse"},"type":"array"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"invalid id"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"404":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"not found"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"fetch failed"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"List servers attached to a node pool (org scoped)","tags":["NodePools"]},"post":{"operationId":"AttachNodePoolServers","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"Node Pool ID (UUID)","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.AttachServersRequest"}}},"description":"Server IDs to attach","required":true},"responses":{"204":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"No Content"},"400":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"invalid id / invalid server_ids"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"404":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"not found"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"attach failed"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"Attach servers to a node pool (org scoped)","tags":["NodePools"]}},"/node-pools/{id}/servers/{serverId}":{"delete":{"operationId":"DetachNodePoolServer","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"Node Pool ID (UUID)","in":"path","name":"id","required":true,"schema":{"type":"string"}},{"description":"Server ID (UUID)","in":"path","name":"serverId","required":true,"schema":{"type":"string"}}],"responses":{"204":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"No Content"},"400":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"invalid id"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"404":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"not found"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"detach failed"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"Detach one server from a node pool (org scoped)","tags":["NodePools"]}},"/node-pools/{id}/taints":{"get":{"operationId":"ListNodePoolTaints","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"Node Pool ID (UUID)","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/dto.TaintResponse"},"type":"array"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"invalid id"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"404":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"not found"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"fetch failed"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"List taints attached to a node pool (org scoped)","tags":["NodePools"]},"post":{"operationId":"AttachNodePoolTaints","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"Node Pool ID (UUID)","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.AttachTaintsRequest"}}},"description":"Taint IDs to attach","required":true},"responses":{"204":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"No Content"},"400":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"invalid id / invalid taint_ids"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"404":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"not found"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"attach failed"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"Attach taints to a node pool (org scoped)","tags":["NodePools"]}},"/node-pools/{id}/taints/{taintId}":{"delete":{"operationId":"DetachNodePoolTaint","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"Node Pool ID (UUID)","in":"path","name":"id","required":true,"schema":{"type":"string"}},{"description":"Taint ID (UUID)","in":"path","name":"taintId","required":true,"schema":{"type":"string"}}],"responses":{"204":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"No Content"},"400":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"invalid id"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"404":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"not found"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"detach failed"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"Detach one taint from a node pool (org scoped)","tags":["NodePools"]}},"/orgs":{"get":{"operationId":"listMyOrgs","responses":{"200":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/models.Organization"},"type":"array"}}},"description":"OK"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/utils.ErrorResponse"}}},"description":"Unauthorized"}},"security":[{"BearerAuth":[]}],"summary":"List organizations I belong to","tags":["Orgs"]},"post":{"operationId":"createOrg","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/handlers.orgCreateReq"}}},"description":"Org payload","required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/models.Organization"}}},"description":"Created"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/utils.ErrorResponse"}}},"description":"Bad Request"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/utils.ErrorResponse"}}},"description":"Unauthorized"},"409":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/utils.ErrorResponse"}}},"description":"Conflict"}},"security":[{"BearerAuth":[]}],"summary":"Create organization","tags":["Orgs"]}},"/orgs/{id}":{"delete":{"operationId":"deleteOrg","parameters":[{"description":"Org ID (UUID)","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"Deleted"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/utils.ErrorResponse"}}},"description":"Unauthorized"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/utils.ErrorResponse"}}},"description":"Not Found"}},"security":[{"BearerAuth":[]}],"summary":"Delete organization (owner)","tags":["Orgs"]},"get":{"operationId":"getOrg","parameters":[{"description":"Org ID (UUID)","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/models.Organization"}}},"description":"OK"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/utils.ErrorResponse"}}},"description":"Unauthorized"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/utils.ErrorResponse"}}},"description":"Not Found"}},"security":[{"BearerAuth":[]}],"summary":"Get organization","tags":["Orgs"]},"patch":{"operationId":"updateOrg","parameters":[{"description":"Org ID (UUID)","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/handlers.orgUpdateReq"}}},"description":"Update payload","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/models.Organization"}}},"description":"OK"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/utils.ErrorResponse"}}},"description":"Unauthorized"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/utils.ErrorResponse"}}},"description":"Not Found"}},"security":[{"BearerAuth":[]}],"summary":"Update organization (owner/admin)","tags":["Orgs"]}},"/orgs/{id}/api-keys":{"get":{"operationId":"listOrgKeys","parameters":[{"description":"Org ID (UUID)","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/models.APIKey"},"type":"array"}}},"description":"OK"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/utils.ErrorResponse"}}},"description":"Unauthorized"}},"security":[{"BearerAuth":[]}],"summary":"List org-scoped API keys (no secrets)","tags":["Orgs"]},"post":{"operationId":"createOrgKey","parameters":[{"description":"Org ID (UUID)","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/handlers.orgKeyCreateReq"}}},"description":"Key name + optional expiry","required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/handlers.orgKeyCreateResp"}}},"description":"Created"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/utils.ErrorResponse"}}},"description":"Unauthorized"}},"security":[{"BearerAuth":[]}],"summary":"Create org key/secret pair (owner/admin)","tags":["Orgs"]}},"/orgs/{id}/api-keys/{key_id}":{"delete":{"operationId":"deleteOrgKey","parameters":[{"description":"Org ID (UUID)","in":"path","name":"id","required":true,"schema":{"type":"string"}},{"description":"Key ID (UUID)","in":"path","name":"key_id","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"Deleted"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/utils.ErrorResponse"}}},"description":"Unauthorized"}},"security":[{"BearerAuth":[]}],"summary":"Delete org key (owner/admin)","tags":["Orgs"]}},"/orgs/{id}/members":{"get":{"operationId":"listMembers","parameters":[{"description":"Org ID (UUID)","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/handlers.memberOut"},"type":"array"}}},"description":"OK"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/utils.ErrorResponse"}}},"description":"Unauthorized"}},"security":[{"BearerAuth":[]}],"summary":"List members in org","tags":["Orgs"]},"post":{"operationId":"addOrUpdateMember","parameters":[{"description":"Org ID (UUID)","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/handlers.memberUpsertReq"}}},"description":"User \u0026 role","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/handlers.memberOut"}}},"description":"OK"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/utils.ErrorResponse"}}},"description":"Unauthorized"}},"security":[{"BearerAuth":[]}],"summary":"Add or update a member (owner/admin)","tags":["Orgs"]}},"/orgs/{id}/members/{user_id}":{"delete":{"operationId":"removeMember","parameters":[{"description":"Org ID (UUID)","in":"path","name":"id","required":true,"schema":{"type":"string"}},{"description":"User ID (UUID)","in":"path","name":"user_id","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"Removed"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/utils.ErrorResponse"}}},"description":"Unauthorized"}},"security":[{"BearerAuth":[]}],"summary":"Remove a member (owner/admin)","tags":["Orgs"]}},"/servers":{"get":{"description":"Returns servers for the organization in X-Org-ID. Optional filters: status, role.","operationId":"ListServers","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"Filter by status (pending|provisioning|ready|failed)","in":"query","name":"status","schema":{"type":"string"}},{"description":"Filter by role","in":"query","name":"role","schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/dto.ServerResponse"},"type":"array"}}},"description":"OK"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"failed to list servers"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"List servers (org scoped)","tags":["Servers"]},"post":{"description":"Creates a server bound to the org in X-Org-ID. Validates that ssh_key_id belongs to the org.","operationId":"CreateServer","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.CreateServerRequest"}}},"description":"Server payload","required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.ServerResponse"}}},"description":"Created"},"400":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"invalid json / missing fields / invalid status / invalid ssh_key_id"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"create failed"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"Create server (org scoped)","tags":["Servers"]}},"/servers/{id}":{"delete":{"description":"Permanently deletes the server.","operationId":"DeleteServer","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"Server ID (UUID)","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"No Content"},"400":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"invalid id"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"delete failed"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"Delete server (org scoped)","tags":["Servers"]},"get":{"description":"Returns one server in the given organization.","operationId":"GetServer","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"Server ID (UUID)","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.ServerResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"invalid id"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"404":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"not found"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"fetch failed"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"Get server by ID (org scoped)","tags":["Servers"]},"patch":{"description":"Partially update fields; changing ssh_key_id validates ownership.","operationId":"UpdateServer","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"Server ID (UUID)","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.UpdateServerRequest"}}},"description":"Fields to update","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.ServerResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"invalid id / invalid json / invalid status / invalid ssh_key_id"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"404":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"not found"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"update failed"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"Update server (org scoped)","tags":["Servers"]}},"/servers/{id}/reset-hostkey":{"post":{"description":"Clears the stored SSH host key for this server. The next SSH connection will re-learn the host key (trust-on-first-use).","operationId":"ResetServerHostKey","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"Server ID (UUID)","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.ServerResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"invalid id"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"404":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"not found"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"reset failed"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"Reset SSH host key (org scoped)","tags":["Servers"]}},"/ssh":{"get":{"description":"Returns ssh keys for the organization in X-Org-ID.","operationId":"ListPublicSshKeys","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/dto.SshResponse"},"type":"array"}}},"description":"OK"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"failed to list keys"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"List ssh keys (org scoped)","tags":["Ssh"]},"post":{"description":"Generates an RSA or ED25519 keypair, saves it, and returns metadata. For RSA you may set bits (2048/3072/4096). Default is 4096. ED25519 ignores bits.","operationId":"CreateSSHKey","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.CreateSSHRequest"}}},"description":"Key generation options","required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.SshResponse"}}},"description":"Created"},"400":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"invalid json / invalid bits"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"generation/create failed"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"Create ssh keypair (org scoped)","tags":["Ssh"]}},"/ssh/{id}":{"delete":{"description":"Permanently deletes a keypair.","operationId":"DeleteSSHKey","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"SSH Key ID (UUID)","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"No Content"},"400":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"invalid id"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"delete failed"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"Delete ssh keypair (org scoped)","tags":["Ssh"]},"get":{"description":"Returns public key fields. Append ` + "`" + `?reveal=true` + "`" + ` to include the private key PEM.","operationId":"GetSSHKey","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"SSH Key ID (UUID)","in":"path","name":"id","required":true,"schema":{"type":"string"}},{"description":"Reveal private key PEM","in":"query","name":"reveal","schema":{"type":"boolean"}}],"responses":{"200":{"content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/dto.SshResponse"},{"$ref":"#/components/schemas/dto.SshRevealResponse"}]}}},"description":"When reveal=true"},"400":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"invalid id"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"404":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"not found"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"fetch failed"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"Get ssh key by ID (org scoped)","tags":["Ssh"]}},"/ssh/{id}/download":{"get":{"description":"Download ` + "`" + `part=public|private|both` + "`" + ` of the keypair. ` + "`" + `both` + "`" + ` returns a zip file.","operationId":"DownloadSSHKey","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","required":true,"schema":{"type":"string"}},{"description":"SSH Key ID (UUID)","in":"path","name":"id","required":true,"schema":{"type":"string"}},{"description":"Which part to download","in":"query","name":"part","required":true,"schema":{"enum":["public","private","both"],"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"file content"},"400":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"invalid id / invalid part"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"404":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"not found"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"download failed"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"Download ssh key files by ID (org scoped)","tags":["Ssh"]}},"/taints":{"get":{"description":"Returns node taints for the organization in X-Org-ID. Filters: ` + "`" + `key` + "`" + `, ` + "`" + `value` + "`" + `, and ` + "`" + `q` + "`" + ` (key contains). Add ` + "`" + `include=node_pools` + "`" + ` to include linked node pools.","operationId":"ListTaints","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"Exact key","in":"query","name":"key","schema":{"type":"string"}},{"description":"Exact value","in":"query","name":"value","schema":{"type":"string"}},{"description":"key contains (case-insensitive)","in":"query","name":"q","schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/dto.TaintResponse"},"type":"array"}}},"description":"OK"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"failed to list node taints"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"List node pool taints (org scoped)","tags":["Taints"]},"post":{"description":"Creates a taint.","operationId":"CreateTaint","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.CreateTaintRequest"}}},"description":"Taint payload","required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.TaintResponse"}}},"description":"Created"},"400":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"invalid json / missing fields / invalid node_pool_ids"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"create failed"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"Create node taint (org scoped)","tags":["Taints"]}},"/taints/{id}":{"delete":{"description":"Permanently deletes the taint.","operationId":"DeleteTaint","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"Node Taint ID (UUID)","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"No Content"},"400":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"invalid id"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"delete failed"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"Delete taint (org scoped)","tags":["Taints"]},"get":{"operationId":"GetTaint","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"Node Taint ID (UUID)","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.TaintResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"invalid id"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"404":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"not found"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"fetch failed"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"Get node taint by ID (org scoped)","tags":["Taints"]},"patch":{"description":"Partially update taint fields.","operationId":"UpdateTaint","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"Node Taint ID (UUID)","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.UpdateTaintRequest"}}},"description":"Fields to update","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.TaintResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"invalid id / invalid json"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"404":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"not found"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"update failed"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"Update node taint (org scoped)","tags":["Taints"]}},"/version":{"get":{"description":"Returns build/runtime metadata for the running service.","operationId":"Version // operationId","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/handlers.VersionResponse"}}},"description":"OK"}},"summary":"Service version information","tags":["Meta"]}}}, + "openapi": "3.1.0", + "servers": [ + {"description":"Production API","url":"https://autoglue.onglueops.rocks/api/v1"}, + {"description":"Staging API","url":"https://autoglue.apps.nonprod.earth.onglueops.rocks/api/v1"}, + {"description":"Local dev","url":"http://localhost:8080/api/v1"} + ] +}` // SwaggerInfo holds exported Swagger Info so clients can modify it var SwaggerInfo = &swag.Spec{ Version: "1.0", - Host: "", - BasePath: "/api/v1", - Schemes: []string{"http", "https"}, Title: "AutoGlue API", Description: "API for managing K3s clusters across cloud providers", InfoInstanceName: "swagger", diff --git a/docs/swagger.json b/docs/swagger.json index bce8979..b6967dd 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -1 +1,12 @@ -{"schemes":["http","https"],"swagger":"2.0","info":{"description":"API for managing K3s clusters across cloud providers","title":"AutoGlue API","contact":{"name":"GlueOps"},"version":"1.0"},"basePath":"/api/v1","paths":{"/.well-known/jwks.json":{"get":{"description":"Returns the JSON Web Key Set for token verification","produces":["application/json"],"tags":["Auth"],"summary":"Get JWKS","operationId":"getJWKS","responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/dto.JWKS"}}}}},"/admin/archer/jobs":{"get":{"security":[{"BearerAuth":[]}],"description":"Paginated background jobs with optional filters. Search `q` may match id, type, error, payload (implementation-dependent).","consumes":["application/json"],"produces":["application/json"],"tags":["ArcherAdmin"],"summary":"List Archer jobs (admin)","operationId":"AdminListArcherJobs","parameters":[{"enum":["queued","running","succeeded","failed","canceled","retrying","scheduled"],"type":"string","description":"Filter by status","name":"status","in":"query"},{"type":"string","description":"Filter by queue name / worker name","name":"queue","in":"query"},{"type":"string","description":"Free-text search","name":"q","in":"query"},{"type":"integer","default":1,"description":"Page number","name":"page","in":"query"},{"maximum":100,"minimum":1,"type":"integer","default":25,"description":"Items per page","name":"page_size","in":"query"}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/dto.PageJob"}},"401":{"description":"Unauthorized","schema":{"type":"string"}},"403":{"description":"forbidden","schema":{"type":"string"}},"500":{"description":"internal error","schema":{"type":"string"}}}},"post":{"security":[{"BearerAuth":[]}],"description":"Create a job immediately or schedule it for the future via `run_at`.","consumes":["application/json"],"produces":["application/json"],"tags":["ArcherAdmin"],"summary":"Enqueue a new Archer job (admin)","operationId":"AdminEnqueueArcherJob","parameters":[{"description":"Job parameters","name":"body","in":"body","required":true,"schema":{"$ref":"#/definitions/dto.EnqueueRequest"}}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/dto.Job"}},"400":{"description":"invalid json or missing fields","schema":{"type":"string"}},"401":{"description":"Unauthorized","schema":{"type":"string"}},"403":{"description":"forbidden","schema":{"type":"string"}},"500":{"description":"internal error","schema":{"type":"string"}}}}},"/admin/archer/jobs/{id}/cancel":{"post":{"security":[{"BearerAuth":[]}],"description":"Set job status to canceled if cancellable. For running jobs, this only affects future picks; wire to Archer if you need active kill.","consumes":["application/json"],"produces":["application/json"],"tags":["ArcherAdmin"],"summary":"Cancel an Archer job (admin)","operationId":"AdminCancelArcherJob","parameters":[{"type":"string","description":"Job ID","name":"id","in":"path","required":true}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/dto.Job"}},"400":{"description":"invalid job or not cancellable","schema":{"type":"string"}},"401":{"description":"Unauthorized","schema":{"type":"string"}},"403":{"description":"forbidden","schema":{"type":"string"}},"404":{"description":"not found","schema":{"type":"string"}}}}},"/admin/archer/jobs/{id}/retry":{"post":{"security":[{"BearerAuth":[]}],"description":"Marks the job retriable (DB flip). Swap this for an Archer admin call if you expose one.","consumes":["application/json"],"produces":["application/json"],"tags":["ArcherAdmin"],"summary":"Retry a failed/canceled Archer job (admin)","operationId":"AdminRetryArcherJob","parameters":[{"type":"string","description":"Job ID","name":"id","in":"path","required":true}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/dto.Job"}},"400":{"description":"invalid job or not eligible","schema":{"type":"string"}},"401":{"description":"Unauthorized","schema":{"type":"string"}},"403":{"description":"forbidden","schema":{"type":"string"}},"404":{"description":"not found","schema":{"type":"string"}}}}},"/admin/archer/queues":{"get":{"security":[{"BearerAuth":[]}],"description":"Summary metrics per queue (pending, running, failed, scheduled).","consumes":["application/json"],"produces":["application/json"],"tags":["ArcherAdmin"],"summary":"List Archer queues (admin)","operationId":"AdminListArcherQueues","responses":{"200":{"description":"OK","schema":{"type":"array","items":{"$ref":"#/definitions/dto.QueueInfo"}}},"401":{"description":"Unauthorized","schema":{"type":"string"}},"403":{"description":"forbidden","schema":{"type":"string"}},"500":{"description":"internal error","schema":{"type":"string"}}}}},"/annotations":{"get":{"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"description":"Returns annotations for the organization in X-Org-ID. Filters: `key`, `value`, and `q` (key contains). Add `include=node_pools` to include linked node pools.","consumes":["application/json"],"produces":["application/json"],"tags":["Annotations"],"summary":"List annotations (org scoped)","operationId":"ListAnnotations","parameters":[{"type":"string","description":"Organization UUID","name":"X-Org-ID","in":"header"},{"type":"string","description":"Exact key","name":"key","in":"query"},{"type":"string","description":"Exact value","name":"value","in":"query"},{"type":"string","description":"key contains (case-insensitive)","name":"q","in":"query"}],"responses":{"200":{"description":"OK","schema":{"type":"array","items":{"$ref":"#/definitions/dto.AnnotationResponse"}}},"401":{"description":"Unauthorized","schema":{"type":"string"}},"403":{"description":"organization required","schema":{"type":"string"}},"500":{"description":"failed to list annotations","schema":{"type":"string"}}}},"post":{"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"description":"Creates an annotation.","consumes":["application/json"],"produces":["application/json"],"tags":["Annotations"],"summary":"Create annotation (org scoped)","operationId":"CreateAnnotation","parameters":[{"type":"string","description":"Organization UUID","name":"X-Org-ID","in":"header"},{"description":"Annotation payload","name":"body","in":"body","required":true,"schema":{"$ref":"#/definitions/dto.CreateAnnotationRequest"}}],"responses":{"201":{"description":"Created","schema":{"$ref":"#/definitions/dto.AnnotationResponse"}},"400":{"description":"invalid json / missing fields","schema":{"type":"string"}},"401":{"description":"Unauthorized","schema":{"type":"string"}},"403":{"description":"organization required","schema":{"type":"string"}},"500":{"description":"create failed","schema":{"type":"string"}}}}},"/annotations/{id}":{"get":{"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"description":"Returns one annotation. Add `include=node_pools` to include node pools.","consumes":["application/json"],"produces":["application/json"],"tags":["Annotations"],"summary":"Get annotation by ID (org scoped)","operationId":"GetAnnotation","parameters":[{"type":"string","description":"Organization UUID","name":"X-Org-ID","in":"header"},{"type":"string","description":"Annotation ID (UUID)","name":"id","in":"path","required":true}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/dto.AnnotationResponse"}},"400":{"description":"invalid id","schema":{"type":"string"}},"401":{"description":"Unauthorized","schema":{"type":"string"}},"403":{"description":"organization required","schema":{"type":"string"}},"404":{"description":"not found","schema":{"type":"string"}},"500":{"description":"fetch failed","schema":{"type":"string"}}}},"delete":{"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"description":"Permanently deletes the annotation.","consumes":["application/json"],"produces":["application/json"],"tags":["Annotations"],"summary":"Delete annotation (org scoped)","operationId":"DeleteAnnotation","parameters":[{"type":"string","description":"Organization UUID","name":"X-Org-ID","in":"header"},{"type":"string","description":"Annotation ID (UUID)","name":"id","in":"path","required":true}],"responses":{"204":{"description":"No Content","schema":{"type":"string"}},"400":{"description":"invalid id","schema":{"type":"string"}},"401":{"description":"Unauthorized","schema":{"type":"string"}},"403":{"description":"organization required","schema":{"type":"string"}},"500":{"description":"delete failed","schema":{"type":"string"}}}},"patch":{"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"description":"Partially update annotation fields.","consumes":["application/json"],"produces":["application/json"],"tags":["Annotations"],"summary":"Update annotation (org scoped)","operationId":"UpdateAnnotation","parameters":[{"type":"string","description":"Organization UUID","name":"X-Org-ID","in":"header"},{"type":"string","description":"Annotation ID (UUID)","name":"id","in":"path","required":true},{"description":"Fields to update","name":"body","in":"body","required":true,"schema":{"$ref":"#/definitions/dto.UpdateAnnotationRequest"}}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/dto.AnnotationResponse"}},"400":{"description":"invalid id / invalid json","schema":{"type":"string"}},"401":{"description":"Unauthorized","schema":{"type":"string"}},"403":{"description":"organization required","schema":{"type":"string"}},"404":{"description":"not found","schema":{"type":"string"}},"500":{"description":"update failed","schema":{"type":"string"}}}}},"/auth/logout":{"post":{"consumes":["application/json"],"produces":["application/json"],"tags":["Auth"],"summary":"Revoke refresh token family (logout everywhere)","operationId":"Logout","parameters":[{"description":"Refresh token","name":"body","in":"body","required":true,"schema":{"$ref":"#/definitions/dto.LogoutRequest"}}],"responses":{"204":{"description":"No Content"}}}},"/auth/refresh":{"post":{"consumes":["application/json"],"produces":["application/json"],"tags":["Auth"],"summary":"Rotate refresh token","operationId":"Refresh","parameters":[{"description":"Refresh token","name":"body","in":"body","required":true,"schema":{"$ref":"#/definitions/dto.RefreshRequest"}}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/dto.TokenPair"}}}}},"/auth/{provider}/callback":{"get":{"produces":["application/json"],"tags":["Auth"],"summary":"Handle social login callback","operationId":"AuthCallback","parameters":[{"type":"string","description":"google|github","name":"provider","in":"path","required":true}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/dto.TokenPair"}}}}},"/auth/{provider}/start":{"post":{"description":"Returns provider authorization URL for the frontend to redirect","produces":["application/json"],"tags":["Auth"],"summary":"Begin social login","operationId":"AuthStart","parameters":[{"type":"string","description":"google|github","name":"provider","in":"path","required":true}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/dto.AuthStartResponse"}}}}},"/clusters":{"get":{"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"description":"Returns clusters for the organization in X-Org-ID. Filter by `q` (name contains).","produces":["application/json"],"tags":["Clusters"],"summary":"List clusters (org scoped)","operationId":"ListClusters","parameters":[{"type":"string","description":"Organization UUID","name":"X-Org-ID","in":"header"},{"type":"string","description":"Name contains (case-insensitive)","name":"q","in":"query"}],"responses":{"200":{"description":"OK","schema":{"type":"array","items":{"$ref":"#/definitions/dto.ClusterResponse"}}},"401":{"description":"Unauthorized","schema":{"type":"string"}},"403":{"description":"organization required","schema":{"type":"string"}},"500":{"description":"failed to list clusters","schema":{"type":"string"}}}},"post":{"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"description":"Creates a cluster. If `kubeconfig` is provided, it will be encrypted per-organization and stored securely (never returned).","consumes":["application/json"],"produces":["application/json"],"tags":["Clusters"],"summary":"Create cluster (org scoped)","operationId":"CreateCluster","parameters":[{"type":"string","description":"Organization UUID","name":"X-Org-ID","in":"header"},{"description":"payload","name":"body","in":"body","required":true,"schema":{"$ref":"#/definitions/dto.CreateClusterRequest"}}],"responses":{"201":{"description":"Created","schema":{"$ref":"#/definitions/dto.ClusterResponse"}},"400":{"description":"invalid json","schema":{"type":"string"}},"401":{"description":"Unauthorized","schema":{"type":"string"}},"403":{"description":"organization required","schema":{"type":"string"}},"500":{"description":"create failed","schema":{"type":"string"}}}}},"/credentials":{"get":{"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"description":"Returns credential metadata for the current org. Secrets are never returned.","consumes":["application/json"],"produces":["application/json"],"tags":["Credentials"],"summary":"List credentials (metadata only)","operationId":"ListCredentials","parameters":[{"type":"string","description":"Organization ID (UUID)","name":"X-Org-ID","in":"header"},{"type":"string","description":"Filter by provider (e.g., aws)","name":"provider","in":"query"},{"type":"string","description":"Filter by kind (e.g., aws_access_key)","name":"kind","in":"query"},{"type":"string","description":"Filter by scope kind (provider/service/resource)","name":"scope_kind","in":"query"}],"responses":{"200":{"description":"OK","schema":{"type":"array","items":{"$ref":"#/definitions/dto.CredentialOut"}}},"401":{"description":"Unauthorized","schema":{"type":"string"}},"403":{"description":"organization required","schema":{"type":"string"}},"500":{"description":"internal server error","schema":{"type":"string"}}}},"post":{"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"consumes":["application/json"],"produces":["application/json"],"tags":["Credentials"],"summary":"Create a credential (encrypts secret)","operationId":"CreateCredential","parameters":[{"type":"string","description":"Organization ID (UUID)","name":"X-Org-ID","in":"header"},{"description":"Credential payload","name":"body","in":"body","required":true,"schema":{"$ref":"#/definitions/dto.CreateCredentialRequest"}}],"responses":{"201":{"description":"Created","schema":{"$ref":"#/definitions/dto.CredentialOut"}},"401":{"description":"Unauthorized","schema":{"type":"string"}},"403":{"description":"organization required","schema":{"type":"string"}},"500":{"description":"internal server error","schema":{"type":"string"}}}}},"/credentials/{id}":{"get":{"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"consumes":["application/json"],"produces":["application/json"],"tags":["Credentials"],"summary":"Get credential by ID (metadata only)","operationId":"GetCredential","parameters":[{"type":"string","description":"Organization ID (UUID)","name":"X-Org-ID","in":"header"},{"type":"string","description":"Credential ID (UUID)","name":"id","in":"path","required":true}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/dto.CredentialOut"}},"401":{"description":"Unauthorized","schema":{"type":"string"}},"403":{"description":"organization required","schema":{"type":"string"}},"500":{"description":"internal server error","schema":{"type":"string"}}}},"delete":{"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"consumes":["application/json"],"produces":["application/json"],"tags":["Credentials"],"summary":"Delete credential","operationId":"DeleteCredential","parameters":[{"type":"string","description":"Organization ID (UUID)","name":"X-Org-ID","in":"header"},{"type":"string","description":"Credential ID (UUID)","name":"id","in":"path","required":true}],"responses":{"204":{"description":"No Content"},"404":{"description":"not found","schema":{"type":"string"}}}},"patch":{"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"consumes":["application/json"],"produces":["application/json"],"tags":["Credentials"],"summary":"Update credential metadata and/or rotate secret","operationId":"UpdateCredential","parameters":[{"type":"string","description":"Organization ID (UUID)","name":"X-Org-ID","in":"header"},{"type":"string","description":"Credential ID (UUID)","name":"id","in":"path","required":true},{"description":"Fields to update","name":"body","in":"body","required":true,"schema":{"$ref":"#/definitions/dto.UpdateCredentialRequest"}}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/dto.CredentialOut"}},"403":{"description":"X-Org-ID required","schema":{"type":"string"}},"404":{"description":"not found","schema":{"type":"string"}}}}},"/credentials/{id}/reveal":{"post":{"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"consumes":["application/json"],"produces":["application/json"],"tags":["Credentials"],"summary":"Reveal decrypted secret (one-time read)","operationId":"RevealCredential","parameters":[{"type":"string","description":"Organization ID (UUID)","name":"X-Org-ID","in":"header"},{"type":"string","description":"Credential ID (UUID)","name":"id","in":"path","required":true}],"responses":{"200":{"description":"OK","schema":{"type":"object","additionalProperties":true}},"403":{"description":"organization required","schema":{"type":"string"}},"404":{"description":"not found","schema":{"type":"string"}}}}},"/dns/domains":{"get":{"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"description":"Returns domains for X-Org-ID. Filters: `domain_name`, `status`, `q` (contains).","consumes":["application/json"],"produces":["application/json"],"tags":["DNS"],"summary":"List domains (org scoped)","operationId":"ListDomains","parameters":[{"type":"string","description":"Organization UUID","name":"X-Org-ID","in":"header"},{"type":"string","description":"Exact domain name (lowercase, no trailing dot)","name":"domain_name","in":"query"},{"type":"string","description":"pending|provisioning|ready|failed","name":"status","in":"query"},{"type":"string","description":"Domain contains (case-insensitive)","name":"q","in":"query"}],"responses":{"200":{"description":"OK","schema":{"type":"array","items":{"$ref":"#/definitions/dto.DomainResponse"}}},"401":{"description":"Unauthorized","schema":{"type":"string"}},"403":{"description":"organization required","schema":{"type":"string"}},"500":{"description":"db error","schema":{"type":"string"}}}},"post":{"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"description":"Creates a domain bound to a Route 53 scoped credential. Archer will backfill ZoneID if omitted.","consumes":["application/json"],"produces":["application/json"],"tags":["DNS"],"summary":"Create a domain (org scoped)","operationId":"CreateDomain","parameters":[{"type":"string","description":"Organization UUID","name":"X-Org-ID","in":"header"},{"description":"Domain payload","name":"body","in":"body","required":true,"schema":{"$ref":"#/definitions/dto.CreateDomainRequest"}}],"responses":{"201":{"description":"Created","schema":{"$ref":"#/definitions/dto.DomainResponse"}},"400":{"description":"validation error","schema":{"type":"string"}},"401":{"description":"Unauthorized","schema":{"type":"string"}},"403":{"description":"organization required","schema":{"type":"string"}},"500":{"description":"db error","schema":{"type":"string"}}}}},"/dns/domains/{domain_id}/records":{"get":{"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"description":"Filters: `name`, `type`, `status`.","consumes":["application/json"],"produces":["application/json"],"tags":["DNS"],"summary":"List record sets for a domain","operationId":"ListRecordSets","parameters":[{"type":"string","description":"Organization UUID","name":"X-Org-ID","in":"header"},{"type":"string","description":"Domain ID (UUID)","name":"domain_id","in":"path","required":true},{"type":"string","description":"Exact relative name or FQDN (server normalizes)","name":"name","in":"query"},{"type":"string","description":"RR type (A, AAAA, CNAME, TXT, MX, NS, SRV, CAA)","name":"type","in":"query"},{"type":"string","description":"pending|provisioning|ready|failed","name":"status","in":"query"}],"responses":{"200":{"description":"OK","schema":{"type":"array","items":{"$ref":"#/definitions/dto.RecordSetResponse"}}},"403":{"description":"organization required","schema":{"type":"string"}},"404":{"description":"domain not found","schema":{"type":"string"}}}},"post":{"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"consumes":["application/json"],"produces":["application/json"],"tags":["DNS"],"summary":"Create a record set (pending; Archer will UPSERT to Route 53)","operationId":"CreateRecordSet","parameters":[{"type":"string","description":"Organization UUID","name":"X-Org-ID","in":"header"},{"type":"string","description":"Domain ID (UUID)","name":"domain_id","in":"path","required":true},{"description":"Record set payload","name":"body","in":"body","required":true,"schema":{"$ref":"#/definitions/dto.CreateRecordSetRequest"}}],"responses":{"201":{"description":"Created","schema":{"$ref":"#/definitions/dto.RecordSetResponse"}},"400":{"description":"validation error","schema":{"type":"string"}},"403":{"description":"organization required","schema":{"type":"string"}},"404":{"description":"domain not found","schema":{"type":"string"}}}}},"/dns/domains/{id}":{"get":{"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"consumes":["application/json"],"produces":["application/json"],"tags":["DNS"],"summary":"Get a domain (org scoped)","operationId":"GetDomain","parameters":[{"type":"string","description":"Organization UUID","name":"X-Org-ID","in":"header"},{"type":"string","description":"Domain ID (UUID)","name":"id","in":"path","required":true}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/dto.DomainResponse"}},"401":{"description":"Unauthorized","schema":{"type":"string"}},"403":{"description":"organization required","schema":{"type":"string"}},"404":{"description":"not found","schema":{"type":"string"}}}},"delete":{"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"consumes":["application/json"],"produces":["application/json"],"tags":["DNS"],"summary":"Delete a domain","operationId":"DeleteDomain","parameters":[{"type":"string","description":"Organization UUID","name":"X-Org-ID","in":"header"},{"type":"string","description":"Domain ID (UUID)","name":"id","in":"path","required":true}],"responses":{"204":{"description":"No Content"},"403":{"description":"organization required","schema":{"type":"string"}},"404":{"description":"not found","schema":{"type":"string"}}}},"patch":{"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"consumes":["application/json"],"produces":["application/json"],"tags":["DNS"],"summary":"Update a domain (org scoped)","operationId":"UpdateDomain","parameters":[{"type":"string","description":"Organization UUID","name":"X-Org-ID","in":"header"},{"type":"string","description":"Domain ID (UUID)","name":"id","in":"path","required":true},{"description":"Fields to update","name":"body","in":"body","required":true,"schema":{"$ref":"#/definitions/dto.UpdateDomainRequest"}}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/dto.DomainResponse"}},"400":{"description":"validation error","schema":{"type":"string"}},"403":{"description":"organization required","schema":{"type":"string"}},"404":{"description":"not found","schema":{"type":"string"}}}}},"/dns/records/{id}":{"delete":{"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"consumes":["application/json"],"produces":["application/json"],"tags":["DNS"],"summary":"Delete a record set (API removes row; worker can optionally handle external deletion policy)","operationId":"DeleteRecordSet","parameters":[{"type":"string","description":"Organization UUID","name":"X-Org-ID","in":"header"},{"type":"string","description":"Record Set ID (UUID)","name":"id","in":"path","required":true}],"responses":{"204":{"description":"No Content"},"403":{"description":"organization required","schema":{"type":"string"}},"404":{"description":"not found","schema":{"type":"string"}}}},"patch":{"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"consumes":["application/json"],"produces":["application/json"],"tags":["DNS"],"summary":"Update a record set (flips to pending for reconciliation)","operationId":"UpdateRecordSet","parameters":[{"type":"string","description":"Organization UUID","name":"X-Org-ID","in":"header"},{"type":"string","description":"Record Set ID (UUID)","name":"id","in":"path","required":true},{"description":"Fields to update","name":"body","in":"body","required":true,"schema":{"$ref":"#/definitions/dto.UpdateRecordSetRequest"}}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/dto.RecordSetResponse"}},"400":{"description":"validation error","schema":{"type":"string"}},"403":{"description":"organization required","schema":{"type":"string"}},"404":{"description":"not found","schema":{"type":"string"}}}}},"/healthz":{"get":{"description":"Returns 200 OK when the service is up","consumes":["application/json"],"produces":["application/json"],"tags":["Health"],"summary":"Basic health check","operationId":"HealthCheck // operationId","responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/handlers.HealthStatus"}}}}},"/labels":{"get":{"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"description":"Returns node labels for the organization in X-Org-ID. Filters: `key`, `value`, and `q` (key contains). Add `include=node_pools` to include linked node groups.","consumes":["application/json"],"produces":["application/json"],"tags":["Labels"],"summary":"List node labels (org scoped)","operationId":"ListLabels","parameters":[{"type":"string","description":"Organization UUID","name":"X-Org-ID","in":"header"},{"type":"string","description":"Exact key","name":"key","in":"query"},{"type":"string","description":"Exact value","name":"value","in":"query"},{"type":"string","description":"Key contains (case-insensitive)","name":"q","in":"query"}],"responses":{"200":{"description":"OK","schema":{"type":"array","items":{"$ref":"#/definitions/dto.LabelResponse"}}},"401":{"description":"Unauthorized","schema":{"type":"string"}},"403":{"description":"organization required","schema":{"type":"string"}},"500":{"description":"failed to list node taints","schema":{"type":"string"}}}},"post":{"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"description":"Creates a label.","consumes":["application/json"],"produces":["application/json"],"tags":["Labels"],"summary":"Create label (org scoped)","operationId":"CreateLabel","parameters":[{"type":"string","description":"Organization UUID","name":"X-Org-ID","in":"header"},{"description":"Label payload","name":"body","in":"body","required":true,"schema":{"$ref":"#/definitions/dto.CreateLabelRequest"}}],"responses":{"201":{"description":"Created","schema":{"$ref":"#/definitions/dto.LabelResponse"}},"400":{"description":"invalid json / missing fields / invalid node_pool_ids","schema":{"type":"string"}},"401":{"description":"Unauthorized","schema":{"type":"string"}},"403":{"description":"organization required","schema":{"type":"string"}},"500":{"description":"create failed","schema":{"type":"string"}}}}},"/labels/{id}":{"get":{"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"description":"Returns one label.","consumes":["application/json"],"produces":["application/json"],"tags":["Labels"],"summary":"Get label by ID (org scoped)","operationId":"GetLabel","parameters":[{"type":"string","description":"Organization UUID","name":"X-Org-ID","in":"header"},{"type":"string","description":"Label ID (UUID)","name":"id","in":"path","required":true}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/dto.LabelResponse"}},"400":{"description":"invalid id","schema":{"type":"string"}},"401":{"description":"Unauthorized","schema":{"type":"string"}},"403":{"description":"organization required","schema":{"type":"string"}},"404":{"description":"not found","schema":{"type":"string"}},"500":{"description":"fetch failed","schema":{"type":"string"}}}},"delete":{"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"description":"Permanently deletes the label.","consumes":["application/json"],"produces":["application/json"],"tags":["Labels"],"summary":"Delete label (org scoped)","operationId":"DeleteLabel","parameters":[{"type":"string","description":"Organization UUID","name":"X-Org-ID","in":"header"},{"type":"string","description":"Label ID (UUID)","name":"id","in":"path","required":true}],"responses":{"204":{"description":"No Content","schema":{"type":"string"}},"400":{"description":"invalid id","schema":{"type":"string"}},"401":{"description":"Unauthorized","schema":{"type":"string"}},"403":{"description":"organization required","schema":{"type":"string"}},"500":{"description":"delete failed","schema":{"type":"string"}}}},"patch":{"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"description":"Partially update label fields.","consumes":["application/json"],"produces":["application/json"],"tags":["Labels"],"summary":"Update label (org scoped)","operationId":"UpdateLabel","parameters":[{"type":"string","description":"Organization UUID","name":"X-Org-ID","in":"header"},{"type":"string","description":"Label ID (UUID)","name":"id","in":"path","required":true},{"description":"Fields to update","name":"body","in":"body","required":true,"schema":{"$ref":"#/definitions/dto.UpdateLabelRequest"}}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/dto.LabelResponse"}},"400":{"description":"invalid id / invalid json","schema":{"type":"string"}},"401":{"description":"Unauthorized","schema":{"type":"string"}},"403":{"description":"organization required","schema":{"type":"string"}},"404":{"description":"not found","schema":{"type":"string"}},"500":{"description":"update failed","schema":{"type":"string"}}}}},"/me":{"get":{"security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"produces":["application/json"],"tags":["Me"],"summary":"Get current user profile","operationId":"GetMe","responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/handlers.meResponse"}}}},"patch":{"security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"consumes":["application/json"],"produces":["application/json"],"tags":["Me"],"summary":"Update current user profile","operationId":"UpdateMe","parameters":[{"description":"Patch profile","name":"body","in":"body","required":true,"schema":{"$ref":"#/definitions/handlers.updateMeRequest"}}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/models.User"}}}}},"/me/api-keys":{"get":{"security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"produces":["application/json"],"tags":["MeAPIKeys"],"summary":"List my API keys","operationId":"ListUserAPIKeys","responses":{"200":{"description":"OK","schema":{"type":"array","items":{"$ref":"#/definitions/handlers.userAPIKeyOut"}}}}},"post":{"security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"description":"Returns the plaintext key once. Store it securely on the client side.","consumes":["application/json"],"produces":["application/json"],"tags":["MeAPIKeys"],"summary":"Create a new user API key","operationId":"CreateUserAPIKey","parameters":[{"description":"Key options","name":"body","in":"body","required":true,"schema":{"$ref":"#/definitions/handlers.createUserKeyRequest"}}],"responses":{"201":{"description":"Created","schema":{"$ref":"#/definitions/handlers.userAPIKeyOut"}}}}},"/me/api-keys/{id}":{"delete":{"security":[{"BearerAuth":[]}],"produces":["application/json"],"tags":["MeAPIKeys"],"summary":"Delete a user API key","operationId":"DeleteUserAPIKey","parameters":[{"type":"string","description":"Key ID (UUID)","name":"id","in":"path","required":true}],"responses":{"204":{"description":"No Content"}}}},"/node-pools":{"get":{"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"description":"Returns node pools for the organization in X-Org-ID.","consumes":["application/json"],"produces":["application/json"],"tags":["NodePools"],"summary":"List node pools (org scoped)","operationId":"ListNodePools","parameters":[{"type":"string","description":"Organization UUID","name":"X-Org-ID","in":"header"},{"type":"string","description":"Name contains (case-insensitive)","name":"q","in":"query"}],"responses":{"200":{"description":"OK","schema":{"type":"array","items":{"$ref":"#/definitions/dto.NodePoolResponse"}}},"401":{"description":"Unauthorized","schema":{"type":"string"}},"403":{"description":"organization required","schema":{"type":"string"}},"500":{"description":"failed to list node pools","schema":{"type":"string"}}}},"post":{"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"description":"Creates a node pool. Optionally attach initial servers.","consumes":["application/json"],"produces":["application/json"],"tags":["NodePools"],"summary":"Create node pool (org scoped)","operationId":"CreateNodePool","parameters":[{"type":"string","description":"Organization UUID","name":"X-Org-ID","in":"header"},{"description":"NodePool payload","name":"body","in":"body","required":true,"schema":{"$ref":"#/definitions/dto.CreateNodePoolRequest"}}],"responses":{"201":{"description":"Created","schema":{"$ref":"#/definitions/dto.NodePoolResponse"}},"400":{"description":"invalid json / missing fields / invalid server_ids","schema":{"type":"string"}},"401":{"description":"Unauthorized","schema":{"type":"string"}},"403":{"description":"organization required","schema":{"type":"string"}},"500":{"description":"create failed","schema":{"type":"string"}}}}},"/node-pools/{id}":{"get":{"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"description":"Returns one node pool. Add `include=servers` to include servers.","consumes":["application/json"],"produces":["application/json"],"tags":["NodePools"],"summary":"Get node pool by ID (org scoped)","operationId":"GetNodePool","parameters":[{"type":"string","description":"Organization UUID","name":"X-Org-ID","in":"header"},{"type":"string","description":"Node Pool ID (UUID)","name":"id","in":"path","required":true}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/dto.NodePoolResponse"}},"400":{"description":"invalid id","schema":{"type":"string"}},"401":{"description":"Unauthorized","schema":{"type":"string"}},"403":{"description":"organization required","schema":{"type":"string"}},"404":{"description":"not found","schema":{"type":"string"}},"500":{"description":"fetch failed","schema":{"type":"string"}}}},"delete":{"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"description":"Permanently deletes the node pool.","consumes":["application/json"],"produces":["application/json"],"tags":["NodePools"],"summary":"Delete node pool (org scoped)","operationId":"DeleteNodePool","parameters":[{"type":"string","description":"Organization UUID","name":"X-Org-ID","in":"header"},{"type":"string","description":"Node Pool ID (UUID)","name":"id","in":"path","required":true}],"responses":{"204":{"description":"No Content","schema":{"type":"string"}},"400":{"description":"invalid id","schema":{"type":"string"}},"401":{"description":"Unauthorized","schema":{"type":"string"}},"403":{"description":"organization required","schema":{"type":"string"}},"500":{"description":"delete failed","schema":{"type":"string"}}}},"patch":{"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"description":"Partially update node pool fields.","consumes":["application/json"],"produces":["application/json"],"tags":["NodePools"],"summary":"Update node pool (org scoped)","operationId":"UpdateNodePool","parameters":[{"type":"string","description":"Organization UUID","name":"X-Org-ID","in":"header"},{"type":"string","description":"Node Pool ID (UUID)","name":"id","in":"path","required":true},{"description":"Fields to update","name":"body","in":"body","required":true,"schema":{"$ref":"#/definitions/dto.UpdateNodePoolRequest"}}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/dto.NodePoolResponse"}},"400":{"description":"invalid id / invalid json","schema":{"type":"string"}},"401":{"description":"Unauthorized","schema":{"type":"string"}},"403":{"description":"organization required","schema":{"type":"string"}},"404":{"description":"not found","schema":{"type":"string"}},"500":{"description":"update failed","schema":{"type":"string"}}}}},"/node-pools/{id}/annotations":{"get":{"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"consumes":["application/json"],"produces":["application/json"],"tags":["NodePools"],"summary":"List annotations attached to a node pool (org scoped)","operationId":"ListNodePoolAnnotations","parameters":[{"type":"string","description":"Organization UUID","name":"X-Org-ID","in":"header"},{"type":"string","description":"Node Pool ID (UUID)","name":"id","in":"path","required":true}],"responses":{"200":{"description":"OK","schema":{"type":"array","items":{"$ref":"#/definitions/dto.AnnotationResponse"}}},"400":{"description":"invalid id","schema":{"type":"string"}},"401":{"description":"Unauthorized","schema":{"type":"string"}},"403":{"description":"organization required","schema":{"type":"string"}},"404":{"description":"not found","schema":{"type":"string"}},"500":{"description":"fetch failed","schema":{"type":"string"}}}},"post":{"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"consumes":["application/json"],"produces":["application/json"],"tags":["NodePools"],"summary":"Attach annotation to a node pool (org scoped)","operationId":"AttachNodePoolAnnotations","parameters":[{"type":"string","description":"Organization UUID","name":"X-Org-ID","in":"header"},{"type":"string","description":"Node Group ID (UUID)","name":"id","in":"path","required":true},{"description":"Annotation IDs to attach","name":"body","in":"body","required":true,"schema":{"$ref":"#/definitions/dto.AttachAnnotationsRequest"}}],"responses":{"204":{"description":"No Content","schema":{"type":"string"}},"400":{"description":"invalid id / invalid server_ids","schema":{"type":"string"}},"401":{"description":"Unauthorized","schema":{"type":"string"}},"403":{"description":"organization required","schema":{"type":"string"}},"404":{"description":"not found","schema":{"type":"string"}},"500":{"description":"attach failed","schema":{"type":"string"}}}}},"/node-pools/{id}/annotations/{annotationId}":{"delete":{"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"consumes":["application/json"],"produces":["application/json"],"tags":["NodePools"],"summary":"Detach one annotation from a node pool (org scoped)","operationId":"DetachNodePoolAnnotation","parameters":[{"type":"string","description":"Organization UUID","name":"X-Org-ID","in":"header"},{"type":"string","description":"Node Pool ID (UUID)","name":"id","in":"path","required":true},{"type":"string","description":"Annotation ID (UUID)","name":"annotationId","in":"path","required":true}],"responses":{"204":{"description":"No Content","schema":{"type":"string"}},"400":{"description":"invalid id","schema":{"type":"string"}},"401":{"description":"Unauthorized","schema":{"type":"string"}},"403":{"description":"organization required","schema":{"type":"string"}},"404":{"description":"not found","schema":{"type":"string"}},"500":{"description":"detach failed","schema":{"type":"string"}}}}},"/node-pools/{id}/labels":{"get":{"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"consumes":["application/json"],"produces":["application/json"],"tags":["NodePools"],"summary":"List labels attached to a node pool (org scoped)","operationId":"ListNodePoolLabels","parameters":[{"type":"string","description":"Organization UUID","name":"X-Org-ID","in":"header"},{"type":"string","description":"Label Pool ID (UUID)","name":"id","in":"path","required":true}],"responses":{"200":{"description":"OK","schema":{"type":"array","items":{"$ref":"#/definitions/dto.LabelResponse"}}},"400":{"description":"invalid id","schema":{"type":"string"}},"401":{"description":"Unauthorized","schema":{"type":"string"}},"403":{"description":"organization required","schema":{"type":"string"}},"404":{"description":"not found","schema":{"type":"string"}},"500":{"description":"fetch failed","schema":{"type":"string"}}}},"post":{"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"consumes":["application/json"],"produces":["application/json"],"tags":["NodePools"],"summary":"Attach labels to a node pool (org scoped)","operationId":"AttachNodePoolLabels","parameters":[{"type":"string","description":"Organization UUID","name":"X-Org-ID","in":"header"},{"type":"string","description":"Node Pool ID (UUID)","name":"id","in":"path","required":true},{"description":"Label IDs to attach","name":"body","in":"body","required":true,"schema":{"$ref":"#/definitions/dto.AttachLabelsRequest"}}],"responses":{"204":{"description":"No Content","schema":{"type":"string"}},"400":{"description":"invalid id / invalid server_ids","schema":{"type":"string"}},"401":{"description":"Unauthorized","schema":{"type":"string"}},"403":{"description":"organization required","schema":{"type":"string"}},"404":{"description":"not found","schema":{"type":"string"}},"500":{"description":"attach failed","schema":{"type":"string"}}}}},"/node-pools/{id}/labels/{labelId}":{"delete":{"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"consumes":["application/json"],"produces":["application/json"],"tags":["NodePools"],"summary":"Detach one label from a node pool (org scoped)","operationId":"DetachNodePoolLabel","parameters":[{"type":"string","description":"Organization UUID","name":"X-Org-ID","in":"header"},{"type":"string","description":"Node Pool ID (UUID)","name":"id","in":"path","required":true},{"type":"string","description":"Label ID (UUID)","name":"labelId","in":"path","required":true}],"responses":{"204":{"description":"No Content","schema":{"type":"string"}},"400":{"description":"invalid id","schema":{"type":"string"}},"401":{"description":"Unauthorized","schema":{"type":"string"}},"403":{"description":"organization required","schema":{"type":"string"}},"404":{"description":"not found","schema":{"type":"string"}},"500":{"description":"detach failed","schema":{"type":"string"}}}}},"/node-pools/{id}/servers":{"get":{"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"consumes":["application/json"],"produces":["application/json"],"tags":["NodePools"],"summary":"List servers attached to a node pool (org scoped)","operationId":"ListNodePoolServers","parameters":[{"type":"string","description":"Organization UUID","name":"X-Org-ID","in":"header"},{"type":"string","description":"Node Pool ID (UUID)","name":"id","in":"path","required":true}],"responses":{"200":{"description":"OK","schema":{"type":"array","items":{"$ref":"#/definitions/dto.ServerResponse"}}},"400":{"description":"invalid id","schema":{"type":"string"}},"401":{"description":"Unauthorized","schema":{"type":"string"}},"403":{"description":"organization required","schema":{"type":"string"}},"404":{"description":"not found","schema":{"type":"string"}},"500":{"description":"fetch failed","schema":{"type":"string"}}}},"post":{"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"consumes":["application/json"],"produces":["application/json"],"tags":["NodePools"],"summary":"Attach servers to a node pool (org scoped)","operationId":"AttachNodePoolServers","parameters":[{"type":"string","description":"Organization UUID","name":"X-Org-ID","in":"header"},{"type":"string","description":"Node Pool ID (UUID)","name":"id","in":"path","required":true},{"description":"Server IDs to attach","name":"body","in":"body","required":true,"schema":{"$ref":"#/definitions/dto.AttachServersRequest"}}],"responses":{"204":{"description":"No Content","schema":{"type":"string"}},"400":{"description":"invalid id / invalid server_ids","schema":{"type":"string"}},"401":{"description":"Unauthorized","schema":{"type":"string"}},"403":{"description":"organization required","schema":{"type":"string"}},"404":{"description":"not found","schema":{"type":"string"}},"500":{"description":"attach failed","schema":{"type":"string"}}}}},"/node-pools/{id}/servers/{serverId}":{"delete":{"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"consumes":["application/json"],"produces":["application/json"],"tags":["NodePools"],"summary":"Detach one server from a node pool (org scoped)","operationId":"DetachNodePoolServer","parameters":[{"type":"string","description":"Organization UUID","name":"X-Org-ID","in":"header"},{"type":"string","description":"Node Pool ID (UUID)","name":"id","in":"path","required":true},{"type":"string","description":"Server ID (UUID)","name":"serverId","in":"path","required":true}],"responses":{"204":{"description":"No Content","schema":{"type":"string"}},"400":{"description":"invalid id","schema":{"type":"string"}},"401":{"description":"Unauthorized","schema":{"type":"string"}},"403":{"description":"organization required","schema":{"type":"string"}},"404":{"description":"not found","schema":{"type":"string"}},"500":{"description":"detach failed","schema":{"type":"string"}}}}},"/node-pools/{id}/taints":{"get":{"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"consumes":["application/json"],"produces":["application/json"],"tags":["NodePools"],"summary":"List taints attached to a node pool (org scoped)","operationId":"ListNodePoolTaints","parameters":[{"type":"string","description":"Organization UUID","name":"X-Org-ID","in":"header"},{"type":"string","description":"Node Pool ID (UUID)","name":"id","in":"path","required":true}],"responses":{"200":{"description":"OK","schema":{"type":"array","items":{"$ref":"#/definitions/dto.TaintResponse"}}},"400":{"description":"invalid id","schema":{"type":"string"}},"401":{"description":"Unauthorized","schema":{"type":"string"}},"403":{"description":"organization required","schema":{"type":"string"}},"404":{"description":"not found","schema":{"type":"string"}},"500":{"description":"fetch failed","schema":{"type":"string"}}}},"post":{"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"consumes":["application/json"],"produces":["application/json"],"tags":["NodePools"],"summary":"Attach taints to a node pool (org scoped)","operationId":"AttachNodePoolTaints","parameters":[{"type":"string","description":"Organization UUID","name":"X-Org-ID","in":"header"},{"type":"string","description":"Node Pool ID (UUID)","name":"id","in":"path","required":true},{"description":"Taint IDs to attach","name":"body","in":"body","required":true,"schema":{"$ref":"#/definitions/dto.AttachTaintsRequest"}}],"responses":{"204":{"description":"No Content","schema":{"type":"string"}},"400":{"description":"invalid id / invalid taint_ids","schema":{"type":"string"}},"401":{"description":"Unauthorized","schema":{"type":"string"}},"403":{"description":"organization required","schema":{"type":"string"}},"404":{"description":"not found","schema":{"type":"string"}},"500":{"description":"attach failed","schema":{"type":"string"}}}}},"/node-pools/{id}/taints/{taintId}":{"delete":{"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"consumes":["application/json"],"produces":["application/json"],"tags":["NodePools"],"summary":"Detach one taint from a node pool (org scoped)","operationId":"DetachNodePoolTaint","parameters":[{"type":"string","description":"Organization UUID","name":"X-Org-ID","in":"header"},{"type":"string","description":"Node Pool ID (UUID)","name":"id","in":"path","required":true},{"type":"string","description":"Taint ID (UUID)","name":"taintId","in":"path","required":true}],"responses":{"204":{"description":"No Content","schema":{"type":"string"}},"400":{"description":"invalid id","schema":{"type":"string"}},"401":{"description":"Unauthorized","schema":{"type":"string"}},"403":{"description":"organization required","schema":{"type":"string"}},"404":{"description":"not found","schema":{"type":"string"}},"500":{"description":"detach failed","schema":{"type":"string"}}}}},"/orgs":{"get":{"security":[{"BearerAuth":[]}],"produces":["application/json"],"tags":["Orgs"],"summary":"List organizations I belong to","operationId":"listMyOrgs","responses":{"200":{"description":"OK","schema":{"type":"array","items":{"$ref":"#/definitions/models.Organization"}}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/utils.ErrorResponse"}}}},"post":{"security":[{"BearerAuth":[]}],"consumes":["application/json"],"produces":["application/json"],"tags":["Orgs"],"summary":"Create organization","operationId":"createOrg","parameters":[{"description":"Org payload","name":"body","in":"body","required":true,"schema":{"$ref":"#/definitions/handlers.orgCreateReq"}}],"responses":{"201":{"description":"Created","schema":{"$ref":"#/definitions/models.Organization"}},"400":{"description":"Bad Request","schema":{"$ref":"#/definitions/utils.ErrorResponse"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/utils.ErrorResponse"}},"409":{"description":"Conflict","schema":{"$ref":"#/definitions/utils.ErrorResponse"}}}}},"/orgs/{id}":{"get":{"security":[{"BearerAuth":[]}],"produces":["application/json"],"tags":["Orgs"],"summary":"Get organization","operationId":"getOrg","parameters":[{"type":"string","description":"Org ID (UUID)","name":"id","in":"path","required":true}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/models.Organization"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/utils.ErrorResponse"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/utils.ErrorResponse"}}}},"delete":{"security":[{"BearerAuth":[]}],"produces":["application/json"],"tags":["Orgs"],"summary":"Delete organization (owner)","operationId":"deleteOrg","parameters":[{"type":"string","description":"Org ID (UUID)","name":"id","in":"path","required":true}],"responses":{"204":{"description":"Deleted"},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/utils.ErrorResponse"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/utils.ErrorResponse"}}}},"patch":{"security":[{"BearerAuth":[]}],"consumes":["application/json"],"produces":["application/json"],"tags":["Orgs"],"summary":"Update organization (owner/admin)","operationId":"updateOrg","parameters":[{"type":"string","description":"Org ID (UUID)","name":"id","in":"path","required":true},{"description":"Update payload","name":"body","in":"body","required":true,"schema":{"$ref":"#/definitions/handlers.orgUpdateReq"}}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/models.Organization"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/utils.ErrorResponse"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/utils.ErrorResponse"}}}}},"/orgs/{id}/api-keys":{"get":{"security":[{"BearerAuth":[]}],"produces":["application/json"],"tags":["Orgs"],"summary":"List org-scoped API keys (no secrets)","operationId":"listOrgKeys","parameters":[{"type":"string","description":"Org ID (UUID)","name":"id","in":"path","required":true}],"responses":{"200":{"description":"OK","schema":{"type":"array","items":{"$ref":"#/definitions/models.APIKey"}}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/utils.ErrorResponse"}}}},"post":{"security":[{"BearerAuth":[]}],"consumes":["application/json"],"produces":["application/json"],"tags":["Orgs"],"summary":"Create org key/secret pair (owner/admin)","operationId":"createOrgKey","parameters":[{"type":"string","description":"Org ID (UUID)","name":"id","in":"path","required":true},{"description":"Key name + optional expiry","name":"body","in":"body","required":true,"schema":{"$ref":"#/definitions/handlers.orgKeyCreateReq"}}],"responses":{"201":{"description":"Created","schema":{"$ref":"#/definitions/handlers.orgKeyCreateResp"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/utils.ErrorResponse"}}}}},"/orgs/{id}/api-keys/{key_id}":{"delete":{"security":[{"BearerAuth":[]}],"produces":["application/json"],"tags":["Orgs"],"summary":"Delete org key (owner/admin)","operationId":"deleteOrgKey","parameters":[{"type":"string","description":"Org ID (UUID)","name":"id","in":"path","required":true},{"type":"string","description":"Key ID (UUID)","name":"key_id","in":"path","required":true}],"responses":{"204":{"description":"Deleted"},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/utils.ErrorResponse"}}}}},"/orgs/{id}/members":{"get":{"security":[{"BearerAuth":[]}],"produces":["application/json"],"tags":["Orgs"],"summary":"List members in org","operationId":"listMembers","parameters":[{"type":"string","description":"Org ID (UUID)","name":"id","in":"path","required":true}],"responses":{"200":{"description":"OK","schema":{"type":"array","items":{"$ref":"#/definitions/handlers.memberOut"}}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/utils.ErrorResponse"}}}},"post":{"security":[{"BearerAuth":[]}],"consumes":["application/json"],"produces":["application/json"],"tags":["Orgs"],"summary":"Add or update a member (owner/admin)","operationId":"addOrUpdateMember","parameters":[{"type":"string","description":"Org ID (UUID)","name":"id","in":"path","required":true},{"description":"User \u0026 role","name":"body","in":"body","required":true,"schema":{"$ref":"#/definitions/handlers.memberUpsertReq"}}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/handlers.memberOut"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/utils.ErrorResponse"}}}}},"/orgs/{id}/members/{user_id}":{"delete":{"security":[{"BearerAuth":[]}],"produces":["application/json"],"tags":["Orgs"],"summary":"Remove a member (owner/admin)","operationId":"removeMember","parameters":[{"type":"string","description":"Org ID (UUID)","name":"id","in":"path","required":true},{"type":"string","description":"User ID (UUID)","name":"user_id","in":"path","required":true}],"responses":{"204":{"description":"Removed"},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/utils.ErrorResponse"}}}}},"/servers":{"get":{"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"description":"Returns servers for the organization in X-Org-ID. Optional filters: status, role.","consumes":["application/json"],"produces":["application/json"],"tags":["Servers"],"summary":"List servers (org scoped)","operationId":"ListServers","parameters":[{"type":"string","description":"Organization UUID","name":"X-Org-ID","in":"header"},{"type":"string","description":"Filter by status (pending|provisioning|ready|failed)","name":"status","in":"query"},{"type":"string","description":"Filter by role","name":"role","in":"query"}],"responses":{"200":{"description":"OK","schema":{"type":"array","items":{"$ref":"#/definitions/dto.ServerResponse"}}},"401":{"description":"Unauthorized","schema":{"type":"string"}},"403":{"description":"organization required","schema":{"type":"string"}},"500":{"description":"failed to list servers","schema":{"type":"string"}}}},"post":{"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"description":"Creates a server bound to the org in X-Org-ID. Validates that ssh_key_id belongs to the org.","consumes":["application/json"],"produces":["application/json"],"tags":["Servers"],"summary":"Create server (org scoped)","operationId":"CreateServer","parameters":[{"type":"string","description":"Organization UUID","name":"X-Org-ID","in":"header"},{"description":"Server payload","name":"body","in":"body","required":true,"schema":{"$ref":"#/definitions/dto.CreateServerRequest"}}],"responses":{"201":{"description":"Created","schema":{"$ref":"#/definitions/dto.ServerResponse"}},"400":{"description":"invalid json / missing fields / invalid status / invalid ssh_key_id","schema":{"type":"string"}},"401":{"description":"Unauthorized","schema":{"type":"string"}},"403":{"description":"organization required","schema":{"type":"string"}},"500":{"description":"create failed","schema":{"type":"string"}}}}},"/servers/{id}":{"get":{"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"description":"Returns one server in the given organization.","consumes":["application/json"],"produces":["application/json"],"tags":["Servers"],"summary":"Get server by ID (org scoped)","operationId":"GetServer","parameters":[{"type":"string","description":"Organization UUID","name":"X-Org-ID","in":"header"},{"type":"string","description":"Server ID (UUID)","name":"id","in":"path","required":true}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/dto.ServerResponse"}},"400":{"description":"invalid id","schema":{"type":"string"}},"401":{"description":"Unauthorized","schema":{"type":"string"}},"403":{"description":"organization required","schema":{"type":"string"}},"404":{"description":"not found","schema":{"type":"string"}},"500":{"description":"fetch failed","schema":{"type":"string"}}}},"delete":{"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"description":"Permanently deletes the server.","consumes":["application/json"],"produces":["application/json"],"tags":["Servers"],"summary":"Delete server (org scoped)","operationId":"DeleteServer","parameters":[{"type":"string","description":"Organization UUID","name":"X-Org-ID","in":"header"},{"type":"string","description":"Server ID (UUID)","name":"id","in":"path","required":true}],"responses":{"204":{"description":"No Content","schema":{"type":"string"}},"400":{"description":"invalid id","schema":{"type":"string"}},"401":{"description":"Unauthorized","schema":{"type":"string"}},"403":{"description":"organization required","schema":{"type":"string"}},"500":{"description":"delete failed","schema":{"type":"string"}}}},"patch":{"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"description":"Partially update fields; changing ssh_key_id validates ownership.","consumes":["application/json"],"produces":["application/json"],"tags":["Servers"],"summary":"Update server (org scoped)","operationId":"UpdateServer","parameters":[{"type":"string","description":"Organization UUID","name":"X-Org-ID","in":"header"},{"type":"string","description":"Server ID (UUID)","name":"id","in":"path","required":true},{"description":"Fields to update","name":"body","in":"body","required":true,"schema":{"$ref":"#/definitions/dto.UpdateServerRequest"}}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/dto.ServerResponse"}},"400":{"description":"invalid id / invalid json / invalid status / invalid ssh_key_id","schema":{"type":"string"}},"401":{"description":"Unauthorized","schema":{"type":"string"}},"403":{"description":"organization required","schema":{"type":"string"}},"404":{"description":"not found","schema":{"type":"string"}},"500":{"description":"update failed","schema":{"type":"string"}}}}},"/servers/{id}/reset-hostkey":{"post":{"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"description":"Clears the stored SSH host key for this server. The next SSH connection will re-learn the host key (trust-on-first-use).","consumes":["application/json"],"produces":["application/json"],"tags":["Servers"],"summary":"Reset SSH host key (org scoped)","operationId":"ResetServerHostKey","parameters":[{"type":"string","description":"Organization UUID","name":"X-Org-ID","in":"header"},{"type":"string","description":"Server ID (UUID)","name":"id","in":"path","required":true}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/dto.ServerResponse"}},"400":{"description":"invalid id","schema":{"type":"string"}},"401":{"description":"Unauthorized","schema":{"type":"string"}},"403":{"description":"organization required","schema":{"type":"string"}},"404":{"description":"not found","schema":{"type":"string"}},"500":{"description":"reset failed","schema":{"type":"string"}}}}},"/ssh":{"get":{"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"description":"Returns ssh keys for the organization in X-Org-ID.","consumes":["application/json"],"produces":["application/json"],"tags":["Ssh"],"summary":"List ssh keys (org scoped)","operationId":"ListPublicSshKeys","parameters":[{"type":"string","description":"Organization UUID","name":"X-Org-ID","in":"header"}],"responses":{"200":{"description":"OK","schema":{"type":"array","items":{"$ref":"#/definitions/dto.SshResponse"}}},"401":{"description":"Unauthorized","schema":{"type":"string"}},"403":{"description":"organization required","schema":{"type":"string"}},"500":{"description":"failed to list keys","schema":{"type":"string"}}}},"post":{"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"description":"Generates an RSA or ED25519 keypair, saves it, and returns metadata. For RSA you may set bits (2048/3072/4096). Default is 4096. ED25519 ignores bits.","consumes":["application/json"],"produces":["application/json"],"tags":["Ssh"],"summary":"Create ssh keypair (org scoped)","operationId":"CreateSSHKey","parameters":[{"type":"string","description":"Organization UUID","name":"X-Org-ID","in":"header"},{"description":"Key generation options","name":"body","in":"body","required":true,"schema":{"$ref":"#/definitions/dto.CreateSSHRequest"}}],"responses":{"201":{"description":"Created","schema":{"$ref":"#/definitions/dto.SshResponse"}},"400":{"description":"invalid json / invalid bits","schema":{"type":"string"}},"401":{"description":"Unauthorized","schema":{"type":"string"}},"403":{"description":"organization required","schema":{"type":"string"}},"500":{"description":"generation/create failed","schema":{"type":"string"}}}}},"/ssh/{id}":{"get":{"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"description":"Returns public key fields. Append `?reveal=true` to include the private key PEM.","consumes":["application/json"],"produces":["application/json"],"tags":["Ssh"],"summary":"Get ssh key by ID (org scoped)","operationId":"GetSSHKey","parameters":[{"type":"string","description":"Organization UUID","name":"X-Org-ID","in":"header"},{"type":"string","description":"SSH Key ID (UUID)","name":"id","in":"path","required":true},{"type":"boolean","description":"Reveal private key PEM","name":"reveal","in":"query"}],"responses":{"200":{"description":"When reveal=true","schema":{"$ref":"#/definitions/dto.SshRevealResponse"}},"400":{"description":"invalid id","schema":{"type":"string"}},"401":{"description":"Unauthorized","schema":{"type":"string"}},"403":{"description":"organization required","schema":{"type":"string"}},"404":{"description":"not found","schema":{"type":"string"}},"500":{"description":"fetch failed","schema":{"type":"string"}}}},"delete":{"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"description":"Permanently deletes a keypair.","consumes":["application/json"],"produces":["application/json"],"tags":["Ssh"],"summary":"Delete ssh keypair (org scoped)","operationId":"DeleteSSHKey","parameters":[{"type":"string","description":"Organization UUID","name":"X-Org-ID","in":"header"},{"type":"string","description":"SSH Key ID (UUID)","name":"id","in":"path","required":true}],"responses":{"204":{"description":"No Content","schema":{"type":"string"}},"400":{"description":"invalid id","schema":{"type":"string"}},"401":{"description":"Unauthorized","schema":{"type":"string"}},"403":{"description":"organization required","schema":{"type":"string"}},"500":{"description":"delete failed","schema":{"type":"string"}}}}},"/ssh/{id}/download":{"get":{"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"description":"Download `part=public|private|both` of the keypair. `both` returns a zip file.","produces":["application/json"],"tags":["Ssh"],"summary":"Download ssh key files by ID (org scoped)","operationId":"DownloadSSHKey","parameters":[{"type":"string","description":"Organization UUID","name":"X-Org-ID","in":"header","required":true},{"type":"string","description":"SSH Key ID (UUID)","name":"id","in":"path","required":true},{"enum":["public","private","both"],"type":"string","description":"Which part to download","name":"part","in":"query","required":true}],"responses":{"200":{"description":"file content","schema":{"type":"string"}},"400":{"description":"invalid id / invalid part","schema":{"type":"string"}},"401":{"description":"Unauthorized","schema":{"type":"string"}},"403":{"description":"organization required","schema":{"type":"string"}},"404":{"description":"not found","schema":{"type":"string"}},"500":{"description":"download failed","schema":{"type":"string"}}}}},"/taints":{"get":{"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"description":"Returns node taints for the organization in X-Org-ID. Filters: `key`, `value`, and `q` (key contains). Add `include=node_pools` to include linked node pools.","consumes":["application/json"],"produces":["application/json"],"tags":["Taints"],"summary":"List node pool taints (org scoped)","operationId":"ListTaints","parameters":[{"type":"string","description":"Organization UUID","name":"X-Org-ID","in":"header"},{"type":"string","description":"Exact key","name":"key","in":"query"},{"type":"string","description":"Exact value","name":"value","in":"query"},{"type":"string","description":"key contains (case-insensitive)","name":"q","in":"query"}],"responses":{"200":{"description":"OK","schema":{"type":"array","items":{"$ref":"#/definitions/dto.TaintResponse"}}},"401":{"description":"Unauthorized","schema":{"type":"string"}},"403":{"description":"organization required","schema":{"type":"string"}},"500":{"description":"failed to list node taints","schema":{"type":"string"}}}},"post":{"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"description":"Creates a taint.","consumes":["application/json"],"produces":["application/json"],"tags":["Taints"],"summary":"Create node taint (org scoped)","operationId":"CreateTaint","parameters":[{"type":"string","description":"Organization UUID","name":"X-Org-ID","in":"header"},{"description":"Taint payload","name":"body","in":"body","required":true,"schema":{"$ref":"#/definitions/dto.CreateTaintRequest"}}],"responses":{"201":{"description":"Created","schema":{"$ref":"#/definitions/dto.TaintResponse"}},"400":{"description":"invalid json / missing fields / invalid node_pool_ids","schema":{"type":"string"}},"401":{"description":"Unauthorized","schema":{"type":"string"}},"403":{"description":"organization required","schema":{"type":"string"}},"500":{"description":"create failed","schema":{"type":"string"}}}}},"/taints/{id}":{"get":{"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"consumes":["application/json"],"produces":["application/json"],"tags":["Taints"],"summary":"Get node taint by ID (org scoped)","operationId":"GetTaint","parameters":[{"type":"string","description":"Organization UUID","name":"X-Org-ID","in":"header"},{"type":"string","description":"Node Taint ID (UUID)","name":"id","in":"path","required":true}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/dto.TaintResponse"}},"400":{"description":"invalid id","schema":{"type":"string"}},"401":{"description":"Unauthorized","schema":{"type":"string"}},"403":{"description":"organization required","schema":{"type":"string"}},"404":{"description":"not found","schema":{"type":"string"}},"500":{"description":"fetch failed","schema":{"type":"string"}}}},"delete":{"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"description":"Permanently deletes the taint.","consumes":["application/json"],"produces":["application/json"],"tags":["Taints"],"summary":"Delete taint (org scoped)","operationId":"DeleteTaint","parameters":[{"type":"string","description":"Organization UUID","name":"X-Org-ID","in":"header"},{"type":"string","description":"Node Taint ID (UUID)","name":"id","in":"path","required":true}],"responses":{"204":{"description":"No Content","schema":{"type":"string"}},"400":{"description":"invalid id","schema":{"type":"string"}},"401":{"description":"Unauthorized","schema":{"type":"string"}},"403":{"description":"organization required","schema":{"type":"string"}},"500":{"description":"delete failed","schema":{"type":"string"}}}},"patch":{"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"description":"Partially update taint fields.","consumes":["application/json"],"produces":["application/json"],"tags":["Taints"],"summary":"Update node taint (org scoped)","operationId":"UpdateTaint","parameters":[{"type":"string","description":"Organization UUID","name":"X-Org-ID","in":"header"},{"type":"string","description":"Node Taint ID (UUID)","name":"id","in":"path","required":true},{"description":"Fields to update","name":"body","in":"body","required":true,"schema":{"$ref":"#/definitions/dto.UpdateTaintRequest"}}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/dto.TaintResponse"}},"400":{"description":"invalid id / invalid json","schema":{"type":"string"}},"401":{"description":"Unauthorized","schema":{"type":"string"}},"403":{"description":"organization required","schema":{"type":"string"}},"404":{"description":"not found","schema":{"type":"string"}},"500":{"description":"update failed","schema":{"type":"string"}}}}},"/version":{"get":{"description":"Returns build/runtime metadata for the running service.","consumes":["application/json"],"produces":["application/json"],"tags":["Meta"],"summary":"Service version information","operationId":"Version // operationId","responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/handlers.VersionResponse"}}}}}},"definitions":{"dto.AnnotationResponse":{"type":"object","properties":{"created_at":{"type":"string"},"id":{"type":"string"},"key":{"type":"string"},"organization_id":{"type":"string"},"updated_at":{"type":"string"},"value":{"type":"string"}}},"dto.AttachAnnotationsRequest":{"type":"object","properties":{"annotation_ids":{"type":"array","items":{"type":"string"}}}},"dto.AttachLabelsRequest":{"type":"object","properties":{"label_ids":{"type":"array","items":{"type":"string"}}}},"dto.AttachServersRequest":{"type":"object","properties":{"server_ids":{"type":"array","items":{"type":"string"}}}},"dto.AttachTaintsRequest":{"type":"object","properties":{"taint_ids":{"type":"array","items":{"type":"string"}}}},"dto.AuthStartResponse":{"type":"object","properties":{"auth_url":{"type":"string","example":"https://accounts.google.com/o/oauth2/v2/auth?client_id=..."}}},"dto.ClusterResponse":{"type":"object","properties":{"bastion_server":{"$ref":"#/definitions/dto.ServerResponse"},"captain_domain":{"type":"string"},"certificate_key":{"type":"string"},"cluster_load_balancer":{"type":"string"},"control_load_balancer":{"type":"string"},"created_at":{"type":"string"},"id":{"type":"string"},"name":{"type":"string"},"node_pools":{"type":"array","items":{"$ref":"#/definitions/dto.NodePoolResponse"}},"provider":{"type":"string"},"random_token":{"type":"string"},"region":{"type":"string"},"status":{"type":"string"},"updated_at":{"type":"string"}}},"dto.CreateAnnotationRequest":{"type":"object","properties":{"key":{"type":"string"},"value":{"type":"string"}}},"dto.CreateClusterRequest":{"type":"object","properties":{"captain_domain":{"type":"string"},"cluster_load_balancer":{"type":"string"},"control_load_balancer":{"type":"string"},"name":{"type":"string"},"provider":{"type":"string"},"region":{"type":"string"},"status":{"type":"string"}}},"dto.CreateCredentialRequest":{"type":"object","required":["kind","provider","schema_version","scope","scope_kind","scope_version","secret"],"properties":{"account_id":{"type":"string","maxLength":32},"kind":{"description":"aws_access_key, api_token, basic_auth, oauth2","type":"string"},"name":{"description":"human label","type":"string","maxLength":100},"provider":{"type":"string","enum":["aws","cloudflare","hetzner","digitalocean","generic"]},"region":{"type":"string","maxLength":32},"schema_version":{"description":"secret schema version","type":"integer","minimum":1},"scope":{"description":"{\"service\":\"route53\"} or {\"arn\":\"...\"}","type":"object"},"scope_kind":{"type":"string","enum":["provider","service","resource"]},"scope_version":{"description":"scope schema version","type":"integer","minimum":1},"secret":{"description":"encrypted later","type":"object"}}},"dto.CreateDomainRequest":{"type":"object","required":["credential_id","domain_name"],"properties":{"credential_id":{"type":"string"},"domain_name":{"type":"string"},"zone_id":{"type":"string","maxLength":128}}},"dto.CreateLabelRequest":{"type":"object","properties":{"key":{"type":"string"},"value":{"type":"string"}}},"dto.CreateNodePoolRequest":{"type":"object","properties":{"name":{"type":"string"},"role":{"type":"string","enum":["master","worker"]}}},"dto.CreateRecordSetRequest":{"type":"object","required":["name","type"],"properties":{"name":{"description":"Name relative to domain (\"endpoint\") OR FQDN (\"endpoint.example.com\").\nServer normalizes to relative.","type":"string","maxLength":253},"ttl":{"type":"integer","maximum":86400,"minimum":1},"type":{"type":"string"},"values":{"type":"array","items":{"type":"string"}}}},"dto.CreateSSHRequest":{"type":"object","properties":{"bits":{"description":"Only for RSA","type":"integer"},"comment":{"type":"string","example":"deploy@autoglue"},"name":{"type":"string"},"type":{"description":"\"rsa\" (default) or \"ed25519\"","type":"string"}}},"dto.CreateServerRequest":{"type":"object","properties":{"hostname":{"type":"string"},"private_ip_address":{"type":"string"},"public_ip_address":{"type":"string"},"role":{"type":"string","enum":["master","worker","bastion"],"example":"master|worker|bastion"},"ssh_key_id":{"type":"string"},"ssh_user":{"type":"string"},"status":{"type":"string","enum":["pending","provisioning","ready","failed"],"example":"pending|provisioning|ready|failed"}}},"dto.CreateTaintRequest":{"type":"object","properties":{"effect":{"type":"string"},"key":{"type":"string"},"value":{"type":"string"}}},"dto.CredentialOut":{"type":"object","properties":{"account_id":{"type":"string"},"created_at":{"type":"string"},"id":{"type":"string"},"kind":{"type":"string"},"name":{"type":"string"},"provider":{"type":"string"},"region":{"type":"string"},"schema_version":{"type":"integer"},"scope":{"type":"object"},"scope_kind":{"type":"string"},"scope_version":{"type":"integer"},"updated_at":{"type":"string"}}},"dto.DomainResponse":{"type":"object","properties":{"created_at":{"type":"string"},"credential_id":{"type":"string"},"domain_name":{"type":"string"},"id":{"type":"string"},"last_error":{"type":"string"},"organization_id":{"type":"string"},"status":{"type":"string"},"updated_at":{"type":"string"},"zone_id":{"type":"string"}}},"dto.EnqueueRequest":{"type":"object","properties":{"payload":{"type":"object"},"queue":{"type":"string","example":"default"},"run_at":{"type":"string","example":"2025-11-05T08:00:00Z"},"type":{"type":"string","example":"email.send"}}},"dto.JWK":{"type":"object","properties":{"alg":{"type":"string","example":"RS256"},"e":{"type":"string","example":"AQAB"},"kid":{"type":"string","example":"7c6f1d0a-7a98-4e6a-9dbf-6b1af4b9f345"},"kty":{"type":"string","example":"RSA"},"n":{"type":"string"},"use":{"type":"string","example":"sig"},"x":{"type":"string"}}},"dto.JWKS":{"type":"object","properties":{"keys":{"type":"array","items":{"$ref":"#/definitions/dto.JWK"}}}},"dto.Job":{"type":"object","properties":{"attempts":{"type":"integer","example":0},"created_at":{"type":"string","example":"2025-11-04T09:30:00Z"},"id":{"type":"string","example":"01HF7SZK8Z8WG1M3J7S2Z8M2N6"},"last_error":{"type":"string","example":"error message"},"max_attempts":{"type":"integer","example":3},"payload":{},"queue":{"type":"string","example":"default"},"run_at":{"type":"string","example":"2025-11-04T09:30:00Z"},"status":{"enum":["queued|running|succeeded|failed|canceled|retrying|scheduled"],"allOf":[{"$ref":"#/definitions/dto.JobStatus"}],"example":"queued"},"type":{"type":"string","example":"email.send"},"updated_at":{"type":"string","example":"2025-11-04T09:30:00Z"}}},"dto.JobStatus":{"type":"string","enum":["queued","running","succeeded","failed","canceled","retrying","scheduled"],"x-enum-varnames":["StatusQueued","StatusRunning","StatusSucceeded","StatusFailed","StatusCanceled","StatusRetrying","StatusScheduled"]},"dto.LabelResponse":{"type":"object","properties":{"created_at":{"type":"string"},"id":{"type":"string"},"key":{"type":"string"},"organization_id":{"type":"string"},"updated_at":{"type":"string"},"value":{"type":"string"}}},"dto.LogoutRequest":{"type":"object","properties":{"refresh_token":{"type":"string","example":"m0l9o8rT3t0V8d3eFf..."}}},"dto.NodePoolResponse":{"type":"object","properties":{"annotations":{"type":"array","items":{"$ref":"#/definitions/dto.AnnotationResponse"}},"created_at":{"type":"string"},"id":{"type":"string"},"labels":{"type":"array","items":{"$ref":"#/definitions/dto.LabelResponse"}},"name":{"type":"string"},"organization_id":{"type":"string"},"role":{"type":"string","enum":["master","worker"]},"servers":{"type":"array","items":{"$ref":"#/definitions/dto.ServerResponse"}},"taints":{"type":"array","items":{"$ref":"#/definitions/dto.TaintResponse"}},"updated_at":{"type":"string"}}},"dto.PageJob":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/definitions/dto.Job"}},"page":{"type":"integer","example":1},"page_size":{"type":"integer","example":25},"total":{"type":"integer","example":120}}},"dto.QueueInfo":{"type":"object","properties":{"failed":{"type":"integer","example":5},"name":{"type":"string","example":"default"},"pending":{"type":"integer","example":42},"running":{"type":"integer","example":3},"scheduled":{"type":"integer","example":7}}},"dto.RecordSetResponse":{"type":"object","properties":{"created_at":{"type":"string"},"domain_id":{"type":"string"},"fingerprint":{"type":"string"},"id":{"type":"string"},"last_error":{"type":"string"},"name":{"type":"string"},"owner":{"type":"string"},"status":{"type":"string"},"ttl":{"type":"integer"},"type":{"type":"string"},"updated_at":{"type":"string"},"values":{"description":"[]string JSON","type":"object"}}},"dto.RefreshRequest":{"type":"object","properties":{"refresh_token":{"type":"string","example":"m0l9o8rT3t0V8d3eFf..."}}},"dto.ServerResponse":{"type":"object","properties":{"created_at":{"type":"string"},"hostname":{"type":"string"},"id":{"type":"string"},"organization_id":{"type":"string"},"private_ip_address":{"type":"string"},"public_ip_address":{"type":"string"},"role":{"type":"string","enum":["master","worker","bastion"],"example":"master|worker|bastion"},"ssh_key_id":{"type":"string"},"ssh_user":{"type":"string"},"status":{"type":"string","enum":["pending","provisioning","ready","failed"],"example":"pending|provisioning|ready|failed"},"updated_at":{"type":"string"}}},"dto.SshResponse":{"type":"object","properties":{"created_at":{"type":"string"},"fingerprint":{"type":"string"},"id":{"type":"string"},"name":{"type":"string"},"organization_id":{"type":"string"},"public_key":{"type":"string"},"updated_at":{"type":"string"}}},"dto.SshRevealResponse":{"type":"object","properties":{"created_at":{"type":"string"},"fingerprint":{"type":"string"},"id":{"type":"string"},"name":{"type":"string"},"organization_id":{"type":"string"},"private_key":{"type":"string"},"public_key":{"type":"string"},"updated_at":{"type":"string"}}},"dto.TaintResponse":{"type":"object","properties":{"created_at":{"type":"string"},"effect":{"type":"string"},"id":{"type":"string"},"key":{"type":"string"},"organization_id":{"type":"string"},"updated_at":{"type":"string"},"value":{"type":"string"}}},"dto.TokenPair":{"type":"object","properties":{"access_token":{"type":"string","example":"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6Ij..."},"expires_in":{"type":"integer","example":3600},"refresh_token":{"type":"string","example":"m0l9o8rT3t0V8d3eFf...."},"token_type":{"type":"string","example":"Bearer"}}},"dto.UpdateAnnotationRequest":{"type":"object","properties":{"key":{"type":"string"},"value":{"type":"string"}}},"dto.UpdateCredentialRequest":{"type":"object","properties":{"account_id":{"type":"string"},"name":{"type":"string"},"region":{"type":"string"},"scope":{"type":"object"},"scope_kind":{"type":"string"},"scope_version":{"type":"integer"},"secret":{"description":"set if rotating","type":"object"}}},"dto.UpdateDomainRequest":{"type":"object","properties":{"credential_id":{"type":"string"},"domain_name":{"type":"string"},"status":{"type":"string","enum":["pending","provisioning","ready","failed"]},"zone_id":{"type":"string","maxLength":128}}},"dto.UpdateLabelRequest":{"type":"object","properties":{"key":{"type":"string"},"value":{"type":"string"}}},"dto.UpdateNodePoolRequest":{"type":"object","properties":{"name":{"type":"string"},"role":{"type":"string","enum":["master","worker"]}}},"dto.UpdateRecordSetRequest":{"type":"object","properties":{"name":{"description":"Any change flips status back to pending (worker will UPSERT)","type":"string","maxLength":253},"status":{"type":"string","enum":["pending","provisioning","ready","failed"]},"ttl":{"type":"integer","maximum":86400,"minimum":1},"type":{"type":"string"},"values":{"type":"array","items":{"type":"string"}}}},"dto.UpdateServerRequest":{"type":"object","properties":{"hostname":{"type":"string"},"private_ip_address":{"type":"string"},"public_ip_address":{"type":"string"},"role":{"type":"string","enum":["master","worker","bastion"],"example":"master|worker|bastion"},"ssh_key_id":{"type":"string"},"ssh_user":{"type":"string"},"status":{"type":"string","enum":["pending","provisioning","ready","failed"],"example":"pending|provisioning|ready|failed"}}},"dto.UpdateTaintRequest":{"type":"object","properties":{"effect":{"type":"string"},"key":{"type":"string"},"value":{"type":"string"}}},"handlers.HealthStatus":{"type":"object","properties":{"status":{"type":"string","example":"ok"}}},"handlers.VersionResponse":{"type":"object","properties":{"built":{"type":"string","example":"2025-11-08T12:34:56Z"},"builtBy":{"type":"string","example":"ci"},"commit":{"type":"string","example":"a1b2c3d"},"commitTime":{"type":"string","example":"2025-11-08T12:31:00Z"},"go":{"type":"string","example":"go1.23.3"},"goArch":{"type":"string","example":"amd64"},"goOS":{"type":"string","example":"linux"},"modified":{"type":"boolean","example":false},"revision":{"type":"string","example":"a1b2c3d4e5f6abcdef"},"vcs":{"type":"string","example":"git"},"version":{"type":"string","example":"1.4.2"}}},"handlers.createUserKeyRequest":{"type":"object","properties":{"expires_in_hours":{"description":"optional TTL","type":"integer"},"name":{"type":"string"}}},"handlers.meResponse":{"type":"object","properties":{"avatar_url":{"type":"string"},"created_at":{"type":"string","format":"date-time"},"display_name":{"type":"string"},"emails":{"type":"array","items":{"$ref":"#/definitions/models.UserEmail"}},"id":{"description":"example: 3fa85f64-5717-4562-b3fc-2c963f66afa6","type":"string","format":"uuid"},"is_admin":{"type":"boolean"},"is_disabled":{"type":"boolean"},"organizations":{"type":"array","items":{"$ref":"#/definitions/models.Organization"}},"primary_email":{"type":"string"},"updated_at":{"type":"string","format":"date-time"}}},"handlers.memberOut":{"type":"object","properties":{"email":{"type":"string"},"role":{"description":"owner/admin/member","type":"string"},"user_id":{"type":"string","format":"uuid"}}},"handlers.memberUpsertReq":{"type":"object","properties":{"role":{"type":"string","example":"member"},"user_id":{"type":"string","format":"uuid"}}},"handlers.orgCreateReq":{"type":"object","properties":{"domain":{"type":"string","example":"acme.com"},"name":{"type":"string","example":"Acme Corp"}}},"handlers.orgKeyCreateReq":{"type":"object","properties":{"expires_in_hours":{"type":"integer","example":720},"name":{"type":"string","example":"automation-bot"}}},"handlers.orgKeyCreateResp":{"type":"object","properties":{"created_at":{"type":"string"},"expires_at":{"type":"string"},"id":{"type":"string"},"name":{"type":"string"},"org_key":{"description":"shown once:","type":"string"},"org_secret":{"description":"shown once:","type":"string"},"scope":{"description":"\"org\"","type":"string"}}},"handlers.orgUpdateReq":{"type":"object","properties":{"domain":{"type":"string"},"name":{"type":"string"}}},"handlers.updateMeRequest":{"type":"object","properties":{"display_name":{"type":"string"}}},"handlers.userAPIKeyOut":{"type":"object","properties":{"created_at":{"type":"string"},"expires_at":{"type":"string"},"id":{"type":"string","format":"uuid"},"last_used_at":{"type":"string"},"name":{"type":"string"},"plain":{"description":"Shown only on create:","type":"string"},"scope":{"description":"\"user\"","type":"string"}}},"models.APIKey":{"type":"object","properties":{"created_at":{"type":"string","format":"date-time"},"expires_at":{"type":"string","format":"date-time"},"id":{"type":"string","format":"uuid"},"last_used_at":{"type":"string","format":"date-time"},"name":{"type":"string"},"org_id":{"type":"string","format":"uuid"},"prefix":{"type":"string"},"revoked":{"type":"boolean"},"scope":{"type":"string"},"updated_at":{"type":"string","format":"date-time"},"user_id":{"type":"string","format":"uuid"}}},"models.Organization":{"type":"object","properties":{"created_at":{"type":"string","format":"date-time"},"domain":{"type":"string"},"id":{"description":"example: 3fa85f64-5717-4562-b3fc-2c963f66afa6","type":"string","format":"uuid"},"name":{"type":"string"},"updated_at":{"type":"string","format":"date-time"}}},"models.User":{"type":"object","properties":{"avatar_url":{"type":"string"},"created_at":{"type":"string","format":"date-time"},"display_name":{"type":"string"},"id":{"description":"example: 3fa85f64-5717-4562-b3fc-2c963f66afa6","type":"string","format":"uuid"},"is_admin":{"type":"boolean"},"is_disabled":{"type":"boolean"},"primary_email":{"type":"string"},"updated_at":{"type":"string","format":"date-time"}}},"models.UserEmail":{"type":"object","properties":{"created_at":{"type":"string","format":"date-time"},"email":{"type":"string"},"id":{"description":"example: 3fa85f64-5717-4562-b3fc-2c963f66afa6","type":"string","format":"uuid"},"is_primary":{"type":"boolean"},"is_verified":{"type":"boolean"},"updated_at":{"type":"string","format":"date-time"},"user":{"$ref":"#/definitions/models.User"},"user_id":{"type":"string","format":"uuid"}}},"utils.ErrorResponse":{"type":"object","properties":{"code":{"description":"A machine-readable error code, e.g. \"validation_error\"\nexample: validation_error","type":"string"},"message":{"description":"Human-readable message\nexample: slug is required","type":"string"}}}},"securityDefinitions":{"ApiKeyAuth":{"description":"User API key","type":"apiKey","name":"X-API-KEY","in":"header"},"BearerAuth":{"description":"Bearer token authentication","type":"apiKey","name":"Authorization","in":"header"},"OrgKeyAuth":{"description":"Org-level key/secret authentication","type":"apiKey","name":"X-ORG-KEY","in":"header"},"OrgSecretAuth":{"description":"Org-level secret","type":"apiKey","name":"X-ORG-SECRET","in":"header"}}} \ No newline at end of file +{ + "components": {"schemas":{"dto.AnnotationResponse":{"properties":{"created_at":{"type":"string"},"id":{"type":"string"},"key":{"type":"string"},"organization_id":{"type":"string"},"updated_at":{"type":"string"},"value":{"type":"string"}},"type":"object"},"dto.AttachAnnotationsRequest":{"properties":{"annotation_ids":{"items":{"type":"string"},"type":"array","uniqueItems":false}},"type":"object"},"dto.AttachBastionRequest":{"properties":{"server_id":{"type":"string"}},"type":"object"},"dto.AttachCaptainDomainRequest":{"properties":{"domain_id":{"type":"string"}},"type":"object"},"dto.AttachLabelsRequest":{"properties":{"label_ids":{"items":{"type":"string"},"type":"array","uniqueItems":false}},"type":"object"},"dto.AttachLoadBalancerRequest":{"properties":{"load_balancer_id":{"type":"string"}},"type":"object"},"dto.AttachRecordSetRequest":{"properties":{"record_set_id":{"type":"string"}},"type":"object"},"dto.AttachServersRequest":{"properties":{"server_ids":{"items":{"type":"string"},"type":"array","uniqueItems":false}},"type":"object"},"dto.AttachTaintsRequest":{"properties":{"taint_ids":{"items":{"type":"string"},"type":"array","uniqueItems":false}},"type":"object"},"dto.AuthStartResponse":{"properties":{"auth_url":{"example":"https://accounts.google.com/o/oauth2/v2/auth?client_id=...","type":"string"}},"type":"object"},"dto.ClusterResponse":{"properties":{"apps_load_balancer":{"$ref":"#/components/schemas/dto.LoadBalancerResponse"},"bastion_server":{"$ref":"#/components/schemas/dto.ServerResponse"},"captain_domain":{"$ref":"#/components/schemas/dto.DomainResponse"},"certificate_key":{"type":"string"},"control_plane_record_set":{"$ref":"#/components/schemas/dto.RecordSetResponse"},"created_at":{"type":"string"},"glueops_load_balancer":{"$ref":"#/components/schemas/dto.LoadBalancerResponse"},"id":{"type":"string"},"last_error":{"type":"string"},"name":{"type":"string"},"node_pools":{"items":{"$ref":"#/components/schemas/dto.NodePoolResponse"},"type":"array","uniqueItems":false},"provider":{"type":"string"},"random_token":{"type":"string"},"region":{"type":"string"},"status":{"type":"string"},"updated_at":{"type":"string"}},"type":"object"},"dto.CreateAnnotationRequest":{"properties":{"key":{"type":"string"},"value":{"type":"string"}},"type":"object"},"dto.CreateClusterRequest":{"properties":{"name":{"type":"string"},"provider":{"type":"string"},"region":{"type":"string"}},"type":"object"},"dto.CreateCredentialRequest":{"properties":{"account_id":{"maxLength":32,"type":"string"},"kind":{"description":"aws_access_key, api_token, basic_auth, oauth2","type":"string"},"name":{"description":"human label","maxLength":100,"type":"string"},"provider":{"enum":["aws","cloudflare","hetzner","digitalocean","generic"],"type":"string"},"region":{"maxLength":32,"type":"string"},"schema_version":{"description":"secret schema version","minimum":1,"type":"integer"},"scope":{"description":"{\"service\":\"route53\"} or {\"arn\":\"...\"}","type":"object"},"scope_kind":{"enum":["provider","service","resource"],"type":"string"},"scope_version":{"description":"scope schema version","minimum":1,"type":"integer"},"secret":{"description":"encrypted later","type":"object"}},"required":["kind","provider","schema_version","scope","scope_kind","scope_version","secret"],"type":"object"},"dto.CreateDomainRequest":{"properties":{"credential_id":{"type":"string"},"domain_name":{"type":"string"},"zone_id":{"maxLength":128,"type":"string"}},"required":["credential_id","domain_name"],"type":"object"},"dto.CreateLabelRequest":{"properties":{"key":{"type":"string"},"value":{"type":"string"}},"type":"object"},"dto.CreateLoadBalancerRequest":{"properties":{"kind":{"enum":["glueops|public"],"example":"public","type":"string"},"name":{"example":"glueops","type":"string"},"private_ip_address":{"example":"192.168.0.2","type":"string"},"public_ip_address":{"example":"8.8.8.8","type":"string"}},"type":"object"},"dto.CreateNodePoolRequest":{"properties":{"name":{"type":"string"},"role":{"enum":["master","worker"],"type":"string"}},"type":"object"},"dto.CreateRecordSetRequest":{"properties":{"name":{"description":"Name relative to domain (\"endpoint\") OR FQDN (\"endpoint.example.com\").\nServer normalizes to relative.","maxLength":253,"type":"string"},"ttl":{"maximum":86400,"minimum":1,"type":"integer"},"type":{"type":"string"},"values":{"items":{"type":"string"},"type":"array","uniqueItems":false}},"required":["name","type"],"type":"object"},"dto.CreateSSHRequest":{"properties":{"bits":{"description":"Only for RSA","type":"integer"},"comment":{"example":"deploy@autoglue","type":"string"},"name":{"type":"string"},"type":{"description":"\"rsa\" (default) or \"ed25519\"","type":"string"}},"type":"object"},"dto.CreateServerRequest":{"properties":{"hostname":{"type":"string"},"private_ip_address":{"type":"string"},"public_ip_address":{"type":"string"},"role":{"enum":["master","worker","bastion"],"example":"master|worker|bastion","type":"string"},"ssh_key_id":{"type":"string"},"ssh_user":{"type":"string"},"status":{"enum":["pending","provisioning","ready","failed"],"example":"pending|provisioning|ready|failed","type":"string"}},"type":"object"},"dto.CreateTaintRequest":{"properties":{"effect":{"type":"string"},"key":{"type":"string"},"value":{"type":"string"}},"type":"object"},"dto.CredentialOut":{"properties":{"account_id":{"type":"string"},"created_at":{"type":"string"},"id":{"type":"string"},"kind":{"type":"string"},"name":{"type":"string"},"provider":{"type":"string"},"region":{"type":"string"},"schema_version":{"type":"integer"},"scope":{"type":"object"},"scope_kind":{"type":"string"},"scope_version":{"type":"integer"},"updated_at":{"type":"string"}},"type":"object"},"dto.DomainResponse":{"properties":{"created_at":{"type":"string"},"credential_id":{"type":"string"},"domain_name":{"type":"string"},"id":{"type":"string"},"last_error":{"type":"string"},"organization_id":{"type":"string"},"status":{"type":"string"},"updated_at":{"type":"string"},"zone_id":{"type":"string"}},"type":"object"},"dto.EnqueueRequest":{"properties":{"payload":{"type":"object"},"queue":{"example":"default","type":"string"},"run_at":{"example":"2025-11-05T08:00:00Z","type":"string"},"type":{"example":"email.send","type":"string"}},"type":"object"},"dto.JWK":{"properties":{"alg":{"example":"RS256","type":"string"},"e":{"example":"AQAB","type":"string"},"kid":{"example":"7c6f1d0a-7a98-4e6a-9dbf-6b1af4b9f345","type":"string"},"kty":{"example":"RSA","type":"string"},"n":{"type":"string"},"use":{"example":"sig","type":"string"},"x":{"type":"string"}},"type":"object"},"dto.JWKS":{"properties":{"keys":{"items":{"$ref":"#/components/schemas/dto.JWK"},"type":"array","uniqueItems":false}},"type":"object"},"dto.Job":{"properties":{"attempts":{"example":0,"type":"integer"},"created_at":{"example":"2025-11-04T09:30:00Z","type":"string"},"id":{"example":"01HF7SZK8Z8WG1M3J7S2Z8M2N6","type":"string"},"last_error":{"example":"error message","type":"string"},"max_attempts":{"example":3,"type":"integer"},"payload":{},"queue":{"example":"default","type":"string"},"run_at":{"example":"2025-11-04T09:30:00Z","type":"string"},"status":{"$ref":"#/components/schemas/dto.JobStatus"},"type":{"example":"email.send","type":"string"},"updated_at":{"example":"2025-11-04T09:30:00Z","type":"string"}},"type":"object"},"dto.JobStatus":{"enum":["queued|running|succeeded|failed|canceled|retrying|scheduled"],"example":"queued","type":"string","x-enum-varnames":["StatusQueued","StatusRunning","StatusSucceeded","StatusFailed","StatusCanceled","StatusRetrying","StatusScheduled"]},"dto.LabelResponse":{"properties":{"created_at":{"type":"string"},"id":{"type":"string"},"key":{"type":"string"},"organization_id":{"type":"string"},"updated_at":{"type":"string"},"value":{"type":"string"}},"type":"object"},"dto.LoadBalancerResponse":{"properties":{"created_at":{"type":"string"},"id":{"type":"string"},"kind":{"enum":["glueops|public"],"type":"string"},"name":{"type":"string"},"organization_id":{"type":"string"},"private_ip_address":{"type":"string"},"public_ip_address":{"type":"string"},"updated_at":{"type":"string"}},"type":"object"},"dto.LogoutRequest":{"properties":{"refresh_token":{"example":"m0l9o8rT3t0V8d3eFf...","type":"string"}},"type":"object"},"dto.NodePoolResponse":{"properties":{"annotations":{"items":{"$ref":"#/components/schemas/dto.AnnotationResponse"},"type":"array","uniqueItems":false},"created_at":{"type":"string"},"id":{"type":"string"},"labels":{"items":{"$ref":"#/components/schemas/dto.LabelResponse"},"type":"array","uniqueItems":false},"name":{"type":"string"},"organization_id":{"type":"string"},"role":{"enum":["master","worker"],"type":"string"},"servers":{"items":{"$ref":"#/components/schemas/dto.ServerResponse"},"type":"array","uniqueItems":false},"taints":{"items":{"$ref":"#/components/schemas/dto.TaintResponse"},"type":"array","uniqueItems":false},"updated_at":{"type":"string"}},"type":"object"},"dto.PageJob":{"properties":{"items":{"items":{"$ref":"#/components/schemas/dto.Job"},"type":"array","uniqueItems":false},"page":{"example":1,"type":"integer"},"page_size":{"example":25,"type":"integer"},"total":{"example":120,"type":"integer"}},"type":"object"},"dto.QueueInfo":{"properties":{"failed":{"example":5,"type":"integer"},"name":{"example":"default","type":"string"},"pending":{"example":42,"type":"integer"},"running":{"example":3,"type":"integer"},"scheduled":{"example":7,"type":"integer"}},"type":"object"},"dto.RecordSetResponse":{"properties":{"created_at":{"type":"string"},"domain_id":{"type":"string"},"fingerprint":{"type":"string"},"id":{"type":"string"},"last_error":{"type":"string"},"name":{"type":"string"},"owner":{"type":"string"},"status":{"type":"string"},"ttl":{"type":"integer"},"type":{"type":"string"},"updated_at":{"type":"string"},"values":{"description":"[]string JSON","type":"object"}},"type":"object"},"dto.RefreshRequest":{"properties":{"refresh_token":{"example":"m0l9o8rT3t0V8d3eFf...","type":"string"}},"type":"object"},"dto.ServerResponse":{"properties":{"created_at":{"type":"string"},"hostname":{"type":"string"},"id":{"type":"string"},"organization_id":{"type":"string"},"private_ip_address":{"type":"string"},"public_ip_address":{"type":"string"},"role":{"enum":["master","worker","bastion"],"example":"master|worker|bastion","type":"string"},"ssh_key_id":{"type":"string"},"ssh_user":{"type":"string"},"status":{"enum":["pending","provisioning","ready","failed"],"example":"pending|provisioning|ready|failed","type":"string"},"updated_at":{"type":"string"}},"type":"object"},"dto.SetKubeconfigRequest":{"properties":{"kubeconfig":{"type":"string"}},"type":"object"},"dto.SshResponse":{"properties":{"created_at":{"type":"string"},"fingerprint":{"type":"string"},"id":{"type":"string"},"name":{"type":"string"},"organization_id":{"type":"string"},"public_key":{"type":"string"},"updated_at":{"type":"string"}},"type":"object"},"dto.SshRevealResponse":{"properties":{"created_at":{"type":"string"},"fingerprint":{"type":"string"},"id":{"type":"string"},"name":{"type":"string"},"organization_id":{"type":"string"},"private_key":{"type":"string"},"public_key":{"type":"string"},"updated_at":{"type":"string"}},"type":"object"},"dto.TaintResponse":{"properties":{"created_at":{"type":"string"},"effect":{"type":"string"},"id":{"type":"string"},"key":{"type":"string"},"organization_id":{"type":"string"},"updated_at":{"type":"string"},"value":{"type":"string"}},"type":"object"},"dto.TokenPair":{"properties":{"access_token":{"example":"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6Ij...","type":"string"},"expires_in":{"example":3600,"type":"integer"},"refresh_token":{"example":"m0l9o8rT3t0V8d3eFf....","type":"string"},"token_type":{"example":"Bearer","type":"string"}},"type":"object"},"dto.UpdateAnnotationRequest":{"properties":{"key":{"type":"string"},"value":{"type":"string"}},"type":"object"},"dto.UpdateClusterRequest":{"properties":{"name":{"type":"string"},"provider":{"type":"string"},"region":{"type":"string"}},"type":"object"},"dto.UpdateCredentialRequest":{"properties":{"account_id":{"type":"string"},"name":{"type":"string"},"region":{"type":"string"},"scope":{"type":"object"},"scope_kind":{"type":"string"},"scope_version":{"type":"integer"},"secret":{"description":"set if rotating","type":"object"}},"type":"object"},"dto.UpdateDomainRequest":{"properties":{"credential_id":{"type":"string"},"domain_name":{"type":"string"},"status":{"enum":["pending","provisioning","ready","failed"],"type":"string"},"zone_id":{"maxLength":128,"type":"string"}},"type":"object"},"dto.UpdateLabelRequest":{"properties":{"key":{"type":"string"},"value":{"type":"string"}},"type":"object"},"dto.UpdateLoadBalancerRequest":{"properties":{"kind":{"enum":["glueops|public"],"example":"public","type":"string"},"name":{"example":"glue","type":"string"},"private_ip_address":{"example":"192.168.0.2","type":"string"},"public_ip_address":{"example":"8.8.8.8","type":"string"}},"type":"object"},"dto.UpdateNodePoolRequest":{"properties":{"name":{"type":"string"},"role":{"enum":["master","worker"],"type":"string"}},"type":"object"},"dto.UpdateRecordSetRequest":{"properties":{"name":{"description":"Any change flips status back to pending (worker will UPSERT)","maxLength":253,"type":"string"},"status":{"enum":["pending","provisioning","ready","failed"],"type":"string"},"ttl":{"maximum":86400,"minimum":1,"type":"integer"},"type":{"type":"string"},"values":{"items":{"type":"string"},"type":"array","uniqueItems":false}},"type":"object"},"dto.UpdateServerRequest":{"properties":{"hostname":{"type":"string"},"private_ip_address":{"type":"string"},"public_ip_address":{"type":"string"},"role":{"enum":["master","worker","bastion"],"example":"master|worker|bastion","type":"string"},"ssh_key_id":{"type":"string"},"ssh_user":{"type":"string"},"status":{"enum":["pending","provisioning","ready","failed"],"example":"pending|provisioning|ready|failed","type":"string"}},"type":"object"},"dto.UpdateTaintRequest":{"properties":{"effect":{"type":"string"},"key":{"type":"string"},"value":{"type":"string"}},"type":"object"},"handlers.HealthStatus":{"properties":{"status":{"example":"ok","type":"string"}},"type":"object"},"handlers.VersionResponse":{"properties":{"built":{"example":"2025-11-08T12:34:56Z","type":"string"},"builtBy":{"example":"ci","type":"string"},"commit":{"example":"a1b2c3d","type":"string"},"commitTime":{"example":"2025-11-08T12:31:00Z","type":"string"},"go":{"example":"go1.23.3","type":"string"},"goArch":{"example":"amd64","type":"string"},"goOS":{"example":"linux","type":"string"},"modified":{"example":false,"type":"boolean"},"revision":{"example":"a1b2c3d4e5f6abcdef","type":"string"},"vcs":{"example":"git","type":"string"},"version":{"example":"1.4.2","type":"string"}},"type":"object"},"handlers.createUserKeyRequest":{"properties":{"expires_in_hours":{"description":"optional TTL","type":"integer"},"name":{"type":"string"}},"type":"object"},"handlers.meResponse":{"properties":{"avatar_url":{"type":"string"},"created_at":{"format":"date-time","type":"string"},"display_name":{"type":"string"},"emails":{"items":{"$ref":"#/components/schemas/models.UserEmail"},"type":"array","uniqueItems":false},"id":{"description":"example: 3fa85f64-5717-4562-b3fc-2c963f66afa6","format":"uuid","type":"string"},"is_admin":{"type":"boolean"},"is_disabled":{"type":"boolean"},"organizations":{"items":{"$ref":"#/components/schemas/models.Organization"},"type":"array","uniqueItems":false},"primary_email":{"type":"string"},"updated_at":{"format":"date-time","type":"string"}},"type":"object"},"handlers.memberOut":{"properties":{"email":{"type":"string"},"role":{"description":"owner/admin/member","type":"string"},"user_id":{"format":"uuid","type":"string"}},"type":"object"},"handlers.memberUpsertReq":{"properties":{"role":{"example":"member","type":"string"},"user_id":{"format":"uuid","type":"string"}},"type":"object"},"handlers.orgCreateReq":{"properties":{"domain":{"example":"acme.com","type":"string"},"name":{"example":"Acme Corp","type":"string"}},"type":"object"},"handlers.orgKeyCreateReq":{"properties":{"expires_in_hours":{"example":720,"type":"integer"},"name":{"example":"automation-bot","type":"string"}},"type":"object"},"handlers.orgKeyCreateResp":{"properties":{"created_at":{"type":"string"},"expires_at":{"type":"string"},"id":{"type":"string"},"name":{"type":"string"},"org_key":{"description":"shown once:","type":"string"},"org_secret":{"description":"shown once:","type":"string"},"scope":{"description":"\"org\"","type":"string"}},"type":"object"},"handlers.orgUpdateReq":{"properties":{"domain":{"type":"string"},"name":{"type":"string"}},"type":"object"},"handlers.updateMeRequest":{"properties":{"display_name":{"type":"string"}},"type":"object"},"handlers.userAPIKeyOut":{"properties":{"created_at":{"type":"string"},"expires_at":{"type":"string"},"id":{"format":"uuid","type":"string"},"last_used_at":{"type":"string"},"name":{"type":"string"},"plain":{"description":"Shown only on create:","type":"string"},"scope":{"description":"\"user\"","type":"string"}},"type":"object"},"models.APIKey":{"properties":{"created_at":{"format":"date-time","type":"string"},"expires_at":{"format":"date-time","type":"string"},"id":{"format":"uuid","type":"string"},"last_used_at":{"format":"date-time","type":"string"},"name":{"type":"string"},"org_id":{"format":"uuid","type":"string"},"prefix":{"type":"string"},"revoked":{"type":"boolean"},"scope":{"type":"string"},"updated_at":{"format":"date-time","type":"string"},"user_id":{"format":"uuid","type":"string"}},"type":"object"},"models.Organization":{"properties":{"created_at":{"format":"date-time","type":"string"},"domain":{"type":"string"},"id":{"description":"example: 3fa85f64-5717-4562-b3fc-2c963f66afa6","format":"uuid","type":"string"},"name":{"type":"string"},"updated_at":{"format":"date-time","type":"string"}},"type":"object"},"models.User":{"properties":{"avatar_url":{"type":"string"},"created_at":{"format":"date-time","type":"string"},"display_name":{"type":"string"},"id":{"description":"example: 3fa85f64-5717-4562-b3fc-2c963f66afa6","format":"uuid","type":"string"},"is_admin":{"type":"boolean"},"is_disabled":{"type":"boolean"},"primary_email":{"type":"string"},"updated_at":{"format":"date-time","type":"string"}},"type":"object"},"models.UserEmail":{"properties":{"created_at":{"format":"date-time","type":"string"},"email":{"type":"string"},"id":{"description":"example: 3fa85f64-5717-4562-b3fc-2c963f66afa6","format":"uuid","type":"string"},"is_primary":{"type":"boolean"},"is_verified":{"type":"boolean"},"updated_at":{"format":"date-time","type":"string"},"user":{"$ref":"#/components/schemas/models.User"},"user_id":{"format":"uuid","type":"string"}},"type":"object"},"utils.ErrorResponse":{"properties":{"code":{"description":"A machine-readable error code, e.g. \"validation_error\"\nexample: validation_error","type":"string"},"message":{"description":"Human-readable message\nexample: slug is required","type":"string"}},"type":"object"}},"securitySchemes":{"":{"description":"Org-level secret","in":"header","name":"X-ORG-SECRET","type":"apiKey"}}}, + "info": {"contact":{"name":"GlueOps"},"description":"API for managing K3s clusters across cloud providers","title":"AutoGlue API","version":"1.0"}, + "externalDocs": {"description":"","url":""}, + "paths": {"/.well-known/jwks.json":{"get":{"description":"Returns the JSON Web Key Set for token verification","operationId":"getJWKS","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.JWKS"}}},"description":"OK"}},"summary":"Get JWKS","tags":["Auth"]}},"/admin/archer/jobs":{"get":{"description":"Paginated background jobs with optional filters. Search `q` may match id, type, error, payload (implementation-dependent).","operationId":"AdminListArcherJobs","parameters":[{"description":"Filter by status","in":"query","name":"status","schema":{"enum":["queued","running","succeeded","failed","canceled","retrying","scheduled"],"type":"string"}},{"description":"Filter by queue name / worker name","in":"query","name":"queue","schema":{"type":"string"}},{"description":"Free-text search","in":"query","name":"q","schema":{"type":"string"}},{"description":"Page number","in":"query","name":"page","schema":{"default":1,"type":"integer"}},{"description":"Items per page","in":"query","name":"page_size","schema":{"default":25,"maximum":100,"minimum":1,"type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.PageJob"}}},"description":"OK"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"forbidden"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"internal error"}},"security":[{"BearerAuth":[]}],"summary":"List Archer jobs (admin)","tags":["ArcherAdmin"]},"post":{"description":"Create a job immediately or schedule it for the future via `run_at`.","operationId":"AdminEnqueueArcherJob","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.EnqueueRequest"}}},"description":"Job parameters","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.Job"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"invalid json or missing fields"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"forbidden"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"internal error"}},"security":[{"BearerAuth":[]}],"summary":"Enqueue a new Archer job (admin)","tags":["ArcherAdmin"]}},"/admin/archer/jobs/{id}/cancel":{"post":{"description":"Set job status to canceled if cancellable. For running jobs, this only affects future picks; wire to Archer if you need active kill.","operationId":"AdminCancelArcherJob","parameters":[{"description":"Job ID","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.Job"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"invalid job or not cancellable"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"forbidden"},"404":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"not found"}},"security":[{"BearerAuth":[]}],"summary":"Cancel an Archer job (admin)","tags":["ArcherAdmin"]}},"/admin/archer/jobs/{id}/retry":{"post":{"description":"Marks the job retriable (DB flip). Swap this for an Archer admin call if you expose one.","operationId":"AdminRetryArcherJob","parameters":[{"description":"Job ID","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.Job"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"invalid job or not eligible"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"forbidden"},"404":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"not found"}},"security":[{"BearerAuth":[]}],"summary":"Retry a failed/canceled Archer job (admin)","tags":["ArcherAdmin"]}},"/admin/archer/queues":{"get":{"description":"Summary metrics per queue (pending, running, failed, scheduled).","operationId":"AdminListArcherQueues","responses":{"200":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/dto.QueueInfo"},"type":"array"}}},"description":"OK"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"forbidden"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"internal error"}},"security":[{"BearerAuth":[]}],"summary":"List Archer queues (admin)","tags":["ArcherAdmin"]}},"/annotations":{"get":{"description":"Returns annotations for the organization in X-Org-ID. Filters: `key`, `value`, and `q` (key contains). Add `include=node_pools` to include linked node pools.","operationId":"ListAnnotations","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"Exact key","in":"query","name":"key","schema":{"type":"string"}},{"description":"Exact value","in":"query","name":"value","schema":{"type":"string"}},{"description":"key contains (case-insensitive)","in":"query","name":"q","schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/dto.AnnotationResponse"},"type":"array"}}},"description":"OK"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"failed to list annotations"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"List annotations (org scoped)","tags":["Annotations"]},"post":{"description":"Creates an annotation.","operationId":"CreateAnnotation","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.CreateAnnotationRequest"}}},"description":"Annotation payload","required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.AnnotationResponse"}}},"description":"Created"},"400":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"invalid json / missing fields"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"create failed"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"Create annotation (org scoped)","tags":["Annotations"]}},"/annotations/{id}":{"delete":{"description":"Permanently deletes the annotation.","operationId":"DeleteAnnotation","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"Annotation ID (UUID)","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"No Content"},"400":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"invalid id"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"delete failed"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"Delete annotation (org scoped)","tags":["Annotations"]},"get":{"description":"Returns one annotation. Add `include=node_pools` to include node pools.","operationId":"GetAnnotation","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"Annotation ID (UUID)","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.AnnotationResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"invalid id"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"404":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"not found"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"fetch failed"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"Get annotation by ID (org scoped)","tags":["Annotations"]},"patch":{"description":"Partially update annotation fields.","operationId":"UpdateAnnotation","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"Annotation ID (UUID)","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.UpdateAnnotationRequest"}}},"description":"Fields to update","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.AnnotationResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"invalid id / invalid json"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"404":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"not found"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"update failed"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"Update annotation (org scoped)","tags":["Annotations"]}},"/auth/logout":{"post":{"operationId":"Logout","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.LogoutRequest"}}},"description":"Refresh token","required":true},"responses":{"204":{"description":"No Content"}},"summary":"Revoke refresh token family (logout everywhere)","tags":["Auth"]}},"/auth/refresh":{"post":{"operationId":"Refresh","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.RefreshRequest"}}},"description":"Refresh token","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.TokenPair"}}},"description":"OK"}},"summary":"Rotate refresh token","tags":["Auth"]}},"/auth/{provider}/callback":{"get":{"operationId":"AuthCallback","parameters":[{"description":"google|github","in":"path","name":"provider","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.TokenPair"}}},"description":"OK"}},"summary":"Handle social login callback","tags":["Auth"]}},"/auth/{provider}/start":{"post":{"description":"Returns provider authorization URL for the frontend to redirect","operationId":"AuthStart","parameters":[{"description":"google|github","in":"path","name":"provider","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.AuthStartResponse"}}},"description":"OK"}},"summary":"Begin social login","tags":["Auth"]}},"/clusters":{"get":{"description":"Returns clusters for the organization in X-Org-ID. Filter by `q` (name contains).","operationId":"ListClusters","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"Name contains (case-insensitive)","in":"query","name":"q","schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/dto.ClusterResponse"},"type":"array"}}},"description":"OK"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"failed to list clusters"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"List clusters (org scoped)","tags":["Clusters"]},"post":{"description":"Creates a cluster. Status is managed by the system and starts as `pre_pending` for validation.","operationId":"CreateCluster","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.CreateClusterRequest"}}},"description":"payload","required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.ClusterResponse"}}},"description":"Created"},"400":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"invalid json"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"create failed"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"Create cluster (org scoped)","tags":["Clusters"]}},"/clusters/{clusterID}":{"delete":{"description":"Deletes the cluster. Related resources are cleaned up via DB constraints (e.g. CASCADE).","operationId":"DeleteCluster","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"Cluster ID","in":"path","name":"clusterID","required":true,"schema":{"type":"string"}}],"responses":{"204":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"deleted"},"400":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"bad request"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"404":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"cluster not found"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"db error"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"Delete a cluster (org scoped)","tags":["Clusters"]},"get":{"description":"Returns a cluster with all related resources (domain, record set, load balancers, bastion, node pools).","operationId":"GetCluster","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"Cluster ID","in":"path","name":"clusterID","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.ClusterResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"bad request"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"404":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"cluster not found"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"db error"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"Get a single cluster by ID (org scoped)","tags":["Clusters"]},"patch":{"description":"Updates the cluster name, provider, and/or region. Status is managed by the system.","operationId":"UpdateCluster","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"Cluster ID","in":"path","name":"clusterID","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.UpdateClusterRequest"}}},"description":"payload","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.ClusterResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"bad request"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"404":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"cluster not found"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"db error"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"Update basic cluster details (org scoped)","tags":["Clusters"]}},"/clusters/{clusterID}/apps-load-balancer":{"delete":{"description":"Clears apps_load_balancer_id on the cluster.","operationId":"DetachAppsLoadBalancer","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"Cluster ID","in":"path","name":"clusterID","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.ClusterResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"bad request"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"404":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"cluster not found"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"db error"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"Detach the apps load balancer from a cluster","tags":["Clusters"]},"post":{"description":"Sets apps_load_balancer_id on the cluster.","operationId":"AttachAppsLoadBalancer","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"Cluster ID","in":"path","name":"clusterID","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.AttachLoadBalancerRequest"}}},"description":"payload","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.ClusterResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"bad request"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"404":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"cluster or load balancer not found"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"db error"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"Attach an apps load balancer to a cluster","tags":["Clusters"]}},"/clusters/{clusterID}/bastion":{"delete":{"description":"Clears bastion_server_id on the cluster.","operationId":"DetachBastionServer","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"Cluster ID","in":"path","name":"clusterID","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.ClusterResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"bad request"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"404":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"cluster not found"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"db error"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"Detach the bastion server from a cluster","tags":["Clusters"]},"post":{"description":"Sets bastion_server_id on the cluster.","operationId":"AttachBastionServer","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"Cluster ID","in":"path","name":"clusterID","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.AttachBastionRequest"}}},"description":"payload","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.ClusterResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"bad request"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"404":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"cluster or server not found"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"db error"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"Attach a bastion server to a cluster","tags":["Clusters"]}},"/clusters/{clusterID}/captain-domain":{"delete":{"description":"Clears captain_domain_id on the cluster. This will likely cause the cluster to become incomplete.","operationId":"DetachCaptainDomain","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"Cluster ID","in":"path","name":"clusterID","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.ClusterResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"bad request"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"404":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"cluster not found"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"db error"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"Detach the captain domain from a cluster","tags":["Clusters"]},"post":{"description":"Sets captain_domain_id on the cluster. Validation of shape happens asynchronously.","operationId":"AttachCaptainDomain","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"Cluster ID","in":"path","name":"clusterID","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.AttachCaptainDomainRequest"}}},"description":"payload","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.ClusterResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"bad request"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"404":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"cluster or domain not found"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"db error"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"Attach a captain domain to a cluster","tags":["Clusters"]}},"/clusters/{clusterID}/control-plane-record-set":{"delete":{"description":"Clears control_plane_record_set_id on the cluster.","operationId":"DetachControlPlaneRecordSet","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"Cluster ID","in":"path","name":"clusterID","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.ClusterResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"bad request"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"404":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"cluster not found"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"db error"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"Detach the control plane record set from a cluster","tags":["Clusters"]},"post":{"description":"Sets control_plane_record_set_id on the cluster.","operationId":"AttachControlPlaneRecordSet","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"Cluster ID","in":"path","name":"clusterID","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.AttachRecordSetRequest"}}},"description":"payload","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.ClusterResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"bad request"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"404":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"cluster or record set not found"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"db error"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"Attach a control plane record set to a cluster","tags":["Clusters"]}},"/clusters/{clusterID}/glueops-load-balancer":{"delete":{"description":"Clears glueops_load_balancer_id on the cluster.","operationId":"DetachGlueOpsLoadBalancer","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"Cluster ID","in":"path","name":"clusterID","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.ClusterResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"bad request"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"404":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"cluster not found"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"db error"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"Detach the GlueOps/control-plane load balancer from a cluster","tags":["Clusters"]},"post":{"description":"Sets glueops_load_balancer_id on the cluster.","operationId":"AttachGlueOpsLoadBalancer","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"Cluster ID","in":"path","name":"clusterID","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.AttachLoadBalancerRequest"}}},"description":"payload","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.ClusterResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"bad request"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"404":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"cluster or load balancer not found"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"db error"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"Attach a GlueOps/control-plane load balancer to a cluster","tags":["Clusters"]}},"/clusters/{clusterID}/kubeconfig":{"delete":{"description":"Removes the encrypted kubeconfig, IV, and tag from the cluster record.","operationId":"ClearClusterKubeconfig","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"Cluster ID","in":"path","name":"clusterID","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.ClusterResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"bad request"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"404":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"cluster not found"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"db error"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"Clear the kubeconfig for a cluster","tags":["Clusters"]},"post":{"description":"Stores the kubeconfig encrypted per organization. The kubeconfig is never returned in responses.","operationId":"SetClusterKubeconfig","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"Cluster ID","in":"path","name":"clusterID","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.SetKubeconfigRequest"}}},"description":"payload","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.ClusterResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"bad request"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"404":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"cluster not found"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"db error"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"Set (or replace) the kubeconfig for a cluster","tags":["Clusters"]}},"/credentials":{"get":{"description":"Returns credential metadata for the current org. Secrets are never returned.","operationId":"ListCredentials","parameters":[{"description":"Organization ID (UUID)","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"Filter by provider (e.g., aws)","in":"query","name":"provider","schema":{"type":"string"}},{"description":"Filter by kind (e.g., aws_access_key)","in":"query","name":"kind","schema":{"type":"string"}},{"description":"Filter by scope kind (provider/service/resource)","in":"query","name":"scope_kind","schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/dto.CredentialOut"},"type":"array"}}},"description":"OK"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"internal server error"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"List credentials (metadata only)","tags":["Credentials"]},"post":{"operationId":"CreateCredential","parameters":[{"description":"Organization ID (UUID)","in":"header","name":"X-Org-ID","schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.CreateCredentialRequest"}}},"description":"Credential payload","required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.CredentialOut"}}},"description":"Created"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"internal server error"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"Create a credential (encrypts secret)","tags":["Credentials"]}},"/credentials/{id}":{"delete":{"operationId":"DeleteCredential","parameters":[{"description":"Organization ID (UUID)","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"Credential ID (UUID)","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"No Content"},"404":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"not found"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"Delete credential","tags":["Credentials"]},"get":{"operationId":"GetCredential","parameters":[{"description":"Organization ID (UUID)","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"Credential ID (UUID)","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.CredentialOut"}}},"description":"OK"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"internal server error"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"Get credential by ID (metadata only)","tags":["Credentials"]},"patch":{"operationId":"UpdateCredential","parameters":[{"description":"Organization ID (UUID)","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"Credential ID (UUID)","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.UpdateCredentialRequest"}}},"description":"Fields to update","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.CredentialOut"}}},"description":"OK"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"X-Org-ID required"},"404":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"not found"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"Update credential metadata and/or rotate secret","tags":["Credentials"]}},"/credentials/{id}/reveal":{"post":{"operationId":"RevealCredential","parameters":[{"description":"Organization ID (UUID)","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"Credential ID (UUID)","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"additionalProperties":{},"type":"object"}}},"description":"OK"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"404":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"not found"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"Reveal decrypted secret (one-time read)","tags":["Credentials"]}},"/dns/domains":{"get":{"description":"Returns domains for X-Org-ID. Filters: `domain_name`, `status`, `q` (contains).","operationId":"ListDomains","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"Exact domain name (lowercase, no trailing dot)","in":"query","name":"domain_name","schema":{"type":"string"}},{"description":"pending|provisioning|ready|failed","in":"query","name":"status","schema":{"type":"string"}},{"description":"Domain contains (case-insensitive)","in":"query","name":"q","schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/dto.DomainResponse"},"type":"array"}}},"description":"OK"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"db error"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"List domains (org scoped)","tags":["DNS"]},"post":{"description":"Creates a domain bound to a Route 53 scoped credential. Archer will backfill ZoneID if omitted.","operationId":"CreateDomain","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.CreateDomainRequest"}}},"description":"Domain payload","required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.DomainResponse"}}},"description":"Created"},"400":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"validation error"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"db error"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"Create a domain (org scoped)","tags":["DNS"]}},"/dns/domains/{domain_id}/records":{"get":{"description":"Filters: `name`, `type`, `status`.","operationId":"ListRecordSets","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"Domain ID (UUID)","in":"path","name":"domain_id","required":true,"schema":{"type":"string"}},{"description":"Exact relative name or FQDN (server normalizes)","in":"query","name":"name","schema":{"type":"string"}},{"description":"RR type (A, AAAA, CNAME, TXT, MX, NS, SRV, CAA)","in":"query","name":"type","schema":{"type":"string"}},{"description":"pending|provisioning|ready|failed","in":"query","name":"status","schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/dto.RecordSetResponse"},"type":"array"}}},"description":"OK"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"404":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"domain not found"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"List record sets for a domain","tags":["DNS"]},"post":{"operationId":"CreateRecordSet","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"Domain ID (UUID)","in":"path","name":"domain_id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.CreateRecordSetRequest"}}},"description":"Record set payload","required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.RecordSetResponse"}}},"description":"Created"},"400":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"validation error"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"404":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"domain not found"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"Create a record set (pending; Archer will UPSERT to Route 53)","tags":["DNS"]}},"/dns/domains/{id}":{"delete":{"operationId":"DeleteDomain","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"Domain ID (UUID)","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"No Content"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"404":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"not found"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"Delete a domain","tags":["DNS"]},"get":{"operationId":"GetDomain","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"Domain ID (UUID)","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.DomainResponse"}}},"description":"OK"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"404":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"not found"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"Get a domain (org scoped)","tags":["DNS"]},"patch":{"operationId":"UpdateDomain","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"Domain ID (UUID)","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.UpdateDomainRequest"}}},"description":"Fields to update","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.DomainResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"validation error"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"404":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"not found"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"Update a domain (org scoped)","tags":["DNS"]}},"/dns/records/{id}":{"delete":{"operationId":"DeleteRecordSet","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"Record Set ID (UUID)","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"No Content"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"404":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"not found"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"Delete a record set (API removes row; worker can optionally handle external deletion policy)","tags":["DNS"]},"patch":{"operationId":"UpdateRecordSet","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"Record Set ID (UUID)","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.UpdateRecordSetRequest"}}},"description":"Fields to update","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.RecordSetResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"validation error"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"404":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"not found"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"Update a record set (flips to pending for reconciliation)","tags":["DNS"]}},"/healthz":{"get":{"description":"Returns 200 OK when the service is up","operationId":"HealthCheck // operationId","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/handlers.HealthStatus"}}},"description":"OK"}},"summary":"Basic health check","tags":["Health"]}},"/labels":{"get":{"description":"Returns node labels for the organization in X-Org-ID. Filters: `key`, `value`, and `q` (key contains). Add `include=node_pools` to include linked node groups.","operationId":"ListLabels","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"Exact key","in":"query","name":"key","schema":{"type":"string"}},{"description":"Exact value","in":"query","name":"value","schema":{"type":"string"}},{"description":"Key contains (case-insensitive)","in":"query","name":"q","schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/dto.LabelResponse"},"type":"array"}}},"description":"OK"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"failed to list node taints"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"List node labels (org scoped)","tags":["Labels"]},"post":{"description":"Creates a label.","operationId":"CreateLabel","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.CreateLabelRequest"}}},"description":"Label payload","required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.LabelResponse"}}},"description":"Created"},"400":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"invalid json / missing fields / invalid node_pool_ids"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"create failed"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"Create label (org scoped)","tags":["Labels"]}},"/labels/{id}":{"delete":{"description":"Permanently deletes the label.","operationId":"DeleteLabel","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"Label ID (UUID)","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"No Content"},"400":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"invalid id"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"delete failed"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"Delete label (org scoped)","tags":["Labels"]},"get":{"description":"Returns one label.","operationId":"GetLabel","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"Label ID (UUID)","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.LabelResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"invalid id"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"404":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"not found"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"fetch failed"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"Get label by ID (org scoped)","tags":["Labels"]},"patch":{"description":"Partially update label fields.","operationId":"UpdateLabel","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"Label ID (UUID)","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.UpdateLabelRequest"}}},"description":"Fields to update","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.LabelResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"invalid id / invalid json"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"404":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"not found"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"update failed"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"Update label (org scoped)","tags":["Labels"]}},"/load-balancers":{"get":{"description":"Returns load balancers for the organization in X-Org-ID.","operationId":"ListLoadBalancers","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/dto.LoadBalancerResponse"},"type":"array"}}},"description":"OK"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"failed to list clusters"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"List load balancers (org scoped)","tags":["LoadBalancers"]},"post":{"operationId":"CreateLoadBalancer","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.CreateLoadBalancerRequest"}}},"description":"Record set payload","required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.LoadBalancerResponse"}}},"description":"Created"},"400":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"validation error"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"404":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"domain not found"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"Create a load balancer","tags":["LoadBalancers"]}},"/load-balancers/{id}":{"delete":{"operationId":"DeleteLoadBalancer","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"Load Balancer ID (UUID)","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"No Content"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"404":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"not found"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"Delete a load balancer","tags":["LoadBalancers"]},"get":{"description":"Returns load balancer for the organization in X-Org-ID.","operationId":"GetLoadBalancers","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"LoadBalancer ID (UUID)","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/dto.LoadBalancerResponse"},"type":"array"}}},"description":"OK"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"failed to list clusters"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"Get a load balancer (org scoped)","tags":["LoadBalancers"]},"patch":{"operationId":"UpdateLoadBalancer","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"Load Balancer ID (UUID)","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.UpdateLoadBalancerRequest"}}},"description":"Fields to update","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.LoadBalancerResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"validation error"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"404":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"not found"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"Update a load balancer (org scoped)","tags":["LoadBalancers"]}},"/me":{"get":{"operationId":"GetMe","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/handlers.meResponse"}}},"description":"OK"}},"security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"summary":"Get current user profile","tags":["Me"]},"patch":{"operationId":"UpdateMe","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/handlers.updateMeRequest"}}},"description":"Patch profile","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/models.User"}}},"description":"OK"}},"security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"summary":"Update current user profile","tags":["Me"]}},"/me/api-keys":{"get":{"operationId":"ListUserAPIKeys","responses":{"200":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/handlers.userAPIKeyOut"},"type":"array"}}},"description":"OK"}},"security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"summary":"List my API keys","tags":["MeAPIKeys"]},"post":{"description":"Returns the plaintext key once. Store it securely on the client side.","operationId":"CreateUserAPIKey","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/handlers.createUserKeyRequest"}}},"description":"Key options","required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/handlers.userAPIKeyOut"}}},"description":"Created"}},"security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"summary":"Create a new user API key","tags":["MeAPIKeys"]}},"/me/api-keys/{id}":{"delete":{"operationId":"DeleteUserAPIKey","parameters":[{"description":"Key ID (UUID)","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"No Content"}},"security":[{"BearerAuth":[]}],"summary":"Delete a user API key","tags":["MeAPIKeys"]}},"/node-pools":{"get":{"description":"Returns node pools for the organization in X-Org-ID.","operationId":"ListNodePools","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"Name contains (case-insensitive)","in":"query","name":"q","schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/dto.NodePoolResponse"},"type":"array"}}},"description":"OK"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"failed to list node pools"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"List node pools (org scoped)","tags":["NodePools"]},"post":{"description":"Creates a node pool. Optionally attach initial servers.","operationId":"CreateNodePool","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.CreateNodePoolRequest"}}},"description":"NodePool payload","required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.NodePoolResponse"}}},"description":"Created"},"400":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"invalid json / missing fields / invalid server_ids"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"create failed"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"Create node pool (org scoped)","tags":["NodePools"]}},"/node-pools/{id}":{"delete":{"description":"Permanently deletes the node pool.","operationId":"DeleteNodePool","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"Node Pool ID (UUID)","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"No Content"},"400":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"invalid id"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"delete failed"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"Delete node pool (org scoped)","tags":["NodePools"]},"get":{"description":"Returns one node pool. Add `include=servers` to include servers.","operationId":"GetNodePool","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"Node Pool ID (UUID)","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.NodePoolResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"invalid id"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"404":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"not found"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"fetch failed"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"Get node pool by ID (org scoped)","tags":["NodePools"]},"patch":{"description":"Partially update node pool fields.","operationId":"UpdateNodePool","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"Node Pool ID (UUID)","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.UpdateNodePoolRequest"}}},"description":"Fields to update","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.NodePoolResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"invalid id / invalid json"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"404":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"not found"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"update failed"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"Update node pool (org scoped)","tags":["NodePools"]}},"/node-pools/{id}/annotations":{"get":{"operationId":"ListNodePoolAnnotations","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"Node Pool ID (UUID)","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/dto.AnnotationResponse"},"type":"array"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"invalid id"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"404":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"not found"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"fetch failed"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"List annotations attached to a node pool (org scoped)","tags":["NodePools"]},"post":{"operationId":"AttachNodePoolAnnotations","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"Node Group ID (UUID)","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.AttachAnnotationsRequest"}}},"description":"Annotation IDs to attach","required":true},"responses":{"204":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"No Content"},"400":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"invalid id / invalid server_ids"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"404":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"not found"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"attach failed"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"Attach annotation to a node pool (org scoped)","tags":["NodePools"]}},"/node-pools/{id}/annotations/{annotationId}":{"delete":{"operationId":"DetachNodePoolAnnotation","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"Node Pool ID (UUID)","in":"path","name":"id","required":true,"schema":{"type":"string"}},{"description":"Annotation ID (UUID)","in":"path","name":"annotationId","required":true,"schema":{"type":"string"}}],"responses":{"204":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"No Content"},"400":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"invalid id"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"404":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"not found"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"detach failed"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"Detach one annotation from a node pool (org scoped)","tags":["NodePools"]}},"/node-pools/{id}/labels":{"get":{"operationId":"ListNodePoolLabels","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"Label Pool ID (UUID)","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/dto.LabelResponse"},"type":"array"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"invalid id"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"404":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"not found"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"fetch failed"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"List labels attached to a node pool (org scoped)","tags":["NodePools"]},"post":{"operationId":"AttachNodePoolLabels","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"Node Pool ID (UUID)","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.AttachLabelsRequest"}}},"description":"Label IDs to attach","required":true},"responses":{"204":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"No Content"},"400":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"invalid id / invalid server_ids"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"404":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"not found"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"attach failed"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"Attach labels to a node pool (org scoped)","tags":["NodePools"]}},"/node-pools/{id}/labels/{labelId}":{"delete":{"operationId":"DetachNodePoolLabel","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"Node Pool ID (UUID)","in":"path","name":"id","required":true,"schema":{"type":"string"}},{"description":"Label ID (UUID)","in":"path","name":"labelId","required":true,"schema":{"type":"string"}}],"responses":{"204":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"No Content"},"400":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"invalid id"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"404":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"not found"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"detach failed"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"Detach one label from a node pool (org scoped)","tags":["NodePools"]}},"/node-pools/{id}/servers":{"get":{"operationId":"ListNodePoolServers","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"Node Pool ID (UUID)","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/dto.ServerResponse"},"type":"array"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"invalid id"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"404":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"not found"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"fetch failed"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"List servers attached to a node pool (org scoped)","tags":["NodePools"]},"post":{"operationId":"AttachNodePoolServers","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"Node Pool ID (UUID)","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.AttachServersRequest"}}},"description":"Server IDs to attach","required":true},"responses":{"204":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"No Content"},"400":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"invalid id / invalid server_ids"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"404":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"not found"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"attach failed"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"Attach servers to a node pool (org scoped)","tags":["NodePools"]}},"/node-pools/{id}/servers/{serverId}":{"delete":{"operationId":"DetachNodePoolServer","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"Node Pool ID (UUID)","in":"path","name":"id","required":true,"schema":{"type":"string"}},{"description":"Server ID (UUID)","in":"path","name":"serverId","required":true,"schema":{"type":"string"}}],"responses":{"204":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"No Content"},"400":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"invalid id"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"404":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"not found"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"detach failed"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"Detach one server from a node pool (org scoped)","tags":["NodePools"]}},"/node-pools/{id}/taints":{"get":{"operationId":"ListNodePoolTaints","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"Node Pool ID (UUID)","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/dto.TaintResponse"},"type":"array"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"invalid id"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"404":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"not found"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"fetch failed"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"List taints attached to a node pool (org scoped)","tags":["NodePools"]},"post":{"operationId":"AttachNodePoolTaints","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"Node Pool ID (UUID)","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.AttachTaintsRequest"}}},"description":"Taint IDs to attach","required":true},"responses":{"204":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"No Content"},"400":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"invalid id / invalid taint_ids"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"404":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"not found"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"attach failed"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"Attach taints to a node pool (org scoped)","tags":["NodePools"]}},"/node-pools/{id}/taints/{taintId}":{"delete":{"operationId":"DetachNodePoolTaint","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"Node Pool ID (UUID)","in":"path","name":"id","required":true,"schema":{"type":"string"}},{"description":"Taint ID (UUID)","in":"path","name":"taintId","required":true,"schema":{"type":"string"}}],"responses":{"204":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"No Content"},"400":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"invalid id"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"404":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"not found"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"detach failed"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"Detach one taint from a node pool (org scoped)","tags":["NodePools"]}},"/orgs":{"get":{"operationId":"listMyOrgs","responses":{"200":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/models.Organization"},"type":"array"}}},"description":"OK"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/utils.ErrorResponse"}}},"description":"Unauthorized"}},"security":[{"BearerAuth":[]}],"summary":"List organizations I belong to","tags":["Orgs"]},"post":{"operationId":"createOrg","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/handlers.orgCreateReq"}}},"description":"Org payload","required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/models.Organization"}}},"description":"Created"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/utils.ErrorResponse"}}},"description":"Bad Request"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/utils.ErrorResponse"}}},"description":"Unauthorized"},"409":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/utils.ErrorResponse"}}},"description":"Conflict"}},"security":[{"BearerAuth":[]}],"summary":"Create organization","tags":["Orgs"]}},"/orgs/{id}":{"delete":{"operationId":"deleteOrg","parameters":[{"description":"Org ID (UUID)","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"Deleted"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/utils.ErrorResponse"}}},"description":"Unauthorized"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/utils.ErrorResponse"}}},"description":"Not Found"}},"security":[{"BearerAuth":[]}],"summary":"Delete organization (owner)","tags":["Orgs"]},"get":{"operationId":"getOrg","parameters":[{"description":"Org ID (UUID)","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/models.Organization"}}},"description":"OK"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/utils.ErrorResponse"}}},"description":"Unauthorized"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/utils.ErrorResponse"}}},"description":"Not Found"}},"security":[{"BearerAuth":[]}],"summary":"Get organization","tags":["Orgs"]},"patch":{"operationId":"updateOrg","parameters":[{"description":"Org ID (UUID)","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/handlers.orgUpdateReq"}}},"description":"Update payload","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/models.Organization"}}},"description":"OK"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/utils.ErrorResponse"}}},"description":"Unauthorized"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/utils.ErrorResponse"}}},"description":"Not Found"}},"security":[{"BearerAuth":[]}],"summary":"Update organization (owner/admin)","tags":["Orgs"]}},"/orgs/{id}/api-keys":{"get":{"operationId":"listOrgKeys","parameters":[{"description":"Org ID (UUID)","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/models.APIKey"},"type":"array"}}},"description":"OK"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/utils.ErrorResponse"}}},"description":"Unauthorized"}},"security":[{"BearerAuth":[]}],"summary":"List org-scoped API keys (no secrets)","tags":["Orgs"]},"post":{"operationId":"createOrgKey","parameters":[{"description":"Org ID (UUID)","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/handlers.orgKeyCreateReq"}}},"description":"Key name + optional expiry","required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/handlers.orgKeyCreateResp"}}},"description":"Created"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/utils.ErrorResponse"}}},"description":"Unauthorized"}},"security":[{"BearerAuth":[]}],"summary":"Create org key/secret pair (owner/admin)","tags":["Orgs"]}},"/orgs/{id}/api-keys/{key_id}":{"delete":{"operationId":"deleteOrgKey","parameters":[{"description":"Org ID (UUID)","in":"path","name":"id","required":true,"schema":{"type":"string"}},{"description":"Key ID (UUID)","in":"path","name":"key_id","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"Deleted"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/utils.ErrorResponse"}}},"description":"Unauthorized"}},"security":[{"BearerAuth":[]}],"summary":"Delete org key (owner/admin)","tags":["Orgs"]}},"/orgs/{id}/members":{"get":{"operationId":"listMembers","parameters":[{"description":"Org ID (UUID)","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/handlers.memberOut"},"type":"array"}}},"description":"OK"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/utils.ErrorResponse"}}},"description":"Unauthorized"}},"security":[{"BearerAuth":[]}],"summary":"List members in org","tags":["Orgs"]},"post":{"operationId":"addOrUpdateMember","parameters":[{"description":"Org ID (UUID)","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/handlers.memberUpsertReq"}}},"description":"User \u0026 role","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/handlers.memberOut"}}},"description":"OK"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/utils.ErrorResponse"}}},"description":"Unauthorized"}},"security":[{"BearerAuth":[]}],"summary":"Add or update a member (owner/admin)","tags":["Orgs"]}},"/orgs/{id}/members/{user_id}":{"delete":{"operationId":"removeMember","parameters":[{"description":"Org ID (UUID)","in":"path","name":"id","required":true,"schema":{"type":"string"}},{"description":"User ID (UUID)","in":"path","name":"user_id","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"Removed"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/utils.ErrorResponse"}}},"description":"Unauthorized"}},"security":[{"BearerAuth":[]}],"summary":"Remove a member (owner/admin)","tags":["Orgs"]}},"/servers":{"get":{"description":"Returns servers for the organization in X-Org-ID. Optional filters: status, role.","operationId":"ListServers","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"Filter by status (pending|provisioning|ready|failed)","in":"query","name":"status","schema":{"type":"string"}},{"description":"Filter by role","in":"query","name":"role","schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/dto.ServerResponse"},"type":"array"}}},"description":"OK"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"failed to list servers"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"List servers (org scoped)","tags":["Servers"]},"post":{"description":"Creates a server bound to the org in X-Org-ID. Validates that ssh_key_id belongs to the org.","operationId":"CreateServer","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.CreateServerRequest"}}},"description":"Server payload","required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.ServerResponse"}}},"description":"Created"},"400":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"invalid json / missing fields / invalid status / invalid ssh_key_id"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"create failed"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"Create server (org scoped)","tags":["Servers"]}},"/servers/{id}":{"delete":{"description":"Permanently deletes the server.","operationId":"DeleteServer","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"Server ID (UUID)","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"No Content"},"400":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"invalid id"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"delete failed"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"Delete server (org scoped)","tags":["Servers"]},"get":{"description":"Returns one server in the given organization.","operationId":"GetServer","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"Server ID (UUID)","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.ServerResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"invalid id"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"404":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"not found"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"fetch failed"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"Get server by ID (org scoped)","tags":["Servers"]},"patch":{"description":"Partially update fields; changing ssh_key_id validates ownership.","operationId":"UpdateServer","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"Server ID (UUID)","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.UpdateServerRequest"}}},"description":"Fields to update","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.ServerResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"invalid id / invalid json / invalid status / invalid ssh_key_id"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"404":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"not found"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"update failed"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"Update server (org scoped)","tags":["Servers"]}},"/servers/{id}/reset-hostkey":{"post":{"description":"Clears the stored SSH host key for this server. The next SSH connection will re-learn the host key (trust-on-first-use).","operationId":"ResetServerHostKey","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"Server ID (UUID)","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.ServerResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"invalid id"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"404":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"not found"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"reset failed"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"Reset SSH host key (org scoped)","tags":["Servers"]}},"/ssh":{"get":{"description":"Returns ssh keys for the organization in X-Org-ID.","operationId":"ListPublicSshKeys","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/dto.SshResponse"},"type":"array"}}},"description":"OK"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"failed to list keys"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"List ssh keys (org scoped)","tags":["Ssh"]},"post":{"description":"Generates an RSA or ED25519 keypair, saves it, and returns metadata. For RSA you may set bits (2048/3072/4096). Default is 4096. ED25519 ignores bits.","operationId":"CreateSSHKey","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.CreateSSHRequest"}}},"description":"Key generation options","required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.SshResponse"}}},"description":"Created"},"400":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"invalid json / invalid bits"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"generation/create failed"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"Create ssh keypair (org scoped)","tags":["Ssh"]}},"/ssh/{id}":{"delete":{"description":"Permanently deletes a keypair.","operationId":"DeleteSSHKey","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"SSH Key ID (UUID)","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"No Content"},"400":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"invalid id"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"delete failed"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"Delete ssh keypair (org scoped)","tags":["Ssh"]},"get":{"description":"Returns public key fields. Append `?reveal=true` to include the private key PEM.","operationId":"GetSSHKey","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"SSH Key ID (UUID)","in":"path","name":"id","required":true,"schema":{"type":"string"}},{"description":"Reveal private key PEM","in":"query","name":"reveal","schema":{"type":"boolean"}}],"responses":{"200":{"content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/dto.SshResponse"},{"$ref":"#/components/schemas/dto.SshRevealResponse"}]}}},"description":"When reveal=true"},"400":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"invalid id"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"404":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"not found"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"fetch failed"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"Get ssh key by ID (org scoped)","tags":["Ssh"]}},"/ssh/{id}/download":{"get":{"description":"Download `part=public|private|both` of the keypair. `both` returns a zip file.","operationId":"DownloadSSHKey","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","required":true,"schema":{"type":"string"}},{"description":"SSH Key ID (UUID)","in":"path","name":"id","required":true,"schema":{"type":"string"}},{"description":"Which part to download","in":"query","name":"part","required":true,"schema":{"enum":["public","private","both"],"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"file content"},"400":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"invalid id / invalid part"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"404":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"not found"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"download failed"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"Download ssh key files by ID (org scoped)","tags":["Ssh"]}},"/taints":{"get":{"description":"Returns node taints for the organization in X-Org-ID. Filters: `key`, `value`, and `q` (key contains). Add `include=node_pools` to include linked node pools.","operationId":"ListTaints","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"Exact key","in":"query","name":"key","schema":{"type":"string"}},{"description":"Exact value","in":"query","name":"value","schema":{"type":"string"}},{"description":"key contains (case-insensitive)","in":"query","name":"q","schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/dto.TaintResponse"},"type":"array"}}},"description":"OK"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"failed to list node taints"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"List node pool taints (org scoped)","tags":["Taints"]},"post":{"description":"Creates a taint.","operationId":"CreateTaint","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.CreateTaintRequest"}}},"description":"Taint payload","required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.TaintResponse"}}},"description":"Created"},"400":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"invalid json / missing fields / invalid node_pool_ids"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"create failed"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"Create node taint (org scoped)","tags":["Taints"]}},"/taints/{id}":{"delete":{"description":"Permanently deletes the taint.","operationId":"DeleteTaint","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"Node Taint ID (UUID)","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"No Content"},"400":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"invalid id"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"delete failed"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"Delete taint (org scoped)","tags":["Taints"]},"get":{"operationId":"GetTaint","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"Node Taint ID (UUID)","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.TaintResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"invalid id"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"404":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"not found"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"fetch failed"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"Get node taint by ID (org scoped)","tags":["Taints"]},"patch":{"description":"Partially update taint fields.","operationId":"UpdateTaint","parameters":[{"description":"Organization UUID","in":"header","name":"X-Org-ID","schema":{"type":"string"}},{"description":"Node Taint ID (UUID)","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.UpdateTaintRequest"}}},"description":"Fields to update","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/dto.TaintResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"invalid id / invalid json"},"401":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"organization required"},"404":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"not found"},"500":{"content":{"application/json":{"schema":{"type":"string"}}},"description":"update failed"}},"security":[{"BearerAuth":[]},{"OrgKeyAuth":[]},{"OrgSecretAuth":[]}],"summary":"Update node taint (org scoped)","tags":["Taints"]}},"/version":{"get":{"description":"Returns build/runtime metadata for the running service.","operationId":"Version // operationId","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/handlers.VersionResponse"}}},"description":"OK"}},"summary":"Service version information","tags":["Meta"]}}}, + "openapi": "3.1.0", + "servers": [ + {"description":"Production API","url":"https://autoglue.onglueops.rocks/api/v1"}, + {"description":"Staging API","url":"https://autoglue.apps.nonprod.earth.onglueops.rocks/api/v1"}, + {"description":"Local dev","url":"http://localhost:8080/api/v1"} + ] +} \ No newline at end of file diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 1945026..fdbc429 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -1,1159 +1,1275 @@ -basePath: /api/v1 -definitions: - dto.AnnotationResponse: - properties: - created_at: - type: string - id: - type: string - key: - type: string - organization_id: - type: string - updated_at: - type: string - value: - type: string - type: object - dto.AttachAnnotationsRequest: - properties: - annotation_ids: - items: +components: + schemas: + dto.AnnotationResponse: + properties: + created_at: type: string - type: array - type: object - dto.AttachLabelsRequest: - properties: - label_ids: - items: + id: type: string - type: array - type: object - dto.AttachServersRequest: - properties: - server_ids: - items: + key: type: string - type: array - type: object - dto.AttachTaintsRequest: - properties: - taint_ids: - items: + organization_id: type: string - type: array - type: object - dto.AuthStartResponse: - properties: - auth_url: - example: https://accounts.google.com/o/oauth2/v2/auth?client_id=... - type: string - type: object - dto.ClusterResponse: - properties: - bastion_server: - $ref: '#/definitions/dto.ServerResponse' - captain_domain: - type: string - certificate_key: - type: string - cluster_load_balancer: - type: string - control_load_balancer: - type: string - created_at: - type: string - id: - type: string - name: - type: string - node_pools: - items: - $ref: '#/definitions/dto.NodePoolResponse' - type: array - provider: - type: string - random_token: - type: string - region: - type: string - status: - type: string - updated_at: - type: string - type: object - dto.CreateAnnotationRequest: - properties: - key: - type: string - value: - type: string - type: object - dto.CreateClusterRequest: - properties: - captain_domain: - type: string - cluster_load_balancer: - type: string - control_load_balancer: - type: string - name: - type: string - provider: - type: string - region: - type: string - status: - type: string - type: object - dto.CreateCredentialRequest: - properties: - account_id: - maxLength: 32 - type: string - kind: - description: aws_access_key, api_token, basic_auth, oauth2 - type: string - name: - description: human label - maxLength: 100 - type: string - provider: - enum: - - aws - - cloudflare - - hetzner - - digitalocean - - generic - type: string - region: - maxLength: 32 - type: string - schema_version: - description: secret schema version - minimum: 1 - type: integer - scope: - description: '{"service":"route53"} or {"arn":"..."}' - type: object - scope_kind: - enum: - - provider - - service - - resource - type: string - scope_version: - description: scope schema version - minimum: 1 - type: integer - secret: - description: encrypted later - type: object - required: - - kind - - provider - - schema_version - - scope - - scope_kind - - scope_version - - secret - type: object - dto.CreateDomainRequest: - properties: - credential_id: - type: string - domain_name: - type: string - zone_id: - maxLength: 128 - type: string - required: - - credential_id - - domain_name - type: object - dto.CreateLabelRequest: - properties: - key: - type: string - value: - type: string - type: object - dto.CreateNodePoolRequest: - properties: - name: - type: string - role: - enum: - - master - - worker - type: string - type: object - dto.CreateRecordSetRequest: - properties: - name: - description: |- - Name relative to domain ("endpoint") OR FQDN ("endpoint.example.com"). - Server normalizes to relative. - maxLength: 253 - type: string - ttl: - maximum: 86400 - minimum: 1 - type: integer - type: - type: string - values: - items: + updated_at: type: string - type: array - required: - - name - - type - type: object - dto.CreateSSHRequest: - properties: - bits: - description: Only for RSA - type: integer - comment: - example: deploy@autoglue - type: string - name: - type: string - type: - description: '"rsa" (default) or "ed25519"' - type: string - type: object - dto.CreateServerRequest: - properties: - hostname: - type: string - private_ip_address: - type: string - public_ip_address: - type: string - role: - enum: - - master - - worker - - bastion - example: master|worker|bastion - type: string - ssh_key_id: - type: string - ssh_user: - type: string - status: - enum: - - pending - - provisioning - - ready - - failed - example: pending|provisioning|ready|failed - type: string - type: object - dto.CreateTaintRequest: - properties: - effect: - type: string - key: - type: string - value: - type: string - type: object - dto.CredentialOut: - properties: - account_id: - type: string - created_at: - type: string - id: - type: string - kind: - type: string - name: - type: string - provider: - type: string - region: - type: string - schema_version: - type: integer - scope: - type: object - scope_kind: - type: string - scope_version: - type: integer - updated_at: - type: string - type: object - dto.DomainResponse: - properties: - created_at: - type: string - credential_id: - type: string - domain_name: - type: string - id: - type: string - last_error: - type: string - organization_id: - type: string - status: - type: string - updated_at: - type: string - zone_id: - type: string - type: object - dto.EnqueueRequest: - properties: - payload: - type: object - queue: - example: default - type: string - run_at: - example: "2025-11-05T08:00:00Z" - type: string - type: - example: email.send - type: string - type: object - dto.JWK: - properties: - alg: - example: RS256 - type: string - e: - example: AQAB - type: string - kid: - example: 7c6f1d0a-7a98-4e6a-9dbf-6b1af4b9f345 - type: string - kty: - example: RSA - type: string - "n": - type: string - use: - example: sig - type: string - x: - type: string - type: object - dto.JWKS: - properties: - keys: - items: - $ref: '#/definitions/dto.JWK' - type: array - type: object - dto.Job: - properties: - attempts: - example: 0 - type: integer - created_at: - example: "2025-11-04T09:30:00Z" - type: string - id: - example: 01HF7SZK8Z8WG1M3J7S2Z8M2N6 - type: string - last_error: - example: error message - type: string - max_attempts: - example: 3 - type: integer - payload: {} - queue: - example: default - type: string - run_at: - example: "2025-11-04T09:30:00Z" - type: string - status: - allOf: - - $ref: '#/definitions/dto.JobStatus' - enum: - - queued|running|succeeded|failed|canceled|retrying|scheduled - example: queued - type: - example: email.send - type: string - updated_at: - example: "2025-11-04T09:30:00Z" - type: string - type: object - dto.JobStatus: - enum: - - queued - - running - - succeeded - - failed - - canceled - - retrying - - scheduled - type: string - x-enum-varnames: - - StatusQueued - - StatusRunning - - StatusSucceeded - - StatusFailed - - StatusCanceled - - StatusRetrying - - StatusScheduled - dto.LabelResponse: - properties: - created_at: - type: string - id: - type: string - key: - type: string - organization_id: - type: string - updated_at: - type: string - value: - type: string - type: object - dto.LogoutRequest: - properties: - refresh_token: - example: m0l9o8rT3t0V8d3eFf... - type: string - type: object - dto.NodePoolResponse: - properties: - annotations: - items: - $ref: '#/definitions/dto.AnnotationResponse' - type: array - created_at: - type: string - id: - type: string - labels: - items: - $ref: '#/definitions/dto.LabelResponse' - type: array - name: - type: string - organization_id: - type: string - role: - enum: - - master - - worker - type: string - servers: - items: - $ref: '#/definitions/dto.ServerResponse' - type: array - taints: - items: - $ref: '#/definitions/dto.TaintResponse' - type: array - updated_at: - type: string - type: object - dto.PageJob: - properties: - items: - items: - $ref: '#/definitions/dto.Job' - type: array - page: - example: 1 - type: integer - page_size: - example: 25 - type: integer - total: - example: 120 - type: integer - type: object - dto.QueueInfo: - properties: - failed: - example: 5 - type: integer - name: - example: default - type: string - pending: - example: 42 - type: integer - running: - example: 3 - type: integer - scheduled: - example: 7 - type: integer - type: object - dto.RecordSetResponse: - properties: - created_at: - type: string - domain_id: - type: string - fingerprint: - type: string - id: - type: string - last_error: - type: string - name: - type: string - owner: - type: string - status: - type: string - ttl: - type: integer - type: - type: string - updated_at: - type: string - values: - description: '[]string JSON' - type: object - type: object - dto.RefreshRequest: - properties: - refresh_token: - example: m0l9o8rT3t0V8d3eFf... - type: string - type: object - dto.ServerResponse: - properties: - created_at: - type: string - hostname: - type: string - id: - type: string - organization_id: - type: string - private_ip_address: - type: string - public_ip_address: - type: string - role: - enum: - - master - - worker - - bastion - example: master|worker|bastion - type: string - ssh_key_id: - type: string - ssh_user: - type: string - status: - enum: - - pending - - provisioning - - ready - - failed - example: pending|provisioning|ready|failed - type: string - updated_at: - type: string - type: object - dto.SshResponse: - properties: - created_at: - type: string - fingerprint: - type: string - id: - type: string - name: - type: string - organization_id: - type: string - public_key: - type: string - updated_at: - type: string - type: object - dto.SshRevealResponse: - properties: - created_at: - type: string - fingerprint: - type: string - id: - type: string - name: - type: string - organization_id: - type: string - private_key: - type: string - public_key: - type: string - updated_at: - type: string - type: object - dto.TaintResponse: - properties: - created_at: - type: string - effect: - type: string - id: - type: string - key: - type: string - organization_id: - type: string - updated_at: - type: string - value: - type: string - type: object - dto.TokenPair: - properties: - access_token: - example: eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6Ij... - type: string - expires_in: - example: 3600 - type: integer - refresh_token: - example: m0l9o8rT3t0V8d3eFf.... - type: string - token_type: - example: Bearer - type: string - type: object - dto.UpdateAnnotationRequest: - properties: - key: - type: string - value: - type: string - type: object - dto.UpdateCredentialRequest: - properties: - account_id: - type: string - name: - type: string - region: - type: string - scope: - type: object - scope_kind: - type: string - scope_version: - type: integer - secret: - description: set if rotating - type: object - type: object - dto.UpdateDomainRequest: - properties: - credential_id: - type: string - domain_name: - type: string - status: - enum: - - pending - - provisioning - - ready - - failed - type: string - zone_id: - maxLength: 128 - type: string - type: object - dto.UpdateLabelRequest: - properties: - key: - type: string - value: - type: string - type: object - dto.UpdateNodePoolRequest: - properties: - name: - type: string - role: - enum: - - master - - worker - type: string - type: object - dto.UpdateRecordSetRequest: - properties: - name: - description: Any change flips status back to pending (worker will UPSERT) - maxLength: 253 - type: string - status: - enum: - - pending - - provisioning - - ready - - failed - type: string - ttl: - maximum: 86400 - minimum: 1 - type: integer - type: - type: string - values: - items: + value: type: string - type: array - type: object - dto.UpdateServerRequest: - properties: - hostname: - type: string - private_ip_address: - type: string - public_ip_address: - type: string - role: - enum: - - master - - worker - - bastion - example: master|worker|bastion - type: string - ssh_key_id: - type: string - ssh_user: - type: string - status: - enum: - - pending - - provisioning - - ready - - failed - example: pending|provisioning|ready|failed - type: string - type: object - dto.UpdateTaintRequest: - properties: - effect: - type: string - key: - type: string - value: - type: string - type: object - handlers.HealthStatus: - properties: - status: - example: ok - type: string - type: object - handlers.VersionResponse: - properties: - built: - example: "2025-11-08T12:34:56Z" - type: string - builtBy: - example: ci - type: string - commit: - example: a1b2c3d - type: string - commitTime: - example: "2025-11-08T12:31:00Z" - type: string - go: - example: go1.23.3 - type: string - goArch: - example: amd64 - type: string - goOS: - example: linux - type: string - modified: - example: false - type: boolean - revision: - example: a1b2c3d4e5f6abcdef - type: string - vcs: - example: git - type: string - version: - example: 1.4.2 - type: string - type: object - handlers.createUserKeyRequest: - properties: - expires_in_hours: - description: optional TTL - type: integer - name: - type: string - type: object - handlers.meResponse: - properties: - avatar_url: - type: string - created_at: - format: date-time - type: string - display_name: - type: string - emails: + type: object + dto.AttachAnnotationsRequest: + properties: + annotation_ids: + items: + type: string + type: array + uniqueItems: false + type: object + dto.AttachBastionRequest: + properties: + server_id: + type: string + type: object + dto.AttachCaptainDomainRequest: + properties: + domain_id: + type: string + type: object + dto.AttachLabelsRequest: + properties: + label_ids: + items: + type: string + type: array + uniqueItems: false + type: object + dto.AttachLoadBalancerRequest: + properties: + load_balancer_id: + type: string + type: object + dto.AttachRecordSetRequest: + properties: + record_set_id: + type: string + type: object + dto.AttachServersRequest: + properties: + server_ids: + items: + type: string + type: array + uniqueItems: false + type: object + dto.AttachTaintsRequest: + properties: + taint_ids: + items: + type: string + type: array + uniqueItems: false + type: object + dto.AuthStartResponse: + properties: + auth_url: + example: https://accounts.google.com/o/oauth2/v2/auth?client_id=... + type: string + type: object + dto.ClusterResponse: + properties: + apps_load_balancer: + $ref: '#/components/schemas/dto.LoadBalancerResponse' + bastion_server: + $ref: '#/components/schemas/dto.ServerResponse' + captain_domain: + $ref: '#/components/schemas/dto.DomainResponse' + certificate_key: + type: string + control_plane_record_set: + $ref: '#/components/schemas/dto.RecordSetResponse' + created_at: + type: string + glueops_load_balancer: + $ref: '#/components/schemas/dto.LoadBalancerResponse' + id: + type: string + last_error: + type: string + name: + type: string + node_pools: + items: + $ref: '#/components/schemas/dto.NodePoolResponse' + type: array + uniqueItems: false + provider: + type: string + random_token: + type: string + region: + type: string + status: + type: string + updated_at: + type: string + type: object + dto.CreateAnnotationRequest: + properties: + key: + type: string + value: + type: string + type: object + dto.CreateClusterRequest: + properties: + name: + type: string + provider: + type: string + region: + type: string + type: object + dto.CreateCredentialRequest: + properties: + account_id: + maxLength: 32 + type: string + kind: + description: aws_access_key, api_token, basic_auth, oauth2 + type: string + name: + description: human label + maxLength: 100 + type: string + provider: + enum: + - aws + - cloudflare + - hetzner + - digitalocean + - generic + type: string + region: + maxLength: 32 + type: string + schema_version: + description: secret schema version + minimum: 1 + type: integer + scope: + description: '{"service":"route53"} or {"arn":"..."}' + type: object + scope_kind: + enum: + - provider + - service + - resource + type: string + scope_version: + description: scope schema version + minimum: 1 + type: integer + secret: + description: encrypted later + type: object + required: + - kind + - provider + - schema_version + - scope + - scope_kind + - scope_version + - secret + type: object + dto.CreateDomainRequest: + properties: + credential_id: + type: string + domain_name: + type: string + zone_id: + maxLength: 128 + type: string + required: + - credential_id + - domain_name + type: object + dto.CreateLabelRequest: + properties: + key: + type: string + value: + type: string + type: object + dto.CreateLoadBalancerRequest: + properties: + kind: + enum: + - glueops|public + example: public + type: string + name: + example: glueops + type: string + private_ip_address: + example: 192.168.0.2 + type: string + public_ip_address: + example: 8.8.8.8 + type: string + type: object + dto.CreateNodePoolRequest: + properties: + name: + type: string + role: + enum: + - master + - worker + type: string + type: object + dto.CreateRecordSetRequest: + properties: + name: + description: |- + Name relative to domain ("endpoint") OR FQDN ("endpoint.example.com"). + Server normalizes to relative. + maxLength: 253 + type: string + ttl: + maximum: 86400 + minimum: 1 + type: integer + type: + type: string + values: + items: + type: string + type: array + uniqueItems: false + required: + - name + - type + type: object + dto.CreateSSHRequest: + properties: + bits: + description: Only for RSA + type: integer + comment: + example: deploy@autoglue + type: string + name: + type: string + type: + description: '"rsa" (default) or "ed25519"' + type: string + type: object + dto.CreateServerRequest: + properties: + hostname: + type: string + private_ip_address: + type: string + public_ip_address: + type: string + role: + enum: + - master + - worker + - bastion + example: master|worker|bastion + type: string + ssh_key_id: + type: string + ssh_user: + type: string + status: + enum: + - pending + - provisioning + - ready + - failed + example: pending|provisioning|ready|failed + type: string + type: object + dto.CreateTaintRequest: + properties: + effect: + type: string + key: + type: string + value: + type: string + type: object + dto.CredentialOut: + properties: + account_id: + type: string + created_at: + type: string + id: + type: string + kind: + type: string + name: + type: string + provider: + type: string + region: + type: string + schema_version: + type: integer + scope: + type: object + scope_kind: + type: string + scope_version: + type: integer + updated_at: + type: string + type: object + dto.DomainResponse: + properties: + created_at: + type: string + credential_id: + type: string + domain_name: + type: string + id: + type: string + last_error: + type: string + organization_id: + type: string + status: + type: string + updated_at: + type: string + zone_id: + type: string + type: object + dto.EnqueueRequest: + properties: + payload: + type: object + queue: + example: default + type: string + run_at: + example: "2025-11-05T08:00:00Z" + type: string + type: + example: email.send + type: string + type: object + dto.JWK: + properties: + alg: + example: RS256 + type: string + e: + example: AQAB + type: string + kid: + example: 7c6f1d0a-7a98-4e6a-9dbf-6b1af4b9f345 + type: string + kty: + example: RSA + type: string + "n": + type: string + use: + example: sig + type: string + x: + type: string + type: object + dto.JWKS: + properties: + keys: + items: + $ref: '#/components/schemas/dto.JWK' + type: array + uniqueItems: false + type: object + dto.Job: + properties: + attempts: + example: 0 + type: integer + created_at: + example: "2025-11-04T09:30:00Z" + type: string + id: + example: 01HF7SZK8Z8WG1M3J7S2Z8M2N6 + type: string + last_error: + example: error message + type: string + max_attempts: + example: 3 + type: integer + payload: {} + queue: + example: default + type: string + run_at: + example: "2025-11-04T09:30:00Z" + type: string + status: + $ref: '#/components/schemas/dto.JobStatus' + type: + example: email.send + type: string + updated_at: + example: "2025-11-04T09:30:00Z" + type: string + type: object + dto.JobStatus: + enum: + - queued|running|succeeded|failed|canceled|retrying|scheduled + example: queued + type: string + x-enum-varnames: + - StatusQueued + - StatusRunning + - StatusSucceeded + - StatusFailed + - StatusCanceled + - StatusRetrying + - StatusScheduled + dto.LabelResponse: + properties: + created_at: + type: string + id: + type: string + key: + type: string + organization_id: + type: string + updated_at: + type: string + value: + type: string + type: object + dto.LoadBalancerResponse: + properties: + created_at: + type: string + id: + type: string + kind: + enum: + - glueops|public + type: string + name: + type: string + organization_id: + type: string + private_ip_address: + type: string + public_ip_address: + type: string + updated_at: + type: string + type: object + dto.LogoutRequest: + properties: + refresh_token: + example: m0l9o8rT3t0V8d3eFf... + type: string + type: object + dto.NodePoolResponse: + properties: + annotations: + items: + $ref: '#/components/schemas/dto.AnnotationResponse' + type: array + uniqueItems: false + created_at: + type: string + id: + type: string + labels: + items: + $ref: '#/components/schemas/dto.LabelResponse' + type: array + uniqueItems: false + name: + type: string + organization_id: + type: string + role: + enum: + - master + - worker + type: string + servers: + items: + $ref: '#/components/schemas/dto.ServerResponse' + type: array + uniqueItems: false + taints: + items: + $ref: '#/components/schemas/dto.TaintResponse' + type: array + uniqueItems: false + updated_at: + type: string + type: object + dto.PageJob: + properties: items: - $ref: '#/definitions/models.UserEmail' - type: array - id: - description: 'example: 3fa85f64-5717-4562-b3fc-2c963f66afa6' - format: uuid - type: string - is_admin: - type: boolean - is_disabled: - type: boolean - organizations: - items: - $ref: '#/definitions/models.Organization' - type: array - primary_email: - type: string - updated_at: - format: date-time - type: string - type: object - handlers.memberOut: - properties: - email: - type: string - role: - description: owner/admin/member - type: string - user_id: - format: uuid - type: string - type: object - handlers.memberUpsertReq: - properties: - role: - example: member - type: string - user_id: - format: uuid - type: string - type: object - handlers.orgCreateReq: - properties: - domain: - example: acme.com - type: string - name: - example: Acme Corp - type: string - type: object - handlers.orgKeyCreateReq: - properties: - expires_in_hours: - example: 720 - type: integer - name: - example: automation-bot - type: string - type: object - handlers.orgKeyCreateResp: - properties: - created_at: - type: string - expires_at: - type: string - id: - type: string - name: - type: string - org_key: - description: 'shown once:' - type: string - org_secret: - description: 'shown once:' - type: string - scope: - description: '"org"' - type: string - type: object - handlers.orgUpdateReq: - properties: - domain: - type: string - name: - type: string - type: object - handlers.updateMeRequest: - properties: - display_name: - type: string - type: object - handlers.userAPIKeyOut: - properties: - created_at: - type: string - expires_at: - type: string - id: - format: uuid - type: string - last_used_at: - type: string - name: - type: string - plain: - description: 'Shown only on create:' - type: string - scope: - description: '"user"' - type: string - type: object - models.APIKey: - properties: - created_at: - format: date-time - type: string - expires_at: - format: date-time - type: string - id: - format: uuid - type: string - last_used_at: - format: date-time - type: string - name: - type: string - org_id: - format: uuid - type: string - prefix: - type: string - revoked: - type: boolean - scope: - type: string - updated_at: - format: date-time - type: string - user_id: - format: uuid - type: string - type: object - models.Organization: - properties: - created_at: - format: date-time - type: string - domain: - type: string - id: - description: 'example: 3fa85f64-5717-4562-b3fc-2c963f66afa6' - format: uuid - type: string - name: - type: string - updated_at: - format: date-time - type: string - type: object - models.User: - properties: - avatar_url: - type: string - created_at: - format: date-time - type: string - display_name: - type: string - id: - description: 'example: 3fa85f64-5717-4562-b3fc-2c963f66afa6' - format: uuid - type: string - is_admin: - type: boolean - is_disabled: - type: boolean - primary_email: - type: string - updated_at: - format: date-time - type: string - type: object - models.UserEmail: - properties: - created_at: - format: date-time - type: string - email: - type: string - id: - description: 'example: 3fa85f64-5717-4562-b3fc-2c963f66afa6' - format: uuid - type: string - is_primary: - type: boolean - is_verified: - type: boolean - updated_at: - format: date-time - type: string - user: - $ref: '#/definitions/models.User' - user_id: - format: uuid - type: string - type: object - utils.ErrorResponse: - properties: - code: - description: |- - A machine-readable error code, e.g. "validation_error" - example: validation_error - type: string - message: - description: |- - Human-readable message - example: slug is required - type: string - type: object + items: + $ref: '#/components/schemas/dto.Job' + type: array + uniqueItems: false + page: + example: 1 + type: integer + page_size: + example: 25 + type: integer + total: + example: 120 + type: integer + type: object + dto.QueueInfo: + properties: + failed: + example: 5 + type: integer + name: + example: default + type: string + pending: + example: 42 + type: integer + running: + example: 3 + type: integer + scheduled: + example: 7 + type: integer + type: object + dto.RecordSetResponse: + properties: + created_at: + type: string + domain_id: + type: string + fingerprint: + type: string + id: + type: string + last_error: + type: string + name: + type: string + owner: + type: string + status: + type: string + ttl: + type: integer + type: + type: string + updated_at: + type: string + values: + description: '[]string JSON' + type: object + type: object + dto.RefreshRequest: + properties: + refresh_token: + example: m0l9o8rT3t0V8d3eFf... + type: string + type: object + dto.ServerResponse: + properties: + created_at: + type: string + hostname: + type: string + id: + type: string + organization_id: + type: string + private_ip_address: + type: string + public_ip_address: + type: string + role: + enum: + - master + - worker + - bastion + example: master|worker|bastion + type: string + ssh_key_id: + type: string + ssh_user: + type: string + status: + enum: + - pending + - provisioning + - ready + - failed + example: pending|provisioning|ready|failed + type: string + updated_at: + type: string + type: object + dto.SetKubeconfigRequest: + properties: + kubeconfig: + type: string + type: object + dto.SshResponse: + properties: + created_at: + type: string + fingerprint: + type: string + id: + type: string + name: + type: string + organization_id: + type: string + public_key: + type: string + updated_at: + type: string + type: object + dto.SshRevealResponse: + properties: + created_at: + type: string + fingerprint: + type: string + id: + type: string + name: + type: string + organization_id: + type: string + private_key: + type: string + public_key: + type: string + updated_at: + type: string + type: object + dto.TaintResponse: + properties: + created_at: + type: string + effect: + type: string + id: + type: string + key: + type: string + organization_id: + type: string + updated_at: + type: string + value: + type: string + type: object + dto.TokenPair: + properties: + access_token: + example: eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6Ij... + type: string + expires_in: + example: 3600 + type: integer + refresh_token: + example: m0l9o8rT3t0V8d3eFf.... + type: string + token_type: + example: Bearer + type: string + type: object + dto.UpdateAnnotationRequest: + properties: + key: + type: string + value: + type: string + type: object + dto.UpdateClusterRequest: + properties: + name: + type: string + provider: + type: string + region: + type: string + type: object + dto.UpdateCredentialRequest: + properties: + account_id: + type: string + name: + type: string + region: + type: string + scope: + type: object + scope_kind: + type: string + scope_version: + type: integer + secret: + description: set if rotating + type: object + type: object + dto.UpdateDomainRequest: + properties: + credential_id: + type: string + domain_name: + type: string + status: + enum: + - pending + - provisioning + - ready + - failed + type: string + zone_id: + maxLength: 128 + type: string + type: object + dto.UpdateLabelRequest: + properties: + key: + type: string + value: + type: string + type: object + dto.UpdateLoadBalancerRequest: + properties: + kind: + enum: + - glueops|public + example: public + type: string + name: + example: glue + type: string + private_ip_address: + example: 192.168.0.2 + type: string + public_ip_address: + example: 8.8.8.8 + type: string + type: object + dto.UpdateNodePoolRequest: + properties: + name: + type: string + role: + enum: + - master + - worker + type: string + type: object + dto.UpdateRecordSetRequest: + properties: + name: + description: Any change flips status back to pending (worker will UPSERT) + maxLength: 253 + type: string + status: + enum: + - pending + - provisioning + - ready + - failed + type: string + ttl: + maximum: 86400 + minimum: 1 + type: integer + type: + type: string + values: + items: + type: string + type: array + uniqueItems: false + type: object + dto.UpdateServerRequest: + properties: + hostname: + type: string + private_ip_address: + type: string + public_ip_address: + type: string + role: + enum: + - master + - worker + - bastion + example: master|worker|bastion + type: string + ssh_key_id: + type: string + ssh_user: + type: string + status: + enum: + - pending + - provisioning + - ready + - failed + example: pending|provisioning|ready|failed + type: string + type: object + dto.UpdateTaintRequest: + properties: + effect: + type: string + key: + type: string + value: + type: string + type: object + handlers.HealthStatus: + properties: + status: + example: ok + type: string + type: object + handlers.VersionResponse: + properties: + built: + example: "2025-11-08T12:34:56Z" + type: string + builtBy: + example: ci + type: string + commit: + example: a1b2c3d + type: string + commitTime: + example: "2025-11-08T12:31:00Z" + type: string + go: + example: go1.23.3 + type: string + goArch: + example: amd64 + type: string + goOS: + example: linux + type: string + modified: + example: false + type: boolean + revision: + example: a1b2c3d4e5f6abcdef + type: string + vcs: + example: git + type: string + version: + example: 1.4.2 + type: string + type: object + handlers.createUserKeyRequest: + properties: + expires_in_hours: + description: optional TTL + type: integer + name: + type: string + type: object + handlers.meResponse: + properties: + avatar_url: + type: string + created_at: + format: date-time + type: string + display_name: + type: string + emails: + items: + $ref: '#/components/schemas/models.UserEmail' + type: array + uniqueItems: false + id: + description: 'example: 3fa85f64-5717-4562-b3fc-2c963f66afa6' + format: uuid + type: string + is_admin: + type: boolean + is_disabled: + type: boolean + organizations: + items: + $ref: '#/components/schemas/models.Organization' + type: array + uniqueItems: false + primary_email: + type: string + updated_at: + format: date-time + type: string + type: object + handlers.memberOut: + properties: + email: + type: string + role: + description: owner/admin/member + type: string + user_id: + format: uuid + type: string + type: object + handlers.memberUpsertReq: + properties: + role: + example: member + type: string + user_id: + format: uuid + type: string + type: object + handlers.orgCreateReq: + properties: + domain: + example: acme.com + type: string + name: + example: Acme Corp + type: string + type: object + handlers.orgKeyCreateReq: + properties: + expires_in_hours: + example: 720 + type: integer + name: + example: automation-bot + type: string + type: object + handlers.orgKeyCreateResp: + properties: + created_at: + type: string + expires_at: + type: string + id: + type: string + name: + type: string + org_key: + description: 'shown once:' + type: string + org_secret: + description: 'shown once:' + type: string + scope: + description: '"org"' + type: string + type: object + handlers.orgUpdateReq: + properties: + domain: + type: string + name: + type: string + type: object + handlers.updateMeRequest: + properties: + display_name: + type: string + type: object + handlers.userAPIKeyOut: + properties: + created_at: + type: string + expires_at: + type: string + id: + format: uuid + type: string + last_used_at: + type: string + name: + type: string + plain: + description: 'Shown only on create:' + type: string + scope: + description: '"user"' + type: string + type: object + models.APIKey: + properties: + created_at: + format: date-time + type: string + expires_at: + format: date-time + type: string + id: + format: uuid + type: string + last_used_at: + format: date-time + type: string + name: + type: string + org_id: + format: uuid + type: string + prefix: + type: string + revoked: + type: boolean + scope: + type: string + updated_at: + format: date-time + type: string + user_id: + format: uuid + type: string + type: object + models.Organization: + properties: + created_at: + format: date-time + type: string + domain: + type: string + id: + description: 'example: 3fa85f64-5717-4562-b3fc-2c963f66afa6' + format: uuid + type: string + name: + type: string + updated_at: + format: date-time + type: string + type: object + models.User: + properties: + avatar_url: + type: string + created_at: + format: date-time + type: string + display_name: + type: string + id: + description: 'example: 3fa85f64-5717-4562-b3fc-2c963f66afa6' + format: uuid + type: string + is_admin: + type: boolean + is_disabled: + type: boolean + primary_email: + type: string + updated_at: + format: date-time + type: string + type: object + models.UserEmail: + properties: + created_at: + format: date-time + type: string + email: + type: string + id: + description: 'example: 3fa85f64-5717-4562-b3fc-2c963f66afa6' + format: uuid + type: string + is_primary: + type: boolean + is_verified: + type: boolean + updated_at: + format: date-time + type: string + user: + $ref: '#/components/schemas/models.User' + user_id: + format: uuid + type: string + type: object + utils.ErrorResponse: + properties: + code: + description: |- + A machine-readable error code, e.g. "validation_error" + example: validation_error + type: string + message: + description: |- + Human-readable message + example: slug is required + type: string + type: object + securitySchemes: + "": + description: Org-level secret + in: header + name: X-ORG-SECRET + type: apiKey +externalDocs: + description: "" + url: "" info: contact: name: GlueOps description: API for managing K3s clusters across cloud providers title: AutoGlue API version: "1.0" +openapi: 3.1.0 paths: /.well-known/jwks.json: get: description: Returns the JSON Web Key Set for token verification operationId: getJWKS - produces: - - application/json responses: "200": + content: + application/json: + schema: + $ref: '#/components/schemas/dto.JWKS' description: OK - schema: - $ref: '#/definitions/dto.JWKS' summary: Get JWKS tags: - Auth /admin/archer/jobs: get: - consumes: - - application/json description: Paginated background jobs with optional filters. Search `q` may match id, type, error, payload (implementation-dependent). operationId: AdminListArcherJobs parameters: - description: Filter by status - enum: - - queued - - running - - succeeded - - failed - - canceled - - retrying - - scheduled in: query name: status - type: string + schema: + enum: + - queued + - running + - succeeded + - failed + - canceled + - retrying + - scheduled + type: string - description: Filter by queue name / worker name in: query name: queue - type: string + schema: + type: string - description: Free-text search in: query name: q - type: string - - default: 1 - description: Page number + schema: + type: string + - description: Page number in: query name: page - type: integer - - default: 25 - description: Items per page + schema: + default: 1 + type: integer + - description: Items per page in: query - maximum: 100 - minimum: 1 name: page_size - type: integer - produces: - - application/json + schema: + default: 25 + maximum: 100 + minimum: 1 + type: integer responses: "200": + content: + application/json: + schema: + $ref: '#/components/schemas/dto.PageJob' description: OK - schema: - $ref: '#/definitions/dto.PageJob' "401": + content: + application/json: + schema: + type: string description: Unauthorized - schema: - type: string "403": + content: + application/json: + schema: + type: string description: forbidden - schema: - type: string "500": + content: + application/json: + schema: + type: string description: internal error - schema: - type: string security: - BearerAuth: [] summary: List Archer jobs (admin) tags: - ArcherAdmin post: - consumes: - - application/json description: Create a job immediately or schedule it for the future via `run_at`. operationId: AdminEnqueueArcherJob - parameters: - - description: Job parameters - in: body - name: body + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/dto.EnqueueRequest' + description: Job parameters required: true - schema: - $ref: '#/definitions/dto.EnqueueRequest' - produces: - - application/json responses: "200": + content: + application/json: + schema: + $ref: '#/components/schemas/dto.Job' description: OK - schema: - $ref: '#/definitions/dto.Job' "400": + content: + application/json: + schema: + type: string description: invalid json or missing fields - schema: - type: string "401": + content: + application/json: + schema: + type: string description: Unauthorized - schema: - type: string "403": + content: + application/json: + schema: + type: string description: forbidden - schema: - type: string "500": + content: + application/json: + schema: + type: string description: internal error - schema: - type: string security: - BearerAuth: [] summary: Enqueue a new Archer job (admin) @@ -1161,8 +1277,6 @@ paths: - ArcherAdmin /admin/archer/jobs/{id}/cancel: post: - consumes: - - application/json description: Set job status to canceled if cancellable. For running jobs, this only affects future picks; wire to Archer if you need active kill. operationId: AdminCancelArcherJob @@ -1171,30 +1285,44 @@ paths: in: path name: id required: true - type: string - produces: - - application/json + schema: + type: string + requestBody: + content: + application/json: + schema: + type: object responses: "200": + content: + application/json: + schema: + $ref: '#/components/schemas/dto.Job' description: OK - schema: - $ref: '#/definitions/dto.Job' "400": + content: + application/json: + schema: + type: string description: invalid job or not cancellable - schema: - type: string "401": + content: + application/json: + schema: + type: string description: Unauthorized - schema: - type: string "403": + content: + application/json: + schema: + type: string description: forbidden - schema: - type: string "404": + content: + application/json: + schema: + type: string description: not found - schema: - type: string security: - BearerAuth: [] summary: Cancel an Archer job (admin) @@ -1202,8 +1330,6 @@ paths: - ArcherAdmin /admin/archer/jobs/{id}/retry: post: - consumes: - - application/json description: Marks the job retriable (DB flip). Swap this for an Archer admin call if you expose one. operationId: AdminRetryArcherJob @@ -1212,30 +1338,44 @@ paths: in: path name: id required: true - type: string - produces: - - application/json + schema: + type: string + requestBody: + content: + application/json: + schema: + type: object responses: "200": + content: + application/json: + schema: + $ref: '#/components/schemas/dto.Job' description: OK - schema: - $ref: '#/definitions/dto.Job' "400": + content: + application/json: + schema: + type: string description: invalid job or not eligible - schema: - type: string "401": + content: + application/json: + schema: + type: string description: Unauthorized - schema: - type: string "403": + content: + application/json: + schema: + type: string description: forbidden - schema: - type: string "404": + content: + application/json: + schema: + type: string description: not found - schema: - type: string security: - BearerAuth: [] summary: Retry a failed/canceled Archer job (admin) @@ -1243,31 +1383,35 @@ paths: - ArcherAdmin /admin/archer/queues: get: - consumes: - - application/json description: Summary metrics per queue (pending, running, failed, scheduled). operationId: AdminListArcherQueues - produces: - - application/json responses: "200": + content: + application/json: + schema: + items: + $ref: '#/components/schemas/dto.QueueInfo' + type: array description: OK - schema: - items: - $ref: '#/definitions/dto.QueueInfo' - type: array "401": + content: + application/json: + schema: + type: string description: Unauthorized - schema: - type: string "403": + content: + application/json: + schema: + type: string description: forbidden - schema: - type: string "500": + content: + application/json: + schema: + type: string description: internal error - schema: - type: string security: - BearerAuth: [] summary: List Archer queues (admin) @@ -1275,8 +1419,6 @@ paths: - ArcherAdmin /annotations: get: - consumes: - - application/json description: 'Returns annotations for the organization in X-Org-ID. Filters: `key`, `value`, and `q` (key contains). Add `include=node_pools` to include linked node pools.' @@ -1285,40 +1427,50 @@ paths: - description: Organization UUID in: header name: X-Org-ID - type: string + schema: + type: string - description: Exact key in: query name: key - type: string + schema: + type: string - description: Exact value in: query name: value - type: string + schema: + type: string - description: key contains (case-insensitive) in: query name: q - type: string - produces: - - application/json + schema: + type: string responses: "200": + content: + application/json: + schema: + items: + $ref: '#/components/schemas/dto.AnnotationResponse' + type: array description: OK - schema: - items: - $ref: '#/definitions/dto.AnnotationResponse' - type: array "401": + content: + application/json: + schema: + type: string description: Unauthorized - schema: - type: string "403": + content: + application/json: + schema: + type: string description: organization required - schema: - type: string "500": + content: + application/json: + schema: + type: string description: failed to list annotations - schema: - type: string security: - BearerAuth: [] - OrgKeyAuth: [] @@ -1327,44 +1479,52 @@ paths: tags: - Annotations post: - consumes: - - application/json description: Creates an annotation. operationId: CreateAnnotation parameters: - description: Organization UUID in: header name: X-Org-ID - type: string - - description: Annotation payload - in: body - name: body - required: true schema: - $ref: '#/definitions/dto.CreateAnnotationRequest' - produces: - - application/json + type: string + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/dto.CreateAnnotationRequest' + description: Annotation payload + required: true responses: "201": + content: + application/json: + schema: + $ref: '#/components/schemas/dto.AnnotationResponse' description: Created - schema: - $ref: '#/definitions/dto.AnnotationResponse' "400": + content: + application/json: + schema: + type: string description: invalid json / missing fields - schema: - type: string "401": + content: + application/json: + schema: + type: string description: Unauthorized - schema: - type: string "403": + content: + application/json: + schema: + type: string description: organization required - schema: - type: string "500": + content: + application/json: + schema: + type: string description: create failed - schema: - type: string security: - BearerAuth: [] - OrgKeyAuth: [] @@ -1374,43 +1534,47 @@ paths: - Annotations /annotations/{id}: delete: - consumes: - - application/json description: Permanently deletes the annotation. operationId: DeleteAnnotation parameters: - description: Organization UUID in: header name: X-Org-ID - type: string + schema: + type: string - description: Annotation ID (UUID) in: path name: id required: true - type: string - produces: - - application/json + schema: + type: string responses: "204": description: No Content - schema: - type: string "400": + content: + application/json: + schema: + type: string description: invalid id - schema: - type: string "401": + content: + application/json: + schema: + type: string description: Unauthorized - schema: - type: string "403": + content: + application/json: + schema: + type: string description: organization required - schema: - type: string "500": + content: + application/json: + schema: + type: string description: delete failed - schema: - type: string security: - BearerAuth: [] - OrgKeyAuth: [] @@ -1419,8 +1583,6 @@ paths: tags: - Annotations get: - consumes: - - application/json description: Returns one annotation. Add `include=node_pools` to include node pools. operationId: GetAnnotation @@ -1428,39 +1590,51 @@ paths: - description: Organization UUID in: header name: X-Org-ID - type: string + schema: + type: string - description: Annotation ID (UUID) in: path name: id required: true - type: string - produces: - - application/json + schema: + type: string responses: "200": + content: + application/json: + schema: + $ref: '#/components/schemas/dto.AnnotationResponse' description: OK - schema: - $ref: '#/definitions/dto.AnnotationResponse' "400": + content: + application/json: + schema: + type: string description: invalid id - schema: - type: string "401": + content: + application/json: + schema: + type: string description: Unauthorized - schema: - type: string "403": + content: + application/json: + schema: + type: string description: organization required - schema: - type: string "404": + content: + application/json: + schema: + type: string description: not found - schema: - type: string "500": + content: + application/json: + schema: + type: string description: fetch failed - schema: - type: string security: - BearerAuth: [] - OrgKeyAuth: [] @@ -1469,53 +1643,64 @@ paths: tags: - Annotations patch: - consumes: - - application/json description: Partially update annotation fields. operationId: UpdateAnnotation parameters: - description: Organization UUID in: header name: X-Org-ID - type: string + schema: + type: string - description: Annotation ID (UUID) in: path name: id required: true - type: string - - description: Fields to update - in: body - name: body - required: true schema: - $ref: '#/definitions/dto.UpdateAnnotationRequest' - produces: - - application/json + type: string + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/dto.UpdateAnnotationRequest' + description: Fields to update + required: true responses: "200": + content: + application/json: + schema: + $ref: '#/components/schemas/dto.AnnotationResponse' description: OK - schema: - $ref: '#/definitions/dto.AnnotationResponse' "400": + content: + application/json: + schema: + type: string description: invalid id / invalid json - schema: - type: string "401": + content: + application/json: + schema: + type: string description: Unauthorized - schema: - type: string "403": + content: + application/json: + schema: + type: string description: organization required - schema: - type: string "404": + content: + application/json: + schema: + type: string description: not found - schema: - type: string "500": + content: + application/json: + schema: + type: string description: update failed - schema: - type: string security: - BearerAuth: [] - OrgKeyAuth: [] @@ -1531,14 +1716,15 @@ paths: in: path name: provider required: true - type: string - produces: - - application/json + schema: + type: string responses: "200": + content: + application/json: + schema: + $ref: '#/components/schemas/dto.TokenPair' description: OK - schema: - $ref: '#/definitions/dto.TokenPair' summary: Handle social login callback tags: - Auth @@ -1551,31 +1737,28 @@ paths: in: path name: provider required: true - type: string - produces: - - application/json + schema: + type: string responses: "200": + content: + application/json: + schema: + $ref: '#/components/schemas/dto.AuthStartResponse' description: OK - schema: - $ref: '#/definitions/dto.AuthStartResponse' summary: Begin social login tags: - Auth /auth/logout: post: - consumes: - - application/json operationId: Logout - parameters: - - description: Refresh token - in: body - name: body + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/dto.LogoutRequest' + description: Refresh token required: true - schema: - $ref: '#/definitions/dto.LogoutRequest' - produces: - - application/json responses: "204": description: No Content @@ -1584,23 +1767,21 @@ paths: - Auth /auth/refresh: post: - consumes: - - application/json operationId: Refresh - parameters: - - description: Refresh token - in: body - name: body + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/dto.RefreshRequest' + description: Refresh token required: true - schema: - $ref: '#/definitions/dto.RefreshRequest' - produces: - - application/json responses: "200": + content: + application/json: + schema: + $ref: '#/components/schemas/dto.TokenPair' description: OK - schema: - $ref: '#/definitions/dto.TokenPair' summary: Rotate refresh token tags: - Auth @@ -1613,32 +1794,40 @@ paths: - description: Organization UUID in: header name: X-Org-ID - type: string + schema: + type: string - description: Name contains (case-insensitive) in: query name: q - type: string - produces: - - application/json + schema: + type: string responses: "200": + content: + application/json: + schema: + items: + $ref: '#/components/schemas/dto.ClusterResponse' + type: array description: OK - schema: - items: - $ref: '#/definitions/dto.ClusterResponse' - type: array "401": + content: + application/json: + schema: + type: string description: Unauthorized - schema: - type: string "403": + content: + application/json: + schema: + type: string description: organization required - schema: - type: string "500": + content: + application/json: + schema: + type: string description: failed to list clusters - schema: - type: string security: - BearerAuth: [] - OrgKeyAuth: [] @@ -1647,45 +1836,53 @@ paths: tags: - Clusters post: - consumes: - - application/json - description: Creates a cluster. If `kubeconfig` is provided, it will be encrypted - per-organization and stored securely (never returned). + description: Creates a cluster. Status is managed by the system and starts as + `pre_pending` for validation. operationId: CreateCluster parameters: - description: Organization UUID in: header name: X-Org-ID - type: string - - description: payload - in: body - name: body - required: true schema: - $ref: '#/definitions/dto.CreateClusterRequest' - produces: - - application/json + type: string + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/dto.CreateClusterRequest' + description: payload + required: true responses: "201": + content: + application/json: + schema: + $ref: '#/components/schemas/dto.ClusterResponse' description: Created - schema: - $ref: '#/definitions/dto.ClusterResponse' "400": + content: + application/json: + schema: + type: string description: invalid json - schema: - type: string "401": + content: + application/json: + schema: + type: string description: Unauthorized - schema: - type: string "403": + content: + application/json: + schema: + type: string description: organization required - schema: - type: string "500": + content: + application/json: + schema: + type: string description: create failed - schema: - type: string security: - BearerAuth: [] - OrgKeyAuth: [] @@ -1693,10 +1890,956 @@ paths: summary: Create cluster (org scoped) tags: - Clusters + /clusters/{clusterID}: + delete: + description: Deletes the cluster. Related resources are cleaned up via DB constraints + (e.g. CASCADE). + operationId: DeleteCluster + parameters: + - description: Organization UUID + in: header + name: X-Org-ID + schema: + type: string + - description: Cluster ID + in: path + name: clusterID + required: true + schema: + type: string + responses: + "204": + content: + application/json: + schema: + type: string + description: deleted + "400": + content: + application/json: + schema: + type: string + description: bad request + "401": + content: + application/json: + schema: + type: string + description: Unauthorized + "403": + content: + application/json: + schema: + type: string + description: organization required + "404": + content: + application/json: + schema: + type: string + description: cluster not found + "500": + content: + application/json: + schema: + type: string + description: db error + security: + - BearerAuth: [] + - OrgKeyAuth: [] + - OrgSecretAuth: [] + summary: Delete a cluster (org scoped) + tags: + - Clusters + get: + description: Returns a cluster with all related resources (domain, record set, + load balancers, bastion, node pools). + operationId: GetCluster + parameters: + - description: Organization UUID + in: header + name: X-Org-ID + schema: + type: string + - description: Cluster ID + in: path + name: clusterID + required: true + schema: + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/dto.ClusterResponse' + description: OK + "400": + content: + application/json: + schema: + type: string + description: bad request + "401": + content: + application/json: + schema: + type: string + description: Unauthorized + "403": + content: + application/json: + schema: + type: string + description: organization required + "404": + content: + application/json: + schema: + type: string + description: cluster not found + "500": + content: + application/json: + schema: + type: string + description: db error + security: + - BearerAuth: [] + - OrgKeyAuth: [] + - OrgSecretAuth: [] + summary: Get a single cluster by ID (org scoped) + tags: + - Clusters + patch: + description: Updates the cluster name, provider, and/or region. Status is managed + by the system. + operationId: UpdateCluster + parameters: + - description: Organization UUID + in: header + name: X-Org-ID + schema: + type: string + - description: Cluster ID + in: path + name: clusterID + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/dto.UpdateClusterRequest' + description: payload + required: true + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/dto.ClusterResponse' + description: OK + "400": + content: + application/json: + schema: + type: string + description: bad request + "401": + content: + application/json: + schema: + type: string + description: Unauthorized + "403": + content: + application/json: + schema: + type: string + description: organization required + "404": + content: + application/json: + schema: + type: string + description: cluster not found + "500": + content: + application/json: + schema: + type: string + description: db error + security: + - BearerAuth: [] + - OrgKeyAuth: [] + - OrgSecretAuth: [] + summary: Update basic cluster details (org scoped) + tags: + - Clusters + /clusters/{clusterID}/apps-load-balancer: + delete: + description: Clears apps_load_balancer_id on the cluster. + operationId: DetachAppsLoadBalancer + parameters: + - description: Organization UUID + in: header + name: X-Org-ID + schema: + type: string + - description: Cluster ID + in: path + name: clusterID + required: true + schema: + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/dto.ClusterResponse' + description: OK + "400": + content: + application/json: + schema: + type: string + description: bad request + "401": + content: + application/json: + schema: + type: string + description: Unauthorized + "403": + content: + application/json: + schema: + type: string + description: organization required + "404": + content: + application/json: + schema: + type: string + description: cluster not found + "500": + content: + application/json: + schema: + type: string + description: db error + security: + - BearerAuth: [] + - OrgKeyAuth: [] + - OrgSecretAuth: [] + summary: Detach the apps load balancer from a cluster + tags: + - Clusters + post: + description: Sets apps_load_balancer_id on the cluster. + operationId: AttachAppsLoadBalancer + parameters: + - description: Organization UUID + in: header + name: X-Org-ID + schema: + type: string + - description: Cluster ID + in: path + name: clusterID + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/dto.AttachLoadBalancerRequest' + description: payload + required: true + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/dto.ClusterResponse' + description: OK + "400": + content: + application/json: + schema: + type: string + description: bad request + "401": + content: + application/json: + schema: + type: string + description: Unauthorized + "403": + content: + application/json: + schema: + type: string + description: organization required + "404": + content: + application/json: + schema: + type: string + description: cluster or load balancer not found + "500": + content: + application/json: + schema: + type: string + description: db error + security: + - BearerAuth: [] + - OrgKeyAuth: [] + - OrgSecretAuth: [] + summary: Attach an apps load balancer to a cluster + tags: + - Clusters + /clusters/{clusterID}/bastion: + delete: + description: Clears bastion_server_id on the cluster. + operationId: DetachBastionServer + parameters: + - description: Organization UUID + in: header + name: X-Org-ID + schema: + type: string + - description: Cluster ID + in: path + name: clusterID + required: true + schema: + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/dto.ClusterResponse' + description: OK + "400": + content: + application/json: + schema: + type: string + description: bad request + "401": + content: + application/json: + schema: + type: string + description: Unauthorized + "403": + content: + application/json: + schema: + type: string + description: organization required + "404": + content: + application/json: + schema: + type: string + description: cluster not found + "500": + content: + application/json: + schema: + type: string + description: db error + security: + - BearerAuth: [] + - OrgKeyAuth: [] + - OrgSecretAuth: [] + summary: Detach the bastion server from a cluster + tags: + - Clusters + post: + description: Sets bastion_server_id on the cluster. + operationId: AttachBastionServer + parameters: + - description: Organization UUID + in: header + name: X-Org-ID + schema: + type: string + - description: Cluster ID + in: path + name: clusterID + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/dto.AttachBastionRequest' + description: payload + required: true + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/dto.ClusterResponse' + description: OK + "400": + content: + application/json: + schema: + type: string + description: bad request + "401": + content: + application/json: + schema: + type: string + description: Unauthorized + "403": + content: + application/json: + schema: + type: string + description: organization required + "404": + content: + application/json: + schema: + type: string + description: cluster or server not found + "500": + content: + application/json: + schema: + type: string + description: db error + security: + - BearerAuth: [] + - OrgKeyAuth: [] + - OrgSecretAuth: [] + summary: Attach a bastion server to a cluster + tags: + - Clusters + /clusters/{clusterID}/captain-domain: + delete: + description: Clears captain_domain_id on the cluster. This will likely cause + the cluster to become incomplete. + operationId: DetachCaptainDomain + parameters: + - description: Organization UUID + in: header + name: X-Org-ID + schema: + type: string + - description: Cluster ID + in: path + name: clusterID + required: true + schema: + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/dto.ClusterResponse' + description: OK + "400": + content: + application/json: + schema: + type: string + description: bad request + "401": + content: + application/json: + schema: + type: string + description: Unauthorized + "403": + content: + application/json: + schema: + type: string + description: organization required + "404": + content: + application/json: + schema: + type: string + description: cluster not found + "500": + content: + application/json: + schema: + type: string + description: db error + security: + - BearerAuth: [] + - OrgKeyAuth: [] + - OrgSecretAuth: [] + summary: Detach the captain domain from a cluster + tags: + - Clusters + post: + description: Sets captain_domain_id on the cluster. Validation of shape happens + asynchronously. + operationId: AttachCaptainDomain + parameters: + - description: Organization UUID + in: header + name: X-Org-ID + schema: + type: string + - description: Cluster ID + in: path + name: clusterID + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/dto.AttachCaptainDomainRequest' + description: payload + required: true + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/dto.ClusterResponse' + description: OK + "400": + content: + application/json: + schema: + type: string + description: bad request + "401": + content: + application/json: + schema: + type: string + description: Unauthorized + "403": + content: + application/json: + schema: + type: string + description: organization required + "404": + content: + application/json: + schema: + type: string + description: cluster or domain not found + "500": + content: + application/json: + schema: + type: string + description: db error + security: + - BearerAuth: [] + - OrgKeyAuth: [] + - OrgSecretAuth: [] + summary: Attach a captain domain to a cluster + tags: + - Clusters + /clusters/{clusterID}/control-plane-record-set: + delete: + description: Clears control_plane_record_set_id on the cluster. + operationId: DetachControlPlaneRecordSet + parameters: + - description: Organization UUID + in: header + name: X-Org-ID + schema: + type: string + - description: Cluster ID + in: path + name: clusterID + required: true + schema: + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/dto.ClusterResponse' + description: OK + "400": + content: + application/json: + schema: + type: string + description: bad request + "401": + content: + application/json: + schema: + type: string + description: Unauthorized + "403": + content: + application/json: + schema: + type: string + description: organization required + "404": + content: + application/json: + schema: + type: string + description: cluster not found + "500": + content: + application/json: + schema: + type: string + description: db error + security: + - BearerAuth: [] + - OrgKeyAuth: [] + - OrgSecretAuth: [] + summary: Detach the control plane record set from a cluster + tags: + - Clusters + post: + description: Sets control_plane_record_set_id on the cluster. + operationId: AttachControlPlaneRecordSet + parameters: + - description: Organization UUID + in: header + name: X-Org-ID + schema: + type: string + - description: Cluster ID + in: path + name: clusterID + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/dto.AttachRecordSetRequest' + description: payload + required: true + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/dto.ClusterResponse' + description: OK + "400": + content: + application/json: + schema: + type: string + description: bad request + "401": + content: + application/json: + schema: + type: string + description: Unauthorized + "403": + content: + application/json: + schema: + type: string + description: organization required + "404": + content: + application/json: + schema: + type: string + description: cluster or record set not found + "500": + content: + application/json: + schema: + type: string + description: db error + security: + - BearerAuth: [] + - OrgKeyAuth: [] + - OrgSecretAuth: [] + summary: Attach a control plane record set to a cluster + tags: + - Clusters + /clusters/{clusterID}/glueops-load-balancer: + delete: + description: Clears glueops_load_balancer_id on the cluster. + operationId: DetachGlueOpsLoadBalancer + parameters: + - description: Organization UUID + in: header + name: X-Org-ID + schema: + type: string + - description: Cluster ID + in: path + name: clusterID + required: true + schema: + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/dto.ClusterResponse' + description: OK + "400": + content: + application/json: + schema: + type: string + description: bad request + "401": + content: + application/json: + schema: + type: string + description: Unauthorized + "403": + content: + application/json: + schema: + type: string + description: organization required + "404": + content: + application/json: + schema: + type: string + description: cluster not found + "500": + content: + application/json: + schema: + type: string + description: db error + security: + - BearerAuth: [] + - OrgKeyAuth: [] + - OrgSecretAuth: [] + summary: Detach the GlueOps/control-plane load balancer from a cluster + tags: + - Clusters + post: + description: Sets glueops_load_balancer_id on the cluster. + operationId: AttachGlueOpsLoadBalancer + parameters: + - description: Organization UUID + in: header + name: X-Org-ID + schema: + type: string + - description: Cluster ID + in: path + name: clusterID + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/dto.AttachLoadBalancerRequest' + description: payload + required: true + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/dto.ClusterResponse' + description: OK + "400": + content: + application/json: + schema: + type: string + description: bad request + "401": + content: + application/json: + schema: + type: string + description: Unauthorized + "403": + content: + application/json: + schema: + type: string + description: organization required + "404": + content: + application/json: + schema: + type: string + description: cluster or load balancer not found + "500": + content: + application/json: + schema: + type: string + description: db error + security: + - BearerAuth: [] + - OrgKeyAuth: [] + - OrgSecretAuth: [] + summary: Attach a GlueOps/control-plane load balancer to a cluster + tags: + - Clusters + /clusters/{clusterID}/kubeconfig: + delete: + description: Removes the encrypted kubeconfig, IV, and tag from the cluster + record. + operationId: ClearClusterKubeconfig + parameters: + - description: Organization UUID + in: header + name: X-Org-ID + schema: + type: string + - description: Cluster ID + in: path + name: clusterID + required: true + schema: + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/dto.ClusterResponse' + description: OK + "400": + content: + application/json: + schema: + type: string + description: bad request + "401": + content: + application/json: + schema: + type: string + description: Unauthorized + "403": + content: + application/json: + schema: + type: string + description: organization required + "404": + content: + application/json: + schema: + type: string + description: cluster not found + "500": + content: + application/json: + schema: + type: string + description: db error + security: + - BearerAuth: [] + - OrgKeyAuth: [] + - OrgSecretAuth: [] + summary: Clear the kubeconfig for a cluster + tags: + - Clusters + post: + description: Stores the kubeconfig encrypted per organization. The kubeconfig + is never returned in responses. + operationId: SetClusterKubeconfig + parameters: + - description: Organization UUID + in: header + name: X-Org-ID + schema: + type: string + - description: Cluster ID + in: path + name: clusterID + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/dto.SetKubeconfigRequest' + description: payload + required: true + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/dto.ClusterResponse' + description: OK + "400": + content: + application/json: + schema: + type: string + description: bad request + "401": + content: + application/json: + schema: + type: string + description: Unauthorized + "403": + content: + application/json: + schema: + type: string + description: organization required + "404": + content: + application/json: + schema: + type: string + description: cluster not found + "500": + content: + application/json: + schema: + type: string + description: db error + security: + - BearerAuth: [] + - OrgKeyAuth: [] + - OrgSecretAuth: [] + summary: Set (or replace) the kubeconfig for a cluster + tags: + - Clusters /credentials: get: - consumes: - - application/json description: Returns credential metadata for the current org. Secrets are never returned. operationId: ListCredentials @@ -1704,40 +2847,50 @@ paths: - description: Organization ID (UUID) in: header name: X-Org-ID - type: string + schema: + type: string - description: Filter by provider (e.g., aws) in: query name: provider - type: string + schema: + type: string - description: Filter by kind (e.g., aws_access_key) in: query name: kind - type: string + schema: + type: string - description: Filter by scope kind (provider/service/resource) in: query name: scope_kind - type: string - produces: - - application/json + schema: + type: string responses: "200": + content: + application/json: + schema: + items: + $ref: '#/components/schemas/dto.CredentialOut' + type: array description: OK - schema: - items: - $ref: '#/definitions/dto.CredentialOut' - type: array "401": + content: + application/json: + schema: + type: string description: Unauthorized - schema: - type: string "403": + content: + application/json: + schema: + type: string description: organization required - schema: - type: string "500": + content: + application/json: + schema: + type: string description: internal server error - schema: - type: string security: - BearerAuth: [] - OrgKeyAuth: [] @@ -1746,39 +2899,45 @@ paths: tags: - Credentials post: - consumes: - - application/json operationId: CreateCredential parameters: - description: Organization ID (UUID) in: header name: X-Org-ID - type: string - - description: Credential payload - in: body - name: body - required: true schema: - $ref: '#/definitions/dto.CreateCredentialRequest' - produces: - - application/json + type: string + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/dto.CreateCredentialRequest' + description: Credential payload + required: true responses: "201": + content: + application/json: + schema: + $ref: '#/components/schemas/dto.CredentialOut' description: Created - schema: - $ref: '#/definitions/dto.CredentialOut' "401": + content: + application/json: + schema: + type: string description: Unauthorized - schema: - type: string "403": + content: + application/json: + schema: + type: string description: organization required - schema: - type: string "500": + content: + application/json: + schema: + type: string description: internal server error - schema: - type: string security: - BearerAuth: [] - OrgKeyAuth: [] @@ -1788,28 +2947,28 @@ paths: - Credentials /credentials/{id}: delete: - consumes: - - application/json operationId: DeleteCredential parameters: - description: Organization ID (UUID) in: header name: X-Org-ID - type: string + schema: + type: string - description: Credential ID (UUID) in: path name: id required: true - type: string - produces: - - application/json + schema: + type: string responses: "204": description: No Content "404": + content: + application/json: + schema: + type: string description: not found - schema: - type: string security: - BearerAuth: [] - OrgKeyAuth: [] @@ -1818,38 +2977,44 @@ paths: tags: - Credentials get: - consumes: - - application/json operationId: GetCredential parameters: - description: Organization ID (UUID) in: header name: X-Org-ID - type: string + schema: + type: string - description: Credential ID (UUID) in: path name: id required: true - type: string - produces: - - application/json + schema: + type: string responses: "200": + content: + application/json: + schema: + $ref: '#/components/schemas/dto.CredentialOut' description: OK - schema: - $ref: '#/definitions/dto.CredentialOut' "401": + content: + application/json: + schema: + type: string description: Unauthorized - schema: - type: string "403": + content: + application/json: + schema: + type: string description: organization required - schema: - type: string "500": + content: + application/json: + schema: + type: string description: internal server error - schema: - type: string security: - BearerAuth: [] - OrgKeyAuth: [] @@ -1858,40 +3023,45 @@ paths: tags: - Credentials patch: - consumes: - - application/json operationId: UpdateCredential parameters: - description: Organization ID (UUID) in: header name: X-Org-ID - type: string + schema: + type: string - description: Credential ID (UUID) in: path name: id required: true - type: string - - description: Fields to update - in: body - name: body - required: true schema: - $ref: '#/definitions/dto.UpdateCredentialRequest' - produces: - - application/json + type: string + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/dto.UpdateCredentialRequest' + description: Fields to update + required: true responses: "200": + content: + application/json: + schema: + $ref: '#/components/schemas/dto.CredentialOut' description: OK - schema: - $ref: '#/definitions/dto.CredentialOut' "403": + content: + application/json: + schema: + type: string description: X-Org-ID required - schema: - type: string "404": + content: + application/json: + schema: + type: string description: not found - schema: - type: string security: - BearerAuth: [] - OrgKeyAuth: [] @@ -1901,35 +3071,44 @@ paths: - Credentials /credentials/{id}/reveal: post: - consumes: - - application/json operationId: RevealCredential parameters: - description: Organization ID (UUID) in: header name: X-Org-ID - type: string + schema: + type: string - description: Credential ID (UUID) in: path name: id required: true - type: string - produces: - - application/json + schema: + type: string + requestBody: + content: + application/json: + schema: + type: object responses: "200": + content: + application/json: + schema: + additionalProperties: {} + type: object description: OK - schema: - additionalProperties: true - type: object "403": + content: + application/json: + schema: + type: string description: organization required - schema: - type: string "404": + content: + application/json: + schema: + type: string description: not found - schema: - type: string security: - BearerAuth: [] - OrgKeyAuth: [] @@ -1939,8 +3118,6 @@ paths: - Credentials /dns/domains: get: - consumes: - - application/json description: 'Returns domains for X-Org-ID. Filters: `domain_name`, `status`, `q` (contains).' operationId: ListDomains @@ -1948,40 +3125,50 @@ paths: - description: Organization UUID in: header name: X-Org-ID - type: string + schema: + type: string - description: Exact domain name (lowercase, no trailing dot) in: query name: domain_name - type: string + schema: + type: string - description: pending|provisioning|ready|failed in: query name: status - type: string + schema: + type: string - description: Domain contains (case-insensitive) in: query name: q - type: string - produces: - - application/json + schema: + type: string responses: "200": + content: + application/json: + schema: + items: + $ref: '#/components/schemas/dto.DomainResponse' + type: array description: OK - schema: - items: - $ref: '#/definitions/dto.DomainResponse' - type: array "401": + content: + application/json: + schema: + type: string description: Unauthorized - schema: - type: string "403": + content: + application/json: + schema: + type: string description: organization required - schema: - type: string "500": + content: + application/json: + schema: + type: string description: db error - schema: - type: string security: - BearerAuth: [] - OrgKeyAuth: [] @@ -1990,8 +3177,6 @@ paths: tags: - DNS post: - consumes: - - application/json description: Creates a domain bound to a Route 53 scoped credential. Archer will backfill ZoneID if omitted. operationId: CreateDomain @@ -1999,36 +3184,46 @@ paths: - description: Organization UUID in: header name: X-Org-ID - type: string - - description: Domain payload - in: body - name: body - required: true schema: - $ref: '#/definitions/dto.CreateDomainRequest' - produces: - - application/json + type: string + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/dto.CreateDomainRequest' + description: Domain payload + required: true responses: "201": + content: + application/json: + schema: + $ref: '#/components/schemas/dto.DomainResponse' description: Created - schema: - $ref: '#/definitions/dto.DomainResponse' "400": + content: + application/json: + schema: + type: string description: validation error - schema: - type: string "401": + content: + application/json: + schema: + type: string description: Unauthorized - schema: - type: string "403": + content: + application/json: + schema: + type: string description: organization required - schema: - type: string "500": + content: + application/json: + schema: + type: string description: db error - schema: - type: string security: - BearerAuth: [] - OrgKeyAuth: [] @@ -2038,49 +3233,56 @@ paths: - DNS /dns/domains/{domain_id}/records: get: - consumes: - - application/json description: 'Filters: `name`, `type`, `status`.' operationId: ListRecordSets parameters: - description: Organization UUID in: header name: X-Org-ID - type: string + schema: + type: string - description: Domain ID (UUID) in: path name: domain_id required: true - type: string + schema: + type: string - description: Exact relative name or FQDN (server normalizes) in: query name: name - type: string + schema: + type: string - description: RR type (A, AAAA, CNAME, TXT, MX, NS, SRV, CAA) in: query name: type - type: string + schema: + type: string - description: pending|provisioning|ready|failed in: query name: status - type: string - produces: - - application/json + schema: + type: string responses: "200": + content: + application/json: + schema: + items: + $ref: '#/components/schemas/dto.RecordSetResponse' + type: array description: OK - schema: - items: - $ref: '#/definitions/dto.RecordSetResponse' - type: array "403": + content: + application/json: + schema: + type: string description: organization required - schema: - type: string "404": + content: + application/json: + schema: + type: string description: domain not found - schema: - type: string security: - BearerAuth: [] - OrgKeyAuth: [] @@ -2089,44 +3291,51 @@ paths: tags: - DNS post: - consumes: - - application/json operationId: CreateRecordSet parameters: - description: Organization UUID in: header name: X-Org-ID - type: string + schema: + type: string - description: Domain ID (UUID) in: path name: domain_id required: true - type: string - - description: Record set payload - in: body - name: body - required: true schema: - $ref: '#/definitions/dto.CreateRecordSetRequest' - produces: - - application/json + type: string + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/dto.CreateRecordSetRequest' + description: Record set payload + required: true responses: "201": + content: + application/json: + schema: + $ref: '#/components/schemas/dto.RecordSetResponse' description: Created - schema: - $ref: '#/definitions/dto.RecordSetResponse' "400": + content: + application/json: + schema: + type: string description: validation error - schema: - type: string "403": + content: + application/json: + schema: + type: string description: organization required - schema: - type: string "404": + content: + application/json: + schema: + type: string description: domain not found - schema: - type: string security: - BearerAuth: [] - OrgKeyAuth: [] @@ -2136,32 +3345,34 @@ paths: - DNS /dns/domains/{id}: delete: - consumes: - - application/json operationId: DeleteDomain parameters: - description: Organization UUID in: header name: X-Org-ID - type: string + schema: + type: string - description: Domain ID (UUID) in: path name: id required: true - type: string - produces: - - application/json + schema: + type: string responses: "204": description: No Content "403": + content: + application/json: + schema: + type: string description: organization required - schema: - type: string "404": + content: + application/json: + schema: + type: string description: not found - schema: - type: string security: - BearerAuth: [] - OrgKeyAuth: [] @@ -2170,38 +3381,44 @@ paths: tags: - DNS get: - consumes: - - application/json operationId: GetDomain parameters: - description: Organization UUID in: header name: X-Org-ID - type: string + schema: + type: string - description: Domain ID (UUID) in: path name: id required: true - type: string - produces: - - application/json + schema: + type: string responses: "200": + content: + application/json: + schema: + $ref: '#/components/schemas/dto.DomainResponse' description: OK - schema: - $ref: '#/definitions/dto.DomainResponse' "401": + content: + application/json: + schema: + type: string description: Unauthorized - schema: - type: string "403": + content: + application/json: + schema: + type: string description: organization required - schema: - type: string "404": + content: + application/json: + schema: + type: string description: not found - schema: - type: string security: - BearerAuth: [] - OrgKeyAuth: [] @@ -2210,44 +3427,51 @@ paths: tags: - DNS patch: - consumes: - - application/json operationId: UpdateDomain parameters: - description: Organization UUID in: header name: X-Org-ID - type: string + schema: + type: string - description: Domain ID (UUID) in: path name: id required: true - type: string - - description: Fields to update - in: body - name: body - required: true schema: - $ref: '#/definitions/dto.UpdateDomainRequest' - produces: - - application/json + type: string + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/dto.UpdateDomainRequest' + description: Fields to update + required: true responses: "200": + content: + application/json: + schema: + $ref: '#/components/schemas/dto.DomainResponse' description: OK - schema: - $ref: '#/definitions/dto.DomainResponse' "400": + content: + application/json: + schema: + type: string description: validation error - schema: - type: string "403": + content: + application/json: + schema: + type: string description: organization required - schema: - type: string "404": + content: + application/json: + schema: + type: string description: not found - schema: - type: string security: - BearerAuth: [] - OrgKeyAuth: [] @@ -2257,32 +3481,34 @@ paths: - DNS /dns/records/{id}: delete: - consumes: - - application/json operationId: DeleteRecordSet parameters: - description: Organization UUID in: header name: X-Org-ID - type: string + schema: + type: string - description: Record Set ID (UUID) in: path name: id required: true - type: string - produces: - - application/json + schema: + type: string responses: "204": description: No Content "403": + content: + application/json: + schema: + type: string description: organization required - schema: - type: string "404": + content: + application/json: + schema: + type: string description: not found - schema: - type: string security: - BearerAuth: [] - OrgKeyAuth: [] @@ -2292,44 +3518,51 @@ paths: tags: - DNS patch: - consumes: - - application/json operationId: UpdateRecordSet parameters: - description: Organization UUID in: header name: X-Org-ID - type: string + schema: + type: string - description: Record Set ID (UUID) in: path name: id required: true - type: string - - description: Fields to update - in: body - name: body - required: true schema: - $ref: '#/definitions/dto.UpdateRecordSetRequest' - produces: - - application/json + type: string + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/dto.UpdateRecordSetRequest' + description: Fields to update + required: true responses: "200": + content: + application/json: + schema: + $ref: '#/components/schemas/dto.RecordSetResponse' description: OK - schema: - $ref: '#/definitions/dto.RecordSetResponse' "400": + content: + application/json: + schema: + type: string description: validation error - schema: - type: string "403": + content: + application/json: + schema: + type: string description: organization required - schema: - type: string "404": + content: + application/json: + schema: + type: string description: not found - schema: - type: string security: - BearerAuth: [] - OrgKeyAuth: [] @@ -2339,24 +3572,20 @@ paths: - DNS /healthz: get: - consumes: - - application/json description: Returns 200 OK when the service is up operationId: HealthCheck // operationId - produces: - - application/json responses: "200": + content: + application/json: + schema: + $ref: '#/components/schemas/handlers.HealthStatus' description: OK - schema: - $ref: '#/definitions/handlers.HealthStatus' summary: Basic health check tags: - Health /labels: get: - consumes: - - application/json description: 'Returns node labels for the organization in X-Org-ID. Filters: `key`, `value`, and `q` (key contains). Add `include=node_pools` to include linked node groups.' @@ -2365,40 +3594,50 @@ paths: - description: Organization UUID in: header name: X-Org-ID - type: string + schema: + type: string - description: Exact key in: query name: key - type: string + schema: + type: string - description: Exact value in: query name: value - type: string + schema: + type: string - description: Key contains (case-insensitive) in: query name: q - type: string - produces: - - application/json + schema: + type: string responses: "200": + content: + application/json: + schema: + items: + $ref: '#/components/schemas/dto.LabelResponse' + type: array description: OK - schema: - items: - $ref: '#/definitions/dto.LabelResponse' - type: array "401": + content: + application/json: + schema: + type: string description: Unauthorized - schema: - type: string "403": + content: + application/json: + schema: + type: string description: organization required - schema: - type: string "500": + content: + application/json: + schema: + type: string description: failed to list node taints - schema: - type: string security: - BearerAuth: [] - OrgKeyAuth: [] @@ -2407,44 +3646,52 @@ paths: tags: - Labels post: - consumes: - - application/json description: Creates a label. operationId: CreateLabel parameters: - description: Organization UUID in: header name: X-Org-ID - type: string - - description: Label payload - in: body - name: body - required: true schema: - $ref: '#/definitions/dto.CreateLabelRequest' - produces: - - application/json + type: string + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/dto.CreateLabelRequest' + description: Label payload + required: true responses: "201": + content: + application/json: + schema: + $ref: '#/components/schemas/dto.LabelResponse' description: Created - schema: - $ref: '#/definitions/dto.LabelResponse' "400": + content: + application/json: + schema: + type: string description: invalid json / missing fields / invalid node_pool_ids - schema: - type: string "401": + content: + application/json: + schema: + type: string description: Unauthorized - schema: - type: string "403": + content: + application/json: + schema: + type: string description: organization required - schema: - type: string "500": + content: + application/json: + schema: + type: string description: create failed - schema: - type: string security: - BearerAuth: [] - OrgKeyAuth: [] @@ -2454,43 +3701,47 @@ paths: - Labels /labels/{id}: delete: - consumes: - - application/json description: Permanently deletes the label. operationId: DeleteLabel parameters: - description: Organization UUID in: header name: X-Org-ID - type: string + schema: + type: string - description: Label ID (UUID) in: path name: id required: true - type: string - produces: - - application/json + schema: + type: string responses: "204": description: No Content - schema: - type: string "400": + content: + application/json: + schema: + type: string description: invalid id - schema: - type: string "401": + content: + application/json: + schema: + type: string description: Unauthorized - schema: - type: string "403": + content: + application/json: + schema: + type: string description: organization required - schema: - type: string "500": + content: + application/json: + schema: + type: string description: delete failed - schema: - type: string security: - BearerAuth: [] - OrgKeyAuth: [] @@ -2499,47 +3750,57 @@ paths: tags: - Labels get: - consumes: - - application/json description: Returns one label. operationId: GetLabel parameters: - description: Organization UUID in: header name: X-Org-ID - type: string + schema: + type: string - description: Label ID (UUID) in: path name: id required: true - type: string - produces: - - application/json + schema: + type: string responses: "200": + content: + application/json: + schema: + $ref: '#/components/schemas/dto.LabelResponse' description: OK - schema: - $ref: '#/definitions/dto.LabelResponse' "400": + content: + application/json: + schema: + type: string description: invalid id - schema: - type: string "401": + content: + application/json: + schema: + type: string description: Unauthorized - schema: - type: string "403": + content: + application/json: + schema: + type: string description: organization required - schema: - type: string "404": + content: + application/json: + schema: + type: string description: not found - schema: - type: string "500": + content: + application/json: + schema: + type: string description: fetch failed - schema: - type: string security: - BearerAuth: [] - OrgKeyAuth: [] @@ -2548,53 +3809,64 @@ paths: tags: - Labels patch: - consumes: - - application/json description: Partially update label fields. operationId: UpdateLabel parameters: - description: Organization UUID in: header name: X-Org-ID - type: string + schema: + type: string - description: Label ID (UUID) in: path name: id required: true - type: string - - description: Fields to update - in: body - name: body - required: true schema: - $ref: '#/definitions/dto.UpdateLabelRequest' - produces: - - application/json + type: string + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/dto.UpdateLabelRequest' + description: Fields to update + required: true responses: "200": + content: + application/json: + schema: + $ref: '#/components/schemas/dto.LabelResponse' description: OK - schema: - $ref: '#/definitions/dto.LabelResponse' "400": + content: + application/json: + schema: + type: string description: invalid id / invalid json - schema: - type: string "401": + content: + application/json: + schema: + type: string description: Unauthorized - schema: - type: string "403": + content: + application/json: + schema: + type: string description: organization required - schema: - type: string "404": + content: + application/json: + schema: + type: string description: not found - schema: - type: string "500": + content: + application/json: + schema: + type: string description: update failed - schema: - type: string security: - BearerAuth: [] - OrgKeyAuth: [] @@ -2602,16 +3874,246 @@ paths: summary: Update label (org scoped) tags: - Labels + /load-balancers: + get: + description: Returns load balancers for the organization in X-Org-ID. + operationId: ListLoadBalancers + parameters: + - description: Organization UUID + in: header + name: X-Org-ID + schema: + type: string + responses: + "200": + content: + application/json: + schema: + items: + $ref: '#/components/schemas/dto.LoadBalancerResponse' + type: array + description: OK + "401": + content: + application/json: + schema: + type: string + description: Unauthorized + "403": + content: + application/json: + schema: + type: string + description: organization required + "500": + content: + application/json: + schema: + type: string + description: failed to list clusters + security: + - BearerAuth: [] + - OrgKeyAuth: [] + - OrgSecretAuth: [] + summary: List load balancers (org scoped) + tags: + - LoadBalancers + post: + operationId: CreateLoadBalancer + parameters: + - description: Organization UUID + in: header + name: X-Org-ID + schema: + type: string + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/dto.CreateLoadBalancerRequest' + description: Record set payload + required: true + responses: + "201": + content: + application/json: + schema: + $ref: '#/components/schemas/dto.LoadBalancerResponse' + description: Created + "400": + content: + application/json: + schema: + type: string + description: validation error + "403": + content: + application/json: + schema: + type: string + description: organization required + "404": + content: + application/json: + schema: + type: string + description: domain not found + security: + - BearerAuth: [] + - OrgKeyAuth: [] + - OrgSecretAuth: [] + summary: Create a load balancer + tags: + - LoadBalancers + /load-balancers/{id}: + delete: + operationId: DeleteLoadBalancer + parameters: + - description: Organization UUID + in: header + name: X-Org-ID + schema: + type: string + - description: Load Balancer ID (UUID) + in: path + name: id + required: true + schema: + type: string + responses: + "204": + description: No Content + "403": + content: + application/json: + schema: + type: string + description: organization required + "404": + content: + application/json: + schema: + type: string + description: not found + security: + - BearerAuth: [] + - OrgKeyAuth: [] + - OrgSecretAuth: [] + summary: Delete a load balancer + tags: + - LoadBalancers + get: + description: Returns load balancer for the organization in X-Org-ID. + operationId: GetLoadBalancers + parameters: + - description: Organization UUID + in: header + name: X-Org-ID + schema: + type: string + - description: LoadBalancer ID (UUID) + in: path + name: id + required: true + schema: + type: string + responses: + "200": + content: + application/json: + schema: + items: + $ref: '#/components/schemas/dto.LoadBalancerResponse' + type: array + description: OK + "401": + content: + application/json: + schema: + type: string + description: Unauthorized + "403": + content: + application/json: + schema: + type: string + description: organization required + "500": + content: + application/json: + schema: + type: string + description: failed to list clusters + security: + - BearerAuth: [] + - OrgKeyAuth: [] + - OrgSecretAuth: [] + summary: Get a load balancer (org scoped) + tags: + - LoadBalancers + patch: + operationId: UpdateLoadBalancer + parameters: + - description: Organization UUID + in: header + name: X-Org-ID + schema: + type: string + - description: Load Balancer ID (UUID) + in: path + name: id + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/dto.UpdateLoadBalancerRequest' + description: Fields to update + required: true + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/dto.LoadBalancerResponse' + description: OK + "400": + content: + application/json: + schema: + type: string + description: validation error + "403": + content: + application/json: + schema: + type: string + description: organization required + "404": + content: + application/json: + schema: + type: string + description: not found + security: + - BearerAuth: [] + - OrgKeyAuth: [] + - OrgSecretAuth: [] + summary: Update a load balancer (org scoped) + tags: + - LoadBalancers /me: get: operationId: GetMe - produces: - - application/json responses: "200": + content: + application/json: + schema: + $ref: '#/components/schemas/handlers.meResponse' description: OK - schema: - $ref: '#/definitions/handlers.meResponse' security: - BearerAuth: [] - ApiKeyAuth: [] @@ -2619,23 +4121,21 @@ paths: tags: - Me patch: - consumes: - - application/json operationId: UpdateMe - parameters: - - description: Patch profile - in: body - name: body + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/handlers.updateMeRequest' + description: Patch profile required: true - schema: - $ref: '#/definitions/handlers.updateMeRequest' - produces: - - application/json responses: "200": + content: + application/json: + schema: + $ref: '#/components/schemas/models.User' description: OK - schema: - $ref: '#/definitions/models.User' security: - BearerAuth: [] - ApiKeyAuth: [] @@ -2645,15 +4145,15 @@ paths: /me/api-keys: get: operationId: ListUserAPIKeys - produces: - - application/json responses: "200": + content: + application/json: + schema: + items: + $ref: '#/components/schemas/handlers.userAPIKeyOut' + type: array description: OK - schema: - items: - $ref: '#/definitions/handlers.userAPIKeyOut' - type: array security: - BearerAuth: [] - ApiKeyAuth: [] @@ -2661,25 +4161,23 @@ paths: tags: - MeAPIKeys post: - consumes: - - application/json description: Returns the plaintext key once. Store it securely on the client side. operationId: CreateUserAPIKey - parameters: - - description: Key options - in: body - name: body + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/handlers.createUserKeyRequest' + description: Key options required: true - schema: - $ref: '#/definitions/handlers.createUserKeyRequest' - produces: - - application/json responses: "201": + content: + application/json: + schema: + $ref: '#/components/schemas/handlers.userAPIKeyOut' description: Created - schema: - $ref: '#/definitions/handlers.userAPIKeyOut' security: - BearerAuth: [] - ApiKeyAuth: [] @@ -2694,9 +4192,8 @@ paths: in: path name: id required: true - type: string - produces: - - application/json + schema: + type: string responses: "204": description: No Content @@ -2707,40 +4204,46 @@ paths: - MeAPIKeys /node-pools: get: - consumes: - - application/json description: Returns node pools for the organization in X-Org-ID. operationId: ListNodePools parameters: - description: Organization UUID in: header name: X-Org-ID - type: string + schema: + type: string - description: Name contains (case-insensitive) in: query name: q - type: string - produces: - - application/json + schema: + type: string responses: "200": + content: + application/json: + schema: + items: + $ref: '#/components/schemas/dto.NodePoolResponse' + type: array description: OK - schema: - items: - $ref: '#/definitions/dto.NodePoolResponse' - type: array "401": + content: + application/json: + schema: + type: string description: Unauthorized - schema: - type: string "403": + content: + application/json: + schema: + type: string description: organization required - schema: - type: string "500": + content: + application/json: + schema: + type: string description: failed to list node pools - schema: - type: string security: - BearerAuth: [] - OrgKeyAuth: [] @@ -2749,44 +4252,52 @@ paths: tags: - NodePools post: - consumes: - - application/json description: Creates a node pool. Optionally attach initial servers. operationId: CreateNodePool parameters: - description: Organization UUID in: header name: X-Org-ID - type: string - - description: NodePool payload - in: body - name: body - required: true schema: - $ref: '#/definitions/dto.CreateNodePoolRequest' - produces: - - application/json + type: string + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/dto.CreateNodePoolRequest' + description: NodePool payload + required: true responses: "201": + content: + application/json: + schema: + $ref: '#/components/schemas/dto.NodePoolResponse' description: Created - schema: - $ref: '#/definitions/dto.NodePoolResponse' "400": + content: + application/json: + schema: + type: string description: invalid json / missing fields / invalid server_ids - schema: - type: string "401": + content: + application/json: + schema: + type: string description: Unauthorized - schema: - type: string "403": + content: + application/json: + schema: + type: string description: organization required - schema: - type: string "500": + content: + application/json: + schema: + type: string description: create failed - schema: - type: string security: - BearerAuth: [] - OrgKeyAuth: [] @@ -2796,43 +4307,47 @@ paths: - NodePools /node-pools/{id}: delete: - consumes: - - application/json description: Permanently deletes the node pool. operationId: DeleteNodePool parameters: - description: Organization UUID in: header name: X-Org-ID - type: string + schema: + type: string - description: Node Pool ID (UUID) in: path name: id required: true - type: string - produces: - - application/json + schema: + type: string responses: "204": description: No Content - schema: - type: string "400": + content: + application/json: + schema: + type: string description: invalid id - schema: - type: string "401": + content: + application/json: + schema: + type: string description: Unauthorized - schema: - type: string "403": + content: + application/json: + schema: + type: string description: organization required - schema: - type: string "500": + content: + application/json: + schema: + type: string description: delete failed - schema: - type: string security: - BearerAuth: [] - OrgKeyAuth: [] @@ -2841,47 +4356,57 @@ paths: tags: - NodePools get: - consumes: - - application/json description: Returns one node pool. Add `include=servers` to include servers. operationId: GetNodePool parameters: - description: Organization UUID in: header name: X-Org-ID - type: string + schema: + type: string - description: Node Pool ID (UUID) in: path name: id required: true - type: string - produces: - - application/json + schema: + type: string responses: "200": + content: + application/json: + schema: + $ref: '#/components/schemas/dto.NodePoolResponse' description: OK - schema: - $ref: '#/definitions/dto.NodePoolResponse' "400": + content: + application/json: + schema: + type: string description: invalid id - schema: - type: string "401": + content: + application/json: + schema: + type: string description: Unauthorized - schema: - type: string "403": + content: + application/json: + schema: + type: string description: organization required - schema: - type: string "404": + content: + application/json: + schema: + type: string description: not found - schema: - type: string "500": + content: + application/json: + schema: + type: string description: fetch failed - schema: - type: string security: - BearerAuth: [] - OrgKeyAuth: [] @@ -2890,53 +4415,64 @@ paths: tags: - NodePools patch: - consumes: - - application/json description: Partially update node pool fields. operationId: UpdateNodePool parameters: - description: Organization UUID in: header name: X-Org-ID - type: string + schema: + type: string - description: Node Pool ID (UUID) in: path name: id required: true - type: string - - description: Fields to update - in: body - name: body - required: true schema: - $ref: '#/definitions/dto.UpdateNodePoolRequest' - produces: - - application/json + type: string + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/dto.UpdateNodePoolRequest' + description: Fields to update + required: true responses: "200": + content: + application/json: + schema: + $ref: '#/components/schemas/dto.NodePoolResponse' description: OK - schema: - $ref: '#/definitions/dto.NodePoolResponse' "400": + content: + application/json: + schema: + type: string description: invalid id / invalid json - schema: - type: string "401": + content: + application/json: + schema: + type: string description: Unauthorized - schema: - type: string "403": + content: + application/json: + schema: + type: string description: organization required - schema: - type: string "404": + content: + application/json: + schema: + type: string description: not found - schema: - type: string "500": + content: + application/json: + schema: + type: string description: update failed - schema: - type: string security: - BearerAuth: [] - OrgKeyAuth: [] @@ -2946,48 +4482,58 @@ paths: - NodePools /node-pools/{id}/annotations: get: - consumes: - - application/json operationId: ListNodePoolAnnotations parameters: - description: Organization UUID in: header name: X-Org-ID - type: string + schema: + type: string - description: Node Pool ID (UUID) in: path name: id required: true - type: string - produces: - - application/json + schema: + type: string responses: "200": + content: + application/json: + schema: + items: + $ref: '#/components/schemas/dto.AnnotationResponse' + type: array description: OK - schema: - items: - $ref: '#/definitions/dto.AnnotationResponse' - type: array "400": + content: + application/json: + schema: + type: string description: invalid id - schema: - type: string "401": + content: + application/json: + schema: + type: string description: Unauthorized - schema: - type: string "403": + content: + application/json: + schema: + type: string description: organization required - schema: - type: string "404": + content: + application/json: + schema: + type: string description: not found - schema: - type: string "500": + content: + application/json: + schema: + type: string description: fetch failed - schema: - type: string security: - BearerAuth: [] - OrgKeyAuth: [] @@ -2996,52 +4542,63 @@ paths: tags: - NodePools post: - consumes: - - application/json operationId: AttachNodePoolAnnotations parameters: - description: Organization UUID in: header name: X-Org-ID - type: string + schema: + type: string - description: Node Group ID (UUID) in: path name: id required: true - type: string - - description: Annotation IDs to attach - in: body - name: body - required: true schema: - $ref: '#/definitions/dto.AttachAnnotationsRequest' - produces: - - application/json + type: string + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/dto.AttachAnnotationsRequest' + description: Annotation IDs to attach + required: true responses: "204": + content: + application/json: + schema: + type: string description: No Content - schema: - type: string "400": + content: + application/json: + schema: + type: string description: invalid id / invalid server_ids - schema: - type: string "401": + content: + application/json: + schema: + type: string description: Unauthorized - schema: - type: string "403": + content: + application/json: + schema: + type: string description: organization required - schema: - type: string "404": + content: + application/json: + schema: + type: string description: not found - schema: - type: string "500": + content: + application/json: + schema: + type: string description: attach failed - schema: - type: string security: - BearerAuth: [] - OrgKeyAuth: [] @@ -3051,51 +4608,62 @@ paths: - NodePools /node-pools/{id}/annotations/{annotationId}: delete: - consumes: - - application/json operationId: DetachNodePoolAnnotation parameters: - description: Organization UUID in: header name: X-Org-ID - type: string + schema: + type: string - description: Node Pool ID (UUID) in: path name: id required: true - type: string + schema: + type: string - description: Annotation ID (UUID) in: path name: annotationId required: true - type: string - produces: - - application/json + schema: + type: string responses: "204": + content: + application/json: + schema: + type: string description: No Content - schema: - type: string "400": + content: + application/json: + schema: + type: string description: invalid id - schema: - type: string "401": + content: + application/json: + schema: + type: string description: Unauthorized - schema: - type: string "403": + content: + application/json: + schema: + type: string description: organization required - schema: - type: string "404": + content: + application/json: + schema: + type: string description: not found - schema: - type: string "500": + content: + application/json: + schema: + type: string description: detach failed - schema: - type: string security: - BearerAuth: [] - OrgKeyAuth: [] @@ -3105,48 +4673,58 @@ paths: - NodePools /node-pools/{id}/labels: get: - consumes: - - application/json operationId: ListNodePoolLabels parameters: - description: Organization UUID in: header name: X-Org-ID - type: string + schema: + type: string - description: Label Pool ID (UUID) in: path name: id required: true - type: string - produces: - - application/json + schema: + type: string responses: "200": + content: + application/json: + schema: + items: + $ref: '#/components/schemas/dto.LabelResponse' + type: array description: OK - schema: - items: - $ref: '#/definitions/dto.LabelResponse' - type: array "400": + content: + application/json: + schema: + type: string description: invalid id - schema: - type: string "401": + content: + application/json: + schema: + type: string description: Unauthorized - schema: - type: string "403": + content: + application/json: + schema: + type: string description: organization required - schema: - type: string "404": + content: + application/json: + schema: + type: string description: not found - schema: - type: string "500": + content: + application/json: + schema: + type: string description: fetch failed - schema: - type: string security: - BearerAuth: [] - OrgKeyAuth: [] @@ -3155,52 +4733,63 @@ paths: tags: - NodePools post: - consumes: - - application/json operationId: AttachNodePoolLabels parameters: - description: Organization UUID in: header name: X-Org-ID - type: string + schema: + type: string - description: Node Pool ID (UUID) in: path name: id required: true - type: string - - description: Label IDs to attach - in: body - name: body - required: true schema: - $ref: '#/definitions/dto.AttachLabelsRequest' - produces: - - application/json + type: string + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/dto.AttachLabelsRequest' + description: Label IDs to attach + required: true responses: "204": + content: + application/json: + schema: + type: string description: No Content - schema: - type: string "400": + content: + application/json: + schema: + type: string description: invalid id / invalid server_ids - schema: - type: string "401": + content: + application/json: + schema: + type: string description: Unauthorized - schema: - type: string "403": + content: + application/json: + schema: + type: string description: organization required - schema: - type: string "404": + content: + application/json: + schema: + type: string description: not found - schema: - type: string "500": + content: + application/json: + schema: + type: string description: attach failed - schema: - type: string security: - BearerAuth: [] - OrgKeyAuth: [] @@ -3210,51 +4799,62 @@ paths: - NodePools /node-pools/{id}/labels/{labelId}: delete: - consumes: - - application/json operationId: DetachNodePoolLabel parameters: - description: Organization UUID in: header name: X-Org-ID - type: string + schema: + type: string - description: Node Pool ID (UUID) in: path name: id required: true - type: string + schema: + type: string - description: Label ID (UUID) in: path name: labelId required: true - type: string - produces: - - application/json + schema: + type: string responses: "204": + content: + application/json: + schema: + type: string description: No Content - schema: - type: string "400": + content: + application/json: + schema: + type: string description: invalid id - schema: - type: string "401": + content: + application/json: + schema: + type: string description: Unauthorized - schema: - type: string "403": + content: + application/json: + schema: + type: string description: organization required - schema: - type: string "404": + content: + application/json: + schema: + type: string description: not found - schema: - type: string "500": + content: + application/json: + schema: + type: string description: detach failed - schema: - type: string security: - BearerAuth: [] - OrgKeyAuth: [] @@ -3264,48 +4864,58 @@ paths: - NodePools /node-pools/{id}/servers: get: - consumes: - - application/json operationId: ListNodePoolServers parameters: - description: Organization UUID in: header name: X-Org-ID - type: string + schema: + type: string - description: Node Pool ID (UUID) in: path name: id required: true - type: string - produces: - - application/json + schema: + type: string responses: "200": + content: + application/json: + schema: + items: + $ref: '#/components/schemas/dto.ServerResponse' + type: array description: OK - schema: - items: - $ref: '#/definitions/dto.ServerResponse' - type: array "400": + content: + application/json: + schema: + type: string description: invalid id - schema: - type: string "401": + content: + application/json: + schema: + type: string description: Unauthorized - schema: - type: string "403": + content: + application/json: + schema: + type: string description: organization required - schema: - type: string "404": + content: + application/json: + schema: + type: string description: not found - schema: - type: string "500": + content: + application/json: + schema: + type: string description: fetch failed - schema: - type: string security: - BearerAuth: [] - OrgKeyAuth: [] @@ -3314,52 +4924,63 @@ paths: tags: - NodePools post: - consumes: - - application/json operationId: AttachNodePoolServers parameters: - description: Organization UUID in: header name: X-Org-ID - type: string + schema: + type: string - description: Node Pool ID (UUID) in: path name: id required: true - type: string - - description: Server IDs to attach - in: body - name: body - required: true schema: - $ref: '#/definitions/dto.AttachServersRequest' - produces: - - application/json + type: string + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/dto.AttachServersRequest' + description: Server IDs to attach + required: true responses: "204": + content: + application/json: + schema: + type: string description: No Content - schema: - type: string "400": + content: + application/json: + schema: + type: string description: invalid id / invalid server_ids - schema: - type: string "401": + content: + application/json: + schema: + type: string description: Unauthorized - schema: - type: string "403": + content: + application/json: + schema: + type: string description: organization required - schema: - type: string "404": + content: + application/json: + schema: + type: string description: not found - schema: - type: string "500": + content: + application/json: + schema: + type: string description: attach failed - schema: - type: string security: - BearerAuth: [] - OrgKeyAuth: [] @@ -3369,51 +4990,62 @@ paths: - NodePools /node-pools/{id}/servers/{serverId}: delete: - consumes: - - application/json operationId: DetachNodePoolServer parameters: - description: Organization UUID in: header name: X-Org-ID - type: string + schema: + type: string - description: Node Pool ID (UUID) in: path name: id required: true - type: string + schema: + type: string - description: Server ID (UUID) in: path name: serverId required: true - type: string - produces: - - application/json + schema: + type: string responses: "204": + content: + application/json: + schema: + type: string description: No Content - schema: - type: string "400": + content: + application/json: + schema: + type: string description: invalid id - schema: - type: string "401": + content: + application/json: + schema: + type: string description: Unauthorized - schema: - type: string "403": + content: + application/json: + schema: + type: string description: organization required - schema: - type: string "404": + content: + application/json: + schema: + type: string description: not found - schema: - type: string "500": + content: + application/json: + schema: + type: string description: detach failed - schema: - type: string security: - BearerAuth: [] - OrgKeyAuth: [] @@ -3423,48 +5055,58 @@ paths: - NodePools /node-pools/{id}/taints: get: - consumes: - - application/json operationId: ListNodePoolTaints parameters: - description: Organization UUID in: header name: X-Org-ID - type: string + schema: + type: string - description: Node Pool ID (UUID) in: path name: id required: true - type: string - produces: - - application/json + schema: + type: string responses: "200": + content: + application/json: + schema: + items: + $ref: '#/components/schemas/dto.TaintResponse' + type: array description: OK - schema: - items: - $ref: '#/definitions/dto.TaintResponse' - type: array "400": + content: + application/json: + schema: + type: string description: invalid id - schema: - type: string "401": + content: + application/json: + schema: + type: string description: Unauthorized - schema: - type: string "403": + content: + application/json: + schema: + type: string description: organization required - schema: - type: string "404": + content: + application/json: + schema: + type: string description: not found - schema: - type: string "500": + content: + application/json: + schema: + type: string description: fetch failed - schema: - type: string security: - BearerAuth: [] - OrgKeyAuth: [] @@ -3473,52 +5115,63 @@ paths: tags: - NodePools post: - consumes: - - application/json operationId: AttachNodePoolTaints parameters: - description: Organization UUID in: header name: X-Org-ID - type: string + schema: + type: string - description: Node Pool ID (UUID) in: path name: id required: true - type: string - - description: Taint IDs to attach - in: body - name: body - required: true schema: - $ref: '#/definitions/dto.AttachTaintsRequest' - produces: - - application/json + type: string + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/dto.AttachTaintsRequest' + description: Taint IDs to attach + required: true responses: "204": + content: + application/json: + schema: + type: string description: No Content - schema: - type: string "400": + content: + application/json: + schema: + type: string description: invalid id / invalid taint_ids - schema: - type: string "401": + content: + application/json: + schema: + type: string description: Unauthorized - schema: - type: string "403": + content: + application/json: + schema: + type: string description: organization required - schema: - type: string "404": + content: + application/json: + schema: + type: string description: not found - schema: - type: string "500": + content: + application/json: + schema: + type: string description: attach failed - schema: - type: string security: - BearerAuth: [] - OrgKeyAuth: [] @@ -3528,51 +5181,62 @@ paths: - NodePools /node-pools/{id}/taints/{taintId}: delete: - consumes: - - application/json operationId: DetachNodePoolTaint parameters: - description: Organization UUID in: header name: X-Org-ID - type: string + schema: + type: string - description: Node Pool ID (UUID) in: path name: id required: true - type: string + schema: + type: string - description: Taint ID (UUID) in: path name: taintId required: true - type: string - produces: - - application/json + schema: + type: string responses: "204": + content: + application/json: + schema: + type: string description: No Content - schema: - type: string "400": + content: + application/json: + schema: + type: string description: invalid id - schema: - type: string "401": + content: + application/json: + schema: + type: string description: Unauthorized - schema: - type: string "403": + content: + application/json: + schema: + type: string description: organization required - schema: - type: string "404": + content: + application/json: + schema: + type: string description: not found - schema: - type: string "500": + content: + application/json: + schema: + type: string description: detach failed - schema: - type: string security: - BearerAuth: [] - OrgKeyAuth: [] @@ -3583,54 +5247,60 @@ paths: /orgs: get: operationId: listMyOrgs - produces: - - application/json responses: "200": + content: + application/json: + schema: + items: + $ref: '#/components/schemas/models.Organization' + type: array description: OK - schema: - items: - $ref: '#/definitions/models.Organization' - type: array "401": + content: + application/json: + schema: + $ref: '#/components/schemas/utils.ErrorResponse' description: Unauthorized - schema: - $ref: '#/definitions/utils.ErrorResponse' security: - BearerAuth: [] summary: List organizations I belong to tags: - Orgs post: - consumes: - - application/json operationId: createOrg - parameters: - - description: Org payload - in: body - name: body + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/handlers.orgCreateReq' + description: Org payload required: true - schema: - $ref: '#/definitions/handlers.orgCreateReq' - produces: - - application/json responses: "201": + content: + application/json: + schema: + $ref: '#/components/schemas/models.Organization' description: Created - schema: - $ref: '#/definitions/models.Organization' "400": + content: + application/json: + schema: + $ref: '#/components/schemas/utils.ErrorResponse' description: Bad Request - schema: - $ref: '#/definitions/utils.ErrorResponse' "401": + content: + application/json: + schema: + $ref: '#/components/schemas/utils.ErrorResponse' description: Unauthorized - schema: - $ref: '#/definitions/utils.ErrorResponse' "409": + content: + application/json: + schema: + $ref: '#/components/schemas/utils.ErrorResponse' description: Conflict - schema: - $ref: '#/definitions/utils.ErrorResponse' security: - BearerAuth: [] summary: Create organization @@ -3644,20 +5314,23 @@ paths: in: path name: id required: true - type: string - produces: - - application/json + schema: + type: string responses: "204": description: Deleted "401": + content: + application/json: + schema: + $ref: '#/components/schemas/utils.ErrorResponse' description: Unauthorized - schema: - $ref: '#/definitions/utils.ErrorResponse' "404": + content: + application/json: + schema: + $ref: '#/components/schemas/utils.ErrorResponse' description: Not Found - schema: - $ref: '#/definitions/utils.ErrorResponse' security: - BearerAuth: [] summary: Delete organization (owner) @@ -3670,58 +5343,67 @@ paths: in: path name: id required: true - type: string - produces: - - application/json + schema: + type: string responses: "200": + content: + application/json: + schema: + $ref: '#/components/schemas/models.Organization' description: OK - schema: - $ref: '#/definitions/models.Organization' "401": + content: + application/json: + schema: + $ref: '#/components/schemas/utils.ErrorResponse' description: Unauthorized - schema: - $ref: '#/definitions/utils.ErrorResponse' "404": + content: + application/json: + schema: + $ref: '#/components/schemas/utils.ErrorResponse' description: Not Found - schema: - $ref: '#/definitions/utils.ErrorResponse' security: - BearerAuth: [] summary: Get organization tags: - Orgs patch: - consumes: - - application/json operationId: updateOrg parameters: - description: Org ID (UUID) in: path name: id required: true - type: string - - description: Update payload - in: body - name: body - required: true schema: - $ref: '#/definitions/handlers.orgUpdateReq' - produces: - - application/json + type: string + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/handlers.orgUpdateReq' + description: Update payload + required: true responses: "200": + content: + application/json: + schema: + $ref: '#/components/schemas/models.Organization' description: OK - schema: - $ref: '#/definitions/models.Organization' "401": + content: + application/json: + schema: + $ref: '#/components/schemas/utils.ErrorResponse' description: Unauthorized - schema: - $ref: '#/definitions/utils.ErrorResponse' "404": + content: + application/json: + schema: + $ref: '#/components/schemas/utils.ErrorResponse' description: Not Found - schema: - $ref: '#/definitions/utils.ErrorResponse' security: - BearerAuth: [] summary: Update organization (owner/admin) @@ -3735,52 +5417,57 @@ paths: in: path name: id required: true - type: string - produces: - - application/json + schema: + type: string responses: "200": + content: + application/json: + schema: + items: + $ref: '#/components/schemas/models.APIKey' + type: array description: OK - schema: - items: - $ref: '#/definitions/models.APIKey' - type: array "401": + content: + application/json: + schema: + $ref: '#/components/schemas/utils.ErrorResponse' description: Unauthorized - schema: - $ref: '#/definitions/utils.ErrorResponse' security: - BearerAuth: [] summary: List org-scoped API keys (no secrets) tags: - Orgs post: - consumes: - - application/json operationId: createOrgKey parameters: - description: Org ID (UUID) in: path name: id required: true - type: string - - description: Key name + optional expiry - in: body - name: body - required: true schema: - $ref: '#/definitions/handlers.orgKeyCreateReq' - produces: - - application/json + type: string + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/handlers.orgKeyCreateReq' + description: Key name + optional expiry + required: true responses: "201": + content: + application/json: + schema: + $ref: '#/components/schemas/handlers.orgKeyCreateResp' description: Created - schema: - $ref: '#/definitions/handlers.orgKeyCreateResp' "401": + content: + application/json: + schema: + $ref: '#/components/schemas/utils.ErrorResponse' description: Unauthorized - schema: - $ref: '#/definitions/utils.ErrorResponse' security: - BearerAuth: [] summary: Create org key/secret pair (owner/admin) @@ -3794,21 +5481,23 @@ paths: in: path name: id required: true - type: string + schema: + type: string - description: Key ID (UUID) in: path name: key_id required: true - type: string - produces: - - application/json + schema: + type: string responses: "204": description: Deleted "401": + content: + application/json: + schema: + $ref: '#/components/schemas/utils.ErrorResponse' description: Unauthorized - schema: - $ref: '#/definitions/utils.ErrorResponse' security: - BearerAuth: [] summary: Delete org key (owner/admin) @@ -3822,52 +5511,57 @@ paths: in: path name: id required: true - type: string - produces: - - application/json + schema: + type: string responses: "200": + content: + application/json: + schema: + items: + $ref: '#/components/schemas/handlers.memberOut' + type: array description: OK - schema: - items: - $ref: '#/definitions/handlers.memberOut' - type: array "401": + content: + application/json: + schema: + $ref: '#/components/schemas/utils.ErrorResponse' description: Unauthorized - schema: - $ref: '#/definitions/utils.ErrorResponse' security: - BearerAuth: [] summary: List members in org tags: - Orgs post: - consumes: - - application/json operationId: addOrUpdateMember parameters: - description: Org ID (UUID) in: path name: id required: true - type: string - - description: User & role - in: body - name: body - required: true schema: - $ref: '#/definitions/handlers.memberUpsertReq' - produces: - - application/json + type: string + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/handlers.memberUpsertReq' + description: User & role + required: true responses: "200": + content: + application/json: + schema: + $ref: '#/components/schemas/handlers.memberOut' description: OK - schema: - $ref: '#/definitions/handlers.memberOut' "401": + content: + application/json: + schema: + $ref: '#/components/schemas/utils.ErrorResponse' description: Unauthorized - schema: - $ref: '#/definitions/utils.ErrorResponse' security: - BearerAuth: [] summary: Add or update a member (owner/admin) @@ -3881,21 +5575,23 @@ paths: in: path name: id required: true - type: string + schema: + type: string - description: User ID (UUID) in: path name: user_id required: true - type: string - produces: - - application/json + schema: + type: string responses: "204": description: Removed "401": + content: + application/json: + schema: + $ref: '#/components/schemas/utils.ErrorResponse' description: Unauthorized - schema: - $ref: '#/definitions/utils.ErrorResponse' security: - BearerAuth: [] summary: Remove a member (owner/admin) @@ -3903,8 +5599,6 @@ paths: - Orgs /servers: get: - consumes: - - application/json description: 'Returns servers for the organization in X-Org-ID. Optional filters: status, role.' operationId: ListServers @@ -3912,36 +5606,45 @@ paths: - description: Organization UUID in: header name: X-Org-ID - type: string + schema: + type: string - description: Filter by status (pending|provisioning|ready|failed) in: query name: status - type: string + schema: + type: string - description: Filter by role in: query name: role - type: string - produces: - - application/json + schema: + type: string responses: "200": + content: + application/json: + schema: + items: + $ref: '#/components/schemas/dto.ServerResponse' + type: array description: OK - schema: - items: - $ref: '#/definitions/dto.ServerResponse' - type: array "401": + content: + application/json: + schema: + type: string description: Unauthorized - schema: - type: string "403": + content: + application/json: + schema: + type: string description: organization required - schema: - type: string "500": + content: + application/json: + schema: + type: string description: failed to list servers - schema: - type: string security: - BearerAuth: [] - OrgKeyAuth: [] @@ -3950,8 +5653,6 @@ paths: tags: - Servers post: - consumes: - - application/json description: Creates a server bound to the org in X-Org-ID. Validates that ssh_key_id belongs to the org. operationId: CreateServer @@ -3959,36 +5660,46 @@ paths: - description: Organization UUID in: header name: X-Org-ID - type: string - - description: Server payload - in: body - name: body - required: true schema: - $ref: '#/definitions/dto.CreateServerRequest' - produces: - - application/json + type: string + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/dto.CreateServerRequest' + description: Server payload + required: true responses: "201": + content: + application/json: + schema: + $ref: '#/components/schemas/dto.ServerResponse' description: Created - schema: - $ref: '#/definitions/dto.ServerResponse' "400": + content: + application/json: + schema: + type: string description: invalid json / missing fields / invalid status / invalid ssh_key_id - schema: - type: string "401": + content: + application/json: + schema: + type: string description: Unauthorized - schema: - type: string "403": + content: + application/json: + schema: + type: string description: organization required - schema: - type: string "500": + content: + application/json: + schema: + type: string description: create failed - schema: - type: string security: - BearerAuth: [] - OrgKeyAuth: [] @@ -3998,43 +5709,47 @@ paths: - Servers /servers/{id}: delete: - consumes: - - application/json description: Permanently deletes the server. operationId: DeleteServer parameters: - description: Organization UUID in: header name: X-Org-ID - type: string + schema: + type: string - description: Server ID (UUID) in: path name: id required: true - type: string - produces: - - application/json + schema: + type: string responses: "204": description: No Content - schema: - type: string "400": + content: + application/json: + schema: + type: string description: invalid id - schema: - type: string "401": + content: + application/json: + schema: + type: string description: Unauthorized - schema: - type: string "403": + content: + application/json: + schema: + type: string description: organization required - schema: - type: string "500": + content: + application/json: + schema: + type: string description: delete failed - schema: - type: string security: - BearerAuth: [] - OrgKeyAuth: [] @@ -4043,47 +5758,57 @@ paths: tags: - Servers get: - consumes: - - application/json description: Returns one server in the given organization. operationId: GetServer parameters: - description: Organization UUID in: header name: X-Org-ID - type: string + schema: + type: string - description: Server ID (UUID) in: path name: id required: true - type: string - produces: - - application/json + schema: + type: string responses: "200": + content: + application/json: + schema: + $ref: '#/components/schemas/dto.ServerResponse' description: OK - schema: - $ref: '#/definitions/dto.ServerResponse' "400": + content: + application/json: + schema: + type: string description: invalid id - schema: - type: string "401": + content: + application/json: + schema: + type: string description: Unauthorized - schema: - type: string "403": + content: + application/json: + schema: + type: string description: organization required - schema: - type: string "404": + content: + application/json: + schema: + type: string description: not found - schema: - type: string "500": + content: + application/json: + schema: + type: string description: fetch failed - schema: - type: string security: - BearerAuth: [] - OrgKeyAuth: [] @@ -4092,53 +5817,64 @@ paths: tags: - Servers patch: - consumes: - - application/json description: Partially update fields; changing ssh_key_id validates ownership. operationId: UpdateServer parameters: - description: Organization UUID in: header name: X-Org-ID - type: string + schema: + type: string - description: Server ID (UUID) in: path name: id required: true - type: string - - description: Fields to update - in: body - name: body - required: true schema: - $ref: '#/definitions/dto.UpdateServerRequest' - produces: - - application/json + type: string + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/dto.UpdateServerRequest' + description: Fields to update + required: true responses: "200": + content: + application/json: + schema: + $ref: '#/components/schemas/dto.ServerResponse' description: OK - schema: - $ref: '#/definitions/dto.ServerResponse' "400": + content: + application/json: + schema: + type: string description: invalid id / invalid json / invalid status / invalid ssh_key_id - schema: - type: string "401": + content: + application/json: + schema: + type: string description: Unauthorized - schema: - type: string "403": + content: + application/json: + schema: + type: string description: organization required - schema: - type: string "404": + content: + application/json: + schema: + type: string description: not found - schema: - type: string "500": + content: + application/json: + schema: + type: string description: update failed - schema: - type: string security: - BearerAuth: [] - OrgKeyAuth: [] @@ -4148,8 +5884,6 @@ paths: - Servers /servers/{id}/reset-hostkey: post: - consumes: - - application/json description: Clears the stored SSH host key for this server. The next SSH connection will re-learn the host key (trust-on-first-use). operationId: ResetServerHostKey @@ -4157,39 +5891,56 @@ paths: - description: Organization UUID in: header name: X-Org-ID - type: string + schema: + type: string - description: Server ID (UUID) in: path name: id required: true - type: string - produces: - - application/json + schema: + type: string + requestBody: + content: + application/json: + schema: + type: object responses: "200": + content: + application/json: + schema: + $ref: '#/components/schemas/dto.ServerResponse' description: OK - schema: - $ref: '#/definitions/dto.ServerResponse' "400": + content: + application/json: + schema: + type: string description: invalid id - schema: - type: string "401": + content: + application/json: + schema: + type: string description: Unauthorized - schema: - type: string "403": + content: + application/json: + schema: + type: string description: organization required - schema: - type: string "404": + content: + application/json: + schema: + type: string description: not found - schema: - type: string "500": + content: + application/json: + schema: + type: string description: reset failed - schema: - type: string security: - BearerAuth: [] - OrgKeyAuth: [] @@ -4199,36 +5950,41 @@ paths: - Servers /ssh: get: - consumes: - - application/json description: Returns ssh keys for the organization in X-Org-ID. operationId: ListPublicSshKeys parameters: - description: Organization UUID in: header name: X-Org-ID - type: string - produces: - - application/json + schema: + type: string responses: "200": + content: + application/json: + schema: + items: + $ref: '#/components/schemas/dto.SshResponse' + type: array description: OK - schema: - items: - $ref: '#/definitions/dto.SshResponse' - type: array "401": + content: + application/json: + schema: + type: string description: Unauthorized - schema: - type: string "403": + content: + application/json: + schema: + type: string description: organization required - schema: - type: string "500": + content: + application/json: + schema: + type: string description: failed to list keys - schema: - type: string security: - BearerAuth: [] - OrgKeyAuth: [] @@ -4237,8 +5993,6 @@ paths: tags: - Ssh post: - consumes: - - application/json description: Generates an RSA or ED25519 keypair, saves it, and returns metadata. For RSA you may set bits (2048/3072/4096). Default is 4096. ED25519 ignores bits. @@ -4247,36 +6001,46 @@ paths: - description: Organization UUID in: header name: X-Org-ID - type: string - - description: Key generation options - in: body - name: body - required: true schema: - $ref: '#/definitions/dto.CreateSSHRequest' - produces: - - application/json + type: string + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/dto.CreateSSHRequest' + description: Key generation options + required: true responses: "201": + content: + application/json: + schema: + $ref: '#/components/schemas/dto.SshResponse' description: Created - schema: - $ref: '#/definitions/dto.SshResponse' "400": + content: + application/json: + schema: + type: string description: invalid json / invalid bits - schema: - type: string "401": + content: + application/json: + schema: + type: string description: Unauthorized - schema: - type: string "403": + content: + application/json: + schema: + type: string description: organization required - schema: - type: string "500": + content: + application/json: + schema: + type: string description: generation/create failed - schema: - type: string security: - BearerAuth: [] - OrgKeyAuth: [] @@ -4286,43 +6050,47 @@ paths: - Ssh /ssh/{id}: delete: - consumes: - - application/json description: Permanently deletes a keypair. operationId: DeleteSSHKey parameters: - description: Organization UUID in: header name: X-Org-ID - type: string + schema: + type: string - description: SSH Key ID (UUID) in: path name: id required: true - type: string - produces: - - application/json + schema: + type: string responses: "204": description: No Content - schema: - type: string "400": + content: + application/json: + schema: + type: string description: invalid id - schema: - type: string "401": + content: + application/json: + schema: + type: string description: Unauthorized - schema: - type: string "403": + content: + application/json: + schema: + type: string description: organization required - schema: - type: string "500": + content: + application/json: + schema: + type: string description: delete failed - schema: - type: string security: - BearerAuth: [] - OrgKeyAuth: [] @@ -4331,8 +6099,6 @@ paths: tags: - Ssh get: - consumes: - - application/json description: Returns public key fields. Append `?reveal=true` to include the private key PEM. operationId: GetSSHKey @@ -4340,43 +6106,58 @@ paths: - description: Organization UUID in: header name: X-Org-ID - type: string + schema: + type: string - description: SSH Key ID (UUID) in: path name: id required: true - type: string + schema: + type: string - description: Reveal private key PEM in: query name: reveal - type: boolean - produces: - - application/json + schema: + type: boolean responses: "200": + content: + application/json: + schema: + oneOf: + - $ref: '#/components/schemas/dto.SshResponse' + - $ref: '#/components/schemas/dto.SshRevealResponse' description: When reveal=true - schema: - $ref: '#/definitions/dto.SshRevealResponse' "400": + content: + application/json: + schema: + type: string description: invalid id - schema: - type: string "401": + content: + application/json: + schema: + type: string description: Unauthorized - schema: - type: string "403": + content: + application/json: + schema: + type: string description: organization required - schema: - type: string "404": + content: + application/json: + schema: + type: string description: not found - schema: - type: string "500": + content: + application/json: + schema: + type: string description: fetch failed - schema: - type: string security: - BearerAuth: [] - OrgKeyAuth: [] @@ -4394,48 +6175,61 @@ paths: in: header name: X-Org-ID required: true - type: string + schema: + type: string - description: SSH Key ID (UUID) in: path name: id required: true - type: string + schema: + type: string - description: Which part to download - enum: - - public - - private - - both in: query name: part required: true - type: string - produces: - - application/json + schema: + enum: + - public + - private + - both + type: string responses: "200": + content: + application/json: + schema: + type: string description: file content - schema: - type: string "400": + content: + application/json: + schema: + type: string description: invalid id / invalid part - schema: - type: string "401": + content: + application/json: + schema: + type: string description: Unauthorized - schema: - type: string "403": + content: + application/json: + schema: + type: string description: organization required - schema: - type: string "404": + content: + application/json: + schema: + type: string description: not found - schema: - type: string "500": + content: + application/json: + schema: + type: string description: download failed - schema: - type: string security: - BearerAuth: [] - OrgKeyAuth: [] @@ -4445,8 +6239,6 @@ paths: - Ssh /taints: get: - consumes: - - application/json description: 'Returns node taints for the organization in X-Org-ID. Filters: `key`, `value`, and `q` (key contains). Add `include=node_pools` to include linked node pools.' @@ -4455,40 +6247,50 @@ paths: - description: Organization UUID in: header name: X-Org-ID - type: string + schema: + type: string - description: Exact key in: query name: key - type: string + schema: + type: string - description: Exact value in: query name: value - type: string + schema: + type: string - description: key contains (case-insensitive) in: query name: q - type: string - produces: - - application/json + schema: + type: string responses: "200": + content: + application/json: + schema: + items: + $ref: '#/components/schemas/dto.TaintResponse' + type: array description: OK - schema: - items: - $ref: '#/definitions/dto.TaintResponse' - type: array "401": + content: + application/json: + schema: + type: string description: Unauthorized - schema: - type: string "403": + content: + application/json: + schema: + type: string description: organization required - schema: - type: string "500": + content: + application/json: + schema: + type: string description: failed to list node taints - schema: - type: string security: - BearerAuth: [] - OrgKeyAuth: [] @@ -4497,44 +6299,52 @@ paths: tags: - Taints post: - consumes: - - application/json description: Creates a taint. operationId: CreateTaint parameters: - description: Organization UUID in: header name: X-Org-ID - type: string - - description: Taint payload - in: body - name: body - required: true schema: - $ref: '#/definitions/dto.CreateTaintRequest' - produces: - - application/json + type: string + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/dto.CreateTaintRequest' + description: Taint payload + required: true responses: "201": + content: + application/json: + schema: + $ref: '#/components/schemas/dto.TaintResponse' description: Created - schema: - $ref: '#/definitions/dto.TaintResponse' "400": + content: + application/json: + schema: + type: string description: invalid json / missing fields / invalid node_pool_ids - schema: - type: string "401": + content: + application/json: + schema: + type: string description: Unauthorized - schema: - type: string "403": + content: + application/json: + schema: + type: string description: organization required - schema: - type: string "500": + content: + application/json: + schema: + type: string description: create failed - schema: - type: string security: - BearerAuth: [] - OrgKeyAuth: [] @@ -4544,43 +6354,47 @@ paths: - Taints /taints/{id}: delete: - consumes: - - application/json description: Permanently deletes the taint. operationId: DeleteTaint parameters: - description: Organization UUID in: header name: X-Org-ID - type: string + schema: + type: string - description: Node Taint ID (UUID) in: path name: id required: true - type: string - produces: - - application/json + schema: + type: string responses: "204": description: No Content - schema: - type: string "400": + content: + application/json: + schema: + type: string description: invalid id - schema: - type: string "401": + content: + application/json: + schema: + type: string description: Unauthorized - schema: - type: string "403": + content: + application/json: + schema: + type: string description: organization required - schema: - type: string "500": + content: + application/json: + schema: + type: string description: delete failed - schema: - type: string security: - BearerAuth: [] - OrgKeyAuth: [] @@ -4589,46 +6403,56 @@ paths: tags: - Taints get: - consumes: - - application/json operationId: GetTaint parameters: - description: Organization UUID in: header name: X-Org-ID - type: string + schema: + type: string - description: Node Taint ID (UUID) in: path name: id required: true - type: string - produces: - - application/json + schema: + type: string responses: "200": + content: + application/json: + schema: + $ref: '#/components/schemas/dto.TaintResponse' description: OK - schema: - $ref: '#/definitions/dto.TaintResponse' "400": + content: + application/json: + schema: + type: string description: invalid id - schema: - type: string "401": + content: + application/json: + schema: + type: string description: Unauthorized - schema: - type: string "403": + content: + application/json: + schema: + type: string description: organization required - schema: - type: string "404": + content: + application/json: + schema: + type: string description: not found - schema: - type: string "500": + content: + application/json: + schema: + type: string description: fetch failed - schema: - type: string security: - BearerAuth: [] - OrgKeyAuth: [] @@ -4637,53 +6461,64 @@ paths: tags: - Taints patch: - consumes: - - application/json description: Partially update taint fields. operationId: UpdateTaint parameters: - description: Organization UUID in: header name: X-Org-ID - type: string + schema: + type: string - description: Node Taint ID (UUID) in: path name: id required: true - type: string - - description: Fields to update - in: body - name: body - required: true schema: - $ref: '#/definitions/dto.UpdateTaintRequest' - produces: - - application/json + type: string + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/dto.UpdateTaintRequest' + description: Fields to update + required: true responses: "200": + content: + application/json: + schema: + $ref: '#/components/schemas/dto.TaintResponse' description: OK - schema: - $ref: '#/definitions/dto.TaintResponse' "400": + content: + application/json: + schema: + type: string description: invalid id / invalid json - schema: - type: string "401": + content: + application/json: + schema: + type: string description: Unauthorized - schema: - type: string "403": + content: + application/json: + schema: + type: string description: organization required - schema: - type: string "404": + content: + application/json: + schema: + type: string description: not found - schema: - type: string "500": + content: + application/json: + schema: + type: string description: update failed - schema: - type: string security: - BearerAuth: [] - OrgKeyAuth: [] @@ -4693,42 +6528,22 @@ paths: - Taints /version: get: - consumes: - - application/json description: Returns build/runtime metadata for the running service. operationId: Version // operationId - produces: - - application/json responses: "200": + content: + application/json: + schema: + $ref: '#/components/schemas/handlers.VersionResponse' description: OK - schema: - $ref: '#/definitions/handlers.VersionResponse' summary: Service version information tags: - Meta -schemes: -- http -- https -securityDefinitions: - ApiKeyAuth: - description: User API key - in: header - name: X-API-KEY - type: apiKey - BearerAuth: - description: Bearer token authentication - in: header - name: Authorization - type: apiKey - OrgKeyAuth: - description: Org-level key/secret authentication - in: header - name: X-ORG-KEY - type: apiKey - OrgSecretAuth: - description: Org-level secret - in: header - name: X-ORG-SECRET - type: apiKey -swagger: "2.0" +servers: +- description: Production API + url: https://autoglue.onglueops.rocks/api/v1 +- description: Staging API + url: https://autoglue.apps.nonprod.earth.onglueops.rocks/api/v1 +- description: Local dev + url: http://localhost:8080/api/v1 diff --git a/go.mod b/go.mod index 17c67a8..498263d 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/glueops/autoglue go 1.25.4 require ( + ariga.io/atlas-provider-gorm v0.6.0 github.com/alexedwards/argon2id v1.0.0 github.com/aws/aws-sdk-go-v2 v1.39.6 github.com/aws/aws-sdk-go-v2/config v1.31.18 @@ -11,6 +12,7 @@ require ( github.com/aws/aws-sdk-go-v2/service/s3 v1.90.0 github.com/coreos/go-oidc/v3 v3.16.0 github.com/dyaksa/archer v1.1.3 + github.com/fergusstrange/embedded-postgres v1.33.0 github.com/gin-gonic/gin v1.11.0 github.com/go-chi/chi/v5 v5.2.3 github.com/go-chi/cors v1.2.2 @@ -23,7 +25,6 @@ require ( github.com/sosedoff/pgweb v0.16.2 github.com/spf13/cobra v1.10.1 github.com/spf13/viper v1.21.0 - github.com/swaggo/http-swagger/v2 v2.0.2 github.com/swaggo/swag/v2 v2.0.0-rc4 golang.org/x/crypto v0.44.0 golang.org/x/oauth2 v0.33.0 @@ -34,8 +35,20 @@ require ( ) require ( + ariga.io/atlas v0.36.2-0.20250806044935-5bb51a0a956e // indirect + cel.dev/expr v0.24.0 // indirect + cloud.google.com/go v0.121.6 // indirect + cloud.google.com/go/auth v0.16.4 // indirect + cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect + cloud.google.com/go/compute/metadata v0.8.0 // indirect + cloud.google.com/go/iam v1.5.2 // indirect + cloud.google.com/go/longrunning v0.6.7 // indirect + cloud.google.com/go/monitoring v1.24.2 // indirect + cloud.google.com/go/spanner v1.84.1 // indirect filippo.io/edwards25519 v1.1.0 // indirect github.com/BurntSushi/toml v1.1.0 // indirect + github.com/GoogleCloudPlatform/grpc-gcp-go/grpcgcp v1.5.3 // indirect + github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.29.0 // indirect github.com/KyleBanks/depth v1.2.1 // indirect github.com/ScaleFT/sshkeys v0.0.0-20200327173127-6142f742bca5 // indirect github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.3 // indirect @@ -55,13 +68,19 @@ require ( github.com/beorn7/perks v1.0.1 // indirect github.com/bytedance/sonic v1.14.0 // indirect github.com/bytedance/sonic/loader v0.3.0 // indirect - github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cloudwego/base64x v0.1.6 // indirect + github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443 // indirect github.com/dchest/bcrypt_pbkdf v0.0.0-20150205184540-83f37f9c154a // indirect + github.com/envoyproxy/go-control-plane/envoy v1.32.4 // indirect + github.com/envoyproxy/protoc-gen-validate v1.2.1 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fsnotify/fsnotify v1.9.0 // indirect github.com/gabriel-vasile/mimetype v1.4.10 // indirect github.com/gin-contrib/sse v1.1.0 // indirect github.com/go-jose/go-jose/v4 v4.1.3 // indirect + github.com/go-logr/logr v1.4.3 // indirect + github.com/go-logr/stdr v1.2.2 // indirect github.com/go-openapi/jsonpointer v0.19.6 // indirect github.com/go-openapi/jsonreference v0.20.2 // indirect github.com/go-openapi/spec v0.20.9 // indirect @@ -72,6 +91,15 @@ require ( github.com/go-viper/mapstructure/v2 v2.4.0 // indirect github.com/goccy/go-json v0.10.5 // indirect github.com/goccy/go-yaml v1.18.0 // indirect + github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect + github.com/golang-sql/sqlexp v0.1.0 // indirect + github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect + github.com/google/s2a-go v0.1.9 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect + github.com/googleapis/gax-go/v2 v2.15.0 // indirect + github.com/googleapis/go-gorm-spanner v1.8.6 // indirect + github.com/googleapis/go-sql-spanner v1.17.0 // indirect + github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect @@ -89,14 +117,17 @@ require ( github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-sqlite3 v1.14.28 // indirect + github.com/microsoft/go-mssqldb v1.7.2 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/mr-tron/base58 v1.2.0 // indirect github.com/pelletier/go-toml/v2 v2.2.4 // indirect github.com/pkg/errors v0.9.1 // indirect + github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect github.com/prometheus/client_golang v1.19.1 // indirect - github.com/prometheus/client_model v0.5.0 // indirect + github.com/prometheus/client_model v0.6.1 // indirect github.com/prometheus/common v0.48.0 // indirect github.com/prometheus/procfs v0.12.0 // indirect github.com/quic-go/qpack v0.5.1 // indirect @@ -107,14 +138,25 @@ require ( github.com/spf13/afero v1.15.0 // indirect github.com/spf13/cast v1.10.0 // indirect github.com/spf13/pflag v1.0.10 // indirect + github.com/spiffe/go-spiffe/v2 v2.5.0 // indirect github.com/subosito/gotenv v1.6.0 // indirect github.com/sv-tools/openapi v0.2.1 // indirect - github.com/swaggo/files/v2 v2.0.0 // indirect - github.com/swaggo/swag v1.8.1 // indirect github.com/tuvistavie/securerandom v0.0.0-20140719024926-15512123a948 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.3.0 // indirect + github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect + github.com/zeebo/errs v1.4.0 // indirect github.com/zeebo/xxh3 v1.0.2 // indirect + go.opencensus.io v0.24.0 // indirect + go.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.opentelemetry.io/contrib/detectors/gcp v1.37.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.62.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 // indirect + go.opentelemetry.io/otel v1.37.0 // indirect + go.opentelemetry.io/otel/metric v1.37.0 // indirect + go.opentelemetry.io/otel/sdk v1.37.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.37.0 // indirect + go.opentelemetry.io/otel/trace v1.37.0 // indirect go.uber.org/mock v0.5.0 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect golang.org/x/arch v0.20.0 // indirect @@ -123,8 +165,16 @@ require ( golang.org/x/sync v0.18.0 // indirect golang.org/x/sys v0.38.0 // indirect golang.org/x/text v0.31.0 // indirect + golang.org/x/time v0.12.0 // indirect golang.org/x/tools v0.38.0 // indirect + google.golang.org/api v0.247.0 // indirect + google.golang.org/genproto v0.0.0-20250804133106-a7a43d27e69b // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250804133106-a7a43d27e69b // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250811230008-5f3141c8851a // indirect + google.golang.org/grpc v1.74.2 // indirect google.golang.org/protobuf v1.36.9 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect - gorm.io/driver/mysql v1.5.6 // indirect + gorm.io/driver/mysql v1.5.7 // indirect + gorm.io/driver/sqlite v1.6.0 // indirect + gorm.io/driver/sqlserver v1.6.0 // indirect ) diff --git a/go.sum b/go.sum index 82363e2..6fbbaf6 100644 --- a/go.sum +++ b/go.sum @@ -1,33 +1,678 @@ +ariga.io/atlas v0.36.2-0.20250806044935-5bb51a0a956e h1:7upp27oOT/fmM5Dz3z9k8cmYwKJ2NAzuTqfT/rEP+50= +ariga.io/atlas v0.36.2-0.20250806044935-5bb51a0a956e/go.mod h1:Ex5l1xHsnWQUc3wYnrJ9gD7RUEzG76P7ZRQp8wNr0wc= +ariga.io/atlas-provider-gorm v0.6.0 h1:nJx1jLKr8pKeIxuYX3NmBuchWR9VldopWyElnPakRDw= +ariga.io/atlas-provider-gorm v0.6.0/go.mod h1:iod/+0ODkmcNBJsmNrs76btfETyqC+zCyy7sh7aXaig= +cel.dev/expr v0.24.0 h1:56OvJKSH3hDGL0ml5uSxZmz3/3Pq4tJ+fb1unVLAFcY= +cel.dev/expr v0.24.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw= +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= +cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= +cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= +cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= +cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= +cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= +cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY= +cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM= +cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY= +cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ= +cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI= +cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4= +cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc= +cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA= +cloud.google.com/go v0.100.1/go.mod h1:fs4QogzfH5n2pBXBP9vRiU+eCny7lD2vmFZy79Iuw1U= +cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A= +cloud.google.com/go v0.102.0/go.mod h1:oWcCzKlqJ5zgHQt9YsaeTY9KzIvjyy0ArmiBUgpQ+nc= +cloud.google.com/go v0.102.1/go.mod h1:XZ77E9qnTEnrgEOvr4xzfdX5TRo7fB4T2F4O6+34hIU= +cloud.google.com/go v0.104.0/go.mod h1:OO6xxXdJyvuJPcEPBLN9BJPD+jep5G1+2U5B5gkRYtA= +cloud.google.com/go v0.105.0/go.mod h1:PrLgOJNe5nfE9UMxKxgXj4mD3voiP+YQ6gdt6KMFOKM= +cloud.google.com/go v0.107.0/go.mod h1:wpc2eNrD7hXUTy8EKS10jkxpZBjASrORK7goS+3YX2I= +cloud.google.com/go v0.110.0/go.mod h1:SJnCLqQ0FCFGSZMUNUf84MV3Aia54kn7pi8st7tMzaY= +cloud.google.com/go v0.121.6 h1:waZiuajrI28iAf40cWgycWNgaXPO06dupuS+sgibK6c= +cloud.google.com/go v0.121.6/go.mod h1:coChdst4Ea5vUpiALcYKXEpR1S9ZgXbhEzzMcMR66vI= +cloud.google.com/go/accessapproval v1.4.0/go.mod h1:zybIuC3KpDOvotz59lFe5qxRZx6C75OtwbisN56xYB4= +cloud.google.com/go/accessapproval v1.5.0/go.mod h1:HFy3tuiGvMdcd/u+Cu5b9NkO1pEICJ46IR82PoUdplw= +cloud.google.com/go/accessapproval v1.6.0/go.mod h1:R0EiYnwV5fsRFiKZkPHr6mwyk2wxUJ30nL4j2pcFY2E= +cloud.google.com/go/accesscontextmanager v1.3.0/go.mod h1:TgCBehyr5gNMz7ZaH9xubp+CE8dkrszb4oK9CWyvD4o= +cloud.google.com/go/accesscontextmanager v1.4.0/go.mod h1:/Kjh7BBu/Gh83sv+K60vN9QE5NJcd80sU33vIe2IFPE= +cloud.google.com/go/accesscontextmanager v1.6.0/go.mod h1:8XCvZWfYw3K/ji0iVnp+6pu7huxoQTLmxAbVjbloTtM= +cloud.google.com/go/accesscontextmanager v1.7.0/go.mod h1:CEGLewx8dwa33aDAZQujl7Dx+uYhS0eay198wB/VumQ= +cloud.google.com/go/aiplatform v1.22.0/go.mod h1:ig5Nct50bZlzV6NvKaTwmplLLddFx0YReh9WfTO5jKw= +cloud.google.com/go/aiplatform v1.24.0/go.mod h1:67UUvRBKG6GTayHKV8DBv2RtR1t93YRu5B1P3x99mYY= +cloud.google.com/go/aiplatform v1.27.0/go.mod h1:Bvxqtl40l0WImSb04d0hXFU7gDOiq9jQmorivIiWcKg= +cloud.google.com/go/aiplatform v1.35.0/go.mod h1:7MFT/vCaOyZT/4IIFfxH4ErVg/4ku6lKv3w0+tFTgXQ= +cloud.google.com/go/aiplatform v1.36.1/go.mod h1:WTm12vJRPARNvJ+v6P52RDHCNe4AhvjcIZ/9/RRHy/k= +cloud.google.com/go/aiplatform v1.37.0/go.mod h1:IU2Cv29Lv9oCn/9LkFiiuKfwrRTq+QQMbW+hPCxJGZw= +cloud.google.com/go/analytics v0.11.0/go.mod h1:DjEWCu41bVbYcKyvlws9Er60YE4a//bK6mnhWvQeFNI= +cloud.google.com/go/analytics v0.12.0/go.mod h1:gkfj9h6XRf9+TS4bmuhPEShsh3hH8PAZzm/41OOhQd4= +cloud.google.com/go/analytics v0.17.0/go.mod h1:WXFa3WSym4IZ+JiKmavYdJwGG/CvpqiqczmL59bTD9M= +cloud.google.com/go/analytics v0.18.0/go.mod h1:ZkeHGQlcIPkw0R/GW+boWHhCOR43xz9RN/jn7WcqfIE= +cloud.google.com/go/analytics v0.19.0/go.mod h1:k8liqf5/HCnOUkbawNtrWWc+UAzyDlW89doe8TtoDsE= +cloud.google.com/go/apigateway v1.3.0/go.mod h1:89Z8Bhpmxu6AmUxuVRg/ECRGReEdiP3vQtk4Z1J9rJk= +cloud.google.com/go/apigateway v1.4.0/go.mod h1:pHVY9MKGaH9PQ3pJ4YLzoj6U5FUDeDFBllIz7WmzJoc= +cloud.google.com/go/apigateway v1.5.0/go.mod h1:GpnZR3Q4rR7LVu5951qfXPJCHquZt02jf7xQx7kpqN8= +cloud.google.com/go/apigeeconnect v1.3.0/go.mod h1:G/AwXFAKo0gIXkPTVfZDd2qA1TxBXJ3MgMRBQkIi9jc= +cloud.google.com/go/apigeeconnect v1.4.0/go.mod h1:kV4NwOKqjvt2JYR0AoIWo2QGfoRtn/pkS3QlHp0Ni04= +cloud.google.com/go/apigeeconnect v1.5.0/go.mod h1:KFaCqvBRU6idyhSNyn3vlHXc8VMDJdRmwDF6JyFRqZ8= +cloud.google.com/go/apigeeregistry v0.4.0/go.mod h1:EUG4PGcsZvxOXAdyEghIdXwAEi/4MEaoqLMLDMIwKXY= +cloud.google.com/go/apigeeregistry v0.5.0/go.mod h1:YR5+s0BVNZfVOUkMa5pAR2xGd0A473vA5M7j247o1wM= +cloud.google.com/go/apigeeregistry v0.6.0/go.mod h1:BFNzW7yQVLZ3yj0TKcwzb8n25CFBri51GVGOEUcgQsc= +cloud.google.com/go/apikeys v0.4.0/go.mod h1:XATS/yqZbaBK0HOssf+ALHp8jAlNHUgyfprvNcBIszU= +cloud.google.com/go/apikeys v0.5.0/go.mod h1:5aQfwY4D+ewMMWScd3hm2en3hCj+BROlyrt3ytS7KLI= +cloud.google.com/go/apikeys v0.6.0/go.mod h1:kbpXu5upyiAlGkKrJgQl8A0rKNNJ7dQ377pdroRSSi8= +cloud.google.com/go/appengine v1.4.0/go.mod h1:CS2NhuBuDXM9f+qscZ6V86m1MIIqPj3WC/UoEuR1Sno= +cloud.google.com/go/appengine v1.5.0/go.mod h1:TfasSozdkFI0zeoxW3PTBLiNqRmzraodCWatWI9Dmak= +cloud.google.com/go/appengine v1.6.0/go.mod h1:hg6i0J/BD2cKmDJbaFSYHFyZkgBEfQrDg/X0V5fJn84= +cloud.google.com/go/appengine v1.7.0/go.mod h1:eZqpbHFCqRGa2aCdope7eC0SWLV1j0neb/QnMJVWx6A= +cloud.google.com/go/appengine v1.7.1/go.mod h1:IHLToyb/3fKutRysUlFO0BPt5j7RiQ45nrzEJmKTo6E= +cloud.google.com/go/area120 v0.5.0/go.mod h1:DE/n4mp+iqVyvxHN41Vf1CR602GiHQjFPusMFW6bGR4= +cloud.google.com/go/area120 v0.6.0/go.mod h1:39yFJqWVgm0UZqWTOdqkLhjoC7uFfgXRC8g/ZegeAh0= +cloud.google.com/go/area120 v0.7.0/go.mod h1:a3+8EUD1SX5RUcCs3MY5YasiO1z6yLiNLRiFrykbynY= +cloud.google.com/go/area120 v0.7.1/go.mod h1:j84i4E1RboTWjKtZVWXPqvK5VHQFJRF2c1Nm69pWm9k= +cloud.google.com/go/artifactregistry v1.6.0/go.mod h1:IYt0oBPSAGYj/kprzsBjZ/4LnG/zOcHyFHjWPCi6SAQ= +cloud.google.com/go/artifactregistry v1.7.0/go.mod h1:mqTOFOnGZx8EtSqK/ZWcsm/4U8B77rbcLP6ruDU2Ixk= +cloud.google.com/go/artifactregistry v1.8.0/go.mod h1:w3GQXkJX8hiKN0v+at4b0qotwijQbYUqF2GWkZzAhC0= +cloud.google.com/go/artifactregistry v1.9.0/go.mod h1:2K2RqvA2CYvAeARHRkLDhMDJ3OXy26h3XW+3/Jh2uYc= +cloud.google.com/go/artifactregistry v1.11.1/go.mod h1:lLYghw+Itq9SONbCa1YWBoWs1nOucMH0pwXN1rOBZFI= +cloud.google.com/go/artifactregistry v1.11.2/go.mod h1:nLZns771ZGAwVLzTX/7Al6R9ehma4WUEhZGWV6CeQNQ= +cloud.google.com/go/artifactregistry v1.12.0/go.mod h1:o6P3MIvtzTOnmvGagO9v/rOjjA0HmhJ+/6KAXrmYDCI= +cloud.google.com/go/artifactregistry v1.13.0/go.mod h1:uy/LNfoOIivepGhooAUpL1i30Hgee3Cu0l4VTWHUC08= +cloud.google.com/go/asset v1.5.0/go.mod h1:5mfs8UvcM5wHhqtSv8J1CtxxaQq3AdBxxQi2jGW/K4o= +cloud.google.com/go/asset v1.7.0/go.mod h1:YbENsRK4+xTiL+Ofoj5Ckf+O17kJtgp3Y3nn4uzZz5s= +cloud.google.com/go/asset v1.8.0/go.mod h1:mUNGKhiqIdbr8X7KNayoYvyc4HbbFO9URsjbytpUaW0= +cloud.google.com/go/asset v1.9.0/go.mod h1:83MOE6jEJBMqFKadM9NLRcs80Gdw76qGuHn8m3h8oHQ= +cloud.google.com/go/asset v1.10.0/go.mod h1:pLz7uokL80qKhzKr4xXGvBQXnzHn5evJAEAtZiIb0wY= +cloud.google.com/go/asset v1.11.1/go.mod h1:fSwLhbRvC9p9CXQHJ3BgFeQNM4c9x10lqlrdEUYXlJo= +cloud.google.com/go/asset v1.12.0/go.mod h1:h9/sFOa4eDIyKmH6QMpm4eUK3pDojWnUhTgJlk762Hg= +cloud.google.com/go/asset v1.13.0/go.mod h1:WQAMyYek/b7NBpYq/K4KJWcRqzoalEsxz/t/dTk4THw= +cloud.google.com/go/assuredworkloads v1.5.0/go.mod h1:n8HOZ6pff6re5KYfBXcFvSViQjDwxFkAkmUFffJRbbY= +cloud.google.com/go/assuredworkloads v1.6.0/go.mod h1:yo2YOk37Yc89Rsd5QMVECvjaMKymF9OP+QXWlKXUkXw= +cloud.google.com/go/assuredworkloads v1.7.0/go.mod h1:z/736/oNmtGAyU47reJgGN+KVoYoxeLBoj4XkKYscNI= +cloud.google.com/go/assuredworkloads v1.8.0/go.mod h1:AsX2cqyNCOvEQC8RMPnoc0yEarXQk6WEKkxYfL6kGIo= +cloud.google.com/go/assuredworkloads v1.9.0/go.mod h1:kFuI1P78bplYtT77Tb1hi0FMxM0vVpRC7VVoJC3ZoT0= +cloud.google.com/go/assuredworkloads v1.10.0/go.mod h1:kwdUQuXcedVdsIaKgKTp9t0UJkE5+PAVNhdQm4ZVq2E= +cloud.google.com/go/auth v0.16.4 h1:fXOAIQmkApVvcIn7Pc2+5J8QTMVbUGLscnSVNl11su8= +cloud.google.com/go/auth v0.16.4/go.mod h1:j10ncYwjX/g3cdX7GpEzsdM+d+ZNsXAbb6qXA7p1Y5M= +cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc= +cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c= +cloud.google.com/go/automl v1.5.0/go.mod h1:34EjfoFGMZ5sgJ9EoLsRtdPSNZLcfflJR39VbVNS2M0= +cloud.google.com/go/automl v1.6.0/go.mod h1:ugf8a6Fx+zP0D59WLhqgTDsQI9w07o64uf/Is3Nh5p8= +cloud.google.com/go/automl v1.7.0/go.mod h1:RL9MYCCsJEOmt0Wf3z9uzG0a7adTT1fe+aObgSpkCt8= +cloud.google.com/go/automl v1.8.0/go.mod h1:xWx7G/aPEe/NP+qzYXktoBSDfjO+vnKMGgsApGJJquM= +cloud.google.com/go/automl v1.12.0/go.mod h1:tWDcHDp86aMIuHmyvjuKeeHEGq76lD7ZqfGLN6B0NuU= +cloud.google.com/go/baremetalsolution v0.3.0/go.mod h1:XOrocE+pvK1xFfleEnShBlNAXf+j5blPPxrhjKgnIFc= +cloud.google.com/go/baremetalsolution v0.4.0/go.mod h1:BymplhAadOO/eBa7KewQ0Ppg4A4Wplbn+PsFKRLo0uI= +cloud.google.com/go/baremetalsolution v0.5.0/go.mod h1:dXGxEkmR9BMwxhzBhV0AioD0ULBmuLZI8CdwalUxuss= +cloud.google.com/go/batch v0.3.0/go.mod h1:TR18ZoAekj1GuirsUsR1ZTKN3FC/4UDnScjT8NXImFE= +cloud.google.com/go/batch v0.4.0/go.mod h1:WZkHnP43R/QCGQsZ+0JyG4i79ranE2u8xvjq/9+STPE= +cloud.google.com/go/batch v0.7.0/go.mod h1:vLZN95s6teRUqRQ4s3RLDsH8PvboqBK+rn1oevL159g= +cloud.google.com/go/beyondcorp v0.2.0/go.mod h1:TB7Bd+EEtcw9PCPQhCJtJGjk/7TC6ckmnSFS+xwTfm4= +cloud.google.com/go/beyondcorp v0.3.0/go.mod h1:E5U5lcrcXMsCuoDNyGrpyTm/hn7ne941Jz2vmksAxW8= +cloud.google.com/go/beyondcorp v0.4.0/go.mod h1:3ApA0mbhHx6YImmuubf5pyW8srKnCEPON32/5hj+RmM= +cloud.google.com/go/beyondcorp v0.5.0/go.mod h1:uFqj9X+dSfrheVp7ssLTaRHd2EHqSL4QZmH4e8WXGGU= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/bigquery v1.42.0/go.mod h1:8dRTJxhtG+vwBKzE5OseQn/hiydoQN3EedCaOdYmxRA= +cloud.google.com/go/bigquery v1.43.0/go.mod h1:ZMQcXHsl+xmU1z36G2jNGZmKp9zNY5BUua5wDgmNCfw= +cloud.google.com/go/bigquery v1.44.0/go.mod h1:0Y33VqXTEsbamHJvJHdFmtqHvMIY28aK1+dFsvaChGc= +cloud.google.com/go/bigquery v1.47.0/go.mod h1:sA9XOgy0A8vQK9+MWhEQTY6Tix87M/ZurWFIxmF9I/E= +cloud.google.com/go/bigquery v1.48.0/go.mod h1:QAwSz+ipNgfL5jxiaK7weyOhzdoAy1zFm0Nf1fysJac= +cloud.google.com/go/bigquery v1.49.0/go.mod h1:Sv8hMmTFFYBlt/ftw2uN6dFdQPzBlREY9yBh7Oy7/4Q= +cloud.google.com/go/bigquery v1.50.0/go.mod h1:YrleYEh2pSEbgTBZYMJ5SuSr0ML3ypjRB1zgf7pvQLU= +cloud.google.com/go/billing v1.4.0/go.mod h1:g9IdKBEFlItS8bTtlrZdVLWSSdSyFUZKXNS02zKMOZY= +cloud.google.com/go/billing v1.5.0/go.mod h1:mztb1tBc3QekhjSgmpf/CV4LzWXLzCArwpLmP2Gm88s= +cloud.google.com/go/billing v1.6.0/go.mod h1:WoXzguj+BeHXPbKfNWkqVtDdzORazmCjraY+vrxcyvI= +cloud.google.com/go/billing v1.7.0/go.mod h1:q457N3Hbj9lYwwRbnlD7vUpyjq6u5U1RAOArInEiD5Y= +cloud.google.com/go/billing v1.12.0/go.mod h1:yKrZio/eu+okO/2McZEbch17O5CB5NpZhhXG6Z766ss= +cloud.google.com/go/billing v1.13.0/go.mod h1:7kB2W9Xf98hP9Sr12KfECgfGclsH3CQR0R08tnRlRbc= +cloud.google.com/go/binaryauthorization v1.1.0/go.mod h1:xwnoWu3Y84jbuHa0zd526MJYmtnVXn0syOjaJgy4+dM= +cloud.google.com/go/binaryauthorization v1.2.0/go.mod h1:86WKkJHtRcv5ViNABtYMhhNWRrD1Vpi//uKEy7aYEfI= +cloud.google.com/go/binaryauthorization v1.3.0/go.mod h1:lRZbKgjDIIQvzYQS1p99A7/U1JqvqeZg0wiI5tp6tg0= +cloud.google.com/go/binaryauthorization v1.4.0/go.mod h1:tsSPQrBd77VLplV70GUhBf/Zm3FsKmgSqgm4UmiDItk= +cloud.google.com/go/binaryauthorization v1.5.0/go.mod h1:OSe4OU1nN/VswXKRBmciKpo9LulY41gch5c68htf3/Q= +cloud.google.com/go/certificatemanager v1.3.0/go.mod h1:n6twGDvcUBFu9uBgt4eYvvf3sQ6My8jADcOVwHmzadg= +cloud.google.com/go/certificatemanager v1.4.0/go.mod h1:vowpercVFyqs8ABSmrdV+GiFf2H/ch3KyudYQEMM590= +cloud.google.com/go/certificatemanager v1.6.0/go.mod h1:3Hh64rCKjRAX8dXgRAyOcY5vQ/fE1sh8o+Mdd6KPgY8= +cloud.google.com/go/channel v1.8.0/go.mod h1:W5SwCXDJsq/rg3tn3oG0LOxpAo6IMxNa09ngphpSlnk= +cloud.google.com/go/channel v1.9.0/go.mod h1:jcu05W0my9Vx4mt3/rEHpfxc9eKi9XwsdDL8yBMbKUk= +cloud.google.com/go/channel v1.11.0/go.mod h1:IdtI0uWGqhEeatSB62VOoJ8FSUhJ9/+iGkJVqp74CGE= +cloud.google.com/go/channel v1.12.0/go.mod h1:VkxCGKASi4Cq7TbXxlaBezonAYpp1GCnKMY6tnMQnLU= +cloud.google.com/go/cloudbuild v1.3.0/go.mod h1:WequR4ULxlqvMsjDEEEFnOG5ZSRSgWOywXYDb1vPE6U= +cloud.google.com/go/cloudbuild v1.4.0/go.mod h1:5Qwa40LHiOXmz3386FrjrYM93rM/hdRr7b53sySrTqA= +cloud.google.com/go/cloudbuild v1.6.0/go.mod h1:UIbc/w9QCbH12xX+ezUsgblrWv+Cv4Tw83GiSMHOn9M= +cloud.google.com/go/cloudbuild v1.7.0/go.mod h1:zb5tWh2XI6lR9zQmsm1VRA+7OCuve5d8S+zJUul8KTg= +cloud.google.com/go/cloudbuild v1.9.0/go.mod h1:qK1d7s4QlO0VwfYn5YuClDGg2hfmLZEb4wQGAbIgL1s= +cloud.google.com/go/clouddms v1.3.0/go.mod h1:oK6XsCDdW4Ib3jCCBugx+gVjevp2TMXFtgxvPSee3OM= +cloud.google.com/go/clouddms v1.4.0/go.mod h1:Eh7sUGCC+aKry14O1NRljhjyrr0NFC0G2cjwX0cByRk= +cloud.google.com/go/clouddms v1.5.0/go.mod h1:QSxQnhikCLUw13iAbffF2CZxAER3xDGNHjsTAkQJcQA= +cloud.google.com/go/cloudtasks v1.5.0/go.mod h1:fD92REy1x5woxkKEkLdvavGnPJGEn8Uic9nWuLzqCpY= +cloud.google.com/go/cloudtasks v1.6.0/go.mod h1:C6Io+sxuke9/KNRkbQpihnW93SWDU3uXt92nu85HkYI= +cloud.google.com/go/cloudtasks v1.7.0/go.mod h1:ImsfdYWwlWNJbdgPIIGJWC+gemEGTBK/SunNQQNCAb4= +cloud.google.com/go/cloudtasks v1.8.0/go.mod h1:gQXUIwCSOI4yPVK7DgTVFiiP0ZW/eQkydWzwVMdHxrI= +cloud.google.com/go/cloudtasks v1.9.0/go.mod h1:w+EyLsVkLWHcOaqNEyvcKAsWp9p29dL6uL9Nst1cI7Y= +cloud.google.com/go/cloudtasks v1.10.0/go.mod h1:NDSoTLkZ3+vExFEWu2UJV1arUyzVDAiZtdWcsUyNwBs= +cloud.google.com/go/compute v0.1.0/go.mod h1:GAesmwr110a34z04OlxYkATPBEfVhkymfTBXtfbBFow= +cloud.google.com/go/compute v1.3.0/go.mod h1:cCZiE1NHEtai4wiufUhW8I8S1JKkAnhnQJWM7YD99wM= +cloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6mkzQJeu0M= +cloud.google.com/go/compute v1.6.0/go.mod h1:T29tfhtVbq1wvAPo0E3+7vhgmkOYeXjhFvz/FMzPu0s= +cloud.google.com/go/compute v1.6.1/go.mod h1:g85FgpzFvNULZ+S8AYq87axRKuf2Kh7deLqV/jJ3thU= +cloud.google.com/go/compute v1.7.0/go.mod h1:435lt8av5oL9P3fv1OEzSbSUe+ybHXGMPQHHZWZxy9U= +cloud.google.com/go/compute v1.10.0/go.mod h1:ER5CLbMxl90o2jtNbGSbtfOpQKR0t15FOtRsugnLrlU= +cloud.google.com/go/compute v1.12.0/go.mod h1:e8yNOBcBONZU1vJKCvCoDw/4JQsA0dpM4x/6PIIOocU= +cloud.google.com/go/compute v1.12.1/go.mod h1:e8yNOBcBONZU1vJKCvCoDw/4JQsA0dpM4x/6PIIOocU= +cloud.google.com/go/compute v1.13.0/go.mod h1:5aPTS0cUNMIc1CE546K+Th6weJUNQErARyZtRXDJ8GE= +cloud.google.com/go/compute v1.14.0/go.mod h1:YfLtxrj9sU4Yxv+sXzZkyPjEyPBZfXHUvjxega5vAdo= +cloud.google.com/go/compute v1.15.1/go.mod h1:bjjoF/NtFUrkD/urWfdHaKuOPDR5nWIs63rR+SXhcpA= +cloud.google.com/go/compute v1.18.0/go.mod h1:1X7yHxec2Ga+Ss6jPyjxRxpu2uu7PLgsOVXvgU0yacs= +cloud.google.com/go/compute v1.19.0/go.mod h1:rikpw2y+UMidAe9tISo04EHNOIf42RLYF/q8Bs93scU= +cloud.google.com/go/compute v1.19.1/go.mod h1:6ylj3a05WF8leseCdIf77NK0g1ey+nj5IKd5/kvShxE= +cloud.google.com/go/compute/metadata v0.1.0/go.mod h1:Z1VN+bulIf6bt4P/C37K4DyZYZEXYonfTBHHFPO/4UU= +cloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= +cloud.google.com/go/compute/metadata v0.2.1/go.mod h1:jgHgmJd2RKBGzXqF5LR2EZMGxBkeanZ9wwa75XHJgOM= +cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= +cloud.google.com/go/compute/metadata v0.8.0 h1:HxMRIbao8w17ZX6wBnjhcDkW6lTFpgcaobyVfZWqRLA= +cloud.google.com/go/compute/metadata v0.8.0/go.mod h1:sYOGTp851OV9bOFJ9CH7elVvyzopvWQFNNghtDQ/Biw= +cloud.google.com/go/contactcenterinsights v1.3.0/go.mod h1:Eu2oemoePuEFc/xKFPjbTuPSj0fYJcPls9TFlPNnHHY= +cloud.google.com/go/contactcenterinsights v1.4.0/go.mod h1:L2YzkGbPsv+vMQMCADxJoT9YiTTnSEd6fEvCeHTYVck= +cloud.google.com/go/contactcenterinsights v1.6.0/go.mod h1:IIDlT6CLcDoyv79kDv8iWxMSTZhLxSCofVV5W6YFM/w= +cloud.google.com/go/container v1.6.0/go.mod h1:Xazp7GjJSeUYo688S+6J5V+n/t+G5sKBTFkKNudGRxg= +cloud.google.com/go/container v1.7.0/go.mod h1:Dp5AHtmothHGX3DwwIHPgq45Y8KmNsgN3amoYfxVkLo= +cloud.google.com/go/container v1.13.1/go.mod h1:6wgbMPeQRw9rSnKBCAJXnds3Pzj03C4JHamr8asWKy4= +cloud.google.com/go/container v1.14.0/go.mod h1:3AoJMPhHfLDxLvrlVWaK57IXzaPnLaZq63WX59aQBfM= +cloud.google.com/go/container v1.15.0/go.mod h1:ft+9S0WGjAyjDggg5S06DXj+fHJICWg8L7isCQe9pQA= +cloud.google.com/go/containeranalysis v0.5.1/go.mod h1:1D92jd8gRR/c0fGMlymRgxWD3Qw9C1ff6/T7mLgVL8I= +cloud.google.com/go/containeranalysis v0.6.0/go.mod h1:HEJoiEIu+lEXM+k7+qLCci0h33lX3ZqoYFdmPcoO7s4= +cloud.google.com/go/containeranalysis v0.7.0/go.mod h1:9aUL+/vZ55P2CXfuZjS4UjQ9AgXoSw8Ts6lemfmxBxI= +cloud.google.com/go/containeranalysis v0.9.0/go.mod h1:orbOANbwk5Ejoom+s+DUCTTJ7IBdBQJDcSylAx/on9s= +cloud.google.com/go/datacatalog v1.3.0/go.mod h1:g9svFY6tuR+j+hrTw3J2dNcmI0dzmSiyOzm8kpLq0a0= +cloud.google.com/go/datacatalog v1.5.0/go.mod h1:M7GPLNQeLfWqeIm3iuiruhPzkt65+Bx8dAKvScX8jvs= +cloud.google.com/go/datacatalog v1.6.0/go.mod h1:+aEyF8JKg+uXcIdAmmaMUmZ3q1b/lKLtXCmXdnc0lbc= +cloud.google.com/go/datacatalog v1.7.0/go.mod h1:9mEl4AuDYWw81UGc41HonIHH7/sn52H0/tc8f8ZbZIE= +cloud.google.com/go/datacatalog v1.8.0/go.mod h1:KYuoVOv9BM8EYz/4eMFxrr4DUKhGIOXxZoKYF5wdISM= +cloud.google.com/go/datacatalog v1.8.1/go.mod h1:RJ58z4rMp3gvETA465Vg+ag8BGgBdnRPEMMSTr5Uv+M= +cloud.google.com/go/datacatalog v1.12.0/go.mod h1:CWae8rFkfp6LzLumKOnmVh4+Zle4A3NXLzVJ1d1mRm0= +cloud.google.com/go/datacatalog v1.13.0/go.mod h1:E4Rj9a5ZtAxcQJlEBTLgMTphfP11/lNaAshpoBgemX8= +cloud.google.com/go/dataflow v0.6.0/go.mod h1:9QwV89cGoxjjSR9/r7eFDqqjtvbKxAK2BaYU6PVk9UM= +cloud.google.com/go/dataflow v0.7.0/go.mod h1:PX526vb4ijFMesO1o202EaUmouZKBpjHsTlCtB4parQ= +cloud.google.com/go/dataflow v0.8.0/go.mod h1:Rcf5YgTKPtQyYz8bLYhFoIV/vP39eL7fWNcSOyFfLJE= +cloud.google.com/go/dataform v0.3.0/go.mod h1:cj8uNliRlHpa6L3yVhDOBrUXH+BPAO1+KFMQQNSThKo= +cloud.google.com/go/dataform v0.4.0/go.mod h1:fwV6Y4Ty2yIFL89huYlEkwUPtS7YZinZbzzj5S9FzCE= +cloud.google.com/go/dataform v0.5.0/go.mod h1:GFUYRe8IBa2hcomWplodVmUx/iTL0FrsauObOM3Ipr0= +cloud.google.com/go/dataform v0.6.0/go.mod h1:QPflImQy33e29VuapFdf19oPbE4aYTJxr31OAPV+ulA= +cloud.google.com/go/dataform v0.7.0/go.mod h1:7NulqnVozfHvWUBpMDfKMUESr+85aJsC/2O0o3jWPDE= +cloud.google.com/go/datafusion v1.4.0/go.mod h1:1Zb6VN+W6ALo85cXnM1IKiPw+yQMKMhB9TsTSRDo/38= +cloud.google.com/go/datafusion v1.5.0/go.mod h1:Kz+l1FGHB0J+4XF2fud96WMmRiq/wj8N9u007vyXZ2w= +cloud.google.com/go/datafusion v1.6.0/go.mod h1:WBsMF8F1RhSXvVM8rCV3AeyWVxcC2xY6vith3iw3S+8= +cloud.google.com/go/datalabeling v0.5.0/go.mod h1:TGcJ0G2NzcsXSE/97yWjIZO0bXj0KbVlINXMG9ud42I= +cloud.google.com/go/datalabeling v0.6.0/go.mod h1:WqdISuk/+WIGeMkpw/1q7bK/tFEZxsrFJOJdY2bXvTQ= +cloud.google.com/go/datalabeling v0.7.0/go.mod h1:WPQb1y08RJbmpM3ww0CSUAGweL0SxByuW2E+FU+wXcM= +cloud.google.com/go/dataplex v1.3.0/go.mod h1:hQuRtDg+fCiFgC8j0zV222HvzFQdRd+SVX8gdmFcZzA= +cloud.google.com/go/dataplex v1.4.0/go.mod h1:X51GfLXEMVJ6UN47ESVqvlsRplbLhcsAt0kZCCKsU0A= +cloud.google.com/go/dataplex v1.5.2/go.mod h1:cVMgQHsmfRoI5KFYq4JtIBEUbYwc3c7tXmIDhRmNNVQ= +cloud.google.com/go/dataplex v1.6.0/go.mod h1:bMsomC/aEJOSpHXdFKFGQ1b0TDPIeL28nJObeO1ppRs= +cloud.google.com/go/dataproc v1.7.0/go.mod h1:CKAlMjII9H90RXaMpSxQ8EU6dQx6iAYNPcYPOkSbi8s= +cloud.google.com/go/dataproc v1.8.0/go.mod h1:5OW+zNAH0pMpw14JVrPONsxMQYMBqJuzORhIBfBn9uI= +cloud.google.com/go/dataproc v1.12.0/go.mod h1:zrF3aX0uV3ikkMz6z4uBbIKyhRITnxvr4i3IjKsKrw4= +cloud.google.com/go/dataqna v0.5.0/go.mod h1:90Hyk596ft3zUQ8NkFfvICSIfHFh1Bc7C4cK3vbhkeo= +cloud.google.com/go/dataqna v0.6.0/go.mod h1:1lqNpM7rqNLVgWBJyk5NF6Uen2PHym0jtVJonplVsDA= +cloud.google.com/go/dataqna v0.7.0/go.mod h1:Lx9OcIIeqCrw1a6KdO3/5KMP1wAmTc0slZWwP12Qq3c= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/datastore v1.10.0/go.mod h1:PC5UzAmDEkAmkfaknstTYbNpgE49HAgW2J1gcgUfmdM= +cloud.google.com/go/datastore v1.11.0/go.mod h1:TvGxBIHCS50u8jzG+AW/ppf87v1of8nwzFNgEZU1D3c= +cloud.google.com/go/datastream v1.2.0/go.mod h1:i/uTP8/fZwgATHS/XFu0TcNUhuA0twZxxQ3EyCUQMwo= +cloud.google.com/go/datastream v1.3.0/go.mod h1:cqlOX8xlyYF/uxhiKn6Hbv6WjwPPuI9W2M9SAXwaLLQ= +cloud.google.com/go/datastream v1.4.0/go.mod h1:h9dpzScPhDTs5noEMQVWP8Wx8AFBRyS0s8KWPx/9r0g= +cloud.google.com/go/datastream v1.5.0/go.mod h1:6TZMMNPwjUqZHBKPQ1wwXpb0d5VDVPl2/XoS5yi88q4= +cloud.google.com/go/datastream v1.6.0/go.mod h1:6LQSuswqLa7S4rPAOZFVjHIG3wJIjZcZrw8JDEDJuIs= +cloud.google.com/go/datastream v1.7.0/go.mod h1:uxVRMm2elUSPuh65IbZpzJNMbuzkcvu5CjMqVIUHrww= +cloud.google.com/go/deploy v1.4.0/go.mod h1:5Xghikd4VrmMLNaF6FiRFDlHb59VM59YoDQnOUdsH/c= +cloud.google.com/go/deploy v1.5.0/go.mod h1:ffgdD0B89tToyW/U/D2eL0jN2+IEV/3EMuXHA0l4r+s= +cloud.google.com/go/deploy v1.6.0/go.mod h1:f9PTHehG/DjCom3QH0cntOVRm93uGBDt2vKzAPwpXQI= +cloud.google.com/go/deploy v1.8.0/go.mod h1:z3myEJnA/2wnB4sgjqdMfgxCA0EqC3RBTNcVPs93mtQ= +cloud.google.com/go/dialogflow v1.15.0/go.mod h1:HbHDWs33WOGJgn6rfzBW1Kv807BE3O1+xGbn59zZWI4= +cloud.google.com/go/dialogflow v1.16.1/go.mod h1:po6LlzGfK+smoSmTBnbkIZY2w8ffjz/RcGSS+sh1el0= +cloud.google.com/go/dialogflow v1.17.0/go.mod h1:YNP09C/kXA1aZdBgC/VtXX74G/TKn7XVCcVumTflA+8= +cloud.google.com/go/dialogflow v1.18.0/go.mod h1:trO7Zu5YdyEuR+BhSNOqJezyFQ3aUzz0njv7sMx/iek= +cloud.google.com/go/dialogflow v1.19.0/go.mod h1:JVmlG1TwykZDtxtTXujec4tQ+D8SBFMoosgy+6Gn0s0= +cloud.google.com/go/dialogflow v1.29.0/go.mod h1:b+2bzMe+k1s9V+F2jbJwpHPzrnIyHihAdRFMtn2WXuM= +cloud.google.com/go/dialogflow v1.31.0/go.mod h1:cuoUccuL1Z+HADhyIA7dci3N5zUssgpBJmCzI6fNRB4= +cloud.google.com/go/dialogflow v1.32.0/go.mod h1:jG9TRJl8CKrDhMEcvfcfFkkpp8ZhgPz3sBGmAUYJ2qE= +cloud.google.com/go/dlp v1.6.0/go.mod h1:9eyB2xIhpU0sVwUixfBubDoRwP+GjeUoxxeueZmqvmM= +cloud.google.com/go/dlp v1.7.0/go.mod h1:68ak9vCiMBjbasxeVD17hVPxDEck+ExiHavX8kiHG+Q= +cloud.google.com/go/dlp v1.9.0/go.mod h1:qdgmqgTyReTz5/YNSSuueR8pl7hO0o9bQ39ZhtgkWp4= +cloud.google.com/go/documentai v1.7.0/go.mod h1:lJvftZB5NRiFSX4moiye1SMxHx0Bc3x1+p9e/RfXYiU= +cloud.google.com/go/documentai v1.8.0/go.mod h1:xGHNEB7CtsnySCNrCFdCyyMz44RhFEEX2Q7UD0c5IhU= +cloud.google.com/go/documentai v1.9.0/go.mod h1:FS5485S8R00U10GhgBC0aNGrJxBP8ZVpEeJ7PQDZd6k= +cloud.google.com/go/documentai v1.10.0/go.mod h1:vod47hKQIPeCfN2QS/jULIvQTugbmdc0ZvxxfQY1bg4= +cloud.google.com/go/documentai v1.16.0/go.mod h1:o0o0DLTEZ+YnJZ+J4wNfTxmDVyrkzFvttBXXtYRMHkM= +cloud.google.com/go/documentai v1.18.0/go.mod h1:F6CK6iUH8J81FehpskRmhLq/3VlwQvb7TvwOceQ2tbs= +cloud.google.com/go/domains v0.6.0/go.mod h1:T9Rz3GasrpYk6mEGHh4rymIhjlnIuB4ofT1wTxDeT4Y= +cloud.google.com/go/domains v0.7.0/go.mod h1:PtZeqS1xjnXuRPKE/88Iru/LdfoRyEHYA9nFQf4UKpg= +cloud.google.com/go/domains v0.8.0/go.mod h1:M9i3MMDzGFXsydri9/vW+EWz9sWb4I6WyHqdlAk0idE= +cloud.google.com/go/edgecontainer v0.1.0/go.mod h1:WgkZ9tp10bFxqO8BLPqv2LlfmQF1X8lZqwW4r1BTajk= +cloud.google.com/go/edgecontainer v0.2.0/go.mod h1:RTmLijy+lGpQ7BXuTDa4C4ssxyXT34NIuHIgKuP4s5w= +cloud.google.com/go/edgecontainer v0.3.0/go.mod h1:FLDpP4nykgwwIfcLt6zInhprzw0lEi2P1fjO6Ie0qbc= +cloud.google.com/go/edgecontainer v1.0.0/go.mod h1:cttArqZpBB2q58W/upSG++ooo6EsblxDIolxa3jSjbY= +cloud.google.com/go/errorreporting v0.3.0/go.mod h1:xsP2yaAp+OAW4OIm60An2bbLpqIhKXdWR/tawvl7QzU= +cloud.google.com/go/essentialcontacts v1.3.0/go.mod h1:r+OnHa5jfj90qIfZDO/VztSFqbQan7HV75p8sA+mdGI= +cloud.google.com/go/essentialcontacts v1.4.0/go.mod h1:8tRldvHYsmnBCHdFpvU+GL75oWiBKl80BiqlFh9tp+8= +cloud.google.com/go/essentialcontacts v1.5.0/go.mod h1:ay29Z4zODTuwliK7SnX8E86aUF2CTzdNtvv42niCX0M= +cloud.google.com/go/eventarc v1.7.0/go.mod h1:6ctpF3zTnaQCxUjHUdcfgcA1A2T309+omHZth7gDfmc= +cloud.google.com/go/eventarc v1.8.0/go.mod h1:imbzxkyAU4ubfsaKYdQg04WS1NvncblHEup4kvF+4gw= +cloud.google.com/go/eventarc v1.10.0/go.mod h1:u3R35tmZ9HvswGRBnF48IlYgYeBcPUCjkr4BTdem2Kw= +cloud.google.com/go/eventarc v1.11.0/go.mod h1:PyUjsUKPWoRBCHeOxZd/lbOOjahV41icXyUY5kSTvVY= +cloud.google.com/go/filestore v1.3.0/go.mod h1:+qbvHGvXU1HaKX2nD0WEPo92TP/8AQuCVEBXNY9z0+w= +cloud.google.com/go/filestore v1.4.0/go.mod h1:PaG5oDfo9r224f8OYXURtAsY+Fbyq/bLYoINEK8XQAI= +cloud.google.com/go/filestore v1.5.0/go.mod h1:FqBXDWBp4YLHqRnVGveOkHDf8svj9r5+mUDLupOWEDs= +cloud.google.com/go/filestore v1.6.0/go.mod h1:di5unNuss/qfZTw2U9nhFqo8/ZDSc466dre85Kydllg= +cloud.google.com/go/firestore v1.9.0/go.mod h1:HMkjKHNTtRyZNiMzu7YAsLr9K3X2udY2AMwDaMEQiiE= +cloud.google.com/go/functions v1.6.0/go.mod h1:3H1UA3qiIPRWD7PeZKLvHZ9SaQhR26XIJcC0A5GbvAk= +cloud.google.com/go/functions v1.7.0/go.mod h1:+d+QBcWM+RsrgZfV9xo6KfA1GlzJfxcfZcRPEhDDfzg= +cloud.google.com/go/functions v1.8.0/go.mod h1:RTZ4/HsQjIqIYP9a9YPbU+QFoQsAlYgrwOXJWHn1POY= +cloud.google.com/go/functions v1.9.0/go.mod h1:Y+Dz8yGguzO3PpIjhLTbnqV1CWmgQ5UwtlpzoyquQ08= +cloud.google.com/go/functions v1.10.0/go.mod h1:0D3hEOe3DbEvCXtYOZHQZmD+SzYsi1YbI7dGvHfldXw= +cloud.google.com/go/functions v1.12.0/go.mod h1:AXWGrF3e2C/5ehvwYo/GH6O5s09tOPksiKhz+hH8WkA= +cloud.google.com/go/functions v1.13.0/go.mod h1:EU4O007sQm6Ef/PwRsI8N2umygGqPBS/IZQKBQBcJ3c= +cloud.google.com/go/gaming v1.5.0/go.mod h1:ol7rGcxP/qHTRQE/RO4bxkXq+Fix0j6D4LFPzYTIrDM= +cloud.google.com/go/gaming v1.6.0/go.mod h1:YMU1GEvA39Qt3zWGyAVA9bpYz/yAhTvaQ1t2sK4KPUA= +cloud.google.com/go/gaming v1.7.0/go.mod h1:LrB8U7MHdGgFG851iHAfqUdLcKBdQ55hzXy9xBJz0+w= +cloud.google.com/go/gaming v1.8.0/go.mod h1:xAqjS8b7jAVW0KFYeRUxngo9My3f33kFmua++Pi+ggM= +cloud.google.com/go/gaming v1.9.0/go.mod h1:Fc7kEmCObylSWLO334NcO+O9QMDyz+TKC4v1D7X+Bc0= +cloud.google.com/go/gkebackup v0.2.0/go.mod h1:XKvv/4LfG829/B8B7xRkk8zRrOEbKtEam6yNfuQNH60= +cloud.google.com/go/gkebackup v0.3.0/go.mod h1:n/E671i1aOQvUxT541aTkCwExO/bTer2HDlj4TsBRAo= +cloud.google.com/go/gkebackup v0.4.0/go.mod h1:byAyBGUwYGEEww7xsbnUTBHIYcOPy/PgUWUtOeRm9Vg= +cloud.google.com/go/gkeconnect v0.5.0/go.mod h1:c5lsNAg5EwAy7fkqX/+goqFsU1Da/jQFqArp+wGNr/o= +cloud.google.com/go/gkeconnect v0.6.0/go.mod h1:Mln67KyU/sHJEBY8kFZ0xTeyPtzbq9StAVvEULYK16A= +cloud.google.com/go/gkeconnect v0.7.0/go.mod h1:SNfmVqPkaEi3bF/B3CNZOAYPYdg7sU+obZ+QTky2Myw= +cloud.google.com/go/gkehub v0.9.0/go.mod h1:WYHN6WG8w9bXU0hqNxt8rm5uxnk8IH+lPY9J2TV7BK0= +cloud.google.com/go/gkehub v0.10.0/go.mod h1:UIPwxI0DsrpsVoWpLB0stwKCP+WFVG9+y977wO+hBH0= +cloud.google.com/go/gkehub v0.11.0/go.mod h1:JOWHlmN+GHyIbuWQPl47/C2RFhnFKH38jH9Ascu3n0E= +cloud.google.com/go/gkehub v0.12.0/go.mod h1:djiIwwzTTBrF5NaXCGv3mf7klpEMcST17VBTVVDcuaw= +cloud.google.com/go/gkemulticloud v0.3.0/go.mod h1:7orzy7O0S+5kq95e4Hpn7RysVA7dPs8W/GgfUtsPbrA= +cloud.google.com/go/gkemulticloud v0.4.0/go.mod h1:E9gxVBnseLWCk24ch+P9+B2CoDFJZTyIgLKSalC7tuI= +cloud.google.com/go/gkemulticloud v0.5.0/go.mod h1:W0JDkiyi3Tqh0TJr//y19wyb1yf8llHVto2Htf2Ja3Y= +cloud.google.com/go/grafeas v0.2.0/go.mod h1:KhxgtF2hb0P191HlY5besjYm6MqTSTj3LSI+M+ByZHc= +cloud.google.com/go/gsuiteaddons v1.3.0/go.mod h1:EUNK/J1lZEZO8yPtykKxLXI6JSVN2rg9bN8SXOa0bgM= +cloud.google.com/go/gsuiteaddons v1.4.0/go.mod h1:rZK5I8hht7u7HxFQcFei0+AtfS9uSushomRlg+3ua1o= +cloud.google.com/go/gsuiteaddons v1.5.0/go.mod h1:TFCClYLd64Eaa12sFVmUyG62tk4mdIsI7pAnSXRkcFo= +cloud.google.com/go/iam v0.1.0/go.mod h1:vcUNEa0pEm0qRVpmWepWaFMIAI8/hjB9mO8rNCJtF6c= +cloud.google.com/go/iam v0.3.0/go.mod h1:XzJPvDayI+9zsASAFO68Hk07u3z+f+JrT2xXNdp4bnY= +cloud.google.com/go/iam v0.5.0/go.mod h1:wPU9Vt0P4UmCux7mqtRu6jcpPAb74cP1fh50J3QpkUc= +cloud.google.com/go/iam v0.6.0/go.mod h1:+1AH33ueBne5MzYccyMHtEKqLE4/kJOibtffMHDMFMc= +cloud.google.com/go/iam v0.7.0/go.mod h1:H5Br8wRaDGNc8XP3keLc4unfUUZeyH3Sfl9XpQEYOeg= +cloud.google.com/go/iam v0.8.0/go.mod h1:lga0/y3iH6CX7sYqypWJ33hf7kkfXJag67naqGESjkE= +cloud.google.com/go/iam v0.11.0/go.mod h1:9PiLDanza5D+oWFZiH1uG+RnRCfEGKoyl6yo4cgWZGY= +cloud.google.com/go/iam v0.12.0/go.mod h1:knyHGviacl11zrtZUoDuYpDgLjvr28sLQaG0YB2GYAY= +cloud.google.com/go/iam v0.13.0/go.mod h1:ljOg+rcNfzZ5d6f1nAUJ8ZIxOaZUVoS14bKCtaLZ/D0= +cloud.google.com/go/iam v1.5.2 h1:qgFRAGEmd8z6dJ/qyEchAuL9jpswyODjA2lS+w234g8= +cloud.google.com/go/iam v1.5.2/go.mod h1:SE1vg0N81zQqLzQEwxL2WI6yhetBdbNQuTvIKCSkUHE= +cloud.google.com/go/iap v1.4.0/go.mod h1:RGFwRJdihTINIe4wZ2iCP0zF/qu18ZwyKxrhMhygBEc= +cloud.google.com/go/iap v1.5.0/go.mod h1:UH/CGgKd4KyohZL5Pt0jSKE4m3FR51qg6FKQ/z/Ix9A= +cloud.google.com/go/iap v1.6.0/go.mod h1:NSuvI9C/j7UdjGjIde7t7HBz+QTwBcapPE07+sSRcLk= +cloud.google.com/go/iap v1.7.0/go.mod h1:beqQx56T9O1G1yNPph+spKpNibDlYIiIixiqsQXxLIo= +cloud.google.com/go/iap v1.7.1/go.mod h1:WapEwPc7ZxGt2jFGB/C/bm+hP0Y6NXzOYGjpPnmMS74= +cloud.google.com/go/ids v1.1.0/go.mod h1:WIuwCaYVOzHIj2OhN9HAwvW+DBdmUAdcWlFxRl+KubM= +cloud.google.com/go/ids v1.2.0/go.mod h1:5WXvp4n25S0rA/mQWAg1YEEBBq6/s+7ml1RDCW1IrcY= +cloud.google.com/go/ids v1.3.0/go.mod h1:JBdTYwANikFKaDP6LtW5JAi4gubs57SVNQjemdt6xV4= +cloud.google.com/go/iot v1.3.0/go.mod h1:r7RGh2B61+B8oz0AGE+J72AhA0G7tdXItODWsaA2oLs= +cloud.google.com/go/iot v1.4.0/go.mod h1:dIDxPOn0UvNDUMD8Ger7FIaTuvMkj+aGk94RPP0iV+g= +cloud.google.com/go/iot v1.5.0/go.mod h1:mpz5259PDl3XJthEmh9+ap0affn/MqNSP4My77Qql9o= +cloud.google.com/go/iot v1.6.0/go.mod h1:IqdAsmE2cTYYNO1Fvjfzo9po179rAtJeVGUvkLN3rLE= +cloud.google.com/go/kms v1.4.0/go.mod h1:fajBHndQ+6ubNw6Ss2sSd+SWvjL26RNo/dr7uxsnnOA= +cloud.google.com/go/kms v1.5.0/go.mod h1:QJS2YY0eJGBg3mnDfuaCyLauWwBJiHRboYxJ++1xJNg= +cloud.google.com/go/kms v1.6.0/go.mod h1:Jjy850yySiasBUDi6KFUwUv2n1+o7QZFyuUJg6OgjA0= +cloud.google.com/go/kms v1.8.0/go.mod h1:4xFEhYFqvW+4VMELtZyxomGSYtSQKzM178ylFW4jMAg= +cloud.google.com/go/kms v1.9.0/go.mod h1:qb1tPTgfF9RQP8e1wq4cLFErVuTJv7UsSC915J8dh3w= +cloud.google.com/go/kms v1.10.0/go.mod h1:ng3KTUtQQU9bPX3+QGLsflZIHlkbn8amFAMY63m8d24= +cloud.google.com/go/kms v1.10.1/go.mod h1:rIWk/TryCkR59GMC3YtHtXeLzd634lBbKenvyySAyYI= +cloud.google.com/go/language v1.4.0/go.mod h1:F9dRpNFQmJbkaop6g0JhSBXCNlO90e1KWx5iDdxbWic= +cloud.google.com/go/language v1.6.0/go.mod h1:6dJ8t3B+lUYfStgls25GusK04NLh3eDLQnWM3mdEbhI= +cloud.google.com/go/language v1.7.0/go.mod h1:DJ6dYN/W+SQOjF8e1hLQXMF21AkH2w9wiPzPCJa2MIE= +cloud.google.com/go/language v1.8.0/go.mod h1:qYPVHf7SPoNNiCL2Dr0FfEFNil1qi3pQEyygwpgVKB8= +cloud.google.com/go/language v1.9.0/go.mod h1:Ns15WooPM5Ad/5no/0n81yUetis74g3zrbeJBE+ptUY= +cloud.google.com/go/lifesciences v0.5.0/go.mod h1:3oIKy8ycWGPUyZDR/8RNnTOYevhaMLqh5vLUXs9zvT8= +cloud.google.com/go/lifesciences v0.6.0/go.mod h1:ddj6tSX/7BOnhxCSd3ZcETvtNr8NZ6t/iPhY2Tyfu08= +cloud.google.com/go/lifesciences v0.8.0/go.mod h1:lFxiEOMqII6XggGbOnKiyZ7IBwoIqA84ClvoezaA/bo= +cloud.google.com/go/logging v1.6.1/go.mod h1:5ZO0mHHbvm8gEmeEUHrmDlTDSu5imF6MUP9OfilNXBw= +cloud.google.com/go/logging v1.7.0/go.mod h1:3xjP2CjkM3ZkO73aj4ASA5wRPGGCRrPIAeNqVNkzY8M= +cloud.google.com/go/longrunning v0.1.1/go.mod h1:UUFxuDWkv22EuY93jjmDMFT5GPQKeFVJBIF6QlTqdsE= +cloud.google.com/go/longrunning v0.3.0/go.mod h1:qth9Y41RRSUE69rDcOn6DdK3HfQfsUI0YSmW3iIlLJc= +cloud.google.com/go/longrunning v0.4.1/go.mod h1:4iWDqhBZ70CvZ6BfETbvam3T8FMvLK+eFj0E6AaRQTo= +cloud.google.com/go/longrunning v0.6.7 h1:IGtfDWHhQCgCjwQjV9iiLnUta9LBCo8R9QmAFsS/PrE= +cloud.google.com/go/longrunning v0.6.7/go.mod h1:EAFV3IZAKmM56TyiE6VAP3VoTzhZzySwI/YI1s/nRsY= +cloud.google.com/go/managedidentities v1.3.0/go.mod h1:UzlW3cBOiPrzucO5qWkNkh0w33KFtBJU281hacNvsdE= +cloud.google.com/go/managedidentities v1.4.0/go.mod h1:NWSBYbEMgqmbZsLIyKvxrYbtqOsxY1ZrGM+9RgDqInM= +cloud.google.com/go/managedidentities v1.5.0/go.mod h1:+dWcZ0JlUmpuxpIDfyP5pP5y0bLdRwOS4Lp7gMni/LA= +cloud.google.com/go/maps v0.1.0/go.mod h1:BQM97WGyfw9FWEmQMpZ5T6cpovXXSd1cGmFma94eubI= +cloud.google.com/go/maps v0.6.0/go.mod h1:o6DAMMfb+aINHz/p/jbcY+mYeXBoZoxTfdSQ8VAJaCw= +cloud.google.com/go/maps v0.7.0/go.mod h1:3GnvVl3cqeSvgMcpRlQidXsPYuDGQ8naBis7MVzpXsY= +cloud.google.com/go/mediatranslation v0.5.0/go.mod h1:jGPUhGTybqsPQn91pNXw0xVHfuJ3leR1wj37oU3y1f4= +cloud.google.com/go/mediatranslation v0.6.0/go.mod h1:hHdBCTYNigsBxshbznuIMFNe5QXEowAuNmmC7h8pu5w= +cloud.google.com/go/mediatranslation v0.7.0/go.mod h1:LCnB/gZr90ONOIQLgSXagp8XUW1ODs2UmUMvcgMfI2I= +cloud.google.com/go/memcache v1.4.0/go.mod h1:rTOfiGZtJX1AaFUrOgsMHX5kAzaTQ8azHiuDoTPzNsE= +cloud.google.com/go/memcache v1.5.0/go.mod h1:dk3fCK7dVo0cUU2c36jKb4VqKPS22BTkf81Xq617aWM= +cloud.google.com/go/memcache v1.6.0/go.mod h1:XS5xB0eQZdHtTuTF9Hf8eJkKtR3pVRCcvJwtm68T3rA= +cloud.google.com/go/memcache v1.7.0/go.mod h1:ywMKfjWhNtkQTxrWxCkCFkoPjLHPW6A7WOTVI8xy3LY= +cloud.google.com/go/memcache v1.9.0/go.mod h1:8oEyzXCu+zo9RzlEaEjHl4KkgjlNDaXbCQeQWlzNFJM= +cloud.google.com/go/metastore v1.5.0/go.mod h1:2ZNrDcQwghfdtCwJ33nM0+GrBGlVuh8rakL3vdPY3XY= +cloud.google.com/go/metastore v1.6.0/go.mod h1:6cyQTls8CWXzk45G55x57DVQ9gWg7RiH65+YgPsNh9s= +cloud.google.com/go/metastore v1.7.0/go.mod h1:s45D0B4IlsINu87/AsWiEVYbLaIMeUSoxlKKDqBGFS8= +cloud.google.com/go/metastore v1.8.0/go.mod h1:zHiMc4ZUpBiM7twCIFQmJ9JMEkDSyZS9U12uf7wHqSI= +cloud.google.com/go/metastore v1.10.0/go.mod h1:fPEnH3g4JJAk+gMRnrAnoqyv2lpUCqJPWOodSaf45Eo= +cloud.google.com/go/monitoring v1.7.0/go.mod h1:HpYse6kkGo//7p6sT0wsIC6IBDET0RhIsnmlA53dvEk= +cloud.google.com/go/monitoring v1.8.0/go.mod h1:E7PtoMJ1kQXWxPjB6mv2fhC5/15jInuulFdYYtlcvT4= +cloud.google.com/go/monitoring v1.12.0/go.mod h1:yx8Jj2fZNEkL/GYZyTLS4ZtZEZN8WtDEiEqG4kLK50w= +cloud.google.com/go/monitoring v1.13.0/go.mod h1:k2yMBAB1H9JT/QETjNkgdCGD9bPF712XiLTVr+cBrpw= +cloud.google.com/go/monitoring v1.24.2 h1:5OTsoJ1dXYIiMiuL+sYscLc9BumrL3CarVLL7dd7lHM= +cloud.google.com/go/monitoring v1.24.2/go.mod h1:x7yzPWcgDRnPEv3sI+jJGBkwl5qINf+6qY4eq0I9B4U= +cloud.google.com/go/networkconnectivity v1.4.0/go.mod h1:nOl7YL8odKyAOtzNX73/M5/mGZgqqMeryi6UPZTk/rA= +cloud.google.com/go/networkconnectivity v1.5.0/go.mod h1:3GzqJx7uhtlM3kln0+x5wyFvuVH1pIBJjhCpjzSt75o= +cloud.google.com/go/networkconnectivity v1.6.0/go.mod h1:OJOoEXW+0LAxHh89nXd64uGG+FbQoeH8DtxCHVOMlaM= +cloud.google.com/go/networkconnectivity v1.7.0/go.mod h1:RMuSbkdbPwNMQjB5HBWD5MpTBnNm39iAVpC3TmsExt8= +cloud.google.com/go/networkconnectivity v1.10.0/go.mod h1:UP4O4sWXJG13AqrTdQCD9TnLGEbtNRqjuaaA7bNjF5E= +cloud.google.com/go/networkconnectivity v1.11.0/go.mod h1:iWmDD4QF16VCDLXUqvyspJjIEtBR/4zq5hwnY2X3scM= +cloud.google.com/go/networkmanagement v1.4.0/go.mod h1:Q9mdLLRn60AsOrPc8rs8iNV6OHXaGcDdsIQe1ohekq8= +cloud.google.com/go/networkmanagement v1.5.0/go.mod h1:ZnOeZ/evzUdUsnvRt792H0uYEnHQEMaz+REhhzJRcf4= +cloud.google.com/go/networkmanagement v1.6.0/go.mod h1:5pKPqyXjB/sgtvB5xqOemumoQNB7y95Q7S+4rjSOPYY= +cloud.google.com/go/networksecurity v0.5.0/go.mod h1:xS6fOCoqpVC5zx15Z/MqkfDwH4+m/61A3ODiDV1xmiQ= +cloud.google.com/go/networksecurity v0.6.0/go.mod h1:Q5fjhTr9WMI5mbpRYEbiexTzROf7ZbDzvzCrNl14nyU= +cloud.google.com/go/networksecurity v0.7.0/go.mod h1:mAnzoxx/8TBSyXEeESMy9OOYwo1v+gZ5eMRnsT5bC8k= +cloud.google.com/go/networksecurity v0.8.0/go.mod h1:B78DkqsxFG5zRSVuwYFRZ9Xz8IcQ5iECsNrPn74hKHU= +cloud.google.com/go/notebooks v1.2.0/go.mod h1:9+wtppMfVPUeJ8fIWPOq1UnATHISkGXGqTkxeieQ6UY= +cloud.google.com/go/notebooks v1.3.0/go.mod h1:bFR5lj07DtCPC7YAAJ//vHskFBxA5JzYlH68kXVdk34= +cloud.google.com/go/notebooks v1.4.0/go.mod h1:4QPMngcwmgb6uw7Po99B2xv5ufVoIQ7nOGDyL4P8AgA= +cloud.google.com/go/notebooks v1.5.0/go.mod h1:q8mwhnP9aR8Hpfnrc5iN5IBhrXUy8S2vuYs+kBJ/gu0= +cloud.google.com/go/notebooks v1.7.0/go.mod h1:PVlaDGfJgj1fl1S3dUwhFMXFgfYGhYQt2164xOMONmE= +cloud.google.com/go/notebooks v1.8.0/go.mod h1:Lq6dYKOYOWUCTvw5t2q1gp1lAp0zxAxRycayS0iJcqQ= +cloud.google.com/go/optimization v1.1.0/go.mod h1:5po+wfvX5AQlPznyVEZjGJTMr4+CAkJf2XSTQOOl9l4= +cloud.google.com/go/optimization v1.2.0/go.mod h1:Lr7SOHdRDENsh+WXVmQhQTrzdu9ybg0NecjHidBq6xs= +cloud.google.com/go/optimization v1.3.1/go.mod h1:IvUSefKiwd1a5p0RgHDbWCIbDFgKuEdB+fPPuP0IDLI= +cloud.google.com/go/orchestration v1.3.0/go.mod h1:Sj5tq/JpWiB//X/q3Ngwdl5K7B7Y0KZ7bfv0wL6fqVA= +cloud.google.com/go/orchestration v1.4.0/go.mod h1:6W5NLFWs2TlniBphAViZEVhrXRSMgUGDfW7vrWKvsBk= +cloud.google.com/go/orchestration v1.6.0/go.mod h1:M62Bevp7pkxStDfFfTuCOaXgaaqRAga1yKyoMtEoWPQ= +cloud.google.com/go/orgpolicy v1.4.0/go.mod h1:xrSLIV4RePWmP9P3tBl8S93lTmlAxjm06NSm2UTmKvE= +cloud.google.com/go/orgpolicy v1.5.0/go.mod h1:hZEc5q3wzwXJaKrsx5+Ewg0u1LxJ51nNFlext7Tanwc= +cloud.google.com/go/orgpolicy v1.10.0/go.mod h1:w1fo8b7rRqlXlIJbVhOMPrwVljyuW5mqssvBtU18ONc= +cloud.google.com/go/osconfig v1.7.0/go.mod h1:oVHeCeZELfJP7XLxcBGTMBvRO+1nQ5tFG9VQTmYS2Fs= +cloud.google.com/go/osconfig v1.8.0/go.mod h1:EQqZLu5w5XA7eKizepumcvWx+m8mJUhEwiPqWiZeEdg= +cloud.google.com/go/osconfig v1.9.0/go.mod h1:Yx+IeIZJ3bdWmzbQU4fxNl8xsZ4amB+dygAwFPlvnNo= +cloud.google.com/go/osconfig v1.10.0/go.mod h1:uMhCzqC5I8zfD9zDEAfvgVhDS8oIjySWh+l4WK6GnWw= +cloud.google.com/go/osconfig v1.11.0/go.mod h1:aDICxrur2ogRd9zY5ytBLV89KEgT2MKB2L/n6x1ooPw= +cloud.google.com/go/oslogin v1.4.0/go.mod h1:YdgMXWRaElXz/lDk1Na6Fh5orF7gvmJ0FGLIs9LId4E= +cloud.google.com/go/oslogin v1.5.0/go.mod h1:D260Qj11W2qx/HVF29zBg+0fd6YCSjSqLUkY/qEenQU= +cloud.google.com/go/oslogin v1.6.0/go.mod h1:zOJ1O3+dTU8WPlGEkFSh7qeHPPSoxrcMbbK1Nm2iX70= +cloud.google.com/go/oslogin v1.7.0/go.mod h1:e04SN0xO1UNJ1M5GP0vzVBFicIe4O53FOfcixIqTyXo= +cloud.google.com/go/oslogin v1.9.0/go.mod h1:HNavntnH8nzrn8JCTT5fj18FuJLFJc4NaZJtBnQtKFs= +cloud.google.com/go/phishingprotection v0.5.0/go.mod h1:Y3HZknsK9bc9dMi+oE8Bim0lczMU6hrX0UpADuMefr0= +cloud.google.com/go/phishingprotection v0.6.0/go.mod h1:9Y3LBLgy0kDTcYET8ZH3bq/7qni15yVUoAxiFxnlSUA= +cloud.google.com/go/phishingprotection v0.7.0/go.mod h1:8qJI4QKHoda/sb/7/YmMQ2omRLSLYSu9bU0EKCNI+Lk= +cloud.google.com/go/policytroubleshooter v1.3.0/go.mod h1:qy0+VwANja+kKrjlQuOzmlvscn4RNsAc0e15GGqfMxg= +cloud.google.com/go/policytroubleshooter v1.4.0/go.mod h1:DZT4BcRw3QoO8ota9xw/LKtPa8lKeCByYeKTIf/vxdE= +cloud.google.com/go/policytroubleshooter v1.5.0/go.mod h1:Rz1WfV+1oIpPdN2VvvuboLVRsB1Hclg3CKQ53j9l8vw= +cloud.google.com/go/policytroubleshooter v1.6.0/go.mod h1:zYqaPTsmfvpjm5ULxAyD/lINQxJ0DDsnWOP/GZ7xzBc= +cloud.google.com/go/privatecatalog v0.5.0/go.mod h1:XgosMUvvPyxDjAVNDYxJ7wBW8//hLDDYmnsNcMGq1K0= +cloud.google.com/go/privatecatalog v0.6.0/go.mod h1:i/fbkZR0hLN29eEWiiwue8Pb+GforiEIBnV9yrRUOKI= +cloud.google.com/go/privatecatalog v0.7.0/go.mod h1:2s5ssIFO69F5csTXcwBP7NPFTZvps26xGzvQ2PQaBYg= +cloud.google.com/go/privatecatalog v0.8.0/go.mod h1:nQ6pfaegeDAq/Q5lrfCQzQLhubPiZhSaNhIgfJlnIXs= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/pubsub v1.26.0/go.mod h1:QgBH3U/jdJy/ftjPhTkyXNj543Tin1pRYcdcPRnFIRI= +cloud.google.com/go/pubsub v1.27.1/go.mod h1:hQN39ymbV9geqBnfQq6Xf63yNhUAhv9CZhzp5O6qsW0= +cloud.google.com/go/pubsub v1.28.0/go.mod h1:vuXFpwaVoIPQMGXqRyUQigu/AX1S3IWugR9xznmcXX8= +cloud.google.com/go/pubsub v1.30.0/go.mod h1:qWi1OPS0B+b5L+Sg6Gmc9zD1Y+HaM0MdUr7LsupY1P4= +cloud.google.com/go/pubsublite v1.5.0/go.mod h1:xapqNQ1CuLfGi23Yda/9l4bBCKz/wC3KIJ5gKcxveZg= +cloud.google.com/go/pubsublite v1.6.0/go.mod h1:1eFCS0U11xlOuMFV/0iBqw3zP12kddMeCbj/F3FSj9k= +cloud.google.com/go/pubsublite v1.7.0/go.mod h1:8hVMwRXfDfvGm3fahVbtDbiLePT3gpoiJYJY+vxWxVM= +cloud.google.com/go/recaptchaenterprise v1.3.1/go.mod h1:OdD+q+y4XGeAlxRaMn1Y7/GveP6zmq76byL6tjPE7d4= +cloud.google.com/go/recaptchaenterprise/v2 v2.1.0/go.mod h1:w9yVqajwroDNTfGuhmOjPDN//rZGySaf6PtFVcSCa7o= +cloud.google.com/go/recaptchaenterprise/v2 v2.2.0/go.mod h1:/Zu5jisWGeERrd5HnlS3EUGb/D335f9k51B/FVil0jk= +cloud.google.com/go/recaptchaenterprise/v2 v2.3.0/go.mod h1:O9LwGCjrhGHBQET5CA7dd5NwwNQUErSgEDit1DLNTdo= +cloud.google.com/go/recaptchaenterprise/v2 v2.4.0/go.mod h1:Am3LHfOuBstrLrNCBrlI5sbwx9LBg3te2N6hGvHn2mE= +cloud.google.com/go/recaptchaenterprise/v2 v2.5.0/go.mod h1:O8LzcHXN3rz0j+LBC91jrwI3R+1ZSZEWrfL7XHgNo9U= +cloud.google.com/go/recaptchaenterprise/v2 v2.6.0/go.mod h1:RPauz9jeLtB3JVzg6nCbe12qNoaa8pXc4d/YukAmcnA= +cloud.google.com/go/recaptchaenterprise/v2 v2.7.0/go.mod h1:19wVj/fs5RtYtynAPJdDTb69oW0vNHYDBTbB4NvMD9c= +cloud.google.com/go/recommendationengine v0.5.0/go.mod h1:E5756pJcVFeVgaQv3WNpImkFP8a+RptV6dDLGPILjvg= +cloud.google.com/go/recommendationengine v0.6.0/go.mod h1:08mq2umu9oIqc7tDy8sx+MNJdLG0fUi3vaSVbztHgJ4= +cloud.google.com/go/recommendationengine v0.7.0/go.mod h1:1reUcE3GIu6MeBz/h5xZJqNLuuVjNg1lmWMPyjatzac= +cloud.google.com/go/recommender v1.5.0/go.mod h1:jdoeiBIVrJe9gQjwd759ecLJbxCDED4A6p+mqoqDvTg= +cloud.google.com/go/recommender v1.6.0/go.mod h1:+yETpm25mcoiECKh9DEScGzIRyDKpZ0cEhWGo+8bo+c= +cloud.google.com/go/recommender v1.7.0/go.mod h1:XLHs/W+T8olwlGOgfQenXBTbIseGclClff6lhFVe9Bs= +cloud.google.com/go/recommender v1.8.0/go.mod h1:PkjXrTT05BFKwxaUxQmtIlrtj0kph108r02ZZQ5FE70= +cloud.google.com/go/recommender v1.9.0/go.mod h1:PnSsnZY7q+VL1uax2JWkt/UegHssxjUVVCrX52CuEmQ= +cloud.google.com/go/redis v1.7.0/go.mod h1:V3x5Jq1jzUcg+UNsRvdmsfuFnit1cfe3Z/PGyq/lm4Y= +cloud.google.com/go/redis v1.8.0/go.mod h1:Fm2szCDavWzBk2cDKxrkmWBqoCiL1+Ctwq7EyqBCA/A= +cloud.google.com/go/redis v1.9.0/go.mod h1:HMYQuajvb2D0LvMgZmLDZW8V5aOC/WxstZHiy4g8OiA= +cloud.google.com/go/redis v1.10.0/go.mod h1:ThJf3mMBQtW18JzGgh41/Wld6vnDDc/F/F35UolRZPM= +cloud.google.com/go/redis v1.11.0/go.mod h1:/X6eicana+BWcUda5PpwZC48o37SiFVTFSs0fWAJ7uQ= +cloud.google.com/go/resourcemanager v1.3.0/go.mod h1:bAtrTjZQFJkiWTPDb1WBjzvc6/kifjj4QBYuKCCoqKA= +cloud.google.com/go/resourcemanager v1.4.0/go.mod h1:MwxuzkumyTX7/a3n37gmsT3py7LIXwrShilPh3P1tR0= +cloud.google.com/go/resourcemanager v1.5.0/go.mod h1:eQoXNAiAvCf5PXxWxXjhKQoTMaUSNrEfg+6qdf/wots= +cloud.google.com/go/resourcemanager v1.6.0/go.mod h1:YcpXGRs8fDzcUl1Xw8uOVmI8JEadvhRIkoXXUNVYcVo= +cloud.google.com/go/resourcemanager v1.7.0/go.mod h1:HlD3m6+bwhzj9XCouqmeiGuni95NTrExfhoSrkC/3EI= +cloud.google.com/go/resourcesettings v1.3.0/go.mod h1:lzew8VfESA5DQ8gdlHwMrqZs1S9V87v3oCnKCWoOuQU= +cloud.google.com/go/resourcesettings v1.4.0/go.mod h1:ldiH9IJpcrlC3VSuCGvjR5of/ezRrOxFtpJoJo5SmXg= +cloud.google.com/go/resourcesettings v1.5.0/go.mod h1:+xJF7QSG6undsQDfsCJyqWXyBwUoJLhetkRMDRnIoXA= +cloud.google.com/go/retail v1.8.0/go.mod h1:QblKS8waDmNUhghY2TI9O3JLlFk8jybHeV4BF19FrE4= +cloud.google.com/go/retail v1.9.0/go.mod h1:g6jb6mKuCS1QKnH/dpu7isX253absFl6iE92nHwlBUY= +cloud.google.com/go/retail v1.10.0/go.mod h1:2gDk9HsL4HMS4oZwz6daui2/jmKvqShXKQuB2RZ+cCc= +cloud.google.com/go/retail v1.11.0/go.mod h1:MBLk1NaWPmh6iVFSz9MeKG/Psyd7TAgm6y/9L2B4x9Y= +cloud.google.com/go/retail v1.12.0/go.mod h1:UMkelN/0Z8XvKymXFbD4EhFJlYKRx1FGhQkVPU5kF14= +cloud.google.com/go/run v0.2.0/go.mod h1:CNtKsTA1sDcnqqIFR3Pb5Tq0usWxJJvsWOCPldRU3Do= +cloud.google.com/go/run v0.3.0/go.mod h1:TuyY1+taHxTjrD0ZFk2iAR+xyOXEA0ztb7U3UNA0zBo= +cloud.google.com/go/run v0.8.0/go.mod h1:VniEnuBwqjigv0A7ONfQUaEItaiCRVujlMqerPPiktM= +cloud.google.com/go/run v0.9.0/go.mod h1:Wwu+/vvg8Y+JUApMwEDfVfhetv30hCG4ZwDR/IXl2Qg= +cloud.google.com/go/scheduler v1.4.0/go.mod h1:drcJBmxF3aqZJRhmkHQ9b3uSSpQoltBPGPxGAWROx6s= +cloud.google.com/go/scheduler v1.5.0/go.mod h1:ri073ym49NW3AfT6DZi21vLZrG07GXr5p3H1KxN5QlI= +cloud.google.com/go/scheduler v1.6.0/go.mod h1:SgeKVM7MIwPn3BqtcBntpLyrIJftQISRrYB5ZtT+KOk= +cloud.google.com/go/scheduler v1.7.0/go.mod h1:jyCiBqWW956uBjjPMMuX09n3x37mtyPJegEWKxRsn44= +cloud.google.com/go/scheduler v1.8.0/go.mod h1:TCET+Y5Gp1YgHT8py4nlg2Sew8nUHMqcpousDgXJVQc= +cloud.google.com/go/scheduler v1.9.0/go.mod h1:yexg5t+KSmqu+njTIh3b7oYPheFtBWGcbVUYF1GGMIc= +cloud.google.com/go/secretmanager v1.6.0/go.mod h1:awVa/OXF6IiyaU1wQ34inzQNc4ISIDIrId8qE5QGgKA= +cloud.google.com/go/secretmanager v1.8.0/go.mod h1:hnVgi/bN5MYHd3Gt0SPuTPPp5ENina1/LxM+2W9U9J4= +cloud.google.com/go/secretmanager v1.9.0/go.mod h1:b71qH2l1yHmWQHt9LC80akm86mX8AL6X1MA01dW8ht4= +cloud.google.com/go/secretmanager v1.10.0/go.mod h1:MfnrdvKMPNra9aZtQFvBcvRU54hbPD8/HayQdlUgJpU= +cloud.google.com/go/security v1.5.0/go.mod h1:lgxGdyOKKjHL4YG3/YwIL2zLqMFCKs0UbQwgyZmfJl4= +cloud.google.com/go/security v1.7.0/go.mod h1:mZklORHl6Bg7CNnnjLH//0UlAlaXqiG7Lb9PsPXLfD0= +cloud.google.com/go/security v1.8.0/go.mod h1:hAQOwgmaHhztFhiQ41CjDODdWP0+AE1B3sX4OFlq+GU= +cloud.google.com/go/security v1.9.0/go.mod h1:6Ta1bO8LXI89nZnmnsZGp9lVoVWXqsVbIq/t9dzI+2Q= +cloud.google.com/go/security v1.10.0/go.mod h1:QtOMZByJVlibUT2h9afNDWRZ1G96gVywH8T5GUSb9IA= +cloud.google.com/go/security v1.12.0/go.mod h1:rV6EhrpbNHrrxqlvW0BWAIawFWq3X90SduMJdFwtLB8= +cloud.google.com/go/security v1.13.0/go.mod h1:Q1Nvxl1PAgmeW0y3HTt54JYIvUdtcpYKVfIB8AOMZ+0= +cloud.google.com/go/securitycenter v1.13.0/go.mod h1:cv5qNAqjY84FCN6Y9z28WlkKXyWsgLO832YiWwkCWcU= +cloud.google.com/go/securitycenter v1.14.0/go.mod h1:gZLAhtyKv85n52XYWt6RmeBdydyxfPeTrpToDPw4Auc= +cloud.google.com/go/securitycenter v1.15.0/go.mod h1:PeKJ0t8MoFmmXLXWm41JidyzI3PJjd8sXWaVqg43WWk= +cloud.google.com/go/securitycenter v1.16.0/go.mod h1:Q9GMaLQFUD+5ZTabrbujNWLtSLZIZF7SAR0wWECrjdk= +cloud.google.com/go/securitycenter v1.18.1/go.mod h1:0/25gAzCM/9OL9vVx4ChPeM/+DlfGQJDwBy/UC8AKK0= +cloud.google.com/go/securitycenter v1.19.0/go.mod h1:LVLmSg8ZkkyaNy4u7HCIshAngSQ8EcIRREP3xBnyfag= +cloud.google.com/go/servicecontrol v1.4.0/go.mod h1:o0hUSJ1TXJAmi/7fLJAedOovnujSEvjKCAFNXPQ1RaU= +cloud.google.com/go/servicecontrol v1.5.0/go.mod h1:qM0CnXHhyqKVuiZnGKrIurvVImCs8gmqWsDoqe9sU1s= +cloud.google.com/go/servicecontrol v1.10.0/go.mod h1:pQvyvSRh7YzUF2efw7H87V92mxU8FnFDawMClGCNuAA= +cloud.google.com/go/servicecontrol v1.11.0/go.mod h1:kFmTzYzTUIuZs0ycVqRHNaNhgR+UMUpw9n02l/pY+mc= +cloud.google.com/go/servicecontrol v1.11.1/go.mod h1:aSnNNlwEFBY+PWGQ2DoM0JJ/QUXqV5/ZD9DOLB7SnUk= +cloud.google.com/go/servicedirectory v1.4.0/go.mod h1:gH1MUaZCgtP7qQiI+F+A+OpeKF/HQWgtAddhTbhL2bs= +cloud.google.com/go/servicedirectory v1.5.0/go.mod h1:QMKFL0NUySbpZJ1UZs3oFAmdvVxhhxB6eJ/Vlp73dfg= +cloud.google.com/go/servicedirectory v1.6.0/go.mod h1:pUlbnWsLH9c13yGkxCmfumWEPjsRs1RlmJ4pqiNjVL4= +cloud.google.com/go/servicedirectory v1.7.0/go.mod h1:5p/U5oyvgYGYejufvxhgwjL8UVXjkuw7q5XcG10wx1U= +cloud.google.com/go/servicedirectory v1.8.0/go.mod h1:srXodfhY1GFIPvltunswqXpVxFPpZjf8nkKQT7XcXaY= +cloud.google.com/go/servicedirectory v1.9.0/go.mod h1:29je5JjiygNYlmsGz8k6o+OZ8vd4f//bQLtvzkPPT/s= +cloud.google.com/go/servicemanagement v1.4.0/go.mod h1:d8t8MDbezI7Z2R1O/wu8oTggo3BI2GKYbdG4y/SJTco= +cloud.google.com/go/servicemanagement v1.5.0/go.mod h1:XGaCRe57kfqu4+lRxaFEAuqmjzF0r+gWHjWqKqBvKFo= +cloud.google.com/go/servicemanagement v1.6.0/go.mod h1:aWns7EeeCOtGEX4OvZUWCCJONRZeFKiptqKf1D0l/Jc= +cloud.google.com/go/servicemanagement v1.8.0/go.mod h1:MSS2TDlIEQD/fzsSGfCdJItQveu9NXnUniTrq/L8LK4= +cloud.google.com/go/serviceusage v1.3.0/go.mod h1:Hya1cozXM4SeSKTAgGXgj97GlqUvF5JaoXacR1JTP/E= +cloud.google.com/go/serviceusage v1.4.0/go.mod h1:SB4yxXSaYVuUBYUml6qklyONXNLt83U0Rb+CXyhjEeU= +cloud.google.com/go/serviceusage v1.5.0/go.mod h1:w8U1JvqUqwJNPEOTQjrMHkw3IaIFLoLsPLvsE3xueec= +cloud.google.com/go/serviceusage v1.6.0/go.mod h1:R5wwQcbOWsyuOfbP9tGdAnCAc6B9DRwPG1xtWMDeuPA= +cloud.google.com/go/shell v1.3.0/go.mod h1:VZ9HmRjZBsjLGXusm7K5Q5lzzByZmJHf1d0IWHEN5X4= +cloud.google.com/go/shell v1.4.0/go.mod h1:HDxPzZf3GkDdhExzD/gs8Grqk+dmYcEjGShZgYa9URw= +cloud.google.com/go/shell v1.6.0/go.mod h1:oHO8QACS90luWgxP3N9iZVuEiSF84zNyLytb+qE2f9A= +cloud.google.com/go/spanner v1.41.0/go.mod h1:MLYDBJR/dY4Wt7ZaMIQ7rXOTLjYrmxLE/5ve9vFfWos= +cloud.google.com/go/spanner v1.44.0/go.mod h1:G8XIgYdOK+Fbcpbs7p2fiprDw4CaZX63whnSMLVBxjk= +cloud.google.com/go/spanner v1.45.0/go.mod h1:FIws5LowYz8YAE1J8fOS7DJup8ff7xJeetWEo5REA2M= +cloud.google.com/go/spanner v1.84.1 h1:ShH4Y3YeDtmHa55dFiSS3YtQ0dmCuP0okfAoHp/d68w= +cloud.google.com/go/spanner v1.84.1/go.mod h1:3GMEIjOcXINJSvb42H3M6TdlGCDzaCFpiiNQpjHPlCM= +cloud.google.com/go/speech v1.6.0/go.mod h1:79tcr4FHCimOp56lwC01xnt/WPJZc4v3gzyT7FoBkCM= +cloud.google.com/go/speech v1.7.0/go.mod h1:KptqL+BAQIhMsj1kOP2la5DSEEerPDuOP/2mmkhHhZQ= +cloud.google.com/go/speech v1.8.0/go.mod h1:9bYIl1/tjsAnMgKGHKmBZzXKEkGgtU+MpdDPTE9f7y0= +cloud.google.com/go/speech v1.9.0/go.mod h1:xQ0jTcmnRFFM2RfX/U+rk6FQNUF6DQlydUSyoooSpco= +cloud.google.com/go/speech v1.14.1/go.mod h1:gEosVRPJ9waG7zqqnsHpYTOoAS4KouMRLDFMekpJ0J0= +cloud.google.com/go/speech v1.15.0/go.mod h1:y6oH7GhqCaZANH7+Oe0BhgIogsNInLlz542tg3VqeYI= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= +cloud.google.com/go/storage v1.22.1/go.mod h1:S8N1cAStu7BOeFfE8KAQzmyyLkK8p/vmRq6kuBTW58Y= +cloud.google.com/go/storage v1.23.0/go.mod h1:vOEEDNFnciUMhBeT6hsJIn3ieU5cFRmzeLgDvXzfIXc= +cloud.google.com/go/storage v1.27.0/go.mod h1:x9DOL8TK/ygDUMieqwfhdpQryTeEkhGKMi80i/iqR2s= +cloud.google.com/go/storage v1.28.1/go.mod h1:Qnisd4CqDdo6BGs2AD5LLnEsmSQ80wQ5ogcBBKhU86Y= +cloud.google.com/go/storage v1.29.0/go.mod h1:4puEjyTKnku6gfKoTfNOU/W+a9JyuVNxjpS5GBrB8h4= +cloud.google.com/go/storagetransfer v1.5.0/go.mod h1:dxNzUopWy7RQevYFHewchb29POFv3/AaBgnhqzqiK0w= +cloud.google.com/go/storagetransfer v1.6.0/go.mod h1:y77xm4CQV/ZhFZH75PLEXY0ROiS7Gh6pSKrM8dJyg6I= +cloud.google.com/go/storagetransfer v1.7.0/go.mod h1:8Giuj1QNb1kfLAiWM1bN6dHzfdlDAVC9rv9abHot2W4= +cloud.google.com/go/storagetransfer v1.8.0/go.mod h1:JpegsHHU1eXg7lMHkvf+KE5XDJ7EQu0GwNJbbVGanEw= +cloud.google.com/go/talent v1.1.0/go.mod h1:Vl4pt9jiHKvOgF9KoZo6Kob9oV4lwd/ZD5Cto54zDRw= +cloud.google.com/go/talent v1.2.0/go.mod h1:MoNF9bhFQbiJ6eFD3uSsg0uBALw4n4gaCaEjBw9zo8g= +cloud.google.com/go/talent v1.3.0/go.mod h1:CmcxwJ/PKfRgd1pBjQgU6W3YBwiewmUzQYH5HHmSCmM= +cloud.google.com/go/talent v1.4.0/go.mod h1:ezFtAgVuRf8jRsvyE6EwmbTK5LKciD4KVnHuDEFmOOA= +cloud.google.com/go/talent v1.5.0/go.mod h1:G+ODMj9bsasAEJkQSzO2uHQWXHHXUomArjWQQYkqK6c= +cloud.google.com/go/texttospeech v1.4.0/go.mod h1:FX8HQHA6sEpJ7rCMSfXuzBcysDAuWusNNNvN9FELDd8= +cloud.google.com/go/texttospeech v1.5.0/go.mod h1:oKPLhR4n4ZdQqWKURdwxMy0uiTS1xU161C8W57Wkea4= +cloud.google.com/go/texttospeech v1.6.0/go.mod h1:YmwmFT8pj1aBblQOI3TfKmwibnsfvhIBzPXcW4EBovc= +cloud.google.com/go/tpu v1.3.0/go.mod h1:aJIManG0o20tfDQlRIej44FcwGGl/cD0oiRyMKG19IQ= +cloud.google.com/go/tpu v1.4.0/go.mod h1:mjZaX8p0VBgllCzF6wcU2ovUXN9TONFLd7iz227X2Xg= +cloud.google.com/go/tpu v1.5.0/go.mod h1:8zVo1rYDFuW2l4yZVY0R0fb/v44xLh3llq7RuV61fPM= +cloud.google.com/go/trace v1.3.0/go.mod h1:FFUE83d9Ca57C+K8rDl/Ih8LwOzWIV1krKgxg6N0G28= +cloud.google.com/go/trace v1.4.0/go.mod h1:UG0v8UBqzusp+z63o7FK74SdFE+AXpCLdFb1rshXG+Y= +cloud.google.com/go/trace v1.8.0/go.mod h1:zH7vcsbAhklH8hWFig58HvxcxyQbaIqMarMg9hn5ECA= +cloud.google.com/go/trace v1.9.0/go.mod h1:lOQqpE5IaWY0Ixg7/r2SjixMuc6lfTFeO4QGM4dQWOk= +cloud.google.com/go/translate v1.3.0/go.mod h1:gzMUwRjvOqj5i69y/LYLd8RrNQk+hOmIXTi9+nb3Djs= +cloud.google.com/go/translate v1.4.0/go.mod h1:06Dn/ppvLD6WvA5Rhdp029IX2Mi3Mn7fpMRLPvXT5Wg= +cloud.google.com/go/translate v1.5.0/go.mod h1:29YDSYveqqpA1CQFD7NQuP49xymq17RXNaUDdc0mNu0= +cloud.google.com/go/translate v1.6.0/go.mod h1:lMGRudH1pu7I3n3PETiOB2507gf3HnfLV8qlkHZEyos= +cloud.google.com/go/translate v1.7.0/go.mod h1:lMGRudH1pu7I3n3PETiOB2507gf3HnfLV8qlkHZEyos= +cloud.google.com/go/video v1.8.0/go.mod h1:sTzKFc0bUSByE8Yoh8X0mn8bMymItVGPfTuUBUyRgxk= +cloud.google.com/go/video v1.9.0/go.mod h1:0RhNKFRF5v92f8dQt0yhaHrEuH95m068JYOvLZYnJSw= +cloud.google.com/go/video v1.12.0/go.mod h1:MLQew95eTuaNDEGriQdcYn0dTwf9oWiA4uYebxM5kdg= +cloud.google.com/go/video v1.13.0/go.mod h1:ulzkYlYgCp15N2AokzKjy7MQ9ejuynOJdf1tR5lGthk= +cloud.google.com/go/video v1.14.0/go.mod h1:SkgaXwT+lIIAKqWAJfktHT/RbgjSuY6DobxEp0C5yTQ= +cloud.google.com/go/video v1.15.0/go.mod h1:SkgaXwT+lIIAKqWAJfktHT/RbgjSuY6DobxEp0C5yTQ= +cloud.google.com/go/videointelligence v1.6.0/go.mod h1:w0DIDlVRKtwPCn/C4iwZIJdvC69yInhW0cfi+p546uU= +cloud.google.com/go/videointelligence v1.7.0/go.mod h1:k8pI/1wAhjznARtVT9U1llUaFNPh7muw8QyOUpavru4= +cloud.google.com/go/videointelligence v1.8.0/go.mod h1:dIcCn4gVDdS7yte/w+koiXn5dWVplOZkE+xwG9FgK+M= +cloud.google.com/go/videointelligence v1.9.0/go.mod h1:29lVRMPDYHikk3v8EdPSaL8Ku+eMzDljjuvRs105XoU= +cloud.google.com/go/videointelligence v1.10.0/go.mod h1:LHZngX1liVtUhZvi2uNS0VQuOzNi2TkY1OakiuoUOjU= +cloud.google.com/go/vision v1.2.0/go.mod h1:SmNwgObm5DpFBme2xpyOyasvBc1aPdjvMk2bBk0tKD0= +cloud.google.com/go/vision/v2 v2.2.0/go.mod h1:uCdV4PpN1S0jyCyq8sIM42v2Y6zOLkZs+4R9LrGYwFo= +cloud.google.com/go/vision/v2 v2.3.0/go.mod h1:UO61abBx9QRMFkNBbf1D8B1LXdS2cGiiCRx0vSpZoUo= +cloud.google.com/go/vision/v2 v2.4.0/go.mod h1:VtI579ll9RpVTrdKdkMzckdnwMyX2JILb+MhPqRbPsY= +cloud.google.com/go/vision/v2 v2.5.0/go.mod h1:MmaezXOOE+IWa+cS7OhRRLK2cNv1ZL98zhqFFZaaH2E= +cloud.google.com/go/vision/v2 v2.6.0/go.mod h1:158Hes0MvOS9Z/bDMSFpjwsUrZ5fPrdwuyyvKSGAGMY= +cloud.google.com/go/vision/v2 v2.7.0/go.mod h1:H89VysHy21avemp6xcf9b9JvZHVehWbET0uT/bcuY/0= +cloud.google.com/go/vmmigration v1.2.0/go.mod h1:IRf0o7myyWFSmVR1ItrBSFLFD/rJkfDCUTO4vLlJvsE= +cloud.google.com/go/vmmigration v1.3.0/go.mod h1:oGJ6ZgGPQOFdjHuocGcLqX4lc98YQ7Ygq8YQwHh9A7g= +cloud.google.com/go/vmmigration v1.5.0/go.mod h1:E4YQ8q7/4W9gobHjQg4JJSgXXSgY21nA5r8swQV+Xxc= +cloud.google.com/go/vmmigration v1.6.0/go.mod h1:bopQ/g4z+8qXzichC7GW1w2MjbErL54rk3/C843CjfY= +cloud.google.com/go/vmwareengine v0.1.0/go.mod h1:RsdNEf/8UDvKllXhMz5J40XxDrNJNN4sagiox+OI208= +cloud.google.com/go/vmwareengine v0.2.2/go.mod h1:sKdctNJxb3KLZkE/6Oui94iw/xs9PRNC2wnNLXsHvH8= +cloud.google.com/go/vmwareengine v0.3.0/go.mod h1:wvoyMvNWdIzxMYSpH/R7y2h5h3WFkx6d+1TIsP39WGY= +cloud.google.com/go/vpcaccess v1.4.0/go.mod h1:aQHVbTWDYUR1EbTApSVvMq1EnT57ppDmQzZ3imqIk4w= +cloud.google.com/go/vpcaccess v1.5.0/go.mod h1:drmg4HLk9NkZpGfCmZ3Tz0Bwnm2+DKqViEpeEpOq0m8= +cloud.google.com/go/vpcaccess v1.6.0/go.mod h1:wX2ILaNhe7TlVa4vC5xce1bCnqE3AeH27RV31lnmZes= +cloud.google.com/go/webrisk v1.4.0/go.mod h1:Hn8X6Zr+ziE2aNd8SliSDWpEnSS1u4R9+xXZmFiHmGE= +cloud.google.com/go/webrisk v1.5.0/go.mod h1:iPG6fr52Tv7sGk0H6qUFzmL3HHZev1htXuWDEEsqMTg= +cloud.google.com/go/webrisk v1.6.0/go.mod h1:65sW9V9rOosnc9ZY7A7jsy1zoHS5W9IAXv6dGqhMQMc= +cloud.google.com/go/webrisk v1.7.0/go.mod h1:mVMHgEYH0r337nmt1JyLthzMr6YxwN1aAIEc2fTcq7A= +cloud.google.com/go/webrisk v1.8.0/go.mod h1:oJPDuamzHXgUc+b8SiHRcVInZQuybnvEW72PqTc7sSg= +cloud.google.com/go/websecurityscanner v1.3.0/go.mod h1:uImdKm2wyeXQevQJXeh8Uun/Ym1VqworNDlBXQevGMo= +cloud.google.com/go/websecurityscanner v1.4.0/go.mod h1:ebit/Fp0a+FWu5j4JOmJEV8S8CzdTkAS77oDsiSqYWQ= +cloud.google.com/go/websecurityscanner v1.5.0/go.mod h1:Y6xdCPy81yi0SQnDY1xdNTNpfY1oAgXUlcfN3B3eSng= +cloud.google.com/go/workflows v1.6.0/go.mod h1:6t9F5h/unJz41YqfBmqSASJSXccBLtD1Vwf+KmJENM0= +cloud.google.com/go/workflows v1.7.0/go.mod h1:JhSrZuVZWuiDfKEFxU0/F1PQjmpnpcoISEXH2bcHC3M= +cloud.google.com/go/workflows v1.8.0/go.mod h1:ysGhmEajwZxGn1OhGOGKsTXc5PyxOc0vfKf5Af+to4M= +cloud.google.com/go/workflows v1.9.0/go.mod h1:ZGkj1aFIOd9c8Gerkjjq7OW7I5+l6cSvT3ujaO/WwSA= +cloud.google.com/go/workflows v1.10.0/go.mod h1:fZ8LmRmZQWacon9UCX1r/g/DfAXx5VcPALq2CxzdePw= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +gioui.org v0.0.0-20210308172011-57750fc8a0a6/go.mod h1:RSH6KIUZ0p2xy5zHDxgAM4zumjgTw83q2ge/PI+yyw8= +git.sr.ht/~sbinet/gg v0.3.1/go.mod h1:KGYtlADtqsqANL9ueOFkWymvzUvLMQllU5Ixo+8v3pc= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.0.0/go.mod h1:uGG2W01BaETf0Ozp+QxxKJdMBNRWPdstHG0Fmdwn1/U= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.1.2/go.mod h1:uGG2W01BaETf0Ozp+QxxKJdMBNRWPdstHG0Fmdwn1/U= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.1 h1:lGlwhPtrX6EVml1hO0ivjkUxsSyl4dsiw9qcA1k/3IQ= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.1/go.mod h1:RKUqNu35KJYcVG/fqTRqmuXJZYNhYkBrnC/hX7yGbTA= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.1.0/go.mod h1:bhXu1AjYL+wutSL/kpSq6s7733q2Rb0yuot9Zgfqa/0= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.5.1 h1:sO0/P7g68FrryJzljemN+6GTssUXdANk6aJ7T1ZxnsQ= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.5.1/go.mod h1:h8hyGFDsU5HMivxiS2iYFZsgDbU9OnnJ163x5UGVKYo= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.0.0/go.mod h1:eWRD7oawr1Mu1sLCawqVc0CUiF43ia3qQMxLscsKQ9w= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.1 h1:6oNBlSdi1QqM1PNW7FPA6xOGA5UNsXnkaYZz9vdPGhA= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.1/go.mod h1:s4kgfzA0covAXNicZHDMN58jExvcng2mC/DepXiF1EI= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.0.1 h1:MyVTgWR8qd/Jw1Le0NZebGBUCLbtak3bJ3z1OlqZBpw= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.0.1/go.mod h1:GpPjLhVR9dnUoJMyHWSPy71xY9/lcmpzIPZXmF0FCVY= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.0.0 h1:D3occbWoio4EBLkbkevetNMAVX197GkzbUMtqjGWn80= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.0.0/go.mod h1:bTSOgj05NGRuHHhQwAdPnYr9TOdNmKlZTgGLL6nyAdI= +github.com/AzureAD/microsoft-authentication-library-for-go v0.5.1/go.mod h1:Vt9sXTKwMyGcOxSmLDMnGPgqsUg7m8pe215qMLrDXw4= +github.com/AzureAD/microsoft-authentication-library-for-go v1.2.1 h1:DzHpqpoJVaCgOUdVHxE8QB52S6NiVdDQvGlny1qvPqA= +github.com/AzureAD/microsoft-authentication-library-for-go v1.2.1/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v1.1.0 h1:ksErzDEI1khOiGPgpwuI7x2ebx/uXQNw7xJpn9Eq1+I= github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU= github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU= +github.com/GoogleCloudPlatform/grpc-gcp-go/grpcgcp v1.5.3 h1:2afWGsMzkIcN8Qm4mgPJKZWyroE5QBszMiDMYEBrnfw= +github.com/GoogleCloudPlatform/grpc-gcp-go/grpcgcp v1.5.3/go.mod h1:dppbR7CwXD4pgtV9t3wD1812RaLDcBjtblcDF5f1vI0= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.29.0 h1:UQUsRi8WTzhZntp5313l+CHIAT95ojUI2lpP/ExlZa4= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.29.0/go.mod h1:Cz6ft6Dkn3Et6l2v2a9/RpN7epQ1GtDlO6lj8bEcOvw= +github.com/JohnCGriffin/overflow v0.0.0-20211019200055-46fa312c352c/go.mod h1:X0CRv0ky0k6m906ixxpzmDRLvX58TFUKS2eePweuyxk= github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/ScaleFT/sshkeys v0.0.0-20200327173127-6142f742bca5 h1:VauE2GcJNZFun2Och6tIT2zJZK1v6jxALQDA9BIji/E= github.com/ScaleFT/sshkeys v0.0.0-20200327173127-6142f742bca5/go.mod h1:gxOHeajFfvGQh/fxlC8oOKBe23xnnJTif00IFFbiT+o= -github.com/agiledragon/gomonkey/v2 v2.3.1 h1:k+UnUY0EMNYUFUAQVETGY9uUTxjMdnUkP0ARyJS1zzs= -github.com/agiledragon/gomonkey/v2 v2.3.1/go.mod h1:ap1AmDzcVOAz1YpeJ3TCzIgstoaWLA6jbbgxfB4w2iY= +github.com/ajstarks/deck v0.0.0-20200831202436-30c9fc6549a9/go.mod h1:JynElWSGnm/4RlzPXRlREEwqTHAN3T56Bv2ITsFT3gY= +github.com/ajstarks/deck/generate v0.0.0-20210309230005-c3f852c02e19/go.mod h1:T13YZdzov6OU0A1+RfKZiZN9ca6VeKdBdyDV+BY97Tk= +github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= +github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b/go.mod h1:1KcenG0jGWcpt8ov532z81sp/kMMUG485J2InIOyADM= +github.com/alecthomas/kong v1.9.0 h1:Wgg0ll5Ys7xDnpgYBuBn/wPeLGAuK0NvYmEcisJgrIs= +github.com/alecthomas/kong v1.9.0/go.mod h1:p2vqieVMeTAnaC83txKtXe8FLke2X07aruPWXyMPQrU= github.com/alexedwards/argon2id v1.0.0 h1:wJzDx66hqWX7siL/SRUmgz3F8YMrd/nfX/xHHcQQP0w= github.com/alexedwards/argon2id v1.0.0/go.mod h1:tYKkqIjzXvZdzPvADMWOEZ+l6+BD6CtBXMj5fnJppiw= +github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/apache/arrow/go/v10 v10.0.1/go.mod h1:YvhnlEePVnBS4+0z3fhPfUy7W1Ikj0Ih0vcRo/gZ1M0= +github.com/apache/arrow/go/v11 v11.0.0/go.mod h1:Eg5OsL5H+e299f7u5ssuXsuHQVEGC4xei5aX110hRiI= +github.com/apache/thrift v0.16.0/go.mod h1:PHK3hniurgQaNMZYaCLEqXKsYK8upmhPbmdP2FXSqgU= github.com/aws/aws-sdk-go-v2 v1.39.6 h1:2JrPCVgWJm7bm83BDwY5z8ietmeJUbh3O2ACnn+Xsqk= github.com/aws/aws-sdk-go-v2 v1.39.6/go.mod h1:c9pm7VwuW0UPxAEYGyTmyurVcNrbF6Rt/wixFqDhcjE= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.3 h1:DHctwEM8P8iTXFxC/QK0MRjwEpWQeM9yzidCRjldUz0= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.3/go.mod h1:xdCzcZEtnSTKVDOmUZs4l/j3pSV6rpo1WXl5ugNsL8Y= github.com/aws/aws-sdk-go-v2/config v1.31.18 h1:RouG3AcF2fLFhw+Z0qbnuIl9HZ0Kh4E/U9sKwTMRpMI= github.com/aws/aws-sdk-go-v2/config v1.31.18/go.mod h1:aXZ13mSQC8S2VEHwGfL1COMuJ1Zty6pX5xU7hyqjvCg= -github.com/aws/aws-sdk-go-v2/config v1.31.19 h1:qdUtOw4JhZr2YcKO3g0ho/IcFXfXrrb8xlX05Y6EvSw= -github.com/aws/aws-sdk-go-v2/config v1.31.19/go.mod h1:tMJ8bur01t8eEm0atLadkIIFA154OJ4JCKZeQ+o+R7k= -github.com/aws/aws-sdk-go-v2/config v1.31.20 h1:/jWF4Wu90EhKCgjTdy1DGxcbcbNrjfBHvksEL79tfQc= -github.com/aws/aws-sdk-go-v2/config v1.31.20/go.mod h1:95Hh1Tc5VYKL9NJ7tAkDcqeKt+MCXQB1hQZaRdJIZE0= github.com/aws/aws-sdk-go-v2/credentials v1.18.22 h1:hyIVGBHhQPaNP9D4BaVRwpjLMCwMMdAkHqB3gGMiykU= github.com/aws/aws-sdk-go-v2/credentials v1.18.22/go.mod h1:B9E2qHs3/YGfeQZ4jrIE/nPvqxtyafZrJ5EQiZBG6pk= -github.com/aws/aws-sdk-go-v2/credentials v1.18.23 h1:IQILcxVgMO2BVLaJ2aAv21dKWvE1MduNrbvuK43XL2Q= -github.com/aws/aws-sdk-go-v2/credentials v1.18.23/go.mod h1:JRodHszhVdh5TPUknxDzJzrMiznG+M+FfR3WSWKgCI8= -github.com/aws/aws-sdk-go-v2/credentials v1.18.24 h1:iJ2FmPT35EaIB0+kMa6TnQ+PwG5A1prEdAw+PsMzfHg= -github.com/aws/aws-sdk-go-v2/credentials v1.18.24/go.mod h1:U91+DrfjAiXPDEGYhh/x29o4p0qHX5HDqG7y5VViv64= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.13 h1:T1brd5dR3/fzNFAQch/iBKeX07/ffu/cLu+q+RuzEWk= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.13/go.mod h1:Peg/GBAQ6JDt+RoBf4meB1wylmAipb7Kg2ZFakZTlwk= github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.13 h1:a+8/MLcWlIxo1lF9xaGt3J/u3yOZx+CdSveSNwjhD40= @@ -50,58 +695,104 @@ github.com/aws/aws-sdk-go-v2/service/route53 v1.59.4 h1:KEszjusgJ2dAqE5nSJY+5AHB github.com/aws/aws-sdk-go-v2/service/route53 v1.59.4/go.mod h1:TUbfYOisWZWyT2qjmlMh93ERw1Ry8G4q/yT2Q8TsDag= github.com/aws/aws-sdk-go-v2/service/s3 v1.90.0 h1:ef6gIJR+xv/JQWwpa5FYirzoQctfSJm7tuDe3SZsUf8= github.com/aws/aws-sdk-go-v2/service/s3 v1.90.0/go.mod h1:+wArOOrcHUevqdto9k1tKOF5++YTe9JEcPSc9Tx2ZSw= -github.com/aws/aws-sdk-go-v2/service/s3 v1.90.1 h1:kKJk9r6iLMfCGy8RL9GWg3n9gUE1IpSwqYP3/5bdL1s= -github.com/aws/aws-sdk-go-v2/service/s3 v1.90.1/go.mod h1:+wArOOrcHUevqdto9k1tKOF5++YTe9JEcPSc9Tx2ZSw= -github.com/aws/aws-sdk-go-v2/service/s3 v1.90.2 h1:DhdbtDl4FdNlj31+xiRXANxEE+eC7n8JQz+/ilwQ8Uc= -github.com/aws/aws-sdk-go-v2/service/s3 v1.90.2/go.mod h1:+wArOOrcHUevqdto9k1tKOF5++YTe9JEcPSc9Tx2ZSw= github.com/aws/aws-sdk-go-v2/service/sso v1.30.1 h1:0JPwLz1J+5lEOfy/g0SURC9cxhbQ1lIMHMa+AHZSzz0= github.com/aws/aws-sdk-go-v2/service/sso v1.30.1/go.mod h1:fKvyjJcz63iL/ftA6RaM8sRCtN4r4zl4tjL3qw5ec7k= -github.com/aws/aws-sdk-go-v2/service/sso v1.30.2 h1:/p6MxkbQoCzaGQT3WO0JwG0FlQyG9RD8VmdmoKc5xqU= -github.com/aws/aws-sdk-go-v2/service/sso v1.30.2/go.mod h1:fKvyjJcz63iL/ftA6RaM8sRCtN4r4zl4tjL3qw5ec7k= -github.com/aws/aws-sdk-go-v2/service/sso v1.30.3 h1:NjShtS1t8r5LUfFVtFeI8xLAHQNTa7UI0VawXlrBMFQ= -github.com/aws/aws-sdk-go-v2/service/sso v1.30.3/go.mod h1:fKvyjJcz63iL/ftA6RaM8sRCtN4r4zl4tjL3qw5ec7k= github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.5 h1:OWs0/j2UYR5LOGi88sD5/lhN6TDLG6SfA7CqsQO9zF0= github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.5/go.mod h1:klO+ejMvYsB4QATfEOIXk8WAEwN4N0aBfJpvC+5SZBo= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.6 h1:0dES42T2dhICCbVB3JSTTn7+Bz93wfJEK1b7jksZIyQ= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.6/go.mod h1:klO+ejMvYsB4QATfEOIXk8WAEwN4N0aBfJpvC+5SZBo= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.7 h1:gTsnx0xXNQ6SBbymoDvcoRHL+q4l/dAFsQuKfDWSaGc= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.7/go.mod h1:klO+ejMvYsB4QATfEOIXk8WAEwN4N0aBfJpvC+5SZBo= github.com/aws/aws-sdk-go-v2/service/sts v1.40.0 h1:ZGDJVmlpPFiNFCb/I42nYVKUanJAdFUiSmUo/32AqPQ= github.com/aws/aws-sdk-go-v2/service/sts v1.40.0/go.mod h1:E19xDjpzPZC7LS2knI9E6BaRFDK43Eul7vd6rSq2HWk= -github.com/aws/aws-sdk-go-v2/service/sts v1.40.1 h1:5sbIM57lHLaEaNWdIx23JH30LNBsSDkjN/QXGcRLAFc= -github.com/aws/aws-sdk-go-v2/service/sts v1.40.1/go.mod h1:E19xDjpzPZC7LS2knI9E6BaRFDK43Eul7vd6rSq2HWk= -github.com/aws/aws-sdk-go-v2/service/sts v1.40.2 h1:HK5ON3KmQV2HcAunnx4sKLB9aPf3gKGwVAf7xnx0QT0= -github.com/aws/aws-sdk-go-v2/service/sts v1.40.2/go.mod h1:E19xDjpzPZC7LS2knI9E6BaRFDK43Eul7vd6rSq2HWk= github.com/aws/smithy-go v1.23.2 h1:Crv0eatJUQhaManss33hS5r40CG3ZFH+21XSkqMrIUM= github.com/aws/smithy-go v1.23.2/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= +github.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= github.com/bytedance/sonic v1.14.0 h1:/OfKt8HFw0kh2rj8N0F6C/qPGRESq0BbaNZgcNXXzQQ= github.com/bytedance/sonic v1.14.0/go.mod h1:WoEbx8WTcFJfzCe0hbmyTGrfjt8PzNEBdxlNUO24NhA= github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA= github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI= -github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M= github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= +github.com/cncf/udpa/go v0.0.0-20220112060539-c52dc94e7fbe/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= +github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20220314180256-7f1daf1720fc/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20230105202645-06c439db220b/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443 h1:aQ3y1lwWyqYPiWZThqv1aFbZMiM9vblcSArJRf2Irls= +github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= github.com/coreos/go-oidc/v3 v3.16.0 h1:qRQUCFstKpXwmEjDQTIbyY/5jF00+asXzSkmkoa/mow= github.com/coreos/go-oidc/v3 v3.16.0/go.mod h1:wqPbKFrVnE90vty060SB40FCJ8fTHTxSwyXJqZH+sI8= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -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/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dchest/bcrypt_pbkdf v0.0.0-20150205184540-83f37f9c154a h1:saTgr5tMLFnmy/yg3qDTft4rE5DY2uJ/cCxCe3q0XTU= github.com/dchest/bcrypt_pbkdf v0.0.0-20150205184540-83f37f9c154a/go.mod h1:Bw9BbhOJVNR+t0jCqx2GC6zv0TGBsShs56Y3gfSCvl0= +github.com/dnaeon/go-vcr v1.1.0/go.mod h1:M7tiix8f0r6mKKJ3Yq/kqU1OYf3MnfmBWVbPx/yU9ko= +github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ= +github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dyaksa/archer v1.1.3 h1:jfe51tSNzzscFpu+Vilm4SKb0Lnv6FR1yaGspjab4x4= github.com/dyaksa/archer v1.1.3/go.mod h1:IYSp67u14JHTNuvvy6gG1eaX2TPywXvfk1FiyZwVEK4= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= +github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= +github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= +github.com/envoyproxy/go-control-plane v0.10.3/go.mod h1:fJJn/j26vwOu972OllsvAgJJM//w9BV6Fxbg2LuVd34= +github.com/envoyproxy/go-control-plane v0.11.1-0.20230524094728-9239064ad72f/go.mod h1:sfYdkwUW4BA3PbKjySwjJy+O4Pu0h62rlqCMHNk+K+Q= +github.com/envoyproxy/go-control-plane v0.13.4 h1:zEqyPVyku6IvWCFwux4x9RxkLOMUL+1vC9xUFv5l2/M= +github.com/envoyproxy/go-control-plane v0.13.4/go.mod h1:kDfuBlDVsSj2MjrLEtRWtHlsWIFcGyB2RMO44Dc5GZA= +github.com/envoyproxy/go-control-plane/envoy v1.32.4 h1:jb83lalDRZSpPWW2Z7Mck/8kXZ5CQAFYVjQcdVIr83A= +github.com/envoyproxy/go-control-plane/envoy v1.32.4/go.mod h1:Gzjc5k8JcJswLjAx1Zm+wSYE20UrLtt7JZMWiWQXQEw= +github.com/envoyproxy/go-control-plane/ratelimit v0.1.0 h1:/G9QYbddjL25KvtKTv3an9lx6VBE2cnb8wp1vEGNYGI= +github.com/envoyproxy/go-control-plane/ratelimit v0.1.0/go.mod h1:Wk+tMFAFbCXaJPzVVHnPgRKdUdwW/KdbRt94AzgRee4= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/envoyproxy/protoc-gen-validate v0.6.7/go.mod h1:dyJXwwfPK2VSqiB9Klm1J6romD608Ba7Hij42vrOBCo= +github.com/envoyproxy/protoc-gen-validate v0.9.1/go.mod h1:OKNgG7TCp5pF4d6XftA0++PMirau2/yoOwVac3AbF2w= +github.com/envoyproxy/protoc-gen-validate v0.10.1/go.mod h1:DRjgyB0I43LtJapqN6NiRwroiAU2PaFuvk/vjgh61ss= +github.com/envoyproxy/protoc-gen-validate v1.2.1 h1:DEo3O99U8j4hBFwbJfrz9VtgcDfUKS7KJ7spH3d86P8= +github.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/fergusstrange/embedded-postgres v1.33.0 h1:ka8vmRpm4IDsES7NPXQ/NThAp1fc/f+crcXYjCW7wK0= +github.com/fergusstrange/embedded-postgres v1.33.0/go.mod h1:w0YvnCgf19o6tskInrOOACtnqfVlOvluz3hlNLY7tRk= +github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= +github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/gabriel-vasile/mimetype v1.4.10 h1:zyueNbySn/z8mJZHLt6IPw0KoZsiQNszIpU+bX4+ZK0= github.com/gabriel-vasile/mimetype v1.4.10/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w= github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM= github.com/gin-gonic/gin v1.11.0 h1:OW/6PLjyusp2PPXtyxKHU0RbX6I/l28FTdDlae5ueWk= @@ -112,8 +803,23 @@ github.com/go-chi/cors v1.2.2 h1:Jmey33TE+b+rB7fT8MUy1u0I4L+NARQlK6LhzKPSyQE= github.com/go-chi/cors v1.2.2/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58= github.com/go-chi/httprate v0.15.0 h1:j54xcWV9KGmPf/X4H32/aTH+wBlrvxL7P+SdnRqxh5g= github.com/go-chi/httprate v0.15.0/go.mod h1:rzGHhVrsBn3IMLYDOZQsSU4fJNWcjui4fWKJcCId1R4= +github.com/go-fonts/dejavu v0.1.0/go.mod h1:4Wt4I4OU2Nq9asgDCteaAaWZOV24E+0/Pwo0gppep4g= +github.com/go-fonts/latin-modern v0.2.0/go.mod h1:rQVLdDMK+mK1xscDwsqM5J8U2jrRa3T0ecnM9pNujks= +github.com/go-fonts/liberation v0.1.1/go.mod h1:K6qoJYypsmfVjWg8KOVDQhLc8UDgIK2HYqyqAO9z7GY= +github.com/go-fonts/liberation v0.2.0/go.mod h1:K6qoJYypsmfVjWg8KOVDQhLc8UDgIK2HYqyqAO9z7GY= +github.com/go-fonts/stix v0.1.0/go.mod h1:w/c1f0ldAUlJmLBvlbkvVXLAD+tAMqobIIQpmnUIzUY= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs= github.com/go-jose/go-jose/v4 v4.1.3/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= +github.com/go-latex/latex v0.0.0-20210118124228-b3d85cf34e07/go.mod h1:CO1AlKB2CSIqUrmQPqA0gdRIlnLEY0gK5JGjh37zN5U= +github.com/go-latex/latex v0.0.0-20210823091927-c0d11ff05a81/go.mod h1:SX0U8uGpxhq9o2S/CELCSUxEWWAuoCUcVCQWv7G2OCk= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= @@ -127,6 +833,8 @@ github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g= github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +github.com/go-pdf/fpdf v0.5.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M= +github.com/go-pdf/fpdf v0.6.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= @@ -141,22 +849,153 @@ github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpv github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs= github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= +github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw= github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/golang-jwt/jwt v3.2.1+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= +github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= +github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo= github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= +github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA= github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A= github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI= +github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= +github.com/golang/glog v1.1.0/go.mod h1:pfYeQZ3JWZoXTV5sFc986z3HTpwQs9At6P4ImfuP3NQ= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= +github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= +github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= +github.com/golang/mock v1.7.0-rc.1 h1:YojYx61/OLFsiv6Rw1Z96LpldJIy31o+UHmwAUMJ6/U= +github.com/golang/mock v1.7.0-rc.1/go.mod h1:s42URUywIqd+OcERslBJvOjepvNymP31m3q8d/GkuRs= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/flatbuffers v2.0.8+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= +github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= +github.com/google/martian/v3 v3.3.2/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0= +github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/enterprise-certificate-proxy v0.0.0-20220520183353-fd19c99a87aa/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= +github.com/googleapis/enterprise-certificate-proxy v0.1.0/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= +github.com/googleapis/enterprise-certificate-proxy v0.2.0/go.mod h1:8C0jb7/mgJe/9KK8Lm7X9ctZC2t60YyIpYEI16jx0Qg= +github.com/googleapis/enterprise-certificate-proxy v0.2.1/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= +github.com/googleapis/enterprise-certificate-proxy v0.2.3/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= +github.com/googleapis/enterprise-certificate-proxy v0.3.6 h1:GW/XbdyBFQ8Qe+YAmFU9uHLo7OnF5tL52HFAgMmyrf4= +github.com/googleapis/enterprise-certificate-proxy v0.3.6/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= +github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM= +github.com/googleapis/gax-go/v2 v2.2.0/go.mod h1:as02EH8zWkzwUoLbBaFeQ+arQaj/OthfcblKl4IGNaM= +github.com/googleapis/gax-go/v2 v2.3.0/go.mod h1:b8LNqSzNabLiUpXKkY7HAR5jr6bIT99EXz9pXxye9YM= +github.com/googleapis/gax-go/v2 v2.4.0/go.mod h1:XOTVJ59hdnfJLIP/dh8n5CGryZR2LxK9wbMD5+iXC6c= +github.com/googleapis/gax-go/v2 v2.5.1/go.mod h1:h6B0KMMFNtI2ddbGJn3T3ZbwkeT6yqEF02fYlzkUCyo= +github.com/googleapis/gax-go/v2 v2.6.0/go.mod h1:1mjbznJAPHFpesgE5ucqfYEscaz5kMdcIDwU/6+DDoY= +github.com/googleapis/gax-go/v2 v2.7.0/go.mod h1:TEop28CZZQ2y+c0VxMUmu1lV+fQx57QpBWsYpwqHJx8= +github.com/googleapis/gax-go/v2 v2.7.1/go.mod h1:4orTrqY6hXxxaUL4LHIPl6lGo8vAE38/qKbhSAKP6QI= +github.com/googleapis/gax-go/v2 v2.15.0 h1:SyjDc1mGgZU5LncH8gimWo9lW1DtIfPibOG81vgd/bo= +github.com/googleapis/gax-go/v2 v2.15.0/go.mod h1:zVVkkxAQHa1RQpg9z2AUCMnKhi0Qld9rcmyfL1OZhoc= +github.com/googleapis/go-gorm-spanner v1.8.6 h1:a7tp91LPLnGkTwe375yhNLwCu1ac6COehZ4RzQo04g8= +github.com/googleapis/go-gorm-spanner v1.8.6/go.mod h1:ZpiB7Qd2sJxuUH6H6tsD71Nj9Q9IaR/6EUuTrxeM4tg= +github.com/googleapis/go-sql-spanner v1.17.0 h1:tYEOVY/uFAdtx5nw8XDdTccTqImPeUM2Ct4sk+Id0EI= +github.com/googleapis/go-sql-spanner v1.17.0/go.mod h1:L7dnHbQARFksUgYhTFM/cbfoIUtNrJ9ENZSoZ5cK58Q= +github.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4= +github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= +github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= +github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0/go.mod h1:hgWBS7lorOAVIJEQMi4ZsPv9hVvWI6+ch50m39Pf2Ks= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.11.3/go.mod h1:o//XUCC/F+yRGJoPO/VU0GSB0f8Nhgmxx0VIRUvaC0w= +github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= +github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= +github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= @@ -167,6 +1006,12 @@ github.com/jackc/pgx/v5 v5.6.0 h1:SWJzexBzPL5jb0GEsrPMLIsi/3jOo7RHlzTjcAeDrPY= github.com/jackc/pgx/v5 v5.6.0/go.mod h1:DNZ/vlrUnhWCoFGxHAG8U2ljioxukquj7utPDgtQdTw= github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= +github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs= +github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM= +github.com/jcmturner/gofork v1.0.0/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o= +github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg= +github.com/jcmturner/gokrb5/v8 v8.4.2/go.mod h1:sb+Xq/fTY5yktf/VxLsE3wlfPqQjp0aWNYyvBVK62bc= +github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc= github.com/jessevdk/go-flags v1.5.0 h1:1jKYvbxEjfUl0fmqTCOfonvskHHXMjBySTLW4y9LFvc= github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= @@ -181,21 +1026,37 @@ github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8Hm github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/jung-kurt/gofpdf v1.0.0/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= +github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/asmfmt v1.3.2/go.mod h1:AG8TuvYojzulgDAMCnYn50l/5QV3Bs/tp6j0HLHbNSE= +github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= +github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lyft/protoc-gen-star v0.6.0/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA= +github.com/lyft/protoc-gen-star v0.6.1/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA= +github.com/lyft/protoc-gen-star/v2 v2.0.1/go.mod h1:RcCdONR2ScXaYnQC5tUzxzlpA3WVYF7/opLeUgcQs/o= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= @@ -203,15 +1064,20 @@ github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0 github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= -github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= -github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +github.com/mattn/go-sqlite3 v1.14.14/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= +github.com/mattn/go-sqlite3 v1.14.28 h1:ThEiQrnbtumT+QMknw63Befp/ce/nUPgBPMlRFEum7A= +github.com/mattn/go-sqlite3 v1.14.28/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +github.com/microsoft/go-mssqldb v0.19.0/go.mod h1:ukJCBnnzLzpVF0qYRT+eg1e+eSwjeQ7IvenUv8QPook= github.com/microsoft/go-mssqldb v1.7.2 h1:CHkFJiObW7ItKTJfHo1QX7QBBD1iV+mn1eOyRP3b/PA= github.com/microsoft/go-mssqldb v1.7.2/go.mod h1:kOvZKUdrhhFQmxLZqbwUV0rHkNkZpthMITIb2Ko1IoA= +github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8/go.mod h1:mC1jAcsrzbxHt8iiaC+zU4b1ylILSosueou12R++wfY= +github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3/go.mod h1:RagcQ7I8IeTMnF8JTXieKnO4Z6JCsikNEzj0DwauVzE= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -219,21 +1085,39 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8= +github.com/montanaflynn/stats v0.6.6/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow= github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/otiai10/copy v1.7.0 h1:hVoPiN+t+7d2nzzwMiDHPSOogsWAStewq3TwU05+clE= -github.com/otiai10/copy v1.7.0/go.mod h1:rmRl6QPdJj6EiUqXQ/4Nn2lLXoNQjFCQbbNrxgc/t3U= github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= +github.com/phpdave11/gofpdf v1.4.2/go.mod h1:zpO6xFn9yxo3YLyMvW8HcKWVdbNqgIfOOp2dXMnm1mY= +github.com/phpdave11/gofpdi v1.0.12/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI= +github.com/phpdave11/gofpdi v1.0.13/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI= +github.com/pierrec/lz4/v4 v4.1.15/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= +github.com/pkg/browser v0.0.0-20210115035449-ce105d075bb4/go.mod h1:N6UoU20jOqggOuDwUaBQpluzLNDqif3kq9z2wpdYEfQ= +github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= +github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= +github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo= +github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE= github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho= -github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= -github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= +github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= +github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= github.com/prometheus/common v0.48.0 h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSzKKE= github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc= github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= @@ -242,20 +1126,33 @@ github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI= github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg= github.com/quic-go/quic-go v0.54.0 h1:6s1YB9QotYI6Ospeiguknbp2Znb/jZYjZLRXn9kMQBg= github.com/quic-go/quic-go v0.54.0/go.mod h1:e68ZEaCdyviluZmy44P6Iey98v/Wfz6HCjQEm+l8zTY= -github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= -github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= +github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY= github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfFZQK844Gfx8o5WFuvpxWRwnSoipWe/p622j1v06w= +github.com/ruudk/golang-pdf417 v0.0.0-20201230142125-a7e3863a1245/go.mod h1:pQAZKsJ8yyVxGRWYNEm9oFB8ieLgKFnamEyDmSA0BRk= github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc= github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik= +github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= +github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sosedoff/pgweb v0.16.2 h1:1F1CWlCLSEgSctMva+nYuUibdhyiCUzlXyU5MQUJbFM= github.com/sosedoff/pgweb v0.16.2/go.mod h1:ER7fsBddI3h7MQKO5RsUPi7Q/PWZYSKcI61kTp369Rw= github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw= github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4= +github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= +github.com/spf13/afero v1.9.2/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y= github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I= github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg= github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY= @@ -267,30 +1164,29 @@ github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU= github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY= +github.com/spiffe/go-spiffe/v2 v2.5.0 h1:N2I01KCUkv1FAjZXJMwh95KK1ZIQLYbPfhaxw8WS0hE= +github.com/spiffe/go-spiffe/v2 v2.5.0/go.mod h1:P+NxobPc6wXhVtINNtFjNWGBTreew1GBUCwT2wPmb7g= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/sv-tools/openapi v0.2.1 h1:ES1tMQMJFGibWndMagvdoo34T1Vllxr1Nlm5wz6b1aA= github.com/sv-tools/openapi v0.2.1/go.mod h1:k5VuZamTw1HuiS9p2Wl5YIDWzYnHG6/FgPOSFXLAhGg= -github.com/swaggo/files/v2 v2.0.0 h1:hmAt8Dkynw7Ssz46F6pn8ok6YmGZqHSVLZ+HQM7i0kw= -github.com/swaggo/files/v2 v2.0.0/go.mod h1:24kk2Y9NYEJ5lHuCra6iVwkMjIekMCaFq/0JQj66kyM= -github.com/swaggo/http-swagger/v2 v2.0.2 h1:FKCdLsl+sFCx60KFsyM0rDarwiUSZ8DqbfSyIKC9OBg= -github.com/swaggo/http-swagger/v2 v2.0.2/go.mod h1:r7/GBkAWIfK6E/OLnE8fXnviHiDeAHmgIyooa4xm3AQ= -github.com/swaggo/swag v1.8.1 h1:JuARzFX1Z1njbCGz+ZytBR15TFJwF2Q7fu8puJHhQYI= -github.com/swaggo/swag v1.8.1/go.mod h1:ugemnJsPZm/kRwFUnzBlbHRd0JY9zE1M4F+uy2pAaPQ= github.com/swaggo/swag/v2 v2.0.0-rc4 h1:SZ8cK68gcV6cslwrJMIOqPkJELRwq4gmjvk77MrvHvY= github.com/swaggo/swag/v2 v2.0.0-rc4/go.mod h1:Ow7Y8gF16BTCDn8YxZbyKn8FkMLRUHekv1kROJZpbvE= github.com/tuvistavie/securerandom v0.0.0-20140719024926-15512123a948 h1:yL0l/u242MzDP6D0B5vGC+wxm5WRY+alQZy+dJk3bFI= @@ -299,11 +1195,53 @@ github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA= github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4= +github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo= +github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ= github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= +github.com/zeebo/errs v1.4.0 h1:XNdoD/RRMKP7HD0UhJnIzUy74ISdGGxURlYG8HSWSfM= +github.com/zeebo/errs v1.4.0/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4= github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0= github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= +go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= +go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/contrib/detectors/gcp v1.37.0 h1:B+WbN9RPsvobe6q4vP6KgM8/9plR/HNjgGBrfcOlweA= +go.opentelemetry.io/contrib/detectors/gcp v1.37.0/go.mod h1:K5zQ3TT7p2ru9Qkzk0bKtCql0RGkPj9pRjpXgZJZ+rU= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.62.0 h1:rbRJ8BBoVMsQShESYZ0FkvcITu8X8QNwJogcLUmDNNw= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.62.0/go.mod h1:ru6KHrNtNHxM4nD/vd6QrLVWgKhxPYgblq4VAtNawTQ= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 h1:Hf9xI/XLML9ElpiHVDNwvqI0hIFlzV8dgIr35kV1kRU= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0/go.mod h1:NfchwuyNoMcZ5MLHwPrODwUF1HWCXWrL31s8gSAdIKY= +go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= +go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= +go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE= +go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E= +go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI= +go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg= +go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc= +go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps= +go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= +go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= +go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +go.opentelemetry.io/proto/otlp v0.15.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= +go.opentelemetry.io/proto/otlp v0.19.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU= go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= @@ -311,69 +1249,685 @@ go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/arch v0.20.0 h1:dx1zTU0MAE98U+TQ8BLl7XsJbgze2WnNKF/8tGp/Q6c= golang.org/x/arch v0.20.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201112155050-0c6587e931a9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220511200225-c6db032c6c88/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/crypto v0.44.0 h1:A97SsFvM3AIwEEmTBiaxPPTYpDC47w720rdiiUvgoAU= golang.org/x/crypto v0.44.0/go.mod h1:013i+Nw79BMiQiMsOPcVCB5ZIJbYkerPrGnOa00tvmc= +golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191002040644-a1355ae1e2c3/go.mod h1:NOZ3BPKG0ec/BKJQgnvsSFpcKLM5xXVWnvZS97DWHgE= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/exp v0.0.0-20220827204233-334a2380cb91/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE= +golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20190910094157-69e4b8554b2a/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20200119044424-58c23975cae1/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20200430140353-33d19683fad8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20200618115811-c13761719519/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20201208152932-35266b937fa6/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20210216034530-4410531fe030/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20210607152325-775e3b0c77b9/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= +golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= +golang.org/x/image v0.0.0-20211028202545-6944b10bf410/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= +golang.org/x/image v0.0.0-20220302094943-723b81ca9867/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= +golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA= golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.0.0-20220617184016-355a448f1bc9/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.0.0-20220909164309-bea034e7d591/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= +golang.org/x/net v0.0.0-20221012135044-0b7e1fb9d458/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= +golang.org/x/net v0.0.0-20221014081412-f15817d10f9b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= +golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= +golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= +golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= +golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4= golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= +golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= +golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= +golang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE= +golang.org/x/oauth2 v0.0.0-20220622183110-fd043fe589d2/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE= +golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= +golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= +golang.org/x/oauth2 v0.0.0-20221006150949-b44042a4b9c1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= +golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= +golang.org/x/oauth2 v0.4.0/go.mod h1:RznEsdpjGAINPTOF0UH/t+xJ75L18YO3Ho6Pyn+uRec= +golang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I= +golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw= +golang.org/x/oauth2 v0.7.0/go.mod h1:hPLQkd9LyjfXTiRohC/41GhcFqxisoUQ99sCUOHO9x4= golang.org/x/oauth2 v0.33.0 h1:4Q+qn+E5z8gPRJfmRy7C2gGG3T4jIprK6aSYgTXGRpo= golang.org/x/oauth2 v0.33.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200219091948-cb0a6d8edb6c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210304124612-50617c2ba197/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220224120231-95c6836cb0e7/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220624220833-87e55d714810/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220829200755-d48e67d00261/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= +golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= +golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= +golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= +golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= +golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU= golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4= golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20220922220347-f3bd1da661af/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.1.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= +golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= +golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190927191325-030b2cf1153e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= +golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= +golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ= golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= +gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0= +gonum.org/v1/gonum v0.9.3/go.mod h1:TZumC3NeyVQskjXqmyWt4S3bINhy7B4eYwW69EbyX+0= +gonum.org/v1/gonum v0.11.0/go.mod h1:fSG4YDCxxUZQJ7rKsQrj0gMOg00Il0Z96/qMA4bVQhA= +gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= +gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc= +gonum.org/v1/plot v0.9.0/go.mod h1:3Pcqqmp6RHvJI72kgb8fThyUnav364FOsdDo2aGW5lY= +gonum.org/v1/plot v0.10.1/go.mod h1:VZW5OlhkL1mysU9vaqNHnsy86inf6Ot+jB3r+BczCEo= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= +google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= +google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= +google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= +google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= +google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo= +google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4= +google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw= +google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU= +google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k= +google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= +google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= +google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI= +google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I= +google.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo= +google.golang.org/api v0.67.0/go.mod h1:ShHKP8E60yPsKNw/w8w+VYaj9H6buA5UqDp8dhbQZ6g= +google.golang.org/api v0.70.0/go.mod h1:Bs4ZM2HGifEvXwd50TtW70ovgJffJYw2oRCOFU/SkfA= +google.golang.org/api v0.71.0/go.mod h1:4PyU6e6JogV1f9eA4voyrTY2batOLdgZ5qZ5HOCc4j8= +google.golang.org/api v0.74.0/go.mod h1:ZpfMZOVRMywNyvJFeqL9HRWBgAuRfSjJFpe9QtRRyDs= +google.golang.org/api v0.75.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA= +google.golang.org/api v0.77.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA= +google.golang.org/api v0.78.0/go.mod h1:1Sg78yoMLOhlQTeF+ARBoytAcH1NNyyl390YMy6rKmw= +google.golang.org/api v0.80.0/go.mod h1:xY3nI94gbvBrE0J6NHXhxOmW97HG7Khjkku6AFB3Hyg= +google.golang.org/api v0.84.0/go.mod h1:NTsGnUFJMYROtiquksZHBWtHfeMC7iYthki7Eq3pa8o= +google.golang.org/api v0.85.0/go.mod h1:AqZf8Ep9uZ2pyTvgL+x0D3Zt0eoT9b5E8fmzfu6FO2g= +google.golang.org/api v0.90.0/go.mod h1:+Sem1dnrKlrXMR/X0bPnMWyluQe4RsNoYfmNLhOIkzw= +google.golang.org/api v0.93.0/go.mod h1:+Sem1dnrKlrXMR/X0bPnMWyluQe4RsNoYfmNLhOIkzw= +google.golang.org/api v0.95.0/go.mod h1:eADj+UBuxkh5zlrSntJghuNeg8HwQ1w5lTKkuqaETEI= +google.golang.org/api v0.96.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= +google.golang.org/api v0.97.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= +google.golang.org/api v0.98.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= +google.golang.org/api v0.99.0/go.mod h1:1YOf74vkVndF7pG6hIHuINsM7eWwpVTAfNMNiL91A08= +google.golang.org/api v0.100.0/go.mod h1:ZE3Z2+ZOr87Rx7dqFsdRQkRBk36kDtp/h+QpHbB7a70= +google.golang.org/api v0.102.0/go.mod h1:3VFl6/fzoA+qNuS1N1/VfXY4LjoXN/wzeIp7TweWwGo= +google.golang.org/api v0.103.0/go.mod h1:hGtW6nK1AC+d9si/UBhw8Xli+QMOf6xyNAyJw4qU9w0= +google.golang.org/api v0.106.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/O9MY= +google.golang.org/api v0.107.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/O9MY= +google.golang.org/api v0.108.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/O9MY= +google.golang.org/api v0.110.0/go.mod h1:7FC4Vvx1Mooxh8C5HWjzZHcavuS2f6pmJpZx60ca7iI= +google.golang.org/api v0.111.0/go.mod h1:qtFHvU9mhgTJegR31csQ+rwxyUTHOKFqCKWp1J0fdw0= +google.golang.org/api v0.114.0/go.mod h1:ifYI2ZsFK6/uGddGfAD5BMxlnkBqCmqHSDUVi45N5Yg= +google.golang.org/api v0.247.0 h1:tSd/e0QrUlLsrwMKmkbQhYVa109qIintOls2Wh6bngc= +google.golang.org/api v0.247.0/go.mod h1:r1qZOPmxXffXg6xS5uhx16Fa/UFY8QU/K4bfKrnvovM= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210329143202-679c6ae281ee/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= +google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= +google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= +google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24= +google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= +google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= +google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= +google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= +google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w= +google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211221195035-429b39de9b1c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20220126215142-9970aeb2e350/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20220207164111-0872dc986b00/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20220218161850-94dd64e39d7c/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= +google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= +google.golang.org/genproto v0.0.0-20220304144024-325a89244dc8/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= +google.golang.org/genproto v0.0.0-20220310185008-1973136f34c6/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= +google.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E= +google.golang.org/genproto v0.0.0-20220329172620-7be39ac1afc7/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220407144326-9054f6ed7bac/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220413183235-5e96e2839df9/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220414192740-2d67ff6cf2b4/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220421151946-72621c1f0bd3/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220429170224-98d788798c3e/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= +google.golang.org/genproto v0.0.0-20220505152158-f39f71e6c8f3/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= +google.golang.org/genproto v0.0.0-20220518221133-4f43b3371335/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= +google.golang.org/genproto v0.0.0-20220523171625-347a074981d8/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= +google.golang.org/genproto v0.0.0-20220608133413-ed9918b62aac/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= +google.golang.org/genproto v0.0.0-20220616135557-88e70c0c3a90/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= +google.golang.org/genproto v0.0.0-20220617124728-180714bec0ad/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= +google.golang.org/genproto v0.0.0-20220624142145-8cd45d7dbd1f/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= +google.golang.org/genproto v0.0.0-20220628213854-d9e0b6570c03/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= +google.golang.org/genproto v0.0.0-20220722212130-b98a9ff5e252/go.mod h1:GkXuJDJ6aQ7lnJcRF+SJVgFdQhypqgl3LB1C9vabdRE= +google.golang.org/genproto v0.0.0-20220801145646-83ce21fca29f/go.mod h1:iHe1svFLAZg9VWz891+QbRMwUv9O/1Ww+/mngYeThbc= +google.golang.org/genproto v0.0.0-20220815135757-37a418bb8959/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= +google.golang.org/genproto v0.0.0-20220817144833-d7fd3f11b9b1/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= +google.golang.org/genproto v0.0.0-20220822174746-9e6da59bd2fc/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= +google.golang.org/genproto v0.0.0-20220829144015-23454907ede3/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= +google.golang.org/genproto v0.0.0-20220829175752-36a9c930ecbf/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= +google.golang.org/genproto v0.0.0-20220913154956-18f8339a66a5/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= +google.golang.org/genproto v0.0.0-20220914142337-ca0e39ece12f/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= +google.golang.org/genproto v0.0.0-20220915135415-7fd63a7952de/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= +google.golang.org/genproto v0.0.0-20220916172020-2692e8806bfa/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= +google.golang.org/genproto v0.0.0-20220919141832-68c03719ef51/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= +google.golang.org/genproto v0.0.0-20220920201722-2b89144ce006/go.mod h1:ht8XFiar2npT/g4vkk7O0WYS1sHOHbdujxbEp7CJWbw= +google.golang.org/genproto v0.0.0-20220926165614-551eb538f295/go.mod h1:woMGP53BroOrRY3xTxlbr8Y3eB/nzAvvFM83q7kG2OI= +google.golang.org/genproto v0.0.0-20220926220553-6981cbe3cfce/go.mod h1:woMGP53BroOrRY3xTxlbr8Y3eB/nzAvvFM83q7kG2OI= +google.golang.org/genproto v0.0.0-20221010155953-15ba04fc1c0e/go.mod h1:3526vdqwhZAwq4wsRUaVG555sVgsNmIjRtO7t/JH29U= +google.golang.org/genproto v0.0.0-20221014173430-6e2ab493f96b/go.mod h1:1vXfmgAz9N9Jx0QA82PqRVauvCz1SGSz739p0f183jM= +google.golang.org/genproto v0.0.0-20221014213838-99cd37c6964a/go.mod h1:1vXfmgAz9N9Jx0QA82PqRVauvCz1SGSz739p0f183jM= +google.golang.org/genproto v0.0.0-20221024153911-1573dae28c9c/go.mod h1:9qHF0xnpdSfF6knlcsnpzUu5y+rpwgbvsyGAZPBMg4s= +google.golang.org/genproto v0.0.0-20221024183307-1bc688fe9f3e/go.mod h1:9qHF0xnpdSfF6knlcsnpzUu5y+rpwgbvsyGAZPBMg4s= +google.golang.org/genproto v0.0.0-20221027153422-115e99e71e1c/go.mod h1:CGI5F/G+E5bKwmfYo09AXuVN4dD894kIKUFmVbP2/Fo= +google.golang.org/genproto v0.0.0-20221109142239-94d6d90a7d66/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= +google.golang.org/genproto v0.0.0-20221114212237-e4508ebdbee1/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= +google.golang.org/genproto v0.0.0-20221117204609-8f9c96812029/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= +google.golang.org/genproto v0.0.0-20221118155620-16455021b5e6/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= +google.golang.org/genproto v0.0.0-20221201164419-0e50fba7f41c/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= +google.golang.org/genproto v0.0.0-20221201204527-e3fa12d562f3/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= +google.golang.org/genproto v0.0.0-20221202195650-67e5cbc046fd/go.mod h1:cTsE614GARnxrLsqKREzmNYJACSWWpAWdNMwnD7c2BE= +google.golang.org/genproto v0.0.0-20221227171554-f9683d7f8bef/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230112194545-e10362b5ecf9/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230113154510-dbe35b8444a5/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230123190316-2c411cf9d197/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230124163310-31e0e69b6fc2/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230125152338-dcaf20b6aeaa/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230127162408-596548ed4efa/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230209215440-0dfe4f8abfcc/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230216225411-c8e22ba71e44/go.mod h1:8B0gmkoRebU8ukX6HP+4wrVQUY1+6PkQ44BSyIlflHA= +google.golang.org/genproto v0.0.0-20230222225845-10f96fb3dbec/go.mod h1:3Dl5ZL0q0isWJt+FVcfpQyirqemEuLAK/iFvg1UP1Hw= +google.golang.org/genproto v0.0.0-20230223222841-637eb2293923/go.mod h1:3Dl5ZL0q0isWJt+FVcfpQyirqemEuLAK/iFvg1UP1Hw= +google.golang.org/genproto v0.0.0-20230303212802-e74f57abe488/go.mod h1:TvhZT5f700eVlTNwND1xoEZQeWTB2RY/65kplwl/bFA= +google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s= +google.golang.org/genproto v0.0.0-20230320184635-7606e756e683/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s= +google.golang.org/genproto v0.0.0-20230323212658-478b75c54725/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak= +google.golang.org/genproto v0.0.0-20230330154414-c0448cd141ea/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak= +google.golang.org/genproto v0.0.0-20230331144136-dcfb400f0633/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak= +google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU= +google.golang.org/genproto v0.0.0-20250804133106-a7a43d27e69b h1:eZTgydvqZO44zyTZAvMaSyAxccZZdraiSAGvqOczVvk= +google.golang.org/genproto v0.0.0-20250804133106-a7a43d27e69b/go.mod h1:suyz2QBHQKlGIF92HEEsCfO1SwxXdk7PFLz+Zd9Uah4= +google.golang.org/genproto/googleapis/api v0.0.0-20250804133106-a7a43d27e69b h1:ULiyYQ0FdsJhwwZUwbaXpZF5yUE3h+RA+gxvBu37ucc= +google.golang.org/genproto/googleapis/api v0.0.0-20250804133106-a7a43d27e69b/go.mod h1:oDOGiMSXHL4sDTJvFvIB9nRQCGdLP1o/iVaqQK8zB+M= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250811230008-5f3141c8851a h1:tPE/Kp+x9dMSwUm/uM0JKK0IfdiJkwAbSMSeZBXXJXc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250811230008-5f3141c8851a/go.mod h1:gw1tLEfykwDz2ET4a12jcXt4couGAm7IwsVaTy0Sflo= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= +google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= +google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= +google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= +google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= +google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= +google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc v1.48.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc v1.49.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= +google.golang.org/grpc v1.50.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= +google.golang.org/grpc v1.50.1/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= +google.golang.org/grpc v1.51.0/go.mod h1:wgNDFcnuBGmxLKI/qn4T+m5BtEBYXJPvibbUPsAIPww= +google.golang.org/grpc v1.52.3/go.mod h1:pu6fVzoFb+NBYNAvQL08ic+lvB2IojljRYuun5vorUY= +google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw= +google.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g= +google.golang.org/grpc v1.56.3/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s= +google.golang.org/grpc v1.74.2 h1:WoosgB65DlWVC9FqI82dGsZhWFNBSLjQ84bjROOpMu4= +google.golang.org/grpc v1.74.2/go.mod h1:CtQ+BGjaAIXHs/5YS3i473GqwBBa1zGQNevxdeBEXrM= +google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.29.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw= google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -381,17 +1935,23 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gorm.io/datatypes v1.2.7 h1:ww9GAhF1aGXZY3EB3cJPJ7//JiuQo7DlQA7NNlVaTdk= gorm.io/datatypes v1.2.7/go.mod h1:M2iO+6S3hhi4nAyYe444Pcb0dcIiOMJ7QHaUXxyiNZY= -gorm.io/driver/mysql v1.5.6 h1:Ld4mkIickM+EliaQZQx3uOJDJHtrd70MxAUqWqlx3Y8= -gorm.io/driver/mysql v1.5.6/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkDM= +gorm.io/driver/mysql v1.5.7 h1:MndhOPYOfEp2rHKgkZIhJ16eVUIRf2HmzgoPmh7FCWo= +gorm.io/driver/mysql v1.5.7/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkDM= gorm.io/driver/postgres v1.6.0 h1:2dxzU8xJ+ivvqTRph34QX+WrRaJlmfyPqXmoGVjMBa4= gorm.io/driver/postgres v1.6.0/go.mod h1:vUw0mrGgrTK+uPHEhAdV4sfFELrByKVGnaVRkXDhtWo= gorm.io/driver/sqlite v1.6.0 h1:WHRRrIiulaPiPFmDcod6prc4l2VGVWHz80KspNsxSfQ= @@ -399,5 +1959,52 @@ gorm.io/driver/sqlite v1.6.0/go.mod h1:AO9V1qIQddBESngQUKWL9yoH93HIeA1X6V633rBwy gorm.io/driver/sqlserver v1.6.0 h1:VZOBQVsVhkHU/NzNhRJKoANt5pZGQAS1Bwc6m6dgfnc= gorm.io/driver/sqlserver v1.6.0/go.mod h1:WQzt4IJo/WHKnckU9jXBLMJIVNMVeTu25dnOzehntWw= gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= +gorm.io/gorm v1.30.0/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE= gorm.io/gorm v1.31.1 h1:7CA8FTFz/gRfgqgpeKIBcervUn3xSyPUmr6B2WXJ7kg= gorm.io/gorm v1.31.1/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las= +lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= +lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= +modernc.org/cc/v3 v3.36.0/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI= +modernc.org/cc/v3 v3.36.2/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI= +modernc.org/cc/v3 v3.36.3/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI= +modernc.org/ccgo/v3 v3.0.0-20220428102840-41399a37e894/go.mod h1:eI31LL8EwEBKPpNpA4bU1/i+sKOwOrQy8D87zWUcRZc= +modernc.org/ccgo/v3 v3.0.0-20220430103911-bc99d88307be/go.mod h1:bwdAnOoaIt8Ax9YdWGjxWsdkPcZyRPHqrOvJxaKAKGw= +modernc.org/ccgo/v3 v3.16.4/go.mod h1:tGtX0gE9Jn7hdZFeU88slbTh1UtCYKusWOoCJuvkWsQ= +modernc.org/ccgo/v3 v3.16.6/go.mod h1:tGtX0gE9Jn7hdZFeU88slbTh1UtCYKusWOoCJuvkWsQ= +modernc.org/ccgo/v3 v3.16.8/go.mod h1:zNjwkizS+fIFDrDjIAgBSCLkWbJuHF+ar3QRn+Z9aws= +modernc.org/ccgo/v3 v3.16.9/go.mod h1:zNMzC9A9xeNUepy6KuZBbugn3c0Mc9TeiJO4lgvkJDo= +modernc.org/ccorpus v1.11.6/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ= +modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM= +modernc.org/libc v0.0.0-20220428101251-2d5f3daf273b/go.mod h1:p7Mg4+koNjc8jkqwcoFBJx7tXkpj00G77X7A72jXPXA= +modernc.org/libc v1.16.0/go.mod h1:N4LD6DBE9cf+Dzf9buBlzVJndKr/iJHG97vGLHYnb5A= +modernc.org/libc v1.16.1/go.mod h1:JjJE0eu4yeK7tab2n4S1w8tlWd9MxXLRzheaRnAKymU= +modernc.org/libc v1.16.17/go.mod h1:hYIV5VZczAmGZAnG15Vdngn5HSF5cSkbvfz2B7GRuVU= +modernc.org/libc v1.16.19/go.mod h1:p7Mg4+koNjc8jkqwcoFBJx7tXkpj00G77X7A72jXPXA= +modernc.org/libc v1.17.0/go.mod h1:XsgLldpP4aWlPlsjqKRdHPqCxCjISdHfM/yeWC5GyW0= +modernc.org/libc v1.17.1/go.mod h1:FZ23b+8LjxZs7XtFMbSzL/EhPxNbfZbErxEHc7cbD9s= +modernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= +modernc.org/mathutil v1.4.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= +modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= +modernc.org/memory v1.1.1/go.mod h1:/0wo5ibyrQiaoUoH7f9D8dnglAmILJ5/cxZlRECf+Nw= +modernc.org/memory v1.2.0/go.mod h1:/0wo5ibyrQiaoUoH7f9D8dnglAmILJ5/cxZlRECf+Nw= +modernc.org/memory v1.2.1/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= +modernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= +modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= +modernc.org/sqlite v1.18.1/go.mod h1:6ho+Gow7oX5V+OiOQ6Tr4xeqbx13UZ6t+Fw9IRUG4d4= +modernc.org/strutil v1.1.1/go.mod h1:DE+MQQ/hjKBZS2zNInV5hhcipt5rLPWkmpbGeW5mmdw= +modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw= +modernc.org/tcl v1.13.1/go.mod h1:XOLfOwzhkljL4itZkK6T72ckMgvj0BDsnKNdZVUOecw= +modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= +modernc.org/z v1.5.1/go.mod h1:eWFB510QWW5Th9YGZT81s+LwvaAs3Q2yr4sP0rmLkv8= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/internal/api/mount_api_routes.go b/internal/api/mount_api_routes.go index c0a10e0..27e4dee 100644 --- a/internal/api/mount_api_routes.go +++ b/internal/api/mount_api_routes.go @@ -32,6 +32,8 @@ func mountAPIRoutes(r chi.Router, db *gorm.DB, jobs *bg.Jobs) { mountAnnotationRoutes(v1, db, authOrg) mountNodePoolRoutes(v1, db, authOrg) mountDNSRoutes(v1, db, authOrg) + mountLoadBalancerRoutes(v1, db, authOrg) + mountClusterRoutes(v1, db, authOrg) }) }) } diff --git a/internal/api/mount_cluster_routes.go b/internal/api/mount_cluster_routes.go new file mode 100644 index 0000000..f105c31 --- /dev/null +++ b/internal/api/mount_cluster_routes.go @@ -0,0 +1,39 @@ +package api + +import ( + "net/http" + + "github.com/glueops/autoglue/internal/handlers" + "github.com/go-chi/chi/v5" + "gorm.io/gorm" +) + +func mountClusterRoutes(r chi.Router, db *gorm.DB, authOrg func(http.Handler) http.Handler) { + r.Route("/clusters", func(c chi.Router) { + c.Use(authOrg) + c.Get("/", handlers.ListClusters(db)) + c.Post("/", handlers.CreateCluster(db)) + + c.Get("/{clusterID}", handlers.GetCluster(db)) + c.Patch("/{clusterID}", handlers.UpdateCluster(db)) + c.Delete("/{clusterID}", handlers.DeleteCluster(db)) + + c.Post("/{clusterID}/captain-domain", handlers.AttachCaptainDomain(db)) + c.Delete("/{clusterID}/captain-domain", handlers.DetachCaptainDomain(db)) + + c.Post("/{clusterID}/control-plane-record-set", handlers.AttachControlPlaneRecordSet(db)) + c.Delete("/{clusterID}/control-plane-record-set", handlers.DetachControlPlaneRecordSet(db)) + + c.Post("/{clusterID}/apps-load-balancer", handlers.AttachAppsLoadBalancer(db)) + c.Delete("/{clusterID}/apps-load-balancer", handlers.DetachAppsLoadBalancer(db)) + c.Post("/{clusterID}/glueops-load-balancer", handlers.AttachGlueOpsLoadBalancer(db)) + c.Delete("/{clusterID}/glueops-load-balancer", handlers.DetachGlueOpsLoadBalancer(db)) + + c.Post("/{clusterID}/bastion", handlers.AttachBastionServer(db)) + c.Delete("/{clusterID}/bastion", handlers.DetachBastionServer(db)) + + c.Post("/{clusterID}/kubeconfig", handlers.SetClusterKubeconfig(db)) + c.Delete("/{clusterID}/kubeconfig", handlers.ClearClusterKubeconfig(db)) + + }) +} diff --git a/internal/api/mount_db_studio.go b/internal/api/mount_db_studio.go index 437421a..5a4e7e0 100644 --- a/internal/api/mount_db_studio.go +++ b/internal/api/mount_db_studio.go @@ -10,7 +10,7 @@ import ( pgcmd "github.com/sosedoff/pgweb/pkg/command" ) -func PgwebHandler(dbURL, prefix string, readonly bool) (http.Handler, error) { +func MountDbStudio(dbURL, prefix string, readonly bool) (http.Handler, error) { // Normalize prefix for pgweb: // - no leading slash // - always trailing slash if not empty diff --git a/internal/api/mount_load_balancer_routes.go b/internal/api/mount_load_balancer_routes.go new file mode 100644 index 0000000..674b51a --- /dev/null +++ b/internal/api/mount_load_balancer_routes.go @@ -0,0 +1,20 @@ +package api + +import ( + "net/http" + + "github.com/glueops/autoglue/internal/handlers" + "github.com/go-chi/chi/v5" + "gorm.io/gorm" +) + +func mountLoadBalancerRoutes(r chi.Router, db *gorm.DB, authOrg func(http.Handler) http.Handler) { + r.Route("/load-balancers", func(l chi.Router) { + l.Use(authOrg) + l.Get("/", handlers.ListLoadBalancers(db)) + l.Post("/", handlers.CreateLoadBalancer(db)) + l.Get("/{id}", handlers.GetLoadBalancer(db)) + l.Patch("/{id}", handlers.UpdateLoadBalancer(db)) + l.Delete("/{id}", handlers.DeleteLoadBalancer(db)) + }) +} diff --git a/internal/api/mount_swagger_routes.go b/internal/api/mount_swagger_routes.go index ae08042..d61c218 100644 --- a/internal/api/mount_swagger_routes.go +++ b/internal/api/mount_swagger_routes.go @@ -1,15 +1,87 @@ package api import ( + "fmt" + "html/template" + "net/http" + "github.com/glueops/autoglue/docs" "github.com/go-chi/chi/v5" - httpSwagger "github.com/swaggo/http-swagger/v2" ) func mountSwaggerRoutes(r chi.Router) { - r.Get("/swagger/*", httpSwagger.Handler( - httpSwagger.URL("swagger.json"), - )) + r.Get("/swagger", RapidDocHandler("/swagger/swagger.yaml")) + r.Get("/swagger/index.html", RapidDocHandler("/swagger/swagger.yaml")) r.Get("/swagger/swagger.json", serveSwaggerFromEmbed(docs.SwaggerJSON, "application/json")) r.Get("/swagger/swagger.yaml", serveSwaggerFromEmbed(docs.SwaggerYAML, "application/x-yaml")) } + +var rapidDocTmpl = template.Must(template.New("redoc").Parse(` + + + + AutoGlue API Docs + + + + + + + + + +`)) + +func RapidDocHandler(specURL string) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + scheme := "http" + if r.TLS != nil || r.Header.Get("X-Forwarded-Proto") == "https" { + scheme = "https" + } + + host := r.Host + defaultServer := fmt.Sprintf("%s://%s/api/v1", scheme, host) + + w.Header().Set("Content-Type", "text/html; charset=utf-8") + if err := rapidDocTmpl.Execute(w, map[string]string{ + "SpecURL": specURL, + "DefaultServer": defaultServer, + }); err != nil { + http.Error(w, "failed to render docs", http.StatusInternalServerError) + return + } + } +} diff --git a/internal/api/mw_security.go b/internal/api/mw_security.go index 2217003..0bf01f0 100644 --- a/internal/api/mw_security.go +++ b/internal/api/mw_security.go @@ -29,14 +29,14 @@ func SecurityHeaders(next http.Handler) http.Handler { "base-uri 'self'", "form-action 'self'", // Vite dev & inline preamble/eval: - "script-src 'self' 'unsafe-inline' 'unsafe-eval' http://localhost:5173", + "script-src 'self' 'unsafe-inline' 'unsafe-eval' http://localhost:5173 https://unpkg.com", // allow dev style + Google Fonts "style-src 'self' 'unsafe-inline' http://localhost:5173 https://fonts.googleapis.com", "img-src 'self' data: blob:", // Google font files "font-src 'self' data: https://fonts.gstatic.com", // HMR connections - "connect-src 'self' http://localhost:5173 ws://localhost:5173 ws://localhost:8080 https://api.github.com", + "connect-src 'self' http://localhost:5173 ws://localhost:5173 ws://localhost:8080 https://api.github.com https://unpkg.com", "frame-ancestors 'none'", }, "; ")) } else { @@ -49,11 +49,11 @@ func SecurityHeaders(next http.Handler) http.Handler { "default-src 'self'", "base-uri 'self'", "form-action 'self'", - "script-src 'self' 'unsafe-inline'", + "script-src 'self' 'unsafe-inline' https://unpkg.com", "style-src 'self' 'unsafe-inline' https://fonts.googleapis.com", "img-src 'self' data: blob:", "font-src 'self' data: https://fonts.gstatic.com", - "connect-src 'self' ws://localhost:8080 https://api.github.com", + "connect-src 'self' ws://localhost:8080 https://api.github.com https://unpkg.com", "frame-ancestors 'none'", }, "; ")) } diff --git a/internal/api/routes.go b/internal/api/routes.go index 0f117fd..8d913e9 100644 --- a/internal/api/routes.go +++ b/internal/api/routes.go @@ -38,6 +38,7 @@ func NewRouter(db *gorm.DB, jobs *bg.Jobs, studio http.Handler) http.Handler { r.Use(SecurityHeaders) r.Use(requestBodyLimit(10 << 20)) r.Use(httprate.LimitByIP(100, 1*time.Minute)) + r.Use(middleware.StripSlashes) allowed := getAllowedOrigins() r.Use(cors.Handler(cors.Options{ @@ -103,18 +104,19 @@ func NewRouter(db *gorm.DB, jobs *bg.Jobs, studio http.Handler) http.Handler { mux := http.NewServeMux() mux.Handle("/api/", r) mux.Handle("/api", r) + mux.Handle("/swagger", r) mux.Handle("/swagger/", r) mux.Handle("/db-studio/", r) mux.Handle("/debug/pprof/", r) mux.Handle("/", proxy) return mux - } - - fmt.Println("Running in production mode") - if h, err := web.SPAHandler(); err == nil { - r.NotFound(h.ServeHTTP) } else { - log.Error().Err(err).Msg("spa handler init failed") + fmt.Println("Running in production mode") + if h, err := web.SPAHandler(); err == nil { + r.NotFound(h.ServeHTTP) + } else { + log.Error().Err(err).Msg("spa handler init failed") + } } return r diff --git a/internal/api/utils.go b/internal/api/utils.go index e9ba419..a906763 100644 --- a/internal/api/utils.go +++ b/internal/api/utils.go @@ -40,6 +40,7 @@ func serveSwaggerFromEmbed(data []byte, contentType string) http.HandlerFunc { return func(w http.ResponseWriter, _ *http.Request) { w.Header().Set("Content-Type", contentType) w.WriteHeader(http.StatusOK) + // nosemgrep: go.lang.security.audit.xss.no-direct-write-to-responsewriter _, _ = w.Write(data) } } diff --git a/internal/app/runtime.go b/internal/app/runtime.go index 673f5e5..952f239 100644 --- a/internal/app/runtime.go +++ b/internal/app/runtime.go @@ -39,10 +39,11 @@ func NewRuntime() *Runtime { &models.Label{}, &models.Annotation{}, &models.NodePool{}, - &models.Cluster{}, &models.Credential{}, &models.Domain{}, &models.RecordSet{}, + &models.LoadBalancer{}, + &models.Cluster{}, ) if err != nil { diff --git a/internal/bg/bastion.go b/internal/bg/bastion.go index d871e66..91ccbed 100644 --- a/internal/bg/bastion.go +++ b/internal/bg/bastion.go @@ -140,10 +140,7 @@ func BastionBootstrapWorker(db *gorm.DB, jobs *Jobs) archer.WorkerFn { _ = setServerStatus(db, s.ID, "failed") continue } - ok++ - // logHostInfo(jobID, s, "done", "host completed", - // "elapsed_ms", time.Since(hostStart).Milliseconds()) } res := BastionBootstrapResult{ diff --git a/internal/handlers/annotations.go b/internal/handlers/annotations.go index c797afe..ced4ca4 100644 --- a/internal/handlers/annotations.go +++ b/internal/handlers/annotations.go @@ -22,7 +22,6 @@ import ( // @Summary List annotations (org scoped) // @Description Returns annotations for the organization in X-Org-ID. Filters: `key`, `value`, and `q` (key contains). Add `include=node_pools` to include linked node pools. // @Tags Annotations -// @Accept json // @Produce json // @Param X-Org-ID header string false "Organization UUID" // @Param key query string false "Exact key" @@ -75,7 +74,6 @@ func ListAnnotations(db *gorm.DB) http.HandlerFunc { // @Summary Get annotation by ID (org scoped) // @Description Returns one annotation. Add `include=node_pools` to include node pools. // @Tags Annotations -// @Accept json // @Produce json // @Param X-Org-ID header string false "Organization UUID" // @Param id path string true "Annotation ID (UUID)" @@ -255,11 +253,10 @@ func UpdateAnnotation(db *gorm.DB) http.HandlerFunc { // @Summary Delete annotation (org scoped) // @Description Permanently deletes the annotation. // @Tags Annotations -// @Accept json // @Produce json -// @Param X-Org-ID header string false "Organization UUID" -// @Param id path string true "Annotation ID (UUID)" -// @Success 204 {string} string "No Content" +// @Param X-Org-ID header string false "Organization UUID" +// @Param id path string true "Annotation ID (UUID)" +// @Success 204 "No Content" // @Failure 400 {string} string "invalid id" // @Failure 401 {string} string "Unauthorized" // @Failure 403 {string} string "organization required" diff --git a/internal/handlers/auth.go b/internal/handlers/auth.go index bcb33aa..72bffef 100644 --- a/internal/handlers/auth.go +++ b/internal/handlers/auth.go @@ -2,7 +2,9 @@ package handlers import ( "context" + "encoding/base64" "encoding/json" + "html/template" "net/http" "net/url" "strings" @@ -252,10 +254,11 @@ func AuthCallback(db *gorm.DB) http.HandlerFunc { accessTTL := 1 * time.Hour refreshTTL := 30 * 24 * time.Hour + cfgLoaded, _ := config.Load() access, err := auth.IssueAccessToken(auth.IssueOpts{ Subject: user.ID.String(), - Issuer: cfg.JWTIssuer, - Audience: cfg.JWTAudience, + Issuer: cfgLoaded.JWTIssuer, + Audience: cfgLoaded.JWTAudience, TTL: accessTTL, Claims: map[string]any{ "email": email, @@ -273,7 +276,10 @@ func AuthCallback(db *gorm.DB) http.HandlerFunc { return } - secure := strings.HasPrefix(cfg.OAuthRedirectBase, "https://") + secure := true + if u, err := url.Parse(cfg.OAuthRedirectBase); err == nil && isLocalDev(u) { + secure = false + } if xf := r.Header.Get("X-Forwarded-Proto"); xf != "" { secure = strings.EqualFold(xf, "https") } @@ -291,14 +297,7 @@ func AuthCallback(db *gorm.DB) http.HandlerFunc { // If the state indicates SPA popup mode, postMessage tokens to the opener and close state := r.URL.Query().Get("state") if strings.Contains(state, "mode=spa") { - origin := "" - for _, part := range strings.Split(state, "|") { - if strings.HasPrefix(part, "origin=") { - origin, _ = url.QueryUnescape(strings.TrimPrefix(part, "origin=")) - break - } - } - // fallback: restrict to backend origin if none supplied + origin := canonicalOrigin(cfg.OAuthRedirectBase) if origin == "" { origin = cfg.OAuthRedirectBase } @@ -371,7 +370,10 @@ func Refresh(db *gorm.DB) http.HandlerFunc { return } - secure := strings.HasPrefix(cfg.OAuthRedirectBase, "https://") + secure := true + if uParsed, err := url.Parse(cfg.OAuthRedirectBase); err == nil && isLocalDev(uParsed) { + secure = false + } if xf := r.Header.Get("X-Forwarded-Proto"); xf != "" { secure = strings.EqualFold(xf, "https") } @@ -424,6 +426,11 @@ func Logout(db *gorm.DB) http.HandlerFunc { } clearCookie: + secure := true + if uParsed, err := url.Parse(cfg.OAuthRedirectBase); err == nil && isLocalDev(uParsed) { + secure = false + } + http.SetCookie(w, &http.Cookie{ Name: "ag_jwt", Value: "", @@ -432,11 +439,10 @@ func Logout(db *gorm.DB) http.HandlerFunc { MaxAge: -1, Expires: time.Unix(0, 0), SameSite: http.SameSiteLaxMode, - Secure: strings.HasPrefix(cfg.OAuthRedirectBase, "https"), + Secure: secure, }) w.WriteHeader(204) - } } @@ -506,21 +512,63 @@ func ensureAutoMembership(db *gorm.DB, userID uuid.UUID, email string) error { }).Error } +// postMessage HTML template +var postMessageTpl = template.Must(template.New("postmsg").Parse(` + + + + +`)) + +type postMessageData struct { + Origin string + PayloadB64 string +} + // writePostMessageHTML sends a tiny HTML page that posts tokens to the SPA and closes the window. func writePostMessageHTML(w http.ResponseWriter, origin string, payload dto.TokenPair) { b, _ := json.Marshal(payload) + + data := postMessageData{ + Origin: origin, + PayloadB64: base64.StdEncoding.EncodeToString(b), + } + w.Header().Set("Content-Type", "text/html; charset=utf-8") w.Header().Set("Cache-Control", "no-store") w.WriteHeader(http.StatusOK) - _, _ = w.Write([]byte(``)) + _ = postMessageTpl.Execute(w, data) +} + +// canonicalOrigin returns scheme://host[:port] for a given URL, or "" if invalid. +func canonicalOrigin(raw string) string { + u, err := url.Parse(raw) + if err != nil || u.Scheme == "" || u.Host == "" { + return "" + } + + // Normalize: no path/query/fragment — just the origin. + return (&url.URL{ + Scheme: u.Scheme, + Host: u.Host, + }).String() +} + +func isLocalDev(u *url.URL) bool { + host := strings.ToLower(u.Hostname()) + return u.Scheme == "http" && + (host == "localhost" || host == "127.0.0.1") } diff --git a/internal/handlers/clusters.go b/internal/handlers/clusters.go index 86619e4..cc8f276 100644 --- a/internal/handlers/clusters.go +++ b/internal/handlers/clusters.go @@ -1,6 +1,11 @@ package handlers import ( + "crypto/rand" + "encoding/hex" + "encoding/json" + "errors" + "fmt" "net/http" "strings" "time" @@ -10,6 +15,8 @@ import ( "github.com/glueops/autoglue/internal/handlers/dto" "github.com/glueops/autoglue/internal/models" "github.com/glueops/autoglue/internal/utils" + "github.com/go-chi/chi/v5" + "github.com/google/uuid" "gorm.io/gorm" ) @@ -45,13 +52,16 @@ func ListClusters(db *gorm.DB) http.HandlerFunc { var rows []models.Cluster if err := q. + Preload("CaptainDomain"). + Preload("ControlPlaneRecordSet"). + Preload("AppsLoadBalancer"). + Preload("GlueOpsLoadBalancer"). + Preload("BastionServer"). Preload("NodePools"). Preload("NodePools.Labels"). Preload("NodePools.Annotations"). - Preload("NodePools.Labels"). Preload("NodePools.Taints"). Preload("NodePools.Servers"). - Preload("BastionServer"). Find(&rows).Error; err != nil { utils.WriteError(w, http.StatusInternalServerError, "db_error", "db error") return @@ -61,21 +71,80 @@ func ListClusters(db *gorm.DB) http.HandlerFunc { for _, row := range rows { out = append(out, clusterToDTO(row)) } - + utils.WriteJSON(w, http.StatusOK, out) } +} +// GetCluster godoc +// +// @ID GetCluster +// @Summary Get a single cluster by ID (org scoped) +// @Description Returns a cluster with all related resources (domain, record set, load balancers, bastion, node pools). +// @Tags Clusters +// @Produce json +// @Param X-Org-ID header string false "Organization UUID" +// @Param clusterID path string true "Cluster ID" +// @Success 200 {object} dto.ClusterResponse +// @Failure 400 {string} string "bad request" +// @Failure 401 {string} string "Unauthorized" +// @Failure 403 {string} string "organization required" +// @Failure 404 {string} string "cluster not found" +// @Failure 500 {string} string "db error" +// @Router /clusters/{clusterID} [get] +// @Security BearerAuth +// @Security OrgKeyAuth +// @Security OrgSecretAuth +func GetCluster(db *gorm.DB) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + orgID, ok := httpmiddleware.OrgIDFrom(r.Context()) + if !ok { + utils.WriteError(w, http.StatusForbidden, "org_required", "specify X-Org-ID") + return + } + + clusterID, err := uuid.Parse(chi.URLParam(r, "clusterID")) + if err != nil { + utils.WriteError(w, http.StatusBadRequest, "bad_cluster_id", "invalid cluster id") + return + } + + var cluster models.Cluster + if err := db. + Where("id = ? AND organization_id = ?", clusterID, orgID). + Preload("CaptainDomain"). + Preload("ControlPlaneRecordSet"). + Preload("AppsLoadBalancer"). + Preload("GlueOpsLoadBalancer"). + Preload("BastionServer"). + Preload("NodePools"). + Preload("NodePools.Labels"). + Preload("NodePools.Annotations"). + Preload("NodePools.Taints"). + Preload("NodePools.Servers"). + First(&cluster).Error; err != nil { + + if errors.Is(err, gorm.ErrRecordNotFound) { + utils.WriteError(w, http.StatusNotFound, "not_found", "cluster not found") + return + } + utils.WriteError(w, http.StatusInternalServerError, "db_error", "db error") + return + } + + utils.WriteJSON(w, http.StatusOK, clusterToDTO(cluster)) + } } // CreateCluster godoc // // @ID CreateCluster // @Summary Create cluster (org scoped) -// @Description Creates a cluster. If `kubeconfig` is provided, it will be encrypted per-organization and stored securely (never returned). +// @Description Creates a cluster. Status is managed by the system and starts as `pre_pending` for validation. // @Tags Clusters // @Accept json // @Produce json -// @Param X-Org-ID header string false "Organization UUID" -// @Param body body dto.CreateClusterRequest true "payload" +// @Param X-Org-ID header string false "Organization UUID" +// @Param body body dto.CreateClusterRequest true "payload" // @Success 201 {object} dto.ClusterResponse // @Failure 400 {string} string "invalid json" // @Failure 401 {string} string "Unauthorized" @@ -87,10 +156,1144 @@ func ListClusters(db *gorm.DB) http.HandlerFunc { // @Security OrgSecretAuth func CreateCluster(db *gorm.DB) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { + orgID, ok := httpmiddleware.OrgIDFrom(r.Context()) + if !ok { + utils.WriteError(w, http.StatusForbidden, "org_required", "specify X-Org-ID") + return + } + + var in dto.CreateClusterRequest + if err := json.NewDecoder(r.Body).Decode(&in); err != nil { + utils.WriteError(w, http.StatusBadRequest, "bad_json", err.Error()) + return + } + + certificateKey, err := GenerateSecureHex(32) + if err != nil { + utils.WriteError(w, http.StatusInternalServerError, "internal_error", "failed to generate certificate key") + return + } + + randomToken, err := GenerateFormattedToken() + if err != nil { + utils.WriteError(w, http.StatusInternalServerError, "internal_error", "failed to generate random token") + return + } + + c := models.Cluster{ + OrganizationID: orgID, + Name: in.Name, + Provider: in.Provider, + Region: in.Region, + Status: models.ClusterStatusPrePending, + LastError: "", + CertificateKey: certificateKey, + RandomToken: randomToken, + } + + if err := db.Create(&c).Error; err != nil { + utils.WriteError(w, http.StatusInternalServerError, "db_error", "db error") + return + } + + utils.WriteJSON(w, http.StatusCreated, clusterToDTO(c)) + } +} + +// UpdateCluster godoc +// +// @ID UpdateCluster +// @Summary Update basic cluster details (org scoped) +// @Description Updates the cluster name, provider, and/or region. Status is managed by the system. +// @Tags Clusters +// @Accept json +// @Produce json +// @Param X-Org-ID header string false "Organization UUID" +// @Param clusterID path string true "Cluster ID" +// @Param body body dto.UpdateClusterRequest true "payload" +// @Success 200 {object} dto.ClusterResponse +// @Failure 400 {string} string "bad request" +// @Failure 401 {string} string "Unauthorized" +// @Failure 403 {string} string "organization required" +// @Failure 404 {string} string "cluster not found" +// @Failure 500 {string} string "db error" +// @Router /clusters/{clusterID} [patch] +// @Security BearerAuth +// @Security OrgKeyAuth +// @Security OrgSecretAuth +func UpdateCluster(db *gorm.DB) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + orgID, ok := httpmiddleware.OrgIDFrom(r.Context()) + if !ok { + utils.WriteError(w, http.StatusForbidden, "org_required", "specify X-Org-ID") + return + } + + clusterID, err := uuid.Parse(chi.URLParam(r, "clusterID")) + if err != nil { + utils.WriteError(w, http.StatusBadRequest, "bad_cluster_id", "invalid cluster id") + return + } + + var in dto.UpdateClusterRequest + if err := json.NewDecoder(r.Body).Decode(&in); err != nil { + utils.WriteError(w, http.StatusBadRequest, "bad_json", err.Error()) + return + } + + var cluster models.Cluster + if err := db.Where("id = ? AND organization_id = ?", clusterID, orgID).First(&cluster).Error; err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + utils.WriteError(w, http.StatusNotFound, "not_found", "cluster not found") + return + } + utils.WriteError(w, http.StatusInternalServerError, "db_error", "db error") + return + } + + // Apply only provided fields + if in.Name != nil { + cluster.Name = *in.Name + } + if in.Provider != nil { + cluster.Provider = *in.Provider + } + if in.Region != nil { + cluster.Region = *in.Region + } + + if err := db.Save(&cluster).Error; err != nil { + utils.WriteError(w, http.StatusInternalServerError, "db_error", "db error") + return + } + + // Any change to the cluster config may require re-validation. + _ = markClusterNeedsValidation(db, cluster.ID) + + // Preload for a rich response + if err := db.Preload("CaptainDomain"). + Preload("ControlPlaneRecordSet"). + Preload("AppsLoadBalancer"). + Preload("GlueOpsLoadBalancer"). + Preload("BastionServer"). + Preload("NodePools"). + Preload("NodePools.Labels"). + Preload("NodePools.Annotations"). + Preload("NodePools.Taints"). + Preload("NodePools.Servers"). + First(&cluster, "id = ?", cluster.ID).Error; err != nil { + utils.WriteError(w, http.StatusInternalServerError, "db_error", "db error") + return + } + + utils.WriteJSON(w, http.StatusOK, clusterToDTO(cluster)) + } +} + +// DeleteCluster godoc +// +// @ID DeleteCluster +// @Summary Delete a cluster (org scoped) +// @Description Deletes the cluster. Related resources are cleaned up via DB constraints (e.g. CASCADE). +// @Tags Clusters +// @Produce json +// @Param X-Org-ID header string false "Organization UUID" +// @Param clusterID path string true "Cluster ID" +// @Success 204 {string} string "deleted" +// @Failure 400 {string} string "bad request" +// @Failure 401 {string} string "Unauthorized" +// @Failure 403 {string} string "organization required" +// @Failure 404 {string} string "cluster not found" +// @Failure 500 {string} string "db error" +// @Router /clusters/{clusterID} [delete] +// @Security BearerAuth +// @Security OrgKeyAuth +// @Security OrgSecretAuth +func DeleteCluster(db *gorm.DB) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + orgID, ok := httpmiddleware.OrgIDFrom(r.Context()) + if !ok { + utils.WriteError(w, http.StatusForbidden, "org_required", "specify X-Org-ID") + return + } + + clusterID, err := uuid.Parse(chi.URLParam(r, "clusterID")) + if err != nil { + utils.WriteError(w, http.StatusBadRequest, "bad_cluster_id", "invalid cluster id") + return + } + + tx := db.Where("id = ? AND organization_id = ?", clusterID, orgID).Delete(&models.Cluster{}) + if tx.Error != nil { + utils.WriteError(w, http.StatusInternalServerError, "db_error", "db error") + return + } + if tx.RowsAffected == 0 { + utils.WriteError(w, http.StatusNotFound, "not_found", "cluster not found") + return + } + w.WriteHeader(http.StatusNoContent) } } +// AttachCaptainDomain godoc +// +// @ID AttachCaptainDomain +// @Summary Attach a captain domain to a cluster +// @Description Sets captain_domain_id on the cluster. Validation of shape happens asynchronously. +// @Tags Clusters +// @Accept json +// @Produce json +// @Param X-Org-ID header string false "Organization UUID" +// @Param clusterID path string true "Cluster ID" +// @Param body body dto.AttachCaptainDomainRequest true "payload" +// @Success 200 {object} dto.ClusterResponse +// @Failure 400 {string} string "bad request" +// @Failure 401 {string} string "Unauthorized" +// @Failure 403 {string} string "organization required" +// @Failure 404 {string} string "cluster or domain not found" +// @Failure 500 {string} string "db error" +// @Router /clusters/{clusterID}/captain-domain [post] +// @Security BearerAuth +// @Security OrgKeyAuth +// @Security OrgSecretAuth +func AttachCaptainDomain(db *gorm.DB) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + orgID, ok := httpmiddleware.OrgIDFrom(r.Context()) + if !ok { + utils.WriteError(w, http.StatusForbidden, "org_required", "specify X-Org-ID") + return + } + + clusterID, err := uuid.Parse(chi.URLParam(r, "clusterID")) + if err != nil { + utils.WriteError(w, http.StatusBadRequest, "bad_cluster_id", "invalid cluster id") + return + } + + var in dto.AttachCaptainDomainRequest + if err := json.NewDecoder(r.Body).Decode(&in); err != nil { + utils.WriteError(w, http.StatusBadRequest, "bad_json", err.Error()) + return + } + + var cluster models.Cluster + if err := db.Where("id = ? AND organization_id = ?", clusterID, orgID).First(&cluster).Error; err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + utils.WriteError(w, http.StatusNotFound, "not_found", "cluster not found") + return + } + utils.WriteError(w, http.StatusInternalServerError, "db_error", "db error") + return + } + + // Ensure domain exists and belongs to the org + var domain models.Domain + if err := db.Where("id = ? AND organization_id = ?", in.DomainID, orgID).First(&domain).Error; err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + utils.WriteError(w, http.StatusNotFound, "domain_not_found", "domain not found for organization") + return + } + utils.WriteError(w, http.StatusInternalServerError, "db_error", "db error") + return + } + + cluster.CaptainDomainID = &domain.ID + if err := db.Save(&cluster).Error; err != nil { + utils.WriteError(w, http.StatusInternalServerError, "db_error", "db error") + return + } + + if err := markClusterNeedsValidation(db, cluster.ID); err != nil { + // Don't fail the request, just log if you have logging. + } + + // Preload domain for response + if err := db.Preload("CaptainDomain"). + Preload("ControlPlaneRecordSet"). + Preload("AppsLoadBalancer"). + Preload("GlueOpsLoadBalancer"). + Preload("BastionServer"). + Preload("NodePools"). + Preload("NodePools.Labels"). + Preload("NodePools.Annotations"). + Preload("NodePools.Taints"). + Preload("NodePools.Servers"). + First(&cluster, "id = ?", cluster.ID).Error; err != nil { + utils.WriteError(w, http.StatusInternalServerError, "db_error", "db error") + return + } + utils.WriteJSON(w, http.StatusOK, clusterToDTO(cluster)) + } +} + +// DetachCaptainDomain godoc +// +// @ID DetachCaptainDomain +// @Summary Detach the captain domain from a cluster +// @Description Clears captain_domain_id on the cluster. This will likely cause the cluster to become incomplete. +// @Tags Clusters +// @Produce json +// @Param X-Org-ID header string false "Organization UUID" +// @Param clusterID path string true "Cluster ID" +// @Success 200 {object} dto.ClusterResponse +// @Failure 400 {string} string "bad request" +// @Failure 401 {string} string "Unauthorized" +// @Failure 403 {string} string "organization required" +// @Failure 404 {string} string "cluster not found" +// @Failure 500 {string} string "db error" +// @Router /clusters/{clusterID}/captain-domain [delete] +// @Security BearerAuth +// @Security OrgKeyAuth +// @Security OrgSecretAuth +func DetachCaptainDomain(db *gorm.DB) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + orgID, ok := httpmiddleware.OrgIDFrom(r.Context()) + if !ok { + utils.WriteError(w, http.StatusForbidden, "org_required", "specify X-Org-ID") + return + } + + clusterID, err := uuid.Parse(chi.URLParam(r, "clusterID")) + if err != nil { + utils.WriteError(w, http.StatusBadRequest, "bad_cluster_id", "invalid cluster id") + return + } + + var cluster models.Cluster + if err := db.Where("id = ? AND organization_id = ?", clusterID, orgID).First(&cluster).Error; err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + utils.WriteError(w, http.StatusNotFound, "not_found", "cluster not found") + return + } + utils.WriteError(w, http.StatusInternalServerError, "db_error", "db error") + return + } + + cluster.CaptainDomainID = nil + if err := db.Save(&cluster).Error; err != nil { + utils.WriteError(w, http.StatusInternalServerError, "db_error", "db error") + return + } + + _ = markClusterNeedsValidation(db, cluster.ID) + + if err := db.Preload("CaptainDomain"). + Preload("ControlPlaneRecordSet"). + Preload("AppsLoadBalancer"). + Preload("GlueOpsLoadBalancer"). + Preload("BastionServer"). + Preload("NodePools"). + Preload("NodePools.Labels"). + Preload("NodePools.Annotations"). + Preload("NodePools.Taints"). + Preload("NodePools.Servers"). + First(&cluster, "id = ?", cluster.ID).Error; err != nil { + utils.WriteError(w, http.StatusInternalServerError, "db_error", "db error") + return + } + + utils.WriteJSON(w, http.StatusOK, clusterToDTO(cluster)) + } +} + +// AttachControlPlaneRecordSet godoc +// +// @ID AttachControlPlaneRecordSet +// @Summary Attach a control plane record set to a cluster +// @Description Sets control_plane_record_set_id on the cluster. +// @Tags Clusters +// @Accept json +// @Produce json +// @Param X-Org-ID header string false "Organization UUID" +// @Param clusterID path string true "Cluster ID" +// @Param body body dto.AttachRecordSetRequest true "payload" +// @Success 200 {object} dto.ClusterResponse +// @Failure 400 {string} string "bad request" +// @Failure 401 {string} string "Unauthorized" +// @Failure 403 {string} string "organization required" +// @Failure 404 {string} string "cluster or record set not found" +// @Failure 500 {string} string "db error" +// @Router /clusters/{clusterID}/control-plane-record-set [post] +// @Security BearerAuth +// @Security OrgKeyAuth +// @Security OrgSecretAuth +func AttachControlPlaneRecordSet(db *gorm.DB) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + orgID, ok := httpmiddleware.OrgIDFrom(r.Context()) + if !ok { + utils.WriteError(w, http.StatusForbidden, "org_required", "specify X-Org-ID") + return + } + + clusterID, err := uuid.Parse(chi.URLParam(r, "clusterID")) + if err != nil { + utils.WriteError(w, http.StatusBadRequest, "bad_cluster_id", "invalid cluster id") + return + } + + var in dto.AttachRecordSetRequest + if err := json.NewDecoder(r.Body).Decode(&in); err != nil { + utils.WriteError(w, http.StatusBadRequest, "bad_json", err.Error()) + return + } + + var cluster models.Cluster + if err := db.Where("id = ? AND organization_id = ?", clusterID, orgID).First(&cluster).Error; err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + utils.WriteError(w, http.StatusNotFound, "not_found", "cluster not found") + return + } + utils.WriteError(w, http.StatusInternalServerError, "db_error", "db error") + return + } + + // record sets are indirectly org-scoped via their domain + var rs models.RecordSet + if err := db. + Joins("JOIN domains d ON d.id = record_sets.domain_id"). + Where("record_sets.id = ? AND d.organization_id = ?", in.RecordSetID, orgID). + First(&rs).Error; err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + utils.WriteError(w, http.StatusNotFound, "recordset_not_found", "record set not found for organization") + return + } + utils.WriteError(w, http.StatusInternalServerError, "db_error", "db error") + return + } + + cluster.ControlPlaneRecordSetID = &rs.ID + if err := db.Save(&cluster).Error; err != nil { + utils.WriteError(w, http.StatusInternalServerError, "db_error", "db error") + return + } + + _ = markClusterNeedsValidation(db, cluster.ID) + + if err := db.Preload("CaptainDomain"). + Preload("ControlPlaneRecordSet"). + Preload("AppsLoadBalancer"). + Preload("GlueOpsLoadBalancer"). + Preload("BastionServer"). + Preload("NodePools"). + Preload("NodePools.Labels"). + Preload("NodePools.Annotations"). + Preload("NodePools.Taints"). + Preload("NodePools.Servers"). + First(&cluster, "id = ?", cluster.ID).Error; err != nil { + utils.WriteError(w, http.StatusInternalServerError, "db_error", "db error") + return + } + + utils.WriteJSON(w, http.StatusOK, clusterToDTO(cluster)) + } +} + +// DetachControlPlaneRecordSet godoc +// +// @ID DetachControlPlaneRecordSet +// @Summary Detach the control plane record set from a cluster +// @Description Clears control_plane_record_set_id on the cluster. +// @Tags Clusters +// @Produce json +// @Param X-Org-ID header string false "Organization UUID" +// @Param clusterID path string true "Cluster ID" +// @Success 200 {object} dto.ClusterResponse +// @Failure 400 {string} string "bad request" +// @Failure 401 {string} string "Unauthorized" +// @Failure 403 {string} string "organization required" +// @Failure 404 {string} string "cluster not found" +// @Failure 500 {string} string "db error" +// @Router /clusters/{clusterID}/control-plane-record-set [delete] +// @Security BearerAuth +// @Security OrgKeyAuth +// @Security OrgSecretAuth +func DetachControlPlaneRecordSet(db *gorm.DB) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + orgID, ok := httpmiddleware.OrgIDFrom(r.Context()) + if !ok { + utils.WriteError(w, http.StatusForbidden, "org_required", "specify X-Org-ID") + return + } + + clusterID, err := uuid.Parse(chi.URLParam(r, "clusterID")) + if err != nil { + utils.WriteError(w, http.StatusBadRequest, "bad_cluster_id", "invalid cluster id") + return + } + + var cluster models.Cluster + if err := db.Where("id = ? AND organization_id = ?", clusterID, orgID).First(&cluster).Error; err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + utils.WriteError(w, http.StatusNotFound, "not_found", "cluster not found") + return + } + utils.WriteError(w, http.StatusInternalServerError, "db_error", "db error") + return + } + + cluster.ControlPlaneRecordSetID = nil + if err := db.Save(&cluster).Error; err != nil { + utils.WriteError(w, http.StatusInternalServerError, "db_error", "db error") + return + } + + _ = markClusterNeedsValidation(db, cluster.ID) + + if err := db.Preload("CaptainDomain"). + Preload("ControlPlaneRecordSet"). + Preload("AppsLoadBalancer"). + Preload("GlueOpsLoadBalancer"). + Preload("BastionServer"). + Preload("NodePools"). + Preload("NodePools.Labels"). + Preload("NodePools.Annotations"). + Preload("NodePools.Taints"). + Preload("NodePools.Servers"). + First(&cluster, "id = ?", cluster.ID).Error; err != nil { + utils.WriteError(w, http.StatusInternalServerError, "db_error", "db error") + return + } + + utils.WriteJSON(w, http.StatusOK, clusterToDTO(cluster)) + } +} + +// AttachAppsLoadBalancer godoc +// +// @ID AttachAppsLoadBalancer +// @Summary Attach an apps load balancer to a cluster +// @Description Sets apps_load_balancer_id on the cluster. +// @Tags Clusters +// @Accept json +// @Produce json +// @Param X-Org-ID header string false "Organization UUID" +// @Param clusterID path string true "Cluster ID" +// @Param body body dto.AttachLoadBalancerRequest true "payload" +// @Success 200 {object} dto.ClusterResponse +// @Failure 400 {string} string "bad request" +// @Failure 401 {string} string "Unauthorized" +// @Failure 403 {string} string "organization required" +// @Failure 404 {string} string "cluster or load balancer not found" +// @Failure 500 {string} string "db error" +// @Router /clusters/{clusterID}/apps-load-balancer [post] +// @Security BearerAuth +// @Security OrgKeyAuth +// @Security OrgSecretAuth +func AttachAppsLoadBalancer(db *gorm.DB) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + orgID, ok := httpmiddleware.OrgIDFrom(r.Context()) + if !ok { + utils.WriteError(w, http.StatusForbidden, "org_required", "specify X-Org-ID") + return + } + + clusterID, err := uuid.Parse(chi.URLParam(r, "clusterID")) + if err != nil { + utils.WriteError(w, http.StatusBadRequest, "bad_cluster_id", "invalid cluster id") + return + } + + var in dto.AttachLoadBalancerRequest + if err := json.NewDecoder(r.Body).Decode(&in); err != nil { + utils.WriteError(w, http.StatusBadRequest, "bad_json", err.Error()) + return + } + + var cluster models.Cluster + if err := db.Where("id = ? AND organization_id = ?", clusterID, orgID).First(&cluster).Error; err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + utils.WriteError(w, http.StatusNotFound, "not_found", "cluster not found") + return + } + utils.WriteError(w, http.StatusInternalServerError, "db_error", "db error") + return + } + + var lb models.LoadBalancer + if err := db.Where("id = ? AND organization_id = ?", in.LoadBalancerID, orgID).First(&lb).Error; err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + utils.WriteError(w, http.StatusNotFound, "lb_not_found", "load balancer not found for organization") + return + } + utils.WriteError(w, http.StatusInternalServerError, "db_error", "db error") + return + } + + cluster.AppsLoadBalancerID = &lb.ID + if err := db.Save(&cluster).Error; err != nil { + utils.WriteError(w, http.StatusInternalServerError, "db_error", "db error") + return + } + + _ = markClusterNeedsValidation(db, cluster.ID) + + if err := db.Preload("CaptainDomain"). + Preload("ControlPlaneRecordSet"). + Preload("AppsLoadBalancer"). + Preload("GlueOpsLoadBalancer"). + Preload("BastionServer"). + Preload("NodePools"). + Preload("NodePools.Labels"). + Preload("NodePools.Annotations"). + Preload("NodePools.Taints"). + Preload("NodePools.Servers"). + First(&cluster, "id = ?", cluster.ID).Error; err != nil { + utils.WriteError(w, http.StatusInternalServerError, "db_error", "db error") + return + } + + utils.WriteJSON(w, http.StatusOK, clusterToDTO(cluster)) + } +} + +// DetachAppsLoadBalancer godoc +// +// @ID DetachAppsLoadBalancer +// @Summary Detach the apps load balancer from a cluster +// @Description Clears apps_load_balancer_id on the cluster. +// @Tags Clusters +// @Produce json +// @Param X-Org-ID header string false "Organization UUID" +// @Param clusterID path string true "Cluster ID" +// @Success 200 {object} dto.ClusterResponse +// @Failure 400 {string} string "bad request" +// @Failure 401 {string} string "Unauthorized" +// @Failure 403 {string} string "organization required" +// @Failure 404 {string} string "cluster not found" +// @Failure 500 {string} string "db error" +// @Router /clusters/{clusterID}/apps-load-balancer [delete] +// @Security BearerAuth +// @Security OrgKeyAuth +// @Security OrgSecretAuth +func DetachAppsLoadBalancer(db *gorm.DB) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + orgID, ok := httpmiddleware.OrgIDFrom(r.Context()) + if !ok { + utils.WriteError(w, http.StatusForbidden, "org_required", "specify X-Org-ID") + return + } + + clusterID, err := uuid.Parse(chi.URLParam(r, "clusterID")) + if err != nil { + utils.WriteError(w, http.StatusBadRequest, "bad_cluster_id", "invalid cluster id") + return + } + + var cluster models.Cluster + if err := db.Where("id = ? AND organization_id = ?", clusterID, orgID).First(&cluster).Error; err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + utils.WriteError(w, http.StatusNotFound, "not_found", "cluster not found") + return + } + utils.WriteError(w, http.StatusInternalServerError, "db_error", "db error") + return + } + + cluster.AppsLoadBalancerID = nil + if err := db.Save(&cluster).Error; err != nil { + utils.WriteError(w, http.StatusInternalServerError, "db_error", "db error") + return + } + + _ = markClusterNeedsValidation(db, cluster.ID) + + if err := db.Preload("CaptainDomain"). + Preload("ControlPlaneRecordSet"). + Preload("AppsLoadBalancer"). + Preload("GlueOpsLoadBalancer"). + Preload("BastionServer"). + Preload("NodePools"). + Preload("NodePools.Labels"). + Preload("NodePools.Annotations"). + Preload("NodePools.Taints"). + Preload("NodePools.Servers"). + First(&cluster, "id = ?", cluster.ID).Error; err != nil { + utils.WriteError(w, http.StatusInternalServerError, "db_error", "db error") + return + } + + utils.WriteJSON(w, http.StatusOK, clusterToDTO(cluster)) + } +} + +// AttachGlueOpsLoadBalancer godoc +// +// @ID AttachGlueOpsLoadBalancer +// @Summary Attach a GlueOps/control-plane load balancer to a cluster +// @Description Sets glueops_load_balancer_id on the cluster. +// @Tags Clusters +// @Accept json +// @Produce json +// @Param X-Org-ID header string false "Organization UUID" +// @Param clusterID path string true "Cluster ID" +// @Param body body dto.AttachLoadBalancerRequest true "payload" +// @Success 200 {object} dto.ClusterResponse +// @Failure 400 {string} string "bad request" +// @Failure 401 {string} string "Unauthorized" +// @Failure 403 {string} string "organization required" +// @Failure 404 {string} string "cluster or load balancer not found" +// @Failure 500 {string} string "db error" +// @Router /clusters/{clusterID}/glueops-load-balancer [post] +// @Security BearerAuth +// @Security OrgKeyAuth +// @Security OrgSecretAuth +func AttachGlueOpsLoadBalancer(db *gorm.DB) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + orgID, ok := httpmiddleware.OrgIDFrom(r.Context()) + if !ok { + utils.WriteError(w, http.StatusForbidden, "org_required", "specify X-Org-ID") + return + } + + clusterID, err := uuid.Parse(chi.URLParam(r, "clusterID")) + if err != nil { + utils.WriteError(w, http.StatusBadRequest, "bad_cluster_id", "invalid cluster id") + return + } + + var in dto.AttachLoadBalancerRequest + if err := json.NewDecoder(r.Body).Decode(&in); err != nil { + utils.WriteError(w, http.StatusBadRequest, "bad_json", err.Error()) + return + } + + var cluster models.Cluster + if err := db.Where("id = ? AND organization_id = ?", clusterID, orgID).First(&cluster).Error; err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + utils.WriteError(w, http.StatusNotFound, "not_found", "cluster not found") + return + } + utils.WriteError(w, http.StatusInternalServerError, "db_error", "db error") + return + } + + var lb models.LoadBalancer + if err := db.Where("id = ? AND organization_id = ?", in.LoadBalancerID, orgID).First(&lb).Error; err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + utils.WriteError(w, http.StatusNotFound, "lb_not_found", "load balancer not found for organization") + return + } + utils.WriteError(w, http.StatusInternalServerError, "db_error", "db error") + return + } + + cluster.GlueOpsLoadBalancerID = &lb.ID + if err := db.Save(&cluster).Error; err != nil { + utils.WriteError(w, http.StatusInternalServerError, "db_error", "db error") + return + } + + _ = markClusterNeedsValidation(db, cluster.ID) + + if err := db.Preload("CaptainDomain"). + Preload("ControlPlaneRecordSet"). + Preload("AppsLoadBalancer"). + Preload("GlueOpsLoadBalancer"). + Preload("BastionServer"). + Preload("NodePools"). + Preload("NodePools.Labels"). + Preload("NodePools.Annotations"). + Preload("NodePools.Taints"). + Preload("NodePools.Servers"). + First(&cluster, "id = ?", cluster.ID).Error; err != nil { + utils.WriteError(w, http.StatusInternalServerError, "db_error", "db error") + return + } + + utils.WriteJSON(w, http.StatusOK, clusterToDTO(cluster)) + } +} + +// DetachGlueOpsLoadBalancer godoc +// +// @ID DetachGlueOpsLoadBalancer +// @Summary Detach the GlueOps/control-plane load balancer from a cluster +// @Description Clears glueops_load_balancer_id on the cluster. +// @Tags Clusters +// @Produce json +// @Param X-Org-ID header string false "Organization UUID" +// @Param clusterID path string true "Cluster ID" +// @Success 200 {object} dto.ClusterResponse +// @Failure 400 {string} string "bad request" +// @Failure 401 {string} string "Unauthorized" +// @Failure 403 {string} string "organization required" +// @Failure 404 {string} string "cluster not found" +// @Failure 500 {string} string "db error" +// @Router /clusters/{clusterID}/glueops-load-balancer [delete] +// @Security BearerAuth +// @Security OrgKeyAuth +// @Security OrgSecretAuth +func DetachGlueOpsLoadBalancer(db *gorm.DB) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + orgID, ok := httpmiddleware.OrgIDFrom(r.Context()) + if !ok { + utils.WriteError(w, http.StatusForbidden, "org_required", "specify X-Org-ID") + return + } + + clusterID, err := uuid.Parse(chi.URLParam(r, "clusterID")) + if err != nil { + utils.WriteError(w, http.StatusBadRequest, "bad_cluster_id", "invalid cluster id") + return + } + + var cluster models.Cluster + if err := db.Where("id = ? AND organization_id = ?", clusterID, orgID).First(&cluster).Error; err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + utils.WriteError(w, http.StatusNotFound, "not_found", "cluster not found") + return + } + utils.WriteError(w, http.StatusInternalServerError, "db_error", "db error") + return + } + + cluster.GlueOpsLoadBalancerID = nil + if err := db.Save(&cluster).Error; err != nil { + utils.WriteError(w, http.StatusInternalServerError, "db_error", "db error") + return + } + + _ = markClusterNeedsValidation(db, cluster.ID) + + if err := db.Preload("CaptainDomain"). + Preload("ControlPlaneRecordSet"). + Preload("AppsLoadBalancer"). + Preload("GlueOpsLoadBalancer"). + Preload("BastionServer"). + Preload("NodePools"). + Preload("NodePools.Labels"). + Preload("NodePools.Annotations"). + Preload("NodePools.Taints"). + Preload("NodePools.Servers"). + First(&cluster, "id = ?", cluster.ID).Error; err != nil { + utils.WriteError(w, http.StatusInternalServerError, "db_error", "db error") + return + } + + utils.WriteJSON(w, http.StatusOK, clusterToDTO(cluster)) + } +} + +// AttachBastionServer godoc +// +// @ID AttachBastionServer +// @Summary Attach a bastion server to a cluster +// @Description Sets bastion_server_id on the cluster. +// @Tags Clusters +// @Accept json +// @Produce json +// @Param X-Org-ID header string false "Organization UUID" +// @Param clusterID path string true "Cluster ID" +// @Param body body dto.AttachBastionRequest true "payload" +// @Success 200 {object} dto.ClusterResponse +// @Failure 400 {string} string "bad request" +// @Failure 401 {string} string "Unauthorized" +// @Failure 403 {string} string "organization required" +// @Failure 404 {string} string "cluster or server not found" +// @Failure 500 {string} string "db error" +// @Router /clusters/{clusterID}/bastion [post] +// @Security BearerAuth +// @Security OrgKeyAuth +// @Security OrgSecretAuth +func AttachBastionServer(db *gorm.DB) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + orgID, ok := httpmiddleware.OrgIDFrom(r.Context()) + if !ok { + utils.WriteError(w, http.StatusForbidden, "org_required", "specify X-Org-ID") + return + } + + clusterID, err := uuid.Parse(chi.URLParam(r, "clusterID")) + if err != nil { + utils.WriteError(w, http.StatusBadRequest, "bad_cluster_id", "invalid cluster id") + return + } + + var in dto.AttachBastionRequest + if err := json.NewDecoder(r.Body).Decode(&in); err != nil { + utils.WriteError(w, http.StatusBadRequest, "bad_json", err.Error()) + return + } + + var cluster models.Cluster + if err := db.Where("id = ? AND organization_id = ?", clusterID, orgID).First(&cluster).Error; err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + utils.WriteError(w, http.StatusNotFound, "not_found", "cluster not found") + return + } + utils.WriteError(w, http.StatusInternalServerError, "db_error", "db error") + return + } + + var server models.Server + if err := db.Where("id = ? AND organization_id = ?", in.ServerID, orgID).First(&server).Error; err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + utils.WriteError(w, http.StatusNotFound, "server_not_found", "server not found for organization") + return + } + utils.WriteError(w, http.StatusInternalServerError, "db_error", "db error") + return + } + + cluster.BastionServerID = &server.ID + if err := db.Save(&cluster).Error; err != nil { + utils.WriteError(w, http.StatusInternalServerError, "db_error", "db error") + return + } + + _ = markClusterNeedsValidation(db, cluster.ID) + + if err := db.Preload("CaptainDomain"). + Preload("ControlPlaneRecordSet"). + Preload("AppsLoadBalancer"). + Preload("GlueOpsLoadBalancer"). + Preload("BastionServer"). + Preload("NodePools"). + Preload("NodePools.Labels"). + Preload("NodePools.Annotations"). + Preload("NodePools.Taints"). + Preload("NodePools.Servers"). + First(&cluster, "id = ?", cluster.ID).Error; err != nil { + utils.WriteError(w, http.StatusInternalServerError, "db_error", "db error") + return + } + + utils.WriteJSON(w, http.StatusOK, clusterToDTO(cluster)) + } +} + +// DetachBastionServer godoc +// +// @ID DetachBastionServer +// @Summary Detach the bastion server from a cluster +// @Description Clears bastion_server_id on the cluster. +// @Tags Clusters +// @Produce json +// @Param X-Org-ID header string false "Organization UUID" +// @Param clusterID path string true "Cluster ID" +// @Success 200 {object} dto.ClusterResponse +// @Failure 400 {string} string "bad request" +// @Failure 401 {string} string "Unauthorized" +// @Failure 403 {string} string "organization required" +// @Failure 404 {string} string "cluster not found" +// @Failure 500 {string} string "db error" +// @Router /clusters/{clusterID}/bastion [delete] +// @Security BearerAuth +// @Security OrgKeyAuth +// @Security OrgSecretAuth +func DetachBastionServer(db *gorm.DB) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + orgID, ok := httpmiddleware.OrgIDFrom(r.Context()) + if !ok { + utils.WriteError(w, http.StatusForbidden, "org_required", "specify X-Org-ID") + return + } + + clusterID, err := uuid.Parse(chi.URLParam(r, "clusterID")) + if err != nil { + utils.WriteError(w, http.StatusBadRequest, "bad_cluster_id", "invalid cluster id") + return + } + + var cluster models.Cluster + if err := db.Where("id = ? AND organization_id = ?", clusterID, orgID).First(&cluster).Error; err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + utils.WriteError(w, http.StatusNotFound, "not_found", "cluster not found") + return + } + utils.WriteError(w, http.StatusInternalServerError, "db_error", "db error") + return + } + + cluster.BastionServerID = nil + if err := db.Save(&cluster).Error; err != nil { + utils.WriteError(w, http.StatusInternalServerError, "db_error", "db error") + return + } + + _ = markClusterNeedsValidation(db, cluster.ID) + + if err := db.Preload("CaptainDomain"). + Preload("ControlPlaneRecordSet"). + Preload("AppsLoadBalancer"). + Preload("GlueOpsLoadBalancer"). + Preload("BastionServer"). + Preload("NodePools"). + Preload("NodePools.Labels"). + Preload("NodePools.Annotations"). + Preload("NodePools.Taints"). + Preload("NodePools.Servers"). + First(&cluster, "id = ?", cluster.ID).Error; err != nil { + utils.WriteError(w, http.StatusInternalServerError, "db_error", "db error") + return + } + + utils.WriteJSON(w, http.StatusOK, clusterToDTO(cluster)) + } +} + +// SetClusterKubeconfig godoc +// +// @ID SetClusterKubeconfig +// @Summary Set (or replace) the kubeconfig for a cluster +// @Description Stores the kubeconfig encrypted per organization. The kubeconfig is never returned in responses. +// @Tags Clusters +// @Accept json +// @Produce json +// @Param X-Org-ID header string false "Organization UUID" +// @Param clusterID path string true "Cluster ID" +// @Param body body dto.SetKubeconfigRequest true "payload" +// @Success 200 {object} dto.ClusterResponse +// @Failure 400 {string} string "bad request" +// @Failure 401 {string} string "Unauthorized" +// @Failure 403 {string} string "organization required" +// @Failure 404 {string} string "cluster not found" +// @Failure 500 {string} string "db error" +// @Router /clusters/{clusterID}/kubeconfig [post] +// @Security BearerAuth +// @Security OrgKeyAuth +// @Security OrgSecretAuth +func SetClusterKubeconfig(db *gorm.DB) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + orgID, ok := httpmiddleware.OrgIDFrom(r.Context()) + if !ok { + utils.WriteError(w, http.StatusForbidden, "org_required", "specify X-Org-ID") + return + } + + clusterID, err := uuid.Parse(chi.URLParam(r, "clusterID")) + if err != nil { + utils.WriteError(w, http.StatusBadRequest, "bad_cluster_id", "invalid cluster id") + return + } + + var in dto.SetKubeconfigRequest + if err := json.NewDecoder(r.Body).Decode(&in); err != nil { + utils.WriteError(w, http.StatusBadRequest, "bad_json", err.Error()) + return + } + + var cluster models.Cluster + if err := db.Where("id = ? AND organization_id = ?", clusterID, orgID).First(&cluster).Error; err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + utils.WriteError(w, http.StatusNotFound, "not_found", "cluster not found") + return + } + utils.WriteError(w, http.StatusInternalServerError, "db_error", "db error") + return + } + + ct, iv, tag, err := utils.EncryptForOrg(orgID, []byte(in.Kubeconfig), db) + if err != nil { + utils.WriteError(w, http.StatusInternalServerError, "encryption_error", "failed to encrypt kubeconfig") + return + } + + cluster.EncryptedKubeconfig = ct + cluster.KubeIV = iv + cluster.KubeTag = tag + + if err := db.Save(&cluster).Error; err != nil { + utils.WriteError(w, http.StatusInternalServerError, "db_error", "db error") + return + } + + _ = markClusterNeedsValidation(db, cluster.ID) + + if err := db.Preload("CaptainDomain"). + Preload("ControlPlaneRecordSet"). + Preload("AppsLoadBalancer"). + Preload("GlueOpsLoadBalancer"). + Preload("BastionServer"). + Preload("NodePools"). + Preload("NodePools.Labels"). + Preload("NodePools.Annotations"). + Preload("NodePools.Taints"). + Preload("NodePools.Servers"). + First(&cluster, "id = ?", cluster.ID).Error; err != nil { + utils.WriteError(w, http.StatusInternalServerError, "db_error", "db error") + return + } + + utils.WriteJSON(w, http.StatusOK, clusterToDTO(cluster)) + } +} + +// ClearClusterKubeconfig godoc +// +// @ID ClearClusterKubeconfig +// @Summary Clear the kubeconfig for a cluster +// @Description Removes the encrypted kubeconfig, IV, and tag from the cluster record. +// @Tags Clusters +// @Produce json +// @Param X-Org-ID header string false "Organization UUID" +// @Param clusterID path string true "Cluster ID" +// @Success 200 {object} dto.ClusterResponse +// @Failure 400 {string} string "bad request" +// @Failure 401 {string} string "Unauthorized" +// @Failure 403 {string} string "organization required" +// @Failure 404 {string} string "cluster not found" +// @Failure 500 {string} string "db error" +// @Router /clusters/{clusterID}/kubeconfig [delete] +// @Security BearerAuth +// @Security OrgKeyAuth +// @Security OrgSecretAuth +func ClearClusterKubeconfig(db *gorm.DB) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + orgID, ok := httpmiddleware.OrgIDFrom(r.Context()) + if !ok { + utils.WriteError(w, http.StatusForbidden, "org_required", "specify X-Org-ID") + return + } + + clusterID, err := uuid.Parse(chi.URLParam(r, "clusterID")) + if err != nil { + utils.WriteError(w, http.StatusBadRequest, "bad_cluster_id", "invalid cluster id") + return + } + + var cluster models.Cluster + if err := db.Where("id = ? AND organization_id = ?", clusterID, orgID).First(&cluster).Error; err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + utils.WriteError(w, http.StatusNotFound, "not_found", "cluster not found") + return + } + utils.WriteError(w, http.StatusInternalServerError, "db_error", "db error") + return + } + + cluster.EncryptedKubeconfig = "" + cluster.KubeIV = "" + cluster.KubeTag = "" + + if err := db.Save(&cluster).Error; err != nil { + utils.WriteError(w, http.StatusInternalServerError, "db_error", "db error") + return + } + + _ = markClusterNeedsValidation(db, cluster.ID) + + if err := db.Preload("CaptainDomain"). + Preload("ControlPlaneRecordSet"). + Preload("AppsLoadBalancer"). + Preload("GlueOpsLoadBalancer"). + Preload("BastionServer"). + Preload("NodePools"). + Preload("NodePools.Labels"). + Preload("NodePools.Annotations"). + Preload("NodePools.Taints"). + Preload("NodePools.Servers"). + First(&cluster, "id = ?", cluster.ID).Error; err != nil { + utils.WriteError(w, http.StatusInternalServerError, "db_error", "db error") + return + } + + utils.WriteJSON(w, http.StatusOK, clusterToDTO(cluster)) + } +} + // -- Helpers func clusterToDTO(c models.Cluster) dto.ClusterResponse { @@ -100,26 +1303,52 @@ func clusterToDTO(c models.Cluster) dto.ClusterResponse { bastion = &b } + var captainDomain *dto.DomainResponse + if c.CaptainDomainID != nil && c.CaptainDomain.ID != uuid.Nil { + dr := domainToDTO(c.CaptainDomain) + captainDomain = &dr + } + + var controlPlane *dto.RecordSetResponse + if c.ControlPlaneRecordSet != nil { + rr := recordSetToDTO(*c.ControlPlaneRecordSet) + controlPlane = &rr + } + + var appsLB *dto.LoadBalancerResponse + if c.AppsLoadBalancer != nil { + lr := loadBalancerToDTO(*c.AppsLoadBalancer) + appsLB = &lr + } + + var glueOpsLB *dto.LoadBalancerResponse + if c.GlueOpsLoadBalancer != nil { + lr := loadBalancerToDTO(*c.GlueOpsLoadBalancer) + glueOpsLB = &lr + } + nps := make([]dto.NodePoolResponse, 0, len(c.NodePools)) for _, np := range c.NodePools { nps = append(nps, nodePoolToDTO(np)) } return dto.ClusterResponse{ - ID: c.ID, - Name: c.Name, - Provider: c.Provider, - Region: c.Region, - Status: c.Status, - CaptainDomain: c.CaptainDomain, - //ClusterLoadBalancer: c.ClusterLoadBalancer, - RandomToken: c.RandomToken, - CertificateKey: c.CertificateKey, - //ControlLoadBalancer: c.ControlLoadBalancer, - NodePools: nps, - BastionServer: bastion, - CreatedAt: c.CreatedAt, - UpdatedAt: c.UpdatedAt, + ID: c.ID, + Name: c.Name, + CaptainDomain: captainDomain, + ControlPlaneRecordSet: controlPlane, + AppsLoadBalancer: appsLB, + GlueOpsLoadBalancer: glueOpsLB, + BastionServer: bastion, + Provider: c.Provider, + Region: c.Region, + Status: c.Status, + LastError: c.LastError, + RandomToken: c.RandomToken, + CertificateKey: c.CertificateKey, + NodePools: nps, + CreatedAt: c.CreatedAt, + UpdatedAt: c.UpdatedAt, } } @@ -184,3 +1413,74 @@ func serverToDTO(s models.Server) dto.ServerResponse { UpdatedAt: s.UpdatedAt.UTC().Format(time.RFC3339), } } + +func domainToDTO(d models.Domain) dto.DomainResponse { + return dto.DomainResponse{ + ID: d.ID.String(), + OrganizationID: d.OrganizationID.String(), + DomainName: d.DomainName, + ZoneID: d.ZoneID, + Status: d.Status, + LastError: d.LastError, + CredentialID: d.CredentialID.String(), + CreatedAt: d.CreatedAt.UTC().Format(time.RFC3339), + UpdatedAt: d.UpdatedAt.UTC().Format(time.RFC3339), + } +} + +func recordSetToDTO(rs models.RecordSet) dto.RecordSetResponse { + return dto.RecordSetResponse{ + ID: rs.ID.String(), + DomainID: rs.DomainID.String(), + Name: rs.Name, + Type: rs.Type, + TTL: rs.TTL, + Values: []byte(rs.Values), + Fingerprint: rs.Fingerprint, + Status: rs.Status, + Owner: rs.Owner, + LastError: rs.LastError, + CreatedAt: rs.CreatedAt.UTC().Format(time.RFC3339), + UpdatedAt: rs.UpdatedAt.UTC().Format(time.RFC3339), + } +} + +func loadBalancerToDTO(lb models.LoadBalancer) dto.LoadBalancerResponse { + return dto.LoadBalancerResponse{ + ID: lb.ID, + OrganizationID: lb.OrganizationID, + Name: lb.Name, + Kind: lb.Kind, + PublicIPAddress: lb.PublicIPAddress, + PrivateIPAddress: lb.PrivateIPAddress, + CreatedAt: lb.CreatedAt, + UpdatedAt: lb.UpdatedAt, + } +} + +func GenerateSecureHex(n int) (string, error) { + bytes := make([]byte, n) + if _, err := rand.Read(bytes); err != nil { + return "", fmt.Errorf("failed to generate random bytes: %w", err) + } + return hex.EncodeToString(bytes), nil +} + +func GenerateFormattedToken() (string, error) { + part1, err := GenerateSecureHex(3) + if err != nil { + return "", fmt.Errorf("failed to generate token part 1: %w", err) + } + part2, err := GenerateSecureHex(8) + if err != nil { + return "", fmt.Errorf("failed to generate token part 2: %w", err) + } + return fmt.Sprintf("%s.%s", part1, part2), nil +} + +func markClusterNeedsValidation(db *gorm.DB, clusterID uuid.UUID) error { + return db.Model(&models.Cluster{}).Where("id = ?", clusterID).Updates(map[string]any{ + "status": models.ClusterStatusPrePending, + "last_error": "", + }).Error +} diff --git a/internal/handlers/credentials.go b/internal/handlers/credentials.go index 6c57cae..898412d 100644 --- a/internal/handlers/credentials.go +++ b/internal/handlers/credentials.go @@ -23,24 +23,24 @@ import ( ) // ListCredentials godoc -// @ID ListCredentials -// @Summary List credentials (metadata only) -// @Description Returns credential metadata for the current org. Secrets are never returned. -// @Tags Credentials -// @Accept json -// @Produce json -// @Param X-Org-ID header string false "Organization ID (UUID)" -// @Param provider query string false "Filter by provider (e.g., aws)" -// @Param kind query string false "Filter by kind (e.g., aws_access_key)" -// @Param scope_kind query string false "Filter by scope kind (provider/service/resource)" -// @Success 200 {array} dto.CredentialOut -// @Failure 401 {string} string "Unauthorized" -// @Failure 403 {string} string "organization required" -// @Failure 500 {string} string "internal server error" -// @Router /credentials [get] -// @Security BearerAuth -// @Security OrgKeyAuth -// @Security OrgSecretAuth +// +// @ID ListCredentials +// @Summary List credentials (metadata only) +// @Description Returns credential metadata for the current org. Secrets are never returned. +// @Tags Credentials +// @Produce json +// @Param X-Org-ID header string false "Organization ID (UUID)" +// @Param provider query string false "Filter by provider (e.g., aws)" +// @Param kind query string false "Filter by kind (e.g., aws_access_key)" +// @Param scope_kind query string false "Filter by scope kind (provider/service/resource)" +// @Success 200 {array} dto.CredentialOut +// @Failure 401 {string} string "Unauthorized" +// @Failure 403 {string} string "organization required" +// @Failure 500 {string} string "internal server error" +// @Router /credentials [get] +// @Security BearerAuth +// @Security OrgKeyAuth +// @Security OrgSecretAuth func ListCredentials(db *gorm.DB) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { orgID, ok := httpmiddleware.OrgIDFrom(r.Context()) @@ -73,21 +73,21 @@ func ListCredentials(db *gorm.DB) http.HandlerFunc { } // GetCredential godoc -// @ID GetCredential -// @Summary Get credential by ID (metadata only) -// @Tags Credentials -// @Accept json -// @Produce json -// @Param X-Org-ID header string false "Organization ID (UUID)" -// @Param id path string true "Credential ID (UUID)" -// @Success 200 {object} dto.CredentialOut -// @Failure 401 {string} string "Unauthorized" -// @Failure 403 {string} string "organization required" -// @Failure 500 {string} string "internal server error" -// @Router /credentials/{id} [get] -// @Security BearerAuth -// @Security OrgKeyAuth -// @Security OrgSecretAuth +// +// @ID GetCredential +// @Summary Get credential by ID (metadata only) +// @Tags Credentials +// @Produce json +// @Param X-Org-ID header string false "Organization ID (UUID)" +// @Param id path string true "Credential ID (UUID)" +// @Success 200 {object} dto.CredentialOut +// @Failure 401 {string} string "Unauthorized" +// @Failure 403 {string} string "organization required" +// @Failure 500 {string} string "internal server error" +// @Router /credentials/{id} [get] +// @Security BearerAuth +// @Security OrgKeyAuth +// @Security OrgSecretAuth func GetCredential(db *gorm.DB) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { orgID, ok := httpmiddleware.OrgIDFrom(r.Context()) @@ -117,21 +117,22 @@ func GetCredential(db *gorm.DB) http.HandlerFunc { } // CreateCredential godoc -// @ID CreateCredential -// @Summary Create a credential (encrypts secret) -// @Tags Credentials -// @Accept json -// @Produce json -// @Param X-Org-ID header string false "Organization ID (UUID)" -// @Param body body dto.CreateCredentialRequest true "Credential payload" -// @Success 201 {object} dto.CredentialOut -// @Failure 401 {string} string "Unauthorized" -// @Failure 403 {string} string "organization required" -// @Failure 500 {string} string "internal server error" -// @Router /credentials [post] -// @Security BearerAuth -// @Security OrgKeyAuth -// @Security OrgSecretAuth +// +// @ID CreateCredential +// @Summary Create a credential (encrypts secret) +// @Tags Credentials +// @Accept json +// @Produce json +// @Param X-Org-ID header string false "Organization ID (UUID)" +// @Param body body dto.CreateCredentialRequest true "Credential payload" +// @Success 201 {object} dto.CredentialOut +// @Failure 401 {string} string "Unauthorized" +// @Failure 403 {string} string "organization required" +// @Failure 500 {string} string "internal server error" +// @Router /credentials [post] +// @Security BearerAuth +// @Security OrgKeyAuth +// @Security OrgSecretAuth func CreateCredential(db *gorm.DB) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { orgID, ok := httpmiddleware.OrgIDFrom(r.Context()) @@ -166,21 +167,22 @@ func CreateCredential(db *gorm.DB) http.HandlerFunc { } // UpdateCredential godoc -// @ID UpdateCredential -// @Summary Update credential metadata and/or rotate secret -// @Tags Credentials -// @Accept json -// @Produce json -// @Param X-Org-ID header string false "Organization ID (UUID)" -// @Param id path string true "Credential ID (UUID)" -// @Param body body dto.UpdateCredentialRequest true "Fields to update" -// @Success 200 {object} dto.CredentialOut -// @Failure 403 {string} string "X-Org-ID required" -// @Failure 404 {string} string "not found" -// @Router /credentials/{id} [patch] -// @Security BearerAuth -// @Security OrgKeyAuth -// @Security OrgSecretAuth +// +// @ID UpdateCredential +// @Summary Update credential metadata and/or rotate secret +// @Tags Credentials +// @Accept json +// @Produce json +// @Param X-Org-ID header string false "Organization ID (UUID)" +// @Param id path string true "Credential ID (UUID)" +// @Param body body dto.UpdateCredentialRequest true "Fields to update" +// @Success 200 {object} dto.CredentialOut +// @Failure 403 {string} string "X-Org-ID required" +// @Failure 404 {string} string "not found" +// @Router /credentials/{id} [patch] +// @Security BearerAuth +// @Security OrgKeyAuth +// @Security OrgSecretAuth func UpdateCredential(db *gorm.DB) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { orgID, ok := httpmiddleware.OrgIDFrom(r.Context()) @@ -296,19 +298,19 @@ func UpdateCredential(db *gorm.DB) http.HandlerFunc { } // DeleteCredential godoc -// @ID DeleteCredential -// @Summary Delete credential -// @Tags Credentials -// @Accept json -// @Produce json -// @Param X-Org-ID header string false "Organization ID (UUID)" -// @Param id path string true "Credential ID (UUID)" -// @Success 204 -// @Failure 404 {string} string "not found" -// @Router /credentials/{id} [delete] -// @Security BearerAuth -// @Security OrgKeyAuth -// @Security OrgSecretAuth +// +// @ID DeleteCredential +// @Summary Delete credential +// @Tags Credentials +// @Produce json +// @Param X-Org-ID header string false "Organization ID (UUID)" +// @Param id path string true "Credential ID (UUID)" +// @Success 204 +// @Failure 404 {string} string "not found" +// @Router /credentials/{id} [delete] +// @Security BearerAuth +// @Security OrgKeyAuth +// @Security OrgSecretAuth func DeleteCredential(db *gorm.DB) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { orgID, ok := httpmiddleware.OrgIDFrom(r.Context()) @@ -335,20 +337,21 @@ func DeleteCredential(db *gorm.DB) http.HandlerFunc { } // RevealCredential godoc -// @ID RevealCredential -// @Summary Reveal decrypted secret (one-time read) -// @Tags Credentials -// @Accept json -// @Produce json -// @Param X-Org-ID header string false "Organization ID (UUID)" -// @Param id path string true "Credential ID (UUID)" -// @Success 200 {object} map[string]any -// @Failure 403 {string} string "organization required" -// @Failure 404 {string} string "not found" -// @Router /credentials/{id}/reveal [post] -// @Security BearerAuth -// @Security OrgKeyAuth -// @Security OrgSecretAuth +// +// @ID RevealCredential +// @Summary Reveal decrypted secret (one-time read) +// @Tags Credentials +// @Accept json +// @Produce json +// @Param X-Org-ID header string false "Organization ID (UUID)" +// @Param id path string true "Credential ID (UUID)" +// @Success 200 {object} map[string]any +// @Failure 403 {string} string "organization required" +// @Failure 404 {string} string "not found" +// @Router /credentials/{id}/reveal [post] +// @Security BearerAuth +// @Security OrgKeyAuth +// @Security OrgSecretAuth func RevealCredential(db *gorm.DB) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { orgID, ok := httpmiddleware.OrgIDFrom(r.Context()) diff --git a/internal/handlers/dns.go b/internal/handlers/dns.go index d34bfc2..2528ee6 100644 --- a/internal/handlers/dns.go +++ b/internal/handlers/dns.go @@ -166,7 +166,6 @@ func mustSameOrgDomainWithCredential(db *gorm.DB, orgID uuid.UUID, credID uuid.U // @Summary List domains (org scoped) // @Description Returns domains for X-Org-ID. Filters: `domain_name`, `status`, `q` (contains). // @Tags DNS -// @Accept json // @Produce json // @Param X-Org-ID header string false "Organization UUID" // @Param domain_name query string false "Exact domain name (lowercase, no trailing dot)" @@ -213,21 +212,20 @@ func ListDomains(db *gorm.DB) http.HandlerFunc { // GetDomain godoc // -// @ID GetDomain -// @Summary Get a domain (org scoped) -// @Tags DNS -// @Accept json -// @Produce json -// @Param X-Org-ID header string false "Organization UUID" -// @Param id path string true "Domain ID (UUID)" -// @Success 200 {object} dto.DomainResponse -// @Failure 401 {string} string "Unauthorized" -// @Failure 403 {string} string "organization required" -// @Failure 404 {string} string "not found" -// @Router /dns/domains/{id} [get] -// @Security BearerAuth -// @Security OrgKeyAuth -// @Security OrgSecretAuth +// @ID GetDomain +// @Summary Get a domain (org scoped) +// @Tags DNS +// @Produce json +// @Param X-Org-ID header string false "Organization UUID" +// @Param id path string true "Domain ID (UUID)" +// @Success 200 {object} dto.DomainResponse +// @Failure 401 {string} string "Unauthorized" +// @Failure 403 {string} string "organization required" +// @Failure 404 {string} string "not found" +// @Router /dns/domains/{id} [get] +// @Security BearerAuth +// @Security OrgKeyAuth +// @Security OrgSecretAuth func GetDomain(db *gorm.DB) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { orgID, ok := httpmiddleware.OrgIDFrom(r.Context()) @@ -261,7 +259,7 @@ func GetDomain(db *gorm.DB) http.HandlerFunc { // @Tags DNS // @Accept json // @Produce json -// @Param X-Org-ID header string false "Organization UUID" +// @Param X-Org-ID header string false "Organization UUID" // @Param body body dto.CreateDomainRequest true "Domain payload" // @Success 201 {object} dto.DomainResponse // @Failure 400 {string} string "validation error" @@ -312,22 +310,22 @@ func CreateDomain(db *gorm.DB) http.HandlerFunc { // UpdateDomain godoc // -// @ID UpdateDomain -// @Summary Update a domain (org scoped) -// @Tags DNS -// @Accept json -// @Produce json -// @Param X-Org-ID header string false "Organization UUID" -// @Param id path string true "Domain ID (UUID)" -// @Param body body dto.UpdateDomainRequest true "Fields to update" -// @Success 200 {object} dto.DomainResponse -// @Failure 400 {string} string "validation error" -// @Failure 403 {string} string "organization required" -// @Failure 404 {string} string "not found" -// @Router /dns/domains/{id} [patch] -// @Security BearerAuth -// @Security OrgKeyAuth -// @Security OrgSecretAuth +// @ID UpdateDomain +// @Summary Update a domain (org scoped) +// @Tags DNS +// @Accept json +// @Produce json +// @Param X-Org-ID header string false "Organization UUID" +// @Param id path string true "Domain ID (UUID)" +// @Param body body dto.UpdateDomainRequest true "Fields to update" +// @Success 200 {object} dto.DomainResponse +// @Failure 400 {string} string "validation error" +// @Failure 403 {string} string "organization required" +// @Failure 404 {string} string "not found" +// @Router /dns/domains/{id} [patch] +// @Security BearerAuth +// @Security OrgKeyAuth +// @Security OrgSecretAuth func UpdateDomain(db *gorm.DB) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { orgID, ok := httpmiddleware.OrgIDFrom(r.Context()) @@ -390,20 +388,19 @@ func UpdateDomain(db *gorm.DB) http.HandlerFunc { // DeleteDomain godoc // -// @ID DeleteDomain -// @Summary Delete a domain -// @Tags DNS -// @Accept json -// @Produce json -// @Param X-Org-ID header string false "Organization UUID" -// @Param id path string true "Domain ID (UUID)" -// @Success 204 -// @Failure 403 {string} string "organization required" -// @Failure 404 {string} string "not found" -// @Router /dns/domains/{id} [delete] -// @Security BearerAuth -// @Security OrgKeyAuth -// @Security OrgSecretAuth +// @ID DeleteDomain +// @Summary Delete a domain +// @Tags DNS +// @Produce json +// @Param X-Org-ID header string false "Organization UUID" +// @Param id path string true "Domain ID (UUID)" +// @Success 204 +// @Failure 403 {string} string "organization required" +// @Failure 404 {string} string "not found" +// @Router /dns/domains/{id} [delete] +// @Security BearerAuth +// @Security OrgKeyAuth +// @Security OrgSecretAuth func DeleteDomain(db *gorm.DB) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { orgID, ok := httpmiddleware.OrgIDFrom(r.Context()) @@ -437,13 +434,12 @@ func DeleteDomain(db *gorm.DB) http.HandlerFunc { // @Summary List record sets for a domain // @Description Filters: `name`, `type`, `status`. // @Tags DNS -// @Accept json // @Produce json -// @Param X-Org-ID header string false "Organization UUID" -// @Param domain_id path string true "Domain ID (UUID)" -// @Param name query string false "Exact relative name or FQDN (server normalizes)" -// @Param type query string false "RR type (A, AAAA, CNAME, TXT, MX, NS, SRV, CAA)" -// @Param status query string false "pending|provisioning|ready|failed" +// @Param X-Org-ID header string false "Organization UUID" +// @Param domain_id path string true "Domain ID (UUID)" +// @Param name query string false "Exact relative name or FQDN (server normalizes)" +// @Param type query string false "RR type (A, AAAA, CNAME, TXT, MX, NS, SRV, CAA)" +// @Param status query string false "pending|provisioning|ready|failed" // @Success 200 {array} dto.RecordSetResponse // @Failure 403 {string} string "organization required" // @Failure 404 {string} string "domain not found" @@ -509,22 +505,22 @@ func ListRecordSets(db *gorm.DB) http.HandlerFunc { // CreateRecordSet godoc // -// @ID CreateRecordSet -// @Summary Create a record set (pending; Archer will UPSERT to Route 53) -// @Tags DNS -// @Accept json -// @Produce json -// @Param X-Org-ID header string false "Organization UUID" -// @Param domain_id path string true "Domain ID (UUID)" -// @Param body body dto.CreateRecordSetRequest true "Record set payload" -// @Success 201 {object} dto.RecordSetResponse -// @Failure 400 {string} string "validation error" -// @Failure 403 {string} string "organization required" -// @Failure 404 {string} string "domain not found" -// @Router /dns/domains/{domain_id}/records [post] -// @Security BearerAuth -// @Security OrgKeyAuth -// @Security OrgSecretAuth +// @ID CreateRecordSet +// @Summary Create a record set (pending; Archer will UPSERT to Route 53) +// @Tags DNS +// @Accept json +// @Produce json +// @Param X-Org-ID header string false "Organization UUID" +// @Param domain_id path string true "Domain ID (UUID)" +// @Param body body dto.CreateRecordSetRequest true "Record set payload" +// @Success 201 {object} dto.RecordSetResponse +// @Failure 400 {string} string "validation error" +// @Failure 403 {string} string "organization required" +// @Failure 404 {string} string "domain not found" +// @Router /dns/domains/{domain_id}/records [post] +// @Security BearerAuth +// @Security OrgKeyAuth +// @Security OrgSecretAuth func CreateRecordSet(db *gorm.DB) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { orgID, ok := httpmiddleware.OrgIDFrom(r.Context()) @@ -610,22 +606,22 @@ func CreateRecordSet(db *gorm.DB) http.HandlerFunc { // UpdateRecordSet godoc // -// @ID UpdateRecordSet -// @Summary Update a record set (flips to pending for reconciliation) -// @Tags DNS -// @Accept json -// @Produce json -// @Param X-Org-ID header string false "Organization UUID" -// @Param id path string true "Record Set ID (UUID)" -// @Param body body dto.UpdateRecordSetRequest true "Fields to update" -// @Success 200 {object} dto.RecordSetResponse -// @Failure 400 {string} string "validation error" -// @Failure 403 {string} string "organization required" -// @Failure 404 {string} string "not found" -// @Router /dns/records/{id} [patch] -// @Security BearerAuth -// @Security OrgKeyAuth -// @Security OrgSecretAuth +// @ID UpdateRecordSet +// @Summary Update a record set (flips to pending for reconciliation) +// @Tags DNS +// @Accept json +// @Produce json +// @Param X-Org-ID header string false "Organization UUID" +// @Param id path string true "Record Set ID (UUID)" +// @Param body body dto.UpdateRecordSetRequest true "Fields to update" +// @Success 200 {object} dto.RecordSetResponse +// @Failure 400 {string} string "validation error" +// @Failure 403 {string} string "organization required" +// @Failure 404 {string} string "not found" +// @Router /dns/records/{id} [patch] +// @Security BearerAuth +// @Security OrgKeyAuth +// @Security OrgSecretAuth func UpdateRecordSet(db *gorm.DB) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { orgID, ok := httpmiddleware.OrgIDFrom(r.Context()) @@ -720,20 +716,19 @@ func UpdateRecordSet(db *gorm.DB) http.HandlerFunc { // DeleteRecordSet godoc // -// @ID DeleteRecordSet -// @Summary Delete a record set (API removes row; worker can optionally handle external deletion policy) -// @Tags DNS -// @Accept json -// @Produce json -// @Param X-Org-ID header string false "Organization UUID" -// @Param id path string true "Record Set ID (UUID)" -// @Success 204 -// @Failure 403 {string} string "organization required" -// @Failure 404 {string} string "not found" -// @Router /dns/records/{id} [delete] -// @Security BearerAuth -// @Security OrgKeyAuth -// @Security OrgSecretAuth +// @ID DeleteRecordSet +// @Summary Delete a record set (API removes row; worker can optionally handle external deletion policy) +// @Tags DNS +// @Produce json +// @Param X-Org-ID header string false "Organization UUID" +// @Param id path string true "Record Set ID (UUID)" +// @Success 204 +// @Failure 403 {string} string "organization required" +// @Failure 404 {string} string "not found" +// @Router /dns/records/{id} [delete] +// @Security BearerAuth +// @Security OrgKeyAuth +// @Security OrgSecretAuth func DeleteRecordSet(db *gorm.DB) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { orgID, ok := httpmiddleware.OrgIDFrom(r.Context()) diff --git a/internal/handlers/dto/clusters.go b/internal/handlers/dto/clusters.go index 3636997..fa92fc3 100644 --- a/internal/handlers/dto/clusters.go +++ b/internal/handlers/dto/clusters.go @@ -7,28 +7,52 @@ import ( ) type ClusterResponse struct { - ID uuid.UUID `json:"id"` - Name string `json:"name"` - Provider string `json:"provider"` - Region string `json:"region"` - Status string `json:"status"` - CaptainDomain string `json:"captain_domain"` - ClusterLoadBalancer string `json:"cluster_load_balancer"` - RandomToken string `json:"random_token"` - CertificateKey string `json:"certificate_key"` - ControlLoadBalancer string `json:"control_load_balancer"` - NodePools []NodePoolResponse `json:"node_pools,omitempty"` - BastionServer *ServerResponse `json:"bastion_server,omitempty"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` + ID uuid.UUID `json:"id"` + Name string `json:"name"` + CaptainDomain *DomainResponse `json:"captain_domain,omitempty"` + ControlPlaneRecordSet *RecordSetResponse `json:"control_plane_record_set,omitempty"` + AppsLoadBalancer *LoadBalancerResponse `json:"apps_load_balancer,omitempty"` + GlueOpsLoadBalancer *LoadBalancerResponse `json:"glueops_load_balancer,omitempty"` + BastionServer *ServerResponse `json:"bastion_server,omitempty"` + Provider string `json:"provider"` + Region string `json:"region"` + Status string `json:"status"` + LastError string `json:"last_error"` + RandomToken string `json:"random_token"` + CertificateKey string `json:"certificate_key"` + NodePools []NodePoolResponse `json:"node_pools,omitempty"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` } type CreateClusterRequest struct { - Name string `json:"name"` - Provider string `json:"provider"` - Region string `json:"region"` - Status string `json:"status"` - CaptainDomain string `json:"captain_domain"` - ClusterLoadBalancer *string `json:"cluster_load_balancer"` - ControlLoadBalancer *string `json:"control_load_balancer"` + Name string `json:"name"` + Provider string `json:"provider"` + Region string `json:"region"` +} + +type UpdateClusterRequest struct { + Name *string `json:"name,omitempty"` + Provider *string `json:"provider,omitempty"` + Region *string `json:"region,omitempty"` +} + +type AttachCaptainDomainRequest struct { + DomainID uuid.UUID `json:"domain_id"` +} + +type AttachRecordSetRequest struct { + RecordSetID uuid.UUID `json:"record_set_id"` +} + +type AttachLoadBalancerRequest struct { + LoadBalancerID uuid.UUID `json:"load_balancer_id"` +} + +type AttachBastionRequest struct { + ServerID uuid.UUID `json:"server_id"` +} + +type SetKubeconfigRequest struct { + Kubeconfig string `json:"kubeconfig"` } diff --git a/internal/handlers/dto/load_balancers.go b/internal/handlers/dto/load_balancers.go new file mode 100644 index 0000000..10cc757 --- /dev/null +++ b/internal/handlers/dto/load_balancers.go @@ -0,0 +1,32 @@ +package dto + +import ( + "time" + + "github.com/google/uuid" +) + +type LoadBalancerResponse struct { + ID uuid.UUID `json:"id"` + OrganizationID uuid.UUID `json:"organization_id"` + Name string `json:"name"` + Kind string `json:"kind" enums:"glueops|public"` + PublicIPAddress string `json:"public_ip_address"` + PrivateIPAddress string `json:"private_ip_address"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` +} + +type CreateLoadBalancerRequest struct { + Name string `json:"name" example:"glueops"` + Kind string `json:"kind" example:"public" enums:"glueops|public"` + PublicIPAddress string `json:"public_ip_address" example:"8.8.8.8"` + PrivateIPAddress string `json:"private_ip_address" example:"192.168.0.2"` +} + +type UpdateLoadBalancerRequest struct { + Name *string `json:"name" example:"glue"` + Kind *string `json:"kind" example:"public" enums:"glueops|public"` + PublicIPAddress *string `json:"public_ip_address" example:"8.8.8.8"` + PrivateIPAddress *string `json:"private_ip_address" example:"192.168.0.2"` +} diff --git a/internal/handlers/health.go b/internal/handlers/health.go index c40d2e1..b3d0a56 100644 --- a/internal/handlers/health.go +++ b/internal/handlers/health.go @@ -16,7 +16,6 @@ type HealthStatus struct { // @Description Returns 200 OK when the service is up // @Tags Health // @ID HealthCheck // operationId -// @Accept json // @Produce json // @Success 200 {object} HealthStatus // @Router /healthz [get] diff --git a/internal/handlers/jobs.go b/internal/handlers/jobs.go index d64bc1e..d4ba17e 100644 --- a/internal/handlers/jobs.go +++ b/internal/handlers/jobs.go @@ -25,7 +25,6 @@ import ( // @Summary List Archer jobs (admin) // @Description Paginated background jobs with optional filters. Search `q` may match id, type, error, payload (implementation-dependent). // @Tags ArcherAdmin -// @Accept json // @Produce json // @Param status query string false "Filter by status" Enums(queued,running,succeeded,failed,canceled,retrying,scheduled) // @Param queue query string false "Filter by queue name / worker name" @@ -283,7 +282,6 @@ func AdminCancelArcherJob(db *gorm.DB) http.HandlerFunc { // @Summary List Archer queues (admin) // @Description Summary metrics per queue (pending, running, failed, scheduled). // @Tags ArcherAdmin -// @Accept json // @Produce json // @Success 200 {array} dto.QueueInfo // @Failure 401 {string} string "Unauthorized" diff --git a/internal/handlers/labels.go b/internal/handlers/labels.go index dad73cf..a39423c 100644 --- a/internal/handlers/labels.go +++ b/internal/handlers/labels.go @@ -22,7 +22,6 @@ import ( // @Summary List node labels (org scoped) // @Description Returns node labels for the organization in X-Org-ID. Filters: `key`, `value`, and `q` (key contains). Add `include=node_pools` to include linked node groups. // @Tags Labels -// @Accept json // @Produce json // @Param X-Org-ID header string false "Organization UUID" // @Param key query string false "Exact key" @@ -74,7 +73,6 @@ func ListLabels(db *gorm.DB) http.HandlerFunc { // @Summary Get label by ID (org scoped) // @Description Returns one label. // @Tags Labels -// @Accept json // @Produce json // @Param X-Org-ID header string false "Organization UUID" // @Param id path string true "Label ID (UUID)" @@ -253,11 +251,10 @@ func UpdateLabel(db *gorm.DB) http.HandlerFunc { // @Summary Delete label (org scoped) // @Description Permanently deletes the label. // @Tags Labels -// @Accept json // @Produce json -// @Param X-Org-ID header string false "Organization UUID" -// @Param id path string true "Label ID (UUID)" -// @Success 204 {string} string "No Content" +// @Param X-Org-ID header string false "Organization UUID" +// @Param id path string true "Label ID (UUID)" +// @Success 204 "No Content" // @Failure 400 {string} string "invalid id" // @Failure 401 {string} string "Unauthorized" // @Failure 403 {string} string "organization required" diff --git a/internal/handlers/load_balancers.go b/internal/handlers/load_balancers.go new file mode 100644 index 0000000..266b5aa --- /dev/null +++ b/internal/handlers/load_balancers.go @@ -0,0 +1,283 @@ +package handlers + +import ( + "encoding/json" + "errors" + "net/http" + "strings" + + "github.com/glueops/autoglue/internal/api/httpmiddleware" + "github.com/glueops/autoglue/internal/handlers/dto" + "github.com/glueops/autoglue/internal/models" + "github.com/glueops/autoglue/internal/utils" + "github.com/go-chi/chi/v5" + "github.com/google/uuid" + "gorm.io/gorm" +) + +// ListLoadBalancers godoc +// +// @ID ListLoadBalancers +// @Summary List load balancers (org scoped) +// @Description Returns load balancers for the organization in X-Org-ID. +// @Tags LoadBalancers +// @Produce json +// @Param X-Org-ID header string false "Organization UUID" +// @Success 200 {array} dto.LoadBalancerResponse +// @Failure 401 {string} string "Unauthorized" +// @Failure 403 {string} string "organization required" +// @Failure 500 {string} string "failed to list clusters" +// @Router /load-balancers [get] +// @Security BearerAuth +// @Security OrgKeyAuth +// @Security OrgSecretAuth +func ListLoadBalancers(db *gorm.DB) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + orgID, ok := httpmiddleware.OrgIDFrom(r.Context()) + if !ok { + utils.WriteError(w, http.StatusForbidden, "org_required", "specify X-Org-ID") + return + } + + var rows []models.LoadBalancer + if err := db.Where("organization_id = ?", orgID).Find(&rows).Error; err != nil { + utils.WriteError(w, http.StatusInternalServerError, "db_error", "db error") + return + } + out := make([]dto.LoadBalancerResponse, 0, len(rows)) + for _, row := range rows { + out = append(out, loadBalancerOut(&row)) + } + utils.WriteJSON(w, http.StatusOK, out) + } +} + +// GetLoadBalancer godoc +// +// @ID GetLoadBalancers +// @Summary Get a load balancer (org scoped) +// @Description Returns load balancer for the organization in X-Org-ID. +// @Tags LoadBalancers +// @Produce json +// @Param X-Org-ID header string false "Organization UUID" +// @Param id path string true "LoadBalancer ID (UUID)" +// @Success 200 {array} dto.LoadBalancerResponse +// @Failure 401 {string} string "Unauthorized" +// @Failure 403 {string} string "organization required" +// @Failure 500 {string} string "failed to list clusters" +// @Router /load-balancers/{id} [get] +// @Security BearerAuth +// @Security OrgKeyAuth +// @Security OrgSecretAuth +func GetLoadBalancer(db *gorm.DB) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + orgID, ok := httpmiddleware.OrgIDFrom(r.Context()) + if !ok { + utils.WriteError(w, http.StatusForbidden, "org_required", "specify X-Org-ID") + return + } + + id, err := uuid.Parse(chi.URLParam(r, "id")) + if err != nil { + utils.WriteError(w, http.StatusBadRequest, "bad_id", "invalid UUID") + return + } + + var row models.LoadBalancer + if err := db.Where("id = ? AND organization_id = ?", id, orgID).First(&row).Error; err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + utils.WriteError(w, http.StatusNotFound, "not_found", "load balancer not found") + return + } + utils.WriteError(w, http.StatusInternalServerError, "db_error", "db error") + return + } + out := loadBalancerOut(&row) + utils.WriteJSON(w, http.StatusOK, out) + } +} + +// CreateLoadBalancer godoc +// +// @ID CreateLoadBalancer +// @Summary Create a load balancer +// @Tags LoadBalancers +// @Accept json +// @Produce json +// @Param X-Org-ID header string false "Organization UUID" +// @Param body body dto.CreateLoadBalancerRequest true "Record set payload" +// @Success 201 {object} dto.LoadBalancerResponse +// @Failure 400 {string} string "validation error" +// @Failure 403 {string} string "organization required" +// @Failure 404 {string} string "domain not found" +// @Router /load-balancers [post] +// @Security BearerAuth +// @Security OrgKeyAuth +// @Security OrgSecretAuth +func CreateLoadBalancer(db *gorm.DB) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + orgID, ok := httpmiddleware.OrgIDFrom(r.Context()) + if !ok { + utils.WriteError(w, http.StatusForbidden, "org_required", "specify X-Org-ID") + return + } + + var in dto.CreateLoadBalancerRequest + if err := json.NewDecoder(r.Body).Decode(&in); err != nil { + utils.WriteError(w, http.StatusBadRequest, "bad_json", err.Error()) + return + } + + if strings.ToLower(in.Kind) != "glueops" || strings.ToLower(in.Kind) != "public" { + utils.WriteError(w, http.StatusBadRequest, "bad_kind", "invalid kind only 'glueops' or 'public'") + return + } + + row := &models.LoadBalancer{ + OrganizationID: orgID, + Name: in.Name, + Kind: strings.ToLower(in.Kind), + PublicIPAddress: in.PublicIPAddress, + PrivateIPAddress: in.PrivateIPAddress, + } + if err := db.Create(row).Error; err != nil { + utils.WriteError(w, http.StatusInternalServerError, "db_error", err.Error()) + return + } + utils.WriteJSON(w, http.StatusCreated, loadBalancerOut(row)) + } +} + +// UpdateLoadBalancer godoc +// +// @ID UpdateLoadBalancer +// @Summary Update a load balancer (org scoped) +// @Tags LoadBalancers +// @Accept json +// @Produce json +// @Param X-Org-ID header string false "Organization UUID" +// @Param id path string true "Load Balancer ID (UUID)" +// @Param body body dto.UpdateLoadBalancerRequest true "Fields to update" +// @Success 200 {object} dto.LoadBalancerResponse +// @Failure 400 {string} string "validation error" +// @Failure 403 {string} string "organization required" +// @Failure 404 {string} string "not found" +// @Router /load-balancers/{id} [patch] +// @Security BearerAuth +// @Security OrgKeyAuth +// @Security OrgSecretAuth +func UpdateLoadBalancer(db *gorm.DB) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + orgID, ok := httpmiddleware.OrgIDFrom(r.Context()) + if !ok { + utils.WriteError(w, http.StatusForbidden, "org_required", "specify X-Org-ID") + return + } + + id, err := uuid.Parse(chi.URLParam(r, "id")) + if err != nil { + utils.WriteError(w, http.StatusBadRequest, "bad_id", "invalid UUID") + return + } + + row := &models.LoadBalancer{} + if err := db.Where("organization_id = ? AND id = ?", orgID, id).First(&row).Error; err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + utils.WriteError(w, http.StatusNotFound, "not_found", "load balancer not found") + return + } + utils.WriteError(w, http.StatusInternalServerError, "db_error", err.Error()) + return + } + + var in dto.UpdateLoadBalancerRequest + if err := json.NewDecoder(r.Body).Decode(&in); err != nil { + utils.WriteError(w, http.StatusBadRequest, "bad_json", err.Error()) + return + } + if in.Name != nil { + row.Name = *in.Name + } + if in.Kind != nil { + if strings.ToLower(*in.Kind) != "glueops" || strings.ToLower(*in.Kind) != "public" { + utils.WriteError(w, http.StatusBadRequest, "bad_kind", "invalid kind only 'glueops' or 'public'") + return + } + row.Kind = strings.ToLower(*in.Kind) + } + if in.PublicIPAddress != nil { + row.PublicIPAddress = *in.PublicIPAddress + } + if in.PrivateIPAddress != nil { + row.PrivateIPAddress = *in.PrivateIPAddress + } + if err := db.Save(row).Error; err != nil { + utils.WriteError(w, http.StatusInternalServerError, "db_error", err.Error()) + return + } + utils.WriteJSON(w, http.StatusOK, loadBalancerOut(row)) + + } +} + +// DeleteLoadBalancer godoc +// +// @ID DeleteLoadBalancer +// @Summary Delete a load balancer +// @Tags LoadBalancers +// @Produce json +// @Param X-Org-ID header string false "Organization UUID" +// @Param id path string true "Load Balancer ID (UUID)" +// @Success 204 +// @Failure 403 {string} string "organization required" +// @Failure 404 {string} string "not found" +// @Router /load-balancers/{id} [delete] +// @Security BearerAuth +// @Security OrgKeyAuth +// @Security OrgSecretAuth +func DeleteLoadBalancer(db *gorm.DB) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + orgID, ok := httpmiddleware.OrgIDFrom(r.Context()) + if !ok { + utils.WriteError(w, http.StatusForbidden, "org_required", "specify X-Org-ID") + return + } + + id, err := uuid.Parse(chi.URLParam(r, "id")) + if err != nil { + utils.WriteError(w, http.StatusBadRequest, "bad_id", "invalid UUID") + return + } + + row := &models.LoadBalancer{} + if err := db.Where("organization_id = ? AND id = ?", orgID, id).First(&row).Error; err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + utils.WriteError(w, http.StatusNotFound, "not_found", "load balancer not found") + return + } + utils.WriteError(w, http.StatusInternalServerError, "db_error", err.Error()) + return + } + + if err := db.Delete(row).Error; err != nil { + utils.WriteError(w, http.StatusInternalServerError, "db_error", err.Error()) + return + } + w.WriteHeader(http.StatusNoContent) + } +} + +// ---------- Out mappers ---------- + +func loadBalancerOut(m *models.LoadBalancer) dto.LoadBalancerResponse { + return dto.LoadBalancerResponse{ + ID: m.ID, + OrganizationID: m.OrganizationID, + Name: m.Name, + Kind: m.Kind, + PublicIPAddress: m.PublicIPAddress, + PrivateIPAddress: m.PrivateIPAddress, + CreatedAt: m.CreatedAt.UTC(), + UpdatedAt: m.UpdatedAt.UTC(), + } +} diff --git a/internal/handlers/node_pools.go b/internal/handlers/node_pools.go index 180e073..45592c4 100644 --- a/internal/handlers/node_pools.go +++ b/internal/handlers/node_pools.go @@ -25,14 +25,13 @@ import ( // @Summary List node pools (org scoped) // @Description Returns node pools for the organization in X-Org-ID. // @Tags NodePools -// @Accept json // @Produce json -// @Param X-Org-ID header string false "Organization UUID" -// @Param q query string false "Name contains (case-insensitive)" -// @Success 200 {array} dto.NodePoolResponse -// @Failure 401 {string} string "Unauthorized" -// @Failure 403 {string} string "organization required" -// @Failure 500 {string} string "failed to list node pools" +// @Param X-Org-ID header string false "Organization UUID" +// @Param q query string false "Name contains (case-insensitive)" +// @Success 200 {array} dto.NodePoolResponse +// @Failure 401 {string} string "Unauthorized" +// @Failure 403 {string} string "organization required" +// @Failure 500 {string} string "failed to list node pools" // @Router /node-pools [get] // @Security BearerAuth // @Security OrgKeyAuth @@ -145,16 +144,15 @@ func ListNodePools(db *gorm.DB) http.HandlerFunc { // @Summary Get node pool by ID (org scoped) // @Description Returns one node pool. Add `include=servers` to include servers. // @Tags NodePools -// @Accept json // @Produce json -// @Param X-Org-ID header string false "Organization UUID" -// @Param id path string true "Node Pool ID (UUID)" -// @Success 200 {object} dto.NodePoolResponse -// @Failure 400 {string} string "invalid id" -// @Failure 401 {string} string "Unauthorized" -// @Failure 403 {string} string "organization required" -// @Failure 404 {string} string "not found" -// @Failure 500 {string} string "fetch failed" +// @Param X-Org-ID header string false "Organization UUID" +// @Param id path string true "Node Pool ID (UUID)" +// @Success 200 {object} dto.NodePoolResponse +// @Failure 400 {string} string "invalid id" +// @Failure 401 {string} string "Unauthorized" +// @Failure 403 {string} string "organization required" +// @Failure 404 {string} string "not found" +// @Failure 500 {string} string "fetch failed" // @Router /node-pools/{id} [get] // @Security BearerAuth // @Security OrgKeyAuth @@ -194,13 +192,13 @@ func GetNodePool(db *gorm.DB) http.HandlerFunc { // @Tags NodePools // @Accept json // @Produce json -// @Param X-Org-ID header string false "Organization UUID" -// @Param body body dto.CreateNodePoolRequest true "NodePool payload" -// @Success 201 {object} dto.NodePoolResponse -// @Failure 400 {string} string "invalid json / missing fields / invalid server_ids" -// @Failure 401 {string} string "Unauthorized" -// @Failure 403 {string} string "organization required" -// @Failure 500 {string} string "create failed" +// @Param X-Org-ID header string false "Organization UUID" +// @Param body body dto.CreateNodePoolRequest true "NodePool payload" +// @Success 201 {object} dto.NodePoolResponse +// @Failure 400 {string} string "invalid json / missing fields / invalid server_ids" +// @Failure 401 {string} string "Unauthorized" +// @Failure 403 {string} string "organization required" +// @Failure 500 {string} string "create failed" // @Router /node-pools [post] // @Security BearerAuth // @Security OrgKeyAuth @@ -257,15 +255,15 @@ func CreateNodePool(db *gorm.DB) http.HandlerFunc { // @Tags NodePools // @Accept json // @Produce json -// @Param X-Org-ID header string false "Organization UUID" -// @Param id path string true "Node Pool ID (UUID)" -// @Param body body dto.UpdateNodePoolRequest true "Fields to update" -// @Success 200 {object} dto.NodePoolResponse -// @Failure 400 {string} string "invalid id / invalid json" -// @Failure 401 {string} string "Unauthorized" -// @Failure 403 {string} string "organization required" -// @Failure 404 {string} string "not found" -// @Failure 500 {string} string "update failed" +// @Param X-Org-ID header string false "Organization UUID" +// @Param id path string true "Node Pool ID (UUID)" +// @Param body body dto.UpdateNodePoolRequest true "Fields to update" +// @Success 200 {object} dto.NodePoolResponse +// @Failure 400 {string} string "invalid id / invalid json" +// @Failure 401 {string} string "Unauthorized" +// @Failure 403 {string} string "organization required" +// @Failure 404 {string} string "not found" +// @Failure 500 {string} string "update failed" // @Router /node-pools/{id} [patch] // @Security BearerAuth // @Security OrgKeyAuth @@ -327,15 +325,14 @@ func UpdateNodePool(db *gorm.DB) http.HandlerFunc { // @Summary Delete node pool (org scoped) // @Description Permanently deletes the node pool. // @Tags NodePools -// @Accept json // @Produce json // @Param X-Org-ID header string false "Organization UUID" // @Param id path string true "Node Pool ID (UUID)" -// @Success 204 {string} string "No Content" -// @Failure 400 {string} string "invalid id" -// @Failure 401 {string} string "Unauthorized" -// @Failure 403 {string} string "organization required" -// @Failure 500 {string} string "delete failed" +// @Success 204 "No Content" +// @Failure 400 {string} string "invalid id" +// @Failure 401 {string} string "Unauthorized" +// @Failure 403 {string} string "organization required" +// @Failure 500 {string} string "delete failed" // @Router /node-pools/{id} [delete] // @Security BearerAuth // @Security OrgKeyAuth @@ -369,16 +366,15 @@ func DeleteNodePool(db *gorm.DB) http.HandlerFunc { // @ID ListNodePoolServers // @Summary List servers attached to a node pool (org scoped) // @Tags NodePools -// @Accept json // @Produce json -// @Param X-Org-ID header string false "Organization UUID" -// @Param id path string true "Node Pool ID (UUID)" -// @Success 200 {array} dto.ServerResponse -// @Failure 400 {string} string "invalid id" -// @Failure 401 {string} string "Unauthorized" -// @Failure 403 {string} string "organization required" -// @Failure 404 {string} string "not found" -// @Failure 500 {string} string "fetch failed" +// @Param X-Org-ID header string false "Organization UUID" +// @Param id path string true "Node Pool ID (UUID)" +// @Success 200 {array} dto.ServerResponse +// @Failure 400 {string} string "invalid id" +// @Failure 401 {string} string "Unauthorized" +// @Failure 403 {string} string "organization required" +// @Failure 404 {string} string "not found" +// @Failure 500 {string} string "fetch failed" // @Router /node-pools/{id}/servers [get] // @Security BearerAuth // @Security OrgKeyAuth @@ -434,15 +430,15 @@ func ListNodePoolServers(db *gorm.DB) http.HandlerFunc { // @Tags NodePools // @Accept json // @Produce json -// @Param X-Org-ID header string false "Organization UUID" -// @Param id path string true "Node Pool ID (UUID)" -// @Param body body dto.AttachServersRequest true "Server IDs to attach" -// @Success 204 {string} string "No Content" -// @Failure 400 {string} string "invalid id / invalid server_ids" -// @Failure 401 {string} string "Unauthorized" -// @Failure 403 {string} string "organization required" -// @Failure 404 {string} string "not found" -// @Failure 500 {string} string "attach failed" +// @Param X-Org-ID header string false "Organization UUID" +// @Param id path string true "Node Pool ID (UUID)" +// @Param body body dto.AttachServersRequest true "Server IDs to attach" +// @Success 204 {string} string "No Content" +// @Failure 400 {string} string "invalid id / invalid server_ids" +// @Failure 401 {string} string "Unauthorized" +// @Failure 403 {string} string "organization required" +// @Failure 404 {string} string "not found" +// @Failure 500 {string} string "attach failed" // @Router /node-pools/{id}/servers [post] // @Security BearerAuth // @Security OrgKeyAuth @@ -521,17 +517,16 @@ func AttachNodePoolServers(db *gorm.DB) http.HandlerFunc { // @ID DetachNodePoolServer // @Summary Detach one server from a node pool (org scoped) // @Tags NodePools -// @Accept json // @Produce json -// @Param X-Org-ID header string false "Organization UUID" -// @Param id path string true "Node Pool ID (UUID)" -// @Param serverId path string true "Server ID (UUID)" -// @Success 204 {string} string "No Content" -// @Failure 400 {string} string "invalid id" -// @Failure 401 {string} string "Unauthorized" -// @Failure 403 {string} string "organization required" -// @Failure 404 {string} string "not found" -// @Failure 500 {string} string "detach failed" +// @Param X-Org-ID header string false "Organization UUID" +// @Param id path string true "Node Pool ID (UUID)" +// @Param serverId path string true "Server ID (UUID)" +// @Success 204 {string} string "No Content" +// @Failure 400 {string} string "invalid id" +// @Failure 401 {string} string "Unauthorized" +// @Failure 403 {string} string "organization required" +// @Failure 404 {string} string "not found" +// @Failure 500 {string} string "detach failed" // @Router /node-pools/{id}/servers/{serverId} [delete] // @Security BearerAuth // @Security OrgKeyAuth @@ -588,16 +583,15 @@ func DetachNodePoolServer(db *gorm.DB) http.HandlerFunc { // @ID ListNodePoolTaints // @Summary List taints attached to a node pool (org scoped) // @Tags NodePools -// @Accept json // @Produce json -// @Param X-Org-ID header string false "Organization UUID" -// @Param id path string true "Node Pool ID (UUID)" -// @Success 200 {array} dto.TaintResponse -// @Failure 400 {string} string "invalid id" -// @Failure 401 {string} string "Unauthorized" -// @Failure 403 {string} string "organization required" -// @Failure 404 {string} string "not found" -// @Failure 500 {string} string "fetch failed" +// @Param X-Org-ID header string false "Organization UUID" +// @Param id path string true "Node Pool ID (UUID)" +// @Success 200 {array} dto.TaintResponse +// @Failure 400 {string} string "invalid id" +// @Failure 401 {string} string "Unauthorized" +// @Failure 403 {string} string "organization required" +// @Failure 404 {string} string "not found" +// @Failure 500 {string} string "fetch failed" // @Router /node-pools/{id}/taints [get] // @Security BearerAuth // @Security OrgKeyAuth @@ -646,15 +640,15 @@ func ListNodePoolTaints(db *gorm.DB) http.HandlerFunc { // @Tags NodePools // @Accept json // @Produce json -// @Param X-Org-ID header string false "Organization UUID" -// @Param id path string true "Node Pool ID (UUID)" -// @Param body body dto.AttachTaintsRequest true "Taint IDs to attach" -// @Success 204 {string} string "No Content" -// @Failure 400 {string} string "invalid id / invalid taint_ids" -// @Failure 401 {string} string "Unauthorized" -// @Failure 403 {string} string "organization required" -// @Failure 404 {string} string "not found" -// @Failure 500 {string} string "attach failed" +// @Param X-Org-ID header string false "Organization UUID" +// @Param id path string true "Node Pool ID (UUID)" +// @Param body body dto.AttachTaintsRequest true "Taint IDs to attach" +// @Success 204 {string} string "No Content" +// @Failure 400 {string} string "invalid id / invalid taint_ids" +// @Failure 401 {string} string "Unauthorized" +// @Failure 403 {string} string "organization required" +// @Failure 404 {string} string "not found" +// @Failure 500 {string} string "attach failed" // @Router /node-pools/{id}/taints [post] // @Security BearerAuth // @Security OrgKeyAuth @@ -730,17 +724,16 @@ func AttachNodePoolTaints(db *gorm.DB) http.HandlerFunc { // @ID DetachNodePoolTaint // @Summary Detach one taint from a node pool (org scoped) // @Tags NodePools -// @Accept json // @Produce json -// @Param X-Org-ID header string false "Organization UUID" -// @Param id path string true "Node Pool ID (UUID)" -// @Param taintId path string true "Taint ID (UUID)" -// @Success 204 {string} string "No Content" -// @Failure 400 {string} string "invalid id" -// @Failure 401 {string} string "Unauthorized" -// @Failure 403 {string} string "organization required" -// @Failure 404 {string} string "not found" -// @Failure 500 {string} string "detach failed" +// @Param X-Org-ID header string false "Organization UUID" +// @Param id path string true "Node Pool ID (UUID)" +// @Param taintId path string true "Taint ID (UUID)" +// @Success 204 {string} string "No Content" +// @Failure 400 {string} string "invalid id" +// @Failure 401 {string} string "Unauthorized" +// @Failure 403 {string} string "organization required" +// @Failure 404 {string} string "not found" +// @Failure 500 {string} string "detach failed" // @Router /node-pools/{id}/taints/{taintId} [delete] // @Security BearerAuth // @Security OrgKeyAuth @@ -798,16 +791,15 @@ func DetachNodePoolTaint(db *gorm.DB) http.HandlerFunc { // @ID ListNodePoolLabels // @Summary List labels attached to a node pool (org scoped) // @Tags NodePools -// @Accept json // @Produce json -// @Param X-Org-ID header string false "Organization UUID" -// @Param id path string true "Label Pool ID (UUID)" -// @Success 200 {array} dto.LabelResponse -// @Failure 400 {string} string "invalid id" -// @Failure 401 {string} string "Unauthorized" -// @Failure 403 {string} string "organization required" -// @Failure 404 {string} string "not found" -// @Failure 500 {string} string "fetch failed" +// @Param X-Org-ID header string false "Organization UUID" +// @Param id path string true "Label Pool ID (UUID)" +// @Success 200 {array} dto.LabelResponse +// @Failure 400 {string} string "invalid id" +// @Failure 401 {string} string "Unauthorized" +// @Failure 403 {string} string "organization required" +// @Failure 404 {string} string "not found" +// @Failure 500 {string} string "fetch failed" // @Router /node-pools/{id}/labels [get] // @Security BearerAuth // @Security OrgKeyAuth @@ -859,15 +851,15 @@ func ListNodePoolLabels(db *gorm.DB) http.HandlerFunc { // @Tags NodePools // @Accept json // @Produce json -// @Param X-Org-ID header string false "Organization UUID" -// @Param id path string true "Node Pool ID (UUID)" -// @Param body body dto.AttachLabelsRequest true "Label IDs to attach" -// @Success 204 {string} string "No Content" -// @Failure 400 {string} string "invalid id / invalid server_ids" -// @Failure 401 {string} string "Unauthorized" -// @Failure 403 {string} string "organization required" -// @Failure 404 {string} string "not found" -// @Failure 500 {string} string "attach failed" +// @Param X-Org-ID header string false "Organization UUID" +// @Param id path string true "Node Pool ID (UUID)" +// @Param body body dto.AttachLabelsRequest true "Label IDs to attach" +// @Success 204 {string} string "No Content" +// @Failure 400 {string} string "invalid id / invalid server_ids" +// @Failure 401 {string} string "Unauthorized" +// @Failure 403 {string} string "organization required" +// @Failure 404 {string} string "not found" +// @Failure 500 {string} string "attach failed" // @Router /node-pools/{id}/labels [post] // @Security BearerAuth // @Security OrgKeyAuth @@ -940,17 +932,16 @@ func AttachNodePoolLabels(db *gorm.DB) http.HandlerFunc { // @ID DetachNodePoolLabel // @Summary Detach one label from a node pool (org scoped) // @Tags NodePools -// @Accept json // @Produce json -// @Param X-Org-ID header string false "Organization UUID" -// @Param id path string true "Node Pool ID (UUID)" -// @Param labelId path string true "Label ID (UUID)" -// @Success 204 {string} string "No Content" -// @Failure 400 {string} string "invalid id" -// @Failure 401 {string} string "Unauthorized" -// @Failure 403 {string} string "organization required" -// @Failure 404 {string} string "not found" -// @Failure 500 {string} string "detach failed" +// @Param X-Org-ID header string false "Organization UUID" +// @Param id path string true "Node Pool ID (UUID)" +// @Param labelId path string true "Label ID (UUID)" +// @Success 204 {string} string "No Content" +// @Failure 400 {string} string "invalid id" +// @Failure 401 {string} string "Unauthorized" +// @Failure 403 {string} string "organization required" +// @Failure 404 {string} string "not found" +// @Failure 500 {string} string "detach failed" // @Router /node-pools/{id}/labels/{labelId} [delete] // @Security BearerAuth // @Security OrgKeyAuth @@ -1008,16 +999,15 @@ func DetachNodePoolLabel(db *gorm.DB) http.HandlerFunc { // @ID ListNodePoolAnnotations // @Summary List annotations attached to a node pool (org scoped) // @Tags NodePools -// @Accept json // @Produce json -// @Param X-Org-ID header string false "Organization UUID" -// @Param id path string true "Node Pool ID (UUID)" -// @Success 200 {array} dto.AnnotationResponse -// @Failure 400 {string} string "invalid id" -// @Failure 401 {string} string "Unauthorized" -// @Failure 403 {string} string "organization required" -// @Failure 404 {string} string "not found" -// @Failure 500 {string} string "fetch failed" +// @Param X-Org-ID header string false "Organization UUID" +// @Param id path string true "Node Pool ID (UUID)" +// @Success 200 {array} dto.AnnotationResponse +// @Failure 400 {string} string "invalid id" +// @Failure 401 {string} string "Unauthorized" +// @Failure 403 {string} string "organization required" +// @Failure 404 {string} string "not found" +// @Failure 500 {string} string "fetch failed" // @Router /node-pools/{id}/annotations [get] // @Security BearerAuth // @Security OrgKeyAuth @@ -1069,15 +1059,15 @@ func ListNodePoolAnnotations(db *gorm.DB) http.HandlerFunc { // @Tags NodePools // @Accept json // @Produce json -// @Param X-Org-ID header string false "Organization UUID" -// @Param id path string true "Node Group ID (UUID)" -// @Param body body dto.AttachAnnotationsRequest true "Annotation IDs to attach" -// @Success 204 {string} string "No Content" -// @Failure 400 {string} string "invalid id / invalid server_ids" -// @Failure 401 {string} string "Unauthorized" -// @Failure 403 {string} string "organization required" -// @Failure 404 {string} string "not found" -// @Failure 500 {string} string "attach failed" +// @Param X-Org-ID header string false "Organization UUID" +// @Param id path string true "Node Group ID (UUID)" +// @Param body body dto.AttachAnnotationsRequest true "Annotation IDs to attach" +// @Success 204 {string} string "No Content" +// @Failure 400 {string} string "invalid id / invalid server_ids" +// @Failure 401 {string} string "Unauthorized" +// @Failure 403 {string} string "organization required" +// @Failure 404 {string} string "not found" +// @Failure 500 {string} string "attach failed" // @Router /node-pools/{id}/annotations [post] // @Security BearerAuth // @Security OrgKeyAuth @@ -1151,17 +1141,16 @@ func AttachNodePoolAnnotations(db *gorm.DB) http.HandlerFunc { // @ID DetachNodePoolAnnotation // @Summary Detach one annotation from a node pool (org scoped) // @Tags NodePools -// @Accept json // @Produce json -// @Param X-Org-ID header string false "Organization UUID" -// @Param id path string true "Node Pool ID (UUID)" -// @Param annotationId path string true "Annotation ID (UUID)" -// @Success 204 {string} string "No Content" -// @Failure 400 {string} string "invalid id" -// @Failure 401 {string} string "Unauthorized" -// @Failure 403 {string} string "organization required" -// @Failure 404 {string} string "not found" -// @Failure 500 {string} string "detach failed" +// @Param X-Org-ID header string false "Organization UUID" +// @Param id path string true "Node Pool ID (UUID)" +// @Param annotationId path string true "Annotation ID (UUID)" +// @Success 204 {string} string "No Content" +// @Failure 400 {string} string "invalid id" +// @Failure 401 {string} string "Unauthorized" +// @Failure 403 {string} string "organization required" +// @Failure 404 {string} string "not found" +// @Failure 500 {string} string "detach failed" // @Router /node-pools/{id}/annotations/{annotationId} [delete] // @Security BearerAuth // @Security OrgKeyAuth diff --git a/internal/handlers/node_pools_test.go b/internal/handlers/node_pools_test.go new file mode 100644 index 0000000..f3c800c --- /dev/null +++ b/internal/handlers/node_pools_test.go @@ -0,0 +1,381 @@ +package handlers + +import ( + "os" + "testing" + + "github.com/glueops/autoglue/internal/common" + "github.com/glueops/autoglue/internal/models" + "github.com/glueops/autoglue/internal/testutil/pgtest" + "github.com/google/uuid" + "gorm.io/gorm" +) + +func TestMain(m *testing.M) { + code := m.Run() + pgtest.Stop() + os.Exit(code) +} + +func TestParseUUIDs_Success(t *testing.T) { + u1 := uuid.New() + u2 := uuid.New() + + got, err := parseUUIDs([]string{u1.String(), u2.String()}) + if err != nil { + t.Fatalf("parseUUIDs returned error: %v", err) + } + if len(got) != 2 { + t.Fatalf("expected 2 UUIDs, got %d", len(got)) + } + if got[0] != u1 || got[1] != u2 { + t.Fatalf("unexpected UUIDs: got=%v", got) + } +} + +func TestParseUUIDs_Invalid(t *testing.T) { + _, err := parseUUIDs([]string{"not-a-uuid"}) + if err == nil { + t.Fatalf("expected error for invalid UUID, got nil") + } +} + +// --- ensureServersBelongToOrg --- + +func TestEnsureServersBelongToOrg_AllBelong(t *testing.T) { + db := pgtest.DB(t) + + org := models.Organization{Name: "org-a"} + if err := db.Create(&org).Error; err != nil { + t.Fatalf("create org: %v", err) + } + + sshKey := createTestSshKey(t, db, org.ID, "org-a-key") + + s1 := models.Server{ + OrganizationID: org.ID, + Hostname: "srv-1", + SSHUser: "ubuntu", + SshKeyID: sshKey.ID, + Role: "worker", + Status: "pending", + } + s2 := models.Server{ + OrganizationID: org.ID, + Hostname: "srv-2", + SSHUser: "ubuntu", + SshKeyID: sshKey.ID, + Role: "worker", + Status: "pending", + } + + if err := db.Create(&s1).Error; err != nil { + t.Fatalf("create server 1: %v", err) + } + if err := db.Create(&s2).Error; err != nil { + t.Fatalf("create server 2: %v", err) + } + + ids := []uuid.UUID{s1.ID, s2.ID} + if err := ensureServersBelongToOrg(org.ID, ids, db); err != nil { + t.Fatalf("expected no error, got %v", err) + } +} + +func TestEnsureServersBelongToOrg_ForeignOrgFails(t *testing.T) { + db := pgtest.DB(t) + + orgA := models.Organization{Name: "org-a"} + orgB := models.Organization{Name: "org-b"} + + if err := db.Create(&orgA).Error; err != nil { + t.Fatalf("create orgA: %v", err) + } + if err := db.Create(&orgB).Error; err != nil { + t.Fatalf("create orgB: %v", err) + } + + sshKeyA := createTestSshKey(t, db, orgA.ID, "org-a-key") + sshKeyB := createTestSshKey(t, db, orgB.ID, "org-b-key") + + s1 := models.Server{ + OrganizationID: orgA.ID, + Hostname: "srv-a-1", + SSHUser: "ubuntu", + SshKeyID: sshKeyA.ID, + Role: "worker", + Status: "pending", + } + s2 := models.Server{ + OrganizationID: orgB.ID, + Hostname: "srv-b-1", + SSHUser: "ubuntu", + SshKeyID: sshKeyB.ID, + Role: "worker", + Status: "pending", + } + + if err := db.Create(&s1).Error; err != nil { + t.Fatalf("create server s1: %v", err) + } + if err := db.Create(&s2).Error; err != nil { + t.Fatalf("create server s2: %v", err) + } + + ids := []uuid.UUID{s1.ID, s2.ID} + if err := ensureServersBelongToOrg(orgA.ID, ids, db); err == nil { + t.Fatalf("expected error when one server belongs to a different org") + } +} + +// --- ensureTaintsBelongToOrg --- + +func TestEnsureTaintsBelongToOrg_AllBelong(t *testing.T) { + db := pgtest.DB(t) + + org := models.Organization{Name: "org-taints"} + if err := db.Create(&org).Error; err != nil { + t.Fatalf("create org: %v", err) + } + + t1 := models.Taint{ + OrganizationID: org.ID, + Key: "key1", + Value: "val1", + Effect: "NoSchedule", + } + t2 := models.Taint{ + OrganizationID: org.ID, + Key: "key2", + Value: "val2", + Effect: "PreferNoSchedule", + } + + if err := db.Create(&t1).Error; err != nil { + t.Fatalf("create taint 1: %v", err) + } + if err := db.Create(&t2).Error; err != nil { + t.Fatalf("create taint 2: %v", err) + } + + ids := []uuid.UUID{t1.ID, t2.ID} + if err := ensureTaintsBelongToOrg(org.ID, ids, db); err != nil { + t.Fatalf("expected no error, got %v", err) + } +} + +func TestEnsureTaintsBelongToOrg_ForeignOrgFails(t *testing.T) { + db := pgtest.DB(t) + + orgA := models.Organization{Name: "org-a"} + orgB := models.Organization{Name: "org-b"} + if err := db.Create(&orgA).Error; err != nil { + t.Fatalf("create orgA: %v", err) + } + if err := db.Create(&orgB).Error; err != nil { + t.Fatalf("create orgB: %v", err) + } + + t1 := models.Taint{ + OrganizationID: orgA.ID, + Key: "key1", + Value: "val1", + Effect: "NoSchedule", + } + t2 := models.Taint{ + OrganizationID: orgB.ID, + Key: "key2", + Value: "val2", + Effect: "NoSchedule", + } + + if err := db.Create(&t1).Error; err != nil { + t.Fatalf("create taint 1: %v", err) + } + if err := db.Create(&t2).Error; err != nil { + t.Fatalf("create taint 2: %v", err) + } + + ids := []uuid.UUID{t1.ID, t2.ID} + if err := ensureTaintsBelongToOrg(orgA.ID, ids, db); err == nil { + t.Fatalf("expected error when a taint belongs to another org") + } +} + +// --- ensureLabelsBelongToOrg --- + +func TestEnsureLabelsBelongToOrg_AllBelong(t *testing.T) { + db := pgtest.DB(t) + + org := models.Organization{Name: "org-labels"} + if err := db.Create(&org).Error; err != nil { + t.Fatalf("create org: %v", err) + } + + l1 := models.Label{ + AuditFields: common.AuditFields{ + OrganizationID: org.ID, + }, + Key: "env", + Value: "dev", + } + l2 := models.Label{ + AuditFields: common.AuditFields{ + OrganizationID: org.ID, + }, + Key: "env", + Value: "prod", + } + + if err := db.Create(&l1).Error; err != nil { + t.Fatalf("create label 1: %v", err) + } + if err := db.Create(&l2).Error; err != nil { + t.Fatalf("create label 2: %v", err) + } + + ids := []uuid.UUID{l1.ID, l2.ID} + if err := ensureLabelsBelongToOrg(org.ID, ids, db); err != nil { + t.Fatalf("expected no error, got %v", err) + } +} + +func TestEnsureLabelsBelongToOrg_ForeignOrgFails(t *testing.T) { + db := pgtest.DB(t) + + orgA := models.Organization{Name: "org-a"} + orgB := models.Organization{Name: "org-b"} + if err := db.Create(&orgA).Error; err != nil { + t.Fatalf("create orgA: %v", err) + } + if err := db.Create(&orgB).Error; err != nil { + t.Fatalf("create orgB: %v", err) + } + + l1 := models.Label{ + AuditFields: common.AuditFields{ + OrganizationID: orgA.ID, + }, + Key: "env", + Value: "dev", + } + l2 := models.Label{ + AuditFields: common.AuditFields{ + OrganizationID: orgB.ID, + }, + Key: "env", + Value: "prod", + } + + if err := db.Create(&l1).Error; err != nil { + t.Fatalf("create label 1: %v", err) + } + if err := db.Create(&l2).Error; err != nil { + t.Fatalf("create label 2: %v", err) + } + + ids := []uuid.UUID{l1.ID, l2.ID} + if err := ensureLabelsBelongToOrg(orgA.ID, ids, db); err == nil { + t.Fatalf("expected error when a label belongs to another org") + } +} + +// --- ensureAnnotaionsBelongToOrg (typo in original name is preserved) --- + +func TestEnsureAnnotationsBelongToOrg_AllBelong(t *testing.T) { + db := pgtest.DB(t) + + org := models.Organization{Name: "org-annotations"} + if err := db.Create(&org).Error; err != nil { + t.Fatalf("create org: %v", err) + } + + a1 := models.Annotation{ + AuditFields: common.AuditFields{ + OrganizationID: org.ID, + }, + Key: "team", + Value: "core", + } + a2 := models.Annotation{ + AuditFields: common.AuditFields{ + OrganizationID: org.ID, + }, + Key: "team", + Value: "platform", + } + + if err := db.Create(&a1).Error; err != nil { + t.Fatalf("create annotation 1: %v", err) + } + if err := db.Create(&a2).Error; err != nil { + t.Fatalf("create annotation 2: %v", err) + } + + ids := []uuid.UUID{a1.ID, a2.ID} + if err := ensureAnnotaionsBelongToOrg(org.ID, ids, db); err != nil { + t.Fatalf("expected no error, got %v", err) + } +} + +func TestEnsureAnnotationsBelongToOrg_ForeignOrgFails(t *testing.T) { + db := pgtest.DB(t) + + orgA := models.Organization{Name: "org-a"} + orgB := models.Organization{Name: "org-b"} + if err := db.Create(&orgA).Error; err != nil { + t.Fatalf("create orgA: %v", err) + } + if err := db.Create(&orgB).Error; err != nil { + t.Fatalf("create orgB: %v", err) + } + + a1 := models.Annotation{ + AuditFields: common.AuditFields{ + OrganizationID: orgA.ID, + }, + Key: "team", + Value: "core", + } + a2 := models.Annotation{ + AuditFields: common.AuditFields{ + OrganizationID: orgB.ID, + }, + Key: "team", + Value: "platform", + } + + if err := db.Create(&a1).Error; err != nil { + t.Fatalf("create annotation 1: %v", err) + } + if err := db.Create(&a2).Error; err != nil { + t.Fatalf("create annotation 2: %v", err) + } + + ids := []uuid.UUID{a1.ID, a2.ID} + if err := ensureAnnotaionsBelongToOrg(orgA.ID, ids, db); err == nil { + t.Fatalf("expected error when an annotation belongs to another org") + } +} + +func createTestSshKey(t *testing.T, db *gorm.DB, orgID uuid.UUID, name string) models.SshKey { + t.Helper() + + key := models.SshKey{ + AuditFields: common.AuditFields{ + OrganizationID: orgID, + }, + Name: name, + PublicKey: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAITestKey", + EncryptedPrivateKey: "encrypted", + PrivateIV: "iv", + PrivateTag: "tag", + Fingerprint: "fp-" + name, + } + + if err := db.Create(&key).Error; err != nil { + t.Fatalf("create ssh key %s: %v", name, err) + } + + return key +} diff --git a/internal/handlers/servers.go b/internal/handlers/servers.go index c7e837e..271cc5e 100644 --- a/internal/handlers/servers.go +++ b/internal/handlers/servers.go @@ -22,7 +22,6 @@ import ( // @Summary List servers (org scoped) // @Description Returns servers for the organization in X-Org-ID. Optional filters: status, role. // @Tags Servers -// @Accept json // @Produce json // @Param X-Org-ID header string false "Organization UUID" // @Param status query string false "Filter by status (pending|provisioning|ready|failed)" @@ -89,7 +88,6 @@ func ListServers(db *gorm.DB) http.HandlerFunc { // @Summary Get server by ID (org scoped) // @Description Returns one server in the given organization. // @Tags Servers -// @Accept json // @Produce json // @Param X-Org-ID header string false "Organization UUID" // @Param id path string true "Server ID (UUID)" @@ -329,11 +327,10 @@ func UpdateServer(db *gorm.DB) http.HandlerFunc { // @Summary Delete server (org scoped) // @Description Permanently deletes the server. // @Tags Servers -// @Accept json // @Produce json -// @Param X-Org-ID header string false "Organization UUID" -// @Param id path string true "Server ID (UUID)" -// @Success 204 {string} string "No Content" +// @Param X-Org-ID header string false "Organization UUID" +// @Param id path string true "Server ID (UUID)" +// @Success 204 "No Content" // @Failure 400 {string} string "invalid id" // @Failure 401 {string} string "Unauthorized" // @Failure 403 {string} string "organization required" diff --git a/internal/handlers/servers_test.go b/internal/handlers/servers_test.go new file mode 100644 index 0000000..ce5cc5d --- /dev/null +++ b/internal/handlers/servers_test.go @@ -0,0 +1,78 @@ +package handlers + +import ( + "testing" + + "github.com/glueops/autoglue/internal/models" + "github.com/glueops/autoglue/internal/testutil/pgtest" + "github.com/google/uuid" +) + +func TestValidStatus(t *testing.T) { + // known-good statuses from servers.go + valid := []string{"pending", "provisioning", "ready", "failed"} + for _, s := range valid { + if !validStatus(s) { + t.Errorf("expected validStatus(%q) = true, got false", s) + } + } + + invalid := []string{"foobar", "unknown"} + for _, s := range invalid { + if validStatus(s) { + t.Errorf("expected validStatus(%q) = false, got true", s) + } + } +} + +func TestEnsureKeyBelongsToOrg_Success(t *testing.T) { + db := pgtest.DB(t) + + org := models.Organization{Name: "servers-org"} + if err := db.Create(&org).Error; err != nil { + t.Fatalf("create org: %v", err) + } + + key := createTestSshKey(t, db, org.ID, "org-key") + + if err := ensureKeyBelongsToOrg(org.ID, key.ID, db); err != nil { + t.Fatalf("expected no error, got %v", err) + } +} + +func TestEnsureKeyBelongsToOrg_WrongOrg(t *testing.T) { + db := pgtest.DB(t) + + orgA := models.Organization{Name: "org-a"} + orgB := models.Organization{Name: "org-b"} + + if err := db.Create(&orgA).Error; err != nil { + t.Fatalf("create orgA: %v", err) + } + if err := db.Create(&orgB).Error; err != nil { + t.Fatalf("create orgB: %v", err) + } + + keyA := createTestSshKey(t, db, orgA.ID, "org-a-key") + + // ask for orgB with a key that belongs to orgA → should fail + if err := ensureKeyBelongsToOrg(orgB.ID, keyA.ID, db); err == nil { + t.Fatalf("expected error when ssh key belongs to a different org, got nil") + } +} + +func TestEnsureKeyBelongsToOrg_NotFound(t *testing.T) { + db := pgtest.DB(t) + + org := models.Organization{Name: "org-nokey"} + if err := db.Create(&org).Error; err != nil { + t.Fatalf("create org: %v", err) + } + + // random keyID that doesn't exist + randomKeyID := uuid.New() + + if err := ensureKeyBelongsToOrg(org.ID, randomKeyID, db); err == nil { + t.Fatalf("expected error when ssh key does not exist, got nil") + } +} diff --git a/internal/handlers/ssh_keys.go b/internal/handlers/ssh_keys.go index 72e5615..430d510 100644 --- a/internal/handlers/ssh_keys.go +++ b/internal/handlers/ssh_keys.go @@ -31,7 +31,6 @@ import ( // @Summary List ssh keys (org scoped) // @Description Returns ssh keys for the organization in X-Org-ID. // @Tags Ssh -// @Accept json // @Produce json // @Param X-Org-ID header string false "Organization UUID" // @Success 200 {array} dto.SshResponse @@ -189,7 +188,6 @@ func CreateSSHKey(db *gorm.DB) http.HandlerFunc { // @Summary Get ssh key by ID (org scoped) // @Description Returns public key fields. Append `?reveal=true` to include the private key PEM. // @Tags Ssh -// @Accept json // @Produce json // @Param X-Org-ID header string false "Organization UUID" // @Param id path string true "SSH Key ID (UUID)" @@ -283,11 +281,10 @@ func GetSSHKey(db *gorm.DB) http.HandlerFunc { // @Summary Delete ssh keypair (org scoped) // @Description Permanently deletes a keypair. // @Tags Ssh -// @Accept json // @Produce json -// @Param X-Org-ID header string false "Organization UUID" -// @Param id path string true "SSH Key ID (UUID)" -// @Success 204 {string} string "No Content" +// @Param X-Org-ID header string false "Organization UUID" +// @Param id path string true "SSH Key ID (UUID)" +// @Success 204 "No Content" // @Failure 400 {string} string "invalid id" // @Failure 401 {string} string "Unauthorized" // @Failure 403 {string} string "organization required" diff --git a/internal/handlers/taints.go b/internal/handlers/taints.go index acb99f2..30e357d 100644 --- a/internal/handlers/taints.go +++ b/internal/handlers/taints.go @@ -22,7 +22,6 @@ import ( // @Summary List node pool taints (org scoped) // @Description Returns node taints for the organization in X-Org-ID. Filters: `key`, `value`, and `q` (key contains). Add `include=node_pools` to include linked node pools. // @Tags Taints -// @Accept json // @Produce json // @Param X-Org-ID header string false "Organization UUID" // @Param key query string false "Exact key" @@ -70,7 +69,6 @@ func ListTaints(db *gorm.DB) http.HandlerFunc { // @ID GetTaint // @Summary Get node taint by ID (org scoped) // @Tags Taints -// @Accept json // @Produce json // @Param X-Org-ID header string false "Organization UUID" // @Param id path string true "Node Taint ID (UUID)" @@ -279,11 +277,10 @@ func UpdateTaint(db *gorm.DB) http.HandlerFunc { // @Summary Delete taint (org scoped) // @Description Permanently deletes the taint. // @Tags Taints -// @Accept json // @Produce json -// @Param X-Org-ID header string false "Organization UUID" -// @Param id path string true "Node Taint ID (UUID)" -// @Success 204 {string} string "No Content" +// @Param X-Org-ID header string false "Organization UUID" +// @Param id path string true "Node Taint ID (UUID)" +// @Success 204 "No Content" // @Failure 400 {string} string "invalid id" // @Failure 401 {string} string "Unauthorized" // @Failure 403 {string} string "organization required" diff --git a/internal/handlers/version.go b/internal/handlers/version.go index 15f3d6f..07e2c18 100644 --- a/internal/handlers/version.go +++ b/internal/handlers/version.go @@ -30,7 +30,6 @@ type VersionResponse struct { // @Description Returns build/runtime metadata for the running service. // @Tags Meta // @ID Version // operationId -// @Accept json // @Produce json // @Success 200 {object} VersionResponse // @Router /version [get] diff --git a/internal/models/cluster.go b/internal/models/cluster.go index 71400c0..bb97f69 100644 --- a/internal/models/cluster.go +++ b/internal/models/cluster.go @@ -6,28 +6,47 @@ import ( "github.com/google/uuid" ) +const ( + ClusterStatusPrePending = "pre_pending" // needs validation + ClusterStatusIncomplete = "incomplete" // invalid/missing shape + ClusterStatusPending = "pending" // valid shape, waiting for provisioning + ClusterStatusProvisioning = "provisioning" + ClusterStatusReady = "ready" + ClusterStatusFailed = "failed" // provisioning/runtime failure +) + type Cluster struct { - ID uuid.UUID `gorm:"type:uuid;default:gen_random_uuid();primaryKey" json:"id"` - OrganizationID uuid.UUID `gorm:"type:uuid;not null" json:"organization_id"` - Organization Organization `gorm:"foreignKey:OrganizationID;constraint:OnDelete:CASCADE" json:"organization"` - Name string `gorm:"not null" json:"name"` - Provider string `json:"provider"` - Region string `json:"region"` - Status string `json:"status"` - CaptainDomain string `gorm:"not null" json:"captain_domain"` // nonprod.earth.onglueops.rocks - AppsLoadBalancer string `json:"cluster_load_balancer"` // {public_ip: 1.2.3.4, private_ip: 10.0.30.1, name: apps.CaqptainDomain} - GlueOpsLoadBalancer string `json:"control_load_balancer"` // {public_ip: 5.6.7.8, private_ip: 10.0.22.1, name: CaptainDomain} + ID uuid.UUID `gorm:"type:uuid;default:gen_random_uuid();primaryKey" json:"id"` + OrganizationID uuid.UUID `gorm:"type:uuid;not null" json:"organization_id"` + Organization Organization `gorm:"foreignKey:OrganizationID;constraint:OnDelete:CASCADE" json:"organization"` - ControlPlane string `json:"control_plane"` // <- dns cntlpn + Name string `gorm:"not null" json:"name"` + Provider string `json:"provider"` + Region string `json:"region"` - RandomToken string `json:"random_token"` - CertificateKey string `json:"certificate_key"` - EncryptedKubeconfig string `gorm:"type:text" json:"-"` - KubeIV string `json:"-"` - KubeTag string `json:"-"` - NodePools []NodePool `gorm:"many2many:cluster_node_pools;constraint:OnDelete:CASCADE" json:"node_pools,omitempty"` - BastionServerID *uuid.UUID `gorm:"type:uuid" json:"bastion_server_id,omitempty"` - BastionServer *Server `gorm:"foreignKey:BastionServerID" json:"bastion_server,omitempty"` - CreatedAt time.Time `json:"created_at,omitempty" gorm:"type:timestamptz;column:created_at;not null;default:now()"` - UpdatedAt time.Time `json:"updated_at,omitempty" gorm:"type:timestamptz;autoUpdateTime;column:updated_at;not null;default:now()"` + Status string `gorm:"type:varchar(20);not null;default:'pre_pending'" json:"status"` + LastError string `gorm:"type:text;not null;default:''" json:"last_error"` + + CaptainDomainID *uuid.UUID `gorm:"type:uuid" json:"captain_domain_id"` + CaptainDomain Domain `gorm:"foreignKey:CaptainDomainID" json:"captain_domain"` + ControlPlaneRecordSetID *uuid.UUID `gorm:"type:uuid" json:"control_plane_record_set_id,omitempty"` + ControlPlaneRecordSet *RecordSet `gorm:"foreignKey:ControlPlaneRecordSetID" json:"control_plane_record_set,omitempty"` + AppsLoadBalancerID *uuid.UUID `gorm:"type:uuid" json:"apps_load_balancer_id,omitempty"` + AppsLoadBalancer *LoadBalancer `gorm:"foreignKey:AppsLoadBalancerID" json:"apps_load_balancer,omitempty"` + GlueOpsLoadBalancerID *uuid.UUID `gorm:"type:uuid" json:"glueops_load_balancer_id,omitempty"` + GlueOpsLoadBalancer *LoadBalancer `gorm:"foreignKey:GlueOpsLoadBalancerID" json:"glueops_load_balancer,omitempty"` + BastionServerID *uuid.UUID `gorm:"type:uuid" json:"bastion_server_id,omitempty"` + BastionServer *Server `gorm:"foreignKey:BastionServerID" json:"bastion_server,omitempty"` + + NodePools []NodePool `gorm:"many2many:cluster_node_pools;constraint:OnDelete:CASCADE" json:"node_pools,omitempty"` + + RandomToken string `json:"random_token"` + CertificateKey string `json:"certificate_key"` + + EncryptedKubeconfig string `gorm:"type:text" json:"-"` + KubeIV string `json:"-"` + KubeTag string `json:"-"` + + CreatedAt time.Time `json:"created_at,omitempty" gorm:"type:timestamptz;column:created_at;not null;default:now()"` + UpdatedAt time.Time `json:"updated_at,omitempty" gorm:"type:timestamptz;autoUpdateTime;column:updated_at;not null;default:now()"` } diff --git a/internal/models/load_balancer.go b/internal/models/load_balancer.go new file mode 100644 index 0000000..5a11b13 --- /dev/null +++ b/internal/models/load_balancer.go @@ -0,0 +1,19 @@ +package models + +import ( + "time" + + "github.com/google/uuid" +) + +type LoadBalancer struct { + ID uuid.UUID `json:"id" gorm:"primaryKey;type:uuid;default:gen_random_uuid()"` + OrganizationID uuid.UUID `json:"organization_id" gorm:"type:uuid;index"` + Organization Organization `json:"organization" gorm:"foreignKey:OrganizationID;constraint:OnDelete:CASCADE"` + Name string `json:"name" gorm:"not null"` + Kind string `json:"kind" gorm:"not null"` + PublicIPAddress string `json:"public_ip_address" gorm:"not null"` + PrivateIPAddress string `json:"private_ip_address" gorm:"not null"` + CreatedAt time.Time `json:"created_at,omitempty" gorm:"type:timestamptz;column:created_at;not null;default:now()"` + UpdatedAt time.Time `json:"updated_at,omitempty" gorm:"type:timestamptz;autoUpdateTime;column:updated_at;not null;default:now()"` +} diff --git a/internal/testutil/pgtest/pgtest.go b/internal/testutil/pgtest/pgtest.go new file mode 100644 index 0000000..6ffd2f3 --- /dev/null +++ b/internal/testutil/pgtest/pgtest.go @@ -0,0 +1,119 @@ +package pgtest + +import ( + "fmt" + "log" + "sync" + "testing" + "time" + + embeddedpostgres "github.com/fergusstrange/embedded-postgres" + "github.com/glueops/autoglue/internal/db" + "github.com/glueops/autoglue/internal/models" + "gorm.io/driver/postgres" + "gorm.io/gorm" +) + +var ( + once sync.Once + epg *embeddedpostgres.EmbeddedPostgres + gdb *gorm.DB + initErr error + dsn string +) + +// initDB is called once via sync.Once. It starts embedded Postgres, +// opens a GORM connection and runs the same migrations as NewRuntime. +func initDB() { + const port uint32 = 55432 + + cfg := embeddedpostgres. + DefaultConfig(). + Database("autoglue_test"). + Username("autoglue"). + Password("autoglue"). + Port(port). + StartTimeout(30 * time.Second) + + epg = embeddedpostgres.NewDatabase(cfg) + if err := epg.Start(); err != nil { + initErr = fmt.Errorf("start embedded postgres: %w", err) + return + } + + dsn = fmt.Sprintf( + "host=127.0.0.1 port=%d user=%s password=%s dbname=%s sslmode=disable", + port, + "autoglue", + "autoglue", + "autoglue_test", + ) + + dbConn, err := gorm.Open(postgres.Open(dsn), &gorm.Config{}) + if err != nil { + initErr = fmt.Errorf("open gorm: %w", err) + return + } + + // Use the same model list as app.NewRuntime so schema matches prod + if err := db.Run( + dbConn, + &models.Job{}, + &models.MasterKey{}, + &models.SigningKey{}, + &models.User{}, + &models.Organization{}, + &models.Account{}, + &models.Membership{}, + &models.APIKey{}, + &models.UserEmail{}, + &models.RefreshToken{}, + &models.OrganizationKey{}, + &models.SshKey{}, + &models.Server{}, + &models.Taint{}, + &models.Label{}, + &models.Annotation{}, + &models.NodePool{}, + &models.Cluster{}, + &models.Credential{}, + &models.Domain{}, + &models.RecordSet{}, + ); err != nil { + initErr = fmt.Errorf("migrate: %w", err) + return + } + + gdb = dbConn +} + +// DB returns a lazily-initialized *gorm.DB backed by embedded Postgres. +// +// Call this from any test that needs a real DB. If init fails, the test +// will fail immediately with a clear message. +func DB(t *testing.T) *gorm.DB { + t.Helper() + once.Do(initDB) + if initErr != nil { + t.Fatalf("failed to init embedded postgres: %v", initErr) + } + return gdb +} + +// URL returns the DSN for the embedded Postgres instance, useful for code +// that expects a DB URL (e.g. bg.NewJobs). +func URL(t *testing.T) string { + t.Helper() + DB(t) // ensure initialized + return dsn +} + +// Stop stops the embedded Postgres process. Call from TestMain in at +// least one package, or let the OS clean it up on process exit. +func Stop() { + if epg != nil { + if err := epg.Stop(); err != nil { + log.Printf("stop embedded postgres: %v", err) + } + } +} diff --git a/internal/web/static.go b/internal/web/static.go index 4d5a652..6e3b91d 100644 --- a/internal/web/static.go +++ b/internal/web/static.go @@ -67,7 +67,13 @@ func SPAHandler() (http.Handler, error) { return } - filePath := strings.TrimPrefix(path.Clean(r.URL.Path), "/") + raw := strings.TrimSpace(r.URL.Path) + if raw == "" || raw == "/" { + raw = "/index.html" + } + + clean := path.Clean("/" + raw) // nosemgrep: autoglue.filesystem.no-path-clean + filePath := strings.TrimPrefix(clean, "/") if filePath == "" { filePath = "index.html" } diff --git a/main.go b/main.go index 700a931..d21063a 100644 --- a/main.go +++ b/main.go @@ -1,21 +1,20 @@ package main import ( - "os" - "github.com/glueops/autoglue/cmd" - "github.com/glueops/autoglue/docs" - "github.com/joho/godotenv" ) // @title AutoGlue API // @version 1.0 // @description API for managing K3s clusters across cloud providers - // @contact.name GlueOps -// @BasePath /api/v1 -// @schemes http https +// @servers.url https://autoglue.onglueops.rocks/api/v1 +// @servers.description Production API +// @servers.url https://autoglue.apps.nonprod.earth.onglueops.rocks/api/v1 +// @servers.description Staging API +// @servers.url http://localhost:8080/api/v1 +// @servers.description Local dev // @securityDefinitions.apikey BearerAuth // @in header @@ -38,7 +37,5 @@ import ( // @description Org-level secret func main() { - _ = godotenv.Load() - docs.SwaggerInfo.Host = os.Getenv("SWAGGER_HOST") cmd.Execute() } diff --git a/postgres/Dockerfile b/postgres/Dockerfile index a1fdc4c..9333ad1 100644 --- a/postgres/Dockerfile +++ b/postgres/Dockerfile @@ -6,5 +6,5 @@ RUN cd /var/lib/postgresql/ && \ openssl req -x509 -in server.req -text -key server.key -out server.crt && \ chmod 600 server.key && \ chown postgres:postgres server.key - +USER non-root CMD ["postgres", "-c", "ssl=on", "-c", "ssl_cert_file=/var/lib/postgresql/server.crt", "-c", "ssl_key_file=/var/lib/postgresql/server.key" ] diff --git a/schema.sql b/schema.sql new file mode 100644 index 0000000..4173526 --- /dev/null +++ b/schema.sql @@ -0,0 +1,430 @@ +-- Add new schema named "public" +CREATE SCHEMA IF NOT EXISTS "public"; +-- Set comment to schema: "public" +COMMENT ON SCHEMA "public" IS 'standard public schema'; +-- Create "jobs" table +CREATE TABLE "public"."jobs" ( + "id" character varying NOT NULL, + "queue_name" character varying NOT NULL, + "status" character varying NOT NULL, + "arguments" jsonb NOT NULL DEFAULT '{}', + "result" jsonb NOT NULL DEFAULT '{}', + "last_error" character varying NULL, + "retry_count" bigint NOT NULL DEFAULT 0, + "max_retry" bigint NOT NULL DEFAULT 0, + "retry_interval" bigint NOT NULL DEFAULT 0, + "scheduled_at" timestamptz NULL DEFAULT now(), + "started_at" timestamptz NULL, + "created_at" timestamptz NOT NULL DEFAULT now(), + "updated_at" timestamptz NOT NULL DEFAULT now(), + PRIMARY KEY ("id") +); +-- Create index "idx_jobs_scheduled_at" to table: "jobs" +CREATE INDEX "idx_jobs_scheduled_at" ON "public"."jobs" ("scheduled_at"); +-- Create index "idx_jobs_started_at" to table: "jobs" +CREATE INDEX "idx_jobs_started_at" ON "public"."jobs" ("started_at"); +-- Create "api_keys" table +CREATE TABLE "public"."api_keys" ( + "id" uuid NOT NULL DEFAULT gen_random_uuid(), + "name" text NOT NULL DEFAULT '', + "key_hash" text NOT NULL, + "scope" text NOT NULL DEFAULT '', + "user_id" text NULL, + "org_id" text NULL, + "secret_hash" text NULL, + "expires_at" timestamptz NULL, + "revoked" boolean NOT NULL DEFAULT false, + "prefix" text NULL, + "last_used_at" timestamptz NULL, + "created_at" timestamptz NOT NULL DEFAULT now(), + "updated_at" timestamptz NOT NULL DEFAULT now(), + PRIMARY KEY ("id") +); +-- Create index "idx_api_keys_key_hash" to table: "api_keys" +CREATE UNIQUE INDEX "idx_api_keys_key_hash" ON "public"."api_keys" ("key_hash"); +-- Create "refresh_tokens" table +CREATE TABLE "public"."refresh_tokens" ( + "id" uuid NOT NULL DEFAULT gen_random_uuid(), + "user_id" text NOT NULL, + "family_id" uuid NOT NULL, + "token_hash" text NOT NULL, + "expires_at" timestamptz NOT NULL, + "revoked_at" timestamptz NULL, + "created_at" timestamptz NOT NULL DEFAULT now(), + PRIMARY KEY ("id") +); +-- Create index "idx_refresh_tokens_family_id" to table: "refresh_tokens" +CREATE INDEX "idx_refresh_tokens_family_id" ON "public"."refresh_tokens" ("family_id"); +-- Create index "idx_refresh_tokens_token_hash" to table: "refresh_tokens" +CREATE UNIQUE INDEX "idx_refresh_tokens_token_hash" ON "public"."refresh_tokens" ("token_hash"); +-- Create index "idx_refresh_tokens_user_id" to table: "refresh_tokens" +CREATE INDEX "idx_refresh_tokens_user_id" ON "public"."refresh_tokens" ("user_id"); +-- Create "signing_keys" table +CREATE TABLE "public"."signing_keys" ( + "id" uuid NOT NULL DEFAULT gen_random_uuid(), + "kid" text NOT NULL, + "alg" text NOT NULL, + "use" text NOT NULL DEFAULT 'sig', + "is_active" boolean NOT NULL DEFAULT true, + "public_pem" text NOT NULL, + "private_pem" text NOT NULL, + "not_before" timestamptz NULL, + "expires_at" timestamptz NULL, + "created_at" timestamptz NOT NULL DEFAULT now(), + "updated_at" timestamptz NOT NULL DEFAULT now(), + "rotated_from" text NULL, + PRIMARY KEY ("id") +); +-- Create index "idx_signing_keys_kid" to table: "signing_keys" +CREATE UNIQUE INDEX "idx_signing_keys_kid" ON "public"."signing_keys" ("kid"); +-- Create "users" table +CREATE TABLE "public"."users" ( + "id" uuid NOT NULL DEFAULT gen_random_uuid(), + "display_name" text NULL, + "primary_email" text NULL, + "avatar_url" text NULL, + "is_disabled" boolean NULL, + "is_admin" boolean NULL DEFAULT false, + "created_at" timestamptz NOT NULL DEFAULT now(), + "updated_at" timestamptz NOT NULL DEFAULT now(), + PRIMARY KEY ("id") +); +-- Create "accounts" table +CREATE TABLE "public"."accounts" ( + "id" uuid NOT NULL DEFAULT gen_random_uuid(), + "user_id" uuid NOT NULL, + "provider" text NOT NULL, + "subject" text NOT NULL, + "email" text NULL, + "email_verified" boolean NOT NULL DEFAULT false, + "profile" jsonb NOT NULL DEFAULT '{}', + "secret_hash" text NULL, + "created_at" timestamptz NOT NULL DEFAULT now(), + "updated_at" timestamptz NOT NULL DEFAULT now(), + PRIMARY KEY ("id"), + CONSTRAINT "fk_accounts_user" FOREIGN KEY ("user_id") REFERENCES "public"."users" ("id") ON UPDATE NO ACTION ON DELETE NO ACTION +); +-- Create index "idx_accounts_user_id" to table: "accounts" +CREATE INDEX "idx_accounts_user_id" ON "public"."accounts" ("user_id"); +-- Create "organizations" table +CREATE TABLE "public"."organizations" ( + "id" uuid NOT NULL DEFAULT gen_random_uuid(), + "name" text NOT NULL, + "domain" text NULL, + "created_at" timestamptz NOT NULL DEFAULT now(), + "updated_at" timestamptz NOT NULL DEFAULT now(), + PRIMARY KEY ("id") +); +-- Create index "idx_organizations_domain" to table: "organizations" +CREATE INDEX "idx_organizations_domain" ON "public"."organizations" ("domain"); +-- Create "annotations" table +CREATE TABLE "public"."annotations" ( + "id" uuid NOT NULL DEFAULT gen_random_uuid(), + "organization_id" uuid NULL, + "created_at" timestamptz NOT NULL DEFAULT now(), + "updated_at" timestamptz NOT NULL DEFAULT now(), + "key" text NOT NULL, + "value" text NOT NULL, + PRIMARY KEY ("id"), + CONSTRAINT "fk_annotations_organization" FOREIGN KEY ("organization_id") REFERENCES "public"."organizations" ("id") ON UPDATE NO ACTION ON DELETE CASCADE +); +-- Create index "idx_annotations_organization_id" to table: "annotations" +CREATE INDEX "idx_annotations_organization_id" ON "public"."annotations" ("organization_id"); +-- Create "credentials" table +CREATE TABLE "public"."credentials" ( + "id" uuid NOT NULL DEFAULT gen_random_uuid(), + "organization_id" uuid NOT NULL, + "provider" character varying(50) NOT NULL, + "kind" character varying(50) NOT NULL, + "scope_kind" character varying(20) NOT NULL, + "scope" jsonb NOT NULL DEFAULT '{}', + "scope_fingerprint" character(64) NOT NULL, + "schema_version" bigint NOT NULL DEFAULT 1, + "name" character varying(100) NOT NULL DEFAULT '', + "scope_version" bigint NOT NULL DEFAULT 1, + "account_id" character varying(32) NULL, + "region" character varying(32) NULL, + "encrypted_data" text NOT NULL, + "iv" text NOT NULL, + "tag" text NOT NULL, + "created_at" timestamptz NOT NULL DEFAULT now(), + "updated_at" timestamptz NOT NULL DEFAULT now(), + PRIMARY KEY ("id"), + CONSTRAINT "fk_credentials_organization" FOREIGN KEY ("organization_id") REFERENCES "public"."organizations" ("id") ON UPDATE NO ACTION ON DELETE CASCADE +); +-- Create index "idx_credentials_organization_id" to table: "credentials" +CREATE INDEX "idx_credentials_organization_id" ON "public"."credentials" ("organization_id"); +-- Create index "idx_credentials_scope_fingerprint" to table: "credentials" +CREATE INDEX "idx_credentials_scope_fingerprint" ON "public"."credentials" ("scope_fingerprint"); +-- Create index "idx_kind_scope" to table: "credentials" +CREATE INDEX "idx_kind_scope" ON "public"."credentials" ("kind", "scope"); +-- Create index "idx_provider_kind" to table: "credentials" +CREATE INDEX "idx_provider_kind" ON "public"."credentials" ("provider", "kind"); +-- Create index "uniq_org_provider_scopekind_scope" to table: "credentials" +CREATE UNIQUE INDEX "uniq_org_provider_scopekind_scope" ON "public"."credentials" ("organization_id", "provider", "scope_kind", "scope_fingerprint"); +-- Create "backups" table +CREATE TABLE "public"."backups" ( + "id" uuid NOT NULL DEFAULT gen_random_uuid(), + "organization_id" uuid NOT NULL, + "enabled" boolean NOT NULL DEFAULT false, + "credential_id" uuid NOT NULL, + "created_at" timestamptz NOT NULL DEFAULT now(), + "updated_at" timestamptz NOT NULL DEFAULT now(), + PRIMARY KEY ("id"), + CONSTRAINT "fk_backups_credential" FOREIGN KEY ("credential_id") REFERENCES "public"."credentials" ("id") ON UPDATE NO ACTION ON DELETE NO ACTION, + CONSTRAINT "fk_backups_organization" FOREIGN KEY ("organization_id") REFERENCES "public"."organizations" ("id") ON UPDATE NO ACTION ON DELETE CASCADE +); +-- Create index "idx_backups_organization_id" to table: "backups" +CREATE INDEX "idx_backups_organization_id" ON "public"."backups" ("organization_id"); +-- Create index "uniq_org_credential" to table: "backups" +CREATE UNIQUE INDEX "uniq_org_credential" ON "public"."backups" ("organization_id", "credential_id"); +-- Create "ssh_keys" table +CREATE TABLE "public"."ssh_keys" ( + "id" uuid NOT NULL DEFAULT gen_random_uuid(), + "organization_id" uuid NULL, + "created_at" timestamptz NOT NULL DEFAULT now(), + "updated_at" timestamptz NOT NULL DEFAULT now(), + "name" text NOT NULL, + "public_key" text NOT NULL, + "encrypted_private_key" text NOT NULL, + "private_iv" text NOT NULL, + "private_tag" text NOT NULL, + "fingerprint" text NOT NULL, + PRIMARY KEY ("id"), + CONSTRAINT "fk_ssh_keys_organization" FOREIGN KEY ("organization_id") REFERENCES "public"."organizations" ("id") ON UPDATE NO ACTION ON DELETE CASCADE +); +-- Create index "idx_ssh_keys_fingerprint" to table: "ssh_keys" +CREATE INDEX "idx_ssh_keys_fingerprint" ON "public"."ssh_keys" ("fingerprint"); +-- Create index "idx_ssh_keys_organization_id" to table: "ssh_keys" +CREATE INDEX "idx_ssh_keys_organization_id" ON "public"."ssh_keys" ("organization_id"); +-- Create "servers" table +CREATE TABLE "public"."servers" ( + "id" uuid NOT NULL DEFAULT gen_random_uuid(), + "organization_id" uuid NOT NULL, + "hostname" text NULL, + "public_ip_address" text NULL, + "private_ip_address" text NOT NULL, + "ssh_user" text NOT NULL, + "ssh_key_id" uuid NOT NULL, + "role" text NOT NULL, + "status" text NULL DEFAULT 'pending', + "ssh_host_key" text NULL, + "ssh_host_key_algo" text NULL, + "created_at" timestamptz NOT NULL DEFAULT now(), + "updated_at" timestamptz NOT NULL DEFAULT now(), + PRIMARY KEY ("id"), + CONSTRAINT "fk_servers_organization" FOREIGN KEY ("organization_id") REFERENCES "public"."organizations" ("id") ON UPDATE NO ACTION ON DELETE CASCADE, + CONSTRAINT "fk_servers_ssh_key" FOREIGN KEY ("ssh_key_id") REFERENCES "public"."ssh_keys" ("id") ON UPDATE NO ACTION ON DELETE NO ACTION +); +-- Create "clusters" table +CREATE TABLE "public"."clusters" ( + "id" uuid NOT NULL DEFAULT gen_random_uuid(), + "organization_id" uuid NOT NULL, + "name" text NOT NULL, + "provider" text NULL, + "region" text NULL, + "status" text NULL, + "captain_domain" text NOT NULL, + "apps_load_balancer" text NULL, + "glue_ops_load_balancer" text NULL, + "control_plane" text NULL, + "random_token" text NULL, + "certificate_key" text NULL, + "encrypted_kubeconfig" text NULL, + "kube_iv" text NULL, + "kube_tag" text NULL, + "bastion_server_id" uuid NULL, + "created_at" timestamptz NOT NULL DEFAULT now(), + "updated_at" timestamptz NOT NULL DEFAULT now(), + PRIMARY KEY ("id"), + CONSTRAINT "fk_clusters_bastion_server" FOREIGN KEY ("bastion_server_id") REFERENCES "public"."servers" ("id") ON UPDATE NO ACTION ON DELETE NO ACTION, + CONSTRAINT "fk_clusters_organization" FOREIGN KEY ("organization_id") REFERENCES "public"."organizations" ("id") ON UPDATE NO ACTION ON DELETE CASCADE +); +-- Create "node_pools" table +CREATE TABLE "public"."node_pools" ( + "id" uuid NOT NULL DEFAULT gen_random_uuid(), + "organization_id" uuid NULL, + "created_at" timestamptz NOT NULL DEFAULT now(), + "updated_at" timestamptz NOT NULL DEFAULT now(), + "name" text NOT NULL, + "role" text NULL, + PRIMARY KEY ("id"), + CONSTRAINT "fk_node_pools_organization" FOREIGN KEY ("organization_id") REFERENCES "public"."organizations" ("id") ON UPDATE NO ACTION ON DELETE CASCADE +); +-- Create index "idx_node_pools_organization_id" to table: "node_pools" +CREATE INDEX "idx_node_pools_organization_id" ON "public"."node_pools" ("organization_id"); +-- Create "cluster_node_pools" table +CREATE TABLE "public"."cluster_node_pools" ( + "node_pool_id" uuid NOT NULL DEFAULT gen_random_uuid(), + "cluster_id" uuid NOT NULL DEFAULT gen_random_uuid(), + PRIMARY KEY ("node_pool_id", "cluster_id"), + CONSTRAINT "fk_cluster_node_pools_cluster" FOREIGN KEY ("cluster_id") REFERENCES "public"."clusters" ("id") ON UPDATE NO ACTION ON DELETE CASCADE, + CONSTRAINT "fk_cluster_node_pools_node_pool" FOREIGN KEY ("node_pool_id") REFERENCES "public"."node_pools" ("id") ON UPDATE NO ACTION ON DELETE CASCADE +); +-- Create "domains" table +CREATE TABLE "public"."domains" ( + "id" uuid NOT NULL DEFAULT gen_random_uuid(), + "organization_id" uuid NOT NULL, + "domain_name" character varying(253) NOT NULL, + "zone_id" character varying(128) NOT NULL DEFAULT '', + "status" character varying(20) NOT NULL DEFAULT 'pending', + "last_error" text NOT NULL DEFAULT '', + "credential_id" uuid NOT NULL, + "created_at" timestamptz NOT NULL DEFAULT now(), + "updated_at" timestamptz NOT NULL DEFAULT now(), + PRIMARY KEY ("id"), + CONSTRAINT "fk_domains_credential" FOREIGN KEY ("credential_id") REFERENCES "public"."credentials" ("id") ON UPDATE NO ACTION ON DELETE NO ACTION, + CONSTRAINT "fk_domains_organization" FOREIGN KEY ("organization_id") REFERENCES "public"."organizations" ("id") ON UPDATE NO ACTION ON DELETE CASCADE +); +-- Create index "idx_domains_organization_id" to table: "domains" +CREATE INDEX "idx_domains_organization_id" ON "public"."domains" ("organization_id"); +-- Create index "uniq_org_domain" to table: "domains" +CREATE UNIQUE INDEX "uniq_org_domain" ON "public"."domains" ("organization_id", "domain_name"); +-- Create "labels" table +CREATE TABLE "public"."labels" ( + "id" uuid NOT NULL DEFAULT gen_random_uuid(), + "organization_id" uuid NULL, + "created_at" timestamptz NOT NULL DEFAULT now(), + "updated_at" timestamptz NOT NULL DEFAULT now(), + "key" text NOT NULL, + "value" text NOT NULL, + PRIMARY KEY ("id"), + CONSTRAINT "fk_labels_organization" FOREIGN KEY ("organization_id") REFERENCES "public"."organizations" ("id") ON UPDATE NO ACTION ON DELETE CASCADE +); +-- Create index "idx_labels_organization_id" to table: "labels" +CREATE INDEX "idx_labels_organization_id" ON "public"."labels" ("organization_id"); +-- Create "load_balancers" table +CREATE TABLE "public"."load_balancers" ( + "id" uuid NOT NULL DEFAULT gen_random_uuid(), + "organization_id" uuid NULL, + "name" text NOT NULL, + "kind" text NOT NULL, + "public_ip_address" text NOT NULL, + "private_ip_address" text NOT NULL, + "created_at" timestamptz NOT NULL DEFAULT now(), + "updated_at" timestamptz NOT NULL DEFAULT now(), + PRIMARY KEY ("id"), + CONSTRAINT "fk_load_balancers_organization" FOREIGN KEY ("organization_id") REFERENCES "public"."organizations" ("id") ON UPDATE NO ACTION ON DELETE CASCADE +); +-- Create index "idx_load_balancers_organization_id" to table: "load_balancers" +CREATE INDEX "idx_load_balancers_organization_id" ON "public"."load_balancers" ("organization_id"); +-- Create "memberships" table +CREATE TABLE "public"."memberships" ( + "id" uuid NOT NULL DEFAULT gen_random_uuid(), + "user_id" uuid NOT NULL, + "organization_id" uuid NOT NULL, + "role" text NOT NULL DEFAULT 'member', + "created_at" timestamptz NOT NULL DEFAULT now(), + "updated_at" timestamptz NOT NULL DEFAULT now(), + PRIMARY KEY ("id"), + CONSTRAINT "fk_memberships_organization" FOREIGN KEY ("organization_id") REFERENCES "public"."organizations" ("id") ON UPDATE NO ACTION ON DELETE CASCADE, + CONSTRAINT "fk_memberships_user" FOREIGN KEY ("user_id") REFERENCES "public"."users" ("id") ON UPDATE NO ACTION ON DELETE NO ACTION +); +-- Create index "idx_memberships_organization_id" to table: "memberships" +CREATE INDEX "idx_memberships_organization_id" ON "public"."memberships" ("organization_id"); +-- Create index "idx_memberships_user_id" to table: "memberships" +CREATE INDEX "idx_memberships_user_id" ON "public"."memberships" ("user_id"); +-- Create "node_annotations" table +CREATE TABLE "public"."node_annotations" ( + "node_pool_id" uuid NOT NULL DEFAULT gen_random_uuid(), + "annotation_id" uuid NOT NULL DEFAULT gen_random_uuid(), + PRIMARY KEY ("node_pool_id", "annotation_id"), + CONSTRAINT "fk_node_annotations_annotation" FOREIGN KEY ("annotation_id") REFERENCES "public"."annotations" ("id") ON UPDATE NO ACTION ON DELETE CASCADE, + CONSTRAINT "fk_node_annotations_node_pool" FOREIGN KEY ("node_pool_id") REFERENCES "public"."node_pools" ("id") ON UPDATE NO ACTION ON DELETE CASCADE +); +-- Create "node_labels" table +CREATE TABLE "public"."node_labels" ( + "node_pool_id" uuid NOT NULL DEFAULT gen_random_uuid(), + "label_id" uuid NOT NULL DEFAULT gen_random_uuid(), + PRIMARY KEY ("node_pool_id", "label_id"), + CONSTRAINT "fk_node_labels_label" FOREIGN KEY ("label_id") REFERENCES "public"."labels" ("id") ON UPDATE NO ACTION ON DELETE CASCADE, + CONSTRAINT "fk_node_labels_node_pool" FOREIGN KEY ("node_pool_id") REFERENCES "public"."node_pools" ("id") ON UPDATE NO ACTION ON DELETE CASCADE +); +-- Create "node_servers" table +CREATE TABLE "public"."node_servers" ( + "server_id" uuid NOT NULL DEFAULT gen_random_uuid(), + "node_pool_id" uuid NOT NULL DEFAULT gen_random_uuid(), + PRIMARY KEY ("server_id", "node_pool_id"), + CONSTRAINT "fk_node_servers_node_pool" FOREIGN KEY ("node_pool_id") REFERENCES "public"."node_pools" ("id") ON UPDATE NO ACTION ON DELETE CASCADE, + CONSTRAINT "fk_node_servers_server" FOREIGN KEY ("server_id") REFERENCES "public"."servers" ("id") ON UPDATE NO ACTION ON DELETE CASCADE +); +-- Create "taints" table +CREATE TABLE "public"."taints" ( + "id" uuid NOT NULL DEFAULT gen_random_uuid(), + "organization_id" uuid NOT NULL, + "key" text NOT NULL, + "value" text NOT NULL, + "effect" text NOT NULL, + "created_at" timestamptz NOT NULL DEFAULT now(), + "updated_at" timestamptz NOT NULL DEFAULT now(), + PRIMARY KEY ("id"), + CONSTRAINT "fk_taints_organization" FOREIGN KEY ("organization_id") REFERENCES "public"."organizations" ("id") ON UPDATE NO ACTION ON DELETE CASCADE +); +-- Create "node_taints" table +CREATE TABLE "public"."node_taints" ( + "taint_id" uuid NOT NULL DEFAULT gen_random_uuid(), + "node_pool_id" uuid NOT NULL DEFAULT gen_random_uuid(), + PRIMARY KEY ("taint_id", "node_pool_id"), + CONSTRAINT "fk_node_taints_node_pool" FOREIGN KEY ("node_pool_id") REFERENCES "public"."node_pools" ("id") ON UPDATE NO ACTION ON DELETE CASCADE, + CONSTRAINT "fk_node_taints_taint" FOREIGN KEY ("taint_id") REFERENCES "public"."taints" ("id") ON UPDATE NO ACTION ON DELETE CASCADE +); +-- Create "master_keys" table +CREATE TABLE "public"."master_keys" ( + "id" uuid NOT NULL DEFAULT gen_random_uuid(), + "key" text NOT NULL, + "is_active" boolean NULL DEFAULT true, + "created_at" timestamptz NOT NULL DEFAULT now(), + "updated_at" timestamptz NOT NULL DEFAULT now(), + PRIMARY KEY ("id") +); +-- Create "organization_keys" table +CREATE TABLE "public"."organization_keys" ( + "id" uuid NOT NULL DEFAULT gen_random_uuid(), + "organization_id" uuid NOT NULL, + "master_key_id" uuid NOT NULL, + "encrypted_key" text NOT NULL, + "iv" text NOT NULL, + "tag" text NOT NULL, + "created_at" timestamptz NOT NULL DEFAULT now(), + "updated_at" timestamptz NOT NULL DEFAULT now(), + PRIMARY KEY ("id"), + CONSTRAINT "fk_organization_keys_master_key" FOREIGN KEY ("master_key_id") REFERENCES "public"."master_keys" ("id") ON UPDATE NO ACTION ON DELETE CASCADE, + CONSTRAINT "fk_organization_keys_organization" FOREIGN KEY ("organization_id") REFERENCES "public"."organizations" ("id") ON UPDATE NO ACTION ON DELETE CASCADE +); +-- Create "record_sets" table +CREATE TABLE "public"."record_sets" ( + "id" uuid NOT NULL DEFAULT gen_random_uuid(), + "domain_id" uuid NOT NULL, + "name" character varying(253) NOT NULL, + "type" character varying(10) NOT NULL, + "ttl" bigint NULL, + "values" jsonb NOT NULL DEFAULT '[]', + "fingerprint" character(64) NOT NULL, + "status" character varying(20) NOT NULL DEFAULT 'pending', + "owner" character varying(16) NOT NULL DEFAULT 'unknown', + "last_error" text NOT NULL DEFAULT '', + "created_at" timestamptz NOT NULL DEFAULT now(), + "updated_at" timestamptz NOT NULL DEFAULT now(), + PRIMARY KEY ("id"), + CONSTRAINT "fk_record_sets_domain" FOREIGN KEY ("domain_id") REFERENCES "public"."domains" ("id") ON UPDATE NO ACTION ON DELETE CASCADE +); +-- Create index "idx_record_sets_domain_id" to table: "record_sets" +CREATE INDEX "idx_record_sets_domain_id" ON "public"."record_sets" ("domain_id"); +-- Create index "idx_record_sets_fingerprint" to table: "record_sets" +CREATE INDEX "idx_record_sets_fingerprint" ON "public"."record_sets" ("fingerprint"); +-- Create index "idx_record_sets_type" to table: "record_sets" +CREATE INDEX "idx_record_sets_type" ON "public"."record_sets" ("type"); +-- Create "user_emails" table +CREATE TABLE "public"."user_emails" ( + "id" uuid NOT NULL DEFAULT gen_random_uuid(), + "user_id" uuid NOT NULL, + "email" text NOT NULL, + "is_verified" boolean NOT NULL DEFAULT false, + "is_primary" boolean NOT NULL DEFAULT false, + "created_at" timestamptz NOT NULL DEFAULT now(), + "updated_at" timestamptz NOT NULL DEFAULT now(), + PRIMARY KEY ("id"), + CONSTRAINT "fk_user_emails_user" FOREIGN KEY ("user_id") REFERENCES "public"."users" ("id") ON UPDATE NO ACTION ON DELETE NO ACTION +); +-- Create index "idx_user_emails_user_id" to table: "user_emails" +CREATE INDEX "idx_user_emails_user_id" ON "public"."user_emails" ("user_id"); diff --git a/tools/pgweb_fetch.go b/tools/pgweb_fetch.go deleted file mode 100644 index b3dcc4a..0000000 --- a/tools/pgweb_fetch.go +++ /dev/null @@ -1,137 +0,0 @@ -//go:build ignore -// +build ignore - -package main - -import ( - "archive/zip" - "crypto/sha256" - "encoding/hex" - "fmt" - "io" - "net/http" - "os" - "path/filepath" -) - -type Target struct { - Name string - URL string - SHA256 string -} - -const version = "0.16.2" - -func main() { - targets := []Target{ - { - Name: "pgweb-linux-amd64", - URL: fmt.Sprintf("https://github.com/sosedoff/pgweb/releases/download/v%s/pgweb_linux_amd64.zip", version), - SHA256: "3d6c2063e1040b8a625eb7c43c9b84f8ed12cfc9a798eacbce85179963ee2554", - }, - { - Name: "pgweb-linux-arm64", - URL: fmt.Sprintf("https://github.com/sosedoff/pgweb/releases/download/v%s/pgweb_linux_arm64.zip", version), - SHA256: "079c698a323ed6431ce7e6343ee5847c7da62afbf45dfb2e78f8289d7b381783", - }, - { - Name: "pgweb-darwin-amd64", - URL: fmt.Sprintf("https://github.com/sosedoff/pgweb/releases/download/v%s/pgweb_darwin_amd64.zip", version), - SHA256: "c0a098e2eb9cf9f7c20161a2947522eb67eacbf2b6c3389c2f8e8c5ed7238957", - }, - { - Name: "pgweb-darwin-arm64", - URL: fmt.Sprintf("https://github.com/sosedoff/pgweb/releases/download/v%s/pgweb_darwin_arm64.zip", version), - SHA256: "c8f5fca847f461ba22a619e2d96cb1656cefdffd8f2aef2340e14fc5b518d3a2", - }, - } - - outDir := filepath.Join("internal", "web", "pgwebbin") - _ = os.MkdirAll(outDir, 0o755) - - for _, t := range targets { - destZip := filepath.Join(outDir, t.Name+".zip") - fmt.Printf("Downloading %s...\n", t.URL) - if err := downloadFile(destZip, t.URL); err != nil { - panic(err) - } - binPath := filepath.Join(outDir, t.Name) - if err := unzipSingle(destZip, binPath); err != nil { - panic(err) - } - _ = os.Remove(destZip) - - // Make executable - if err := os.Chmod(binPath, 0o755); err != nil { - panic(err) - } - fmt.Printf("Saved %s\n", binPath) - - // Compute checksum - sum, _ := fileSHA256(binPath) - fmt.Printf(" SHA256: %s\n", sum) - } -} - -func downloadFile(dest, url string) error { - resp, err := http.Get(url) - if err != nil { - return err - } - defer resp.Body.Close() - if resp.StatusCode != 200 { - return fmt.Errorf("bad status: %s", resp.Status) - } - out, err := os.Create(dest) - if err != nil { - return err - } - defer out.Close() - _, err = io.Copy(out, resp.Body) - return err -} - -func fileSHA256(path string) (string, error) { - f, err := os.Open(path) - if err != nil { - return "", err - } - defer f.Close() - h := sha256.New() - if _, err := io.Copy(h, f); err != nil { - return "", err - } - return hex.EncodeToString(h.Sum(nil)), nil -} - -func unzipSingle(zipPath, outPath string) error { - zr, err := zip.OpenReader(zipPath) - if err != nil { - return err - } - defer zr.Close() - - if len(zr.File) == 0 { - return fmt.Errorf("zip file %s is empty", zipPath) - } - - f := zr.File[0] - - rc, err := f.Open() - if err != nil { - return err - } - defer rc.Close() - - out, err := os.Create(outPath) - if err != nil { - return err - } - defer out.Close() - - if _, err := io.Copy(out, rc); err != nil { - return err - } - - return nil -} diff --git a/ui/eslint.config.js b/ui/eslint.config.js index b19330b..41f1089 100644 --- a/ui/eslint.config.js +++ b/ui/eslint.config.js @@ -1,20 +1,29 @@ -import js from '@eslint/js' -import globals from 'globals' -import reactHooks from 'eslint-plugin-react-hooks' -import reactRefresh from 'eslint-plugin-react-refresh' -import tseslint from 'typescript-eslint' -import { defineConfig, globalIgnores } from 'eslint/config' +import js from "@eslint/js" +import reactHooks from "eslint-plugin-react-hooks" +import reactRefresh from "eslint-plugin-react-refresh" +import { defineConfig, globalIgnores } from "eslint/config" +import globals from "globals" +import tseslint from "typescript-eslint" export default defineConfig([ - globalIgnores(['dist']), + globalIgnores(["dist", "src/sdk", "src/components"]), { - files: ['**/*.{ts,tsx}'], + files: ["**/*.{ts,tsx}"], extends: [ js.configs.recommended, - tseslint.configs.recommended, - reactHooks.configs['recommended-latest'], - reactRefresh.configs.vite, + ...tseslint.configs.recommended, + //reactHooks.configs['recommended-latest'], + //reactRefresh.configs.vite, ], + plugins: { + "react-hooks": reactHooks, + "react-refresh": reactRefresh, + }, + rules: { + ...(reactHooks.configs["recommended-latest"]?.rules ?? {}), + ...(reactRefresh.configs.vite?.rules ?? {}), + "@typescript-eslint/no-explicit-any": "off", + }, languageOptions: { ecmaVersion: 2020, globals: globals.browser, diff --git a/ui/package.json b/ui/package.json index 753396f..0af84c0 100644 --- a/ui/package.json +++ b/ui/package.json @@ -37,8 +37,9 @@ "@radix-ui/react-toggle": "^1.1.10", "@radix-ui/react-toggle-group": "^1.1.11", "@radix-ui/react-tooltip": "^1.2.8", + "@radix-ui/react-use-controllable-state": "^1.2.2", "@tailwindcss/vite": "^4.1.17", - "@tanstack/react-query": "^5.90.7", + "@tanstack/react-query": "^5.90.9", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "cmdk": "^1.1.1", @@ -46,14 +47,16 @@ "embla-carousel-react": "^8.6.0", "input-otp": "^1.4.2", "lucide-react": "^0.553.0", + "motion": "^12.23.24", "next-themes": "^0.4.6", + "rapidoc": "^9.3.8", "react": "^19.2.0", "react-day-picker": "^9.11.1", "react-dom": "^19.2.0", "react-hook-form": "^7.66.0", "react-icons": "^5.5.0", "react-resizable-panels": "^3.0.6", - "react-router-dom": "^7.9.5", + "react-router-dom": "^7.9.6", "recharts": "2.15.4", "sonner": "^2.0.7", "tailwind-merge": "^3.4.0", @@ -64,10 +67,10 @@ "devDependencies": { "@eslint/js": "9.39.1", "@ianvs/prettier-plugin-sort-imports": "4.7.0", - "@types/node": "24.10.0", - "@types/react": "19.2.2", - "@types/react-dom": "19.2.2", - "@vitejs/plugin-react": "5.1.0", + "@types/node": "24.10.1", + "@types/react": "19.2.5", + "@types/react-dom": "19.2.3", + "@vitejs/plugin-react": "5.1.1", "eslint": "9.39.1", "eslint-plugin-react-hooks": "7.0.1", "eslint-plugin-react-refresh": "0.4.24", diff --git a/ui/src/App.tsx b/ui/src/App.tsx index 30e8a1f..479845a 100644 --- a/ui/src/App.tsx +++ b/ui/src/App.tsx @@ -6,6 +6,7 @@ import { AnnotationPage } from "@/pages/annotations/annotation-page.tsx" import { Login } from "@/pages/auth/login.tsx" import { CredentialPage } from "@/pages/credentials/credential-page.tsx" import { DnsPage } from "@/pages/dns/dns-page.tsx" +import { DocsPage } from "@/pages/docs/docs-page.tsx" import { JobsPage } from "@/pages/jobs/jobs-page.tsx" import { LabelsPage } from "@/pages/labels/labels-page.tsx" import { MePage } from "@/pages/me/me-page.tsx" @@ -21,6 +22,8 @@ export default function App() { return ( } /> + } /> + }> }> } /> diff --git a/ui/src/api/archer_admin.ts b/ui/src/api/archer_admin.ts index 5162ff7..3cdd635 100644 --- a/ui/src/api/archer_admin.ts +++ b/ui/src/api/archer_admin.ts @@ -4,14 +4,6 @@ import { makeArcherAdminApi } from "@/sdkClient.ts" const archerAdmin = makeArcherAdminApi() -type ListParams = { - status?: "queued" | "running" | "succeeded" | "failed" | "canceled" | "retrying" | "scheduled" - queue?: string - q?: string - page?: number - pageSize?: number -} - export const archerAdminApi = { listJobs: (params: AdminListArcherJobsRequest = {}) => { return withRefresh(async () => { diff --git a/ui/src/layouts/app-shell.tsx b/ui/src/layouts/app-shell.tsx index 9af479c..ed95f3c 100644 --- a/ui/src/layouts/app-shell.tsx +++ b/ui/src/layouts/app-shell.tsx @@ -4,6 +4,7 @@ import { orgStore } from "@/auth/org.ts" import { Footer } from "@/layouts/footer.tsx" import { adminNav, mainNav, orgNav, userNav } from "@/layouts/nav-config.ts" import { OrgSwitcher } from "@/layouts/org-switcher.tsx" +import { ThemePillSwitcher } from "@/layouts/theme-switcher" import { Topbar } from "@/layouts/topbar.tsx" import { NavLink, Outlet } from "react-router-dom" @@ -147,6 +148,7 @@ export const AppShell = () => { cn("flex items-center gap-2", isActive && "text-primary") } @@ -160,6 +162,9 @@ export const AppShell = () => { +
+ +
diff --git a/ui/src/layouts/footer.tsx b/ui/src/layouts/footer.tsx index 25e9cf4..ce9b086 100644 --- a/ui/src/layouts/footer.tsx +++ b/ui/src/layouts/footer.tsx @@ -33,7 +33,7 @@ function asClipboardText(v?: VersionInfo) { return `v${v.version} (${shortCommit(v.commit)}) • built ${v.built} • ${v.go} ${v.goOS}/${v.goArch}` } -export const Footer = memo(function Footer({ className }: { className?: string }) { +export const Footer = memo(function Footer() { const footerQ = useQuery({ queryKey: ["footer"], queryFn: () => metaApi.footer() as Promise, diff --git a/ui/src/layouts/nav-config.ts b/ui/src/layouts/nav-config.ts index b4acbb1..fad9b51 100644 --- a/ui/src/layouts/nav-config.ts +++ b/ui/src/layouts/nav-config.ts @@ -15,11 +15,13 @@ import { import { AiOutlineCluster } from "react-icons/ai" import { GrUserWorker } from "react-icons/gr" import { MdOutlineDns } from "react-icons/md" +import { SiSwagger } from "react-icons/si" export type NavItem = { to: string label: string icon: ComponentType<{ className?: string }> + target?: string } export const mainNav: NavItem[] = [ @@ -45,4 +47,5 @@ export const userNav: NavItem[] = [{ to: "/me", label: "Profile", icon: User2 }] export const adminNav: NavItem[] = [ { to: "/admin/users", label: "Users Admin", icon: Users }, { to: "/admin/jobs", label: "Jobs Admin", icon: GrUserWorker }, + { to: "/docs", label: "API Docs ", icon: SiSwagger, target: "_blank" }, ] diff --git a/ui/src/layouts/theme-switcher.tsx b/ui/src/layouts/theme-switcher.tsx new file mode 100644 index 0000000..1788f74 --- /dev/null +++ b/ui/src/layouts/theme-switcher.tsx @@ -0,0 +1,78 @@ +import { type ComponentType } from "react" +import { motion } from "framer-motion" +import { Monitor, Moon, Sun } from "lucide-react" +import { useTheme } from "next-themes" + +import { cn } from "@/lib/utils" + +type ThemeValue = "light" | "dark" | "system" + +const options: { id: ThemeValue; icon: ComponentType<{ className?: string }>; label: string }[] = [ + { id: "light", icon: Sun, label: "Light" }, + { id: "dark", icon: Moon, label: "Dark" }, + { id: "system", icon: Monitor, label: "System" }, +] + +interface ThemePillSwitcherProps { + className?: string + variant?: "pill" | "wide" + ariaLabel?: string +} + +export const ThemePillSwitcher = ({ + className = "", + variant = "pill", + ariaLabel = "Toggle theme", +}: ThemePillSwitcherProps) => { + const { theme, setTheme } = useTheme() + + const currentTheme = (theme ?? "system") as ThemeValue + const isPill = variant === "pill" + return ( +
+ {options.map(({ id, icon: Icon, label }) => { + const isActive = currentTheme === id + + return ( + + ) + })} +
+ ) +} \ No newline at end of file diff --git a/ui/src/layouts/topbar.tsx b/ui/src/layouts/topbar.tsx index e0219b5..971f759 100644 --- a/ui/src/layouts/topbar.tsx +++ b/ui/src/layouts/topbar.tsx @@ -1,4 +1,5 @@ import { useMemo } from "react" +import { ThemePillSwitcher } from "@/layouts/theme-switcher" import { Link, useLocation } from "react-router-dom" import { useMe } from "@/hooks/use-me.ts" @@ -69,6 +70,7 @@ export const Topbar = () => { + diff --git a/ui/src/pages/annotations/annotation-page.tsx b/ui/src/pages/annotations/annotation-page.tsx index 51a89ed..fc17eeb 100644 --- a/ui/src/pages/annotations/annotation-page.tsx +++ b/ui/src/pages/annotations/annotation-page.tsx @@ -1,6 +1,5 @@ import { useMemo, useState } from "react" import { annotationsApi } from "@/api/annotations.ts" -import { labelsApi } from "@/api/labels.ts" import type { DtoLabelResponse } from "@/sdk" import { zodResolver } from "@hookform/resolvers/zod" import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query" diff --git a/ui/src/pages/credentials/credential-page.tsx b/ui/src/pages/credentials/credential-page.tsx index 902cfbf..5b70b88 100644 --- a/ui/src/pages/credentials/credential-page.tsx +++ b/ui/src/pages/credentials/credential-page.tsx @@ -1,66 +1,28 @@ -import { useMemo, useState } from "react" -import { credentialsApi } from "@/api/credentials" -import { zodResolver } from "@hookform/resolvers/zod" -import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query" -import { - AlertTriangle, - Eye, - Loader2, - MoreHorizontal, - Pencil, - Plus, - Search, - Trash2, -} from "lucide-react" -import { Controller, useForm } from "react-hook-form" -import { toast } from "sonner" -import { z } from "zod" +import { useMemo, useState } from "react"; +import { credentialsApi } from "@/api/credentials"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; +import { AlertTriangle, Eye, Loader2, MoreHorizontal, Pencil, Plus, Search, Trash2 } from "lucide-react"; +import { Controller, useForm } from "react-hook-form"; +import { toast } from "sonner"; +import { z } from "zod"; + + + +import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger } from "@/components/ui/alert-dialog"; +import { Badge } from "@/components/ui/badge"; +import { Button } from "@/components/ui/button"; +import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog"; +import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"; +import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form"; +import { Input } from "@/components/ui/input"; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; +import { Switch } from "@/components/ui/switch"; +import { Textarea } from "@/components/ui/textarea"; + + + -import { - AlertDialog, - AlertDialogAction, - AlertDialogCancel, - AlertDialogContent, - AlertDialogDescription, - AlertDialogFooter, - AlertDialogHeader, - AlertDialogTitle, - AlertDialogTrigger, -} from "@/components/ui/alert-dialog" -import { Badge } from "@/components/ui/badge" -import { Button } from "@/components/ui/button" -import { - Dialog, - DialogContent, - DialogFooter, - DialogHeader, - DialogTitle, - DialogTrigger, -} from "@/components/ui/dialog" -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuTrigger, -} from "@/components/ui/dropdown-menu" -import { - Form, - FormControl, - FormField, - FormItem, - FormLabel, - FormMessage, -} from "@/components/ui/form" -import { Input } from "@/components/ui/input" -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from "@/components/ui/select" -import { Switch } from "@/components/ui/switch" -import { Textarea } from "@/components/ui/textarea" // -------------------- Constants -------------------- @@ -192,7 +154,9 @@ function extractErr(e: any): string { try { const msg = (e as any)?.response?.data?.message || (e as any)?.message if (msg) return String(msg) - } catch {} + } catch { + return "Unknown error" + } return "Unknown error" } diff --git a/ui/src/pages/dns/dns-page.tsx b/ui/src/pages/dns/dns-page.tsx index 58f2505..9caf381 100644 --- a/ui/src/pages/dns/dns-page.tsx +++ b/ui/src/pages/dns/dns-page.tsx @@ -224,9 +224,12 @@ export const DnsPage = () => { const r53Credentials = useMemo(() => (credentialQ.data ?? []).filter(isR53), [credentialQ.data]) useEffect(() => { - if (!selected && domainsQ.data && domainsQ.data.length) { - setSelected(domainsQ.data[0]!) + const setSelectedDns = () => { + if (!selected && domainsQ.data && domainsQ.data.length) { + setSelected(domainsQ.data[0]!) + } } + setSelectedDns() }, [domainsQ.data, selected]) const filteredDomains = useMemo(() => { diff --git a/ui/src/pages/docs/docs-page.tsx b/ui/src/pages/docs/docs-page.tsx new file mode 100644 index 0000000..39d30b1 --- /dev/null +++ b/ui/src/pages/docs/docs-page.tsx @@ -0,0 +1,171 @@ +import { useEffect, useRef, useState, type FC } from "react" +import { useTheme } from "next-themes" + +import { Button } from "@/components/ui/button" +import { Input } from "@/components/ui/input" + +import "rapidoc" + +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card.tsx" +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select.tsx" + +type RdThemeMode = "auto" | "light" | "dark" + +export const DocsPage: FC = () => { + const rdRef = useRef(null) + const { theme, systemTheme, setTheme } = useTheme() + + const [orgId, setOrgId] = useState("") + const [rdThemeMode, setRdThemeMode] = useState("auto") + + useEffect(() => { + const stateSetter = () => { + const stored = localStorage.getItem("autoglue.org") + if (stored) setOrgId(stored) + } + + stateSetter() + }, []) + + useEffect(() => { + const rd = rdRef.current + if (!rd) return + + let effectiveTheme: "light" | "dark" = "light" + if (rdThemeMode === "light") { + effectiveTheme = "light" + } else if (rdThemeMode === "dark") { + effectiveTheme = "dark" + } else { + const appTheme = theme === "system" ? systemTheme : theme + effectiveTheme = appTheme === "dark" ? "dark" : "light" + } + + rd.setAttribute("theme", effectiveTheme) + + if (typeof window !== "undefined") { + const defaultServer = `${window.location.origin}/api/v1` + rd.setAttribute("default-api-server", defaultServer) + } + + if (orgId) { + rd.setAttribute("api-key-name", "X-ORG-ID") + rd.setAttribute("api-key-location", "header") + rd.setAttribute("api-key-value", orgId) + } else { + rd.removeAttribute("api-key-value") + } + }, [theme, systemTheme, rdThemeMode, orgId]) + + const handleSaveOrg = () => { + const trimmed = orgId.trim() + localStorage.setItem("autoglue.org", trimmed) + const rd = rdRef.current + if (!rd) return + + if (trimmed) { + rd.setAttribute("api-key-value", trimmed) + } else { + rd.removeAttribute("api-key-value") + } + } + + const handleResetOrg = () => { + localStorage.removeItem("autoglue.org") + setOrgId("") + const rd = rdRef.current + if (!rd) return + rd.removeAttribute("api-key-value") + } + + return ( +
+ {/* Control bar */} + + + + AutoGlue API Docs +
+
+ {/* Theme selector */} +
+ Docs theme + +
+ + {/* Org ID controls */} +
+ Org ID (X-ORG-ID) + setOrgId(e.target.value)} + placeholder="org_..." + /> + + +
+
+
+
+
+ + Requests from <rapi-doc> will include: + Cookie: ag_jwt=… and{" "} + X-ORG-ID={orgId} + {!orgId && <> (set an Org ID above to send an X-ORG-ID header)} + +
+ + {/* @ts-expect-error ts-2339 */} + +
+ ) +} \ No newline at end of file diff --git a/ui/src/pages/org/settings.tsx b/ui/src/pages/org/settings.tsx index 50dca75..5c38c36 100644 --- a/ui/src/pages/org/settings.tsx +++ b/ui/src/pages/org/settings.tsx @@ -1,8 +1,6 @@ -import { useEffect, useMemo } from "react" -import { credentialsApi } from "@/api/credentials.ts" +import { useEffect } from "react" import { withRefresh } from "@/api/with-refresh.ts" import { orgStore } from "@/auth/org.ts" -import type { DtoCredentialOut } from "@/sdk" import { makeOrgsApi } from "@/sdkClient.ts" import { zodResolver } from "@hookform/resolvers/zod" import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query" @@ -22,6 +20,7 @@ import { } from "@/components/ui/form.tsx" import { Input } from "@/components/ui/input.tsx" +/* const isS3 = (c: DtoCredentialOut) => c.provider === "aws" && c.scope_kind === "service" && @@ -35,6 +34,7 @@ const isS3 = (c: DtoCredentialOut) => return false } })() +*/ const schema = z.object({ name: z.string().min(1, "Required"), @@ -54,13 +54,14 @@ export const OrgSettings = () => { queryFn: () => withRefresh(() => api.getOrg({ id: orgId! })), }) + /* const credentialQ = useQuery({ queryKey: ["credentials", "s3"], queryFn: () => credentialsApi.listCredentials(), // client-side filter }) const s3Credentials = useMemo(() => (credentialQ.data ?? []).filter(isS3), [credentialQ.data]) - + */ const form = useForm({ resolver: zodResolver(schema), defaultValues: { @@ -76,7 +77,7 @@ export const OrgSettings = () => { domain: q.data.domain ?? "", }) } - }, [q.data]) + }, [q.data, form]) const updateMut = useMutation({ mutationFn: (v: Partial) => api.updateOrg({ id: orgId!, body: v }), diff --git a/ui/src/pages/servers/server-page.tsx b/ui/src/pages/servers/server-page.tsx index ae118e0..6485bb9 100644 --- a/ui/src/pages/servers/server-page.tsx +++ b/ui/src/pages/servers/server-page.tsx @@ -5,7 +5,7 @@ import { zodResolver } from "@hookform/resolvers/zod" import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query" import { formatDistanceToNow } from "date-fns" import { Plus, Search } from "lucide-react" -import { useForm } from "react-hook-form" +import { useForm, useWatch } from "react-hook-form" import { toast } from "sonner" import { z } from "zod" @@ -136,8 +136,17 @@ export const ServerPage = () => { mode: "onChange", }) - const roleIsBastion = createForm.watch("role") === "bastion" - const pubCreate = createForm.watch("public_ip_address")?.trim() ?? "" + const watchedRoleCreate = useWatch({ + control: createForm.control, + name: "role", + }) + const roleIsBastion = watchedRoleCreate === "bastion" + + const watchedPublicIpCreate = useWatch({ + control: createForm.control, + name: "public_ip_address", + }) + const pubCreate = watchedPublicIpCreate?.trim() ?? "" const needPubCreate = roleIsBastion && pubCreate === "" const createMut = useMutation({ @@ -160,8 +169,19 @@ export const ServerPage = () => { mode: "onChange", }) - const roleIsBastionU = updateForm.watch("role") === "bastion" - const pubUpdate = updateForm.watch("public_ip_address")?.trim() ?? "" + const watchedRoleUpdate = useWatch({ + control: updateForm.control, + name: "role", + }) + + const watchedPublicIpAddressUpdate = useWatch({ + control: updateForm.control, + name: "public_ip_address", + }) + + const roleIsBastionU = watchedRoleUpdate === "bastion" + + const pubUpdate = watchedPublicIpAddressUpdate?.trim() ?? "" const needPubUpdate = roleIsBastionU && pubUpdate === "" const updateMut = useMutation({ diff --git a/ui/src/pages/ssh/ssh-page.tsx b/ui/src/pages/ssh/ssh-page.tsx index c21e37e..7e85869 100644 --- a/ui/src/pages/ssh/ssh-page.tsx +++ b/ui/src/pages/ssh/ssh-page.tsx @@ -4,11 +4,10 @@ import type { DtoCreateSSHRequest, DtoSshRevealResponse } from "@/sdk" import { zodResolver } from "@hookform/resolvers/zod" import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query" import { Download, Eye, Loader2, Plus, Trash2 } from "lucide-react" -import { useForm } from "react-hook-form" +import { useForm, useWatch } from "react-hook-form" import { toast } from "sonner" import { z } from "zod" -import { truncateMiddle } from "@/lib/utils.ts" import { Badge } from "@/components/ui/badge.tsx" import { Button } from "@/components/ui/button.tsx" import { @@ -105,6 +104,11 @@ export const SshPage = () => { }, }) + const watchedType = useWatch({ + control: form.control, + name: "type", + }) + const createMutation = useMutation({ mutationFn: async (values: CreateKeyInput) => { const payload: DtoCreateSSHRequest = { @@ -257,7 +261,7 @@ export const SshPage = () => {