Compare commits

...

68 Commits

Author SHA1 Message Date
allanice001
165d2a2af1 Merge remote-tracking branch 'origin/main'
# Conflicts:
#	go.mod
2025-11-14 06:13:16 +00:00
allanice001
fc1c83ba18 chore: cleanup and route refactoring
Signed-off-by: allanice001 <allanice001@gmail.com>
2025-11-14 06:12:59 +00:00
public-glueops-renovatebot[bot]
2be0eb8180 chore: lock file maintenance (#279)
Co-authored-by: public-glueops-renovatebot[bot] <186083205+public-glueops-renovatebot[bot]@users.noreply.github.com>
2025-11-14 03:03:45 +00:00
public-glueops-renovatebot[bot]
81043419e1 chore(fallback): update postgres (#278)
Co-authored-by: public-glueops-renovatebot[bot] <186083205+public-glueops-renovatebot[bot]@users.noreply.github.com>
2025-11-14 01:31:51 +00:00
public-glueops-renovatebot[bot]
f2ff08993a feat: update postgres to 17.7 #minor (#276)
Co-authored-by: public-glueops-renovatebot[bot] <186083205+public-glueops-renovatebot[bot]@users.noreply.github.com>
2025-11-13 21:46:01 +00:00
public-glueops-renovatebot[bot]
fabf456786 chore: lock file maintenance (#275)
Co-authored-by: public-glueops-renovatebot[bot] <186083205+public-glueops-renovatebot[bot]@users.noreply.github.com>
2025-11-13 20:51:37 +00:00
public-glueops-renovatebot[bot]
23c60d4ce8 chore: lock file maintenance (#274)
Co-authored-by: public-glueops-renovatebot[bot] <186083205+public-glueops-renovatebot[bot]@users.noreply.github.com>
2025-11-13 17:40:05 +00:00
public-glueops-renovatebot[bot]
c9bc09ae92 chore(patch): update typescript-eslint to 8.46.4 #patch (#257)
Co-authored-by: public-glueops-renovatebot[bot] <186083205+public-glueops-renovatebot[bot]@users.noreply.github.com>
2025-11-13 17:38:53 +00:00
public-glueops-renovatebot[bot]
6ec8305962 chore: lock file maintenance (#273)
Co-authored-by: public-glueops-renovatebot[bot] <186083205+public-glueops-renovatebot[bot]@users.noreply.github.com>
2025-11-13 14:36:16 +00:00
public-glueops-renovatebot[bot]
8d34198477 chore: lock file maintenance (#272)
Co-authored-by: public-glueops-renovatebot[bot] <186083205+public-glueops-renovatebot[bot]@users.noreply.github.com>
2025-11-12 21:47:38 +00:00
public-glueops-renovatebot[bot]
f79224b831 chore(patch): update github.com/aws/aws-sdk-go-v2/config to v1.31.20 #patch (#271)
Co-authored-by: public-glueops-renovatebot[bot] <186083205+public-glueops-renovatebot[bot]@users.noreply.github.com>
2025-11-12 20:50:59 +00:00
public-glueops-renovatebot[bot]
cd8e3f2d86 chore: lock file maintenance (#270)
Co-authored-by: public-glueops-renovatebot[bot] <186083205+public-glueops-renovatebot[bot]@users.noreply.github.com>
2025-11-12 16:25:54 +00:00
public-glueops-renovatebot[bot]
ad0ec48027 chore: lock file maintenance (#269)
Co-authored-by: public-glueops-renovatebot[bot] <186083205+public-glueops-renovatebot[bot]@users.noreply.github.com>
2025-11-12 15:27:59 +00:00
public-glueops-renovatebot[bot]
7a3e56b3ff chore: lock file maintenance (#268)
Co-authored-by: public-glueops-renovatebot[bot] <186083205+public-glueops-renovatebot[bot]@users.noreply.github.com>
2025-11-12 14:38:46 +00:00
public-glueops-renovatebot[bot]
015044abb1 chore: lock file maintenance (#267)
Co-authored-by: public-glueops-renovatebot[bot] <186083205+public-glueops-renovatebot[bot]@users.noreply.github.com>
2025-11-12 13:56:14 +00:00
public-glueops-renovatebot[bot]
afd8c8ceb2 chore(patch): update github.com/aws/aws-sdk-go-v2/config to v1.31.19 #patch (#264)
Co-authored-by: public-glueops-renovatebot[bot] <186083205+public-glueops-renovatebot[bot]@users.noreply.github.com>
2025-11-12 06:20:54 +00:00
allanice001
b358911b1b Merge remote-tracking branch 'origin/main' 2025-11-12 05:33:18 +00:00
allanice001
ad8141a497 feat: adding hourly backups to s3
Signed-off-by: allanice001 <allanice001@gmail.com>
2025-11-12 05:33:09 +00:00
public-glueops-renovatebot[bot]
9485b2ae4f feat: update golang.org/x/crypto to v0.44.0 #minor (#262)
Co-authored-by: public-glueops-renovatebot[bot] <186083205+public-glueops-renovatebot[bot]@users.noreply.github.com>
2025-11-11 19:04:59 +00:00
public-glueops-renovatebot[bot]
cc8e8b38c7 chore: lock file maintenance (#260)
Co-authored-by: public-glueops-renovatebot[bot] <186083205+public-glueops-renovatebot[bot]@users.noreply.github.com>
2025-11-11 14:22:01 +00:00
allanice001
586e51b8cc fix: db-studio prefix fixes 2025-11-11 04:23:27 +00:00
allanice001
ea4c625269 fix: db-studio prefix fixes 2025-11-11 04:01:57 +00:00
allanice001
b4c108a5be Merge branch 'main' of github.com:GlueOps/autoglue 2025-11-11 03:20:10 +00:00
allanice001
3a1ce33bca feat: adding embedded db-studio
Signed-off-by: allanice001 <allanice001@gmail.com>
2025-11-11 03:19:09 +00:00
public-glueops-renovatebot[bot]
dbb7ec398e chore: lock file maintenance (#259)
Co-authored-by: public-glueops-renovatebot[bot] <186083205+public-glueops-renovatebot[bot]@users.noreply.github.com>
2025-11-11 03:03:05 +00:00
public-glueops-renovatebot[bot]
82847e5027 chore: lock file maintenance (#258)
Co-authored-by: public-glueops-renovatebot[bot] <186083205+public-glueops-renovatebot[bot]@users.noreply.github.com>
2025-11-10 21:03:26 +00:00
public-glueops-renovatebot[bot]
4314599427 chore: lock file maintenance (#256)
Co-authored-by: public-glueops-renovatebot[bot] <186083205+public-glueops-renovatebot[bot]@users.noreply.github.com>
2025-11-10 17:24:20 +00:00
public-glueops-renovatebot[bot]
9108ee8f8f breaking: the dependency sigstore/cosign-installer has been updated to a new major version (v4.0.0), which may include breaking changes. #major (#253)
Co-authored-by: public-glueops-renovatebot[bot] <186083205+public-glueops-renovatebot[bot]@users.noreply.github.com>
2025-11-10 15:14:40 +00:00
allanice001
1feb3e29e1 chore: prettier 2025-11-10 14:47:39 +00:00
allanice001
0e9ce98624 fix: credentials page bugfix
Signed-off-by: allanice001 <allanice001@gmail.com>
2025-11-10 14:47:24 +00:00
allanice001
96aef81959 fix: credentials page bugfix
Signed-off-by: allanice001 <allanice001@gmail.com>
2025-11-10 14:46:56 +00:00
allanice001
62232e18f3 chore: prettier 2025-11-10 14:42:30 +00:00
allanice001
515327153c fix: credentials bugfix
Signed-off-by: allanice001 <allanice001@gmail.com>
2025-11-10 14:41:54 +00:00
allanice001
5f2e885a8e Merge remote-tracking branch 'origin/main' 2025-11-10 14:41:30 +00:00
allanice001
01b48efba0 fix: credentials bugfix
Signed-off-by: allanice001 <allanice001@gmail.com>
2025-11-10 14:41:16 +00:00
public-glueops-renovatebot[bot]
1c87566c5b chore: lock file maintenance (#255)
Co-authored-by: public-glueops-renovatebot[bot] <186083205+public-glueops-renovatebot[bot]@users.noreply.github.com>
2025-11-10 09:12:44 +00:00
public-glueops-renovatebot[bot]
ad00a3c45d chore(fallback): update axllent/mailpit (#252)
Co-authored-by: public-glueops-renovatebot[bot] <186083205+public-glueops-renovatebot[bot]@users.noreply.github.com>
2025-11-10 00:17:33 +00:00
public-glueops-renovatebot[bot]
158fdce780 chore(pin): update @types/react to #patch (#251)
Co-authored-by: public-glueops-renovatebot[bot] <186083205+public-glueops-renovatebot[bot]@users.noreply.github.com>
2025-11-10 00:17:24 +00:00
allanice001
c4fd344364 fix: prettier
Signed-off-by: allanice001 <allanice001@gmail.com>
2025-11-10 00:15:34 +00:00
allanice001
953e724ba0 fix: types fixed - credentials page
Signed-off-by: allanice001 <allanice001@gmail.com>
2025-11-10 00:14:49 +00:00
allanice001
256acfd686 fix: types fixed - credentials
Signed-off-by: allanice001 <allanice001@gmail.com>
2025-11-10 00:13:53 +00:00
allanice001
1dd0a39aad fix: types fixed - credentials
Signed-off-by: allanice001 <allanice001@gmail.com>
2025-11-10 00:05:54 +00:00
allanice001
7ef0605c2b Merge remote-tracking branch 'origin/main' 2025-11-10 00:02:35 +00:00
allanice001
8a92727b88 fix: types fixed - credentials
Signed-off-by: allanice001 <allanice001@gmail.com>
2025-11-10 00:02:21 +00:00
public-glueops-renovatebot[bot]
1f9920a04c chore: lock file maintenance (#248)
Co-authored-by: public-glueops-renovatebot[bot] <186083205+public-glueops-renovatebot[bot]@users.noreply.github.com>
2025-11-09 22:37:46 +00:00
allanice001
5fd96ec40f fix: types fixed
Signed-off-by: allanice001 <allanice001@gmail.com>
2025-11-09 22:01:44 +00:00
allanice001
bc72df3c9a feat: add credentials management
Signed-off-by: allanice001 <allanice001@gmail.com>
2025-11-09 21:46:31 +00:00
allanice001
56ea963b47 feat: add version info
Signed-off-by: allanice001 <allanice001@gmail.com>
2025-11-08 13:08:19 +00:00
allanice001
c9d5080d50 Merge remote-tracking branch 'origin/main'
# Conflicts:
#	ui/yarn.lock
2025-11-08 11:03:44 +00:00
allanice001
4adad29c4f fix: update dependencies
Signed-off-by: allanice001 <allanice001@gmail.com>
2025-11-08 11:03:27 +00:00
public-glueops-renovatebot[bot]
13c9ba0ec0 chore: lock file maintenance (#243)
Co-authored-by: public-glueops-renovatebot[bot] <186083205+public-glueops-renovatebot[bot]@users.noreply.github.com>
2025-11-08 11:03:12 +00:00
public-glueops-renovatebot[bot]
e80d818e86 chore(fallback): update golang (#244)
Co-authored-by: public-glueops-renovatebot[bot] <186083205+public-glueops-renovatebot[bot]@users.noreply.github.com>
2025-11-08 11:02:52 +00:00
allanice001
f1533eb325 fix: exclude terraform
Signed-off-by: allanice001 <allanice001@gmail.com>
2025-11-08 11:01:28 +00:00
allanice001
334df457ce feat: complete node pool api, sdk and ui
Signed-off-by: allanice001 <allanice001@gmail.com>
2025-11-08 10:22:45 +00:00
Alanis
c478a8d10f fix: ui dependencies update
Signed-off-by: allanice001 <allanice001@gmail.com>
2025-11-06 13:04:29 +00:00
public-glueops-renovatebot[bot]
3463b2cb33 chore: lock file maintenance (#241)
Co-authored-by: public-glueops-renovatebot[bot] <186083205+public-glueops-renovatebot[bot]@users.noreply.github.com>
2025-11-06 08:34:50 +00:00
allanice001
325e162d39 fix: add DockerIgnore to exclude terraform & sdk Directories, build process should reduce the container image size 2025-11-06 08:18:55 +00:00
allanice001
30e91bfd88 fix: add DockerIgnore to exclude terraform Directories 2025-11-06 08:17:05 +00:00
allanice001
12f2c5e1c5 feat: dynamically set swagger host, and some ui improvements on ssh page 2025-11-06 05:52:06 +00:00
public-glueops-renovatebot[bot]
7dc7d1a1f1 chore(pindigest): update golang to d2ede9f #patch (#239)
Co-authored-by: public-glueops-renovatebot[bot] <186083205+public-glueops-renovatebot[bot]@users.noreply.github.com>
2025-11-06 05:16:38 +00:00
allanice001
6a16eccce5 fix: relax csp 2025-11-06 05:07:53 +00:00
allanice001
adb3c01382 Merge remote-tracking branch 'origin/main' 2025-11-06 04:43:36 +00:00
allanice001
35b42c6b19 fix: go upgrades didnt patch Dockerfile 2025-11-06 04:43:28 +00:00
public-glueops-renovatebot[bot]
ac12c48c27 chore(pindigest): update postgres to 00bc866 #patch (#237)
Co-authored-by: public-glueops-renovatebot[bot] <186083205+public-glueops-renovatebot[bot]@users.noreply.github.com>
2025-11-06 04:38:07 +00:00
allanice001
04fc75a699 fix: go mod upgrades 2025-11-06 04:37:14 +00:00
allanice001
f4c41cfed7 Merge branch 'main' of github.com:GlueOps/autoglue
# Conflicts:
#	go.mod
#	go.sum
2025-11-06 04:31:49 +00:00
public-glueops-renovatebot[bot]
d7a87c3add feat: update github.com/swaggo/swag to v1.16.6 #minor (#236)
Co-authored-by: public-glueops-renovatebot[bot] <186083205+public-glueops-renovatebot[bot]@users.noreply.github.com>
2025-11-06 04:23:21 +00:00
public-glueops-renovatebot[bot]
6b824769ba feat: update github.com/swaggo/swag to v1.9.0 #minor (#235)
Co-authored-by: public-glueops-renovatebot[bot] <186083205+public-glueops-renovatebot[bot]@users.noreply.github.com>
2025-11-06 04:19:50 +00:00
521 changed files with 12479 additions and 68172 deletions

3
.dockerignore Normal file
View File

@@ -0,0 +1,3 @@
terraform
terraform-provider-autoglue
sdk

View File

@@ -39,7 +39,7 @@ jobs:
# https://github.com/sigstore/cosign-installer
- name: Install cosign
if: github.event_name != 'pull_request'
uses: sigstore/cosign-installer@7e8b541eb2e61bf99390e1afd4be13a184e9ebc5 # v3.10.1
uses: sigstore/cosign-installer@faadad0cce49287aee09b3a48701e75088a2c6ad # v4.0.0
with:
cosign-release: 'v2.2.4'

4
.gitignore vendored
View File

@@ -137,4 +137,6 @@ notes.txt
.terraform
.terraform.lock*
terraform.tfstate*
terraform.tfstate*
ui/src/sdk

View File

@@ -1,7 +1,7 @@
#################################
# Builder: Go + Node in one
#################################
FROM golang:1.25.3-alpine@sha256:aee43c3ccbf24fdffb7295693b6e33b21e01baec1b2a55acc351fde345e9ec34 AS builder
FROM golang:1.25.4-alpine@sha256:d3f0cf7723f3429e3f9ed846243970b20a2de7bae6a5b66fc5914e228d831bbb AS builder
RUN apk add --no-cache \
bash git ca-certificates tzdata \
@@ -15,7 +15,11 @@ RUN npm i -g yarn pnpm
WORKDIR /src
COPY . .
RUN make clean && make swagger && make -j3 sdk-all && make ui && make build
RUN make clean
RUN make swagger
RUN make sdk-ts-ui
RUN make ui
RUN make build
#################################
# Runtime

View File

@@ -23,13 +23,13 @@ MODULE_PATH ?= $(GIT_HOST)/$(GIT_USER)/$(BIN)
# SDK / module settings (Go)
SDK_REPO ?= $(BIN)-sdk-go # repo name used for module path
SDK_OUTDIR ?= sdk/go # output directory (inside repo)
SDK_OUTDIR ?= ../autoglue-sdk-go # output directory (inside repo)
SDK_PKG ?= ${BIN} # package name inside the SDK
UI_SSG_ROUTES ?= /,/login,/docs,/pricing
# Go versioning (go.mod uses major.minor; youre on 1.25.3)
GO_VERSION ?= 1.25.3
# Go versioning (go.mod uses major.minor; youre on 1.25.4)
GO_VERSION ?= 1.25.4
# SDK / package settings (TypeScript)
SDK_TS_OUTDIR ?= sdk/ts
@@ -70,7 +70,7 @@ export GO_POST_PROCESS_FILE := gofmt -w
.DEFAULT_GOAL := help
# --- version metadata (ldflags) ---
VERSION := $(shell git describe --tags --always --dirty 2>/dev/null || echo "dev")
VERSION := $(shell git describe --tags --always 2>/dev/null || echo "dev")
COMMIT := $(shell git rev-parse HEAD 2>/dev/null || echo "none")
DATE := $(shell date -u +'%Y-%m-%dT%H:%M:%SZ')
BUILT_BY := $(shell whoami)
@@ -103,7 +103,7 @@ DOCS_YAML := docs/swagger.yaml
# Prefer git for speed; fall back to find. Exclude UI dir.
#GO_SRCS := $(shell (git ls-files '*.go' ':!$(UI_DIR)/**' 2>/dev/null || find . -name '*.go' -not -path './$(UI_DIR)/*' -type f))
GO_SRCS := $(shell ( \
git ls-files '*.go' ':!$(UI_DIR)/**' ':!docs/**' ':!sdk/**' 2>/dev/null \
git ls-files '*.go' ':!$(UI_DIR)/**' ':!docs/**' ':!sdk/**' ':!terraform-provider-autoglue/**' 2>/dev/null \
|| find . -name '*.go' -not -path './$(UI_DIR)/*' -not -path './docs/*' -type f \
))
@@ -247,7 +247,6 @@ TS_PROPS := -p npmName=$(SDK_TS_NPM_NAME) -p npmVersion=$(SDK_TS_NPM_VER) $
# --- sdk generation (Go) ---
sdk-go: $(DOCS_JSON) validate-spec check-tags ## Generate Go SDK + tidy module
@echo ">> Generating Go SDK (module $(GIT_HOST_CLEAN)/$(GIT_USER_CLEAN)/$(SDK_REPO_CLEAN), Go $(GO_VERSION))..."
@rm -rf "$(SDK_OUTDIR_CLEAN)"; mkdir -p "$(SDK_OUTDIR_CLEAN)"
@$(call OGC_GENERATE,go,$(SDK_OUTDIR_CLEAN),--additional-properties=packageName=$(SDK_PKG_CLEAN) $(OAG_GIT_PROPS))
@cd "$(SDK_OUTDIR_CLEAN)"; \
$(GOCMD) mod edit -go=$(GO_VERSION); \
@@ -311,6 +310,9 @@ doctor: ## Print environment diagnostics (shell, versions, generator availabilit
$(OGC_BIN) version || true; \
}
fetch-pgweb: ## Fetch PGWeb Binaries for embedding
go run ./tools/pgweb_fetch.go
help: ## Show this help
@grep -hE '^[a-zA-Z0-9_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | \
awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'

51
cmd/db.go Normal file
View File

@@ -0,0 +1,51 @@
package cmd
import (
"context"
"errors"
"fmt"
"os"
"os/exec"
"runtime"
"time"
"github.com/glueops/autoglue/internal/config"
"github.com/spf13/cobra"
)
var dbCmd = &cobra.Command{
Use: "db",
Short: "Database utilities",
}
var dbPsqlCmd = &cobra.Command{
Use: "psql",
Short: "Open a psql session to the app database",
RunE: func(cmd *cobra.Command, args []string) error {
cfg, err := config.Load()
if err != nil {
return err
}
if cfg.DbURL == "" {
return errors.New("database.url is empty")
}
psql := "psql"
if runtime.GOOS == "windows" {
psql = "psql.exe"
}
ctx, cancel := context.WithTimeout(context.Background(), 72*time.Hour)
defer cancel()
psqlCmd := exec.CommandContext(ctx, psql, cfg.DbURL)
psqlCmd.Stdin, psqlCmd.Stdout, psqlCmd.Stderr = os.Stdin, os.Stdout, os.Stderr
fmt.Println("Launching psql…")
return psqlCmd.Run()
},
}
func init() {
dbCmd.AddCommand(dbPsqlCmd)
rootCmd.AddCommand(dbCmd)
}

View File

@@ -18,6 +18,7 @@ import (
"github.com/glueops/autoglue/internal/auth"
"github.com/glueops/autoglue/internal/bg"
"github.com/glueops/autoglue/internal/config"
"github.com/glueops/autoglue/internal/models"
"github.com/google/uuid"
"github.com/spf13/cobra"
)
@@ -38,6 +39,8 @@ var serveCmd = &cobra.Command{
log.Fatalf("failed to init background jobs: %v", err)
}
rt.DB.Where("status IN ?", []string{"scheduled", "queued", "pending"}).Delete(&models.Job{})
// Start workers in background ONCE
go func() {
if err := jobs.Start(); err != nil {
@@ -50,7 +53,7 @@ var serveCmd = &cobra.Command{
{
// schedule next 03:30 local time
next := time.Now().Truncate(24 * time.Hour).Add(24 * time.Hour).Add(3*time.Hour + 30*time.Minute)
_, _ = jobs.Enqueue(
_, err = jobs.Enqueue(
context.Background(),
uuid.NewString(),
"archer_cleanup",
@@ -58,10 +61,13 @@ var serveCmd = &cobra.Command{
archer.WithScheduleTime(next),
archer.WithMaxRetries(1),
)
if err != nil {
log.Fatalf("failed to enqueue archer cleanup job: %v", err)
}
// schedule next 03:45 local time
next2 := time.Now().Truncate(24 * time.Hour).Add(24 * time.Hour).Add(3*time.Hour + 45*time.Minute)
_, _ = jobs.Enqueue(
_, err = jobs.Enqueue(
context.Background(),
uuid.NewString(),
"tokens_cleanup",
@@ -69,46 +75,47 @@ var serveCmd = &cobra.Command{
archer.WithScheduleTime(next2),
archer.WithMaxRetries(1),
)
}
// Periodic scheduler
schedCtx, schedCancel := context.WithCancel(context.Background())
defer schedCancel()
ticker := time.NewTicker(10 * time.Second)
defer ticker.Stop()
go func() {
for {
select {
case <-ticker.C:
_, err := jobs.Enqueue(
context.Background(),
uuid.NewString(),
"bootstrap_bastion",
bg.BastionBootstrapArgs{},
archer.WithMaxRetries(3),
// while debugging, avoid extra schedule delay:
archer.WithScheduleTime(time.Now().Add(60*time.Second)),
)
if err != nil {
log.Printf("failed to enqueue bootstrap_bastion: %v", err)
}
/*
_, _ = jobs.Enqueue(
context.Background(),
uuid.NewString(),
"tokens_cleanup",
bg.TokensCleanupArgs{},
archer.WithMaxRetries(3),
archer.WithScheduleTime(time.Now().Add(10*time.Second)),
)
*/
case <-schedCtx.Done():
return
}
if err != nil {
log.Fatalf("failed to enqueue token cleanup job: %v", err)
}
}()
_, err = jobs.Enqueue(
context.Background(),
uuid.NewString(),
"db_backup_s3",
bg.DbBackupArgs{IntervalS: 3600},
archer.WithMaxRetries(1),
archer.WithScheduleTime(time.Now().Add(1*time.Hour)),
)
if err != nil {
log.Fatalf("failed to enqueue backup jobs: %v", err)
}
_, err = jobs.Enqueue(
context.Background(),
uuid.NewString(),
"dns_reconcile",
bg.DNSReconcileArgs{MaxDomains: 25, MaxRecords: 100, IntervalS: 10},
archer.WithScheduleTime(time.Now().Add(5*time.Second)),
archer.WithMaxRetries(1),
)
if err != nil {
log.Fatalf("failed to enqueue dns reconcile: %v", err)
}
_, err := jobs.Enqueue(
context.Background(),
uuid.NewString(),
"bootstrap_bastion",
bg.BastionBootstrapArgs{IntervalS: 10},
archer.WithMaxRetries(3),
// while debugging, avoid extra schedule delay:
archer.WithScheduleTime(time.Now().Add(60*time.Second)),
)
if err != nil {
log.Printf("failed to enqueue bootstrap_bastion: %v", err)
}
}
_ = auth.Refresh(rt.DB, rt.Cfg.JWTPrivateEncKey)
go func() {
@@ -119,7 +126,26 @@ var serveCmd = &cobra.Command{
}
}()
r := api.NewRouter(rt.DB, jobs)
r := api.NewRouter(rt.DB, jobs, nil)
if cfg.DBStudioEnabled {
dbURL := cfg.DbURLRO
if dbURL == "" {
dbURL = cfg.DbURL
}
studio, err := api.PgwebHandler(
dbURL,
"db-studio",
false,
)
if err != nil {
log.Fatalf("failed to init db studio: %v", err)
} else {
r = api.NewRouter(rt.DB, jobs, studio)
log.Printf("pgweb mounted at /db-studio/")
}
}
addr := fmt.Sprintf("%s:%s", cfg.Host, cfg.Port)

View File

@@ -42,7 +42,7 @@ services:
- postgres
mailpit:
image: axllent/mailpit@sha256:6abc8e633df15eaf785cfcf38bae48e66f64beecdc03121e249d0f9ec15f0707
image: axllent/mailpit@sha256:e22dce5b36f93c77082e204a3942fb6b283b7896e057458400a4c88344c3df68
restart: always
ports:
- "1025:1025"

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

76
go.mod
View File

@@ -1,24 +1,32 @@
module github.com/glueops/autoglue
go 1.25.3
go 1.25.4
require (
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
github.com/aws/aws-sdk-go-v2/credentials v1.18.22
github.com/aws/aws-sdk-go-v2/service/route53 v1.59.4
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/gin-gonic/gin v1.11.0
github.com/go-chi/chi/v5 v5.2.3
github.com/go-chi/cors v1.2.2
github.com/go-chi/httprate v0.15.0
github.com/go-playground/validator/v10 v10.28.0
github.com/golang-jwt/jwt/v5 v5.3.0
github.com/google/uuid v1.6.0
github.com/joho/godotenv v1.5.1
github.com/rs/zerolog v1.34.0
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.43.0
golang.org/x/oauth2 v0.32.0
golang.org/x/crypto v0.44.0
golang.org/x/oauth2 v0.33.0
gopkg.in/yaml.v3 v3.0.1
gorm.io/datatypes v1.2.7
gorm.io/driver/postgres v1.6.0
@@ -27,32 +35,74 @@ require (
require (
filippo.io/edwards25519 v1.1.0 // indirect
github.com/BurntSushi/toml v1.1.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
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.13 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.13 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.13 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 // indirect
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.13 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.3 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.4 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.13 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.13 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.30.1 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.5 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.40.0 // indirect
github.com/aws/smithy-go v1.23.2 // indirect
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/cloudwego/base64x v0.1.6 // indirect
github.com/dchest/bcrypt_pbkdf v0.0.0-20150205184540-83f37f9c154a // 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-openapi/jsonpointer v0.19.6 // indirect
github.com/go-openapi/jsonreference v0.20.2 // indirect
github.com/go-openapi/spec v0.20.9 // indirect
github.com/go-openapi/swag v0.22.3 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-sql-driver/mysql v1.8.1 // indirect
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/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
github.com/jackc/pgx/v5 v5.6.0 // indirect
github.com/jackc/puddle/v2 v2.2.2 // indirect
github.com/jessevdk/go-flags v1.5.0 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/jmoiron/sqlx v1.3.5 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/klauspost/cpuid/v2 v2.2.10 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/lib/pq v1.10.9 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.19 // indirect
github.com/mattn/go-isatty v0.0.20 // 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/prometheus/client_golang v1.19.1 // indirect
github.com/prometheus/client_model v0.5.0 // 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
github.com/quic-go/quic-go v0.54.0 // indirect
github.com/sagikazarmark/locafero v0.11.0 // indirect
github.com/sirupsen/logrus v1.9.0 // indirect
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect
github.com/spf13/afero v1.15.0 // indirect
github.com/spf13/cast v1.10.0 // indirect
@@ -61,12 +111,20 @@ require (
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/zeebo/xxh3 v1.0.2 // indirect
go.uber.org/mock v0.5.0 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/sync v0.17.0 // indirect
golang.org/x/sys v0.37.0 // indirect
golang.org/x/text v0.30.0 // indirect
golang.org/x/tools v0.37.0 // indirect
golang.org/x/arch v0.20.0 // indirect
golang.org/x/mod v0.29.0 // indirect
golang.org/x/net v0.46.0 // indirect
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/tools v0.38.0 // 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
)

199
go.sum
View File

@@ -1,13 +1,89 @@
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
github.com/BurntSushi/toml v1.1.0 h1:ksErzDEI1khOiGPgpwuI7x2ebx/uXQNw7xJpn9Eq1+I=
github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
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/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
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/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/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=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.13/go.mod h1:oGnKwIYZ4XttyU2JWxFrwvhF6YKiK/9/wmE3v3Iu9K8=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.13 h1:HBSI2kDkMdWz4ZM7FjwE7e/pWDEZ+nR95x8Ztet1ooY=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.13/go.mod h1:YE94ZoDArI7awZqJzBAZ3PDD2zSfuP7w6P2knOzIn8M=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 h1:WKuaxf++XKWlHWu9ECbMlha8WOEGm0OUEZqm4K/Gcfk=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4/go.mod h1:ZWy7j6v1vWGmPReu0iSGvRiise4YI5SkR3OHKTZ6Wuc=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.13 h1:eg/WYAa12vqTphzIdWMzqYRVKKnCboVPRlvaybNCqPA=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.13/go.mod h1:/FDdxWhz1486obGrKKC1HONd7krpk38LBt+dutLcN9k=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.3 h1:x2Ibm/Af8Fi+BH+Hsn9TXGdT+hKbDd5XOTZxTMxDk7o=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.3/go.mod h1:IW1jwyrQgMdhisceG8fQLmQIydcT/jWY21rFhzgaKwo=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.4 h1:NvMjwvv8hpGUILarKw7Z4Q0w1H9anXKsesMxtw++MA4=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.4/go.mod h1:455WPHSwaGj2waRSpQp7TsnpOnBfw8iDfPfbwl7KPJE=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.13 h1:kDqdFvMY4AtKoACfzIGD8A0+hbT41KTKF//gq7jITfM=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.13/go.mod h1:lmKuogqSU3HzQCwZ9ZtcqOc5XGMqtDK7OIc2+DxiUEg=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.13 h1:zhBJXdhWIFZ1acfDYIhu4+LCzdUS2Vbcum7D01dXlHQ=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.13/go.mod h1:JaaOeCE368qn2Hzi3sEzY6FgAZVCIYcC2nwbro2QCh8=
github.com/aws/aws-sdk-go-v2/service/route53 v1.59.4 h1:KEszjusgJ2dAqE5nSJY+5AHBkakfah8Sx6Vk3pjgrq8=
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/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/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=
github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=
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=
@@ -16,12 +92,20 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3
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/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/dyaksa/archer v1.1.3 h1:jfe51tSNzzscFpu+Vilm4SKb0Lnv6FR1yaGspjab4x4=
github.com/dyaksa/archer v1.1.3/go.mod h1:IYSp67u14JHTNuvvy6gG1eaX2TPywXvfk1FiyZwVEK4=
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/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=
github.com/gin-gonic/gin v1.11.0/go.mod h1:+iq/FyxlGzII0KHiBGjuNn4UNENUlKbGlNmc+W50Dls=
github.com/go-chi/chi/v5 v5.2.3 h1:WQIt9uxdsAbgIYgid+BpYc+liqQZGMHRaUwp0JUcvdE=
github.com/go-chi/chi/v5 v5.2.3/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
github.com/go-chi/cors v1.2.2 h1:Jmey33TE+b+rB7fT8MUy1u0I4L+NARQlK6LhzKPSyQE=
@@ -43,6 +127,15 @@ 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-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=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.28.0 h1:Q7ibns33JjyW48gHkuFT91qX48KG0ktULL6FgHdG688=
github.com/go-playground/validator/v10 v10.28.0/go.mod h1:GoI6I1SjPBh9p7ykNE/yj3fFYbyDOpwMn5KXd+m2hUU=
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
@@ -50,6 +143,8 @@ github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9L
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
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/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo=
github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
@@ -57,8 +152,9 @@ github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0kt
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/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
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/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
@@ -71,16 +167,22 @@ 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/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=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g=
github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=
github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
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/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
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.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
@@ -89,6 +191,9 @@ 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/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/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
@@ -99,12 +204,23 @@ github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJ
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.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
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/microsoft/go-mssqldb v1.7.2 h1:CHkFJiObW7ItKTJfHo1QX7QBBD1iV+mn1eOyRP3b/PA=
github.com/microsoft/go-mssqldb v1.7.2/go.mod h1:kOvZKUdrhhFQmxLZqbwUV0rHkNkZpthMITIb2Ko1IoA=
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=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
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/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=
@@ -114,14 +230,30 @@ 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/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
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/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=
github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
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/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/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc=
github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik=
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/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
@@ -141,6 +273,7 @@ github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpE
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.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
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=
@@ -160,38 +293,56 @@ 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=
github.com/tuvistavie/securerandom v0.0.0-20140719024926-15512123a948/go.mod h1:a06d/M1pxWi51qiSrfGMHaEydtuXT06nha8N2aNQuXk=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
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/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/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0=
github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA=
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=
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-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04=
golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0=
golang.org/x/crypto v0.44.0 h1:A97SsFvM3AIwEEmTBiaxPPTYpDC47w720rdiiUvgoAU=
golang.org/x/crypto v0.44.0/go.mod h1:013i+Nw79BMiQiMsOPcVCB5ZIJbYkerPrGnOa00tvmc=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.28.0 h1:gQBtGhjxykdjY9YhZpSlZIsbnaE2+PgjfLWUQTnoZ1U=
golang.org/x/mod v0.28.0/go.mod h1:yfB/L0NOf/kmEbXjzCPOx1iK1fRutOydrCMsqRhEBxI=
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-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/oauth2 v0.32.0 h1:jsCblLleRMDrxMN29H3z/k1KliIvpLgCkE6R8FXXNgY=
golang.org/x/oauth2 v0.32.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
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.33.0 h1:4Q+qn+E5z8gPRJfmRy7C2gGG3T4jIprK6aSYgTXGRpo=
golang.org/x/oauth2 v0.33.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/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.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
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-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200219091948-cb0a6d8edb6c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/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-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-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -199,30 +350,32 @@ golang.org/x/sys v0.6.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.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
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/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.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
golang.org/x/term v0.36.0 h1:zMPR+aF8gfksFprF/Nc/rd1wRS1EI6nDBGyWAvDzx2Q=
golang.org/x/term v0.36.0/go.mod h1:Qu394IJq6V6dCBRgwqshf3mPF85AqzYEzofzRdZkWss=
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.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
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.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.37.0 h1:DVSRzp7FwePZW356yEAChSdNcQo6Nsp+fex1SUW09lE=
golang.org/x/tools v0.37.0/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w=
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=
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=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

View File

@@ -49,6 +49,14 @@ func AuthMiddleware(db *gorm.DB, requireOrg bool) func(http.Handler) http.Handle
} else if appKey := r.Header.Get("X-APP-KEY"); appKey != "" {
secret := r.Header.Get("X-APP-SECRET")
user = auth.ValidateAppKeyPair(appKey, secret, db)
} else if c, err := r.Cookie("ag_jwt"); err == nil {
tok := strings.TrimSpace(c.Value)
if strings.HasPrefix(strings.ToLower(tok), "bearer ") {
tok = tok[7:]
}
if tok != "" {
user = auth.ValidateJWT(tok, db)
}
}
if user == nil {

View File

@@ -0,0 +1,26 @@
package api
import (
"net/http"
"github.com/glueops/autoglue/internal/api/httpmiddleware"
"github.com/glueops/autoglue/internal/bg"
"github.com/glueops/autoglue/internal/handlers"
"github.com/go-chi/chi/v5"
"gorm.io/gorm"
)
func mountAdminRoutes(r chi.Router, db *gorm.DB, jobs *bg.Jobs, authUser func(http.Handler) http.Handler) {
r.Route("/admin", func(admin chi.Router) {
admin.Route("/archer", func(archer chi.Router) {
archer.Use(authUser)
archer.Use(httpmiddleware.RequirePlatformAdmin())
archer.Get("/jobs", handlers.AdminListArcherJobs(db))
archer.Post("/jobs", handlers.AdminEnqueueArcherJob(db, jobs))
archer.Post("/jobs/{id}/retry", handlers.AdminRetryArcherJob(db))
archer.Post("/jobs/{id}/cancel", handlers.AdminCancelArcherJob(db))
archer.Get("/queues", handlers.AdminListArcherQueues(db))
})
})
}

View File

@@ -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 mountAnnotationRoutes(r chi.Router, db *gorm.DB, authOrg func(http.Handler) http.Handler) {
r.Route("/annotations", func(a chi.Router) {
a.Use(authOrg)
a.Get("/", handlers.ListAnnotations(db))
a.Post("/", handlers.CreateAnnotation(db))
a.Get("/{id}", handlers.GetAnnotation(db))
a.Patch("/{id}", handlers.UpdateAnnotation(db))
a.Delete("/{id}", handlers.DeleteAnnotation(db))
})
}

View File

@@ -0,0 +1,37 @@
package api
import (
"github.com/glueops/autoglue/internal/api/httpmiddleware"
"github.com/glueops/autoglue/internal/bg"
"github.com/go-chi/chi/v5"
"gorm.io/gorm"
)
func mountAPIRoutes(r chi.Router, db *gorm.DB, jobs *bg.Jobs) {
r.Route("/api", func(api chi.Router) {
api.Route("/v1", func(v1 chi.Router) {
authUser := httpmiddleware.AuthMiddleware(db, false)
authOrg := httpmiddleware.AuthMiddleware(db, true)
// shared basics
mountMetaRoutes(v1)
mountAuthRoutes(v1, db)
// admin
mountAdminRoutes(v1, db, jobs, authUser)
// user/org scoped
mountMeRoutes(v1, db, authUser)
mountOrgRoutes(v1, db, authUser, authOrg)
mountCredentialRoutes(v1, db, authOrg)
mountSSHRoutes(v1, db, authOrg)
mountServerRoutes(v1, db, authOrg)
mountTaintRoutes(v1, db, authOrg)
mountLabelRoutes(v1, db, authOrg)
mountAnnotationRoutes(v1, db, authOrg)
mountNodePoolRoutes(v1, db, authOrg)
mountDNSRoutes(v1, db, authOrg)
})
})
}

View File

@@ -0,0 +1,16 @@
package api
import (
"github.com/glueops/autoglue/internal/handlers"
"github.com/go-chi/chi/v5"
"gorm.io/gorm"
)
func mountAuthRoutes(r chi.Router, db *gorm.DB) {
r.Route("/auth", func(a chi.Router) {
a.Post("/{provider}/start", handlers.AuthStart(db))
a.Get("/{provider}/callback", handlers.AuthCallback(db))
a.Post("/refresh", handlers.Refresh(db))
a.Post("/logout", handlers.Logout(db))
})
}

View File

@@ -0,0 +1,21 @@
package api
import (
"net/http"
"github.com/glueops/autoglue/internal/handlers"
"github.com/go-chi/chi/v5"
"gorm.io/gorm"
)
func mountCredentialRoutes(r chi.Router, db *gorm.DB, authOrg func(http.Handler) http.Handler) {
r.Route("/credentials", func(c chi.Router) {
c.Use(authOrg)
c.Get("/", handlers.ListCredentials(db))
c.Post("/", handlers.CreateCredential(db))
c.Get("/{id}", handlers.GetCredential(db))
c.Patch("/{id}", handlers.UpdateCredential(db))
c.Delete("/{id}", handlers.DeleteCredential(db))
c.Post("/{id}/reveal", handlers.RevealCredential(db))
})
}

View File

@@ -0,0 +1,53 @@
package api
import (
"net/http"
"strings"
"github.com/gin-gonic/gin"
pgapi "github.com/sosedoff/pgweb/pkg/api"
pgclient "github.com/sosedoff/pgweb/pkg/client"
pgcmd "github.com/sosedoff/pgweb/pkg/command"
)
func PgwebHandler(dbURL, prefix string, readonly bool) (http.Handler, error) {
// Normalize prefix for pgweb:
// - no leading slash
// - always trailing slash if not empty
prefix = strings.Trim(prefix, "/")
if prefix != "" {
prefix = prefix + "/"
}
pgcmd.Opts = pgcmd.Options{
URL: dbURL,
Prefix: prefix, // e.g. "db-studio/"
ReadOnly: readonly,
Sessions: false,
LockSession: true,
SkipOpen: true,
}
cli, err := pgclient.NewFromUrl(dbURL, nil)
if err != nil {
return nil, err
}
if readonly {
_ = cli.SetReadOnlyMode()
}
if err := cli.Test(); err != nil {
return nil, err
}
pgapi.DbClient = cli
gin.SetMode(gin.ReleaseMode)
g := gin.New()
g.Use(gin.Recovery())
pgapi.SetupRoutes(g)
pgapi.SetupMetrics(g)
return g, nil
}

View File

@@ -0,0 +1,26 @@
package api
import (
"net/http"
"github.com/glueops/autoglue/internal/handlers"
"github.com/go-chi/chi/v5"
"gorm.io/gorm"
)
func mountDNSRoutes(r chi.Router, db *gorm.DB, authOrg func(http.Handler) http.Handler) {
r.Route("/dns", func(d chi.Router) {
d.Use(authOrg)
d.Get("/domains", handlers.ListDomains(db))
d.Post("/domains", handlers.CreateDomain(db))
d.Get("/domains/{id}", handlers.GetDomain(db))
d.Patch("/domains/{id}", handlers.UpdateDomain(db))
d.Delete("/domains/{id}", handlers.DeleteDomain(db))
d.Get("/domains/{domain_id}/records", handlers.ListRecordSets(db))
d.Post("/domains/{domain_id}/records", handlers.CreateRecordSet(db))
d.Patch("/records/{id}", handlers.UpdateRecordSet(db))
d.Delete("/records/{id}", handlers.DeleteRecordSet(db))
})
}

View File

@@ -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 mountLabelRoutes(r chi.Router, db *gorm.DB, authOrg func(http.Handler) http.Handler) {
r.Route("/labels", func(l chi.Router) {
l.Use(authOrg)
l.Get("/", handlers.ListLabels(db))
l.Post("/", handlers.CreateLabel(db))
l.Get("/{id}", handlers.GetLabel(db))
l.Patch("/{id}", handlers.UpdateLabel(db))
l.Delete("/{id}", handlers.DeleteLabel(db))
})
}

View File

@@ -0,0 +1,22 @@
package api
import (
"net/http"
"github.com/glueops/autoglue/internal/handlers"
"github.com/go-chi/chi/v5"
"gorm.io/gorm"
)
func mountMeRoutes(r chi.Router, db *gorm.DB, authUser func(http.Handler) http.Handler) {
r.Route("/me", func(me chi.Router) {
me.Use(authUser)
me.Get("/", handlers.GetMe(db))
me.Patch("/", handlers.UpdateMe(db))
me.Get("/api-keys", handlers.ListUserAPIKeys(db))
me.Post("/api-keys", handlers.CreateUserAPIKey(db))
me.Delete("/api-keys/{id}", handlers.DeleteUserAPIKey(db))
})
}

View File

@@ -0,0 +1,13 @@
package api
import (
"github.com/glueops/autoglue/internal/handlers"
"github.com/go-chi/chi/v5"
)
func mountMetaRoutes(r chi.Router) {
// Versioned JWKS for swagger
r.Get("/.well-known/jwks.json", handlers.JWKSHandler)
r.Get("/healthz", handlers.HealthCheck)
r.Get("/version", handlers.Version)
}

View File

@@ -0,0 +1,40 @@
package api
import (
"net/http"
"github.com/glueops/autoglue/internal/handlers"
"github.com/go-chi/chi/v5"
"gorm.io/gorm"
)
func mountNodePoolRoutes(r chi.Router, db *gorm.DB, authOrg func(http.Handler) http.Handler) {
r.Route("/node-pools", func(n chi.Router) {
n.Use(authOrg)
n.Get("/", handlers.ListNodePools(db))
n.Post("/", handlers.CreateNodePool(db))
n.Get("/{id}", handlers.GetNodePool(db))
n.Patch("/{id}", handlers.UpdateNodePool(db))
n.Delete("/{id}", handlers.DeleteNodePool(db))
// Servers
n.Get("/{id}/servers", handlers.ListNodePoolServers(db))
n.Post("/{id}/servers", handlers.AttachNodePoolServers(db))
n.Delete("/{id}/servers/{serverId}", handlers.DetachNodePoolServer(db))
// Taints
n.Get("/{id}/taints", handlers.ListNodePoolTaints(db))
n.Post("/{id}/taints", handlers.AttachNodePoolTaints(db))
n.Delete("/{id}/taints/{taintId}", handlers.DetachNodePoolTaint(db))
// Labels
n.Get("/{id}/labels", handlers.ListNodePoolLabels(db))
n.Post("/{id}/labels", handlers.AttachNodePoolLabels(db))
n.Delete("/{id}/labels/{labelId}", handlers.DetachNodePoolLabel(db))
// Annotations
n.Get("/{id}/annotations", handlers.ListNodePoolAnnotations(db))
n.Post("/{id}/annotations", handlers.AttachNodePoolAnnotations(db))
n.Delete("/{id}/annotations/{annotationId}", handlers.DetachNodePoolAnnotation(db))
})
}

View File

@@ -0,0 +1,35 @@
package api
import (
"net/http"
"github.com/glueops/autoglue/internal/handlers"
"github.com/go-chi/chi/v5"
"gorm.io/gorm"
)
func mountOrgRoutes(r chi.Router, db *gorm.DB, authUser, authOrg func(http.Handler) http.Handler) {
r.Route("/orgs", func(o chi.Router) {
o.Use(authUser)
o.Get("/", handlers.ListMyOrgs(db))
o.Post("/", handlers.CreateOrg(db))
o.Group(func(og chi.Router) {
og.Use(authOrg)
og.Get("/{id}", handlers.GetOrg(db))
og.Patch("/{id}", handlers.UpdateOrg(db))
og.Delete("/{id}", handlers.DeleteOrg(db))
// members
og.Get("/{id}/members", handlers.ListMembers(db))
og.Post("/{id}/members", handlers.AddOrUpdateMember(db))
og.Delete("/{id}/members/{user_id}", handlers.RemoveMember(db))
// org-scoped key/secret pair
og.Get("/{id}/api-keys", handlers.ListOrgKeys(db))
og.Post("/{id}/api-keys", handlers.CreateOrgKey(db))
og.Delete("/{id}/api-keys/{key_id}", handlers.DeleteOrgKey(db))
})
})
}

View File

@@ -0,0 +1,24 @@
package api
import (
httpPprof "net/http/pprof"
"github.com/go-chi/chi/v5"
)
func mountPprofRoutes(r chi.Router) {
r.Route("/debug/pprof", func(pr chi.Router) {
pr.Get("/", httpPprof.Index)
pr.Get("/cmdline", httpPprof.Cmdline)
pr.Get("/profile", httpPprof.Profile)
pr.Get("/symbol", httpPprof.Symbol)
pr.Get("/trace", httpPprof.Trace)
pr.Handle("/allocs", httpPprof.Handler("allocs"))
pr.Handle("/block", httpPprof.Handler("block"))
pr.Handle("/goroutine", httpPprof.Handler("goroutine"))
pr.Handle("/heap", httpPprof.Handler("heap"))
pr.Handle("/mutex", httpPprof.Handler("mutex"))
pr.Handle("/threadcreate", httpPprof.Handler("threadcreate"))
})
}

View File

@@ -0,0 +1,21 @@
package api
import (
"net/http"
"github.com/glueops/autoglue/internal/handlers"
"github.com/go-chi/chi/v5"
"gorm.io/gorm"
)
func mountServerRoutes(r chi.Router, db *gorm.DB, authOrg func(http.Handler) http.Handler) {
r.Route("/servers", func(s chi.Router) {
s.Use(authOrg)
s.Get("/", handlers.ListServers(db))
s.Post("/", handlers.CreateServer(db))
s.Get("/{id}", handlers.GetServer(db))
s.Patch("/{id}", handlers.UpdateServer(db))
s.Delete("/{id}", handlers.DeleteServer(db))
s.Post("/{id}/reset-hostkey", handlers.ResetServerHostKey(db))
})
}

View File

@@ -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 mountSSHRoutes(r chi.Router, db *gorm.DB, authOrg func(http.Handler) http.Handler) {
r.Route("/ssh", func(s chi.Router) {
s.Use(authOrg)
s.Get("/", handlers.ListPublicSshKeys(db))
s.Post("/", handlers.CreateSSHKey(db))
s.Get("/{id}", handlers.GetSSHKey(db))
s.Delete("/{id}", handlers.DeleteSSHKey(db))
s.Get("/{id}/download", handlers.DownloadSSHKey(db))
})
}

View File

@@ -0,0 +1,15 @@
package api
import (
"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/swagger.json", serveSwaggerFromEmbed(docs.SwaggerJSON, "application/json"))
r.Get("/swagger/swagger.yaml", serveSwaggerFromEmbed(docs.SwaggerYAML, "application/x-yaml"))
}

View File

@@ -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 mountTaintRoutes(r chi.Router, db *gorm.DB, authOrg func(http.Handler) http.Handler) {
r.Route("/taints", func(t chi.Router) {
t.Use(authOrg)
t.Get("/", handlers.ListTaints(db))
t.Post("/", handlers.CreateTaint(db))
t.Get("/{id}", handlers.GetTaint(db))
t.Patch("/{id}", handlers.UpdateTaint(db))
t.Delete("/{id}", handlers.DeleteTaint(db))
})
}

View File

@@ -36,7 +36,7 @@ func SecurityHeaders(next http.Handler) http.Handler {
// 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",
"connect-src 'self' http://localhost:5173 ws://localhost:5173 ws://localhost:8080 https://api.github.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'",
"script-src 'self' 'unsafe-inline'",
"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'",
"connect-src 'self' ws://localhost:8080 https://api.github.com",
"frame-ancestors 'none'",
}, "; "))
}

View File

@@ -3,11 +3,10 @@ package api
import (
"fmt"
"net/http"
httpPprof "net/http/pprof"
"os"
"strings"
"time"
"github.com/glueops/autoglue/docs"
"github.com/glueops/autoglue/internal/api/httpmiddleware"
"github.com/glueops/autoglue/internal/bg"
"github.com/glueops/autoglue/internal/config"
@@ -23,11 +22,9 @@ import (
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
httpSwagger "github.com/swaggo/http-swagger/v2"
)
func NewRouter(db *gorm.DB, jobs *bg.Jobs) http.Handler {
func NewRouter(db *gorm.DB, jobs *bg.Jobs, studio http.Handler) http.Handler {
zerolog.TimeFieldFormat = time.RFC3339
l := log.Output(zerolog.ConsoleWriter{Out: os.Stdout, TimeFormat: "15:04:05"})
@@ -59,145 +56,44 @@ func NewRouter(db *gorm.DB, jobs *bg.Jobs) http.Handler {
MaxAge: 600,
}))
r.Use(middleware.AllowContentType("application/json"))
r.Use(middleware.Maybe(
middleware.AllowContentType("application/json"),
func(r *http.Request) bool {
// return true => run AllowContentType
// return false => skip AllowContentType for this request
return !strings.HasPrefix(r.URL.Path, "/db-studio")
}))
//r.Use(middleware.AllowContentType("application/json"))
// Unversioned, non-auth endpoints
r.Get("/.well-known/jwks.json", handlers.JWKSHandler)
r.Route("/api", func(api chi.Router) {
api.Route("/v1", func(v1 chi.Router) {
// Versioned API
mountAPIRoutes(r, db, jobs)
// Optional DB studio
if studio != nil {
r.Group(func(gr chi.Router) {
authUser := httpmiddleware.AuthMiddleware(db, false)
authOrg := httpmiddleware.AuthMiddleware(db, true)
// Also serving a versioned JWKS for swagger, which uses BasePath
v1.Get("/.well-known/jwks.json", handlers.JWKSHandler)
v1.Get("/healthz", handlers.HealthCheck)
v1.Route("/auth", func(a chi.Router) {
a.Post("/{provider}/start", handlers.AuthStart(db))
a.Get("/{provider}/callback", handlers.AuthCallback(db))
a.Post("/refresh", handlers.Refresh(db))
a.Post("/logout", handlers.Logout(db))
})
v1.Route("/admin/archer", func(a chi.Router) {
a.Use(authUser)
a.Use(httpmiddleware.RequirePlatformAdmin())
a.Get("/jobs", handlers.AdminListArcherJobs(db))
a.Post("/jobs", handlers.AdminEnqueueArcherJob(db, jobs))
a.Post("/jobs/{id}/retry", handlers.AdminRetryArcherJob(db))
a.Post("/jobs/{id}/cancel", handlers.AdminCancelArcherJob(db))
a.Get("/queues", handlers.AdminListArcherQueues(db))
})
v1.Route("/me", func(me chi.Router) {
me.Use(authUser)
me.Get("/", handlers.GetMe(db))
me.Patch("/", handlers.UpdateMe(db))
me.Get("/api-keys", handlers.ListUserAPIKeys(db))
me.Post("/api-keys", handlers.CreateUserAPIKey(db))
me.Delete("/api-keys/{id}", handlers.DeleteUserAPIKey(db))
})
v1.Route("/orgs", func(o chi.Router) {
o.Use(authUser)
o.Get("/", handlers.ListMyOrgs(db))
o.Post("/", handlers.CreateOrg(db))
o.Group(func(og chi.Router) {
og.Use(authOrg)
og.Get("/{id}", handlers.GetOrg(db))
og.Patch("/{id}", handlers.UpdateOrg(db))
og.Delete("/{id}", handlers.DeleteOrg(db))
// members
og.Get("/{id}/members", handlers.ListMembers(db))
og.Post("/{id}/members", handlers.AddOrUpdateMember(db))
og.Delete("/{id}/members/{user_id}", handlers.RemoveMember(db))
// org-scoped key/secret pair
og.Get("/{id}/api-keys", handlers.ListOrgKeys(db))
og.Post("/{id}/api-keys", handlers.CreateOrgKey(db))
og.Delete("/{id}/api-keys/{key_id}", handlers.DeleteOrgKey(db))
})
})
v1.Route("/ssh", func(s chi.Router) {
s.Use(authOrg)
s.Get("/", handlers.ListPublicSshKeys(db))
s.Post("/", handlers.CreateSSHKey(db))
s.Get("/{id}", handlers.GetSSHKey(db))
s.Delete("/{id}", handlers.DeleteSSHKey(db))
s.Get("/{id}/download", handlers.DownloadSSHKey(db))
})
v1.Route("/servers", func(s chi.Router) {
s.Use(authOrg)
s.Get("/", handlers.ListServers(db))
s.Post("/", handlers.CreateServer(db))
s.Get("/{id}", handlers.GetServer(db))
s.Patch("/{id}", handlers.UpdateServer(db))
s.Delete("/{id}", handlers.DeleteServer(db))
})
v1.Route("/taints", func(s chi.Router) {
s.Use(authOrg)
s.Get("/", handlers.ListTaints(db))
s.Post("/", handlers.CreateTaint(db))
s.Get("/{id}", handlers.GetTaint(db))
s.Patch("/{id}", handlers.UpdateTaint(db))
s.Delete("/{id}", handlers.DeleteTaint(db))
})
v1.Route("/labels", func(l chi.Router) {
l.Use(authOrg)
l.Get("/", handlers.ListLabels(db))
l.Post("/", handlers.CreateLabel(db))
l.Get("/{id}", handlers.GetLabel(db))
l.Patch("/{id}", handlers.UpdateLabel(db))
l.Delete("/{id}", handlers.DeleteLabel(db))
})
v1.Route("/annotations", func(a chi.Router) {
a.Use(authOrg)
a.Get("/", handlers.ListAnnotations(db))
a.Post("/", handlers.CreateAnnotation(db))
a.Get("/{id}", handlers.GetAnnotation(db))
a.Patch("/{id}", handlers.UpdateAnnotation(db))
a.Delete("/{id}", handlers.DeleteAnnotation(db))
})
adminOnly := httpmiddleware.RequirePlatformAdmin()
gr.Use(authUser, adminOnly)
gr.Mount("/db-studio", studio)
})
})
}
// pprof
if config.IsDebug() {
r.Route("/debug/pprof", func(pr chi.Router) {
pr.Get("/", httpPprof.Index)
pr.Get("/cmdline", httpPprof.Cmdline)
pr.Get("/profile", httpPprof.Profile)
pr.Get("/symbol", httpPprof.Symbol)
pr.Get("/trace", httpPprof.Trace)
pr.Handle("/allocs", httpPprof.Handler("allocs"))
pr.Handle("/block", httpPprof.Handler("block"))
pr.Handle("/goroutine", httpPprof.Handler("goroutine"))
pr.Handle("/heap", httpPprof.Handler("heap"))
pr.Handle("/mutex", httpPprof.Handler("mutex"))
pr.Handle("/threadcreate", httpPprof.Handler("threadcreate"))
})
mountPprofRoutes(r)
}
// Swagger
if config.IsSwaggerEnabled() {
r.Get("/swagger/*", httpSwagger.Handler(
httpSwagger.URL("swagger.json"),
))
r.Get("/swagger/swagger.json", serveSwaggerFromEmbed(docs.SwaggerJSON, "application/json"))
r.Get("/swagger/swagger.yaml", serveSwaggerFromEmbed(docs.SwaggerYAML, "application/x-yaml"))
mountSwaggerRoutes(r)
}
// UI dev/prod
if config.IsUIDev() {
fmt.Println("Running in development mode")
// Dev: isolate proxy from chi middlewares so WS upgrade can hijack.
proxy, err := web.DevProxy("http://localhost:5173")
if err != nil {
log.Error().Err(err).Msg("dev proxy init failed")
@@ -205,22 +101,20 @@ func NewRouter(db *gorm.DB, jobs *bg.Jobs) http.Handler {
}
mux := http.NewServeMux()
// Send API/Swagger/pprof to chi
mux.Handle("/api/", r)
mux.Handle("/api", r)
mux.Handle("/swagger/", r)
mux.Handle("/db-studio/", r)
mux.Handle("/debug/pprof/", r)
// Everything else (/, /brand-preview, assets) → proxy (no middlewares)
mux.Handle("/", proxy)
return mux
}
fmt.Println("Running in production mode")
if h, err := web.SPAHandler(); err == nil {
r.NotFound(h.ServeHTTP)
} else {
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")
}
log.Error().Err(err).Msg("spa handler init failed")
}
return r

View File

@@ -38,7 +38,13 @@ func NewRuntime() *Runtime {
&models.Taint{},
&models.Label{},
&models.Annotation{},
&models.NodePool{},
&models.Cluster{},
&models.Credential{},
&models.Domain{},
&models.RecordSet{},
)
if err != nil {
log.Fatalf("Error initializing database: %v", err)
}

264
internal/bg/backup_s3.go Normal file
View File

@@ -0,0 +1,264 @@
package bg
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"mime"
"os"
"os/exec"
"path/filepath"
"strings"
"time"
"github.com/aws/aws-sdk-go-v2/aws"
awsconfig "github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/credentials"
"github.com/aws/aws-sdk-go-v2/service/s3"
s3types "github.com/aws/aws-sdk-go-v2/service/s3/types"
"github.com/dyaksa/archer"
"github.com/dyaksa/archer/job"
"github.com/glueops/autoglue/internal/config"
"github.com/glueops/autoglue/internal/models"
"github.com/glueops/autoglue/internal/utils"
"github.com/google/uuid"
"github.com/rs/zerolog/log"
"gorm.io/gorm"
)
type DbBackupArgs struct {
IntervalS int `json:"interval_seconds,omitempty"`
}
type s3Scope struct {
Service string `json:"service"`
Region string `json:"region"`
}
type encAWS struct {
AccessKeyID string `json:"access_key_id"`
SecretAccessKey string `json:"secret_access_key"`
}
func DbBackupWorker(db *gorm.DB, jobs *Jobs) archer.WorkerFn {
return func(ctx context.Context, j job.Job) (any, error) {
args := DbBackupArgs{IntervalS: 3600}
_ = j.ParseArguments(&args)
if args.IntervalS <= 0 {
args.IntervalS = 3600
}
if err := DbBackup(ctx, db); err != nil {
return nil, err
}
queue := j.QueueName
if strings.TrimSpace(queue) == "" {
queue = "db_backup_s3"
}
next := time.Now().Add(time.Duration(args.IntervalS) * time.Second)
payload := DbBackupArgs{}
opts := []archer.FnOptions{
archer.WithScheduleTime(next),
archer.WithMaxRetries(1),
}
if _, err := jobs.Enqueue(ctx, uuid.NewString(), queue, payload, opts...); err != nil {
log.Error().Err(err).Str("queue", queue).Time("next", next).Msg("failed to enqueue next db backup")
} else {
log.Info().Str("queue", queue).Time("next", next).Msg("scheduled next db backup")
}
return nil, nil
}
}
func DbBackup(ctx context.Context, db *gorm.DB) error {
cfg, err := config.Load()
if err != nil {
return fmt.Errorf("load config: %w", err)
}
cred, sc, err := loadS3Credential(ctx, db)
if err != nil {
return fmt.Errorf("load credential: %w", err)
}
ak, sk, err := decryptAwsAccessKeys(ctx, db, cred)
if err != nil {
return fmt.Errorf("decrypt aws keys: %w", err)
}
region := sc.Region
if strings.TrimSpace(region) == "" {
region = cred.Region
if strings.TrimSpace(region) == "" {
region = "us-west-1"
}
}
bucket := strings.ToLower(fmt.Sprintf("%s-autoglue-backups-%s", cred.OrganizationID, region))
s3cli, err := makeS3Client(ctx, ak, sk, region)
if err != nil {
return err
}
if err := ensureBucket(ctx, s3cli, bucket, region); err != nil {
return fmt.Errorf("ensure bucket: %w", err)
}
tmpDir := os.TempDir()
now := time.Now().UTC()
key := fmt.Sprintf("%04d/%02d/%02d/backup-%02d.sql", now.Year(), now.Month(), now.Day(), now.Hour())
outPath := filepath.Join(tmpDir, "autoglue-backup-"+now.Format("20060102T150405Z")+".sql")
if err := runPgDump(ctx, cfg.DbURL, outPath); err != nil {
return fmt.Errorf("pg_dump: %w", err)
}
defer os.Remove(outPath)
if err := uploadFileToS3(ctx, s3cli, bucket, key, outPath); err != nil {
return fmt.Errorf("s3 upload: %w", err)
}
log.Info().Str("bucket", bucket).Str("key", key).Msg("backup uploaded")
return nil
}
// --- Helpers
func loadS3Credential(ctx context.Context, db *gorm.DB) (models.Credential, s3Scope, error) {
var c models.Credential
err := db.
WithContext(ctx).
Where("provider = ? AND kind = ? AND scope_kind = ?", "aws", "aws_access_key", "service").
Where("scope ->> 'service' = ?", "s3").
Order("created_at DESC").
First(&c).Error
if err != nil {
return models.Credential{}, s3Scope{}, fmt.Errorf("load credential: %w", err)
}
var sc s3Scope
_ = json.Unmarshal(c.Scope, &sc)
return c, sc, nil
}
func decryptAwsAccessKeys(ctx context.Context, db *gorm.DB, c models.Credential) (string, string, error) {
plain, err := utils.DecryptForOrg(c.OrganizationID, c.EncryptedData, c.IV, c.Tag, db)
if err != nil {
return "", "", err
}
var payload encAWS
if err := json.Unmarshal([]byte(plain), &payload); err != nil {
return "", "", fmt.Errorf("parse decrypted payload: %w", err)
}
if payload.AccessKeyID == "" || payload.SecretAccessKey == "" {
return "", "", errors.New("decrypted payload missing keys")
}
return payload.AccessKeyID, payload.SecretAccessKey, nil
}
func makeS3Client(ctx context.Context, accessKey, secret, region string) (*s3.Client, error) {
staticCredentialsProvider := credentials.NewStaticCredentialsProvider(accessKey, secret, "")
cfg, err := awsconfig.LoadDefaultConfig(ctx, awsconfig.WithCredentialsProvider(staticCredentialsProvider), awsconfig.WithRegion(region))
if err != nil {
return nil, fmt.Errorf("aws config: %w", err)
}
return s3.NewFromConfig(cfg), nil
}
func ensureBucket(ctx context.Context, s3cli *s3.Client, bucket, region string) error {
_, err := s3cli.HeadBucket(ctx, &s3.HeadBucketInput{Bucket: aws.String(bucket)})
if err == nil {
return nil
}
if out, err := s3cli.GetBucketLocation(ctx, &s3.GetBucketLocationInput{Bucket: aws.String(bucket)}); err == nil {
existing := string(out.LocationConstraint)
if existing == "" {
existing = "us-east-1"
}
if existing != region {
return fmt.Errorf("bucket %q already exists in region %q (requested %q)", bucket, existing, region)
}
}
// Create; LocationConstraint except us-east-1
in := &s3.CreateBucketInput{Bucket: aws.String(bucket)}
if region != "us-east-1" {
in.CreateBucketConfiguration = &s3types.CreateBucketConfiguration{
LocationConstraint: s3types.BucketLocationConstraint(region),
}
}
if _, err := s3cli.CreateBucket(ctx, in); err != nil {
return fmt.Errorf("create bucket: %w", err)
}
// default SSE (best-effort)
_, _ = s3cli.PutBucketEncryption(ctx, &s3.PutBucketEncryptionInput{
Bucket: aws.String(bucket),
ServerSideEncryptionConfiguration: &s3types.ServerSideEncryptionConfiguration{
Rules: []s3types.ServerSideEncryptionRule{
{ApplyServerSideEncryptionByDefault: &s3types.ServerSideEncryptionByDefault{
SSEAlgorithm: s3types.ServerSideEncryptionAes256,
}},
},
},
})
return nil
}
func runPgDump(ctx context.Context, dbURL, outPath string) error {
if err := os.MkdirAll(filepath.Dir(outPath), 0o755); err != nil {
return err
}
args := []string{
"--no-owner",
"--no-privileges",
"--format=plain",
"--file", outPath,
dbURL,
}
cmd := exec.CommandContext(ctx, "pg_dump", args...)
var stderr bytes.Buffer
cmd.Stderr = &stderr
if err := cmd.Run(); err != nil {
return fmt.Errorf("pg_dump failed: %v | %s", err, stderr.String())
}
return nil
}
func uploadFileToS3(ctx context.Context, s3cli *s3.Client, bucket, key, path string) error {
f, err := os.Open(path)
if err != nil {
return err
}
defer f.Close()
info, _ := f.Stat()
_, err = s3cli.PutObject(ctx, &s3.PutObjectInput{
Bucket: aws.String(bucket),
Key: aws.String(key),
Body: f,
ContentLength: aws.Int64(info.Size()),
ContentType: aws.String(mime.TypeByExtension(filepath.Ext(path))),
ServerSideEncryption: s3types.ServerSideEncryptionAes256,
})
return err
}

View File

@@ -2,8 +2,8 @@ package bg
import (
"context"
"encoding/base64"
"fmt"
"log"
"net"
"strings"
"time"
@@ -13,13 +13,16 @@ import (
"github.com/glueops/autoglue/internal/models"
"github.com/glueops/autoglue/internal/utils"
"github.com/google/uuid"
"github.com/rs/zerolog/log"
"golang.org/x/crypto/ssh"
"gorm.io/gorm"
)
// ----- Public types -----
type BastionBootstrapArgs struct{}
type BastionBootstrapArgs struct {
IntervalS int `json:"interval_seconds,omitempty"`
}
type BastionBootstrapFailure struct {
ID uuid.UUID `json:"id"`
@@ -39,11 +42,17 @@ type BastionBootstrapResult struct {
// ----- Worker -----
func BastionBootstrapWorker(db *gorm.DB) archer.WorkerFn {
func BastionBootstrapWorker(db *gorm.DB, jobs *Jobs) archer.WorkerFn {
return func(ctx context.Context, j job.Job) (any, error) {
args := BastionBootstrapArgs{IntervalS: 120}
jobID := j.ID
start := time.Now()
_ = j.ParseArguments(&args)
if args.IntervalS <= 0 {
args.IntervalS = 120
}
var servers []models.Server
if err := db.
Preload("SshKey").
@@ -105,7 +114,7 @@ func BastionBootstrapWorker(db *gorm.DB) archer.WorkerFn {
// 4) SSH + install docker
host := net.JoinHostPort(*s.PublicIPAddress, "22")
runCtx, cancel := context.WithTimeout(ctx, perHostTimeout)
out, err := sshInstallDockerWithOutput(runCtx, host, s.SSHUser, []byte(privKey))
out, err := sshInstallDockerWithOutput(runCtx, db, s, host, s.SSHUser, []byte(privKey))
cancel()
if err != nil {
@@ -147,9 +156,17 @@ func BastionBootstrapWorker(db *gorm.DB) archer.WorkerFn {
Failures: failures,
}
// log.Printf("[bastion] level=INFO job=%s step=finish processed=%d ready=%d failed=%d elapsed_ms=%d",
// jobID, proc, ok, fail, res.ElapsedMs)
log.Debug().Int("processed", proc).Int("ready", ok).Int("failed", fail).Msg("[bastion] reconcile tick ok")
next := time.Now().Add(time.Duration(args.IntervalS) * time.Second)
_, _ = jobs.Enqueue(
ctx,
uuid.NewString(),
"bootstrap_bastion",
args,
archer.WithScheduleTime(next),
archer.WithMaxRetries(1),
)
return res, nil
}
}
@@ -187,16 +204,24 @@ func logHostInfo(jobID string, s *models.Server, step, msg string, kv ...any) {
// ----- SSH & command execution -----
// returns combined stdout/stderr so caller can log it on error
func sshInstallDockerWithOutput(ctx context.Context, host, user string, privateKeyPEM []byte) (string, error) {
func sshInstallDockerWithOutput(
ctx context.Context,
db *gorm.DB,
s *models.Server,
host, user string,
privateKeyPEM []byte,
) (string, error) {
signer, err := ssh.ParsePrivateKey(privateKeyPEM)
if err != nil {
return "", fmt.Errorf("parse private key: %w", err)
}
hkcb := makeDBHostKeyCallback(db, s)
config := &ssh.ClientConfig{
User: user,
Auth: []ssh.AuthMethod{ssh.PublicKeys(signer)},
HostKeyCallback: ssh.InsecureIgnoreHostKey(), // TODO: known_hosts verification
HostKeyCallback: hkcb,
Timeout: 30 * time.Second,
}
@@ -225,19 +250,242 @@ func sshInstallDockerWithOutput(ctx context.Context, host, user string, privateK
script := `
set -euxo pipefail
if ! command -v docker >/dev/null 2>&1; then
curl -fsSL https://get.docker.com | sh
# ----------- toggles (set to 0 to skip) -----------
: "${BASELINE_PKGS:=1}"
: "${INSTALL_DOCKER:=1}"
: "${SSH_HARDEN:=1}"
: "${FIREWALL:=1}"
: "${AUTO_UPDATES:=1}"
: "${TIME_SYNC:=1}"
: "${FAIL2BAN:=1}"
: "${BANNER:=1}"
# ----------- helpers -----------
have() { command -v "$1" >/dev/null 2>&1; }
pm=""
if have apt-get; then pm="apt"
elif have dnf; then pm="dnf"
elif have yum; then pm="yum"
elif have zypper; then pm="zypper"
elif have apk; then pm="apk"
fi
# try to enable/start (handles distros with systemd)
if command -v systemctl >/dev/null 2>&1; then
sudo systemctl enable --now docker || true
pm_update_install() {
case "$pm" in
apt)
sudo apt-get update -y
sudo DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends "$@"
;;
dnf) sudo dnf install -y "$@" ;;
yum) sudo yum install -y "$@" ;;
zypper) sudo zypper --non-interactive install -y "$@" || true ;;
apk) sudo apk add --no-cache "$@" ;;
*)
echo "Unsupported distro: couldn't detect package manager" >&2
return 1
;;
esac
}
systemd_enable_now() {
if have systemctl; then
sudo systemctl enable --now "$1" || true
fi
}
sshd_reload() {
if have systemctl && systemctl is-enabled ssh >/dev/null 2>&1; then
sudo systemctl reload ssh || true
elif have systemctl && systemctl is-enabled sshd >/dev/null 2>&1; then
sudo systemctl reload sshd || true
fi
}
# ----------- baseline packages -----------
if [ "$BASELINE_PKGS" = "1" ] && [ -n "$pm" ]; then
pkgs_common="curl ca-certificates gnupg git jq unzip tar vim tmux htop net-tools"
case "$pm" in
apt) pkgs="$pkgs_common ufw openssh-client" ;;
dnf|yum) pkgs="$pkgs_common firewalld openssh-clients" ;;
zypper) pkgs="$pkgs_common firewalld openssh" ;;
apk) pkgs="$pkgs_common openssh-client" ;;
esac
pm_update_install $pkgs || true
fi
# add current ssh user to docker group if exists
if getent group docker >/dev/null 2>&1; then
sudo usermod -aG docker "$(id -un)" || true
# ----------- docker & compose v2 -----------
if [ "$INSTALL_DOCKER" = "1" ]; then
if ! have docker; then
curl -fsSL https://get.docker.com | sh
fi
# try to enable/start (handles distros with systemd)
if have systemctl; then
sudo systemctl enable --now docker || true
fi
# add current ssh user to docker group if exists
if getent group docker >/dev/null 2>&1; then
sudo usermod -aG docker "$(id -un)" || true
fi
# docker compose v2 (plugin) if missing
if ! docker compose version >/dev/null 2>&1; then
# Try package first (Debian/Ubuntu name)
if [ "$pm" = "apt" ]; then
sudo apt-get update -y
sudo apt-get install -y docker-compose-plugin || true
fi
# Fallback: install static plugin binary under ~/.docker/cli-plugins
if ! docker compose version >/dev/null 2>&1; then
mkdir -p ~/.docker/cli-plugins
arch="$(uname -m)"
case "$arch" in
x86_64|amd64) arch="x86_64" ;;
aarch64|arm64) arch="aarch64" ;;
esac
curl -fsSL -o ~/.docker/cli-plugins/docker-compose \
"https://github.com/docker/compose/releases/download/v2.29.7/docker-compose-$(uname -s)-$arch"
chmod +x ~/.docker/cli-plugins/docker-compose
fi
fi
fi
# ----------- SSH hardening (non-destructive: separate conf file) -----------
if [ "$SSH_HARDEN" = "1" ]; then
confd="/etc/ssh/sshd_config.d"
if [ -d "$confd" ] && [ -w "$confd" ]; then
sudo tee "$confd/10-bastion.conf" >/dev/null <<'EOF'
# Bastion hardening
PasswordAuthentication no
ChallengeResponseAuthentication no
KbdInteractiveAuthentication no
UsePAM yes
PermitEmptyPasswords no
PubkeyAuthentication yes
ClientAliveInterval 300
ClientAliveCountMax 2
LoginGraceTime 20
MaxAuthTries 3
MaxSessions 10
AllowAgentForwarding no
X11Forwarding no
EOF
sshd_reload
else
echo "Skipping SSH hardening: $confd not present or not writable" >&2
fi
# lock root password (no effect if already locked)
if have passwd; then
sudo passwd -l root || true
fi
fi
# ----------- firewall -----------
if [ "$FIREWALL" = "1" ]; then
if have ufw; then
# Keep it minimal: allow SSH and rate-limit
sudo ufw --force reset || true
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow OpenSSH || sudo ufw allow 22/tcp
sudo ufw limit OpenSSH || true
sudo ufw --force enable
elif have firewall-cmd; then
systemd_enable_now firewalld
sudo firewall-cmd --permanent --add-service=ssh || sudo firewall-cmd --permanent --add-port=22/tcp
sudo firewall-cmd --reload || true
else
echo "No supported firewall tool detected; skipping." >&2
fi
fi
# ----------- unattended / automatic updates -----------
if [ "$AUTO_UPDATES" = "1" ] && [ -n "$pm" ]; then
case "$pm" in
apt)
pm_update_install unattended-upgrades apt-listchanges || true
sudo dpkg-reconfigure -f noninteractive unattended-upgrades || true
sudo tee /etc/apt/apt.conf.d/20auto-upgrades >/dev/null <<'EOF'
APT::Periodic::Update-Package-Lists "1";
APT::Periodic::Unattended-Upgrade "1";
APT::Periodic::AutocleanInterval "7";
EOF
;;
dnf)
pm_update_install dnf-automatic || true
sudo sed -i 's/^apply_updates = .*/apply_updates = yes/' /etc/dnf/automatic.conf || true
systemd_enable_now dnf-automatic.timer
;;
yum)
pm_update_install yum-cron || true
sudo sed -i 's/apply_updates = no/apply_updates = yes/' /etc/yum/yum-cron.conf || true
systemd_enable_now yum-cron
;;
zypper)
pm_update_install pkgconf-pkg-config || true
# SUSE has automatic updates via transactional-update / yast2-online-update; skipping heavy config.
;;
apk)
# Alpine: no official unattended updater; consider periodic 'apk upgrade' via cron (skipped by default).
;;
esac
fi
# ----------- time sync -----------
if [ "$TIME_SYNC" = "1" ]; then
if have timedatectl; then
# Prefer systemd-timesyncd if available; else install/enable chrony
if [ -f /lib/systemd/system/systemd-timesyncd.service ] || [ -f /usr/lib/systemd/system/systemd-timesyncd.service ]; then
systemd_enable_now systemd-timesyncd
else
pm_update_install chrony || true
systemd_enable_now chronyd || systemd_enable_now chrony || true
fi
timedatectl set-ntp true || true
else
pm_update_install chrony || true
systemd_enable_now chronyd || systemd_enable_now chrony || true
fi
fi
# ----------- fail2ban (basic sshd jail) -----------
if [ "$FAIL2BAN" = "1" ]; then
pm_update_install fail2ban || true
if [ -d /etc/fail2ban ]; then
sudo tee /etc/fail2ban/jail.d/sshd.local >/dev/null <<'EOF'
[sshd]
enabled = true
port = ssh
logpath = %(sshd_log)s
maxretry = 4
bantime = 1h
findtime = 10m
EOF
systemd_enable_now fail2ban
fi
fi
# ----------- SSH banner / MOTD -----------
if [ "$BANNER" = "1" ]; then
if [ -w /etc/issue.net ] || sudo test -w /etc/issue.net; then
sudo tee /etc/issue.net >/dev/null <<'EOF'
NOTICE: Authorized use only. Activity may be monitored and reported.
EOF
# Ensure banner is enabled via our bastion conf
if [ -d /etc/ssh/sshd_config.d ]; then
if ! grep -q '^Banner ' /etc/ssh/sshd_config.d/10-bastion.conf 2>/dev/null; then
echo 'Banner /etc/issue.net' | sudo tee -a /etc/ssh/sshd_config.d/10-bastion.conf >/dev/null
sshd_reload
fi
fi
fi
fi
echo "Bootstrap complete. If you were added to the docker group, log out and back in to apply."
`
// Send script via stdin to avoid quoting/escaping issues
@@ -271,3 +519,38 @@ func wrapSSHError(err error, output string) error {
func sshEscape(s string) string {
return fmt.Sprintf("%q", s)
}
// makeDBHostKeyCallback returns a HostKeyCallback bound to a specific server row.
// TOFU semantics:
// - If s.SSHHostKey is empty: store the current key in DB and accept.
// - If s.SSHHostKey is set: require exact match, else error (possible MITM/reinstall).
func makeDBHostKeyCallback(db *gorm.DB, s *models.Server) ssh.HostKeyCallback {
return func(hostname string, remote net.Addr, key ssh.PublicKey) error {
algo := key.Type()
enc := base64.StdEncoding.EncodeToString(key.Marshal())
// First-time connect: persist key (TOFU).
if s.SSHHostKey == "" {
if err := db.Model(&models.Server{}).
Where("id = ? AND (ssh_host_key IS NULL or ssh_host_key = '')", s.ID).
Updates(map[string]any{
"ssh_host_key": enc,
"ssh_host_key_algo": algo,
}).Error; err != nil {
return fmt.Errorf("store new host key for %s (%s): %w", hostname, s.ID, err)
}
s.SSHHostKey = enc
s.SSHHostKeyAlgo = algo
return nil
}
if s.SSHHostKeyAlgo != algo || s.SSHHostKey != enc {
return fmt.Errorf(
"host key mismatch for %s (server_id=%s, stored=%s/%s, got=%s/%s) - POSSIBLE MITM or host reinstalled",
hostname, s.ID, s.SSHHostKeyAlgo, s.SSHHostKey, algo, enc,
)
}
return nil
}
}

View File

@@ -67,7 +67,7 @@ func NewJobs(gdb *gorm.DB, dbUrl string) (*Jobs, error) {
archer.WithSetTableName("jobs"), // <- ensure correct table
archer.WithSleepInterval(1*time.Second), // fast poll while debugging
archer.WithErrHandler(func(err error) { // bubble up worker SQL errors
log.Printf("[archer] ERROR: %v", err)
log.Error().Err(err).Msg("[archer] worker error")
}),
)
@@ -75,7 +75,7 @@ func NewJobs(gdb *gorm.DB, dbUrl string) (*Jobs, error) {
c.Register(
"bootstrap_bastion",
BastionBootstrapWorker(gdb),
BastionBootstrapWorker(gdb, jobs),
archer.WithInstances(instances),
archer.WithTimeout(time.Duration(timeoutSec)*time.Second),
)
@@ -94,6 +94,19 @@ func NewJobs(gdb *gorm.DB, dbUrl string) (*Jobs, error) {
archer.WithTimeout(5*time.Minute),
)
c.Register(
"db_backup_s3",
DbBackupWorker(gdb, jobs),
archer.WithInstances(1),
archer.WithTimeout(15*time.Minute),
)
c.Register(
"dns_reconcile",
DNSReconsileWorker(gdb, jobs),
archer.WithInstances(1),
archer.WithTimeout(2*time.Minute),
)
return jobs, nil
}

597
internal/bg/dns.go Normal file
View File

@@ -0,0 +1,597 @@
package bg
import (
"context"
"encoding/json"
"errors"
"fmt"
"strconv"
"strings"
"time"
"github.com/dyaksa/archer"
"github.com/dyaksa/archer/job"
"github.com/glueops/autoglue/internal/handlers/dto"
"github.com/glueops/autoglue/internal/models"
"github.com/glueops/autoglue/internal/utils"
"github.com/google/uuid"
"github.com/rs/zerolog/log"
"gorm.io/gorm"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/credentials"
r53 "github.com/aws/aws-sdk-go-v2/service/route53"
r53types "github.com/aws/aws-sdk-go-v2/service/route53/types"
)
/************* args & small DTOs *************/
type DNSReconcileArgs struct {
MaxDomains int `json:"max_domains,omitempty"`
MaxRecords int `json:"max_records,omitempty"`
IntervalS int `json:"interval_seconds,omitempty"`
}
// TXT marker content (compact)
type ownershipMarker struct {
Ver string `json:"v"` // "ag1"
Org string `json:"org"` // org UUID
Rec string `json:"rec"` // record UUID
Fp string `json:"fp"` // short fp (first 16 of sha256)
}
// ExternalDNS poison owner id MUST NOT match any real external-dns --txt-owner-id
const externalDNSPoisonOwner = "autoglue-lock"
// ExternalDNS poison content fake owner so real external-dns skips it.
const externalDNSPoisonValue = "heritage=external-dns,external-dns/owner=" + externalDNSPoisonOwner + ",external-dns/resource=manual/autoglue"
/************* entrypoint worker *************/
func DNSReconsileWorker(db *gorm.DB, jobs *Jobs) archer.WorkerFn {
return func(ctx context.Context, j job.Job) (any, error) {
args := DNSReconcileArgs{MaxDomains: 25, MaxRecords: 100, IntervalS: 30}
_ = j.ParseArguments(&args)
if args.MaxDomains <= 0 {
args.MaxDomains = 25
}
if args.MaxRecords <= 0 {
args.MaxRecords = 100
}
if args.IntervalS <= 0 {
args.IntervalS = 30
}
processedDomains, processedRecords, err := reconcileDNSOnce(ctx, db, args)
if err != nil {
log.Error().Err(err).Msg("[dns] reconcile tick failed")
} else {
log.Debug().
Int("domains", processedDomains).
Int("records", processedRecords).
Msg("[dns] reconcile tick ok")
}
next := time.Now().Add(time.Duration(args.IntervalS) * time.Second)
_, _ = jobs.Enqueue(ctx, uuid.NewString(), "dns_reconcile", args,
archer.WithScheduleTime(next),
archer.WithMaxRetries(1),
)
return map[string]any{
"domains_processed": processedDomains,
"records_processed": processedRecords,
}, nil
}
}
/************* core tick *************/
func reconcileDNSOnce(ctx context.Context, db *gorm.DB, args DNSReconcileArgs) (int, int, error) {
var domains []models.Domain
// 1) validate/backfill pending domains
if err := db.
Where("status = ?", "pending").
Order("created_at ASC").
Limit(args.MaxDomains).
Find(&domains).Error; err != nil {
return 0, 0, err
}
domainsProcessed := 0
for i := range domains {
if err := processDomain(ctx, db, &domains[i]); err != nil {
log.Error().Err(err).Str("domain", domains[i].DomainName).Msg("[dns] domain processing failed")
} else {
domainsProcessed++
}
}
// 2) apply pending record sets for ready domains
var readyDomains []models.Domain
if err := db.Where("status = ?", "ready").Find(&readyDomains).Error; err != nil {
return domainsProcessed, 0, err
}
recordsProcessed := 0
for i := range readyDomains {
n, err := processPendingRecordsForDomain(ctx, db, &readyDomains[i], args.MaxRecords)
if err != nil {
log.Error().Err(err).Str("domain", readyDomains[i].DomainName).Msg("[dns] record processing failed")
continue
}
recordsProcessed += n
}
return domainsProcessed, recordsProcessed, nil
}
/************* domain processing *************/
func processDomain(ctx context.Context, db *gorm.DB, d *models.Domain) error {
orgID := d.OrganizationID
// 1) Load credential (org-guarded)
var cred models.Credential
if err := db.Where("id = ? AND organization_id = ?", d.CredentialID, orgID).First(&cred).Error; err != nil {
return setDomainFailed(db, d, fmt.Errorf("credential not found: %w", err))
}
// 2) Decrypt → dto.AWSCredential
secret, err := utils.DecryptForOrg(orgID, cred.EncryptedData, cred.IV, cred.Tag, db)
if err != nil {
return setDomainFailed(db, d, fmt.Errorf("decrypt: %w", err))
}
var awsCred dto.AWSCredential
if err := jsonUnmarshalStrict([]byte(secret), &awsCred); err != nil {
return setDomainFailed(db, d, fmt.Errorf("secret decode: %w", err))
}
// 3) Client
r53c, _, err := newRoute53Client(ctx, awsCred)
if err != nil {
return setDomainFailed(db, d, err)
}
// 4) Backfill zone id if missing
zoneID := strings.TrimSpace(d.ZoneID)
if zoneID == "" {
zid, err := findHostedZoneID(ctx, r53c, d.DomainName)
if err != nil {
return setDomainFailed(db, d, fmt.Errorf("discover zone id: %w", err))
}
zoneID = zid
d.ZoneID = zoneID
}
// 5) Sanity: can fetch zone
if _, err := r53c.GetHostedZone(ctx, &r53.GetHostedZoneInput{Id: aws.String(zoneID)}); err != nil {
return setDomainFailed(db, d, fmt.Errorf("get hosted zone: %w", err))
}
// 6) Mark ready
d.Status = "ready"
d.LastError = ""
if err := db.Save(d).Error; err != nil {
return err
}
return nil
}
func setDomainFailed(db *gorm.DB, d *models.Domain, cause error) error {
d.Status = "failed"
d.LastError = truncateErr(cause.Error())
_ = db.Save(d).Error
return cause
}
/************* record processing *************/
func processPendingRecordsForDomain(ctx context.Context, db *gorm.DB, d *models.Domain, max int) (int, error) {
orgID := d.OrganizationID
// reload credential
var cred models.Credential
if err := db.Where("id = ? AND organization_id = ?", d.CredentialID, orgID).First(&cred).Error; err != nil {
return 0, err
}
secret, err := utils.DecryptForOrg(orgID, cred.EncryptedData, cred.IV, cred.Tag, db)
if err != nil {
return 0, err
}
var awsCred dto.AWSCredential
if err := jsonUnmarshalStrict([]byte(secret), &awsCred); err != nil {
return 0, err
}
r53c, _, err := newRoute53Client(ctx, awsCred)
if err != nil {
return 0, err
}
var records []models.RecordSet
if err := db.
Where("domain_id = ? AND status = ?", d.ID, "pending").
Order("created_at ASC").
Limit(max).
Find(&records).Error; err != nil {
return 0, err
}
applied := 0
for i := range records {
if err := applyRecord(ctx, db, r53c, d, &records[i]); err != nil {
log.Error().Err(err).Str("rr", records[i].Name).Msg("[dns] apply record failed")
_ = setRecordFailed(db, &records[i], err)
continue
}
applied++
}
return applied, nil
}
// core write + ownership + external-dns hardening
func applyRecord(ctx context.Context, db *gorm.DB, r53c *r53.Client, d *models.Domain, r *models.RecordSet) error {
zoneID := strings.TrimSpace(d.ZoneID)
if zoneID == "" {
return errors.New("domain has no zone_id")
}
rt := strings.ToUpper(r.Type)
// FQDN & marker
fq := recordFQDN(r.Name, d.DomainName) // ends with "."
mname := markerName(fq)
expected := buildMarkerValue(d.OrganizationID.String(), r.ID.String(), r.Fingerprint)
// ---- ExternalDNS preflight ----
extOwned, err := hasExternalDNSOwnership(ctx, r53c, zoneID, fq, rt)
if err != nil {
return fmt.Errorf("external_dns_lookup: %w", err)
}
if extOwned {
r.Owner = "external"
_ = db.Save(r).Error
return fmt.Errorf("ownership_conflict: external-dns claims %s; refusing to modify", strings.TrimSuffix(fq, "."))
}
// ---- Autoglue ownership preflight via _autoglue.<fqdn> TXT ----
markerVals, err := getMarkerTXTValues(ctx, r53c, zoneID, mname)
if err != nil {
return fmt.Errorf("marker lookup: %w", err)
}
hasForeignOwner := false
hasOurExact := false
for _, v := range markerVals {
mk, ok := parseMarkerValue(v)
if !ok {
continue
}
switch {
case mk.Org == d.OrganizationID.String() && mk.Rec == r.ID.String() && mk.Fp == shortFP(r.Fingerprint):
hasOurExact = true
case mk.Org != d.OrganizationID.String() || mk.Rec != r.ID.String():
hasForeignOwner = true
}
}
if hasForeignOwner {
r.Owner = "external"
_ = db.Save(r).Error
return fmt.Errorf("ownership_conflict: marker for %s is owned by another controller; refusing to modify", strings.TrimSuffix(fq, "."))
}
// Build RR change (UPSERT)
rrChange := r53types.Change{
Action: r53types.ChangeActionUpsert,
ResourceRecordSet: &r53types.ResourceRecordSet{
Name: aws.String(fq),
Type: r53types.RRType(rt),
},
}
// Decode user values
var userVals []string
if len(r.Values) > 0 {
if err := jsonUnmarshalStrict([]byte(r.Values), &userVals); err != nil {
return fmt.Errorf("values decode: %w", err)
}
}
// Quote TXT values as required by Route53
recs := make([]r53types.ResourceRecord, 0, len(userVals))
for _, v := range userVals {
v = strings.TrimSpace(v)
if rt == "TXT" && !(strings.HasPrefix(v, `"`) && strings.HasSuffix(v, `"`)) {
v = strconv.Quote(v)
}
recs = append(recs, r53types.ResourceRecord{Value: aws.String(v)})
}
rrChange.ResourceRecordSet.ResourceRecords = recs
if r.TTL != nil {
ttl := int64(*r.TTL)
rrChange.ResourceRecordSet.TTL = aws.Int64(ttl)
}
// Build marker TXT change (UPSERT)
markerChange := r53types.Change{
Action: r53types.ChangeActionUpsert,
ResourceRecordSet: &r53types.ResourceRecordSet{
Name: aws.String(mname),
Type: r53types.RRTypeTxt,
TTL: aws.Int64(300),
ResourceRecords: []r53types.ResourceRecord{
{Value: aws.String(strconv.Quote(expected))},
},
},
}
// Build external-dns poison TXT changes
poisonChanges := buildExternalDNSPoisonTXTChanges(fq, rt)
// Apply all in one batch (atomic-ish)
changes := []r53types.Change{rrChange, markerChange}
changes = append(changes, poisonChanges...)
_, err = r53c.ChangeResourceRecordSets(ctx, &r53.ChangeResourceRecordSetsInput{
HostedZoneId: aws.String(zoneID),
ChangeBatch: &r53types.ChangeBatch{Changes: changes},
})
if err != nil {
return err
}
// Success → mark ready & ownership
r.Status = "ready"
r.LastError = ""
r.Owner = "autoglue"
if err := db.Save(r).Error; err != nil {
return err
}
_ = hasOurExact // could be used to skip marker write in future
return nil
}
func setRecordFailed(db *gorm.DB, r *models.RecordSet, cause error) error {
msg := truncateErr(cause.Error())
r.Status = "failed"
r.LastError = msg
// classify ownership on conflict
if strings.HasPrefix(msg, "ownership_conflict") {
r.Owner = "external"
} else if r.Owner == "" || r.Owner == "unknown" {
r.Owner = "unknown"
}
_ = db.Save(r).Error
return cause
}
/************* AWS helpers *************/
func newRoute53Client(ctx context.Context, cred dto.AWSCredential) (*r53.Client, *aws.Config, error) {
// Route53 is global, but config still wants a region
region := strings.TrimSpace(cred.Region)
if region == "" {
region = "us-east-1"
}
cfg, err := config.LoadDefaultConfig(ctx,
config.WithRegion(region),
config.WithCredentialsProvider(credentials.NewStaticCredentialsProvider(
cred.AccessKeyID, cred.SecretAccessKey, "",
)),
)
if err != nil {
return nil, nil, err
}
return r53.NewFromConfig(cfg), &cfg, nil
}
func findHostedZoneID(ctx context.Context, c *r53.Client, domain string) (string, error) {
d := normalizeDomain(domain)
out, err := c.ListHostedZonesByName(ctx, &r53.ListHostedZonesByNameInput{
DNSName: aws.String(d),
})
if err != nil {
return "", err
}
for _, hz := range out.HostedZones {
if strings.TrimSuffix(aws.ToString(hz.Name), ".") == d {
return trimZoneID(aws.ToString(hz.Id)), nil
}
}
return "", fmt.Errorf("hosted zone not found for %q", d)
}
func trimZoneID(id string) string {
return strings.TrimPrefix(id, "/hostedzone/")
}
func normalizeDomain(s string) string {
s = strings.TrimSpace(strings.ToLower(s))
return strings.TrimSuffix(s, ".")
}
func recordFQDN(name, domain string) string {
name = strings.TrimSpace(name)
if name == "" || name == "@" {
return normalizeDomain(domain) + "."
}
if strings.HasSuffix(name, ".") {
return name
}
return fmt.Sprintf("%s.%s.", name, normalizeDomain(domain))
}
/************* TXT marker / external-dns helpers *************/
func markerName(fqdn string) string {
trimmed := strings.TrimSuffix(fqdn, ".")
return "_autoglue." + trimmed + "."
}
func shortFP(full string) string {
if len(full) > 16 {
return full[:16]
}
return full
}
func buildMarkerValue(orgID, recID, fp string) string {
return "v=ag1 org=" + orgID + " rec=" + recID + " fp=" + shortFP(fp)
}
func parseMarkerValue(s string) (ownershipMarker, bool) {
out := ownershipMarker{}
fields := strings.Fields(s)
if len(fields) < 4 {
return out, false
}
kv := map[string]string{}
for _, f := range fields {
parts := strings.SplitN(f, "=", 2)
if len(parts) == 2 {
kv[parts[0]] = parts[1]
}
}
if kv["v"] == "" || kv["org"] == "" || kv["rec"] == "" || kv["fp"] == "" {
return out, false
}
out.Ver, out.Org, out.Rec, out.Fp = kv["v"], kv["org"], kv["rec"], kv["fp"]
return out, true
}
func getMarkerTXTValues(ctx context.Context, c *r53.Client, zoneID, marker string) ([]string, error) {
return getTXTValues(ctx, c, zoneID, marker)
}
// generic TXT fetcher
func getTXTValues(ctx context.Context, c *r53.Client, zoneID, name string) ([]string, error) {
out, err := c.ListResourceRecordSets(ctx, &r53.ListResourceRecordSetsInput{
HostedZoneId: aws.String(zoneID),
StartRecordName: aws.String(name),
StartRecordType: r53types.RRTypeTxt,
MaxItems: aws.Int32(1),
})
if err != nil {
return nil, err
}
if len(out.ResourceRecordSets) == 0 {
return nil, nil
}
rrset := out.ResourceRecordSets[0]
if aws.ToString(rrset.Name) != name || rrset.Type != r53types.RRTypeTxt {
return nil, nil
}
vals := make([]string, 0, len(rrset.ResourceRecords))
for _, rr := range rrset.ResourceRecords {
vals = append(vals, aws.ToString(rr.Value))
}
return vals, nil
}
// detect external-dns-style ownership for this fqdn/type
func hasExternalDNSOwnership(ctx context.Context, c *r53.Client, zoneID, fqdn, rrType string) (bool, error) {
base := strings.TrimSuffix(fqdn, ".")
candidates := []string{
// with txtPrefix=extdns-, external-dns writes both:
// extdns-<fqdn> and extdns-<rrtype-lc>-<fqdn>
"extdns-" + base + ".",
"extdns-" + strings.ToLower(rrType) + "-" + base + ".",
}
for _, name := range candidates {
vals, err := getTXTValues(ctx, c, zoneID, name)
if err != nil {
return false, err
}
for _, raw := range vals {
v := strings.TrimSpace(raw)
// strip surrounding quotes if present
if len(v) >= 2 && v[0] == '"' && v[len(v)-1] == '"' {
if unq, err := strconv.Unquote(v); err == nil {
v = unq
}
}
meta := parseExternalDNSMeta(v)
if meta == nil {
continue
}
if meta["heritage"] == "external-dns" &&
meta["external-dns/owner"] != "" &&
meta["external-dns/owner"] != externalDNSPoisonOwner {
return true, nil
}
}
}
return false, nil
}
// parseExternalDNSMeta parses the comma-separated external-dns TXT format into a small map.
func parseExternalDNSMeta(v string) map[string]string {
parts := strings.Split(v, ",")
if len(parts) == 0 {
return nil
}
meta := make(map[string]string, len(parts))
for _, p := range parts {
p = strings.TrimSpace(p)
if p == "" {
continue
}
kv := strings.SplitN(p, "=", 2)
if len(kv) != 2 {
continue
}
meta[kv[0]] = kv[1]
}
if len(meta) == 0 {
return nil
}
return meta
}
// build poison TXT records so external-dns thinks some *other* owner manages this
func buildExternalDNSPoisonTXTChanges(fqdn, rrType string) []r53types.Change {
base := strings.TrimSuffix(fqdn, ".")
names := []string{
"extdns-" + base + ".",
"extdns-" + strings.ToLower(rrType) + "-" + base + ".",
}
val := strconv.Quote(externalDNSPoisonValue)
changes := make([]r53types.Change, 0, len(names))
for _, n := range names {
changes = append(changes, r53types.Change{
Action: r53types.ChangeActionUpsert,
ResourceRecordSet: &r53types.ResourceRecordSet{
Name: aws.String(n),
Type: r53types.RRTypeTxt,
TTL: aws.Int64(300),
ResourceRecords: []r53types.ResourceRecord{
{Value: aws.String(val)},
},
},
})
}
return changes
}
/************* misc utils *************/
func truncateErr(s string) string {
const max = 2000
if len(s) > max {
return s[:max]
}
return s
}
// Strict unmarshal that treats "null" -> zero value correctly.
func jsonUnmarshalStrict(b []byte, dst any) error {
if len(b) == 0 {
return errors.New("empty json")
}
return json.Unmarshal(b, dst)
}

View File

@@ -13,6 +13,7 @@ import (
type Config struct {
DbURL string
DbURLRO string
Port string
Host string
JWTIssuer string
@@ -24,10 +25,17 @@ type Config struct {
GithubClientID string
GithubClientSecret string
UIDev bool
Env string
Debug bool
Swagger bool
UIDev bool
Env string
Debug bool
Swagger bool
SwaggerHost string
DBStudioEnabled bool
DBStudioBind string
DBStudioPort string
DBStudioUser string
DBStudioPass string
}
var (
@@ -47,11 +55,18 @@ func Load() (Config, error) {
v.SetDefault("bind.address", "127.0.0.1")
v.SetDefault("bind.port", "8080")
v.SetDefault("database.url", "postgres://user:pass@localhost:5432/db?sslmode=disable")
v.SetDefault("database.url_ro", "")
v.SetDefault("db_studio.enabled", false)
v.SetDefault("db_studio.bind", "127.0.0.1")
v.SetDefault("db_studio.port", "0") // 0 = random
v.SetDefault("db_studio.user", "")
v.SetDefault("db_studio.pass", "")
v.SetDefault("ui.dev", false)
v.SetDefault("env", "development")
v.SetDefault("debug", false)
v.SetDefault("swagger", false)
v.SetDefault("swagger.host", "localhost:8080")
// Env setup and binding
v.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
@@ -61,6 +76,7 @@ func Load() (Config, error) {
"bind.address",
"bind.port",
"database.url",
"database.url_ro",
"jwt.issuer",
"jwt.audience",
"jwt.private.enc.key",
@@ -73,6 +89,12 @@ func Load() (Config, error) {
"env",
"debug",
"swagger",
"swagger.host",
"db_studio.enabled",
"db_studio.bind",
"db_studio.port",
"db_studio.user",
"db_studio.pass",
}
for _, k := range keys {
_ = v.BindEnv(k)
@@ -81,6 +103,7 @@ func Load() (Config, error) {
// Build config
cfg := Config{
DbURL: v.GetString("database.url"),
DbURLRO: v.GetString("database.url_ro"),
Port: v.GetString("bind.port"),
Host: v.GetString("bind.address"),
JWTIssuer: v.GetString("jwt.issuer"),
@@ -92,10 +115,17 @@ func Load() (Config, error) {
GithubClientID: v.GetString("github.client.id"),
GithubClientSecret: v.GetString("github.client.secret"),
UIDev: v.GetBool("ui.dev"),
Env: v.GetString("env"),
Debug: v.GetBool("debug"),
Swagger: v.GetBool("swagger"),
UIDev: v.GetBool("ui.dev"),
Env: v.GetString("env"),
Debug: v.GetBool("debug"),
Swagger: v.GetBool("swagger"),
SwaggerHost: v.GetString("swagger.host"),
DBStudioEnabled: v.GetBool("db_studio.enabled"),
DBStudioBind: v.GetString("db_studio.bind"),
DBStudioPort: v.GetString("db_studio.port"),
DBStudioUser: v.GetString("db_studio.user"),
DBStudioPass: v.GetString("db_studio.pass"),
}
// Validate

View File

@@ -273,6 +273,21 @@ func AuthCallback(db *gorm.DB) http.HandlerFunc {
return
}
secure := strings.HasPrefix(cfg.OAuthRedirectBase, "https://")
if xf := r.Header.Get("X-Forwarded-Proto"); xf != "" {
secure = strings.EqualFold(xf, "https")
}
http.SetCookie(w, &http.Cookie{
Name: "ag_jwt",
Value: "Bearer " + access,
Path: "/",
HttpOnly: true,
SameSite: http.SameSiteLaxMode,
Secure: secure,
MaxAge: int((time.Hour * 8).Seconds()),
})
// 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") {
@@ -356,6 +371,21 @@ func Refresh(db *gorm.DB) http.HandlerFunc {
return
}
secure := strings.HasPrefix(cfg.OAuthRedirectBase, "https://")
if xf := r.Header.Get("X-Forwarded-Proto"); xf != "" {
secure = strings.EqualFold(xf, "https")
}
http.SetCookie(w, &http.Cookie{
Name: "ag_jwt",
Value: "Bearer " + access,
Path: "/",
HttpOnly: true,
SameSite: http.SameSiteLaxMode,
Secure: secure,
MaxAge: int((time.Hour * 8).Seconds()),
})
utils.WriteJSON(w, 200, dto.TokenPair{
AccessToken: access,
RefreshToken: newPair.Plain,
@@ -377,6 +407,7 @@ func Refresh(db *gorm.DB) http.HandlerFunc {
// @Router /auth/logout [post]
func Logout(db *gorm.DB) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
cfg, _ := config.Load()
var req dto.LogoutRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
utils.WriteError(w, 400, "invalid_json", err.Error())
@@ -385,13 +416,27 @@ func Logout(db *gorm.DB) http.HandlerFunc {
rec, err := auth.ValidateRefreshToken(db, req.RefreshToken)
if err != nil {
w.WriteHeader(204) // already invalid/revoked
return
goto clearCookie
}
if err := auth.RevokeFamily(db, rec.FamilyID); err != nil {
utils.WriteError(w, 500, "revoke_failed", err.Error())
return
}
clearCookie:
http.SetCookie(w, &http.Cookie{
Name: "ag_jwt",
Value: "",
Path: "/",
HttpOnly: true,
MaxAge: -1,
Expires: time.Unix(0, 0),
SameSite: http.SameSiteLaxMode,
Secure: strings.HasPrefix(cfg.OAuthRedirectBase, "https"),
})
w.WriteHeader(204)
}
}

View File

@@ -0,0 +1,186 @@
package handlers
import (
"net/http"
"strings"
"time"
"github.com/glueops/autoglue/internal/api/httpmiddleware"
"github.com/glueops/autoglue/internal/common"
"github.com/glueops/autoglue/internal/handlers/dto"
"github.com/glueops/autoglue/internal/models"
"github.com/glueops/autoglue/internal/utils"
"gorm.io/gorm"
)
// ListClusters godoc
//
// @ID ListClusters
// @Summary List clusters (org scoped)
// @Description Returns clusters for the organization in X-Org-ID. Filter by `q` (name contains).
// @Tags Clusters
// @Produce json
// @Param X-Org-ID header string false "Organization UUID"
// @Param q query string false "Name contains (case-insensitive)"
// @Success 200 {array} dto.ClusterResponse
// @Failure 401 {string} string "Unauthorized"
// @Failure 403 {string} string "organization required"
// @Failure 500 {string} string "failed to list clusters"
// @Router /clusters [get]
// @Security BearerAuth
// @Security OrgKeyAuth
// @Security OrgSecretAuth
func ListClusters(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
}
q := db.Where("organization_id = ?", orgID)
if needle := strings.TrimSpace(r.URL.Query().Get("q")); needle != "" {
q = q.Where(`name ILIKE ?`, "%"+needle+"%")
}
var rows []models.Cluster
if err := q.
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
}
out := make([]dto.ClusterResponse, 0, len(rows))
for _, row := range rows {
out = append(out, clusterToDTO(row))
}
}
}
// 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).
// @Tags Clusters
// @Accept json
// @Produce json
// @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"
// @Failure 403 {string} string "organization required"
// @Failure 500 {string} string "create failed"
// @Router /clusters [post]
// @Security BearerAuth
// @Security OrgKeyAuth
// @Security OrgSecretAuth
func CreateCluster(db *gorm.DB) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNoContent)
}
}
// -- Helpers
func clusterToDTO(c models.Cluster) dto.ClusterResponse {
var bastion *dto.ServerResponse
if c.BastionServer != nil {
b := serverToDTO(*c.BastionServer)
bastion = &b
}
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,
}
}
func nodePoolToDTO(np models.NodePool) dto.NodePoolResponse {
labels := make([]dto.LabelResponse, 0, len(np.Labels))
for _, l := range np.Labels {
labels = append(labels, dto.LabelResponse{
Key: l.Key,
Value: l.Value,
})
}
annotations := make([]dto.AnnotationResponse, 0, len(np.Annotations))
for _, a := range np.Annotations {
annotations = append(annotations, dto.AnnotationResponse{
Key: a.Key,
Value: a.Value,
})
}
taints := make([]dto.TaintResponse, 0, len(np.Taints))
for _, t := range np.Taints {
taints = append(taints, dto.TaintResponse{
Key: t.Key,
Value: t.Value,
Effect: t.Effect,
})
}
servers := make([]dto.ServerResponse, 0, len(np.Servers))
for _, s := range np.Servers {
servers = append(servers, serverToDTO(s))
}
return dto.NodePoolResponse{
AuditFields: common.AuditFields{
ID: np.ID,
OrganizationID: np.OrganizationID,
CreatedAt: np.CreatedAt,
UpdatedAt: np.UpdatedAt,
},
Name: np.Name,
Role: dto.NodeRole(np.Role),
Labels: labels,
Annotations: annotations,
Taints: taints,
Servers: servers,
}
}
func serverToDTO(s models.Server) dto.ServerResponse {
return dto.ServerResponse{
ID: s.ID,
Hostname: s.Hostname,
PrivateIPAddress: s.PrivateIPAddress,
PublicIPAddress: s.PublicIPAddress,
Role: s.Role,
Status: s.Status,
SSHUser: s.SSHUser,
SshKeyID: s.SshKeyID,
CreatedAt: s.CreatedAt.UTC().Format(time.RFC3339),
UpdatedAt: s.UpdatedAt.UTC().Format(time.RFC3339),
}
}

View File

@@ -0,0 +1,561 @@
package handlers
import (
"bytes"
"context"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"net/http"
"sort"
"time"
"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/datatypes"
"gorm.io/gorm"
)
// 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
func ListCredentials(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
}
q := db.Where("organization_id = ?", orgID)
if v := r.URL.Query().Get("provider"); v != "" {
q = q.Where("provider = ?", v)
}
if v := r.URL.Query().Get("kind"); v != "" {
q = q.Where("kind = ?", v)
}
if v := r.URL.Query().Get("scope_kind"); v != "" {
q = q.Where("scope_kind = ?", v)
}
var rows []models.Credential
if err := q.Order("updated_at DESC").Find(&rows).Error; err != nil {
utils.WriteError(w, http.StatusInternalServerError, "db_error", err.Error())
return
}
out := make([]dto.CredentialOut, 0, len(rows))
for i := range rows {
out = append(out, credOut(&rows[i]))
}
utils.WriteJSON(w, http.StatusOK, out)
}
}
// 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
func GetCredential(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
}
idStr := chi.URLParam(r, "id")
id, err := uuid.Parse(idStr)
if err != nil {
utils.WriteError(w, http.StatusBadRequest, "bad_id", "invalid UUID")
return
}
var row models.Credential
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", "credential not found")
return
}
utils.WriteError(w, http.StatusInternalServerError, "db_error", err.Error())
return
}
utils.WriteJSON(w, http.StatusOK, credOut(&row))
}
}
// 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
func CreateCredential(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.CreateCredentialRequest
if err := json.NewDecoder(r.Body).Decode(&in); err != nil {
utils.WriteError(w, http.StatusBadRequest, "bad_json", err.Error())
return
}
if err := dto.Validate.Struct(in); err != nil {
utils.WriteError(w, http.StatusBadRequest, "validation_error", err.Error())
return
}
cred, err := SaveCredentialWithScope(
r.Context(), db, orgID,
in.Provider, in.Kind, in.SchemaVersion,
in.ScopeKind, in.ScopeVersion, json.RawMessage(in.Scope), json.RawMessage(in.Secret),
in.Name, in.AccountID, in.Region,
)
if err != nil {
utils.WriteError(w, http.StatusBadRequest, "save_failed", err.Error())
return
}
utils.WriteJSON(w, http.StatusCreated, credOut(cred))
}
}
// 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
func UpdateCredential(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.Credential
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", "credential not found")
return
}
utils.WriteError(w, http.StatusInternalServerError, "db_error", err.Error())
return
}
var in dto.UpdateCredentialRequest
if err := json.NewDecoder(r.Body).Decode(&in); err != nil {
utils.WriteError(w, http.StatusBadRequest, "bad_json", err.Error())
return
}
// Update metadata
if in.Name != nil {
row.Name = *in.Name
}
if in.AccountID != nil {
row.AccountID = *in.AccountID
}
if in.Region != nil {
row.Region = *in.Region
}
// Update scope (re-validate + fingerprint)
if in.ScopeKind != nil || in.Scope != nil || in.ScopeVersion != nil {
newKind := row.ScopeKind
if in.ScopeKind != nil {
newKind = *in.ScopeKind
}
newVersion := row.ScopeVersion
if in.ScopeVersion != nil {
newVersion = *in.ScopeVersion
}
if in.Scope == nil {
utils.WriteError(w, http.StatusBadRequest, "validation_error", "scope must be provided when changing scope kind/version")
return
}
prScopes := dto.ScopeRegistry[row.Provider]
kScopes := prScopes[newKind]
sdef := kScopes[newVersion]
dst := sdef.New()
if err := json.Unmarshal(*in.Scope, dst); err != nil {
utils.WriteError(w, http.StatusBadRequest, "invalid_scope_json", err.Error())
return
}
if err := sdef.Validate(dst); err != nil {
utils.WriteError(w, http.StatusBadRequest, "invalid_scope", err.Error())
return
}
canonScope, err := canonicalJSON(dst)
if err != nil {
utils.WriteError(w, http.StatusInternalServerError, "canon_error", err.Error())
return
}
row.Scope = canonScope
row.ScopeKind = newKind
row.ScopeVersion = newVersion
row.ScopeFingerprint = sha256Hex(canonScope)
}
// Rotate secret
if in.Secret != nil {
// validate against current Provider/Kind/SchemaVersion
def := dto.CredentialRegistry[row.Provider][row.Kind][row.SchemaVersion]
dst := def.New()
if err := json.Unmarshal(*in.Secret, dst); err != nil {
utils.WriteError(w, http.StatusBadRequest, "invalid_secret_json", err.Error())
return
}
if err := def.Validate(dst); err != nil {
utils.WriteError(w, http.StatusBadRequest, "invalid_secret", err.Error())
return
}
canonSecret, err := canonicalJSON(dst)
if err != nil {
utils.WriteError(w, http.StatusInternalServerError, "canon_error", err.Error())
return
}
cipher, iv, tag, err := utils.EncryptForOrg(orgID, canonSecret, db)
if err != nil {
utils.WriteError(w, http.StatusInternalServerError, "encrypt_error", err.Error())
return
}
row.EncryptedData = cipher
row.IV = iv
row.Tag = tag
}
if err := db.Save(&row).Error; err != nil {
utils.WriteError(w, http.StatusInternalServerError, "db_error", err.Error())
return
}
utils.WriteJSON(w, http.StatusOK, credOut(&row))
}
}
// 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
func DeleteCredential(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
}
res := db.Where("organization_id = ? AND id = ?", orgID, id).Delete(&models.Credential{})
if res.Error != nil {
utils.WriteError(w, http.StatusInternalServerError, "db_error", res.Error.Error())
return
}
if res.RowsAffected == 0 {
utils.WriteError(w, http.StatusNotFound, "not_found", "credential not found")
return
}
w.WriteHeader(http.StatusNoContent)
}
}
// 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
func RevealCredential(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.Credential
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", "credential not found")
return
}
utils.WriteError(w, http.StatusInternalServerError, "db_error", err.Error())
return
}
plain, err := utils.DecryptForOrg(orgID, row.EncryptedData, row.IV, row.Tag, db)
if err != nil {
utils.WriteError(w, http.StatusInternalServerError, "decrypt_error", err.Error())
return
}
utils.WriteJSON(w, http.StatusOK, plain)
}
}
// -- Helpers
func canonicalJSON(v any) ([]byte, error) {
b, err := json.Marshal(v)
if err != nil {
return nil, err
}
var m any
if err := json.Unmarshal(b, &m); err != nil {
return nil, err
}
return marshalSorted(m)
}
func marshalSorted(v any) ([]byte, error) {
switch vv := v.(type) {
case map[string]any:
keys := make([]string, 0, len(vv))
for k := range vv {
keys = append(keys, k)
}
sort.Strings(keys)
buf := bytes.NewBufferString("{")
for i, k := range keys {
if i > 0 {
buf.WriteByte(',')
}
kb, _ := json.Marshal(k)
buf.Write(kb)
buf.WriteByte(':')
b, err := marshalSorted(vv[k])
if err != nil {
return nil, err
}
buf.Write(b)
}
buf.WriteByte('}')
return buf.Bytes(), nil
case []any:
buf := bytes.NewBufferString("[")
for i, e := range vv {
if i > 0 {
buf.WriteByte(',')
}
b, err := marshalSorted(e)
if err != nil {
return nil, err
}
buf.Write(b)
}
buf.WriteByte(']')
return buf.Bytes(), nil
default:
return json.Marshal(v)
}
}
func sha256Hex(b []byte) string {
sum := sha256.Sum256(b)
return hex.EncodeToString(sum[:])
}
// SaveCredentialWithScope validates secret+scope, encrypts, fingerprints, and stores.
func SaveCredentialWithScope(
ctx context.Context,
db *gorm.DB,
orgID uuid.UUID,
provider, kind string,
schemaVersion int,
scopeKind string,
scopeVersion int,
rawScope json.RawMessage,
rawSecret json.RawMessage,
name, accountID, region string,
) (*models.Credential, error) {
// 1) secret shape
pv, ok := dto.CredentialRegistry[provider]
if !ok {
return nil, fmt.Errorf("unknown provider %q", provider)
}
kv, ok := pv[kind]
if !ok {
return nil, fmt.Errorf("unknown kind %q for provider %q", kind, provider)
}
def, ok := kv[schemaVersion]
if !ok {
return nil, fmt.Errorf("unsupported schema version %d for %s/%s", schemaVersion, provider, kind)
}
secretDst := def.New()
if err := json.Unmarshal(rawSecret, secretDst); err != nil {
return nil, fmt.Errorf("payload is not valid JSON for %s/%s: %w", provider, kind, err)
}
if err := def.Validate(secretDst); err != nil {
return nil, fmt.Errorf("invalid %s/%s: %w", provider, kind, err)
}
// 2) scope shape
prScopes, ok := dto.ScopeRegistry[provider]
if !ok {
return nil, fmt.Errorf("no scopes registered for provider %q", provider)
}
kScopes, ok := prScopes[scopeKind]
if !ok {
return nil, fmt.Errorf("invalid scope_kind %q for provider %q", scopeKind, provider)
}
sdef, ok := kScopes[scopeVersion]
if !ok {
return nil, fmt.Errorf("unsupported scope version %d for %s/%s", scopeVersion, provider, scopeKind)
}
scopeDst := sdef.New()
if err := json.Unmarshal(rawScope, scopeDst); err != nil {
return nil, fmt.Errorf("invalid scope JSON: %w", err)
}
if err := sdef.Validate(scopeDst); err != nil {
return nil, fmt.Errorf("invalid scope: %w", err)
}
// 3) canonicalize scope (also what we persist in plaintext)
canonScope, err := canonicalJSON(scopeDst)
if err != nil {
return nil, err
}
fp := sha256Hex(canonScope) // or HMAC if you have a server-side key
// 4) canonicalize + encrypt secret
canonSecret, err := canonicalJSON(secretDst)
if err != nil {
return nil, err
}
cipher, iv, tag, err := utils.EncryptForOrg(orgID, canonSecret, db)
if err != nil {
return nil, fmt.Errorf("encrypt: %w", err)
}
cred := &models.Credential{
OrganizationID: orgID,
Provider: provider,
Kind: kind,
SchemaVersion: schemaVersion,
Name: name,
ScopeKind: scopeKind,
Scope: datatypes.JSON(canonScope),
ScopeVersion: scopeVersion,
AccountID: accountID,
Region: region,
ScopeFingerprint: fp,
EncryptedData: cipher,
IV: iv,
Tag: tag,
}
if err := db.WithContext(ctx).Create(cred).Error; err != nil {
return nil, err
}
return cred, nil
}
// credOut converts model → response DTO
func credOut(c *models.Credential) dto.CredentialOut {
return dto.CredentialOut{
ID: c.ID.String(),
Provider: c.Provider,
Kind: c.Kind,
SchemaVersion: c.SchemaVersion,
Name: c.Name,
ScopeKind: c.ScopeKind,
ScopeVersion: c.ScopeVersion,
Scope: dto.RawJSON(c.Scope),
AccountID: c.AccountID,
Region: c.Region,
CreatedAt: c.CreatedAt.UTC().Format(time.RFC3339),
UpdatedAt: c.UpdatedAt.UTC().Format(time.RFC3339),
}
}

802
internal/handlers/dns.go Normal file
View File

@@ -0,0 +1,802 @@
package handlers
import (
"bytes"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"net/http"
"strings"
"time"
"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"
"github.com/rs/zerolog/log"
"gorm.io/datatypes"
"gorm.io/gorm"
)
// ---------- Helpers ----------
func normLowerNoDot(s string) string {
s = strings.TrimSpace(strings.ToLower(s))
return strings.TrimSuffix(s, ".")
}
func fqdn(domain string, rel string) string {
d := normLowerNoDot(domain)
r := normLowerNoDot(rel)
if r == "" || r == "@" {
return d
}
return r + "." + d
}
func canonicalJSONAny(v any) ([]byte, error) {
b, err := json.Marshal(v)
if err != nil {
return nil, err
}
var anyv any
if err := json.Unmarshal(b, &anyv); err != nil {
return nil, err
}
return marshalSortedDNS(anyv)
}
func marshalSortedDNS(v any) ([]byte, error) {
switch vv := v.(type) {
case map[string]any:
keys := make([]string, 0, len(vv))
for k := range vv {
keys = append(keys, k)
}
sortStrings(keys)
var buf bytes.Buffer
buf.WriteByte('{')
for i, k := range keys {
if i > 0 {
buf.WriteByte(',')
}
kb, _ := json.Marshal(k)
buf.Write(kb)
buf.WriteByte(':')
b, err := marshalSortedDNS(vv[k])
if err != nil {
return nil, err
}
buf.Write(b)
}
buf.WriteByte('}')
return buf.Bytes(), nil
case []any:
var buf bytes.Buffer
buf.WriteByte('[')
for i, e := range vv {
if i > 0 {
buf.WriteByte(',')
}
b, err := marshalSortedDNS(e)
if err != nil {
return nil, err
}
buf.Write(b)
}
buf.WriteByte(']')
return buf.Bytes(), nil
default:
return json.Marshal(v)
}
}
func sortStrings(a []string) {
for i := 0; i < len(a); i++ {
for j := i + 1; j < len(a); j++ {
if a[j] < a[i] {
a[i], a[j] = a[j], a[i]
}
}
}
}
func sha256HexBytes(b []byte) string {
sum := sha256.Sum256(b)
return hex.EncodeToString(sum[:])
}
/* Fingerprint (provider-agnostic) */
type desiredRecord struct {
ZoneID string `json:"zone_id"`
FQDN string `json:"fqdn"`
Type string `json:"type"`
TTL *int `json:"ttl,omitempty"`
Values []string `json:"values,omitempty"`
}
func computeFingerprint(zoneID, fqdn, typ string, ttl *int, values datatypes.JSON) (string, error) {
var vals []string
if len(values) > 0 && string(values) != "null" {
if err := json.Unmarshal(values, &vals); err != nil {
return "", err
}
sortStrings(vals)
}
payload := &desiredRecord{
ZoneID: zoneID, FQDN: fqdn, Type: strings.ToUpper(typ), TTL: ttl, Values: vals,
}
can, err := canonicalJSONAny(payload)
if err != nil {
return "", err
}
return sha256HexBytes(can), nil
}
func mustSameOrgDomainWithCredential(db *gorm.DB, orgID uuid.UUID, credID uuid.UUID) error {
var cred models.Credential
if err := db.Where("id = ? AND organization_id = ?", credID, orgID).First(&cred).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return fmt.Errorf("credential not found or belongs to different org")
}
return err
}
if cred.Provider != "aws" || cred.ScopeKind != "service" {
return fmt.Errorf("credential must be AWS Route 53 service scoped")
}
var scope map[string]any
if err := json.Unmarshal(cred.Scope, &scope); err != nil {
return fmt.Errorf("credential scope invalid json: %w", err)
}
if strings.ToLower(fmt.Sprint(scope["service"])) != "route53" {
return fmt.Errorf("credential scope.service must be route53")
}
return nil
}
// ---------- Domain Handlers ----------
// ListDomains godoc
//
// @ID ListDomains
// @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)"
// @Param status query string false "pending|provisioning|ready|failed"
// @Param q query string false "Domain contains (case-insensitive)"
// @Success 200 {array} dto.DomainResponse
// @Failure 401 {string} string "Unauthorized"
// @Failure 403 {string} string "organization required"
// @Failure 500 {string} string "db error"
// @Router /dns/domains [get]
// @Security BearerAuth
// @Security OrgKeyAuth
// @Security OrgSecretAuth
func ListDomains(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
}
q := db.Model(&models.Domain{}).Where("organization_id = ?", orgID)
if v := strings.TrimSpace(strings.ToLower(r.URL.Query().Get("domain_name"))); v != "" {
q = q.Where("LOWER(domain_name) = ?", v)
}
if v := strings.TrimSpace(strings.ToLower(r.URL.Query().Get("status"))); v != "" {
q = q.Where("status = ?", v)
}
if needle := strings.TrimSpace(strings.ToLower(r.URL.Query().Get("q"))); needle != "" {
q = q.Where("LOWER(domain_name) LIKE ?", "%"+needle+"%")
}
var rows []models.Domain
if err := q.Order("created_at DESC").Find(&rows).Error; err != nil {
utils.WriteError(w, http.StatusInternalServerError, "db_error", "db error")
return
}
out := make([]dto.DomainResponse, 0, len(rows))
for i := range rows {
out = append(out, domainOut(&rows[i]))
}
utils.WriteJSON(w, http.StatusOK, out)
}
}
// 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
func GetDomain(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.Domain
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", "domain not found")
return
}
utils.WriteError(w, http.StatusInternalServerError, "db_error", "db error")
return
}
utils.WriteJSON(w, http.StatusOK, domainOut(&row))
}
}
// CreateDomain godoc
//
// @ID CreateDomain
// @Summary Create a domain (org scoped)
// @Description Creates a domain bound to a Route 53 scoped credential. Archer will backfill ZoneID if omitted.
// @Tags DNS
// @Accept json
// @Produce json
// @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"
// @Failure 401 {string} string "Unauthorized"
// @Failure 403 {string} string "organization required"
// @Failure 500 {string} string "db error"
// @Router /dns/domains [post]
// @Security BearerAuth
// @Security OrgKeyAuth
// @Security OrgSecretAuth
func CreateDomain(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.CreateDomainRequest
if err := json.NewDecoder(r.Body).Decode(&in); err != nil {
utils.WriteError(w, http.StatusBadRequest, "bad_json", err.Error())
return
}
if err := dto.DNSValidate(in); err != nil {
utils.WriteError(w, http.StatusBadRequest, "validation_error", err.Error())
return
}
credID, _ := uuid.Parse(in.CredentialID)
if err := mustSameOrgDomainWithCredential(db, orgID, credID); err != nil {
utils.WriteError(w, http.StatusBadRequest, "invalid_credential", err.Error())
return
}
row := &models.Domain{
OrganizationID: orgID,
DomainName: normLowerNoDot(in.DomainName),
ZoneID: strings.TrimSpace(in.ZoneID),
Status: "pending",
LastError: "",
CredentialID: credID,
}
if err := db.Create(row).Error; err != nil {
utils.WriteError(w, http.StatusInternalServerError, "db_error", err.Error())
return
}
utils.WriteJSON(w, http.StatusCreated, domainOut(row))
}
}
// 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
func UpdateDomain(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.Domain
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", "domain not found")
return
}
utils.WriteError(w, http.StatusInternalServerError, "db_error", err.Error())
return
}
var in dto.UpdateDomainRequest
if err := json.NewDecoder(r.Body).Decode(&in); err != nil {
utils.WriteError(w, http.StatusBadRequest, "bad_json", err.Error())
return
}
if err := dto.DNSValidate(in); err != nil {
utils.WriteError(w, http.StatusBadRequest, "validation_error", err.Error())
return
}
if in.DomainName != nil {
row.DomainName = normLowerNoDot(*in.DomainName)
}
if in.CredentialID != nil {
credID, _ := uuid.Parse(*in.CredentialID)
if err := mustSameOrgDomainWithCredential(db, orgID, credID); err != nil {
utils.WriteError(w, http.StatusBadRequest, "invalid_credential", err.Error())
return
}
row.CredentialID = credID
row.Status = "pending"
row.LastError = ""
}
if in.ZoneID != nil {
row.ZoneID = strings.TrimSpace(*in.ZoneID)
}
if in.Status != nil {
row.Status = *in.Status
if row.Status == "pending" {
row.LastError = ""
}
}
if err := db.Save(&row).Error; err != nil {
utils.WriteError(w, http.StatusInternalServerError, "db_error", err.Error())
return
}
utils.WriteJSON(w, http.StatusOK, domainOut(&row))
}
}
// 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
func DeleteDomain(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
}
res := db.Where("organization_id = ? AND id = ?", orgID, id).Delete(&models.Domain{})
if res.Error != nil {
utils.WriteError(w, http.StatusInternalServerError, "db_error", res.Error.Error())
return
}
if res.RowsAffected == 0 {
utils.WriteError(w, http.StatusNotFound, "not_found", "domain not found")
return
}
w.WriteHeader(http.StatusNoContent)
}
}
// ---------- Record Set Handlers ----------
// ListRecordSets godoc
//
// @ID ListRecordSets
// @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"
// @Success 200 {array} dto.RecordSetResponse
// @Failure 403 {string} string "organization required"
// @Failure 404 {string} string "domain not found"
// @Router /dns/domains/{domain_id}/records [get]
// @Security BearerAuth
// @Security OrgKeyAuth
// @Security OrgSecretAuth
func ListRecordSets(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
}
did, err := uuid.Parse(chi.URLParam(r, "domain_id"))
if err != nil {
log.Info().Msg(err.Error())
utils.WriteError(w, http.StatusBadRequest, "bad_id", "invalid domain UUID:")
return
}
var domain models.Domain
if err := db.Where("organization_id = ? AND id = ?", orgID, did).First(&domain).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
utils.WriteError(w, http.StatusNotFound, "not_found", "domain not found")
return
}
utils.WriteError(w, http.StatusInternalServerError, "db_error", "db error")
return
}
q := db.Model(&models.RecordSet{}).Where("domain_id = ?", did)
if v := strings.TrimSpace(strings.ToLower(r.URL.Query().Get("name"))); v != "" {
dn := strings.ToLower(domain.DomainName)
rel := v
// normalize apex or FQDN into relative
if v == dn || v == dn+"." {
rel = ""
} else {
rel = strings.TrimSuffix(v, "."+dn)
rel = normLowerNoDot(rel)
}
q = q.Where("LOWER(name) = ?", rel)
}
if v := strings.TrimSpace(strings.ToUpper(r.URL.Query().Get("type"))); v != "" {
q = q.Where("type = ?", v)
}
if v := strings.TrimSpace(strings.ToLower(r.URL.Query().Get("status"))); v != "" {
q = q.Where("status = ?", v)
}
var rows []models.RecordSet
if err := q.Order("created_at DESC").Find(&rows).Error; err != nil {
utils.WriteError(w, http.StatusInternalServerError, "db_error", "db error")
return
}
out := make([]dto.RecordSetResponse, 0, len(rows))
for i := range rows {
out = append(out, recordOut(&rows[i]))
}
utils.WriteJSON(w, http.StatusOK, out)
}
}
// 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
func CreateRecordSet(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
}
did, err := uuid.Parse(chi.URLParam(r, "domain_id"))
if err != nil {
utils.WriteError(w, http.StatusBadRequest, "bad_id", "invalid domain UUID")
return
}
var domain models.Domain
if err := db.Where("organization_id = ? AND id = ?", orgID, did).First(&domain).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
utils.WriteError(w, http.StatusNotFound, "not_found", "domain not found")
return
}
utils.WriteError(w, http.StatusInternalServerError, "db_error", err.Error())
return
}
var in dto.CreateRecordSetRequest
if err := json.NewDecoder(r.Body).Decode(&in); err != nil {
utils.WriteError(w, http.StatusBadRequest, "bad_json", err.Error())
return
}
if err := dto.DNSValidate(in); err != nil {
utils.WriteError(w, http.StatusBadRequest, "validation_error", err.Error())
return
}
t := strings.ToUpper(in.Type)
if t == "CNAME" && len(in.Values) != 1 {
utils.WriteError(w, http.StatusBadRequest, "validation_error", "CNAME requires exactly one value")
return
}
rel := normLowerNoDot(in.Name)
fq := fqdn(domain.DomainName, rel)
// Pre-flight: block duplicate tuple and protect from non-autoglue rows
var existing models.RecordSet
if err := db.Where("domain_id = ? AND LOWER(name) = ? AND type = ?",
domain.ID, strings.ToLower(rel), t).First(&existing).Error; err == nil {
if existing.Owner != "" && existing.Owner != "autoglue" {
utils.WriteError(w, http.StatusConflict, "ownership_conflict",
"record with the same (name,type) exists but is not owned by autoglue")
return
}
utils.WriteError(w, http.StatusConflict, "already_exists",
"a record with the same (name,type) already exists; use PATCH to modify")
return
} else if !errors.Is(err, gorm.ErrRecordNotFound) {
utils.WriteError(w, http.StatusInternalServerError, "db_error", err.Error())
return
}
valuesJSON, _ := json.Marshal(in.Values)
fp, err := computeFingerprint(domain.ZoneID, fq, t, in.TTL, datatypes.JSON(valuesJSON))
if err != nil {
utils.WriteError(w, http.StatusInternalServerError, "fingerprint_error", err.Error())
return
}
row := &models.RecordSet{
DomainID: domain.ID,
Name: rel,
Type: t,
TTL: in.TTL,
Values: datatypes.JSON(valuesJSON),
Fingerprint: fp,
Status: "pending",
LastError: "",
Owner: "autoglue",
}
if err := db.Create(row).Error; err != nil {
utils.WriteError(w, http.StatusInternalServerError, "db_error", err.Error())
return
}
utils.WriteJSON(w, http.StatusCreated, recordOut(row))
}
}
// 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
func UpdateRecordSet(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.RecordSet
if err := db.
Joins("Domain").
Where(`record_sets.id = ? AND "Domain"."organization_id" = ?`, id, orgID).
First(&row).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
utils.WriteError(w, http.StatusNotFound, "not_found", "record set not found")
return
}
utils.WriteError(w, http.StatusInternalServerError, "db_error", err.Error())
return
}
var domain models.Domain
if err := db.Where("id = ?", row.DomainID).First(&domain).Error; err != nil {
utils.WriteError(w, http.StatusInternalServerError, "db_error", err.Error())
return
}
var in dto.UpdateRecordSetRequest
if err := json.NewDecoder(r.Body).Decode(&in); err != nil {
utils.WriteError(w, http.StatusBadRequest, "bad_json", err.Error())
return
}
if err := dto.DNSValidate(in); err != nil {
utils.WriteError(w, http.StatusBadRequest, "validation_error", err.Error())
return
}
if row.Owner != "" && row.Owner != "autoglue" {
utils.WriteError(w, http.StatusConflict, "ownership_conflict",
"record is not owned by autoglue; refuse to modify")
return
}
// Mutations
if in.Name != nil {
row.Name = normLowerNoDot(*in.Name)
}
if in.Type != nil {
row.Type = strings.ToUpper(*in.Type)
}
if in.TTL != nil {
row.TTL = in.TTL
}
if in.Values != nil {
t := row.Type
if in.Type != nil {
t = strings.ToUpper(*in.Type)
}
if t == "CNAME" && len(*in.Values) != 1 {
utils.WriteError(w, http.StatusBadRequest, "validation_error", "CNAME requires exactly one value")
return
}
b, _ := json.Marshal(*in.Values)
row.Values = datatypes.JSON(b)
}
if in.Status != nil {
row.Status = *in.Status
} else {
row.Status = "pending"
row.LastError = ""
}
fq := fqdn(domain.DomainName, row.Name)
fp, err := computeFingerprint(domain.ZoneID, fq, row.Type, row.TTL, row.Values)
if err != nil {
utils.WriteError(w, http.StatusInternalServerError, "fingerprint_error", err.Error())
return
}
row.Fingerprint = fp
if err := db.Save(&row).Error; err != nil {
utils.WriteError(w, http.StatusInternalServerError, "db_error", err.Error())
return
}
utils.WriteJSON(w, http.StatusOK, recordOut(&row))
}
}
// 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
func DeleteRecordSet(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
}
sub := db.Model(&models.RecordSet{}).
Select("record_sets.id").
Joins("JOIN domains ON domains.id = record_sets.domain_id").
Where("record_sets.id = ? AND domains.organization_id = ?", id, orgID)
res := db.Where("id IN (?)", sub).Delete(&models.RecordSet{})
if res.Error != nil {
utils.WriteError(w, http.StatusInternalServerError, "db_error", res.Error.Error())
return
}
if res.RowsAffected == 0 {
utils.WriteError(w, http.StatusNotFound, "not_found", "record set not found")
return
}
w.WriteHeader(http.StatusNoContent)
}
}
// ---------- Out mappers ----------
func domainOut(m *models.Domain) dto.DomainResponse {
return dto.DomainResponse{
ID: m.ID.String(),
OrganizationID: m.OrganizationID.String(),
DomainName: m.DomainName,
ZoneID: m.ZoneID,
Status: m.Status,
LastError: m.LastError,
CredentialID: m.CredentialID.String(),
CreatedAt: m.CreatedAt.UTC().Format(time.RFC3339),
UpdatedAt: m.UpdatedAt.UTC().Format(time.RFC3339),
}
}
func recordOut(r *models.RecordSet) dto.RecordSetResponse {
vals := r.Values
if len(vals) == 0 {
vals = datatypes.JSON("[]")
}
return dto.RecordSetResponse{
ID: r.ID.String(),
DomainID: r.DomainID.String(),
Name: r.Name,
Type: r.Type,
TTL: r.TTL,
Values: []byte(vals),
Fingerprint: r.Fingerprint,
Status: r.Status,
LastError: r.LastError,
Owner: r.Owner,
CreatedAt: r.CreatedAt.UTC().Format(time.RFC3339),
UpdatedAt: r.UpdatedAt.UTC().Format(time.RFC3339),
}
}

View File

@@ -0,0 +1,34 @@
package dto
import (
"time"
"github.com/google/uuid"
)
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"`
}
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"`
}

View File

@@ -0,0 +1,138 @@
package dto
import (
"encoding/json"
"github.com/go-playground/validator/v10"
)
// RawJSON is a swagger-friendly wrapper for json.RawMessage.
type RawJSON = json.RawMessage
var Validate = validator.New()
func init() {
_ = Validate.RegisterValidation("awsarn", func(fl validator.FieldLevel) bool {
v := fl.Field().String()
return len(v) > 10 && len(v) < 2048 && len(v) >= 4 && v[:4] == "arn:"
})
}
/*** Shapes for secrets ***/
type AWSCredential struct {
AccessKeyID string `json:"access_key_id" validate:"required,alphanum,len=20"`
SecretAccessKey string `json:"secret_access_key" validate:"required"`
Region string `json:"region" validate:"omitempty"`
}
type BasicAuth struct {
Username string `json:"username" validate:"required"`
Password string `json:"password" validate:"required"`
}
type APIToken struct {
Token string `json:"token" validate:"required"`
}
type OAuth2Credential struct {
ClientID string `json:"client_id" validate:"required"`
ClientSecret string `json:"client_secret" validate:"required"`
RefreshToken string `json:"refresh_token" validate:"required"`
}
/*** Shapes for scopes ***/
type AWSProviderScope struct{}
type AWSServiceScope struct {
Service string `json:"service" validate:"required,oneof=route53 s3 ec2 iam rds dynamodb"`
}
type AWSResourceScope struct {
ARN string `json:"arn" validate:"required,awsarn"`
}
/*** Registries ***/
type ProviderDef struct {
New func() any
Validate func(any) error
}
type ScopeDef struct {
New func() any
Validate func(any) error
Specificity int // 0=provider, 1=service, 2=resource
}
// Secret shapes per provider/kind/version
var CredentialRegistry = map[string]map[string]map[int]ProviderDef{
"aws": {
"aws_access_key": {
1: {New: func() any { return &AWSCredential{} }, Validate: func(x any) error { return Validate.Struct(x) }},
},
},
"cloudflare": {"api_token": {1: {New: func() any { return &APIToken{} }, Validate: func(x any) error { return Validate.Struct(x) }}}},
"hetzner": {"api_token": {1: {New: func() any { return &APIToken{} }, Validate: func(x any) error { return Validate.Struct(x) }}}},
"digitalocean": {"api_token": {1: {New: func() any { return &APIToken{} }, Validate: func(x any) error { return Validate.Struct(x) }}}},
"generic": {
"basic_auth": {1: {New: func() any { return &BasicAuth{} }, Validate: func(x any) error { return Validate.Struct(x) }}},
"oauth2": {1: {New: func() any { return &OAuth2Credential{} }, Validate: func(x any) error { return Validate.Struct(x) }}},
},
}
// Scope shapes per provider/scopeKind/version
var ScopeRegistry = map[string]map[string]map[int]ScopeDef{
"aws": {
"provider": {1: {New: func() any { return &AWSProviderScope{} }, Validate: func(any) error { return nil }, Specificity: 0}},
"service": {1: {New: func() any { return &AWSServiceScope{} }, Validate: func(x any) error { return Validate.Struct(x) }, Specificity: 1}},
"resource": {1: {New: func() any { return &AWSResourceScope{} }, Validate: func(x any) error { return Validate.Struct(x) }, Specificity: 2}},
},
}
/*** API DTOs used by swagger ***/
// CreateCredentialRequest represents the POST /credentials payload
type CreateCredentialRequest struct {
Provider string `json:"provider" validate:"required,oneof=aws cloudflare hetzner digitalocean generic"`
Kind string `json:"kind" validate:"required"` // aws_access_key, api_token, basic_auth, oauth2
SchemaVersion int `json:"schema_version" validate:"required,gte=1"` // secret schema version
Name string `json:"name" validate:"omitempty,max=100"` // human label
ScopeKind string `json:"scope_kind" validate:"required,oneof=provider service resource"`
ScopeVersion int `json:"scope_version" validate:"required,gte=1"` // scope schema version
Scope RawJSON `json:"scope" validate:"required" swaggertype:"object"` // {"service":"route53"} or {"arn":"..."}
AccountID string `json:"account_id,omitempty" validate:"omitempty,max=32"`
Region string `json:"region,omitempty" validate:"omitempty,max=32"`
Secret RawJSON `json:"secret" validate:"required" swaggertype:"object"` // encrypted later
}
// UpdateCredentialRequest represents PATCH /credentials/{id}
type UpdateCredentialRequest struct {
Name *string `json:"name,omitempty"`
AccountID *string `json:"account_id,omitempty"`
Region *string `json:"region,omitempty"`
ScopeKind *string `json:"scope_kind,omitempty"`
ScopeVersion *int `json:"scope_version,omitempty"`
Scope *RawJSON `json:"scope,omitempty" swaggertype:"object"`
Secret *RawJSON `json:"secret,omitempty" swaggertype:"object"` // set if rotating
}
// CredentialOut is what we return (no secrets)
type CredentialOut struct {
ID string `json:"id"`
Provider string `json:"provider"`
Kind string `json:"kind"`
SchemaVersion int `json:"schema_version"`
Name string `json:"name"`
ScopeKind string `json:"scope_kind"`
ScopeVersion int `json:"scope_version"`
Scope RawJSON `json:"scope" swaggertype:"object"`
AccountID string `json:"account_id,omitempty"`
Region string `json:"region,omitempty"`
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
}

View File

@@ -0,0 +1,103 @@
package dto
import (
"encoding/json"
"strings"
"github.com/go-playground/validator/v10"
)
var dnsValidate = validator.New()
func init() {
_ = dnsValidate.RegisterValidation("fqdn", func(fl validator.FieldLevel) bool {
s := strings.TrimSpace(fl.Field().String())
if s == "" || len(s) > 253 {
return false
}
// Minimal: lower-cased, no trailing dot in our API (normalize server-side)
// You can add stricter checks later.
return !strings.HasPrefix(s, ".") && !strings.Contains(s, "..")
})
_ = dnsValidate.RegisterValidation("rrtype", func(fl validator.FieldLevel) bool {
switch strings.ToUpper(fl.Field().String()) {
case "A", "AAAA", "CNAME", "TXT", "MX", "NS", "SRV", "CAA":
return true
default:
return false
}
})
}
// ---- Domains ----
type CreateDomainRequest struct {
DomainName string `json:"domain_name" validate:"required,fqdn"`
CredentialID string `json:"credential_id" validate:"required,uuid4"`
ZoneID string `json:"zone_id,omitempty" validate:"omitempty,max=128"`
}
type UpdateDomainRequest struct {
CredentialID *string `json:"credential_id,omitempty" validate:"omitempty,uuid4"`
ZoneID *string `json:"zone_id,omitempty" validate:"omitempty,max=128"`
Status *string `json:"status,omitempty" validate:"omitempty,oneof=pending provisioning ready failed"`
DomainName *string `json:"domain_name,omitempty" validate:"omitempty,fqdn"`
}
type DomainResponse struct {
ID string `json:"id"`
OrganizationID string `json:"organization_id"`
DomainName string `json:"domain_name"`
ZoneID string `json:"zone_id"`
Status string `json:"status"`
LastError string `json:"last_error"`
CredentialID string `json:"credential_id"`
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
}
// ---- Record Sets ----
type AliasTarget struct {
HostedZoneID string `json:"hosted_zone_id" validate:"required"`
DNSName string `json:"dns_name" validate:"required"`
EvaluateTargetHealth bool `json:"evaluate_target_health"`
}
type CreateRecordSetRequest struct {
// Name relative to domain ("endpoint") OR FQDN ("endpoint.example.com").
// Server normalizes to relative.
Name string `json:"name" validate:"required,max=253"`
Type string `json:"type" validate:"required,rrtype"`
TTL *int `json:"ttl,omitempty" validate:"omitempty,gte=1,lte=86400"`
Values []string `json:"values" validate:"omitempty,dive,min=1,max=1024"`
}
type UpdateRecordSetRequest struct {
// Any change flips status back to pending (worker will UPSERT)
Name *string `json:"name,omitempty" validate:"omitempty,max=253"`
Type *string `json:"type,omitempty" validate:"omitempty,rrtype"`
TTL *int `json:"ttl,omitempty" validate:"omitempty,gte=1,lte=86400"`
Values *[]string `json:"values,omitempty" validate:"omitempty,dive,min=1,max=1024"`
Status *string `json:"status,omitempty" validate:"omitempty,oneof=pending provisioning ready failed"`
}
type RecordSetResponse struct {
ID string `json:"id"`
DomainID string `json:"domain_id"`
Name string `json:"name"`
Type string `json:"type"`
TTL *int `json:"ttl,omitempty"`
Values json.RawMessage `json:"values" swaggertype:"object"` // []string JSON
Fingerprint string `json:"fingerprint"`
Status string `json:"status"`
LastError string `json:"last_error"`
Owner string `json:"owner"`
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
}
// DNSValidate Quick helper to validate DTOs in handlers
func DNSValidate(i any) error {
return dnsValidate.Struct(i)
}

View File

@@ -57,6 +57,6 @@ type PageJob struct {
type EnqueueRequest struct {
Queue string `json:"queue" example:"default"`
Type string `json:"type" example:"email.send"`
Payload json.RawMessage `json:"payload"`
Payload json.RawMessage `json:"payload" swaggertype:"object"`
RunAt *time.Time `json:"run_at" example:"2025-11-05T08:00:00Z"`
}

View File

@@ -0,0 +1,46 @@
package dto
import "github.com/glueops/autoglue/internal/common"
type NodeRole string
const (
NodeRoleMaster NodeRole = "master"
NodeRoleWorker NodeRole = "worker"
)
type CreateNodePoolRequest struct {
Name string `json:"name"`
Role NodeRole `json:"role" enums:"master,worker" swaggertype:"string"`
}
type UpdateNodePoolRequest struct {
Name *string `json:"name"`
Role *NodeRole `json:"role" enums:"master,worker" swaggertype:"string"`
}
type NodePoolResponse struct {
common.AuditFields
Name string `json:"name"`
Role NodeRole `json:"role" enums:"master,worker" swaggertype:"string"`
Servers []ServerResponse `json:"servers"`
Annotations []AnnotationResponse `json:"annotations"`
Labels []LabelResponse `json:"labels"`
Taints []TaintResponse `json:"taints"`
}
type AttachServersRequest struct {
ServerIDs []string `json:"server_ids"`
}
type AttachTaintsRequest struct {
TaintIDs []string `json:"taint_ids"`
}
type AttachLabelsRequest struct {
LabelIDs []string `json:"label_ids"`
}
type AttachAnnotationsRequest struct {
AnnotationIDs []string `json:"annotation_ids"`
}

View File

@@ -8,8 +8,8 @@ type CreateServerRequest struct {
PrivateIPAddress string `json:"private_ip_address"`
SSHUser string `json:"ssh_user"`
SshKeyID string `json:"ssh_key_id"`
Role string `json:"role" example:"master|worker|bastion"`
Status string `json:"status,omitempty" example:"pending|provisioning|ready|failed"`
Role string `json:"role" example:"master|worker|bastion" enums:"master,worker,bastion"`
Status string `json:"status,omitempty" example:"pending|provisioning|ready|failed" enums:"pending,provisioning,ready,failed"`
}
type UpdateServerRequest struct {
@@ -18,8 +18,8 @@ type UpdateServerRequest struct {
PrivateIPAddress *string `json:"private_ip_address,omitempty"`
SSHUser *string `json:"ssh_user,omitempty"`
SshKeyID *string `json:"ssh_key_id,omitempty"`
Role *string `json:"role,omitempty" example:"master|worker|bastion"`
Status *string `json:"status,omitempty" example:"pending|provisioning|ready|failed"`
Role *string `json:"role" example:"master|worker|bastion" enums:"master,worker,bastion"`
Status *string `json:"status,omitempty" example:"pending|provisioning|ready|failed" enums:"pending,provisioning,ready,failed"`
}
type ServerResponse struct {
@@ -30,8 +30,8 @@ type ServerResponse struct {
PrivateIPAddress string `json:"private_ip_address"`
SSHUser string `json:"ssh_user"`
SshKeyID uuid.UUID `json:"ssh_key_id"`
Role string `json:"role"`
Status string `json:"status"`
Role string `json:"role" example:"master|worker|bastion" enums:"master,worker,bastion"`
Status string `json:"status,omitempty" example:"pending|provisioning|ready|failed" enums:"pending,provisioning,ready,failed"`
CreatedAt string `json:"created_at,omitempty"`
UpdatedAt string `json:"updated_at,omitempty"`
}

File diff suppressed because it is too large Load Diff

View File

@@ -370,6 +370,63 @@ func DeleteServer(db *gorm.DB) http.HandlerFunc {
}
}
// ResetServerHostKey godoc
//
// @ID ResetServerHostKey
// @Summary Reset SSH host key (org scoped)
// @Description Clears the stored SSH host key for this server. The next SSH connection will re-learn the host key (trust-on-first-use).
// @Tags Servers
// @Accept json
// @Produce json
// @Param X-Org-ID header string false "Organization UUID"
// @Param id path string true "Server ID (UUID)"
// @Success 200 {object} 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 "reset failed"
// @Router /servers/{id}/reset-hostkey [post]
// @Security BearerAuth
// @Security OrgKeyAuth
// @Security OrgSecretAuth
func ResetServerHostKey(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, "id_invalid", "invalid id")
return
}
var server models.Server
if err := db.Where("id = ? AND organization_id = ?", id, orgID).First(&server).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
utils.WriteError(w, http.StatusNotFound, "server_not_found", "server not found")
return
}
utils.WriteError(w, http.StatusInternalServerError, "db_error", "failed to get server")
return
}
// Clear stored host key so next SSH handshake will TOFU and persist a new one.
server.SSHHostKey = ""
server.SSHHostKeyAlgo = ""
if err := db.Save(&server).Error; err != nil {
utils.WriteError(w, http.StatusInternalServerError, "db_error", "failed to reset host key")
return
}
utils.WriteJSON(w, http.StatusOK, server)
}
}
// --- Helpers ---
func validStatus(status string) bool {

View File

@@ -62,6 +62,9 @@ func ListPublicSshKeys(db *gorm.DB) http.HandlerFunc {
return
}
if out == nil {
out = []dto.SshResponse{}
}
utils.WriteJSON(w, http.StatusOK, out)
}
}
@@ -379,7 +382,6 @@ func DownloadSSHKey(db *gorm.DB) http.HandlerFunc {
}
if mode == "json" {
prefix := keyFilenamePrefix(key.PublicKey)
resp := dto.SshMaterialJSON{
ID: key.ID.String(),
Name: key.Name,
@@ -389,7 +391,7 @@ func DownloadSSHKey(db *gorm.DB) http.HandlerFunc {
case "public":
pub := key.PublicKey
resp.PublicKey = &pub
resp.Filenames = []string{fmt.Sprintf("%s_%s.pub", prefix, key.ID.String())}
resp.Filenames = []string{fmt.Sprintf("%s.pub", key.ID.String())}
utils.WriteJSON(w, http.StatusOK, resp)
return
@@ -400,7 +402,7 @@ func DownloadSSHKey(db *gorm.DB) http.HandlerFunc {
return
}
resp.PrivatePEM = &plain
resp.Filenames = []string{fmt.Sprintf("%s_%s.pem", prefix, key.ID.String())}
resp.Filenames = []string{fmt.Sprintf("%s.pem", key.ID.String())}
utils.WriteJSON(w, http.StatusOK, resp)
return
@@ -413,16 +415,16 @@ func DownloadSSHKey(db *gorm.DB) http.HandlerFunc {
var buf bytes.Buffer
zw := zip.NewWriter(&buf)
_ = toZipFile(fmt.Sprintf("%s_%s.pem", prefix, key.ID.String()), []byte(plain), zw)
_ = toZipFile(fmt.Sprintf("%s_%s.pub", prefix, key.ID.String()), []byte(key.PublicKey), zw)
_ = toZipFile(fmt.Sprintf("%s.pem", key.ID.String()), []byte(plain), zw)
_ = toZipFile(fmt.Sprintf("%s.pub", key.ID.String()), []byte(key.PublicKey), zw)
_ = zw.Close()
b64 := utils.EncodeB64(buf.Bytes())
resp.ZipBase64 = &b64
resp.Filenames = []string{
fmt.Sprintf("%s_%s.zip", prefix, key.ID.String()),
fmt.Sprintf("%s_%s.pem", prefix, key.ID.String()),
fmt.Sprintf("%s_%s.pub", prefix, key.ID.String()),
fmt.Sprintf("%s.zip", key.ID.String()),
fmt.Sprintf("%s.pem", key.ID.String()),
fmt.Sprintf("%s.pub", key.ID.String()),
}
utils.WriteJSON(w, http.StatusOK, resp)
return
@@ -433,11 +435,9 @@ func DownloadSSHKey(db *gorm.DB) http.HandlerFunc {
}
}
prefix := keyFilenamePrefix(key.PublicKey)
switch part {
case "public":
filename := fmt.Sprintf("%s_%s.pub", prefix, key.ID.String())
filename := fmt.Sprintf("%s.pub", key.ID.String())
w.Header().Set("Content-Type", "text/plain")
w.Header().Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, filename))
_, _ = w.Write([]byte(key.PublicKey))
@@ -449,7 +449,7 @@ func DownloadSSHKey(db *gorm.DB) http.HandlerFunc {
utils.WriteError(w, http.StatusInternalServerError, "db_error", "failed to decrypt ssh key")
return
}
filename := fmt.Sprintf("%s_%s.pem", prefix, key.ID.String())
filename := fmt.Sprintf("%s.pem", key.ID.String())
w.Header().Set("Content-Type", "application/x-pem-file")
w.Header().Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, filename))
_, _ = w.Write([]byte(plain))
@@ -464,8 +464,8 @@ func DownloadSSHKey(db *gorm.DB) http.HandlerFunc {
var buf bytes.Buffer
zw := zip.NewWriter(&buf)
_ = toZipFile(fmt.Sprintf("%s_%s.pem", prefix, key.ID.String()), []byte(plain), zw)
_ = toZipFile(fmt.Sprintf("%s_%s.pub", prefix, key.ID.String()), []byte(key.PublicKey), zw)
_ = toZipFile(fmt.Sprintf("%s.pem", key.ID.String()), []byte(plain), zw)
_ = toZipFile(fmt.Sprintf("%s.pub", key.ID.String()), []byte(key.PublicKey), zw)
_ = zw.Close()
filename := fmt.Sprintf("ssh_key_%s.zip", key.ID.String())

View File

@@ -0,0 +1,65 @@
package handlers
import (
"net/http"
"runtime"
"runtime/debug"
"strconv"
"github.com/glueops/autoglue/internal/utils"
"github.com/glueops/autoglue/internal/version"
)
type VersionResponse struct {
Version string `json:"version" example:"1.4.2"`
Commit string `json:"commit" example:"a1b2c3d"`
Built string `json:"built" example:"2025-11-08T12:34:56Z"`
BuiltBy string `json:"builtBy" example:"ci"`
Go string `json:"go" example:"go1.23.3"`
GOOS string `json:"goOS" example:"linux"`
GOARCH string `json:"goArch" example:"amd64"`
VCS string `json:"vcs,omitempty" example:"git"`
Revision string `json:"revision,omitempty" example:"a1b2c3d4e5f6abcdef"`
CommitTime string `json:"commitTime,omitempty" example:"2025-11-08T12:31:00Z"`
Modified *bool `json:"modified,omitempty" example:"false"`
}
// Version godoc
//
// @Summary Service version information
// @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]
func Version(w http.ResponseWriter, r *http.Request) {
resp := VersionResponse{
Version: version.Version,
Commit: version.Commit,
Built: version.Date,
BuiltBy: version.BuiltBy,
Go: runtime.Version(),
GOOS: runtime.GOOS,
GOARCH: runtime.GOARCH,
}
if bi, ok := debug.ReadBuildInfo(); ok {
for _, s := range bi.Settings {
switch s.Key {
case "vcs":
resp.VCS = s.Value
case "vcs.revision":
resp.Revision = s.Value
case "vcs.time":
resp.CommitTime = s.Value
case "vcs.modified":
if b, err := strconv.ParseBool(s.Value); err == nil {
resp.Modified = &b
}
}
}
}
utils.WriteJSON(w, http.StatusOK, resp)
}

View File

@@ -9,4 +9,5 @@ type Annotation struct {
Organization Organization `gorm:"foreignKey:OrganizationID;constraint:OnDelete:CASCADE" json:"organization"`
Key string `gorm:"not null" json:"key"`
Value string `gorm:"not null" json:"value"`
NodePools []NodePool `gorm:"many2many:node_annotations;constraint:OnDelete:CASCADE" json:"node_pools,omitempty"`
}

18
internal/models/backup.go Normal file
View File

@@ -0,0 +1,18 @@
package models
import (
"time"
"github.com/google/uuid"
)
type Backup struct {
ID uuid.UUID `gorm:"type:uuid;primaryKey;default:gen_random_uuid()"`
OrganizationID uuid.UUID `gorm:"type:uuid;not null;index;uniqueIndex:uniq_org_credential,priority:1"`
Organization Organization `gorm:"foreignKey:OrganizationID;constraint:OnDelete:CASCADE" json:"organization"`
Enabled bool `gorm:"not null;default:false" json:"enabled"`
CredentialID uuid.UUID `gorm:"type:uuid;not null;uniqueIndex:uniq_org_credential,priority:2" json:"credential_id"`
Credential Credential `gorm:"foreignKey:CredentialID" json:"credential,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()"`
}

View File

@@ -0,0 +1,33 @@
package models
import (
"time"
"github.com/google/uuid"
)
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}
ControlPlane string `json:"control_plane"` // <- dns cntlpn
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()"`
}

View File

@@ -0,0 +1,29 @@
package models
import (
"time"
"github.com/google/uuid"
"gorm.io/datatypes"
)
type Credential struct {
ID uuid.UUID `gorm:"type:uuid;primaryKey;default:gen_random_uuid()"`
OrganizationID uuid.UUID `gorm:"type:uuid;not null;index;uniqueIndex:uniq_org_provider_scopekind_scope,priority:1" json:"organization_id"`
Organization Organization `gorm:"foreignKey:OrganizationID;constraint:OnDelete:CASCADE" json:"organization"`
Provider string `gorm:"type:varchar(50);not null;uniqueIndex:uniq_org_provider_scopekind_scope,priority:2;index:idx_provider_kind"`
Kind string `gorm:"type:varchar(50);not null;index:idx_provider_kind;index:idx_kind_scope"`
ScopeKind string `gorm:"type:varchar(20);not null;uniqueIndex:uniq_org_provider_scopekind_scope,priority:3"`
Scope datatypes.JSON `gorm:"type:jsonb;not null;default:'{}';index:idx_kind_scope"`
ScopeFingerprint string `gorm:"type:char(64);not null;uniqueIndex:uniq_org_provider_scopekind_scope,priority:4;index"`
SchemaVersion int `gorm:"not null;default:1"`
Name string `gorm:"type:varchar(100);not null;default:''"`
ScopeVersion int `gorm:"not null;default:1"`
AccountID string `gorm:"type:varchar(32)"`
Region string `gorm:"type:varchar(32)"`
EncryptedData string `gorm:"not null"`
IV string `gorm:"not null"`
Tag string `gorm:"not null"`
CreatedAt time.Time `json:"created_at,omitempty" gorm:"type:timestamptz;column:created_at;not null;default:now()" format:"date-time"`
UpdatedAt time.Time `json:"updated_at,omitempty" gorm:"type:timestamptz;autoUpdateTime;column:updated_at;not null;default:now()" format:"date-time"`
}

41
internal/models/domain.go Normal file
View File

@@ -0,0 +1,41 @@
package models
import (
"time"
"github.com/google/uuid"
"gorm.io/datatypes"
)
type Domain struct {
ID uuid.UUID `gorm:"type:uuid;primaryKey;default:gen_random_uuid()"`
OrganizationID uuid.UUID `gorm:"type:uuid;not null;index;uniqueIndex:uniq_org_domain,priority:1"`
Organization Organization `gorm:"foreignKey:OrganizationID;constraint:OnDelete:CASCADE" json:"organization"`
DomainName string `gorm:"type:varchar(253);not null;uniqueIndex:uniq_org_domain,priority:2"`
ZoneID string `gorm:"type:varchar(128);not null;default:''"` // backfilled for R53 (e.g. "/hostedzone/Z123...")
Status string `gorm:"type:varchar(20);not null;default:'pending'"` // pending, provisioning, ready, failed
LastError string `gorm:"type:text;not null;default:''"`
CredentialID uuid.UUID `gorm:"type:uuid;not null" json:"credential_id"`
Credential Credential `gorm:"foreignKey:CredentialID" json:"credential,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()"`
}
type RecordSet struct {
ID uuid.UUID `gorm:"type:uuid;primaryKey;default:gen_random_uuid()"`
DomainID uuid.UUID `gorm:"type:uuid;not null;index"`
Domain Domain `gorm:"foreignKey:DomainID;constraint:OnDelete:CASCADE"`
Name string `gorm:"type:varchar(253);not null"` // e.g. "endpoint" (relative to DomainName)
Type string `gorm:"type:varchar(10);not null;index"` // A, AAAA, CNAME, TXT, MX, SRV, NS, CAA...
TTL *int `gorm:""` // nil for alias targets (Route 53 ignores TTL for alias)
Values datatypes.JSON `gorm:"type:jsonb;not null;default:'[]'"`
Fingerprint string `gorm:"type:char(64);not null;index"` // sha256 of canonical(name,type,ttl,values|alias)
Status string `gorm:"type:varchar(20);not null;default:'pending'"`
Owner string `gorm:"type:varchar(16);not null;default:'unknown'"` // 'autoglue' | 'external' | 'unknown'
LastError string `gorm:"type:text;not null;default:''"`
_ struct{} `gorm:"uniqueIndex:uniq_domain_name_type,priority:1"` // tag holder
_ struct{} `gorm:"uniqueIndex:uniq_domain_name_type,priority:2"`
_ struct{} `gorm:"uniqueIndex:uniq_domain_name_type,priority:3"`
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()"`
}

View File

@@ -9,5 +9,5 @@ type Label struct {
Organization Organization `gorm:"foreignKey:OrganizationID;constraint:OnDelete:CASCADE" json:"organization"`
Key string `gorm:"not null" json:"key"`
Value string `gorm:"not null" json:"value"`
NodePools []NodePool `gorm:"many2many:node_labels;constraint:OnDelete:CASCADE" json:"servers,omitempty"`
NodePools []NodePool `gorm:"many2many:node_labels;constraint:OnDelete:CASCADE" json:"node_pools,omitempty"`
}

View File

@@ -1,23 +1,18 @@
package models
import (
"time"
"github.com/google/uuid"
"github.com/glueops/autoglue/internal/common"
)
type NodePool struct {
ID uuid.UUID `gorm:"type:uuid;primaryKey;default:gen_random_uuid()" 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"`
Servers []Server `gorm:"many2many:node_servers;constraint:OnDelete:CASCADE" json:"servers,omitempty"`
Annotations []Annotation `gorm:"many2many:node_annotations;constraint:OnDelete:CASCADE" json:"annotations,omitempty"`
Labels []Label `gorm:"many2many:node_labels;constraint:OnDelete:CASCADE" json:"labels,omitempty"`
Taints []Taint `gorm:"many2many:node_taints;constraint:OnDelete:CASCADE" json:"taints,omitempty"`
//Clusters []Cluster `gorm:"many2many:cluster_node_pools;constraint:OnDelete:CASCADE" json:"clusters,omitempty"`
common.AuditFields
Organization Organization `gorm:"foreignKey:OrganizationID;constraint:OnDelete:CASCADE" json:"organization"`
Name string `gorm:"not null" json:"name"`
Servers []Server `gorm:"many2many:node_servers;constraint:OnDelete:CASCADE" json:"servers,omitempty"`
Annotations []Annotation `gorm:"many2many:node_annotations;constraint:OnDelete:CASCADE" json:"annotations,omitempty"`
Labels []Label `gorm:"many2many:node_labels;constraint:OnDelete:CASCADE" json:"labels,omitempty"`
Taints []Taint `gorm:"many2many:node_taints;constraint:OnDelete:CASCADE" json:"taints,omitempty"`
Clusters []Cluster `gorm:"many2many:cluster_node_pools;constraint:OnDelete:CASCADE" json:"clusters,omitempty"`
//Topology string `gorm:"not null,default:'stacked'" json:"topology,omitempty"` // stacked or external
Role string `gorm:"not null,default:'worker'" json:"role,omitempty"` // master, worker, or etcd (etcd only if topology = external
CreatedAt time.Time `gorm:"not null;default:now()" json:"created_at" format:"date-time"`
UpdatedAt time.Time `gorm:"not null;default:now()" json:"updated_at" format:"date-time"`
Role string `gorm:"not null,default:'worker'" json:"role,omitempty"` // master, worker, or etcd (etcd only if topology = external
}

View File

@@ -19,8 +19,11 @@ type Server struct {
SSHUser string `gorm:"not null" json:"ssh_user"`
SshKeyID uuid.UUID `gorm:"type:uuid;not null" json:"ssh_key_id"`
SshKey SshKey `gorm:"foreignKey:SshKeyID" json:"ssh_key"`
Role string `gorm:"not null" json:"role"` // e.g., "master", "worker", "bastion"
Status string `gorm:"default:'pending'" json:"status"` // pending, provisioning, ready, failed
Role string `gorm:"not null" json:"role" enums:"master,worker,bastion"` // e.g., "master", "worker", "bastion"
Status string `gorm:"default:'pending'" json:"status" enums:"pending, provisioning, ready, failed"` // pending, provisioning, ready, failed
NodePools []NodePool `gorm:"many2many:node_servers;constraint:OnDelete:CASCADE" json:"node_pools,omitempty"`
SSHHostKey string `gorm:"column:ssh_host_key"`
SSHHostKeyAlgo string `gorm:"column:ssh_host_key_algo"`
CreatedAt time.Time `gorm:"not null;default:now()" json:"created_at" format:"date-time"`
UpdatedAt time.Time `gorm:"not null;default:now()" json:"updated_at" format:"date-time"`
}

File diff suppressed because one or more lines are too long

Binary file not shown.

Binary file not shown.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

Binary file not shown.

File diff suppressed because one or more lines are too long

Binary file not shown.

Binary file not shown.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -5,9 +5,9 @@
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>AutoGlue</title>
<script type="module" crossorigin src="/assets/index-DbPirwVN.js"></script>
<script type="module" crossorigin src="/assets/index-52pog1DZ.js"></script>
<link rel="modulepreload" crossorigin href="/assets/react-B75e6Si-.js">
<link rel="stylesheet" crossorigin href="/assets/index-CjaTfP6H.css">
<link rel="stylesheet" crossorigin href="/assets/index-tX4seA_J.css">
</head>
<body>
<div id="root"></div>

Binary file not shown.

Binary file not shown.

View File

@@ -1 +1,2 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88"
height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -61,6 +61,7 @@ func SPAHandler() (http.Handler, error) {
if strings.HasPrefix(r.URL.Path, "/api/") ||
r.URL.Path == "/api" ||
strings.HasPrefix(r.URL.Path, "/swagger") ||
strings.HasPrefix(r.URL.Path, "/db-studio") ||
strings.HasPrefix(r.URL.Path, "/debug/pprof") {
http.NotFound(w, r)
return

11
main.go
View File

@@ -1,6 +1,12 @@
package main
import "github.com/glueops/autoglue/cmd"
import (
"os"
"github.com/glueops/autoglue/cmd"
"github.com/glueops/autoglue/docs"
"github.com/joho/godotenv"
)
// @title AutoGlue API
// @version 1.0
@@ -10,7 +16,6 @@ import "github.com/glueops/autoglue/cmd"
// @BasePath /api/v1
// @schemes http https
// @host localhost:8080
// @securityDefinitions.apikey BearerAuth
// @in header
@@ -33,5 +38,7 @@ import "github.com/glueops/autoglue/cmd"
// @description Org-level secret
func main() {
_ = godotenv.Load()
docs.SwaggerInfo.Host = os.Getenv("SWAGGER_HOST")
cmd.Execute()
}

View File

@@ -1,4 +1,4 @@
FROM postgres:latest@sha256:feff5b24fedd610975a1f5e743c51a4b360437f4dc3a11acf740dcd708f413f6
FROM postgres:17.7@sha256:44640f16641cf36716cabd011e2f7eb4742b6b6b19f4488ddcbb7c250e5c9753
RUN cd /var/lib/postgresql/ && \
openssl req -new -text -passout pass:abcd -subj /CN=localhost -out server.req -keyout privkey.pem && \

24
sdk/go/.gitignore vendored
View File

@@ -1,24 +0,0 @@
# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
*.a
*.so
# Folders
_obj
_test
# Architecture specific extensions/prefixes
*.[568vq]
[568vq].out
*.cgo1.go
*.cgo2.c
_cgo_defun.c
_cgo_gotypes.go
_cgo_export.*
_testmain.go
*.exe
*.test
*.prof

View File

@@ -1,23 +0,0 @@
# OpenAPI Generator Ignore
# Generated by openapi-generator https://github.com/openapitools/openapi-generator
# Use this file to prevent files from being overwritten by the generator.
# The patterns follow closely to .gitignore or .dockerignore.
# As an example, the C# client generator defines ApiClient.cs.
# You can make changes and tell OpenAPI Generator to ignore just this file by uncommenting the following line:
#ApiClient.cs
# You can match any string of characters against a directory, file or extension with a single asterisk (*):
#foo/*/qux
# The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux
# You can recursively match patterns against a directory, file or extension with a double asterisk (**):
#foo/**/qux
# This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux
# You can also negate patterns with an exclamation (!).
# For example, you can ignore all files in a docs folder with the file extension .md:
#docs/*.md
# Then explicitly reverse the ignore rule for a single file:
#!docs/README.md

View File

@@ -1,127 +0,0 @@
.gitignore
.openapi-generator-ignore
.travis.yml
README.md
api/openapi.yaml
api_annotations.go
api_archer_admin.go
api_auth.go
api_health.go
api_labels.go
api_me.go
api_me_api_keys.go
api_orgs.go
api_servers.go
api_ssh.go
api_taints.go
client.go
configuration.go
docs/AnnotationsAPI.md
docs/ArcherAdminAPI.md
docs/AuthAPI.md
docs/DtoAnnotationResponse.md
docs/DtoAuthStartResponse.md
docs/DtoCreateAnnotationRequest.md
docs/DtoCreateLabelRequest.md
docs/DtoCreateSSHRequest.md
docs/DtoCreateServerRequest.md
docs/DtoCreateTaintRequest.md
docs/DtoJWK.md
docs/DtoJWKS.md
docs/DtoJob.md
docs/DtoJobStatus.md
docs/DtoLabelResponse.md
docs/DtoLogoutRequest.md
docs/DtoPageJob.md
docs/DtoQueueInfo.md
docs/DtoRefreshRequest.md
docs/DtoServerResponse.md
docs/DtoSshResponse.md
docs/DtoSshRevealResponse.md
docs/DtoTaintResponse.md
docs/DtoTokenPair.md
docs/DtoUpdateAnnotationRequest.md
docs/DtoUpdateLabelRequest.md
docs/DtoUpdateServerRequest.md
docs/DtoUpdateTaintRequest.md
docs/HandlersCreateUserKeyRequest.md
docs/HandlersHealthStatus.md
docs/HandlersMeResponse.md
docs/HandlersMemberOut.md
docs/HandlersMemberUpsertReq.md
docs/HandlersOrgCreateReq.md
docs/HandlersOrgKeyCreateReq.md
docs/HandlersOrgKeyCreateResp.md
docs/HandlersOrgUpdateReq.md
docs/HandlersUpdateMeRequest.md
docs/HandlersUserAPIKeyOut.md
docs/HealthAPI.md
docs/LabelsAPI.md
docs/MeAPI.md
docs/MeAPIKeysAPI.md
docs/ModelsAPIKey.md
docs/ModelsOrganization.md
docs/ModelsUser.md
docs/ModelsUserEmail.md
docs/OrgsAPI.md
docs/ServersAPI.md
docs/SshAPI.md
docs/TaintsAPI.md
docs/UtilsErrorResponse.md
git_push.sh
go.mod
go.sum
model_dto_annotation_response.go
model_dto_auth_start_response.go
model_dto_create_annotation_request.go
model_dto_create_label_request.go
model_dto_create_server_request.go
model_dto_create_ssh_request.go
model_dto_create_taint_request.go
model_dto_job.go
model_dto_job_status.go
model_dto_jwk.go
model_dto_jwks.go
model_dto_label_response.go
model_dto_logout_request.go
model_dto_page_job.go
model_dto_queue_info.go
model_dto_refresh_request.go
model_dto_server_response.go
model_dto_ssh_response.go
model_dto_ssh_reveal_response.go
model_dto_taint_response.go
model_dto_token_pair.go
model_dto_update_annotation_request.go
model_dto_update_label_request.go
model_dto_update_server_request.go
model_dto_update_taint_request.go
model_handlers_create_user_key_request.go
model_handlers_health_status.go
model_handlers_me_response.go
model_handlers_member_out.go
model_handlers_member_upsert_req.go
model_handlers_org_create_req.go
model_handlers_org_key_create_req.go
model_handlers_org_key_create_resp.go
model_handlers_org_update_req.go
model_handlers_update_me_request.go
model_handlers_user_api_key_out.go
model_models_api_key.go
model_models_organization.go
model_models_user.go
model_models_user_email.go
model_utils_error_response.go
response.go
test/api_annotations_test.go
test/api_archer_admin_test.go
test/api_auth_test.go
test/api_health_test.go
test/api_labels_test.go
test/api_me_api_keys_test.go
test/api_me_test.go
test/api_orgs_test.go
test/api_servers_test.go
test/api_ssh_test.go
test/api_taints_test.go
utils.go

View File

@@ -1 +0,0 @@
7.17.0

View File

@@ -1,8 +0,0 @@
language: go
install:
- go get -d -v .
script:
- go build -v ./

View File

@@ -1,288 +0,0 @@
# Go API client for autoglue
API for managing K3s clusters across cloud providers
## Overview
This API client was generated by the [OpenAPI Generator](https://openapi-generator.tech) project. By using the [OpenAPI-spec](https://www.openapis.org/) from a remote server, you can easily generate an API client.
- API version: 1.0
- Package version: 1.0.0
- Generator version: 7.17.0
- Build package: org.openapitools.codegen.languages.GoClientCodegen
## Installation
Install the following dependencies:
```sh
go get github.com/stretchr/testify/assert
go get golang.org/x/net/context
```
Put the package under your project folder and add the following in import:
```go
import autoglue "github.com/glueops/autoglue-sdk-go"
```
To use a proxy, set the environment variable `HTTP_PROXY`:
```go
os.Setenv("HTTP_PROXY", "http://proxy_name:proxy_port")
```
## Configuration of Server URL
Default configuration comes with `Servers` field that contains server objects as defined in the OpenAPI specification.
### Select Server Configuration
For using other server than the one defined on index 0 set context value `autoglue.ContextServerIndex` of type `int`.
```go
ctx := context.WithValue(context.Background(), autoglue.ContextServerIndex, 1)
```
### Templated Server URL
Templated server URL is formatted using default variables from configuration or from context value `autoglue.ContextServerVariables` of type `map[string]string`.
```go
ctx := context.WithValue(context.Background(), autoglue.ContextServerVariables, map[string]string{
"basePath": "v2",
})
```
Note, enum values are always validated and all unused variables are silently ignored.
### URLs Configuration per Operation
Each operation can use different server URL defined using `OperationServers` map in the `Configuration`.
An operation is uniquely identified by `"{classname}Service.{nickname}"` string.
Similar rules for overriding default operation server index and variables applies by using `autoglue.ContextOperationServerIndices` and `autoglue.ContextOperationServerVariables` context maps.
```go
ctx := context.WithValue(context.Background(), autoglue.ContextOperationServerIndices, map[string]int{
"{classname}Service.{nickname}": 2,
})
ctx = context.WithValue(context.Background(), autoglue.ContextOperationServerVariables, map[string]map[string]string{
"{classname}Service.{nickname}": {
"port": "8443",
},
})
```
## Documentation for API Endpoints
All URIs are relative to *http://localhost:8080/api/v1*
Class | Method | HTTP request | Description
------------ | ------------- | ------------- | -------------
*AnnotationsAPI* | [**CreateAnnotation**](docs/AnnotationsAPI.md#createannotation) | **Post** /annotations | Create annotation (org scoped)
*AnnotationsAPI* | [**DeleteAnnotation**](docs/AnnotationsAPI.md#deleteannotation) | **Delete** /annotations/{id} | Delete annotation (org scoped)
*AnnotationsAPI* | [**GetAnnotation**](docs/AnnotationsAPI.md#getannotation) | **Get** /annotations/{id} | Get annotation by ID (org scoped)
*AnnotationsAPI* | [**ListAnnotations**](docs/AnnotationsAPI.md#listannotations) | **Get** /annotations | List annotations (org scoped)
*AnnotationsAPI* | [**UpdateAnnotation**](docs/AnnotationsAPI.md#updateannotation) | **Patch** /annotations/{id} | Update annotation (org scoped)
*ArcherAdminAPI* | [**AdminCancelArcherJob**](docs/ArcherAdminAPI.md#admincancelarcherjob) | **Post** /admin/archer/jobs/{id}/cancel | Cancel an Archer job (admin)
*ArcherAdminAPI* | [**AdminEnqueueArcherJob**](docs/ArcherAdminAPI.md#adminenqueuearcherjob) | **Post** /admin/archer/jobs | Enqueue a new Archer job (admin)
*ArcherAdminAPI* | [**AdminListArcherJobs**](docs/ArcherAdminAPI.md#adminlistarcherjobs) | **Get** /admin/archer/jobs | List Archer jobs (admin)
*ArcherAdminAPI* | [**AdminListArcherQueues**](docs/ArcherAdminAPI.md#adminlistarcherqueues) | **Get** /admin/archer/queues | List Archer queues (admin)
*ArcherAdminAPI* | [**AdminRetryArcherJob**](docs/ArcherAdminAPI.md#adminretryarcherjob) | **Post** /admin/archer/jobs/{id}/retry | Retry a failed/canceled Archer job (admin)
*AuthAPI* | [**AuthCallback**](docs/AuthAPI.md#authcallback) | **Get** /auth/{provider}/callback | Handle social login callback
*AuthAPI* | [**AuthStart**](docs/AuthAPI.md#authstart) | **Post** /auth/{provider}/start | Begin social login
*AuthAPI* | [**GetJWKS**](docs/AuthAPI.md#getjwks) | **Get** /.well-known/jwks.json | Get JWKS
*AuthAPI* | [**Logout**](docs/AuthAPI.md#logout) | **Post** /auth/logout | Revoke refresh token family (logout everywhere)
*AuthAPI* | [**Refresh**](docs/AuthAPI.md#refresh) | **Post** /auth/refresh | Rotate refresh token
*HealthAPI* | [**HealthCheckOperationId**](docs/HealthAPI.md#healthcheckoperationid) | **Get** /healthz | Basic health check
*LabelsAPI* | [**CreateLabel**](docs/LabelsAPI.md#createlabel) | **Post** /labels | Create label (org scoped)
*LabelsAPI* | [**DeleteLabel**](docs/LabelsAPI.md#deletelabel) | **Delete** /labels/{id} | Delete label (org scoped)
*LabelsAPI* | [**GetLabel**](docs/LabelsAPI.md#getlabel) | **Get** /labels/{id} | Get label by ID (org scoped)
*LabelsAPI* | [**ListLabels**](docs/LabelsAPI.md#listlabels) | **Get** /labels | List node labels (org scoped)
*LabelsAPI* | [**UpdateLabel**](docs/LabelsAPI.md#updatelabel) | **Patch** /labels/{id} | Update label (org scoped)
*MeAPI* | [**GetMe**](docs/MeAPI.md#getme) | **Get** /me | Get current user profile
*MeAPI* | [**UpdateMe**](docs/MeAPI.md#updateme) | **Patch** /me | Update current user profile
*MeAPIKeysAPI* | [**CreateUserAPIKey**](docs/MeAPIKeysAPI.md#createuserapikey) | **Post** /me/api-keys | Create a new user API key
*MeAPIKeysAPI* | [**DeleteUserAPIKey**](docs/MeAPIKeysAPI.md#deleteuserapikey) | **Delete** /me/api-keys/{id} | Delete a user API key
*MeAPIKeysAPI* | [**ListUserAPIKeys**](docs/MeAPIKeysAPI.md#listuserapikeys) | **Get** /me/api-keys | List my API keys
*OrgsAPI* | [**AddOrUpdateMember**](docs/OrgsAPI.md#addorupdatemember) | **Post** /orgs/{id}/members | Add or update a member (owner/admin)
*OrgsAPI* | [**CreateOrg**](docs/OrgsAPI.md#createorg) | **Post** /orgs | Create organization
*OrgsAPI* | [**CreateOrgKey**](docs/OrgsAPI.md#createorgkey) | **Post** /orgs/{id}/api-keys | Create org key/secret pair (owner/admin)
*OrgsAPI* | [**DeleteOrg**](docs/OrgsAPI.md#deleteorg) | **Delete** /orgs/{id} | Delete organization (owner)
*OrgsAPI* | [**DeleteOrgKey**](docs/OrgsAPI.md#deleteorgkey) | **Delete** /orgs/{id}/api-keys/{key_id} | Delete org key (owner/admin)
*OrgsAPI* | [**GetOrg**](docs/OrgsAPI.md#getorg) | **Get** /orgs/{id} | Get organization
*OrgsAPI* | [**ListMembers**](docs/OrgsAPI.md#listmembers) | **Get** /orgs/{id}/members | List members in org
*OrgsAPI* | [**ListMyOrgs**](docs/OrgsAPI.md#listmyorgs) | **Get** /orgs | List organizations I belong to
*OrgsAPI* | [**ListOrgKeys**](docs/OrgsAPI.md#listorgkeys) | **Get** /orgs/{id}/api-keys | List org-scoped API keys (no secrets)
*OrgsAPI* | [**RemoveMember**](docs/OrgsAPI.md#removemember) | **Delete** /orgs/{id}/members/{user_id} | Remove a member (owner/admin)
*OrgsAPI* | [**UpdateOrg**](docs/OrgsAPI.md#updateorg) | **Patch** /orgs/{id} | Update organization (owner/admin)
*ServersAPI* | [**CreateServer**](docs/ServersAPI.md#createserver) | **Post** /servers | Create server (org scoped)
*ServersAPI* | [**DeleteServer**](docs/ServersAPI.md#deleteserver) | **Delete** /servers/{id} | Delete server (org scoped)
*ServersAPI* | [**GetServer**](docs/ServersAPI.md#getserver) | **Get** /servers/{id} | Get server by ID (org scoped)
*ServersAPI* | [**ListServers**](docs/ServersAPI.md#listservers) | **Get** /servers | List servers (org scoped)
*ServersAPI* | [**UpdateServer**](docs/ServersAPI.md#updateserver) | **Patch** /servers/{id} | Update server (org scoped)
*SshAPI* | [**CreateSSHKey**](docs/SshAPI.md#createsshkey) | **Post** /ssh | Create ssh keypair (org scoped)
*SshAPI* | [**DeleteSSHKey**](docs/SshAPI.md#deletesshkey) | **Delete** /ssh/{id} | Delete ssh keypair (org scoped)
*SshAPI* | [**DownloadSSHKey**](docs/SshAPI.md#downloadsshkey) | **Get** /ssh/{id}/download | Download ssh key files by ID (org scoped)
*SshAPI* | [**GetSSHKey**](docs/SshAPI.md#getsshkey) | **Get** /ssh/{id} | Get ssh key by ID (org scoped)
*SshAPI* | [**ListPublicSshKeys**](docs/SshAPI.md#listpublicsshkeys) | **Get** /ssh | List ssh keys (org scoped)
*TaintsAPI* | [**CreateTaint**](docs/TaintsAPI.md#createtaint) | **Post** /taints | Create node taint (org scoped)
*TaintsAPI* | [**DeleteTaint**](docs/TaintsAPI.md#deletetaint) | **Delete** /taints/{id} | Delete taint (org scoped)
*TaintsAPI* | [**GetTaint**](docs/TaintsAPI.md#gettaint) | **Get** /taints/{id} | Get node taint by ID (org scoped)
*TaintsAPI* | [**ListTaints**](docs/TaintsAPI.md#listtaints) | **Get** /taints | List node pool taints (org scoped)
*TaintsAPI* | [**UpdateTaint**](docs/TaintsAPI.md#updatetaint) | **Patch** /taints/{id} | Update node taint (org scoped)
## Documentation For Models
- [DtoAnnotationResponse](docs/DtoAnnotationResponse.md)
- [DtoAuthStartResponse](docs/DtoAuthStartResponse.md)
- [DtoCreateAnnotationRequest](docs/DtoCreateAnnotationRequest.md)
- [DtoCreateLabelRequest](docs/DtoCreateLabelRequest.md)
- [DtoCreateSSHRequest](docs/DtoCreateSSHRequest.md)
- [DtoCreateServerRequest](docs/DtoCreateServerRequest.md)
- [DtoCreateTaintRequest](docs/DtoCreateTaintRequest.md)
- [DtoJWK](docs/DtoJWK.md)
- [DtoJWKS](docs/DtoJWKS.md)
- [DtoJob](docs/DtoJob.md)
- [DtoJobStatus](docs/DtoJobStatus.md)
- [DtoLabelResponse](docs/DtoLabelResponse.md)
- [DtoLogoutRequest](docs/DtoLogoutRequest.md)
- [DtoPageJob](docs/DtoPageJob.md)
- [DtoQueueInfo](docs/DtoQueueInfo.md)
- [DtoRefreshRequest](docs/DtoRefreshRequest.md)
- [DtoServerResponse](docs/DtoServerResponse.md)
- [DtoSshResponse](docs/DtoSshResponse.md)
- [DtoSshRevealResponse](docs/DtoSshRevealResponse.md)
- [DtoTaintResponse](docs/DtoTaintResponse.md)
- [DtoTokenPair](docs/DtoTokenPair.md)
- [DtoUpdateAnnotationRequest](docs/DtoUpdateAnnotationRequest.md)
- [DtoUpdateLabelRequest](docs/DtoUpdateLabelRequest.md)
- [DtoUpdateServerRequest](docs/DtoUpdateServerRequest.md)
- [DtoUpdateTaintRequest](docs/DtoUpdateTaintRequest.md)
- [HandlersCreateUserKeyRequest](docs/HandlersCreateUserKeyRequest.md)
- [HandlersHealthStatus](docs/HandlersHealthStatus.md)
- [HandlersMeResponse](docs/HandlersMeResponse.md)
- [HandlersMemberOut](docs/HandlersMemberOut.md)
- [HandlersMemberUpsertReq](docs/HandlersMemberUpsertReq.md)
- [HandlersOrgCreateReq](docs/HandlersOrgCreateReq.md)
- [HandlersOrgKeyCreateReq](docs/HandlersOrgKeyCreateReq.md)
- [HandlersOrgKeyCreateResp](docs/HandlersOrgKeyCreateResp.md)
- [HandlersOrgUpdateReq](docs/HandlersOrgUpdateReq.md)
- [HandlersUpdateMeRequest](docs/HandlersUpdateMeRequest.md)
- [HandlersUserAPIKeyOut](docs/HandlersUserAPIKeyOut.md)
- [ModelsAPIKey](docs/ModelsAPIKey.md)
- [ModelsOrganization](docs/ModelsOrganization.md)
- [ModelsUser](docs/ModelsUser.md)
- [ModelsUserEmail](docs/ModelsUserEmail.md)
- [UtilsErrorResponse](docs/UtilsErrorResponse.md)
## Documentation For Authorization
Authentication schemes defined for the API:
### ApiKeyAuth
- **Type**: API key
- **API key parameter name**: X-API-KEY
- **Location**: HTTP header
Note, each API key must be added to a map of `map[string]APIKey` where the key is: ApiKeyAuth and passed in as the auth context for each request.
Example
```go
auth := context.WithValue(
context.Background(),
autoglue.ContextAPIKeys,
map[string]autoglue.APIKey{
"ApiKeyAuth": {Key: "API_KEY_STRING"},
},
)
r, err := client.Service.Operation(auth, args)
```
### BearerAuth
- **Type**: API key
- **API key parameter name**: Authorization
- **Location**: HTTP header
Note, each API key must be added to a map of `map[string]APIKey` where the key is: BearerAuth and passed in as the auth context for each request.
Example
```go
auth := context.WithValue(
context.Background(),
autoglue.ContextAPIKeys,
map[string]autoglue.APIKey{
"BearerAuth": {Key: "API_KEY_STRING"},
},
)
r, err := client.Service.Operation(auth, args)
```
### OrgKeyAuth
- **Type**: API key
- **API key parameter name**: X-ORG-KEY
- **Location**: HTTP header
Note, each API key must be added to a map of `map[string]APIKey` where the key is: OrgKeyAuth and passed in as the auth context for each request.
Example
```go
auth := context.WithValue(
context.Background(),
autoglue.ContextAPIKeys,
map[string]autoglue.APIKey{
"OrgKeyAuth": {Key: "API_KEY_STRING"},
},
)
r, err := client.Service.Operation(auth, args)
```
### OrgSecretAuth
- **Type**: API key
- **API key parameter name**: X-ORG-SECRET
- **Location**: HTTP header
Note, each API key must be added to a map of `map[string]APIKey` where the key is: OrgSecretAuth and passed in as the auth context for each request.
Example
```go
auth := context.WithValue(
context.Background(),
autoglue.ContextAPIKeys,
map[string]autoglue.APIKey{
"OrgSecretAuth": {Key: "API_KEY_STRING"},
},
)
r, err := client.Service.Operation(auth, args)
```
## Documentation for Utility Methods
Due to the fact that model structure members are all pointers, this package contains
a number of utility functions to easily obtain pointers to values of basic types.
Each of these functions takes a value of the given basic type and returns a pointer to it:
* `PtrBool`
* `PtrInt`
* `PtrInt32`
* `PtrInt64`
* `PtrFloat`
* `PtrFloat32`
* `PtrFloat64`
* `PtrString`
* `PtrTime`
## Author

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,864 +0,0 @@
/*
AutoGlue API
API for managing K3s clusters across cloud providers
API version: 1.0
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.
package autoglue
import (
"bytes"
"context"
"io"
"net/http"
"net/url"
"strings"
)
// ArcherAdminAPIService ArcherAdminAPI service
type ArcherAdminAPIService service
type ApiAdminCancelArcherJobRequest struct {
ctx context.Context
ApiService *ArcherAdminAPIService
id string
}
func (r ApiAdminCancelArcherJobRequest) Execute() (*DtoJob, *http.Response, error) {
return r.ApiService.AdminCancelArcherJobExecute(r)
}
/*
AdminCancelArcherJob Cancel an Archer job (admin)
Set job status to canceled if cancellable. For running jobs, this only affects future picks; wire to Archer if you need active kill.
@param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background().
@param id Job ID
@return ApiAdminCancelArcherJobRequest
*/
func (a *ArcherAdminAPIService) AdminCancelArcherJob(ctx context.Context, id string) ApiAdminCancelArcherJobRequest {
return ApiAdminCancelArcherJobRequest{
ApiService: a,
ctx: ctx,
id: id,
}
}
// Execute executes the request
//
// @return DtoJob
func (a *ArcherAdminAPIService) AdminCancelArcherJobExecute(r ApiAdminCancelArcherJobRequest) (*DtoJob, *http.Response, error) {
var (
localVarHTTPMethod = http.MethodPost
localVarPostBody interface{}
formFiles []formFile
localVarReturnValue *DtoJob
)
localBasePath, err := a.client.cfg.ServerURLWithContext(r.ctx, "ArcherAdminAPIService.AdminCancelArcherJob")
if err != nil {
return localVarReturnValue, nil, &GenericOpenAPIError{error: err.Error()}
}
localVarPath := localBasePath + "/admin/archer/jobs/{id}/cancel"
localVarPath = strings.Replace(localVarPath, "{"+"id"+"}", url.PathEscape(parameterValueToString(r.id, "id")), -1)
localVarHeaderParams := make(map[string]string)
localVarQueryParams := url.Values{}
localVarFormParams := url.Values{}
// to determine the Content-Type header
localVarHTTPContentTypes := []string{}
// set Content-Type header
localVarHTTPContentType := selectHeaderContentType(localVarHTTPContentTypes)
if localVarHTTPContentType != "" {
localVarHeaderParams["Content-Type"] = localVarHTTPContentType
}
// to determine the Accept header
localVarHTTPHeaderAccepts := []string{"application/json"}
// set Accept header
localVarHTTPHeaderAccept := selectHeaderAccept(localVarHTTPHeaderAccepts)
if localVarHTTPHeaderAccept != "" {
localVarHeaderParams["Accept"] = localVarHTTPHeaderAccept
}
if r.ctx != nil {
// API Key Authentication
if auth, ok := r.ctx.Value(ContextAPIKeys).(map[string]APIKey); ok {
if apiKey, ok := auth["BearerAuth"]; ok {
var key string
if apiKey.Prefix != "" {
key = apiKey.Prefix + " " + apiKey.Key
} else {
key = apiKey.Key
}
localVarHeaderParams["Authorization"] = key
}
}
}
req, err := a.client.prepareRequest(r.ctx, localVarPath, localVarHTTPMethod, localVarPostBody, localVarHeaderParams, localVarQueryParams, localVarFormParams, formFiles)
if err != nil {
return localVarReturnValue, nil, err
}
localVarHTTPResponse, err := a.client.callAPI(req)
if err != nil || localVarHTTPResponse == nil {
return localVarReturnValue, localVarHTTPResponse, err
}
localVarBody, err := io.ReadAll(localVarHTTPResponse.Body)
localVarHTTPResponse.Body.Close()
localVarHTTPResponse.Body = io.NopCloser(bytes.NewBuffer(localVarBody))
if err != nil {
return localVarReturnValue, localVarHTTPResponse, err
}
if localVarHTTPResponse.StatusCode >= 300 {
newErr := &GenericOpenAPIError{
body: localVarBody,
error: localVarHTTPResponse.Status,
}
if localVarHTTPResponse.StatusCode == 400 {
var v string
err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type"))
if err != nil {
newErr.error = err.Error()
return localVarReturnValue, localVarHTTPResponse, newErr
}
newErr.error = formatErrorMessage(localVarHTTPResponse.Status, &v)
newErr.model = v
return localVarReturnValue, localVarHTTPResponse, newErr
}
if localVarHTTPResponse.StatusCode == 401 {
var v string
err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type"))
if err != nil {
newErr.error = err.Error()
return localVarReturnValue, localVarHTTPResponse, newErr
}
newErr.error = formatErrorMessage(localVarHTTPResponse.Status, &v)
newErr.model = v
return localVarReturnValue, localVarHTTPResponse, newErr
}
if localVarHTTPResponse.StatusCode == 403 {
var v string
err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type"))
if err != nil {
newErr.error = err.Error()
return localVarReturnValue, localVarHTTPResponse, newErr
}
newErr.error = formatErrorMessage(localVarHTTPResponse.Status, &v)
newErr.model = v
return localVarReturnValue, localVarHTTPResponse, newErr
}
if localVarHTTPResponse.StatusCode == 404 {
var v string
err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type"))
if err != nil {
newErr.error = err.Error()
return localVarReturnValue, localVarHTTPResponse, newErr
}
newErr.error = formatErrorMessage(localVarHTTPResponse.Status, &v)
newErr.model = v
}
return localVarReturnValue, localVarHTTPResponse, newErr
}
err = a.client.decode(&localVarReturnValue, localVarBody, localVarHTTPResponse.Header.Get("Content-Type"))
if err != nil {
newErr := &GenericOpenAPIError{
body: localVarBody,
error: err.Error(),
}
return localVarReturnValue, localVarHTTPResponse, newErr
}
return localVarReturnValue, localVarHTTPResponse, nil
}
type ApiAdminEnqueueArcherJobRequest struct {
ctx context.Context
ApiService *ArcherAdminAPIService
body *map[string]interface{}
}
// Job parameters
func (r ApiAdminEnqueueArcherJobRequest) Body(body map[string]interface{}) ApiAdminEnqueueArcherJobRequest {
r.body = &body
return r
}
func (r ApiAdminEnqueueArcherJobRequest) Execute() (*DtoJob, *http.Response, error) {
return r.ApiService.AdminEnqueueArcherJobExecute(r)
}
/*
AdminEnqueueArcherJob Enqueue a new Archer job (admin)
Create a job immediately or schedule it for the future via `run_at`.
@param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background().
@return ApiAdminEnqueueArcherJobRequest
*/
func (a *ArcherAdminAPIService) AdminEnqueueArcherJob(ctx context.Context) ApiAdminEnqueueArcherJobRequest {
return ApiAdminEnqueueArcherJobRequest{
ApiService: a,
ctx: ctx,
}
}
// Execute executes the request
//
// @return DtoJob
func (a *ArcherAdminAPIService) AdminEnqueueArcherJobExecute(r ApiAdminEnqueueArcherJobRequest) (*DtoJob, *http.Response, error) {
var (
localVarHTTPMethod = http.MethodPost
localVarPostBody interface{}
formFiles []formFile
localVarReturnValue *DtoJob
)
localBasePath, err := a.client.cfg.ServerURLWithContext(r.ctx, "ArcherAdminAPIService.AdminEnqueueArcherJob")
if err != nil {
return localVarReturnValue, nil, &GenericOpenAPIError{error: err.Error()}
}
localVarPath := localBasePath + "/admin/archer/jobs"
localVarHeaderParams := make(map[string]string)
localVarQueryParams := url.Values{}
localVarFormParams := url.Values{}
if r.body == nil {
return localVarReturnValue, nil, reportError("body is required and must be specified")
}
// to determine the Content-Type header
localVarHTTPContentTypes := []string{"application/json"}
// set Content-Type header
localVarHTTPContentType := selectHeaderContentType(localVarHTTPContentTypes)
if localVarHTTPContentType != "" {
localVarHeaderParams["Content-Type"] = localVarHTTPContentType
}
// to determine the Accept header
localVarHTTPHeaderAccepts := []string{"application/json"}
// set Accept header
localVarHTTPHeaderAccept := selectHeaderAccept(localVarHTTPHeaderAccepts)
if localVarHTTPHeaderAccept != "" {
localVarHeaderParams["Accept"] = localVarHTTPHeaderAccept
}
// body params
localVarPostBody = r.body
if r.ctx != nil {
// API Key Authentication
if auth, ok := r.ctx.Value(ContextAPIKeys).(map[string]APIKey); ok {
if apiKey, ok := auth["BearerAuth"]; ok {
var key string
if apiKey.Prefix != "" {
key = apiKey.Prefix + " " + apiKey.Key
} else {
key = apiKey.Key
}
localVarHeaderParams["Authorization"] = key
}
}
}
req, err := a.client.prepareRequest(r.ctx, localVarPath, localVarHTTPMethod, localVarPostBody, localVarHeaderParams, localVarQueryParams, localVarFormParams, formFiles)
if err != nil {
return localVarReturnValue, nil, err
}
localVarHTTPResponse, err := a.client.callAPI(req)
if err != nil || localVarHTTPResponse == nil {
return localVarReturnValue, localVarHTTPResponse, err
}
localVarBody, err := io.ReadAll(localVarHTTPResponse.Body)
localVarHTTPResponse.Body.Close()
localVarHTTPResponse.Body = io.NopCloser(bytes.NewBuffer(localVarBody))
if err != nil {
return localVarReturnValue, localVarHTTPResponse, err
}
if localVarHTTPResponse.StatusCode >= 300 {
newErr := &GenericOpenAPIError{
body: localVarBody,
error: localVarHTTPResponse.Status,
}
if localVarHTTPResponse.StatusCode == 400 {
var v string
err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type"))
if err != nil {
newErr.error = err.Error()
return localVarReturnValue, localVarHTTPResponse, newErr
}
newErr.error = formatErrorMessage(localVarHTTPResponse.Status, &v)
newErr.model = v
return localVarReturnValue, localVarHTTPResponse, newErr
}
if localVarHTTPResponse.StatusCode == 401 {
var v string
err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type"))
if err != nil {
newErr.error = err.Error()
return localVarReturnValue, localVarHTTPResponse, newErr
}
newErr.error = formatErrorMessage(localVarHTTPResponse.Status, &v)
newErr.model = v
return localVarReturnValue, localVarHTTPResponse, newErr
}
if localVarHTTPResponse.StatusCode == 403 {
var v string
err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type"))
if err != nil {
newErr.error = err.Error()
return localVarReturnValue, localVarHTTPResponse, newErr
}
newErr.error = formatErrorMessage(localVarHTTPResponse.Status, &v)
newErr.model = v
return localVarReturnValue, localVarHTTPResponse, newErr
}
if localVarHTTPResponse.StatusCode == 500 {
var v string
err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type"))
if err != nil {
newErr.error = err.Error()
return localVarReturnValue, localVarHTTPResponse, newErr
}
newErr.error = formatErrorMessage(localVarHTTPResponse.Status, &v)
newErr.model = v
}
return localVarReturnValue, localVarHTTPResponse, newErr
}
err = a.client.decode(&localVarReturnValue, localVarBody, localVarHTTPResponse.Header.Get("Content-Type"))
if err != nil {
newErr := &GenericOpenAPIError{
body: localVarBody,
error: err.Error(),
}
return localVarReturnValue, localVarHTTPResponse, newErr
}
return localVarReturnValue, localVarHTTPResponse, nil
}
type ApiAdminListArcherJobsRequest struct {
ctx context.Context
ApiService *ArcherAdminAPIService
status *string
queue *string
q *string
page *int32
pageSize *int32
}
// Filter by status
func (r ApiAdminListArcherJobsRequest) Status(status string) ApiAdminListArcherJobsRequest {
r.status = &status
return r
}
// Filter by queue name / worker name
func (r ApiAdminListArcherJobsRequest) Queue(queue string) ApiAdminListArcherJobsRequest {
r.queue = &queue
return r
}
// Free-text search
func (r ApiAdminListArcherJobsRequest) Q(q string) ApiAdminListArcherJobsRequest {
r.q = &q
return r
}
// Page number
func (r ApiAdminListArcherJobsRequest) Page(page int32) ApiAdminListArcherJobsRequest {
r.page = &page
return r
}
// Items per page
func (r ApiAdminListArcherJobsRequest) PageSize(pageSize int32) ApiAdminListArcherJobsRequest {
r.pageSize = &pageSize
return r
}
func (r ApiAdminListArcherJobsRequest) Execute() (*DtoPageJob, *http.Response, error) {
return r.ApiService.AdminListArcherJobsExecute(r)
}
/*
AdminListArcherJobs List Archer jobs (admin)
Paginated background jobs with optional filters. Search `q` may match id, type, error, payload (implementation-dependent).
@param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background().
@return ApiAdminListArcherJobsRequest
*/
func (a *ArcherAdminAPIService) AdminListArcherJobs(ctx context.Context) ApiAdminListArcherJobsRequest {
return ApiAdminListArcherJobsRequest{
ApiService: a,
ctx: ctx,
}
}
// Execute executes the request
//
// @return DtoPageJob
func (a *ArcherAdminAPIService) AdminListArcherJobsExecute(r ApiAdminListArcherJobsRequest) (*DtoPageJob, *http.Response, error) {
var (
localVarHTTPMethod = http.MethodGet
localVarPostBody interface{}
formFiles []formFile
localVarReturnValue *DtoPageJob
)
localBasePath, err := a.client.cfg.ServerURLWithContext(r.ctx, "ArcherAdminAPIService.AdminListArcherJobs")
if err != nil {
return localVarReturnValue, nil, &GenericOpenAPIError{error: err.Error()}
}
localVarPath := localBasePath + "/admin/archer/jobs"
localVarHeaderParams := make(map[string]string)
localVarQueryParams := url.Values{}
localVarFormParams := url.Values{}
if r.status != nil {
parameterAddToHeaderOrQuery(localVarQueryParams, "status", r.status, "", "")
}
if r.queue != nil {
parameterAddToHeaderOrQuery(localVarQueryParams, "queue", r.queue, "", "")
}
if r.q != nil {
parameterAddToHeaderOrQuery(localVarQueryParams, "q", r.q, "", "")
}
if r.page != nil {
parameterAddToHeaderOrQuery(localVarQueryParams, "page", r.page, "", "")
} else {
var defaultValue int32 = 1
parameterAddToHeaderOrQuery(localVarQueryParams, "page", defaultValue, "", "")
r.page = &defaultValue
}
if r.pageSize != nil {
parameterAddToHeaderOrQuery(localVarQueryParams, "page_size", r.pageSize, "", "")
} else {
var defaultValue int32 = 25
parameterAddToHeaderOrQuery(localVarQueryParams, "page_size", defaultValue, "", "")
r.pageSize = &defaultValue
}
// to determine the Content-Type header
localVarHTTPContentTypes := []string{}
// set Content-Type header
localVarHTTPContentType := selectHeaderContentType(localVarHTTPContentTypes)
if localVarHTTPContentType != "" {
localVarHeaderParams["Content-Type"] = localVarHTTPContentType
}
// to determine the Accept header
localVarHTTPHeaderAccepts := []string{"application/json"}
// set Accept header
localVarHTTPHeaderAccept := selectHeaderAccept(localVarHTTPHeaderAccepts)
if localVarHTTPHeaderAccept != "" {
localVarHeaderParams["Accept"] = localVarHTTPHeaderAccept
}
if r.ctx != nil {
// API Key Authentication
if auth, ok := r.ctx.Value(ContextAPIKeys).(map[string]APIKey); ok {
if apiKey, ok := auth["BearerAuth"]; ok {
var key string
if apiKey.Prefix != "" {
key = apiKey.Prefix + " " + apiKey.Key
} else {
key = apiKey.Key
}
localVarHeaderParams["Authorization"] = key
}
}
}
req, err := a.client.prepareRequest(r.ctx, localVarPath, localVarHTTPMethod, localVarPostBody, localVarHeaderParams, localVarQueryParams, localVarFormParams, formFiles)
if err != nil {
return localVarReturnValue, nil, err
}
localVarHTTPResponse, err := a.client.callAPI(req)
if err != nil || localVarHTTPResponse == nil {
return localVarReturnValue, localVarHTTPResponse, err
}
localVarBody, err := io.ReadAll(localVarHTTPResponse.Body)
localVarHTTPResponse.Body.Close()
localVarHTTPResponse.Body = io.NopCloser(bytes.NewBuffer(localVarBody))
if err != nil {
return localVarReturnValue, localVarHTTPResponse, err
}
if localVarHTTPResponse.StatusCode >= 300 {
newErr := &GenericOpenAPIError{
body: localVarBody,
error: localVarHTTPResponse.Status,
}
if localVarHTTPResponse.StatusCode == 401 {
var v string
err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type"))
if err != nil {
newErr.error = err.Error()
return localVarReturnValue, localVarHTTPResponse, newErr
}
newErr.error = formatErrorMessage(localVarHTTPResponse.Status, &v)
newErr.model = v
return localVarReturnValue, localVarHTTPResponse, newErr
}
if localVarHTTPResponse.StatusCode == 403 {
var v string
err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type"))
if err != nil {
newErr.error = err.Error()
return localVarReturnValue, localVarHTTPResponse, newErr
}
newErr.error = formatErrorMessage(localVarHTTPResponse.Status, &v)
newErr.model = v
return localVarReturnValue, localVarHTTPResponse, newErr
}
if localVarHTTPResponse.StatusCode == 500 {
var v string
err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type"))
if err != nil {
newErr.error = err.Error()
return localVarReturnValue, localVarHTTPResponse, newErr
}
newErr.error = formatErrorMessage(localVarHTTPResponse.Status, &v)
newErr.model = v
}
return localVarReturnValue, localVarHTTPResponse, newErr
}
err = a.client.decode(&localVarReturnValue, localVarBody, localVarHTTPResponse.Header.Get("Content-Type"))
if err != nil {
newErr := &GenericOpenAPIError{
body: localVarBody,
error: err.Error(),
}
return localVarReturnValue, localVarHTTPResponse, newErr
}
return localVarReturnValue, localVarHTTPResponse, nil
}
type ApiAdminListArcherQueuesRequest struct {
ctx context.Context
ApiService *ArcherAdminAPIService
}
func (r ApiAdminListArcherQueuesRequest) Execute() ([]DtoQueueInfo, *http.Response, error) {
return r.ApiService.AdminListArcherQueuesExecute(r)
}
/*
AdminListArcherQueues List Archer queues (admin)
Summary metrics per queue (pending, running, failed, scheduled).
@param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background().
@return ApiAdminListArcherQueuesRequest
*/
func (a *ArcherAdminAPIService) AdminListArcherQueues(ctx context.Context) ApiAdminListArcherQueuesRequest {
return ApiAdminListArcherQueuesRequest{
ApiService: a,
ctx: ctx,
}
}
// Execute executes the request
//
// @return []DtoQueueInfo
func (a *ArcherAdminAPIService) AdminListArcherQueuesExecute(r ApiAdminListArcherQueuesRequest) ([]DtoQueueInfo, *http.Response, error) {
var (
localVarHTTPMethod = http.MethodGet
localVarPostBody interface{}
formFiles []formFile
localVarReturnValue []DtoQueueInfo
)
localBasePath, err := a.client.cfg.ServerURLWithContext(r.ctx, "ArcherAdminAPIService.AdminListArcherQueues")
if err != nil {
return localVarReturnValue, nil, &GenericOpenAPIError{error: err.Error()}
}
localVarPath := localBasePath + "/admin/archer/queues"
localVarHeaderParams := make(map[string]string)
localVarQueryParams := url.Values{}
localVarFormParams := url.Values{}
// to determine the Content-Type header
localVarHTTPContentTypes := []string{}
// set Content-Type header
localVarHTTPContentType := selectHeaderContentType(localVarHTTPContentTypes)
if localVarHTTPContentType != "" {
localVarHeaderParams["Content-Type"] = localVarHTTPContentType
}
// to determine the Accept header
localVarHTTPHeaderAccepts := []string{"application/json"}
// set Accept header
localVarHTTPHeaderAccept := selectHeaderAccept(localVarHTTPHeaderAccepts)
if localVarHTTPHeaderAccept != "" {
localVarHeaderParams["Accept"] = localVarHTTPHeaderAccept
}
if r.ctx != nil {
// API Key Authentication
if auth, ok := r.ctx.Value(ContextAPIKeys).(map[string]APIKey); ok {
if apiKey, ok := auth["BearerAuth"]; ok {
var key string
if apiKey.Prefix != "" {
key = apiKey.Prefix + " " + apiKey.Key
} else {
key = apiKey.Key
}
localVarHeaderParams["Authorization"] = key
}
}
}
req, err := a.client.prepareRequest(r.ctx, localVarPath, localVarHTTPMethod, localVarPostBody, localVarHeaderParams, localVarQueryParams, localVarFormParams, formFiles)
if err != nil {
return localVarReturnValue, nil, err
}
localVarHTTPResponse, err := a.client.callAPI(req)
if err != nil || localVarHTTPResponse == nil {
return localVarReturnValue, localVarHTTPResponse, err
}
localVarBody, err := io.ReadAll(localVarHTTPResponse.Body)
localVarHTTPResponse.Body.Close()
localVarHTTPResponse.Body = io.NopCloser(bytes.NewBuffer(localVarBody))
if err != nil {
return localVarReturnValue, localVarHTTPResponse, err
}
if localVarHTTPResponse.StatusCode >= 300 {
newErr := &GenericOpenAPIError{
body: localVarBody,
error: localVarHTTPResponse.Status,
}
if localVarHTTPResponse.StatusCode == 401 {
var v string
err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type"))
if err != nil {
newErr.error = err.Error()
return localVarReturnValue, localVarHTTPResponse, newErr
}
newErr.error = formatErrorMessage(localVarHTTPResponse.Status, &v)
newErr.model = v
return localVarReturnValue, localVarHTTPResponse, newErr
}
if localVarHTTPResponse.StatusCode == 403 {
var v string
err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type"))
if err != nil {
newErr.error = err.Error()
return localVarReturnValue, localVarHTTPResponse, newErr
}
newErr.error = formatErrorMessage(localVarHTTPResponse.Status, &v)
newErr.model = v
return localVarReturnValue, localVarHTTPResponse, newErr
}
if localVarHTTPResponse.StatusCode == 500 {
var v string
err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type"))
if err != nil {
newErr.error = err.Error()
return localVarReturnValue, localVarHTTPResponse, newErr
}
newErr.error = formatErrorMessage(localVarHTTPResponse.Status, &v)
newErr.model = v
}
return localVarReturnValue, localVarHTTPResponse, newErr
}
err = a.client.decode(&localVarReturnValue, localVarBody, localVarHTTPResponse.Header.Get("Content-Type"))
if err != nil {
newErr := &GenericOpenAPIError{
body: localVarBody,
error: err.Error(),
}
return localVarReturnValue, localVarHTTPResponse, newErr
}
return localVarReturnValue, localVarHTTPResponse, nil
}
type ApiAdminRetryArcherJobRequest struct {
ctx context.Context
ApiService *ArcherAdminAPIService
id string
}
func (r ApiAdminRetryArcherJobRequest) Execute() (*DtoJob, *http.Response, error) {
return r.ApiService.AdminRetryArcherJobExecute(r)
}
/*
AdminRetryArcherJob Retry a failed/canceled Archer job (admin)
Marks the job retriable (DB flip). Swap this for an Archer admin call if you expose one.
@param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background().
@param id Job ID
@return ApiAdminRetryArcherJobRequest
*/
func (a *ArcherAdminAPIService) AdminRetryArcherJob(ctx context.Context, id string) ApiAdminRetryArcherJobRequest {
return ApiAdminRetryArcherJobRequest{
ApiService: a,
ctx: ctx,
id: id,
}
}
// Execute executes the request
//
// @return DtoJob
func (a *ArcherAdminAPIService) AdminRetryArcherJobExecute(r ApiAdminRetryArcherJobRequest) (*DtoJob, *http.Response, error) {
var (
localVarHTTPMethod = http.MethodPost
localVarPostBody interface{}
formFiles []formFile
localVarReturnValue *DtoJob
)
localBasePath, err := a.client.cfg.ServerURLWithContext(r.ctx, "ArcherAdminAPIService.AdminRetryArcherJob")
if err != nil {
return localVarReturnValue, nil, &GenericOpenAPIError{error: err.Error()}
}
localVarPath := localBasePath + "/admin/archer/jobs/{id}/retry"
localVarPath = strings.Replace(localVarPath, "{"+"id"+"}", url.PathEscape(parameterValueToString(r.id, "id")), -1)
localVarHeaderParams := make(map[string]string)
localVarQueryParams := url.Values{}
localVarFormParams := url.Values{}
// to determine the Content-Type header
localVarHTTPContentTypes := []string{}
// set Content-Type header
localVarHTTPContentType := selectHeaderContentType(localVarHTTPContentTypes)
if localVarHTTPContentType != "" {
localVarHeaderParams["Content-Type"] = localVarHTTPContentType
}
// to determine the Accept header
localVarHTTPHeaderAccepts := []string{"application/json"}
// set Accept header
localVarHTTPHeaderAccept := selectHeaderAccept(localVarHTTPHeaderAccepts)
if localVarHTTPHeaderAccept != "" {
localVarHeaderParams["Accept"] = localVarHTTPHeaderAccept
}
if r.ctx != nil {
// API Key Authentication
if auth, ok := r.ctx.Value(ContextAPIKeys).(map[string]APIKey); ok {
if apiKey, ok := auth["BearerAuth"]; ok {
var key string
if apiKey.Prefix != "" {
key = apiKey.Prefix + " " + apiKey.Key
} else {
key = apiKey.Key
}
localVarHeaderParams["Authorization"] = key
}
}
}
req, err := a.client.prepareRequest(r.ctx, localVarPath, localVarHTTPMethod, localVarPostBody, localVarHeaderParams, localVarQueryParams, localVarFormParams, formFiles)
if err != nil {
return localVarReturnValue, nil, err
}
localVarHTTPResponse, err := a.client.callAPI(req)
if err != nil || localVarHTTPResponse == nil {
return localVarReturnValue, localVarHTTPResponse, err
}
localVarBody, err := io.ReadAll(localVarHTTPResponse.Body)
localVarHTTPResponse.Body.Close()
localVarHTTPResponse.Body = io.NopCloser(bytes.NewBuffer(localVarBody))
if err != nil {
return localVarReturnValue, localVarHTTPResponse, err
}
if localVarHTTPResponse.StatusCode >= 300 {
newErr := &GenericOpenAPIError{
body: localVarBody,
error: localVarHTTPResponse.Status,
}
if localVarHTTPResponse.StatusCode == 400 {
var v string
err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type"))
if err != nil {
newErr.error = err.Error()
return localVarReturnValue, localVarHTTPResponse, newErr
}
newErr.error = formatErrorMessage(localVarHTTPResponse.Status, &v)
newErr.model = v
return localVarReturnValue, localVarHTTPResponse, newErr
}
if localVarHTTPResponse.StatusCode == 401 {
var v string
err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type"))
if err != nil {
newErr.error = err.Error()
return localVarReturnValue, localVarHTTPResponse, newErr
}
newErr.error = formatErrorMessage(localVarHTTPResponse.Status, &v)
newErr.model = v
return localVarReturnValue, localVarHTTPResponse, newErr
}
if localVarHTTPResponse.StatusCode == 403 {
var v string
err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type"))
if err != nil {
newErr.error = err.Error()
return localVarReturnValue, localVarHTTPResponse, newErr
}
newErr.error = formatErrorMessage(localVarHTTPResponse.Status, &v)
newErr.model = v
return localVarReturnValue, localVarHTTPResponse, newErr
}
if localVarHTTPResponse.StatusCode == 404 {
var v string
err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type"))
if err != nil {
newErr.error = err.Error()
return localVarReturnValue, localVarHTTPResponse, newErr
}
newErr.error = formatErrorMessage(localVarHTTPResponse.Status, &v)
newErr.model = v
}
return localVarReturnValue, localVarHTTPResponse, newErr
}
err = a.client.decode(&localVarReturnValue, localVarBody, localVarHTTPResponse.Header.Get("Content-Type"))
if err != nil {
newErr := &GenericOpenAPIError{
body: localVarBody,
error: err.Error(),
}
return localVarReturnValue, localVarHTTPResponse, newErr
}
return localVarReturnValue, localVarHTTPResponse, nil
}

View File

@@ -1,537 +0,0 @@
/*
AutoGlue API
API for managing K3s clusters across cloud providers
API version: 1.0
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.
package autoglue
import (
"bytes"
"context"
"io"
"net/http"
"net/url"
"strings"
)
// AuthAPIService AuthAPI service
type AuthAPIService service
type ApiAuthCallbackRequest struct {
ctx context.Context
ApiService *AuthAPIService
provider string
}
func (r ApiAuthCallbackRequest) Execute() (*DtoTokenPair, *http.Response, error) {
return r.ApiService.AuthCallbackExecute(r)
}
/*
AuthCallback Handle social login callback
@param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background().
@param provider google|github
@return ApiAuthCallbackRequest
*/
func (a *AuthAPIService) AuthCallback(ctx context.Context, provider string) ApiAuthCallbackRequest {
return ApiAuthCallbackRequest{
ApiService: a,
ctx: ctx,
provider: provider,
}
}
// Execute executes the request
//
// @return DtoTokenPair
func (a *AuthAPIService) AuthCallbackExecute(r ApiAuthCallbackRequest) (*DtoTokenPair, *http.Response, error) {
var (
localVarHTTPMethod = http.MethodGet
localVarPostBody interface{}
formFiles []formFile
localVarReturnValue *DtoTokenPair
)
localBasePath, err := a.client.cfg.ServerURLWithContext(r.ctx, "AuthAPIService.AuthCallback")
if err != nil {
return localVarReturnValue, nil, &GenericOpenAPIError{error: err.Error()}
}
localVarPath := localBasePath + "/auth/{provider}/callback"
localVarPath = strings.Replace(localVarPath, "{"+"provider"+"}", url.PathEscape(parameterValueToString(r.provider, "provider")), -1)
localVarHeaderParams := make(map[string]string)
localVarQueryParams := url.Values{}
localVarFormParams := url.Values{}
// to determine the Content-Type header
localVarHTTPContentTypes := []string{}
// set Content-Type header
localVarHTTPContentType := selectHeaderContentType(localVarHTTPContentTypes)
if localVarHTTPContentType != "" {
localVarHeaderParams["Content-Type"] = localVarHTTPContentType
}
// to determine the Accept header
localVarHTTPHeaderAccepts := []string{"application/json"}
// set Accept header
localVarHTTPHeaderAccept := selectHeaderAccept(localVarHTTPHeaderAccepts)
if localVarHTTPHeaderAccept != "" {
localVarHeaderParams["Accept"] = localVarHTTPHeaderAccept
}
req, err := a.client.prepareRequest(r.ctx, localVarPath, localVarHTTPMethod, localVarPostBody, localVarHeaderParams, localVarQueryParams, localVarFormParams, formFiles)
if err != nil {
return localVarReturnValue, nil, err
}
localVarHTTPResponse, err := a.client.callAPI(req)
if err != nil || localVarHTTPResponse == nil {
return localVarReturnValue, localVarHTTPResponse, err
}
localVarBody, err := io.ReadAll(localVarHTTPResponse.Body)
localVarHTTPResponse.Body.Close()
localVarHTTPResponse.Body = io.NopCloser(bytes.NewBuffer(localVarBody))
if err != nil {
return localVarReturnValue, localVarHTTPResponse, err
}
if localVarHTTPResponse.StatusCode >= 300 {
newErr := &GenericOpenAPIError{
body: localVarBody,
error: localVarHTTPResponse.Status,
}
return localVarReturnValue, localVarHTTPResponse, newErr
}
err = a.client.decode(&localVarReturnValue, localVarBody, localVarHTTPResponse.Header.Get("Content-Type"))
if err != nil {
newErr := &GenericOpenAPIError{
body: localVarBody,
error: err.Error(),
}
return localVarReturnValue, localVarHTTPResponse, newErr
}
return localVarReturnValue, localVarHTTPResponse, nil
}
type ApiAuthStartRequest struct {
ctx context.Context
ApiService *AuthAPIService
provider string
}
func (r ApiAuthStartRequest) Execute() (*DtoAuthStartResponse, *http.Response, error) {
return r.ApiService.AuthStartExecute(r)
}
/*
AuthStart Begin social login
Returns provider authorization URL for the frontend to redirect
@param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background().
@param provider google|github
@return ApiAuthStartRequest
*/
func (a *AuthAPIService) AuthStart(ctx context.Context, provider string) ApiAuthStartRequest {
return ApiAuthStartRequest{
ApiService: a,
ctx: ctx,
provider: provider,
}
}
// Execute executes the request
//
// @return DtoAuthStartResponse
func (a *AuthAPIService) AuthStartExecute(r ApiAuthStartRequest) (*DtoAuthStartResponse, *http.Response, error) {
var (
localVarHTTPMethod = http.MethodPost
localVarPostBody interface{}
formFiles []formFile
localVarReturnValue *DtoAuthStartResponse
)
localBasePath, err := a.client.cfg.ServerURLWithContext(r.ctx, "AuthAPIService.AuthStart")
if err != nil {
return localVarReturnValue, nil, &GenericOpenAPIError{error: err.Error()}
}
localVarPath := localBasePath + "/auth/{provider}/start"
localVarPath = strings.Replace(localVarPath, "{"+"provider"+"}", url.PathEscape(parameterValueToString(r.provider, "provider")), -1)
localVarHeaderParams := make(map[string]string)
localVarQueryParams := url.Values{}
localVarFormParams := url.Values{}
// to determine the Content-Type header
localVarHTTPContentTypes := []string{}
// set Content-Type header
localVarHTTPContentType := selectHeaderContentType(localVarHTTPContentTypes)
if localVarHTTPContentType != "" {
localVarHeaderParams["Content-Type"] = localVarHTTPContentType
}
// to determine the Accept header
localVarHTTPHeaderAccepts := []string{"application/json"}
// set Accept header
localVarHTTPHeaderAccept := selectHeaderAccept(localVarHTTPHeaderAccepts)
if localVarHTTPHeaderAccept != "" {
localVarHeaderParams["Accept"] = localVarHTTPHeaderAccept
}
req, err := a.client.prepareRequest(r.ctx, localVarPath, localVarHTTPMethod, localVarPostBody, localVarHeaderParams, localVarQueryParams, localVarFormParams, formFiles)
if err != nil {
return localVarReturnValue, nil, err
}
localVarHTTPResponse, err := a.client.callAPI(req)
if err != nil || localVarHTTPResponse == nil {
return localVarReturnValue, localVarHTTPResponse, err
}
localVarBody, err := io.ReadAll(localVarHTTPResponse.Body)
localVarHTTPResponse.Body.Close()
localVarHTTPResponse.Body = io.NopCloser(bytes.NewBuffer(localVarBody))
if err != nil {
return localVarReturnValue, localVarHTTPResponse, err
}
if localVarHTTPResponse.StatusCode >= 300 {
newErr := &GenericOpenAPIError{
body: localVarBody,
error: localVarHTTPResponse.Status,
}
return localVarReturnValue, localVarHTTPResponse, newErr
}
err = a.client.decode(&localVarReturnValue, localVarBody, localVarHTTPResponse.Header.Get("Content-Type"))
if err != nil {
newErr := &GenericOpenAPIError{
body: localVarBody,
error: err.Error(),
}
return localVarReturnValue, localVarHTTPResponse, newErr
}
return localVarReturnValue, localVarHTTPResponse, nil
}
type ApiGetJWKSRequest struct {
ctx context.Context
ApiService *AuthAPIService
}
func (r ApiGetJWKSRequest) Execute() (*DtoJWKS, *http.Response, error) {
return r.ApiService.GetJWKSExecute(r)
}
/*
GetJWKS Get JWKS
Returns the JSON Web Key Set for token verification
@param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background().
@return ApiGetJWKSRequest
*/
func (a *AuthAPIService) GetJWKS(ctx context.Context) ApiGetJWKSRequest {
return ApiGetJWKSRequest{
ApiService: a,
ctx: ctx,
}
}
// Execute executes the request
//
// @return DtoJWKS
func (a *AuthAPIService) GetJWKSExecute(r ApiGetJWKSRequest) (*DtoJWKS, *http.Response, error) {
var (
localVarHTTPMethod = http.MethodGet
localVarPostBody interface{}
formFiles []formFile
localVarReturnValue *DtoJWKS
)
localBasePath, err := a.client.cfg.ServerURLWithContext(r.ctx, "AuthAPIService.GetJWKS")
if err != nil {
return localVarReturnValue, nil, &GenericOpenAPIError{error: err.Error()}
}
localVarPath := localBasePath + "/.well-known/jwks.json"
localVarHeaderParams := make(map[string]string)
localVarQueryParams := url.Values{}
localVarFormParams := url.Values{}
// to determine the Content-Type header
localVarHTTPContentTypes := []string{}
// set Content-Type header
localVarHTTPContentType := selectHeaderContentType(localVarHTTPContentTypes)
if localVarHTTPContentType != "" {
localVarHeaderParams["Content-Type"] = localVarHTTPContentType
}
// to determine the Accept header
localVarHTTPHeaderAccepts := []string{"application/json"}
// set Accept header
localVarHTTPHeaderAccept := selectHeaderAccept(localVarHTTPHeaderAccepts)
if localVarHTTPHeaderAccept != "" {
localVarHeaderParams["Accept"] = localVarHTTPHeaderAccept
}
req, err := a.client.prepareRequest(r.ctx, localVarPath, localVarHTTPMethod, localVarPostBody, localVarHeaderParams, localVarQueryParams, localVarFormParams, formFiles)
if err != nil {
return localVarReturnValue, nil, err
}
localVarHTTPResponse, err := a.client.callAPI(req)
if err != nil || localVarHTTPResponse == nil {
return localVarReturnValue, localVarHTTPResponse, err
}
localVarBody, err := io.ReadAll(localVarHTTPResponse.Body)
localVarHTTPResponse.Body.Close()
localVarHTTPResponse.Body = io.NopCloser(bytes.NewBuffer(localVarBody))
if err != nil {
return localVarReturnValue, localVarHTTPResponse, err
}
if localVarHTTPResponse.StatusCode >= 300 {
newErr := &GenericOpenAPIError{
body: localVarBody,
error: localVarHTTPResponse.Status,
}
return localVarReturnValue, localVarHTTPResponse, newErr
}
err = a.client.decode(&localVarReturnValue, localVarBody, localVarHTTPResponse.Header.Get("Content-Type"))
if err != nil {
newErr := &GenericOpenAPIError{
body: localVarBody,
error: err.Error(),
}
return localVarReturnValue, localVarHTTPResponse, newErr
}
return localVarReturnValue, localVarHTTPResponse, nil
}
type ApiLogoutRequest struct {
ctx context.Context
ApiService *AuthAPIService
body *DtoLogoutRequest
}
// Refresh token
func (r ApiLogoutRequest) Body(body DtoLogoutRequest) ApiLogoutRequest {
r.body = &body
return r
}
func (r ApiLogoutRequest) Execute() (*http.Response, error) {
return r.ApiService.LogoutExecute(r)
}
/*
Logout Revoke refresh token family (logout everywhere)
@param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background().
@return ApiLogoutRequest
*/
func (a *AuthAPIService) Logout(ctx context.Context) ApiLogoutRequest {
return ApiLogoutRequest{
ApiService: a,
ctx: ctx,
}
}
// Execute executes the request
func (a *AuthAPIService) LogoutExecute(r ApiLogoutRequest) (*http.Response, error) {
var (
localVarHTTPMethod = http.MethodPost
localVarPostBody interface{}
formFiles []formFile
)
localBasePath, err := a.client.cfg.ServerURLWithContext(r.ctx, "AuthAPIService.Logout")
if err != nil {
return nil, &GenericOpenAPIError{error: err.Error()}
}
localVarPath := localBasePath + "/auth/logout"
localVarHeaderParams := make(map[string]string)
localVarQueryParams := url.Values{}
localVarFormParams := url.Values{}
if r.body == nil {
return nil, reportError("body is required and must be specified")
}
// to determine the Content-Type header
localVarHTTPContentTypes := []string{"application/json"}
// set Content-Type header
localVarHTTPContentType := selectHeaderContentType(localVarHTTPContentTypes)
if localVarHTTPContentType != "" {
localVarHeaderParams["Content-Type"] = localVarHTTPContentType
}
// to determine the Accept header
localVarHTTPHeaderAccepts := []string{}
// set Accept header
localVarHTTPHeaderAccept := selectHeaderAccept(localVarHTTPHeaderAccepts)
if localVarHTTPHeaderAccept != "" {
localVarHeaderParams["Accept"] = localVarHTTPHeaderAccept
}
// body params
localVarPostBody = r.body
req, err := a.client.prepareRequest(r.ctx, localVarPath, localVarHTTPMethod, localVarPostBody, localVarHeaderParams, localVarQueryParams, localVarFormParams, formFiles)
if err != nil {
return nil, err
}
localVarHTTPResponse, err := a.client.callAPI(req)
if err != nil || localVarHTTPResponse == nil {
return localVarHTTPResponse, err
}
localVarBody, err := io.ReadAll(localVarHTTPResponse.Body)
localVarHTTPResponse.Body.Close()
localVarHTTPResponse.Body = io.NopCloser(bytes.NewBuffer(localVarBody))
if err != nil {
return localVarHTTPResponse, err
}
if localVarHTTPResponse.StatusCode >= 300 {
newErr := &GenericOpenAPIError{
body: localVarBody,
error: localVarHTTPResponse.Status,
}
return localVarHTTPResponse, newErr
}
return localVarHTTPResponse, nil
}
type ApiRefreshRequest struct {
ctx context.Context
ApiService *AuthAPIService
body *DtoRefreshRequest
}
// Refresh token
func (r ApiRefreshRequest) Body(body DtoRefreshRequest) ApiRefreshRequest {
r.body = &body
return r
}
func (r ApiRefreshRequest) Execute() (*DtoTokenPair, *http.Response, error) {
return r.ApiService.RefreshExecute(r)
}
/*
Refresh Rotate refresh token
@param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background().
@return ApiRefreshRequest
*/
func (a *AuthAPIService) Refresh(ctx context.Context) ApiRefreshRequest {
return ApiRefreshRequest{
ApiService: a,
ctx: ctx,
}
}
// Execute executes the request
//
// @return DtoTokenPair
func (a *AuthAPIService) RefreshExecute(r ApiRefreshRequest) (*DtoTokenPair, *http.Response, error) {
var (
localVarHTTPMethod = http.MethodPost
localVarPostBody interface{}
formFiles []formFile
localVarReturnValue *DtoTokenPair
)
localBasePath, err := a.client.cfg.ServerURLWithContext(r.ctx, "AuthAPIService.Refresh")
if err != nil {
return localVarReturnValue, nil, &GenericOpenAPIError{error: err.Error()}
}
localVarPath := localBasePath + "/auth/refresh"
localVarHeaderParams := make(map[string]string)
localVarQueryParams := url.Values{}
localVarFormParams := url.Values{}
if r.body == nil {
return localVarReturnValue, nil, reportError("body is required and must be specified")
}
// to determine the Content-Type header
localVarHTTPContentTypes := []string{"application/json"}
// set Content-Type header
localVarHTTPContentType := selectHeaderContentType(localVarHTTPContentTypes)
if localVarHTTPContentType != "" {
localVarHeaderParams["Content-Type"] = localVarHTTPContentType
}
// to determine the Accept header
localVarHTTPHeaderAccepts := []string{"application/json"}
// set Accept header
localVarHTTPHeaderAccept := selectHeaderAccept(localVarHTTPHeaderAccepts)
if localVarHTTPHeaderAccept != "" {
localVarHeaderParams["Accept"] = localVarHTTPHeaderAccept
}
// body params
localVarPostBody = r.body
req, err := a.client.prepareRequest(r.ctx, localVarPath, localVarHTTPMethod, localVarPostBody, localVarHeaderParams, localVarQueryParams, localVarFormParams, formFiles)
if err != nil {
return localVarReturnValue, nil, err
}
localVarHTTPResponse, err := a.client.callAPI(req)
if err != nil || localVarHTTPResponse == nil {
return localVarReturnValue, localVarHTTPResponse, err
}
localVarBody, err := io.ReadAll(localVarHTTPResponse.Body)
localVarHTTPResponse.Body.Close()
localVarHTTPResponse.Body = io.NopCloser(bytes.NewBuffer(localVarBody))
if err != nil {
return localVarReturnValue, localVarHTTPResponse, err
}
if localVarHTTPResponse.StatusCode >= 300 {
newErr := &GenericOpenAPIError{
body: localVarBody,
error: localVarHTTPResponse.Status,
}
return localVarReturnValue, localVarHTTPResponse, newErr
}
err = a.client.decode(&localVarReturnValue, localVarBody, localVarHTTPResponse.Header.Get("Content-Type"))
if err != nil {
newErr := &GenericOpenAPIError{
body: localVarBody,
error: err.Error(),
}
return localVarReturnValue, localVarHTTPResponse, newErr
}
return localVarReturnValue, localVarHTTPResponse, nil
}

View File

@@ -1,122 +0,0 @@
/*
AutoGlue API
API for managing K3s clusters across cloud providers
API version: 1.0
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.
package autoglue
import (
"bytes"
"context"
"io"
"net/http"
"net/url"
)
// HealthAPIService HealthAPI service
type HealthAPIService service
type ApiHealthCheckOperationIdRequest struct {
ctx context.Context
ApiService *HealthAPIService
}
func (r ApiHealthCheckOperationIdRequest) Execute() (*HandlersHealthStatus, *http.Response, error) {
return r.ApiService.HealthCheckOperationIdExecute(r)
}
/*
HealthCheckOperationId Basic health check
Returns 200 OK when the service is up
@param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background().
@return ApiHealthCheckOperationIdRequest
*/
func (a *HealthAPIService) HealthCheckOperationId(ctx context.Context) ApiHealthCheckOperationIdRequest {
return ApiHealthCheckOperationIdRequest{
ApiService: a,
ctx: ctx,
}
}
// Execute executes the request
//
// @return HandlersHealthStatus
func (a *HealthAPIService) HealthCheckOperationIdExecute(r ApiHealthCheckOperationIdRequest) (*HandlersHealthStatus, *http.Response, error) {
var (
localVarHTTPMethod = http.MethodGet
localVarPostBody interface{}
formFiles []formFile
localVarReturnValue *HandlersHealthStatus
)
localBasePath, err := a.client.cfg.ServerURLWithContext(r.ctx, "HealthAPIService.HealthCheckOperationId")
if err != nil {
return localVarReturnValue, nil, &GenericOpenAPIError{error: err.Error()}
}
localVarPath := localBasePath + "/healthz"
localVarHeaderParams := make(map[string]string)
localVarQueryParams := url.Values{}
localVarFormParams := url.Values{}
// to determine the Content-Type header
localVarHTTPContentTypes := []string{}
// set Content-Type header
localVarHTTPContentType := selectHeaderContentType(localVarHTTPContentTypes)
if localVarHTTPContentType != "" {
localVarHeaderParams["Content-Type"] = localVarHTTPContentType
}
// to determine the Accept header
localVarHTTPHeaderAccepts := []string{"application/json"}
// set Accept header
localVarHTTPHeaderAccept := selectHeaderAccept(localVarHTTPHeaderAccepts)
if localVarHTTPHeaderAccept != "" {
localVarHeaderParams["Accept"] = localVarHTTPHeaderAccept
}
req, err := a.client.prepareRequest(r.ctx, localVarPath, localVarHTTPMethod, localVarPostBody, localVarHeaderParams, localVarQueryParams, localVarFormParams, formFiles)
if err != nil {
return localVarReturnValue, nil, err
}
localVarHTTPResponse, err := a.client.callAPI(req)
if err != nil || localVarHTTPResponse == nil {
return localVarReturnValue, localVarHTTPResponse, err
}
localVarBody, err := io.ReadAll(localVarHTTPResponse.Body)
localVarHTTPResponse.Body.Close()
localVarHTTPResponse.Body = io.NopCloser(bytes.NewBuffer(localVarBody))
if err != nil {
return localVarReturnValue, localVarHTTPResponse, err
}
if localVarHTTPResponse.StatusCode >= 300 {
newErr := &GenericOpenAPIError{
body: localVarBody,
error: localVarHTTPResponse.Status,
}
return localVarReturnValue, localVarHTTPResponse, newErr
}
err = a.client.decode(&localVarReturnValue, localVarBody, localVarHTTPResponse.Header.Get("Content-Type"))
if err != nil {
newErr := &GenericOpenAPIError{
body: localVarBody,
error: err.Error(),
}
return localVarReturnValue, localVarHTTPResponse, newErr
}
return localVarReturnValue, localVarHTTPResponse, nil
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,286 +0,0 @@
/*
AutoGlue API
API for managing K3s clusters across cloud providers
API version: 1.0
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.
package autoglue
import (
"bytes"
"context"
"io"
"net/http"
"net/url"
)
// MeAPIService MeAPI service
type MeAPIService service
type ApiGetMeRequest struct {
ctx context.Context
ApiService *MeAPIService
}
func (r ApiGetMeRequest) Execute() (*HandlersMeResponse, *http.Response, error) {
return r.ApiService.GetMeExecute(r)
}
/*
GetMe Get current user profile
@param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background().
@return ApiGetMeRequest
*/
func (a *MeAPIService) GetMe(ctx context.Context) ApiGetMeRequest {
return ApiGetMeRequest{
ApiService: a,
ctx: ctx,
}
}
// Execute executes the request
//
// @return HandlersMeResponse
func (a *MeAPIService) GetMeExecute(r ApiGetMeRequest) (*HandlersMeResponse, *http.Response, error) {
var (
localVarHTTPMethod = http.MethodGet
localVarPostBody interface{}
formFiles []formFile
localVarReturnValue *HandlersMeResponse
)
localBasePath, err := a.client.cfg.ServerURLWithContext(r.ctx, "MeAPIService.GetMe")
if err != nil {
return localVarReturnValue, nil, &GenericOpenAPIError{error: err.Error()}
}
localVarPath := localBasePath + "/me"
localVarHeaderParams := make(map[string]string)
localVarQueryParams := url.Values{}
localVarFormParams := url.Values{}
// to determine the Content-Type header
localVarHTTPContentTypes := []string{}
// set Content-Type header
localVarHTTPContentType := selectHeaderContentType(localVarHTTPContentTypes)
if localVarHTTPContentType != "" {
localVarHeaderParams["Content-Type"] = localVarHTTPContentType
}
// to determine the Accept header
localVarHTTPHeaderAccepts := []string{"application/json"}
// set Accept header
localVarHTTPHeaderAccept := selectHeaderAccept(localVarHTTPHeaderAccepts)
if localVarHTTPHeaderAccept != "" {
localVarHeaderParams["Accept"] = localVarHTTPHeaderAccept
}
if r.ctx != nil {
// API Key Authentication
if auth, ok := r.ctx.Value(ContextAPIKeys).(map[string]APIKey); ok {
if apiKey, ok := auth["ApiKeyAuth"]; ok {
var key string
if apiKey.Prefix != "" {
key = apiKey.Prefix + " " + apiKey.Key
} else {
key = apiKey.Key
}
localVarHeaderParams["X-API-KEY"] = key
}
}
}
if r.ctx != nil {
// API Key Authentication
if auth, ok := r.ctx.Value(ContextAPIKeys).(map[string]APIKey); ok {
if apiKey, ok := auth["BearerAuth"]; ok {
var key string
if apiKey.Prefix != "" {
key = apiKey.Prefix + " " + apiKey.Key
} else {
key = apiKey.Key
}
localVarHeaderParams["Authorization"] = key
}
}
}
req, err := a.client.prepareRequest(r.ctx, localVarPath, localVarHTTPMethod, localVarPostBody, localVarHeaderParams, localVarQueryParams, localVarFormParams, formFiles)
if err != nil {
return localVarReturnValue, nil, err
}
localVarHTTPResponse, err := a.client.callAPI(req)
if err != nil || localVarHTTPResponse == nil {
return localVarReturnValue, localVarHTTPResponse, err
}
localVarBody, err := io.ReadAll(localVarHTTPResponse.Body)
localVarHTTPResponse.Body.Close()
localVarHTTPResponse.Body = io.NopCloser(bytes.NewBuffer(localVarBody))
if err != nil {
return localVarReturnValue, localVarHTTPResponse, err
}
if localVarHTTPResponse.StatusCode >= 300 {
newErr := &GenericOpenAPIError{
body: localVarBody,
error: localVarHTTPResponse.Status,
}
return localVarReturnValue, localVarHTTPResponse, newErr
}
err = a.client.decode(&localVarReturnValue, localVarBody, localVarHTTPResponse.Header.Get("Content-Type"))
if err != nil {
newErr := &GenericOpenAPIError{
body: localVarBody,
error: err.Error(),
}
return localVarReturnValue, localVarHTTPResponse, newErr
}
return localVarReturnValue, localVarHTTPResponse, nil
}
type ApiUpdateMeRequest struct {
ctx context.Context
ApiService *MeAPIService
body *HandlersUpdateMeRequest
}
// Patch profile
func (r ApiUpdateMeRequest) Body(body HandlersUpdateMeRequest) ApiUpdateMeRequest {
r.body = &body
return r
}
func (r ApiUpdateMeRequest) Execute() (*ModelsUser, *http.Response, error) {
return r.ApiService.UpdateMeExecute(r)
}
/*
UpdateMe Update current user profile
@param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background().
@return ApiUpdateMeRequest
*/
func (a *MeAPIService) UpdateMe(ctx context.Context) ApiUpdateMeRequest {
return ApiUpdateMeRequest{
ApiService: a,
ctx: ctx,
}
}
// Execute executes the request
//
// @return ModelsUser
func (a *MeAPIService) UpdateMeExecute(r ApiUpdateMeRequest) (*ModelsUser, *http.Response, error) {
var (
localVarHTTPMethod = http.MethodPatch
localVarPostBody interface{}
formFiles []formFile
localVarReturnValue *ModelsUser
)
localBasePath, err := a.client.cfg.ServerURLWithContext(r.ctx, "MeAPIService.UpdateMe")
if err != nil {
return localVarReturnValue, nil, &GenericOpenAPIError{error: err.Error()}
}
localVarPath := localBasePath + "/me"
localVarHeaderParams := make(map[string]string)
localVarQueryParams := url.Values{}
localVarFormParams := url.Values{}
if r.body == nil {
return localVarReturnValue, nil, reportError("body is required and must be specified")
}
// to determine the Content-Type header
localVarHTTPContentTypes := []string{"application/json"}
// set Content-Type header
localVarHTTPContentType := selectHeaderContentType(localVarHTTPContentTypes)
if localVarHTTPContentType != "" {
localVarHeaderParams["Content-Type"] = localVarHTTPContentType
}
// to determine the Accept header
localVarHTTPHeaderAccepts := []string{"application/json"}
// set Accept header
localVarHTTPHeaderAccept := selectHeaderAccept(localVarHTTPHeaderAccepts)
if localVarHTTPHeaderAccept != "" {
localVarHeaderParams["Accept"] = localVarHTTPHeaderAccept
}
// body params
localVarPostBody = r.body
if r.ctx != nil {
// API Key Authentication
if auth, ok := r.ctx.Value(ContextAPIKeys).(map[string]APIKey); ok {
if apiKey, ok := auth["ApiKeyAuth"]; ok {
var key string
if apiKey.Prefix != "" {
key = apiKey.Prefix + " " + apiKey.Key
} else {
key = apiKey.Key
}
localVarHeaderParams["X-API-KEY"] = key
}
}
}
if r.ctx != nil {
// API Key Authentication
if auth, ok := r.ctx.Value(ContextAPIKeys).(map[string]APIKey); ok {
if apiKey, ok := auth["BearerAuth"]; ok {
var key string
if apiKey.Prefix != "" {
key = apiKey.Prefix + " " + apiKey.Key
} else {
key = apiKey.Key
}
localVarHeaderParams["Authorization"] = key
}
}
}
req, err := a.client.prepareRequest(r.ctx, localVarPath, localVarHTTPMethod, localVarPostBody, localVarHeaderParams, localVarQueryParams, localVarFormParams, formFiles)
if err != nil {
return localVarReturnValue, nil, err
}
localVarHTTPResponse, err := a.client.callAPI(req)
if err != nil || localVarHTTPResponse == nil {
return localVarReturnValue, localVarHTTPResponse, err
}
localVarBody, err := io.ReadAll(localVarHTTPResponse.Body)
localVarHTTPResponse.Body.Close()
localVarHTTPResponse.Body = io.NopCloser(bytes.NewBuffer(localVarBody))
if err != nil {
return localVarReturnValue, localVarHTTPResponse, err
}
if localVarHTTPResponse.StatusCode >= 300 {
newErr := &GenericOpenAPIError{
body: localVarBody,
error: localVarHTTPResponse.Status,
}
return localVarReturnValue, localVarHTTPResponse, newErr
}
err = a.client.decode(&localVarReturnValue, localVarBody, localVarHTTPResponse.Header.Get("Content-Type"))
if err != nil {
newErr := &GenericOpenAPIError{
body: localVarBody,
error: err.Error(),
}
return localVarReturnValue, localVarHTTPResponse, newErr
}
return localVarReturnValue, localVarHTTPResponse, nil
}

View File

@@ -1,393 +0,0 @@
/*
AutoGlue API
API for managing K3s clusters across cloud providers
API version: 1.0
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.
package autoglue
import (
"bytes"
"context"
"io"
"net/http"
"net/url"
"strings"
)
// MeAPIKeysAPIService MeAPIKeysAPI service
type MeAPIKeysAPIService service
type ApiCreateUserAPIKeyRequest struct {
ctx context.Context
ApiService *MeAPIKeysAPIService
body *HandlersCreateUserKeyRequest
}
// Key options
func (r ApiCreateUserAPIKeyRequest) Body(body HandlersCreateUserKeyRequest) ApiCreateUserAPIKeyRequest {
r.body = &body
return r
}
func (r ApiCreateUserAPIKeyRequest) Execute() (*HandlersUserAPIKeyOut, *http.Response, error) {
return r.ApiService.CreateUserAPIKeyExecute(r)
}
/*
CreateUserAPIKey Create a new user API key
Returns the plaintext key once. Store it securely on the client side.
@param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background().
@return ApiCreateUserAPIKeyRequest
*/
func (a *MeAPIKeysAPIService) CreateUserAPIKey(ctx context.Context) ApiCreateUserAPIKeyRequest {
return ApiCreateUserAPIKeyRequest{
ApiService: a,
ctx: ctx,
}
}
// Execute executes the request
//
// @return HandlersUserAPIKeyOut
func (a *MeAPIKeysAPIService) CreateUserAPIKeyExecute(r ApiCreateUserAPIKeyRequest) (*HandlersUserAPIKeyOut, *http.Response, error) {
var (
localVarHTTPMethod = http.MethodPost
localVarPostBody interface{}
formFiles []formFile
localVarReturnValue *HandlersUserAPIKeyOut
)
localBasePath, err := a.client.cfg.ServerURLWithContext(r.ctx, "MeAPIKeysAPIService.CreateUserAPIKey")
if err != nil {
return localVarReturnValue, nil, &GenericOpenAPIError{error: err.Error()}
}
localVarPath := localBasePath + "/me/api-keys"
localVarHeaderParams := make(map[string]string)
localVarQueryParams := url.Values{}
localVarFormParams := url.Values{}
if r.body == nil {
return localVarReturnValue, nil, reportError("body is required and must be specified")
}
// to determine the Content-Type header
localVarHTTPContentTypes := []string{"application/json"}
// set Content-Type header
localVarHTTPContentType := selectHeaderContentType(localVarHTTPContentTypes)
if localVarHTTPContentType != "" {
localVarHeaderParams["Content-Type"] = localVarHTTPContentType
}
// to determine the Accept header
localVarHTTPHeaderAccepts := []string{"application/json"}
// set Accept header
localVarHTTPHeaderAccept := selectHeaderAccept(localVarHTTPHeaderAccepts)
if localVarHTTPHeaderAccept != "" {
localVarHeaderParams["Accept"] = localVarHTTPHeaderAccept
}
// body params
localVarPostBody = r.body
if r.ctx != nil {
// API Key Authentication
if auth, ok := r.ctx.Value(ContextAPIKeys).(map[string]APIKey); ok {
if apiKey, ok := auth["ApiKeyAuth"]; ok {
var key string
if apiKey.Prefix != "" {
key = apiKey.Prefix + " " + apiKey.Key
} else {
key = apiKey.Key
}
localVarHeaderParams["X-API-KEY"] = key
}
}
}
if r.ctx != nil {
// API Key Authentication
if auth, ok := r.ctx.Value(ContextAPIKeys).(map[string]APIKey); ok {
if apiKey, ok := auth["BearerAuth"]; ok {
var key string
if apiKey.Prefix != "" {
key = apiKey.Prefix + " " + apiKey.Key
} else {
key = apiKey.Key
}
localVarHeaderParams["Authorization"] = key
}
}
}
req, err := a.client.prepareRequest(r.ctx, localVarPath, localVarHTTPMethod, localVarPostBody, localVarHeaderParams, localVarQueryParams, localVarFormParams, formFiles)
if err != nil {
return localVarReturnValue, nil, err
}
localVarHTTPResponse, err := a.client.callAPI(req)
if err != nil || localVarHTTPResponse == nil {
return localVarReturnValue, localVarHTTPResponse, err
}
localVarBody, err := io.ReadAll(localVarHTTPResponse.Body)
localVarHTTPResponse.Body.Close()
localVarHTTPResponse.Body = io.NopCloser(bytes.NewBuffer(localVarBody))
if err != nil {
return localVarReturnValue, localVarHTTPResponse, err
}
if localVarHTTPResponse.StatusCode >= 300 {
newErr := &GenericOpenAPIError{
body: localVarBody,
error: localVarHTTPResponse.Status,
}
return localVarReturnValue, localVarHTTPResponse, newErr
}
err = a.client.decode(&localVarReturnValue, localVarBody, localVarHTTPResponse.Header.Get("Content-Type"))
if err != nil {
newErr := &GenericOpenAPIError{
body: localVarBody,
error: err.Error(),
}
return localVarReturnValue, localVarHTTPResponse, newErr
}
return localVarReturnValue, localVarHTTPResponse, nil
}
type ApiDeleteUserAPIKeyRequest struct {
ctx context.Context
ApiService *MeAPIKeysAPIService
id string
}
func (r ApiDeleteUserAPIKeyRequest) Execute() (*http.Response, error) {
return r.ApiService.DeleteUserAPIKeyExecute(r)
}
/*
DeleteUserAPIKey Delete a user API key
@param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background().
@param id Key ID (UUID)
@return ApiDeleteUserAPIKeyRequest
*/
func (a *MeAPIKeysAPIService) DeleteUserAPIKey(ctx context.Context, id string) ApiDeleteUserAPIKeyRequest {
return ApiDeleteUserAPIKeyRequest{
ApiService: a,
ctx: ctx,
id: id,
}
}
// Execute executes the request
func (a *MeAPIKeysAPIService) DeleteUserAPIKeyExecute(r ApiDeleteUserAPIKeyRequest) (*http.Response, error) {
var (
localVarHTTPMethod = http.MethodDelete
localVarPostBody interface{}
formFiles []formFile
)
localBasePath, err := a.client.cfg.ServerURLWithContext(r.ctx, "MeAPIKeysAPIService.DeleteUserAPIKey")
if err != nil {
return nil, &GenericOpenAPIError{error: err.Error()}
}
localVarPath := localBasePath + "/me/api-keys/{id}"
localVarPath = strings.Replace(localVarPath, "{"+"id"+"}", url.PathEscape(parameterValueToString(r.id, "id")), -1)
localVarHeaderParams := make(map[string]string)
localVarQueryParams := url.Values{}
localVarFormParams := url.Values{}
// to determine the Content-Type header
localVarHTTPContentTypes := []string{}
// set Content-Type header
localVarHTTPContentType := selectHeaderContentType(localVarHTTPContentTypes)
if localVarHTTPContentType != "" {
localVarHeaderParams["Content-Type"] = localVarHTTPContentType
}
// to determine the Accept header
localVarHTTPHeaderAccepts := []string{}
// set Accept header
localVarHTTPHeaderAccept := selectHeaderAccept(localVarHTTPHeaderAccepts)
if localVarHTTPHeaderAccept != "" {
localVarHeaderParams["Accept"] = localVarHTTPHeaderAccept
}
if r.ctx != nil {
// API Key Authentication
if auth, ok := r.ctx.Value(ContextAPIKeys).(map[string]APIKey); ok {
if apiKey, ok := auth["BearerAuth"]; ok {
var key string
if apiKey.Prefix != "" {
key = apiKey.Prefix + " " + apiKey.Key
} else {
key = apiKey.Key
}
localVarHeaderParams["Authorization"] = key
}
}
}
req, err := a.client.prepareRequest(r.ctx, localVarPath, localVarHTTPMethod, localVarPostBody, localVarHeaderParams, localVarQueryParams, localVarFormParams, formFiles)
if err != nil {
return nil, err
}
localVarHTTPResponse, err := a.client.callAPI(req)
if err != nil || localVarHTTPResponse == nil {
return localVarHTTPResponse, err
}
localVarBody, err := io.ReadAll(localVarHTTPResponse.Body)
localVarHTTPResponse.Body.Close()
localVarHTTPResponse.Body = io.NopCloser(bytes.NewBuffer(localVarBody))
if err != nil {
return localVarHTTPResponse, err
}
if localVarHTTPResponse.StatusCode >= 300 {
newErr := &GenericOpenAPIError{
body: localVarBody,
error: localVarHTTPResponse.Status,
}
return localVarHTTPResponse, newErr
}
return localVarHTTPResponse, nil
}
type ApiListUserAPIKeysRequest struct {
ctx context.Context
ApiService *MeAPIKeysAPIService
}
func (r ApiListUserAPIKeysRequest) Execute() ([]HandlersUserAPIKeyOut, *http.Response, error) {
return r.ApiService.ListUserAPIKeysExecute(r)
}
/*
ListUserAPIKeys List my API keys
@param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background().
@return ApiListUserAPIKeysRequest
*/
func (a *MeAPIKeysAPIService) ListUserAPIKeys(ctx context.Context) ApiListUserAPIKeysRequest {
return ApiListUserAPIKeysRequest{
ApiService: a,
ctx: ctx,
}
}
// Execute executes the request
//
// @return []HandlersUserAPIKeyOut
func (a *MeAPIKeysAPIService) ListUserAPIKeysExecute(r ApiListUserAPIKeysRequest) ([]HandlersUserAPIKeyOut, *http.Response, error) {
var (
localVarHTTPMethod = http.MethodGet
localVarPostBody interface{}
formFiles []formFile
localVarReturnValue []HandlersUserAPIKeyOut
)
localBasePath, err := a.client.cfg.ServerURLWithContext(r.ctx, "MeAPIKeysAPIService.ListUserAPIKeys")
if err != nil {
return localVarReturnValue, nil, &GenericOpenAPIError{error: err.Error()}
}
localVarPath := localBasePath + "/me/api-keys"
localVarHeaderParams := make(map[string]string)
localVarQueryParams := url.Values{}
localVarFormParams := url.Values{}
// to determine the Content-Type header
localVarHTTPContentTypes := []string{}
// set Content-Type header
localVarHTTPContentType := selectHeaderContentType(localVarHTTPContentTypes)
if localVarHTTPContentType != "" {
localVarHeaderParams["Content-Type"] = localVarHTTPContentType
}
// to determine the Accept header
localVarHTTPHeaderAccepts := []string{"application/json"}
// set Accept header
localVarHTTPHeaderAccept := selectHeaderAccept(localVarHTTPHeaderAccepts)
if localVarHTTPHeaderAccept != "" {
localVarHeaderParams["Accept"] = localVarHTTPHeaderAccept
}
if r.ctx != nil {
// API Key Authentication
if auth, ok := r.ctx.Value(ContextAPIKeys).(map[string]APIKey); ok {
if apiKey, ok := auth["ApiKeyAuth"]; ok {
var key string
if apiKey.Prefix != "" {
key = apiKey.Prefix + " " + apiKey.Key
} else {
key = apiKey.Key
}
localVarHeaderParams["X-API-KEY"] = key
}
}
}
if r.ctx != nil {
// API Key Authentication
if auth, ok := r.ctx.Value(ContextAPIKeys).(map[string]APIKey); ok {
if apiKey, ok := auth["BearerAuth"]; ok {
var key string
if apiKey.Prefix != "" {
key = apiKey.Prefix + " " + apiKey.Key
} else {
key = apiKey.Key
}
localVarHeaderParams["Authorization"] = key
}
}
}
req, err := a.client.prepareRequest(r.ctx, localVarPath, localVarHTTPMethod, localVarPostBody, localVarHeaderParams, localVarQueryParams, localVarFormParams, formFiles)
if err != nil {
return localVarReturnValue, nil, err
}
localVarHTTPResponse, err := a.client.callAPI(req)
if err != nil || localVarHTTPResponse == nil {
return localVarReturnValue, localVarHTTPResponse, err
}
localVarBody, err := io.ReadAll(localVarHTTPResponse.Body)
localVarHTTPResponse.Body.Close()
localVarHTTPResponse.Body = io.NopCloser(bytes.NewBuffer(localVarBody))
if err != nil {
return localVarReturnValue, localVarHTTPResponse, err
}
if localVarHTTPResponse.StatusCode >= 300 {
newErr := &GenericOpenAPIError{
body: localVarBody,
error: localVarHTTPResponse.Status,
}
return localVarReturnValue, localVarHTTPResponse, newErr
}
err = a.client.decode(&localVarReturnValue, localVarBody, localVarHTTPResponse.Header.Get("Content-Type"))
if err != nil {
newErr := &GenericOpenAPIError{
body: localVarBody,
error: err.Error(),
}
return localVarReturnValue, localVarHTTPResponse, newErr
}
return localVarReturnValue, localVarHTTPResponse, nil
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More