Compare commits

...

52 Commits

Author SHA1 Message Date
allanice001
810218124e revert, but increase httprate 2026-01-06 10:11:42 +00:00
allanice001
5aff256377 fix import issue 2026-01-06 09:59:23 +00:00
Alanis
bdd6b61859 Remove rate limiting by IP from routes
Removed rate limiting middleware from the API routes.
2026-01-06 09:50:10 +00:00
public-glueops-renovatebot[bot]
42a86b22dd chore: lock file maintenance (#567)
Co-authored-by: public-glueops-renovatebot[bot] <186083205+public-glueops-renovatebot[bot]@users.noreply.github.com>
2026-01-05 10:45:19 +00:00
public-glueops-renovatebot[bot]
b8cb1e1a2a chore: lock file maintenance (#566)
Co-authored-by: public-glueops-renovatebot[bot] <186083205+public-glueops-renovatebot[bot]@users.noreply.github.com>
2026-01-05 10:11:13 +00:00
public-glueops-renovatebot[bot]
5a4ae19900 chore: lock file maintenance (#564)
Co-authored-by: public-glueops-renovatebot[bot] <186083205+public-glueops-renovatebot[bot]@users.noreply.github.com>
2026-01-04 10:51:50 +00:00
public-glueops-renovatebot[bot]
d9db293894 chore: lock file maintenance (#563)
Co-authored-by: public-glueops-renovatebot[bot] <186083205+public-glueops-renovatebot[bot]@users.noreply.github.com>
2026-01-04 08:27:53 +00:00
public-glueops-renovatebot[bot]
19d5ee7251 chore: lock file maintenance (#562)
Co-authored-by: public-glueops-renovatebot[bot] <186083205+public-glueops-renovatebot[bot]@users.noreply.github.com>
2026-01-04 04:12:39 +00:00
public-glueops-renovatebot[bot]
6b91a5760b chore: lock file maintenance (#560)
Co-authored-by: public-glueops-renovatebot[bot] <186083205+public-glueops-renovatebot[bot]@users.noreply.github.com>
2026-01-03 13:32:05 +00:00
public-glueops-renovatebot[bot]
bbd4c86013 chore: lock file maintenance (#559)
Co-authored-by: public-glueops-renovatebot[bot] <186083205+public-glueops-renovatebot[bot]@users.noreply.github.com>
2026-01-02 14:40:46 +00:00
public-glueops-renovatebot[bot]
99ebcb11a3 chore: lock file maintenance (#558)
Co-authored-by: public-glueops-renovatebot[bot] <186083205+public-glueops-renovatebot[bot]@users.noreply.github.com>
2026-01-02 13:44:37 +00:00
public-glueops-renovatebot[bot]
be1b35da3c feat: update typescript-eslint to 8.51.0 #minor (#540)
Co-authored-by: public-glueops-renovatebot[bot] <186083205+public-glueops-renovatebot[bot]@users.noreply.github.com>
2026-01-01 17:09:21 +00:00
public-glueops-renovatebot[bot]
a6a296f283 chore: lock file maintenance (#556)
Co-authored-by: public-glueops-renovatebot[bot] <186083205+public-glueops-renovatebot[bot]@users.noreply.github.com>
2025-12-31 23:27:07 +00:00
public-glueops-renovatebot[bot]
341ecf8b0a chore: lock file maintenance (#555)
Co-authored-by: public-glueops-renovatebot[bot] <186083205+public-glueops-renovatebot[bot]@users.noreply.github.com>
2025-12-31 19:37:14 +00:00
public-glueops-renovatebot[bot]
92998015ec chore: lock file maintenance (#554)
Co-authored-by: public-glueops-renovatebot[bot] <186083205+public-glueops-renovatebot[bot]@users.noreply.github.com>
2025-12-31 16:10:12 +00:00
public-glueops-renovatebot[bot]
9345c2761c chore: lock file maintenance (#553)
Co-authored-by: public-glueops-renovatebot[bot] <186083205+public-glueops-renovatebot[bot]@users.noreply.github.com>
2025-12-31 15:08:55 +00:00
public-glueops-renovatebot[bot]
6944e5d027 chore: lock file maintenance (#552)
Co-authored-by: public-glueops-renovatebot[bot] <186083205+public-glueops-renovatebot[bot]@users.noreply.github.com>
2025-12-31 06:14:14 +00:00
public-glueops-renovatebot[bot]
48b3bf5d3c chore: lock file maintenance (#551)
Co-authored-by: public-glueops-renovatebot[bot] <186083205+public-glueops-renovatebot[bot]@users.noreply.github.com>
2025-12-31 06:13:11 +00:00
public-glueops-renovatebot[bot]
4c595db85e chore: lock file maintenance (#550)
Co-authored-by: public-glueops-renovatebot[bot] <186083205+public-glueops-renovatebot[bot]@users.noreply.github.com>
2025-12-31 05:42:43 +00:00
allanice001
79f3259bd6 fix: ensure only a single instance is fired
Signed-off-by: allanice001 <allanice001@gmail.com>
2025-12-30 17:30:38 +00:00
allanice001
e0d163181a Merge branch 'main' of github.com:GlueOps/autoglue 2025-12-30 16:35:18 +00:00
allanice001
e8d568eba7 fix: ensure only a single instance is fired
Signed-off-by: allanice001 <allanice001@gmail.com>
2025-12-30 16:35:00 +00:00
public-glueops-renovatebot[bot]
c21a766dab chore: lock file maintenance (#548)
Co-authored-by: public-glueops-renovatebot[bot] <186083205+public-glueops-renovatebot[bot]@users.noreply.github.com>
2025-12-30 15:09:36 +00:00
public-glueops-renovatebot[bot]
495c1551b4 chore: lock file maintenance (#547)
Co-authored-by: public-glueops-renovatebot[bot] <186083205+public-glueops-renovatebot[bot]@users.noreply.github.com>
2025-12-30 08:45:47 +00:00
public-glueops-renovatebot[bot]
4a5c0df481 chore: lock file maintenance (#545)
Co-authored-by: public-glueops-renovatebot[bot] <186083205+public-glueops-renovatebot[bot]@users.noreply.github.com>
2025-12-29 22:09:22 +00:00
public-glueops-renovatebot[bot]
c92bba5518 chore: lock file maintenance (#544)
Co-authored-by: public-glueops-renovatebot[bot] <186083205+public-glueops-renovatebot[bot]@users.noreply.github.com>
2025-12-29 22:08:16 +00:00
public-glueops-renovatebot[bot]
823088e294 chore: lock file maintenance (#543)
Co-authored-by: public-glueops-renovatebot[bot] <186083205+public-glueops-renovatebot[bot]@users.noreply.github.com>
2025-12-29 19:52:02 +00:00
public-glueops-renovatebot[bot]
938689fda3 chore: lock file maintenance (#542)
Co-authored-by: public-glueops-renovatebot[bot] <186083205+public-glueops-renovatebot[bot]@users.noreply.github.com>
2025-12-29 18:46:56 +00:00
public-glueops-renovatebot[bot]
77332f208f chore: lock file maintenance (#541)
Co-authored-by: public-glueops-renovatebot[bot] <186083205+public-glueops-renovatebot[bot]@users.noreply.github.com>
2025-12-29 17:26:24 +00:00
public-glueops-renovatebot[bot]
711488492c breaking: the dependency actions/checkout has been updated to a new major version (v6), which may include breaking changes. #major (#536)
Co-authored-by: public-glueops-renovatebot[bot] <186083205+public-glueops-renovatebot[bot]@users.noreply.github.com>
2025-12-29 17:03:09 +00:00
public-glueops-renovatebot[bot]
27b89722a4 chore: lock file maintenance (#539)
Co-authored-by: public-glueops-renovatebot[bot] <186083205+public-glueops-renovatebot[bot]@users.noreply.github.com>
2025-12-29 16:27:51 +00:00
public-glueops-renovatebot[bot]
c8a537e30f chore(fallback): update postgres (#535)
Co-authored-by: public-glueops-renovatebot[bot] <186083205+public-glueops-renovatebot[bot]@users.noreply.github.com>
2025-12-29 12:34:44 +00:00
public-glueops-renovatebot[bot]
98de70b96b chore: lock file maintenance (#538)
Co-authored-by: public-glueops-renovatebot[bot] <186083205+public-glueops-renovatebot[bot]@users.noreply.github.com>
2025-12-29 12:34:10 +00:00
public-glueops-renovatebot[bot]
63c4574f9c chore(fallback): update axllent/mailpit (#534)
Co-authored-by: public-glueops-renovatebot[bot] <186083205+public-glueops-renovatebot[bot]@users.noreply.github.com>
2025-12-29 12:33:13 +00:00
public-glueops-renovatebot[bot]
5e85cad5b7 chore(pin): update typescript to #patch (#533)
Co-authored-by: public-glueops-renovatebot[bot] <186083205+public-glueops-renovatebot[bot]@users.noreply.github.com>
2025-12-29 12:32:35 +00:00
allanice001
53725bb834 chore: add org id to org table 2025-12-29 11:59:27 +00:00
public-glueops-renovatebot[bot]
6611dc4950 chore(patch): update typescript-eslint to 8.50.1 #patch (#511)
Co-authored-by: public-glueops-renovatebot[bot] <186083205+public-glueops-renovatebot[bot]@users.noreply.github.com>
2025-12-28 01:26:48 +00:00
public-glueops-renovatebot[bot]
49665ffc9c feat: update github.com/aws/aws-sdk-go-v2/service/s3 to v1.95.0 #minor (#516)
Co-authored-by: public-glueops-renovatebot[bot] <186083205+public-glueops-renovatebot[bot]@users.noreply.github.com>
2025-12-28 01:26:33 +00:00
public-glueops-renovatebot[bot]
ac14ef8fff chore: lock file maintenance (#528)
Co-authored-by: public-glueops-renovatebot[bot] <186083205+public-glueops-renovatebot[bot]@users.noreply.github.com>
2025-12-28 01:25:13 +00:00
allanice001
f8e543b595 fix: improve dns error logging 2025-12-28 00:08:42 +00:00
allanice001
8cc81e52b7 fix: add get record for dns 2025-12-27 23:21:59 +00:00
public-glueops-renovatebot[bot]
d6e28c7fa2 chore(patch): update @types/node to 25.0.3 #patch (#507)
Co-authored-by: public-glueops-renovatebot[bot] <186083205+public-glueops-renovatebot[bot]@users.noreply.github.com>
2025-12-26 15:30:04 +00:00
public-glueops-renovatebot[bot]
9832229194 chore(patch): update eslint-plugin-react-refresh to 0.4.26 #patch (#508)
Co-authored-by: public-glueops-renovatebot[bot] <186083205+public-glueops-renovatebot[bot]@users.noreply.github.com>
2025-12-26 15:29:07 +00:00
public-glueops-renovatebot[bot]
da82998754 chore(fallback): update alpine (#487)
Co-authored-by: public-glueops-renovatebot[bot] <186083205+public-glueops-renovatebot[bot]@users.noreply.github.com>
2025-12-26 15:28:34 +00:00
public-glueops-renovatebot[bot]
bca32fe784 chore(fallback): update golang (#486)
Co-authored-by: public-glueops-renovatebot[bot] <186083205+public-glueops-renovatebot[bot]@users.noreply.github.com>
2025-12-26 15:28:18 +00:00
public-glueops-renovatebot[bot]
848e8d5179 chore(patch): update shadcn to 3.6.2 #patch (#510)
Co-authored-by: public-glueops-renovatebot[bot] <186083205+public-glueops-renovatebot[bot]@users.noreply.github.com>
2025-12-26 15:28:03 +00:00
public-glueops-renovatebot[bot]
d3ee38881c feat: update docker/setup-buildx-action to v3.12.0 #minor (#515)
Co-authored-by: public-glueops-renovatebot[bot] <186083205+public-glueops-renovatebot[bot]@users.noreply.github.com>
2025-12-26 15:27:20 +00:00
public-glueops-renovatebot[bot]
d39db44aa7 feat: update lucide-react to 0.562.0 #minor (#518)
Co-authored-by: public-glueops-renovatebot[bot] <186083205+public-glueops-renovatebot[bot]@users.noreply.github.com>
2025-12-26 15:26:54 +00:00
public-glueops-renovatebot[bot]
01b29a4706 feat: update vite to 7.3.0 #minor (#519)
Co-authored-by: public-glueops-renovatebot[bot] <186083205+public-glueops-renovatebot[bot]@users.noreply.github.com>
2025-12-26 15:26:38 +00:00
allanice001
bc3bd92d54 fix: improve job tracking
Signed-off-by: allanice001 <allanice001@gmail.com>
2025-12-26 15:14:48 +00:00
allanice001
2057f92b82 fix: improve job tracking 2025-12-26 15:04:31 +00:00
allanice001
169283b6c7 fix: improve job tracking
Signed-off-by: allanice001 <allanice001@gmail.com>
2025-12-26 15:04:15 +00:00
21 changed files with 770 additions and 478 deletions

View File

@@ -33,7 +33,7 @@ jobs:
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5 uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
# Install the cosign tool except on PR # Install the cosign tool except on PR
# https://github.com/sigstore/cosign-installer # https://github.com/sigstore/cosign-installer
@@ -47,7 +47,7 @@ jobs:
# multi-platform images and export cache # multi-platform images and export cache
# https://github.com/docker/setup-buildx-action # https://github.com/docker/setup-buildx-action
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1 uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
# Login against a Docker registry except on PR # Login against a Docker registry except on PR
# https://github.com/docker/login-action # https://github.com/docker/login-action

View File

@@ -1,7 +1,7 @@
################################# #################################
# Builder: Go + Node in one # Builder: Go + Node in one
################################# #################################
FROM golang:1.25.5-alpine@sha256:26111811bc967321e7b6f852e914d14bede324cd1accb7f81811929a6a57fea9 AS builder FROM golang:1.25.5-alpine@sha256:ac09a5f469f307e5da71e766b0bd59c9c49ea460a528cc3e6686513d64a6f1fb AS builder
RUN apk add --no-cache \ RUN apk add --no-cache \
bash git ca-certificates tzdata \ bash git ca-certificates tzdata \
@@ -24,7 +24,7 @@ RUN make build
################################# #################################
# Runtime # Runtime
################################# #################################
FROM alpine:3.23@sha256:51183f2cfa6320055da30872f211093f9ff1d3cf06f39a0bdb212314c5dc7375 FROM alpine:3.23@sha256:865b95f46d98cf867a156fe4a135ad3fe50d2056aa3f25ed31662dff6da4eb62
RUN apk add --no-cache ca-certificates tzdata postgresql17-client \ RUN apk add --no-cache ca-certificates tzdata postgresql17-client \
&& addgroup -S app && adduser -S app -G app && addgroup -S app && adduser -S app -G app

View File

@@ -15,7 +15,7 @@ services:
- postgres_data:/var/lib/postgresql/data - postgres_data:/var/lib/postgresql/data
mailpit: mailpit:
image: axllent/mailpit@sha256:e22dce5b36f93c77082e204a3942fb6b283b7896e057458400a4c88344c3df68 image: axllent/mailpit@sha256:c076638db1e15662150be4fb62b8a6e96ef6ba5bde90c838a0239225854830f7
restart: always restart: always
ports: ports:
- "1025:1025" - "1025:1025"

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -4164,6 +4164,42 @@ paths:
external deletion policy) external deletion policy)
tags: tags:
- DNS - DNS
get:
operationId: GetRecordSet
parameters:
- description: Organization UUID
in: header
name: X-Org-ID
schema:
type: string
- description: Record Set ID (UUID)
in: path
name: id
required: true
schema:
type: string
responses:
"200":
content:
application/json:
schema:
$ref: '#/components/schemas/dto.RecordSetResponse'
description: OK
"403":
content:
application/json:
schema:
type: string
description: organization required
"404":
content:
application/json:
schema:
type: string
description: not found
summary: Get a record set (org scoped)
tags:
- DNS
patch: patch:
operationId: UpdateRecordSet operationId: UpdateRecordSet
parameters: parameters:

4
go.mod
View File

@@ -8,7 +8,8 @@ require (
github.com/aws/aws-sdk-go-v2/config v1.32.6 github.com/aws/aws-sdk-go-v2/config v1.32.6
github.com/aws/aws-sdk-go-v2/credentials v1.19.6 github.com/aws/aws-sdk-go-v2/credentials v1.19.6
github.com/aws/aws-sdk-go-v2/service/route53 v1.62.0 github.com/aws/aws-sdk-go-v2/service/route53 v1.62.0
github.com/aws/aws-sdk-go-v2/service/s3 v1.94.0 github.com/aws/aws-sdk-go-v2/service/s3 v1.95.0
github.com/aws/smithy-go v1.24.0
github.com/coreos/go-oidc/v3 v3.17.0 github.com/coreos/go-oidc/v3 v3.17.0
github.com/dyaksa/archer v1.1.5 github.com/dyaksa/archer v1.1.5
github.com/fergusstrange/embedded-postgres v1.33.0 github.com/fergusstrange/embedded-postgres v1.33.0
@@ -52,7 +53,6 @@ require (
github.com/aws/aws-sdk-go-v2/service/sso v1.30.8 // indirect github.com/aws/aws-sdk-go-v2/service/sso v1.30.8 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.12 // indirect github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.12 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.41.5 // indirect github.com/aws/aws-sdk-go-v2/service/sts v1.41.5 // indirect
github.com/aws/smithy-go v1.24.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect github.com/beorn7/perks v1.0.1 // indirect
github.com/bytedance/sonic v1.14.0 // indirect github.com/bytedance/sonic v1.14.0 // indirect
github.com/bytedance/sonic/loader v0.3.0 // indirect github.com/bytedance/sonic/loader v0.3.0 // indirect

20
go.sum
View File

@@ -14,12 +14,8 @@ github.com/aws/aws-sdk-go-v2 v1.41.0 h1:tNvqh1s+v0vFYdA1xq0aOJH+Y5cRyZ5upu6roPgP
github.com/aws/aws-sdk-go-v2 v1.41.0/go.mod h1:MayyLB8y+buD9hZqkCW3kX1AKq07Y5pXxtgB+rRFhz0= github.com/aws/aws-sdk-go-v2 v1.41.0/go.mod h1:MayyLB8y+buD9hZqkCW3kX1AKq07Y5pXxtgB+rRFhz0=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.4 h1:489krEF9xIGkOaaX3CE/Be2uWjiXrkCH6gUX+bZA/BU= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.4 h1:489krEF9xIGkOaaX3CE/Be2uWjiXrkCH6gUX+bZA/BU=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.4/go.mod h1:IOAPF6oT9KCsceNTvvYMNHy0+kMF8akOjeDvPENWxp4= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.4/go.mod h1:IOAPF6oT9KCsceNTvvYMNHy0+kMF8akOjeDvPENWxp4=
github.com/aws/aws-sdk-go-v2/config v1.32.5 h1:pz3duhAfUgnxbtVhIK39PGF/AHYyrzGEyRD9Og0QrE8=
github.com/aws/aws-sdk-go-v2/config v1.32.5/go.mod h1:xmDjzSUs/d0BB7ClzYPAZMmgQdrodNjPPhd6bGASwoE=
github.com/aws/aws-sdk-go-v2/config v1.32.6 h1:hFLBGUKjmLAekvi1evLi5hVvFQtSo3GYwi+Bx4lpJf8= github.com/aws/aws-sdk-go-v2/config v1.32.6 h1:hFLBGUKjmLAekvi1evLi5hVvFQtSo3GYwi+Bx4lpJf8=
github.com/aws/aws-sdk-go-v2/config v1.32.6/go.mod h1:lcUL/gcd8WyjCrMnxez5OXkO3/rwcNmvfno62tnXNcI= github.com/aws/aws-sdk-go-v2/config v1.32.6/go.mod h1:lcUL/gcd8WyjCrMnxez5OXkO3/rwcNmvfno62tnXNcI=
github.com/aws/aws-sdk-go-v2/credentials v1.19.5 h1:xMo63RlqP3ZZydpJDMBsH9uJ10hgHYfQFIk1cHDXrR4=
github.com/aws/aws-sdk-go-v2/credentials v1.19.5/go.mod h1:hhbH6oRcou+LpXfA/0vPElh/e0M3aFeOblE1sssAAEk=
github.com/aws/aws-sdk-go-v2/credentials v1.19.6 h1:F9vWao2TwjV2MyiyVS+duza0NIRtAslgLUM0vTA1ZaE= github.com/aws/aws-sdk-go-v2/credentials v1.19.6 h1:F9vWao2TwjV2MyiyVS+duza0NIRtAslgLUM0vTA1ZaE=
github.com/aws/aws-sdk-go-v2/credentials v1.19.6/go.mod h1:SgHzKjEVsdQr6Opor0ihgWtkWdfRAIwxYzSJ8O85VHY= github.com/aws/aws-sdk-go-v2/credentials v1.19.6/go.mod h1:SgHzKjEVsdQr6Opor0ihgWtkWdfRAIwxYzSJ8O85VHY=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.16 h1:80+uETIWS1BqjnN9uJ0dBUaETh+P1XwFy5vwHwK5r9k= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.16 h1:80+uETIWS1BqjnN9uJ0dBUaETh+P1XwFy5vwHwK5r9k=
@@ -42,14 +38,10 @@ github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.16 h1:NSbvS17MlI2lu
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.16/go.mod h1:SwT8Tmqd4sA6G1qaGdzWCJN99bUmPGHfRwwq3G5Qb+A= github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.16/go.mod h1:SwT8Tmqd4sA6G1qaGdzWCJN99bUmPGHfRwwq3G5Qb+A=
github.com/aws/aws-sdk-go-v2/service/route53 v1.62.0 h1:80pDB3Tpmb2RCSZORrK9/3iQxsd+w6vSzVqpT1FGiwE= github.com/aws/aws-sdk-go-v2/service/route53 v1.62.0 h1:80pDB3Tpmb2RCSZORrK9/3iQxsd+w6vSzVqpT1FGiwE=
github.com/aws/aws-sdk-go-v2/service/route53 v1.62.0/go.mod h1:6EZUGGNLPLh5Unt30uEoA+KQcByERfXIkax9qrc80nA= github.com/aws/aws-sdk-go-v2/service/route53 v1.62.0/go.mod h1:6EZUGGNLPLh5Unt30uEoA+KQcByERfXIkax9qrc80nA=
github.com/aws/aws-sdk-go-v2/service/s3 v1.93.2 h1:U3ygWUhCpiSPYSHOrRhb3gOl9T5Y3kB8k5Vjs//57bE= github.com/aws/aws-sdk-go-v2/service/s3 v1.95.0 h1:MIWra+MSq53CFaXXAywB2qg9YvVZifkk6vEGl/1Qor0=
github.com/aws/aws-sdk-go-v2/service/s3 v1.93.2/go.mod h1:79S2BdqCJpScXZA2y+cpZuocWsjGjJINyXnOsf5DTz8= github.com/aws/aws-sdk-go-v2/service/s3 v1.95.0/go.mod h1:79S2BdqCJpScXZA2y+cpZuocWsjGjJINyXnOsf5DTz8=
github.com/aws/aws-sdk-go-v2/service/s3 v1.94.0 h1:SWTxh/EcUCDVqi/0s26V6pVUq0BBG7kx0tDTmF/hCgA=
github.com/aws/aws-sdk-go-v2/service/s3 v1.94.0/go.mod h1:79S2BdqCJpScXZA2y+cpZuocWsjGjJINyXnOsf5DTz8=
github.com/aws/aws-sdk-go-v2/service/signin v1.0.4 h1:HpI7aMmJ+mm1wkSHIA2t5EaFFv5EFYXePW30p1EIrbQ= github.com/aws/aws-sdk-go-v2/service/signin v1.0.4 h1:HpI7aMmJ+mm1wkSHIA2t5EaFFv5EFYXePW30p1EIrbQ=
github.com/aws/aws-sdk-go-v2/service/signin v1.0.4/go.mod h1:C5RdGMYGlfM0gYq/tifqgn4EbyX99V15P2V3R+VHbQU= github.com/aws/aws-sdk-go-v2/service/signin v1.0.4/go.mod h1:C5RdGMYGlfM0gYq/tifqgn4EbyX99V15P2V3R+VHbQU=
github.com/aws/aws-sdk-go-v2/service/sso v1.30.7 h1:eYnlt6QxnFINKzwxP5/Ucs1vkG7VT3Iezmvfgc2waUw=
github.com/aws/aws-sdk-go-v2/service/sso v1.30.7/go.mod h1:+fWt2UHSb4kS7Pu8y+BMBvJF0EWx+4H0hzNwtDNRTrg=
github.com/aws/aws-sdk-go-v2/service/sso v1.30.8 h1:aM/Q24rIlS3bRAhTyFurowU8A0SMyGDtEOY/l/s/1Uw= github.com/aws/aws-sdk-go-v2/service/sso v1.30.8 h1:aM/Q24rIlS3bRAhTyFurowU8A0SMyGDtEOY/l/s/1Uw=
github.com/aws/aws-sdk-go-v2/service/sso v1.30.8/go.mod h1:+fWt2UHSb4kS7Pu8y+BMBvJF0EWx+4H0hzNwtDNRTrg= github.com/aws/aws-sdk-go-v2/service/sso v1.30.8/go.mod h1:+fWt2UHSb4kS7Pu8y+BMBvJF0EWx+4H0hzNwtDNRTrg=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.12 h1:AHDr0DaHIAo8c9t1emrzAlVDFp+iMMKnPdYy6XO4MCE= github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.12 h1:AHDr0DaHIAo8c9t1emrzAlVDFp+iMMKnPdYy6XO4MCE=
@@ -87,10 +79,6 @@ github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHk
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= 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 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= 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/gabriel-vasile/mimetype v1.4.11 h1:AQvxbp830wPhHTqc1u7nzoLT+ZFxGY7emj5DR5DYFik=
github.com/gabriel-vasile/mimetype v1.4.11/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
github.com/gabriel-vasile/mimetype v1.4.12 h1:e9hWvmLYvtp846tLHam2o++qitpguFiYCKbn0w9jyqw= github.com/gabriel-vasile/mimetype v1.4.12 h1:e9hWvmLYvtp846tLHam2o++qitpguFiYCKbn0w9jyqw=
github.com/gabriel-vasile/mimetype v1.4.12/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= github.com/gabriel-vasile/mimetype v1.4.12/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w= github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
@@ -124,10 +112,6 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= 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 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= 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-playground/validator/v10 v10.29.0 h1:lQlF5VNJWNlRbRZNeOIkWElR+1LL/OuHcc0Kp14w1xk=
github.com/go-playground/validator/v10 v10.29.0/go.mod h1:D6QxqeMlgIPuT02L66f2ccrZ7AGgHkzKmmTMZhk/Kc4=
github.com/go-playground/validator/v10 v10.30.1 h1:f3zDSN/zOma+w6+1Wswgd9fLkdwy06ntQJp0BBvFG0w= github.com/go-playground/validator/v10 v10.30.1 h1:f3zDSN/zOma+w6+1Wswgd9fLkdwy06ntQJp0BBvFG0w=
github.com/go-playground/validator/v10 v10.30.1/go.mod h1:oSuBIQzuJxL//3MelwSLD5hc2Tu889bF0Idm9Dg26cM= github.com/go-playground/validator/v10 v10.30.1/go.mod h1:oSuBIQzuJxL//3MelwSLD5hc2Tu889bF0Idm9Dg26cM=
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=

View File

@@ -20,6 +20,7 @@ func mountDNSRoutes(r chi.Router, db *gorm.DB, authOrg func(http.Handler) http.H
d.Get("/domains/{domain_id}/records", handlers.ListRecordSets(db)) d.Get("/domains/{domain_id}/records", handlers.ListRecordSets(db))
d.Post("/domains/{domain_id}/records", handlers.CreateRecordSet(db)) d.Post("/domains/{domain_id}/records", handlers.CreateRecordSet(db))
d.Get("/records/{id}", handlers.GetRecordSet(db))
d.Patch("/records/{id}", handlers.UpdateRecordSet(db)) d.Patch("/records/{id}", handlers.UpdateRecordSet(db))
d.Delete("/records/{id}", handlers.DeleteRecordSet(db)) d.Delete("/records/{id}", handlers.DeleteRecordSet(db))
}) })

View File

@@ -37,7 +37,7 @@ func NewRouter(db *gorm.DB, jobs *bg.Jobs, studio http.Handler) http.Handler {
r.Use(middleware.Recoverer) r.Use(middleware.Recoverer)
r.Use(SecurityHeaders) r.Use(SecurityHeaders)
r.Use(requestBodyLimit(10 << 20)) r.Use(requestBodyLimit(10 << 20))
r.Use(httprate.LimitByIP(100, 1*time.Minute)) r.Use(httprate.LimitByIP(1000, 1*time.Minute))
r.Use(middleware.StripSlashes) r.Use(middleware.StripSlashes)
allowed := getAllowedOrigins() allowed := getAllowedOrigins()

View File

@@ -138,7 +138,11 @@ func NewJobs(gdb *gorm.DB, dbUrl string) (*Jobs, error) {
archer.WithTimeout(5*time.Minute), archer.WithTimeout(5*time.Minute),
) )
c.Register("cluster_action", ClusterActionWorker(gdb)) c.Register(
"cluster_action",
ClusterActionWorker(gdb),
archer.WithInstances(1),
)
return jobs, nil return jobs, nil
} }

View File

@@ -36,6 +36,21 @@ func ClusterActionWorker(db *gorm.DB) archer.WorkerFn {
var args ClusterActionArgs var args ClusterActionArgs
_ = j.ParseArguments(&args) _ = j.ParseArguments(&args)
runID, _ := uuid.Parse(j.ID)
updateRun := func(status string, errMsg string) {
updates := map[string]any{
"status": status,
"error": errMsg,
}
if status == "succeeded" || status == "failed" {
updates["finished_at"] = time.Now().UTC().Format(time.RFC3339)
}
db.Model(&models.ClusterRun{}).Where("id = ?", runID).Updates(updates)
}
updateRun("running", "")
logger := log.With(). logger := log.With().
Str("job", j.ID). Str("job", j.ID).
Str("cluster_id", args.ClusterID.String()). Str("cluster_id", args.ClusterID.String()).
@@ -56,18 +71,20 @@ func ClusterActionWorker(db *gorm.DB) archer.WorkerFn {
Preload("NodePools.Servers.SshKey"). Preload("NodePools.Servers.SshKey").
Where("id = ? AND organization_id = ?", args.ClusterID, args.OrgID). Where("id = ? AND organization_id = ?", args.ClusterID, args.OrgID).
First(&c).Error; err != nil { First(&c).Error; err != nil {
updateRun("failed", fmt.Errorf("load cluster: %w", err).Error())
return nil, fmt.Errorf("load cluster: %w", err) return nil, fmt.Errorf("load cluster: %w", err)
} }
// ---- Step 1: Prepare (mostly lifted from ClusterPrepareWorker) // ---- Step 1: Prepare (mostly lifted from ClusterPrepareWorker)
if err := setClusterStatus(db, c.ID, clusterStatusBootstrapping, ""); err != nil { if err := setClusterStatus(db, c.ID, clusterStatusBootstrapping, ""); err != nil {
updateRun("failed", err.Error())
return nil, fmt.Errorf("mark bootstrapping: %w", err) return nil, fmt.Errorf("mark bootstrapping: %w", err)
} }
c.Status = clusterStatusBootstrapping c.Status = clusterStatusBootstrapping
if err := validateClusterForPrepare(&c); err != nil { if err := validateClusterForPrepare(&c); err != nil {
_ = setClusterStatus(db, c.ID, clusterStatusFailed, err.Error()) _ = setClusterStatus(db, c.ID, clusterStatusFailed, err.Error())
updateRun("failed", err.Error())
return nil, fmt.Errorf("validate: %w", err) return nil, fmt.Errorf("validate: %w", err)
} }
@@ -75,6 +92,7 @@ func ClusterActionWorker(db *gorm.DB) archer.WorkerFn {
keyPayloads, sshConfig, err := buildSSHAssetsForCluster(db, &c, allServers) keyPayloads, sshConfig, err := buildSSHAssetsForCluster(db, &c, allServers)
if err != nil { if err != nil {
_ = setClusterStatus(db, c.ID, clusterStatusFailed, err.Error()) _ = setClusterStatus(db, c.ID, clusterStatusFailed, err.Error())
updateRun("failed", err.Error())
return nil, fmt.Errorf("build ssh assets: %w", err) return nil, fmt.Errorf("build ssh assets: %w", err)
} }
@@ -98,6 +116,7 @@ func ClusterActionWorker(db *gorm.DB) archer.WorkerFn {
orgKey, orgSecret, err := findOrCreateClusterAutomationKey(db, c.OrganizationID, c.ID, 24*time.Hour) orgKey, orgSecret, err := findOrCreateClusterAutomationKey(db, c.OrganizationID, c.ID, 24*time.Hour)
if err != nil { if err != nil {
_ = setClusterStatus(db, c.ID, clusterStatusFailed, err.Error()) _ = setClusterStatus(db, c.ID, clusterStatusFailed, err.Error())
updateRun("failed", err.Error())
return nil, fmt.Errorf("org key: %w", err) return nil, fmt.Errorf("org key: %w", err)
} }
dtoCluster.OrgKey = &orgKey dtoCluster.OrgKey = &orgKey
@@ -106,6 +125,7 @@ func ClusterActionWorker(db *gorm.DB) archer.WorkerFn {
payloadJSON, err := json.MarshalIndent(dtoCluster, "", " ") payloadJSON, err := json.MarshalIndent(dtoCluster, "", " ")
if err != nil { if err != nil {
_ = setClusterStatus(db, c.ID, clusterStatusFailed, err.Error()) _ = setClusterStatus(db, c.ID, clusterStatusFailed, err.Error())
updateRun("failed", err.Error())
return nil, fmt.Errorf("marshal payload: %w", err) return nil, fmt.Errorf("marshal payload: %w", err)
} }
@@ -115,11 +135,13 @@ func ClusterActionWorker(db *gorm.DB) archer.WorkerFn {
cancel() cancel()
if err != nil { if err != nil {
_ = setClusterStatus(db, c.ID, clusterStatusFailed, err.Error()) _ = setClusterStatus(db, c.ID, clusterStatusFailed, err.Error())
updateRun("failed", err.Error())
return nil, fmt.Errorf("push assets: %w", err) return nil, fmt.Errorf("push assets: %w", err)
} }
} }
if err := setClusterStatus(db, c.ID, clusterStatusPending, ""); err != nil { if err := setClusterStatus(db, c.ID, clusterStatusPending, ""); err != nil {
updateRun("failed", err.Error())
return nil, fmt.Errorf("mark pending: %w", err) return nil, fmt.Errorf("mark pending: %w", err)
} }
c.Status = clusterStatusPending c.Status = clusterStatusPending
@@ -132,11 +154,13 @@ func ClusterActionWorker(db *gorm.DB) archer.WorkerFn {
if err != nil { if err != nil {
logger.Error().Err(err).Str("output", out).Msg("ping-servers failed") logger.Error().Err(err).Str("output", out).Msg("ping-servers failed")
_ = setClusterStatus(db, c.ID, clusterStatusFailed, fmt.Sprintf("make ping-servers: %v", err)) _ = setClusterStatus(db, c.ID, clusterStatusFailed, fmt.Sprintf("make ping-servers: %v", err))
updateRun("failed", err.Error())
return nil, fmt.Errorf("ping-servers: %w", err) return nil, fmt.Errorf("ping-servers: %w", err)
} }
} }
if err := setClusterStatus(db, c.ID, clusterStatusProvisioning, ""); err != nil { if err := setClusterStatus(db, c.ID, clusterStatusProvisioning, ""); err != nil {
updateRun("failed", err.Error())
return nil, fmt.Errorf("mark provisioning: %w", err) return nil, fmt.Errorf("mark provisioning: %w", err)
} }
c.Status = clusterStatusProvisioning c.Status = clusterStatusProvisioning
@@ -149,13 +173,18 @@ func ClusterActionWorker(db *gorm.DB) archer.WorkerFn {
if err != nil { if err != nil {
logger.Error().Err(err).Str("output", out).Msg("bootstrap target failed") logger.Error().Err(err).Str("output", out).Msg("bootstrap target failed")
_ = setClusterStatus(db, c.ID, clusterStatusFailed, fmt.Sprintf("make %s: %v", args.MakeTarget, err)) _ = setClusterStatus(db, c.ID, clusterStatusFailed, fmt.Sprintf("make %s: %v", args.MakeTarget, err))
updateRun("failed", err.Error())
return nil, fmt.Errorf("make %s: %w", args.MakeTarget, err) return nil, fmt.Errorf("make %s: %w", args.MakeTarget, err)
} }
} }
if err := setClusterStatus(db, c.ID, clusterStatusReady, ""); err != nil { if err := setClusterStatus(db, c.ID, clusterStatusReady, ""); err != nil {
updateRun("failed", err.Error())
return nil, fmt.Errorf("mark ready: %w", err) return nil, fmt.Errorf("mark ready: %w", err)
} }
updateRun("succeeded", "")
return ClusterActionResult{ return ClusterActionResult{
Status: "ok", Status: "ok",
Action: args.Action, Action: args.Action,

View File

@@ -15,6 +15,7 @@ import (
"github.com/glueops/autoglue/internal/models" "github.com/glueops/autoglue/internal/models"
"github.com/glueops/autoglue/internal/utils" "github.com/glueops/autoglue/internal/utils"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"gorm.io/gorm" "gorm.io/gorm"
@@ -23,6 +24,8 @@ import (
"github.com/aws/aws-sdk-go-v2/credentials" "github.com/aws/aws-sdk-go-v2/credentials"
r53 "github.com/aws/aws-sdk-go-v2/service/route53" r53 "github.com/aws/aws-sdk-go-v2/service/route53"
r53types "github.com/aws/aws-sdk-go-v2/service/route53/types" r53types "github.com/aws/aws-sdk-go-v2/service/route53/types"
"github.com/aws/smithy-go"
smithyhttp "github.com/aws/smithy-go/transport/http"
) )
/************* args & small DTOs *************/ /************* args & small DTOs *************/
@@ -47,6 +50,9 @@ const externalDNSPoisonOwner = "autoglue-lock"
// ExternalDNS poison content fake owner so real external-dns skips it. // 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" const externalDNSPoisonValue = "heritage=external-dns,external-dns/owner=" + externalDNSPoisonOwner + ",external-dns/resource=manual/autoglue"
// Default TTL for non-alias records (alias not supported in this reconciler)
const defaultRecordTTLSeconds int64 = 300
/************* entrypoint worker *************/ /************* entrypoint worker *************/
func DNSReconsileWorker(db *gorm.DB, jobs *Jobs) archer.WorkerFn { func DNSReconsileWorker(db *gorm.DB, jobs *Jobs) archer.WorkerFn {
@@ -225,7 +231,14 @@ func processPendingRecordsForDomain(ctx context.Context, db *gorm.DB, d *models.
applied := 0 applied := 0
for i := range records { for i := range records {
if err := applyRecord(ctx, db, r53c, d, &records[i]); err != nil { 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") log.Error().
Err(err).
Str("zone_id", d.ZoneID).
Str("domain", d.DomainName).
Str("record_id", records[i].ID.String()).
Str("name", records[i].Name).
Str("type", strings.ToUpper(records[i].Type)).
Msg("[dns] apply record failed")
_ = setRecordFailed(db, &records[i], err) _ = setRecordFailed(db, &records[i], err)
continue continue
} }
@@ -249,12 +262,24 @@ func applyRecord(ctx context.Context, db *gorm.DB, r53c *r53.Client, d *models.D
mname := markerName(fq) mname := markerName(fq)
expected := buildMarkerValue(d.OrganizationID.String(), r.ID.String(), r.Fingerprint) expected := buildMarkerValue(d.OrganizationID.String(), r.ID.String(), r.Fingerprint)
logCtx := log.With().
Str("zone_id", zoneID).
Str("domain", d.DomainName).
Str("fqdn", fq).
Str("rr_type", rt).
Str("record_id", r.ID.String()).
Str("org_id", d.OrganizationID.String()).
Logger()
start := time.Now()
// ---- ExternalDNS preflight ---- // ---- ExternalDNS preflight ----
extOwned, err := hasExternalDNSOwnership(ctx, r53c, zoneID, fq, rt) extOwned, err := hasExternalDNSOwnership(ctx, r53c, zoneID, fq, rt)
if err != nil { if err != nil {
return fmt.Errorf("external_dns_lookup: %w", err) return fmt.Errorf("external_dns_lookup: %w", err)
} }
if extOwned { if extOwned {
logCtx.Warn().Msg("[dns] ownership conflict: external-dns claims this record")
r.Owner = "external" r.Owner = "external"
_ = db.Save(r).Error _ = db.Save(r).Error
return fmt.Errorf("ownership_conflict: external-dns claims %s; refusing to modify", strings.TrimSuffix(fq, ".")) return fmt.Errorf("ownership_conflict: external-dns claims %s; refusing to modify", strings.TrimSuffix(fq, "."))
@@ -265,6 +290,7 @@ func applyRecord(ctx context.Context, db *gorm.DB, r53c *r53.Client, d *models.D
if err != nil { if err != nil {
return fmt.Errorf("marker lookup: %w", err) return fmt.Errorf("marker lookup: %w", err)
} }
hasForeignOwner := false hasForeignOwner := false
hasOurExact := false hasOurExact := false
for _, v := range markerVals { for _, v := range markerVals {
@@ -279,25 +305,26 @@ func applyRecord(ctx context.Context, db *gorm.DB, r53c *r53.Client, d *models.D
hasForeignOwner = true hasForeignOwner = true
} }
} }
logCtx.Debug().
Bool("externaldns_owned", extOwned).
Int("marker_txt_count", len(markerVals)).
Bool("marker_has_our_exact", hasOurExact).
Bool("marker_has_foreign", hasForeignOwner).
Msg("[dns] ownership preflight")
if hasForeignOwner { if hasForeignOwner {
logCtx.Warn().Msg("[dns] ownership conflict: foreign _autoglue marker")
r.Owner = "external" r.Owner = "external"
_ = db.Save(r).Error _ = db.Save(r).Error
return fmt.Errorf("ownership_conflict: marker for %s is owned by another controller; refusing to modify", strings.TrimSuffix(fq, ".")) 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 // Decode user values
var userVals []string var userVals []string
if len(r.Values) > 0 { rawVals := strings.TrimSpace(string(r.Values))
if err := jsonUnmarshalStrict([]byte(r.Values), &userVals); err != nil { if rawVals != "" && rawVals != "null" {
if err := jsonUnmarshalStrict([]byte(rawVals), &userVals); err != nil {
return fmt.Errorf("values decode: %w", err) return fmt.Errorf("values decode: %w", err)
} }
} }
@@ -306,15 +333,38 @@ func applyRecord(ctx context.Context, db *gorm.DB, r53c *r53.Client, d *models.D
recs := make([]r53types.ResourceRecord, 0, len(userVals)) recs := make([]r53types.ResourceRecord, 0, len(userVals))
for _, v := range userVals { for _, v := range userVals {
v = strings.TrimSpace(v) v = strings.TrimSpace(v)
if v == "" {
continue
}
if rt == "TXT" && !(strings.HasPrefix(v, `"`) && strings.HasSuffix(v, `"`)) { if rt == "TXT" && !(strings.HasPrefix(v, `"`) && strings.HasSuffix(v, `"`)) {
v = strconv.Quote(v) v = strconv.Quote(v)
} }
recs = append(recs, r53types.ResourceRecord{Value: aws.String(v)}) recs = append(recs, r53types.ResourceRecord{Value: aws.String(v)})
} }
rrChange.ResourceRecordSet.ResourceRecords = recs
if r.TTL != nil { // Alias is NOT supported - enforce at least one value for all record types we manage
ttl := int64(*r.TTL) if len(recs) == 0 {
rrChange.ResourceRecordSet.TTL = aws.Int64(ttl) logCtx.Warn().
Str("raw_values", truncateForLog(string(r.Values), 240)).
Int("decoded_value_count", len(userVals)).
Msg("[dns] invalid record: no values (alias not supported)")
return fmt.Errorf("invalid_record: %s %s requires at least one value (alias not supported)", strings.TrimSuffix(fq, "."), rt)
}
ttl := defaultRecordTTLSeconds
if r.TTL != nil && *r.TTL > 0 {
ttl = int64(*r.TTL)
}
// Build RR change (UPSERT)
rrChange := r53types.Change{
Action: r53types.ChangeActionUpsert,
ResourceRecordSet: &r53types.ResourceRecordSet{
Name: aws.String(fq),
Type: r53types.RRType(rt),
TTL: aws.Int64(ttl),
ResourceRecords: recs,
},
} }
// Build marker TXT change (UPSERT) // Build marker TXT change (UPSERT)
@@ -323,7 +373,7 @@ func applyRecord(ctx context.Context, db *gorm.DB, r53c *r53.Client, d *models.D
ResourceRecordSet: &r53types.ResourceRecordSet{ ResourceRecordSet: &r53types.ResourceRecordSet{
Name: aws.String(mname), Name: aws.String(mname),
Type: r53types.RRTypeTxt, Type: r53types.RRTypeTxt,
TTL: aws.Int64(300), TTL: aws.Int64(defaultRecordTTLSeconds),
ResourceRecords: []r53types.ResourceRecord{ ResourceRecords: []r53types.ResourceRecord{
{Value: aws.String(strconv.Quote(expected))}, {Value: aws.String(strconv.Quote(expected))},
}, },
@@ -337,14 +387,26 @@ func applyRecord(ctx context.Context, db *gorm.DB, r53c *r53.Client, d *models.D
changes := []r53types.Change{rrChange, markerChange} changes := []r53types.Change{rrChange, markerChange}
changes = append(changes, poisonChanges...) changes = append(changes, poisonChanges...)
// Log what we are about to send
logCtx.Debug().
Interface("route53_change_batch", toLogChangeBatch(zoneID, changes)).
Msg("[dns] route53 request preview")
_, err = r53c.ChangeResourceRecordSets(ctx, &r53.ChangeResourceRecordSetsInput{ _, err = r53c.ChangeResourceRecordSets(ctx, &r53.ChangeResourceRecordSetsInput{
HostedZoneId: aws.String(zoneID), HostedZoneId: aws.String(zoneID),
ChangeBatch: &r53types.ChangeBatch{Changes: changes}, ChangeBatch: &r53types.ChangeBatch{Changes: changes},
}) })
if err != nil { if err != nil {
logAWSError(logCtx, err)
logCtx.Info().Dur("elapsed", time.Since(start)).Msg("[dns] apply failed")
return err return err
} }
logCtx.Info().
Dur("elapsed", time.Since(start)).
Int("change_count", len(changes)).
Msg("[dns] apply ok")
// Success → mark ready & ownership // Success → mark ready & ownership
r.Status = "ready" r.Status = "ready"
r.LastError = "" r.LastError = ""
@@ -352,6 +414,7 @@ func applyRecord(ctx context.Context, db *gorm.DB, r53c *r53.Client, d *models.D
if err := db.Save(r).Error; err != nil { if err := db.Save(r).Error; err != nil {
return err return err
} }
_ = hasOurExact // could be used to skip marker write in future _ = hasOurExact // could be used to skip marker write in future
return nil return nil
} }
@@ -568,7 +631,7 @@ func buildExternalDNSPoisonTXTChanges(fqdn, rrType string) []r53types.Change {
ResourceRecordSet: &r53types.ResourceRecordSet{ ResourceRecordSet: &r53types.ResourceRecordSet{
Name: aws.String(n), Name: aws.String(n),
Type: r53types.RRTypeTxt, Type: r53types.RRTypeTxt,
TTL: aws.Int64(300), TTL: aws.Int64(defaultRecordTTLSeconds),
ResourceRecords: []r53types.ResourceRecord{ ResourceRecords: []r53types.ResourceRecord{
{Value: aws.String(val)}, {Value: aws.String(val)},
}, },
@@ -595,3 +658,125 @@ func jsonUnmarshalStrict(b []byte, dst any) error {
} }
return json.Unmarshal(b, dst) return json.Unmarshal(b, dst)
} }
/************* logging DTOs & helpers *************/
type logRR struct {
Value string `json:"value"`
}
type logRRSet struct {
Action string `json:"action"`
Name string `json:"name"`
Type string `json:"type"`
TTL *int64 `json:"ttl,omitempty"`
Records []logRR `json:"records,omitempty"`
RecordCount int `json:"record_count"`
HasAliasTarget bool `json:"has_alias_target"`
SetIdentifier *string `json:"set_identifier,omitempty"`
}
type logChangeBatch struct {
HostedZoneID string `json:"hosted_zone_id"`
ChangeCount int `json:"change_count"`
Changes []logRRSet `json:"changes"`
}
func truncateForLog(s string, max int) string {
s = strings.TrimSpace(s)
if max <= 0 || len(s) <= max {
return s
}
return s[:max] + "…"
}
func toLogChangeBatch(zoneID string, changes []r53types.Change) logChangeBatch {
out := logChangeBatch{
HostedZoneID: zoneID,
ChangeCount: len(changes),
Changes: make([]logRRSet, 0, len(changes)),
}
for _, ch := range changes {
if ch.ResourceRecordSet == nil {
continue
}
rrs := ch.ResourceRecordSet
lc := logRRSet{
Action: string(ch.Action),
Name: aws.ToString(rrs.Name),
Type: string(rrs.Type),
TTL: rrs.TTL,
HasAliasTarget: rrs.AliasTarget != nil,
SetIdentifier: rrs.SetIdentifier,
RecordCount: len(rrs.ResourceRecords),
Records: make([]logRR, 0, min(len(rrs.ResourceRecords), 5)),
}
// Log up to first 5 values (truncate each) to avoid log bloat / secrets
for i, rr := range rrs.ResourceRecords {
if i >= 5 {
break
}
lc.Records = append(lc.Records, logRR{Value: truncateForLog(aws.ToString(rr.Value), 160)})
}
out.Changes = append(out.Changes, lc)
}
return out
}
func min(a, b int) int {
if a < b {
return a
}
return b
}
// logAWSError extracts useful smithy/HTTP metadata (status code + request id + api code) into logs.
// logAWSError extracts useful smithy/HTTP metadata (status code + request id + api code) into logs.
func logAWSError(l zerolog.Logger, err error) {
// Add operation context if present
var opErr *smithy.OperationError
if errors.As(err, &opErr) {
l = l.With().
Str("aws_service", opErr.ServiceID).
Str("aws_operation", opErr.OperationName).
Logger()
err = opErr.Unwrap()
}
// HTTP status + request id (smithy-go transport/http)
var re *smithyhttp.ResponseError
if errors.As(err, &re) {
status := re.HTTPStatusCode()
reqID := ""
if resp := re.HTTPResponse(); resp != nil && resp.Header != nil {
reqID = resp.Header.Get("x-amzn-RequestId")
if reqID == "" {
reqID = resp.Header.Get("x-amz-request-id")
}
}
ev := l.Error().Int("http_status", status).Err(err)
if reqID != "" {
ev = ev.Str("aws_request_id", reqID)
}
ev.Msg("[dns] aws route53 call failed")
return
}
// API error code/message (best-effort)
var apiErr smithy.APIError
if errors.As(err, &apiErr) {
l.Error().
Str("aws_error_code", apiErr.ErrorCode()).
Str("aws_error_message", apiErr.ErrorMessage()).
Err(err).
Msg("[dns] aws route53 api error")
return
}
l.Error().Err(err).Msg("[dns] aws route53 error")
}

View File

@@ -223,7 +223,7 @@ func RunClusterAction(db *gorm.DB, jobs *bg.Jobs) http.HandlerFunc {
run.ID.String(), run.ID.String(),
"cluster_action", "cluster_action",
args, args,
archer.WithMaxRetries(3), archer.WithMaxRetries(0),
) )
if enqueueErr != nil { if enqueueErr != nil {

View File

@@ -503,6 +503,50 @@ func ListRecordSets(db *gorm.DB) http.HandlerFunc {
} }
} }
// GetRecordSet godoc
//
// @ID GetRecordSet
// @Summary Get a record set (org scoped)
// @Tags DNS
// @Produce json
// @Param X-Org-ID header string false "Organization UUID"
// @Param id path string true "Record Set ID (UUID)"
// @Success 200 {object} dto.RecordSetResponse
// @Failure 403 {string} string "organization required"
// @Failure 404 {string} string "not found"
// @Router /dns/records/{id} [get]
func GetRecordSet(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
}
utils.WriteJSON(w, http.StatusOK, recordOut(&row))
}
}
// CreateRecordSet godoc // CreateRecordSet godoc
// //
// @ID CreateRecordSet // @ID CreateRecordSet

View File

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

View File

@@ -16,6 +16,6 @@
"prepare": "npm run build" "prepare": "npm run build"
}, },
"devDependencies": { "devDependencies": {
"typescript": "^4.0 || ^5.0" "typescript": "5.9.3"
} }
} }

View File

@@ -46,41 +46,41 @@
"date-fns": "^4.1.0", "date-fns": "^4.1.0",
"embla-carousel-react": "^8.6.0", "embla-carousel-react": "^8.6.0",
"input-otp": "^1.4.2", "input-otp": "^1.4.2",
"lucide-react": "^0.561.0", "lucide-react": "^0.562.0",
"motion": "^12.23.26", "motion": "^12.23.26",
"next-themes": "^0.4.6", "next-themes": "^0.4.6",
"rapidoc": "^9.3.8", "rapidoc": "^9.3.8",
"react": "^19.2.3", "react": "^19.2.3",
"react-day-picker": "^9.12.0", "react-day-picker": "^9.13.0",
"react-dom": "^19.2.3", "react-dom": "^19.2.3",
"react-hook-form": "^7.68.0", "react-hook-form": "^7.69.0",
"react-icons": "^5.5.0", "react-icons": "^5.5.0",
"react-resizable-panels": "^3.0.6", "react-resizable-panels": "^3.0.6",
"react-router-dom": "^7.10.1", "react-router-dom": "^7.11.0",
"recharts": "2.15.4", "recharts": "2.15.4",
"sonner": "^2.0.7", "sonner": "^2.0.7",
"tailwind-merge": "^3.4.0", "tailwind-merge": "^3.4.0",
"tailwindcss": "^4.1.18", "tailwindcss": "^4.1.18",
"vaul": "^1.1.2", "vaul": "^1.1.2",
"zod": "^4.1.13" "zod": "^4.2.1"
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "9.39.2", "@eslint/js": "9.39.2",
"@ianvs/prettier-plugin-sort-imports": "4.7.0", "@ianvs/prettier-plugin-sort-imports": "4.7.0",
"@types/node": "25.0.1", "@types/node": "25.0.3",
"@types/react": "19.2.7", "@types/react": "19.2.7",
"@types/react-dom": "19.2.3", "@types/react-dom": "19.2.3",
"@vitejs/plugin-react": "5.1.2", "@vitejs/plugin-react": "5.1.2",
"eslint": "9.39.2", "eslint": "9.39.2",
"eslint-plugin-react-hooks": "7.0.1", "eslint-plugin-react-hooks": "7.0.1",
"eslint-plugin-react-refresh": "0.4.24", "eslint-plugin-react-refresh": "0.4.26",
"globals": "16.5.0", "globals": "16.5.0",
"prettier": "3.7.4", "prettier": "3.7.4",
"prettier-plugin-tailwindcss": "0.7.2", "prettier-plugin-tailwindcss": "0.7.2",
"shadcn": "3.6.0", "shadcn": "3.6.2",
"tw-animate-css": "1.4.0", "tw-animate-css": "1.4.0",
"typescript": "5.9.3", "typescript": "5.9.3",
"typescript-eslint": "8.50.0", "typescript-eslint": "8.51.0",
"vite": "7.2.7" "vite": "7.3.0"
} }
} }

View File

@@ -946,13 +946,12 @@ export const ClustersPage = () => {
{/* Configure dialog (attachments + kubeconfig + node pools + actions/runs) */} {/* Configure dialog (attachments + kubeconfig + node pools + actions/runs) */}
<Dialog open={!!configCluster} onOpenChange={(open) => !open && setConfigCluster(null)}> <Dialog open={!!configCluster} onOpenChange={(open) => !open && setConfigCluster(null)}>
<DialogContent className="max-h-[90vh] w-full max-w-3xl overflow-y-auto"> <DialogContent className="max-h-[90vh] overflow-y-auto sm:max-w-2xl lg:max-w-250 ">
<DialogHeader> <DialogHeader>
<DialogTitle> <DialogTitle>
Configure Cluster{configCluster?.name ? `: ${configCluster.name}` : ""} Configure Cluster{configCluster?.name ? `: ${configCluster.name}` : ""}
</DialogTitle> </DialogTitle>
</DialogHeader> </DialogHeader>
{configCluster && ( {configCluster && (
<div className="space-y-6 py-2"> <div className="space-y-6 py-2">
{/* Cluster Actions */} {/* Cluster Actions */}

View File

@@ -305,6 +305,7 @@ export const MePage = () => {
<Table> <Table>
<TableHeader> <TableHeader>
<TableRow> <TableRow>
<TableHead>Id</TableHead>
<TableHead>Name</TableHead> <TableHead>Name</TableHead>
<TableHead>Domain</TableHead> <TableHead>Domain</TableHead>
</TableRow> </TableRow>
@@ -312,6 +313,7 @@ export const MePage = () => {
<TableBody> <TableBody>
{meQ.data?.organizations?.map((o) => ( {meQ.data?.organizations?.map((o) => (
<TableRow key={o.id}> <TableRow key={o.id}>
<TableCell>{o.id}</TableCell>
<TableCell>{o.name}</TableCell> <TableCell>{o.name}</TableCell>
<TableCell>{(o as any).domain ?? "—"}</TableCell> <TableCell>{(o as any).domain ?? "—"}</TableCell>
</TableRow> </TableRow>

File diff suppressed because it is too large Load Diff