mirror of
https://github.com/mulgadc/spinifex.git
synced 2026-10-15 09:01:31 +00:00
feat: migrate to golangci-lint (#82)
This commit is contained in:
@@ -120,14 +120,11 @@ jobs:
|
||||
- name: Setup Go Workspace
|
||||
run: go work init ./spinifex ./viperblock ./predastore
|
||||
|
||||
- name: Check Format
|
||||
run: make -C spinifex check-format
|
||||
- name: Lint
|
||||
uses: golangci/golangci-lint-action@v7
|
||||
with:
|
||||
version: v2.11.3
|
||||
working-directory: spinifex
|
||||
|
||||
- name: Check Modernize
|
||||
run: make -C spinifex check-modernize
|
||||
|
||||
- name: Go Vet
|
||||
run: make -C spinifex vet
|
||||
|
||||
- name: Security Checks
|
||||
run: make -C spinifex security-check
|
||||
- name: Govulncheck
|
||||
run: make -C spinifex govulncheck
|
||||
|
||||
+128
@@ -0,0 +1,128 @@
|
||||
version: "2"
|
||||
|
||||
formatters:
|
||||
enable:
|
||||
- gofmt
|
||||
settings:
|
||||
gofmt:
|
||||
simplify: false # modernize linter covers gofmt -s simplifications (Go 1.23+)
|
||||
|
||||
linters:
|
||||
enable:
|
||||
- asasalint # catches pass []any as any in variadic func(...any)
|
||||
- asciicheck # checks that code does not contain non-ASCII identifiers
|
||||
- bidichk # checks for dangerous unicode bidi character sequences
|
||||
- bodyclose # checks whether HTTP response body is closed successfully
|
||||
- canonicalheader # checks whether net/http.Header uses canonical header keys
|
||||
- decorder # checks declaration order of types, consts, vars, and funcs
|
||||
- durationcheck # checks for two durations multiplied together
|
||||
- embeddedstructfieldcheck # checks embedded sync.Mutex/RWMutex in structs
|
||||
- errcheck # checks for unchecked errors in type assertions and I/O
|
||||
- errchkjson # checks types passed to json encoding functions, flags omittable error checks
|
||||
- errname # checks that sentinel errors are prefixed with Err, error types suffixed with Error
|
||||
- exptostd # detects golang.org/x/exp/ functions replaceable by stdlib
|
||||
- fatcontext # detects nested contexts in loops
|
||||
- gocheckcompilerdirectives # validates go compiler directive comments (//go:)
|
||||
- goprintffuncname # checks that printf-like functions are named with f at the end
|
||||
- gosec # inspects source code for security problems
|
||||
- govet # reports suspicious constructs (Printf alignment, shadow, etc.)
|
||||
- ineffassign # detects assignments to existing variables that are not used
|
||||
- makezero # finds slice declarations with non-zero initial length followed by append
|
||||
- mirror # catches wrong mirror patterns in bytes/strings usage
|
||||
- modernize # modernizes code to use newer Go idioms (go fix analyzers)
|
||||
- nakedret # finds naked returns in functions
|
||||
- nilerr # catches code that returns nil even after checking err != nil
|
||||
- nilnesserr # catches returning a different nil error variable after err != nil check
|
||||
- nolintlint # reports ill-formed or insufficient nolint directives
|
||||
- predeclared # catches shadowing of Go builtins (len, cap, error, etc.)
|
||||
- reassign # checks that package variables are not reassigned
|
||||
- recvcheck # checks for receiver type consistency (pointer vs value)
|
||||
- sloglint # enforces consistent log/slog usage (key-value pairs, no globals)
|
||||
- staticcheck # extensive static analysis checks
|
||||
- unconvert # removes unnecessary type conversions
|
||||
- usestdlibvars # detects hardcoded values that should use stdlib constants
|
||||
- usetesting # catches os.TempDir() etc. in tests — should use t.TempDir()
|
||||
- wastedassign # finds assignments to variables that are never subsequently read
|
||||
- whitespace # detects leading and trailing whitespace in functions
|
||||
|
||||
settings:
|
||||
embeddedstructfieldcheck:
|
||||
forbid-mutex: true
|
||||
|
||||
errcheck:
|
||||
check-type-assertions: true
|
||||
exclude-functions:
|
||||
- (*github.com/nats-io/nats.go.Subscription).Unsubscribe # cleanup-only, errors not actionable
|
||||
- (*github.com/kdomanski/iso9660.ImageWriter).Cleanup # deferred cleanup, errors not actionable
|
||||
|
||||
govet:
|
||||
enable-all: true
|
||||
disable:
|
||||
- fieldalignment
|
||||
- shadow # too noisy to retrofit — all 50 findings are err re-declarations
|
||||
- unusedwrite # false positives on struct field assignments before marshal/send
|
||||
|
||||
staticcheck:
|
||||
checks:
|
||||
- all
|
||||
- -ST1000 # package comment
|
||||
- -ST1003 # naming conventions (Id→ID etc.) — too noisy to retrofit
|
||||
- -ST1012 # error var naming — errname covers this + error type suffixes
|
||||
- -ST1013 # HTTP status constants — usestdlibvars is broader
|
||||
- -ST1016 # receiver name consistency (too noisy to retrofit)
|
||||
- -ST1020 # godoc on exported types — not enforcing yet
|
||||
- -ST1021 # godoc on exported type groups — not enforcing yet
|
||||
- -ST1022 # godoc on exported vars/consts — not enforcing yet
|
||||
- -SA1019 # deprecated usage (handle separately)
|
||||
- -SA4006 # value overwritten — ineffassign + wastedassign cover this better
|
||||
- -SA9005 # ineffective break (staticcheck false positives)
|
||||
|
||||
gosec:
|
||||
excludes:
|
||||
- G104 # unchecked errors — errcheck is stricter
|
||||
- G116 # bidi chars — bidichk is more thorough
|
||||
- G117 # use of net/http serve functions (we handle TLS separately)
|
||||
- G703 # path traversal taint analysis — false positives on config file loading
|
||||
- G705 # XSS taint analysis — false positives on internal error responses
|
||||
- G706 # log injection taint analysis — false positives on structured slog usage
|
||||
|
||||
modernize:
|
||||
disable:
|
||||
- newexpr # don't replace aws.String() with new() — AWS SDK idiom
|
||||
- stringsbuilder # don't replace small string concatenations with strings.Builder
|
||||
|
||||
nakedret:
|
||||
max-func-lines: 0
|
||||
|
||||
nolintlint:
|
||||
require-explanation: true
|
||||
require-specific: true
|
||||
|
||||
reassign:
|
||||
patterns:
|
||||
- ".*"
|
||||
|
||||
exclusions:
|
||||
presets:
|
||||
- std-error-handling
|
||||
- common-false-positives
|
||||
rules:
|
||||
- path: '_test\.go'
|
||||
linters:
|
||||
- bodyclose
|
||||
- errcheck
|
||||
- errchkjson
|
||||
- gosec
|
||||
|
||||
# spinifex-specific: gosec exclusions
|
||||
- path: "cmd/"
|
||||
linters:
|
||||
- errcheck # CLI glue code — viper/cobra/pterm errors not actionable
|
||||
- gosec # matches current -exclude-dir=cmd
|
||||
|
||||
- text: "G204"
|
||||
linters: [gosec] # subprocess execution — legitimate in VM orchestration
|
||||
- text: "G304"
|
||||
linters: [gosec] # file path from variable — necessary for config loading
|
||||
- text: "G402"
|
||||
linters: [gosec] # TLS config — we handle TLS ourselves
|
||||
@@ -67,10 +67,10 @@ go_run:
|
||||
@echo -e "\n....Running $(GO_PROJECT_NAME)...."
|
||||
$(GOPATH)/bin/$(GO_PROJECT_NAME)
|
||||
|
||||
# Preflight — runs the same checks as GitHub Actions (format + lint + security + tests).
|
||||
# Preflight — runs the same checks as GitHub Actions (lint + vuln + tests).
|
||||
# Use this before committing to catch CI failures locally.
|
||||
preflight:
|
||||
@$(MAKE) --no-print-directory QUIET=1 check-format check-modernize vet security-check test-cover diff-coverage test-race
|
||||
@$(MAKE) --no-print-directory QUIET=1 lint govulncheck test-cover diff-coverage test-race
|
||||
@echo -e "\n ✅ Preflight passed — safe to commit."
|
||||
|
||||
# Run unit tests
|
||||
@@ -143,59 +143,21 @@ quickinstall: install-system install-go install-aws
|
||||
@echo " Please ensure /usr/local/go/bin is in your PATH."
|
||||
@echo " Installed: Go ($(GO_ARCH)), AWS CLI ($(AWS_ARCH)), QEMU ($(QEMU_PACKAGES))"
|
||||
|
||||
# Format all Go files in place
|
||||
format:
|
||||
gofmt -w .
|
||||
# Lint all Go code via golangci-lint (replaces check-format, vet, gosec, staticcheck)
|
||||
lint:
|
||||
@echo "Running golangci-lint..."
|
||||
$(_Q)golangci-lint run ./...
|
||||
@echo " golangci-lint ok"
|
||||
|
||||
# Check that all Go files are formatted (CI-compatible, fails on diff)
|
||||
check-format:
|
||||
@echo "Checking gofmt..."
|
||||
@UNFORMATTED=$$(gofmt -l .); \
|
||||
if [ -n "$$UNFORMATTED" ]; then \
|
||||
echo "Files not formatted:"; \
|
||||
echo "$$UNFORMATTED"; \
|
||||
echo "Run 'make format' to fix."; \
|
||||
exit 1; \
|
||||
fi
|
||||
@echo " gofmt ok"
|
||||
# Auto-fix all linter issues that have fixers
|
||||
fix:
|
||||
golangci-lint run --fix ./...
|
||||
|
||||
# Go vet (fails on issues, matches CI)
|
||||
vet:
|
||||
@echo "Running go vet..."
|
||||
$(_Q)go vet ./...
|
||||
@echo " go vet ok"
|
||||
|
||||
# Excluded: newexpr (replaces aws.String with new, not idiomatic for AWS SDK)
|
||||
# Excluded: stringsbuilder (replaces string += in loops with strings.Builder, not worth the complexity for small loops)
|
||||
GOFIX_EXCLUDE := -newexpr=false -stringsbuilder=false
|
||||
|
||||
# Apply go fix modernizations
|
||||
modernize:
|
||||
@echo "Applying go fix modernizations..."
|
||||
go fix $(GOFIX_EXCLUDE) ./...
|
||||
@echo " go fix applied"
|
||||
|
||||
# Check that code is modernized (CI-compatible, fails on diff)
|
||||
check-modernize:
|
||||
@echo "Checking go fix modernizations..."
|
||||
@DIFF=$$(go fix $(GOFIX_EXCLUDE) -diff ./...); \
|
||||
if [ -n "$$DIFF" ]; then \
|
||||
echo "$$DIFF"; \
|
||||
echo "Run 'make modernize' to fix."; \
|
||||
exit 1; \
|
||||
fi
|
||||
@echo " go fix ok"
|
||||
|
||||
# Security checks — each tool fails the build on findings (matches CI).
|
||||
# Reports are also saved to tests/ for review.
|
||||
security-check:
|
||||
@echo -e "\n....Running security checks for $(GO_PROJECT_NAME)...."
|
||||
$(_Q)set -o pipefail && go tool govulncheck ./... $(_SECQ) tests/govulncheck-report.txt $(if $(QUIET),|| { cat tests/govulncheck-report.txt; exit 1; })
|
||||
# Govulncheck — dependency vulnerability scanning (not covered by golangci-lint)
|
||||
govulncheck:
|
||||
@echo "Running govulncheck..."
|
||||
$(_Q)go tool govulncheck ./...
|
||||
@echo " govulncheck ok"
|
||||
$(_Q)set -o pipefail && go tool gosec -quiet -exclude=G204,G304,G402,G117,G703,G705,G706 -exclude-generated -exclude-dir=cmd ./... $(_SECQ) tests/gosec-report.txt $(if $(QUIET),|| { cat tests/gosec-report.txt; exit 1; })
|
||||
@echo " gosec ok"
|
||||
$(_Q)set -o pipefail && go tool staticcheck -checks="all,-ST1000,-ST1003,-ST1016,-ST1020,-ST1021,-ST1022,-SA1019,-SA9005" ./... $(_SECQ) tests/staticcheck-report.txt $(if $(QUIET),|| { cat tests/staticcheck-report.txt; exit 1; })
|
||||
@echo " staticcheck ok"
|
||||
|
||||
# Build release tarballs — use distro-ARCH for single arch, distro for both
|
||||
distro: distro-amd64 distro-arm64
|
||||
@@ -234,5 +196,5 @@ distro-clean:
|
||||
|
||||
.PHONY: build build-ui go_build go_run preflight test test-cover test-race diff-coverage bench run clean \
|
||||
install-system install-go install-aws quickinstall \
|
||||
format check-format modernize check-modernize vet security-check \
|
||||
lint fix govulncheck \
|
||||
distro distro-amd64 distro-arm64 distro-clean
|
||||
|
||||
@@ -238,11 +238,9 @@ func init() {
|
||||
imagesImportCmd.Flags().String("arch", "", "Specified distro arch (e.g aarch64, arm64, x86_64)")
|
||||
imagesImportCmd.Flags().String("platform", "Linux/UNIX", "Specified platform (e.g Linux/UNIX, Windows)")
|
||||
imagesImportCmd.Flags().Bool("force", false, "Force command execution (overwrites existing files)")
|
||||
|
||||
}
|
||||
|
||||
func runimagesImportCmd(cmd *cobra.Command, args []string) {
|
||||
|
||||
var image utils.Images
|
||||
|
||||
var imageFile string
|
||||
@@ -277,7 +275,6 @@ func runimagesImportCmd(cmd *cobra.Command, args []string) {
|
||||
imageName, _ := cmd.Flags().GetString("name")
|
||||
|
||||
if imageName != "" {
|
||||
|
||||
var exists bool
|
||||
// Confirm the image exists
|
||||
image, exists = utils.AvailableImages[imageName]
|
||||
@@ -286,9 +283,7 @@ func runimagesImportCmd(cmd *cobra.Command, args []string) {
|
||||
fmt.Fprintf(os.Stderr, "Image name not found in available images")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
imageFile, _ = cmd.Flags().GetString("file")
|
||||
|
||||
if imageFile == "" {
|
||||
@@ -305,7 +300,6 @@ func runimagesImportCmd(cmd *cobra.Command, args []string) {
|
||||
image.Version, _ = cmd.Flags().GetString("version")
|
||||
image.Arch, _ = cmd.Flags().GetString("arch")
|
||||
image.Platform, _ = cmd.Flags().GetString("platform")
|
||||
|
||||
}
|
||||
|
||||
if image.Distro == "" {
|
||||
@@ -352,14 +346,12 @@ func runimagesImportCmd(cmd *cobra.Command, args []string) {
|
||||
if admin.FileExists(imageFile) && !forceCmd {
|
||||
fmt.Printf("Image file already exists, skipping download, use --force to overwrite: %s\n", imageFile)
|
||||
} else {
|
||||
|
||||
err := utils.DownloadFileWithProgress(image.URL, image.Name, imageFile, 0)
|
||||
|
||||
if err != nil {
|
||||
fmt.Printf("Download failed: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Update image file path for later extraction
|
||||
@@ -476,12 +468,10 @@ func runimagesImportCmd(cmd *cobra.Command, args []string) {
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
fmt.Printf("✅ Image import complete. Image-ID (AMI): %s\n", volumeId)
|
||||
|
||||
}
|
||||
|
||||
// List remote images available
|
||||
func runimagesListCmd(cmd *cobra.Command, args []string) {
|
||||
|
||||
//fmt.Println(availableImages)
|
||||
|
||||
tableData := pterm.TableData{
|
||||
@@ -500,7 +490,6 @@ func runimagesListCmd(cmd *cobra.Command, args []string) {
|
||||
|
||||
// 3. Iterate in sorted order
|
||||
for _, k := range keys {
|
||||
|
||||
img := utils.AvailableImages[k]
|
||||
|
||||
//for _, img := range utils.AvailableImages {
|
||||
@@ -790,7 +779,6 @@ func runAdminInit(cmd *cobra.Command, args []string) {
|
||||
func runAdminInitMultiNode(cmd *cobra.Command, accessKey, secretKey, accountID, natsToken, clusterName,
|
||||
configDir, spxRoot, certPath, region, az, node, bindIP, clusterBind string,
|
||||
port, expectedNodes int, formationTimeoutStr string, services []string) {
|
||||
|
||||
formationTimeout, err := time.ParseDuration(formationTimeoutStr)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "❌ Error: Invalid --formation-timeout: %v\n", err)
|
||||
|
||||
@@ -236,6 +236,7 @@ func runGetVMs(cmd *cobra.Command, args []string) {
|
||||
|
||||
type vmRow struct {
|
||||
types.VMInfo
|
||||
|
||||
Node string
|
||||
Host string
|
||||
}
|
||||
|
||||
@@ -102,7 +102,6 @@ func init() {
|
||||
//viper.BindPFlag("nats.host", rootCmd.PersistentFlags().Lookup("nats-host"))
|
||||
//viper.BindPFlag("nats.acl.token", rootCmd.PersistentFlags().Lookup("nats-token"))
|
||||
//viper.BindPFlag("nats.sub.subject", rootCmd.PersistentFlags().Lookup("nats-subject"))
|
||||
|
||||
}
|
||||
|
||||
// initConfig reads in config file and ENV variables if set.
|
||||
|
||||
@@ -744,7 +744,6 @@ var vpcdStatusCmd = &cobra.Command{
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
||||
viper.SetEnvPrefix("SPINIFEX") // Prefix for environment variables
|
||||
viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_"))
|
||||
|
||||
@@ -928,5 +927,4 @@ func init() {
|
||||
vpcdCmd.AddCommand(vpcdStartCmd)
|
||||
vpcdCmd.AddCommand(vpcdStopCmd)
|
||||
vpcdCmd.AddCommand(vpcdStatusCmd)
|
||||
|
||||
}
|
||||
|
||||
@@ -25,17 +25,11 @@ require (
|
||||
atomicgo.dev/cursor v0.2.0 // indirect
|
||||
atomicgo.dev/keyboard v0.2.9 // indirect
|
||||
atomicgo.dev/schedule v0.1.0 // indirect
|
||||
cloud.google.com/go v0.123.0 // indirect
|
||||
cloud.google.com/go/auth v0.18.2 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.9.0 // indirect
|
||||
github.com/BurntSushi/toml v1.6.0 // indirect
|
||||
github.com/anthropics/anthropic-sdk-go v1.26.0 // indirect
|
||||
github.com/antithesishq/antithesis-sdk-go v0.6.0 // indirect
|
||||
github.com/armon/go-metrics v0.4.1 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/boltdb/bolt v1.3.1 // indirect
|
||||
github.com/buraksezer/consistent v0.10.0 // indirect
|
||||
github.com/ccojocar/zxcvbn-go v1.0.4 // indirect
|
||||
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
||||
github.com/cenkalti/hub v1.0.2 // indirect
|
||||
github.com/cenkalti/rpc2 v1.0.5 // indirect
|
||||
@@ -47,7 +41,6 @@ require (
|
||||
github.com/dgraph-io/ristretto/v2 v2.4.0 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/fatih/color v1.18.0 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.13 // indirect
|
||||
github.com/go-logr/logr v1.4.3 // indirect
|
||||
@@ -57,13 +50,8 @@ require (
|
||||
github.com/go-playground/validator/v10 v10.30.1 // indirect
|
||||
github.com/go-viper/mapstructure/v2 v2.5.0 // indirect
|
||||
github.com/google/flatbuffers v25.12.19+incompatible // indirect
|
||||
github.com/google/go-cmp v0.7.0 // indirect
|
||||
github.com/google/go-tpm v0.9.8 // indirect
|
||||
github.com/google/s2a-go v0.1.9 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.13 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.17.0 // indirect
|
||||
github.com/gookit/color v1.6.0 // indirect
|
||||
github.com/gorilla/websocket v1.5.3 // indirect
|
||||
github.com/hashicorp/go-hclog v1.6.3 // indirect
|
||||
github.com/hashicorp/go-immutable-radix v1.3.1 // indirect
|
||||
github.com/hashicorp/go-metrics v0.5.4 // indirect
|
||||
@@ -87,7 +75,6 @@ require (
|
||||
github.com/nats-io/jwt/v2 v2.8.0 // indirect
|
||||
github.com/nats-io/nkeys v0.4.15 // indirect
|
||||
github.com/nats-io/nuid v1.0.1 // indirect
|
||||
github.com/openai/openai-go/v3 v3.24.0 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/prometheus/client_golang v1.23.2 // indirect
|
||||
github.com/prometheus/client_model v0.6.2 // indirect
|
||||
@@ -95,26 +82,20 @@ require (
|
||||
github.com/prometheus/procfs v0.20.1 // indirect
|
||||
github.com/quic-go/quic-go v0.59.0 // indirect
|
||||
github.com/sagikazarmark/locafero v0.12.0 // indirect
|
||||
github.com/securego/gosec/v2 v2.24.7 // indirect
|
||||
github.com/spf13/afero v1.15.0 // indirect
|
||||
github.com/spf13/cast v1.10.0 // indirect
|
||||
github.com/spf13/pflag v1.0.10 // indirect
|
||||
github.com/subosito/gotenv v1.6.0 // indirect
|
||||
github.com/tidwall/gjson v1.18.0 // indirect
|
||||
github.com/tidwall/match v1.2.0 // indirect
|
||||
github.com/tidwall/pretty v1.2.1 // indirect
|
||||
github.com/tidwall/sjson v1.2.5 // indirect
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
||||
go.etcd.io/bbolt v1.4.3 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.66.0 // indirect
|
||||
go.opentelemetry.io/otel v1.41.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.41.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.41.0 // indirect
|
||||
go.yaml.in/yaml/v2 v2.4.3 // indirect
|
||||
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||
golang.org/x/crypto v0.48.0 // indirect
|
||||
golang.org/x/exp/typeparams v0.0.0-20260218203240-3dfff04db8fa // indirect
|
||||
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa // indirect
|
||||
golang.org/x/mod v0.33.0 // indirect
|
||||
golang.org/x/net v0.51.0 // indirect
|
||||
golang.org/x/sync v0.19.0 // indirect
|
||||
@@ -124,17 +105,10 @@ require (
|
||||
golang.org/x/text v0.34.0 // indirect
|
||||
golang.org/x/time v0.14.0 // indirect
|
||||
golang.org/x/tools v0.42.0 // indirect
|
||||
golang.org/x/tools/go/expect v0.1.1-deprecated // indirect
|
||||
golang.org/x/vuln v1.1.4 // indirect
|
||||
google.golang.org/genai v1.49.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 // indirect
|
||||
google.golang.org/grpc v1.79.3 // indirect
|
||||
google.golang.org/protobuf v1.36.11 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
honnef.co/go/tools v0.7.0 // indirect
|
||||
)
|
||||
|
||||
tool (
|
||||
github.com/securego/gosec/v2/cmd/gosec
|
||||
golang.org/x/vuln/cmd/govulncheck
|
||||
honnef.co/go/tools/cmd/staticcheck
|
||||
)
|
||||
tool golang.org/x/vuln/cmd/govulncheck
|
||||
|
||||
@@ -7,14 +7,6 @@ atomicgo.dev/keyboard v0.2.9/go.mod h1:BC4w9g00XkxH/f1HXhW2sXmJFOCWbKn9xrOunSFtE
|
||||
atomicgo.dev/schedule v0.1.0 h1:nTthAbhZS5YZmgYbb2+DH8uQIZcTlIrd4eYr3UQxEjs=
|
||||
atomicgo.dev/schedule v0.1.0/go.mod h1:xeUa3oAkiuHYh8bKiQBRojqAMq3PXXbJujjb0hw8pEU=
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.123.0 h1:2NAUJwPR47q+E35uaJeYoNhuNEM9kM8SjgRgdeOJUSE=
|
||||
cloud.google.com/go v0.123.0/go.mod h1:xBoMV08QcqUGuPW65Qfm1o9Y4zKZBpGS+7bImXLTAZU=
|
||||
cloud.google.com/go/auth v0.18.2 h1:+Nbt5Ev0xEqxlNjd6c+yYUeosQ5TtEUaNcN/3FozlaM=
|
||||
cloud.google.com/go/auth v0.18.2/go.mod h1:xD+oY7gcahcu7G2SG2DsBerfFxgPAJz17zz2joOFF3M=
|
||||
cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs=
|
||||
cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10=
|
||||
github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk=
|
||||
github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
||||
github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
|
||||
github.com/MarvinJWendt/testza v0.1.0/go.mod h1:7AxNvlfeHP7Z/hDQ5JtE3OKYT3XFUeLCDE2DQninSqs=
|
||||
github.com/MarvinJWendt/testza v0.2.1/go.mod h1:God7bhG8n6uQxwdScay+gjm9/LnO4D3kkcZX4hv9Rp8=
|
||||
@@ -25,15 +17,11 @@ github.com/MarvinJWendt/testza v0.3.0/go.mod h1:eFcL4I0idjtIx8P9C6KkAuLgATNKpX4/
|
||||
github.com/MarvinJWendt/testza v0.4.2/go.mod h1:mSdhXiKH8sg/gQehJ63bINcCKp7RtYewEjXsvsVUPbE=
|
||||
github.com/MarvinJWendt/testza v0.5.2 h1:53KDo64C1z/h/d/stCYCPY69bt/OSwjq5KpFNwi+zB4=
|
||||
github.com/MarvinJWendt/testza v0.5.2/go.mod h1:xu53QFE5sCdjtMCKk8YMQ2MnymimEctc4n3EjyIYvEY=
|
||||
github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=
|
||||
github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
|
||||
github.com/anthropics/anthropic-sdk-go v1.26.0 h1:oUTzFaUpAevfuELAP1sjL6CQJ9HHAfT7CoSYSac11PY=
|
||||
github.com/anthropics/anthropic-sdk-go v1.26.0/go.mod h1:qUKmaW+uuPB64iy1l+4kOSvaLqPXnHTTBKH6RVZ7q5Q=
|
||||
github.com/antithesishq/antithesis-sdk-go v0.6.0 h1:v/YViLhFYkZOEEof4AXjD5AgGnGM84YHF4RqEwp6I2g=
|
||||
github.com/antithesishq/antithesis-sdk-go v0.6.0/go.mod h1:IUpT2DPAKh6i/YhSbt6Gl3v2yvUZjmKncl7U91fup7E=
|
||||
github.com/armon/go-metrics v0.4.1 h1:hR91U9KYmb6bLBYLQjyM+3j+rcd/UhE+G78SFnF8gJA=
|
||||
@@ -87,8 +75,6 @@ github.com/boltdb/bolt v1.3.1 h1:JQmyP4ZBrce+ZQu0dY660FMfatumYDLun9hBCUVIkF4=
|
||||
github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps=
|
||||
github.com/buraksezer/consistent v0.10.0 h1:hqBgz1PvNLC5rkWcEBVAL9dFMBWz6I0VgUCW25rrZlU=
|
||||
github.com/buraksezer/consistent v0.10.0/go.mod h1:6BrVajWq7wbKZlTOUPs/XVfR8c0maujuPowduSpZqmw=
|
||||
github.com/ccojocar/zxcvbn-go v1.0.4 h1:FWnCIRMXPj43ukfX000kvBZvV6raSxakYr1nzyNrUcc=
|
||||
github.com/ccojocar/zxcvbn-go v1.0.4/go.mod h1:3GxGX+rHmueTUMvm5ium7irpyjmm7ikxYFOSJB21Das=
|
||||
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
|
||||
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
||||
github.com/cenkalti/hub v1.0.2 h1:Nqv9TNaA9boeO2wQFW8o87BY3zKthtnzXmWGmJqhAV8=
|
||||
@@ -115,15 +101,11 @@ github.com/dgraph-io/ristretto/v2 v2.4.0 h1:I/w09yLjhdcVD2QV192UJcq8dPBaAJb9pOuM
|
||||
github.com/dgraph-io/ristretto/v2 v2.4.0/go.mod h1:0KsrXtXvnv0EqnzyowllbVJB8yBonswa2lTCK2gGo9E=
|
||||
github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da h1:aIftn67I1fkbMa512G+w+Pxci9hJPB8oMnkcP3iZF38=
|
||||
github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
|
||||
github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI=
|
||||
github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
|
||||
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
|
||||
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
|
||||
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
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=
|
||||
@@ -152,8 +134,6 @@ github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91
|
||||
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-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
|
||||
github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=
|
||||
github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
@@ -167,8 +147,6 @@ github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:W
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
github.com/google/flatbuffers v25.12.19+incompatible h1:haMV2JRRJCe1998HeW/p0X9UaMTK6SDo0ffLn2+DbLs=
|
||||
github.com/google/flatbuffers v25.12.19+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
|
||||
github.com/google/go-cmdtest v0.4.1-0.20220921163831-55ab3332a786 h1:rcv+Ippz6RAtvaGgKxc+8FQIpxHgsF+HBzPyYL2cyVU=
|
||||
@@ -183,26 +161,16 @@ github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX
|
||||
github.com/google/go-tpm v0.9.8 h1:slArAR9Ft+1ybZu0lBwpSmpwhRXaa85hWtMinMyRAWo=
|
||||
github.com/google/go-tpm v0.9.8/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83 h1:z2ogiKUYzX5Is6zr/vP9vJGqPwcdqsWjOt+V8J7+bTc=
|
||||
github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83/go.mod h1:MxpfABSjhmINe3F1It9d+8exIHFvUqtLIRCdOGNXqiI=
|
||||
github.com/google/renameio v0.1.0 h1:GOZbcHa3HfsPKPlmyPyN2KEohoMXOhdMbHrvbpl2QaA=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0=
|
||||
github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.13 h1:hSPAhW3NX+7HNlTsmrvU0jL75cIzxFktheceg95Nq14=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.13/go.mod h1:vqVt9yG9480NtzREnTlmGSBmFrA+bzb0yl0TxoBQXOg=
|
||||
github.com/googleapis/gax-go/v2 v2.17.0 h1:RksgfBpxqff0EZkDWYuz9q/uWsTVz+kf43LsZ1J6SMc=
|
||||
github.com/googleapis/gax-go/v2 v2.17.0/go.mod h1:mzaqghpQp4JDh3HvADwrat+6M3MOIDp5YKHhb9PAgDY=
|
||||
github.com/gookit/assert v0.1.1 h1:lh3GcawXe/p+cU7ESTZ5Ui3Sm/x8JWpIis4/1aF0mY0=
|
||||
github.com/gookit/assert v0.1.1/go.mod h1:jS5bmIVQZTIwk42uXl4lyj4iaaxx32tqH16CFj0VX2E=
|
||||
github.com/gookit/color v1.4.2/go.mod h1:fqRyamkC1W8uxl+lxCQxOT09l/vYfZ+QeiX3rKQHCoQ=
|
||||
github.com/gookit/color v1.5.0/go.mod h1:43aQb+Zerm/BWh2GnrgOQm7ffz7tvQXEKV6BFMl7wAo=
|
||||
github.com/gookit/color v1.6.0 h1:JjJXBTk1ETNyqyilJhkTXJYYigHG24TM9Xa2M1xAhRA=
|
||||
github.com/gookit/color v1.6.0/go.mod h1:9ACFc7/1IpHGBW8RwuDm/0YEnhg3dwwXpoMsmtyHfjs=
|
||||
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
||||
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
||||
github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k=
|
||||
github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
|
||||
@@ -305,12 +273,6 @@ github.com/nats-io/nkeys v0.4.15 h1:JACV5jRVO9V856KOapQ7x+EY8Jo3qw1vJt/9Jpwzkk4=
|
||||
github.com/nats-io/nkeys v0.4.15/go.mod h1:CpMchTXC9fxA5zrMo4KpySxNjiDVvr8ANOSZdiNfUrs=
|
||||
github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
|
||||
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
|
||||
github.com/onsi/ginkgo/v2 v2.28.1 h1:S4hj+HbZp40fNKuLUQOYLDgZLwNUVn19N3Atb98NCyI=
|
||||
github.com/onsi/ginkgo/v2 v2.28.1/go.mod h1:CLtbVInNckU3/+gC8LzkGUb9oF+e8W8TdUsxPwvdOgE=
|
||||
github.com/onsi/gomega v1.39.1 h1:1IJLAad4zjPn2PsnhH70V4DKRFlrCzGBNrNaru+Vf28=
|
||||
github.com/onsi/gomega v1.39.1/go.mod h1:hL6yVALoTOxeWudERyfppUcZXjMwIMLnuSfruD2lcfg=
|
||||
github.com/openai/openai-go/v3 v3.24.0 h1:08x6GnYiB+AAejTo6yzPY8RkZMJQ8NpreiOyM5QfyYU=
|
||||
github.com/openai/openai-go/v3 v3.24.0/go.mod h1:cdufnVK14cWcT9qA1rRtrXx4FTRsgbDPW7Ia7SS5cZo=
|
||||
github.com/ovn-kubernetes/libovsdb v0.8.1 h1:M2J8bcJt5mXCom0HqzfEtuHkT80CTSQRcYG7acT8gf4=
|
||||
github.com/ovn-kubernetes/libovsdb v0.8.1/go.mod h1:ZlnHLzagmLOSvyd9qfxBIZp6wOSOw0IsRsc+6lNUGbU=
|
||||
github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY=
|
||||
@@ -364,10 +326,6 @@ github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/sagikazarmark/locafero v0.12.0 h1:/NQhBAkUb4+fH1jivKHWusDYFjMOOKU88eegjfxfHb4=
|
||||
github.com/sagikazarmark/locafero v0.12.0/go.mod h1:sZh36u/YSZ918v0Io+U9ogLYQJ9tLLBmM4eneO6WwsI=
|
||||
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 h1:KRzFb2m7YtdldCEkzs6KqmJw4nqEVZGK7IN2kJkjTuQ=
|
||||
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU=
|
||||
github.com/securego/gosec/v2 v2.24.7 h1:3k5yJnrhT1TTdsG0ZsnenlfCcT+7Y/+zeCPHbL7QAn8=
|
||||
github.com/securego/gosec/v2 v2.24.7/go.mod h1:AdDJbjcG/XxFgVv7pW19vMNYlFM6+Q6Qy3t6lWAUcEY=
|
||||
github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ=
|
||||
github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
@@ -388,7 +346,6 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
@@ -403,17 +360,6 @@ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
||||
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
||||
github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
|
||||
github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||
github.com/tidwall/match v1.2.0 h1:0pt8FlkOwjN2fPt4bIl4BoNxb98gGHN2ObFEDkrfZnM=
|
||||
github.com/tidwall/match v1.2.0/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
|
||||
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||
github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
|
||||
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
|
||||
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
|
||||
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778/go.mod h1:2MuV+tbUrU1zIOPMxZ5EncGwgmMJsa+9ucAQZXxsObs=
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
|
||||
@@ -423,16 +369,10 @@ go.etcd.io/bbolt v1.4.3 h1:dEadXpI6G79deX5prL3QRNP6JB8UxVkqo4UPnHaNXJo=
|
||||
go.etcd.io/bbolt v1.4.3/go.mod h1:tKQlpPaYCVFctUIgFKFnAlvbmB3tpy1vkTnDWohtc0E=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.66.0 h1:PnV4kVnw0zOmwwFkAzCN5O07fw1YOIQor120zrh0AVo=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.66.0/go.mod h1:ofAwF4uinaf8SXdVzzbL4OsxJ3VfeEg3f/F6CeF49/Y=
|
||||
go.opentelemetry.io/otel v1.41.0 h1:YlEwVsGAlCvczDILpUXpIpPSL/VPugt7zHThEMLce1c=
|
||||
go.opentelemetry.io/otel v1.41.0/go.mod h1:Yt4UwgEKeT05QbLwbyHXEwhnjxNO6D8L5PQP51/46dE=
|
||||
go.opentelemetry.io/otel/metric v1.41.0 h1:rFnDcs4gRzBcsO9tS8LCpgR0dxg4aaxWlJxCno7JlTQ=
|
||||
go.opentelemetry.io/otel/metric v1.41.0/go.mod h1:xPvCwd9pU0VN8tPZYzDZV/BMj9CM9vs00GuBjeKhJps=
|
||||
go.opentelemetry.io/otel/sdk v1.41.0 h1:YPIEXKmiAwkGl3Gu1huk1aYWwtpRLeskpV+wPisxBp8=
|
||||
go.opentelemetry.io/otel/sdk v1.41.0/go.mod h1:ahFdU0G5y8IxglBf0QBJXgSe7agzjE4GiTJ6HT9ud90=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.41.0 h1:siZQIYBAUd1rlIWQT2uCxWJxcCO7q3TriaMlf08rXw8=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.41.0/go.mod h1:HNBuSvT7ROaGtGI50ArdRLUnvRTRGniSUZbxiWxSO8Y=
|
||||
go.opentelemetry.io/otel/trace v1.41.0 h1:Vbk2co6bhj8L59ZJ6/xFTskY+tGAbOnCtQGVVa9TIN0=
|
||||
go.opentelemetry.io/otel/trace v1.41.0/go.mod h1:U1NU4ULCoxeDKc09yCWdWe+3QoyweJcISEVa1RBzOis=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
@@ -451,8 +391,6 @@ golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
|
||||
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
|
||||
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa h1:FRnLl4eNAQl8hwxVVC17teOw8kdjVDVAiFMtgUdTSRQ=
|
||||
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE=
|
||||
golang.org/x/exp/typeparams v0.0.0-20260218203240-3dfff04db8fa h1:6Wi43P0isP8Nl8D4qJmA3VC4FswVH0RJkr5cauo67SQ=
|
||||
golang.org/x/exp/typeparams v0.0.0-20260218203240-3dfff04db8fa/go.mod h1:PqrXSW65cXDZH0k4IeUbhmg/bcAZDbzNz3byBpKCsXo=
|
||||
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.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8=
|
||||
@@ -542,15 +480,7 @@ golang.org/x/vuln v1.1.4 h1:Ju8QsuyhX3Hk8ma3CesTbO8vfJD9EvUBgHvkxHBzj0I=
|
||||
golang.org/x/vuln v1.1.4/go.mod h1:F+45wmU18ym/ca5PLTPLsSzr2KppzswxPP603ldA67s=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
||||
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/genai v1.49.0 h1:Se+QJaH2GYK1aaR1o5S38mlU2GD5FnVvP76nfkV7LH0=
|
||||
google.golang.org/genai v1.49.0/go.mod h1:A3kkl0nyBjyFlNjgxIwKq70julKbIxpSxqKO5gw/gmk=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 h1:ggcbiqK8WWh6l1dnltU4BgWGIGo+EVYxCaAPih/zQXQ=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
|
||||
google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE=
|
||||
google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
@@ -579,5 +509,3 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
honnef.co/go/tools v0.7.0 h1:w6WUp1VbkqPEgLz4rkBzH/CSU6HkoqNLp6GstyTx3lU=
|
||||
honnef.co/go/tools v0.7.0/go.mod h1:pm29oPxeP3P82ISxZDgIYeOaf9ta6Pi0EWvCFoLG2vc=
|
||||
|
||||
@@ -81,7 +81,6 @@ type ConfigFile struct {
|
||||
}
|
||||
|
||||
func GenerateConfigFiles(configs []ConfigFile, configSettings ConfigSettings) error {
|
||||
|
||||
for _, cfg := range configs {
|
||||
if err := GenerateConfigFile(cfg.Path, cfg.Template, configSettings); err != nil {
|
||||
return fmt.Errorf("error creating %s: %v", cfg.Name, err)
|
||||
@@ -178,7 +177,6 @@ func GenerateServerCertOnly(configDir string, bindIP string) error {
|
||||
}
|
||||
|
||||
func CreateServiceDirectories(spxRoot string) {
|
||||
|
||||
// Create additional directories
|
||||
dirs := []string{
|
||||
filepath.Join(spxRoot, "images"),
|
||||
@@ -194,7 +192,6 @@ func CreateServiceDirectories(spxRoot string) {
|
||||
|
||||
fmt.Println("\n📁 Creating directory structure...")
|
||||
for _, dir := range dirs {
|
||||
|
||||
// Check if directory exists
|
||||
if _, err := os.Stat(dir); os.IsNotExist(err) {
|
||||
if err := os.MkdirAll(dir, 0750); err != nil {
|
||||
@@ -203,7 +200,6 @@ func CreateServiceDirectories(spxRoot string) {
|
||||
}
|
||||
}
|
||||
fmt.Printf("✅ Directory structure created in %s\n", spxRoot)
|
||||
|
||||
}
|
||||
|
||||
// Helper functions
|
||||
@@ -239,7 +235,8 @@ func ChownRecursive(path, username string) {
|
||||
|
||||
_ = fs.WalkDir(root.FS(), ".", func(p string, d fs.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
return nil
|
||||
slog.Debug("chown walk: skipping inaccessible entry", "path", p, "err", err)
|
||||
return nil // best-effort: continue past inaccessible entries
|
||||
}
|
||||
fullPath := filepath.Join(path, p)
|
||||
if chownErr := os.Lchown(fullPath, uid, gid); chownErr != nil {
|
||||
|
||||
@@ -148,7 +148,6 @@ func LoadConfig(configPath string) (*ClusterConfig, error) {
|
||||
return nil, fmt.Errorf("error reading config file: %w", err)
|
||||
}
|
||||
//fmt.Fprintf(os.Stderr, "Using config file: %s\n", viper.ConfigFileUsed())
|
||||
|
||||
} else {
|
||||
fmt.Fprintf(os.Stderr, "Config file not found: %s, using environment variables and defaults\n", configPath)
|
||||
}
|
||||
|
||||
+15
-32
@@ -302,13 +302,13 @@ func (rm *ResourceManager) GetResourceStats() (totalVCPU int, totalMemGB float64
|
||||
remainingMem := rm.availableMem - rm.allocatedMem
|
||||
|
||||
for _, it := range rm.instanceTypes {
|
||||
cap := resourceStatsForType(remainingVCPU, remainingMem, it)
|
||||
if cap.VCPU == 0 || cap.MemoryGB == 0 {
|
||||
typeCap := resourceStatsForType(remainingVCPU, remainingMem, it)
|
||||
if typeCap.VCPU == 0 || typeCap.MemoryGB == 0 {
|
||||
continue
|
||||
}
|
||||
caps = append(caps, cap)
|
||||
caps = append(caps, typeCap)
|
||||
}
|
||||
return
|
||||
return totalVCPU, totalMemGB, allocVCPU, allocMemGB, caps
|
||||
}
|
||||
|
||||
// SetConfigPath sets the configuration file path for cluster management
|
||||
@@ -1131,7 +1131,6 @@ func (d *Daemon) saveClusterConfig() error {
|
||||
|
||||
// ClusterManager starts the HTTP cluster management server
|
||||
func (d *Daemon) ClusterManager() error {
|
||||
|
||||
// Get daemon host from config
|
||||
daemonHost := d.config.Daemon.Host
|
||||
if daemonHost == "" {
|
||||
@@ -1145,7 +1144,7 @@ func (d *Daemon) ClusterManager() error {
|
||||
configHash, err := d.computeConfigHash()
|
||||
if err != nil {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(500)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
_, _ = w.Write([]byte(`{"error":"failed to compute config hash"}`))
|
||||
return
|
||||
}
|
||||
@@ -1202,7 +1201,7 @@ func (d *Daemon) ClusterManager() error {
|
||||
var req types.NodeJoinRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(400)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
if err := json.NewEncoder(w).Encode(types.NodeJoinResponse{
|
||||
Success: false,
|
||||
Message: "invalid request body",
|
||||
@@ -1217,7 +1216,7 @@ func (d *Daemon) ClusterManager() error {
|
||||
// Validate request
|
||||
if req.Node == "" || req.Region == "" || req.AZ == "" {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(400)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
if err := json.NewEncoder(w).Encode(types.NodeJoinResponse{
|
||||
Success: false,
|
||||
Message: "node, region, and az are required",
|
||||
@@ -1230,7 +1229,7 @@ func (d *Daemon) ClusterManager() error {
|
||||
// Check if node already exists
|
||||
if _, exists := d.clusterConfig.Nodes[req.Node]; exists {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(409)
|
||||
w.WriteHeader(http.StatusConflict)
|
||||
if err := json.NewEncoder(w).Encode(types.NodeJoinResponse{
|
||||
Success: false,
|
||||
Message: fmt.Sprintf("node %s already exists in cluster", req.Node),
|
||||
@@ -1265,7 +1264,7 @@ func (d *Daemon) ClusterManager() error {
|
||||
if err := d.saveClusterConfig(); err != nil {
|
||||
slog.Error("Failed to save cluster config", "error", err)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(500)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
if err := json.NewEncoder(w).Encode(types.NodeJoinResponse{
|
||||
Success: false,
|
||||
Message: "failed to save cluster config",
|
||||
@@ -1405,7 +1404,6 @@ func (d *Daemon) LoadState() error {
|
||||
}
|
||||
|
||||
func (d *Daemon) SendQMPCommand(q *qmp.QMPClient, cmd qmp.QMPCommand, instanceId string) (*qmp.QMPResponse, error) {
|
||||
|
||||
// Confirm QMP client is initialized
|
||||
if q == nil || q.Encoder == nil || q.Decoder == nil {
|
||||
return nil, fmt.Errorf("QMP client is not initialized")
|
||||
@@ -1419,7 +1417,7 @@ func (d *Daemon) SendQMPCommand(q *qmp.QMPClient, cmd qmp.QMPCommand, instanceId
|
||||
if err := q.Conn.SetReadDeadline(time.Now().Add(30 * time.Second)); err != nil {
|
||||
return nil, fmt.Errorf("set read deadline: %w", err)
|
||||
}
|
||||
defer q.Conn.SetReadDeadline(time.Time{}) // clear deadline after command
|
||||
defer func() { _ = q.Conn.SetReadDeadline(time.Time{}) }() // clear deadline after command
|
||||
|
||||
if err := q.Encoder.Encode(cmd); err != nil {
|
||||
return nil, fmt.Errorf("encode error: %w", err)
|
||||
@@ -1446,7 +1444,10 @@ func (d *Daemon) SendQMPCommand(q *qmp.QMPClient, cmd qmp.QMPCommand, instanceId
|
||||
return nil, fmt.Errorf("QMP error: %s: %s", errObj["class"], errObj["desc"])
|
||||
}
|
||||
if _, ok := msg["return"]; ok {
|
||||
respBytes, _ := json.Marshal(msg)
|
||||
respBytes, err := json.Marshal(msg)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("marshal QMP response: %w", err)
|
||||
}
|
||||
var resp qmp.QMPResponse
|
||||
if err := json.Unmarshal(respBytes, &resp); err != nil {
|
||||
return nil, fmt.Errorf("unmarshal error: %w", err)
|
||||
@@ -1457,15 +1458,12 @@ func (d *Daemon) SendQMPCommand(q *qmp.QMPClient, cmd qmp.QMPCommand, instanceId
|
||||
}
|
||||
|
||||
func (d *Daemon) stopInstance(instances map[string]*vm.VM, deleteVolume bool) error {
|
||||
|
||||
// Signal to shutdown each VM
|
||||
var wg sync.WaitGroup
|
||||
|
||||
// Run asynchronously within a worker group
|
||||
for _, instance := range instances {
|
||||
|
||||
wg.Go(func() {
|
||||
|
||||
// Send shutdown command - if it fails, VM may already be dead, continue with cleanup
|
||||
_, err := d.SendQMPCommand(instance.QMPClient, qmp.QMPCommand{Execute: "system_powerdown"}, instance.ID)
|
||||
if err != nil {
|
||||
@@ -1495,7 +1493,6 @@ func (d *Daemon) stopInstance(instances map[string]*vm.VM, deleteVolume bool) er
|
||||
defer instance.EBSRequests.Mu.Unlock()
|
||||
|
||||
for _, ebsRequest := range instance.EBSRequests.Requests {
|
||||
|
||||
// Send the volume payload as JSON
|
||||
ebsUnMountRequest, err := json.Marshal(ebsRequest)
|
||||
|
||||
@@ -1615,12 +1612,10 @@ func (d *Daemon) stopInstance(instances map[string]*vm.VM, deleteVolume bool) er
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
func (d *Daemon) setupShutdown() {
|
||||
d.shutdownWg.Go(func() {
|
||||
|
||||
sigChan := make(chan os.Signal, 1)
|
||||
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP)
|
||||
|
||||
@@ -1650,7 +1645,6 @@ func (d *Daemon) setupShutdown() {
|
||||
if err := sub.Unsubscribe(); err != nil {
|
||||
slog.Error("Error unsubscribing from NATS", "err", err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Write shutdown marker to cluster state KV
|
||||
@@ -1682,7 +1676,6 @@ func (d *Daemon) setupShutdown() {
|
||||
}
|
||||
|
||||
func (d *Daemon) CreateQMPClient(instance *vm.VM) (err error) {
|
||||
|
||||
// Create a new QMP client to communicate with the instance
|
||||
instance.QMPClient, err = qmp.NewQMPClient(instance.Config.QMPSocket)
|
||||
|
||||
@@ -1734,11 +1727,9 @@ func (d *Daemon) CreateQMPClient(instance *vm.VM) (err error) {
|
||||
}()
|
||||
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
func (d *Daemon) LaunchInstance(instance *vm.VM) (err error) {
|
||||
|
||||
// Abort if instance is no longer in a launchable state (e.g., terminated
|
||||
// by a concurrent request while waiting in the launch queue).
|
||||
d.Instances.Mu.Lock()
|
||||
@@ -1749,7 +1740,7 @@ func (d *Daemon) LaunchInstance(instance *vm.VM) (err error) {
|
||||
}
|
||||
|
||||
// First, confirm if the instance is already running
|
||||
pid, err := utils.ReadPidFile(instance.ID)
|
||||
pid, _ := utils.ReadPidFile(instance.ID)
|
||||
|
||||
if pid > 0 {
|
||||
process, err := os.FindProcess(pid)
|
||||
@@ -1974,7 +1965,6 @@ func (d *Daemon) startPendingWatchdog() {
|
||||
}
|
||||
|
||||
func (d *Daemon) StartInstance(instance *vm.VM) error {
|
||||
|
||||
pidFile, err := utils.GeneratePidFile(instance.ID)
|
||||
|
||||
if err != nil {
|
||||
@@ -2025,7 +2015,6 @@ func (d *Daemon) StartInstance(instance *vm.VM) error {
|
||||
instance.EBSRequests.Mu.Lock()
|
||||
|
||||
for _, v := range instance.EBSRequests.Requests {
|
||||
|
||||
drive := vm.Drive{}
|
||||
|
||||
// Use the NBDURI from mount response - contains socket path or TCP address
|
||||
@@ -2293,12 +2282,10 @@ func (d *Daemon) ebsTopic(action string) string {
|
||||
|
||||
// MountVolumes mounts the volumes for an instance
|
||||
func (d *Daemon) MountVolumes(instance *vm.VM) error {
|
||||
|
||||
instance.EBSRequests.Mu.Lock()
|
||||
defer instance.EBSRequests.Mu.Unlock()
|
||||
|
||||
for k, v := range instance.EBSRequests.Requests {
|
||||
|
||||
// Send the volume payload as JSON
|
||||
ebsMountRequest, err := json.Marshal(v)
|
||||
|
||||
@@ -2327,21 +2314,17 @@ func (d *Daemon) MountVolumes(instance *vm.VM) error {
|
||||
}
|
||||
|
||||
if ebsMountResponse.Error == "" {
|
||||
|
||||
slog.Debug("Mounted volume successfully", "response", ebsMountResponse.URI)
|
||||
|
||||
// Append the NBD URI to the request
|
||||
instance.EBSRequests.Requests[k].NBDURI = ebsMountResponse.URI
|
||||
|
||||
} else {
|
||||
slog.Error("Failed to mount volume", "error", ebsMountResponse.Error)
|
||||
return fmt.Errorf("failed to mount volume: %s", ebsMountResponse.Error)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
// rollbackEBSMount sends an ebs.unmount request to undo a previously successful ebs.mount.
|
||||
|
||||
@@ -31,12 +31,7 @@ import (
|
||||
// createTestDaemon creates a test daemon instance with minimal configuration
|
||||
func createTestDaemon(t *testing.T, natsURL string) *Daemon {
|
||||
// Create a temporary directory for test data
|
||||
tmpDir, err := os.MkdirTemp("", "spx-daemon-test-*")
|
||||
require.NoError(t, err, "Failed to create temp directory")
|
||||
|
||||
t.Cleanup(func() {
|
||||
os.RemoveAll(tmpDir)
|
||||
})
|
||||
tmpDir := t.TempDir()
|
||||
|
||||
// New cluster config
|
||||
clusterCfg := &config.ClusterConfig{
|
||||
@@ -345,9 +340,7 @@ func TestHandleEC2RunInstances_ResourceManagement(t *testing.T) {
|
||||
|
||||
// TestDaemon_Initialization tests daemon initialization
|
||||
func TestDaemon_Initialization(t *testing.T) {
|
||||
tmpDir, err := os.MkdirTemp("", "spx-daemon-init-test-*")
|
||||
require.NoError(t, err)
|
||||
defer os.RemoveAll(tmpDir)
|
||||
tmpDir := t.TempDir()
|
||||
|
||||
// New cluster config
|
||||
clusterCfg := &config.ClusterConfig{
|
||||
@@ -859,9 +852,7 @@ func TestDaemon_BootAllocation(t *testing.T) {
|
||||
natsURL := sharedJSNATSURL
|
||||
|
||||
// Create daemon temp directory
|
||||
tmpDir, err := os.MkdirTemp("", "spx-daemon-boot-test-*")
|
||||
require.NoError(t, err)
|
||||
defer os.RemoveAll(tmpDir)
|
||||
tmpDir := t.TempDir()
|
||||
|
||||
// Create test VMs with one running and one stopped instance
|
||||
vms := map[string]*vm.VM{
|
||||
|
||||
@@ -403,7 +403,7 @@ func TestGenerateDevMAC_Format(t *testing.T) {
|
||||
t.Errorf("generateDevMAC(%q) = %q, expected ':' at pos %d", id, mac, i)
|
||||
}
|
||||
} else {
|
||||
if !((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f')) {
|
||||
if (c < '0' || c > '9') && (c < 'a' || c > 'f') {
|
||||
t.Errorf("generateDevMAC(%q) = %q, invalid hex char '%c' at pos %d", id, mac, c, i)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,6 @@ var errInsufficientCapacity = errors.New("insufficient capacity to satisfy MinCo
|
||||
// or side effects.
|
||||
func canAllocateCount(availVCPU, allocVCPU int, availMem, allocMem float64,
|
||||
vCPUs int64, memMiB int64, maxCount int) int {
|
||||
|
||||
remainingVCPU := availVCPU - allocVCPU
|
||||
remainingMem := availMem - allocMem
|
||||
memoryGB := float64(memMiB) / 1024.0
|
||||
|
||||
@@ -201,11 +201,11 @@ func TestResourceStatsForType(t *testing.T) {
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
cap := resourceStatsForType(tc.remainVCPU, tc.remainMem, tc.it)
|
||||
assert.Equal(t, tc.wantName, cap.Name)
|
||||
assert.Equal(t, tc.wantVCPU, cap.VCPU)
|
||||
assert.InDelta(t, tc.wantMemGB, cap.MemoryGB, 0.001)
|
||||
assert.Equal(t, tc.wantAvail, cap.Available)
|
||||
typeCap := resourceStatsForType(tc.remainVCPU, tc.remainMem, tc.it)
|
||||
assert.Equal(t, tc.wantName, typeCap.Name)
|
||||
assert.Equal(t, tc.wantVCPU, typeCap.VCPU)
|
||||
assert.InDelta(t, tc.wantMemGB, typeCap.MemoryGB, 0.001)
|
||||
assert.Equal(t, tc.wantAvail, typeCap.Available)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ package daemon
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"testing"
|
||||
@@ -24,9 +23,7 @@ import (
|
||||
func createDaemonWithJetStream(t *testing.T) *Daemon {
|
||||
t.Helper()
|
||||
|
||||
jsTmpDir, err := os.MkdirTemp("", "nats-js-state-test-*")
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() { os.RemoveAll(jsTmpDir) })
|
||||
jsTmpDir := t.TempDir()
|
||||
|
||||
opts := &server.Options{
|
||||
Host: "127.0.0.1",
|
||||
@@ -44,9 +41,7 @@ func createDaemonWithJetStream(t *testing.T) *Daemon {
|
||||
|
||||
natsURL := ns.ClientURL()
|
||||
|
||||
tmpDir, err := os.MkdirTemp("", "spx-state-test-*")
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() { os.RemoveAll(tmpDir) })
|
||||
tmpDir := t.TempDir()
|
||||
|
||||
clusterCfg := &config.ClusterConfig{
|
||||
Node: "node-1",
|
||||
@@ -260,7 +255,7 @@ func TestEveryState_CanReachTerminated(t *testing.T) {
|
||||
continue
|
||||
}
|
||||
|
||||
visited := map[vm.InstanceState]bool{start: true}
|
||||
visited := map[vm.InstanceState]bool{start: true} //nolint:exhaustive // BFS seed — populated dynamically
|
||||
queue := []vm.InstanceState{start}
|
||||
found := false
|
||||
|
||||
@@ -406,7 +401,7 @@ func TestTransitionState_ConcurrentTransitions(t *testing.T) {
|
||||
|
||||
// reachableStates returns all states reachable from start via BFS.
|
||||
func reachableStates(start vm.InstanceState) map[vm.InstanceState]bool {
|
||||
visited := map[vm.InstanceState]bool{start: true}
|
||||
visited := map[vm.InstanceState]bool{start: true} //nolint:exhaustive // BFS seed — populated dynamically
|
||||
queue := []vm.InstanceState{start}
|
||||
for len(queue) > 0 {
|
||||
current := queue[0]
|
||||
|
||||
@@ -3,6 +3,5 @@ package gateway
|
||||
import "net/http"
|
||||
|
||||
func (gw *GatewayConfig) Account_Request(w http.ResponseWriter, r *http.Request) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ func (gw *GatewayConfig) SigV4AuthMiddleware() func(http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// Skip OPTIONS requests (CORS preflight)
|
||||
if r.Method == "OPTIONS" {
|
||||
if r.Method == http.MethodOptions {
|
||||
next.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
@@ -301,7 +301,7 @@ func canonicalHeaderName(header string) string {
|
||||
|
||||
// writeSigV4Error writes an EC2-compatible XML error response for authentication failures.
|
||||
func (gw *GatewayConfig) writeSigV4Error(w http.ResponseWriter, r *http.Request, errorCode string) {
|
||||
requestID := r.Header.Get("x-amz-request-id")
|
||||
requestID := r.Header.Get("X-Amz-Request-Id")
|
||||
if requestID == "" {
|
||||
requestID = uuid.NewString()
|
||||
}
|
||||
|
||||
@@ -211,12 +211,12 @@ func setupTestApp(accessKey, secretKey string) http.Handler {
|
||||
func TestSigV4Auth_NoAuthorizationHeader(t *testing.T) {
|
||||
handler := setupTestApp(testAccessKey, testSecretKey)
|
||||
|
||||
req := httptest.NewRequest("GET", "/", nil)
|
||||
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||
req.Host = "localhost:9999"
|
||||
|
||||
resp := doRequest(handler, req)
|
||||
|
||||
if resp.StatusCode != 403 {
|
||||
if resp.StatusCode != http.StatusForbidden {
|
||||
t.Errorf("Expected status 403, got %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
@@ -242,14 +242,14 @@ func TestSigV4Auth_MalformedHeader(t *testing.T) {
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
req := httptest.NewRequest("GET", "/", nil)
|
||||
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||
req.Host = "localhost:9999"
|
||||
req.Header.Set("Authorization", tc.authHeader)
|
||||
req.Header.Set("X-Amz-Date", time.Now().UTC().Format(auth.TimeFormat))
|
||||
|
||||
resp := doRequest(handler, req)
|
||||
|
||||
if resp.StatusCode != 400 {
|
||||
if resp.StatusCode != http.StatusBadRequest {
|
||||
t.Errorf("Expected status 400, got %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
@@ -269,14 +269,14 @@ func TestSigV4Auth_InvalidAccessKey(t *testing.T) {
|
||||
"INVALID_ACCESS_KEY", testSecretKey, testRegion, testService,
|
||||
)
|
||||
|
||||
req := httptest.NewRequest("GET", "/", nil)
|
||||
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||
req.Host = "localhost:9999"
|
||||
req.Header.Set("Authorization", authHeader)
|
||||
req.Header.Set("X-Amz-Date", timestamp)
|
||||
|
||||
resp := doRequest(handler, req)
|
||||
|
||||
if resp.StatusCode != 403 {
|
||||
if resp.StatusCode != http.StatusForbidden {
|
||||
t.Errorf("Expected status 403, got %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
@@ -295,14 +295,14 @@ func TestSigV4Auth_InvalidSignature(t *testing.T) {
|
||||
testAccessKey, "WRONG_SECRET_KEY", testRegion, testService,
|
||||
)
|
||||
|
||||
req := httptest.NewRequest("GET", "/", nil)
|
||||
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||
req.Host = "localhost:9999"
|
||||
req.Header.Set("Authorization", authHeader)
|
||||
req.Header.Set("X-Amz-Date", timestamp)
|
||||
|
||||
resp := doRequest(handler, req)
|
||||
|
||||
if resp.StatusCode != 403 {
|
||||
if resp.StatusCode != http.StatusForbidden {
|
||||
t.Errorf("Expected status 403, got %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
@@ -320,14 +320,14 @@ func TestSigV4Auth_ValidSignature(t *testing.T) {
|
||||
testAccessKey, testSecretKey, testRegion, testService,
|
||||
)
|
||||
|
||||
req := httptest.NewRequest("GET", "/", nil)
|
||||
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||
req.Host = "localhost:9999"
|
||||
req.Header.Set("Authorization", authHeader)
|
||||
req.Header.Set("X-Amz-Date", timestamp)
|
||||
|
||||
resp := doRequest(handler, req)
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
t.Errorf("Expected status 200, got %d, body: %s", resp.StatusCode, string(body))
|
||||
}
|
||||
@@ -336,13 +336,13 @@ func TestSigV4Auth_ValidSignature(t *testing.T) {
|
||||
func TestSigV4Auth_OptionsSkipsAuth(t *testing.T) {
|
||||
handler := setupTestApp(testAccessKey, testSecretKey)
|
||||
|
||||
req := httptest.NewRequest("OPTIONS", "/", nil)
|
||||
req := httptest.NewRequest(http.MethodOptions, "/", nil)
|
||||
req.Host = "localhost:9999"
|
||||
// No Authorization header
|
||||
|
||||
resp := doRequest(handler, req)
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Errorf("Expected status 200 for OPTIONS, got %d", resp.StatusCode)
|
||||
}
|
||||
}
|
||||
@@ -356,7 +356,7 @@ func TestSigV4Auth_ValidSignatureWithBody(t *testing.T) {
|
||||
testAccessKey, testSecretKey, testRegion, testService,
|
||||
)
|
||||
|
||||
req := httptest.NewRequest("POST", "/", strings.NewReader(body))
|
||||
req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(body))
|
||||
req.Host = "localhost:9999"
|
||||
req.Header.Set("Authorization", authHeader)
|
||||
req.Header.Set("X-Amz-Date", timestamp)
|
||||
@@ -364,7 +364,7 @@ func TestSigV4Auth_ValidSignatureWithBody(t *testing.T) {
|
||||
|
||||
resp := doRequest(handler, req)
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
respBody, _ := io.ReadAll(resp.Body)
|
||||
t.Errorf("Expected status 200, got %d, body: %s", resp.StatusCode, string(respBody))
|
||||
}
|
||||
@@ -379,14 +379,14 @@ func TestSigV4Auth_ValidSignatureWithQueryString(t *testing.T) {
|
||||
testAccessKey, testSecretKey, testRegion, testService,
|
||||
)
|
||||
|
||||
req := httptest.NewRequest("GET", "/?"+queryString, nil)
|
||||
req := httptest.NewRequest(http.MethodGet, "/?"+queryString, nil)
|
||||
req.Host = "localhost:9999"
|
||||
req.Header.Set("Authorization", authHeader)
|
||||
req.Header.Set("X-Amz-Date", timestamp)
|
||||
|
||||
resp := doRequest(handler, req)
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
respBody, _ := io.ReadAll(resp.Body)
|
||||
t.Errorf("Expected status 200, got %d, body: %s", resp.StatusCode, string(respBody))
|
||||
}
|
||||
@@ -427,14 +427,14 @@ func TestSigV4Auth_InactiveAccessKey(t *testing.T) {
|
||||
testAccessKey, testSecretKey, testRegion, testService,
|
||||
)
|
||||
|
||||
req := httptest.NewRequest("GET", "/", nil)
|
||||
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||
req.Host = "localhost:9999"
|
||||
req.Header.Set("Authorization", authHeader)
|
||||
req.Header.Set("X-Amz-Date", timestamp)
|
||||
|
||||
resp := doRequest(r, req)
|
||||
|
||||
if resp.StatusCode != 403 {
|
||||
if resp.StatusCode != http.StatusForbidden {
|
||||
t.Errorf("Expected status 403 for inactive key, got %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
@@ -462,14 +462,14 @@ func TestSigV4Auth_NilIAMService(t *testing.T) {
|
||||
testAccessKey, testSecretKey, testRegion, testService,
|
||||
)
|
||||
|
||||
req := httptest.NewRequest("GET", "/", nil)
|
||||
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||
req.Host = "localhost:9999"
|
||||
req.Header.Set("Authorization", authHeader)
|
||||
req.Header.Set("X-Amz-Date", timestamp)
|
||||
|
||||
resp := doRequest(r, req)
|
||||
|
||||
if resp.StatusCode != 500 {
|
||||
if resp.StatusCode != http.StatusInternalServerError {
|
||||
t.Errorf("Expected status 500 for nil IAM service, got %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
@@ -515,14 +515,14 @@ func TestSigV4Auth_CallerIdentity(t *testing.T) {
|
||||
testAccessKey, testSecretKey, testRegion, testService,
|
||||
)
|
||||
|
||||
req := httptest.NewRequest("GET", "/", nil)
|
||||
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||
req.Host = "localhost:9999"
|
||||
req.Header.Set("Authorization", authHeader)
|
||||
req.Header.Set("X-Amz-Date", timestamp)
|
||||
|
||||
resp := doRequest(r, req)
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
t.Fatalf("Expected status 200, got %d, body: %s", resp.StatusCode, string(body))
|
||||
}
|
||||
@@ -650,14 +650,14 @@ func TestSigV4Auth_DecryptFailure(t *testing.T) {
|
||||
testAccessKey, testSecretKey, testRegion, testService,
|
||||
)
|
||||
|
||||
req := httptest.NewRequest("GET", "/", nil)
|
||||
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||
req.Host = "localhost:9999"
|
||||
req.Header.Set("Authorization", authHeader)
|
||||
req.Header.Set("X-Amz-Date", timestamp)
|
||||
|
||||
resp := doRequest(r, req)
|
||||
|
||||
if resp.StatusCode != 500 {
|
||||
if resp.StatusCode != http.StatusInternalServerError {
|
||||
t.Errorf("Expected status 500 for decrypt failure, got %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
@@ -786,14 +786,14 @@ func TestSigV4Auth_ExpiredTimestamp(t *testing.T) {
|
||||
testAccessKey, testSecretKey, testRegion, testService, past,
|
||||
)
|
||||
|
||||
req := httptest.NewRequest("GET", "/", nil)
|
||||
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||
req.Host = "localhost:9999"
|
||||
req.Header.Set("Authorization", authHeader)
|
||||
req.Header.Set("X-Amz-Date", timestamp)
|
||||
|
||||
resp := doRequest(handler, req)
|
||||
|
||||
if resp.StatusCode != 403 {
|
||||
if resp.StatusCode != http.StatusForbidden {
|
||||
t.Errorf("Expected status 403, got %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
@@ -813,14 +813,14 @@ func TestSigV4Auth_FutureTimestamp(t *testing.T) {
|
||||
testAccessKey, testSecretKey, testRegion, testService, future,
|
||||
)
|
||||
|
||||
req := httptest.NewRequest("GET", "/", nil)
|
||||
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||
req.Host = "localhost:9999"
|
||||
req.Header.Set("Authorization", authHeader)
|
||||
req.Header.Set("X-Amz-Date", timestamp)
|
||||
|
||||
resp := doRequest(handler, req)
|
||||
|
||||
if resp.StatusCode != 403 {
|
||||
if resp.StatusCode != http.StatusForbidden {
|
||||
t.Errorf("Expected status 403, got %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
@@ -840,14 +840,14 @@ func TestSigV4Auth_TimestampWithinSkew(t *testing.T) {
|
||||
testAccessKey, testSecretKey, testRegion, testService, recent,
|
||||
)
|
||||
|
||||
req := httptest.NewRequest("GET", "/", nil)
|
||||
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||
req.Host = "localhost:9999"
|
||||
req.Header.Set("Authorization", authHeader)
|
||||
req.Header.Set("X-Amz-Date", timestamp)
|
||||
|
||||
resp := doRequest(handler, req)
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
t.Errorf("Expected status 200, got %d, body: %s", resp.StatusCode, string(body))
|
||||
}
|
||||
@@ -878,18 +878,18 @@ func TestSigV4Auth_ClockSkewBoundary(t *testing.T) {
|
||||
testAccessKey, testSecretKey, testRegion, testService, at,
|
||||
)
|
||||
|
||||
req := httptest.NewRequest("GET", "/", nil)
|
||||
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||
req.Host = "localhost:9999"
|
||||
req.Header.Set("Authorization", authHeader)
|
||||
req.Header.Set("X-Amz-Date", timestamp)
|
||||
|
||||
resp := doRequest(handler, req)
|
||||
|
||||
if tc.expectPass && resp.StatusCode != 200 {
|
||||
if tc.expectPass && resp.StatusCode != http.StatusOK {
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
t.Errorf("Expected 200, got %d, body: %s", resp.StatusCode, string(body))
|
||||
}
|
||||
if !tc.expectPass && resp.StatusCode != 403 {
|
||||
if !tc.expectPass && resp.StatusCode != http.StatusForbidden {
|
||||
t.Errorf("Expected 403, got %d", resp.StatusCode)
|
||||
}
|
||||
})
|
||||
@@ -904,14 +904,14 @@ func TestSigV4Auth_MissingXAmzDate(t *testing.T) {
|
||||
testAccessKey, testSecretKey, testRegion, testService,
|
||||
)
|
||||
|
||||
req := httptest.NewRequest("GET", "/", nil)
|
||||
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||
req.Host = "localhost:9999"
|
||||
req.Header.Set("Authorization", authHeader)
|
||||
// Deliberately omit X-Amz-Date
|
||||
|
||||
resp := doRequest(handler, req)
|
||||
|
||||
if resp.StatusCode != 400 {
|
||||
if resp.StatusCode != http.StatusBadRequest {
|
||||
t.Errorf("Expected status 400, got %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
@@ -929,14 +929,14 @@ func TestSigV4Auth_MalformedTimestamp(t *testing.T) {
|
||||
testAccessKey, testSecretKey, testRegion, testService,
|
||||
)
|
||||
|
||||
req := httptest.NewRequest("GET", "/", nil)
|
||||
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||
req.Host = "localhost:9999"
|
||||
req.Header.Set("Authorization", authHeader)
|
||||
req.Header.Set("X-Amz-Date", "not-a-valid-date")
|
||||
|
||||
resp := doRequest(handler, req)
|
||||
|
||||
if resp.StatusCode != 400 {
|
||||
if resp.StatusCode != http.StatusBadRequest {
|
||||
t.Errorf("Expected status 400, got %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
@@ -965,7 +965,7 @@ func TestWriteSigV4Error_ResponseFormat(t *testing.T) {
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.errorCode, func(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
req := httptest.NewRequest("GET", "/", nil)
|
||||
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||
|
||||
gw.writeSigV4Error(w, req, tc.errorCode)
|
||||
resp := w.Result()
|
||||
@@ -1002,8 +1002,8 @@ func TestWriteSigV4Error_PreservesRequestID(t *testing.T) {
|
||||
gw := &GatewayConfig{DisableLogging: true, Region: testRegion}
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req := httptest.NewRequest("GET", "/", nil)
|
||||
req.Header.Set("x-amz-request-id", "test-request-id-123")
|
||||
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||
req.Header.Set("X-Amz-Request-Id", "test-request-id-123")
|
||||
|
||||
gw.writeSigV4Error(w, req, awserrors.ErrorIncompleteSignature)
|
||||
|
||||
@@ -1059,14 +1059,14 @@ func TestSigV4Auth_ContextPropagation(t *testing.T) {
|
||||
testAccessKey, testSecretKey, testRegion, testService,
|
||||
)
|
||||
|
||||
req := httptest.NewRequest("GET", "/", nil)
|
||||
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||
req.Host = "localhost:9999"
|
||||
req.Header.Set("Authorization", authHeader)
|
||||
req.Header.Set("X-Amz-Date", timestamp)
|
||||
|
||||
resp := doRequest(r, req)
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
t.Fatalf("Expected status 200, got %d, body: %s", resp.StatusCode, string(body))
|
||||
}
|
||||
@@ -1142,7 +1142,7 @@ func TestSigV4Auth_ContextDoesNotLeakBetweenRequests(t *testing.T) {
|
||||
"GET", "/", "", "",
|
||||
testAccessKey, testSecretKey, testRegion, testService,
|
||||
)
|
||||
req1 := httptest.NewRequest("GET", "/", nil)
|
||||
req1 := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||
req1.Host = "localhost:9999"
|
||||
req1.Header.Set("Authorization", authHeader1)
|
||||
req1.Header.Set("X-Amz-Date", timestamp1)
|
||||
@@ -1158,7 +1158,7 @@ func TestSigV4Auth_ContextDoesNotLeakBetweenRequests(t *testing.T) {
|
||||
"GET", "/", "", "",
|
||||
secondKey, testSecretKey, testRegion, testService,
|
||||
)
|
||||
req2 := httptest.NewRequest("GET", "/", nil)
|
||||
req2 := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||
req2.Host = "localhost:9999"
|
||||
req2.Header.Set("Authorization", authHeader2)
|
||||
req2.Header.Set("X-Amz-Date", timestamp2)
|
||||
@@ -1191,14 +1191,14 @@ func TestSigV4Auth_LookupAccessKeyUnexpectedError(t *testing.T) {
|
||||
testAccessKey, testSecretKey, testRegion, testService,
|
||||
)
|
||||
|
||||
req := httptest.NewRequest("GET", "/", nil)
|
||||
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||
req.Host = "localhost:9999"
|
||||
req.Header.Set("Authorization", authHeader)
|
||||
req.Header.Set("X-Amz-Date", timestamp)
|
||||
|
||||
resp := doRequest(r, req)
|
||||
|
||||
if resp.StatusCode != 500 {
|
||||
if resp.StatusCode != http.StatusInternalServerError {
|
||||
t.Errorf("Expected status 500 for unexpected lookup error, got %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
@@ -1230,14 +1230,14 @@ func TestSigV4Auth_PathWithSpecialCharacters(t *testing.T) {
|
||||
testAccessKey, testSecretKey, testRegion, testService,
|
||||
)
|
||||
|
||||
req := httptest.NewRequest("GET", tc.reqPath, nil)
|
||||
req := httptest.NewRequest(http.MethodGet, tc.reqPath, nil)
|
||||
req.Host = "localhost:9999"
|
||||
req.Header.Set("Authorization", authHeader)
|
||||
req.Header.Set("X-Amz-Date", timestamp)
|
||||
|
||||
resp := doRequest(handler, req)
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
t.Errorf("Expected status 200, got %d, body: %s", resp.StatusCode, string(body))
|
||||
}
|
||||
@@ -1291,7 +1291,7 @@ func TestSigV4Auth_SignedContentType(t *testing.T) {
|
||||
headers, "content-type;host;x-amz-date",
|
||||
)
|
||||
|
||||
req := httptest.NewRequest("POST", "/", strings.NewReader(body))
|
||||
req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(body))
|
||||
req.Host = "localhost:9999"
|
||||
req.Header.Set("Authorization", authHeader)
|
||||
req.Header.Set("X-Amz-Date", timestamp)
|
||||
@@ -1299,7 +1299,7 @@ func TestSigV4Auth_SignedContentType(t *testing.T) {
|
||||
|
||||
resp := doRequest(r, req)
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
respBody, _ := io.ReadAll(resp.Body)
|
||||
t.Errorf("Expected status 200, got %d, body: %s", resp.StatusCode, string(respBody))
|
||||
}
|
||||
@@ -1314,14 +1314,14 @@ func TestSigV4Auth_EmptyBodyPOST(t *testing.T) {
|
||||
testAccessKey, testSecretKey, testRegion, testService,
|
||||
)
|
||||
|
||||
req := httptest.NewRequest("POST", "/", strings.NewReader(""))
|
||||
req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(""))
|
||||
req.Host = "localhost:9999"
|
||||
req.Header.Set("Authorization", authHeader)
|
||||
req.Header.Set("X-Amz-Date", timestamp)
|
||||
|
||||
resp := doRequest(handler, req)
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
t.Errorf("Expected status 200, got %d, body: %s", resp.StatusCode, string(body))
|
||||
}
|
||||
@@ -1388,7 +1388,7 @@ func TestComputeSignatureWithSecret_SpecialURIPaths(t *testing.T) {
|
||||
if reqPath == "" {
|
||||
reqPath = "/"
|
||||
}
|
||||
req := httptest.NewRequest("GET", reqPath, nil)
|
||||
req := httptest.NewRequest(http.MethodGet, reqPath, nil)
|
||||
req.Host = "localhost:9999"
|
||||
req.Header.Set("X-Amz-Date", timestamp)
|
||||
|
||||
@@ -1412,7 +1412,7 @@ func TestComputeSignatureWithSecret_Determinism(t *testing.T) {
|
||||
|
||||
var first string
|
||||
for i := range 10 {
|
||||
req := httptest.NewRequest("POST", "/", nil)
|
||||
req := httptest.NewRequest(http.MethodPost, "/", nil)
|
||||
req.Host = "localhost:9999"
|
||||
req.Header.Set("X-Amz-Date", timestamp)
|
||||
|
||||
@@ -1442,7 +1442,7 @@ func TestComputeSignatureWithSecret_MultipartContentType(t *testing.T) {
|
||||
headers, "content-type;host;x-amz-date",
|
||||
)
|
||||
|
||||
req := httptest.NewRequest("POST", "/", strings.NewReader(body))
|
||||
req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(body))
|
||||
req.Host = "localhost:9999"
|
||||
req.Header.Set("Authorization", authHeader)
|
||||
req.Header.Set("X-Amz-Date", timestamp)
|
||||
@@ -1450,7 +1450,7 @@ func TestComputeSignatureWithSecret_MultipartContentType(t *testing.T) {
|
||||
|
||||
resp := doRequest(handler, req)
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
respBody, _ := io.ReadAll(resp.Body)
|
||||
t.Errorf("Expected status 200 for multipart content-type, got %d, body: %s", resp.StatusCode, string(respBody))
|
||||
}
|
||||
@@ -1475,14 +1475,14 @@ func TestSigV4Auth_QueryStringEdgeCases(t *testing.T) {
|
||||
testAccessKey, testSecretKey, testRegion, testService,
|
||||
)
|
||||
|
||||
req := httptest.NewRequest("GET", "/?"+tc.queryString, nil)
|
||||
req := httptest.NewRequest(http.MethodGet, "/?"+tc.queryString, nil)
|
||||
req.Host = "localhost:9999"
|
||||
req.Header.Set("Authorization", authHeader)
|
||||
req.Header.Set("X-Amz-Date", timestamp)
|
||||
|
||||
resp := doRequest(handler, req)
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
t.Errorf("Expected status 200, got %d, body: %s", resp.StatusCode, string(body))
|
||||
}
|
||||
@@ -1495,6 +1495,7 @@ func TestSigV4Auth_QueryStringEdgeCases(t *testing.T) {
|
||||
// policyMockIAMService extends mockIAMService with configurable GetUserPolicies.
|
||||
type policyMockIAMService struct {
|
||||
mockIAMService
|
||||
|
||||
getUserPoliciesFn func(accountID, userName string) ([]handlers_iam.PolicyDocument, error)
|
||||
}
|
||||
|
||||
@@ -1508,6 +1509,7 @@ func (m *policyMockIAMService) GetUserPolicies(accountID, userName string) ([]ha
|
||||
// errorLookupMockIAMService returns a configurable error from LookupAccessKey.
|
||||
type errorLookupMockIAMService struct {
|
||||
mockIAMService
|
||||
|
||||
err error
|
||||
}
|
||||
|
||||
@@ -1566,14 +1568,14 @@ func TestCheckPolicy_NonRootNoPolicies_Denied(t *testing.T) {
|
||||
testAccessKey, testSecretKey, testRegion, testService,
|
||||
)
|
||||
|
||||
req := httptest.NewRequest("GET", "/", nil)
|
||||
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||
req.Host = "localhost:9999"
|
||||
req.Header.Set("Authorization", authHeader)
|
||||
req.Header.Set("X-Amz-Date", timestamp)
|
||||
|
||||
resp := doRequest(handler, req)
|
||||
|
||||
if resp.StatusCode != 403 {
|
||||
if resp.StatusCode != http.StatusForbidden {
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
t.Errorf("Expected status 403, got %d, body: %s", resp.StatusCode, string(body))
|
||||
}
|
||||
@@ -1632,14 +1634,14 @@ func TestCheckPolicy_NonRootWithAllow_Passes(t *testing.T) {
|
||||
testAccessKey, testSecretKey, testRegion, testService,
|
||||
)
|
||||
|
||||
req := httptest.NewRequest("GET", "/", nil)
|
||||
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||
req.Host = "localhost:9999"
|
||||
req.Header.Set("Authorization", authHeader)
|
||||
req.Header.Set("X-Amz-Date", timestamp)
|
||||
|
||||
resp := doRequest(handler, req)
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
t.Errorf("Expected status 200, got %d, body: %s", resp.StatusCode, string(body))
|
||||
}
|
||||
@@ -1698,14 +1700,14 @@ func TestCheckPolicy_NonRootWithExplicitDeny_Denied(t *testing.T) {
|
||||
testAccessKey, testSecretKey, testRegion, testService,
|
||||
)
|
||||
|
||||
req := httptest.NewRequest("GET", "/", nil)
|
||||
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||
req.Host = "localhost:9999"
|
||||
req.Header.Set("Authorization", authHeader)
|
||||
req.Header.Set("X-Amz-Date", timestamp)
|
||||
|
||||
resp := doRequest(handler, req)
|
||||
|
||||
if resp.StatusCode != 403 {
|
||||
if resp.StatusCode != http.StatusForbidden {
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
t.Errorf("Expected status 403, got %d, body: %s", resp.StatusCode, string(body))
|
||||
}
|
||||
@@ -1765,14 +1767,14 @@ func TestCheckPolicy_RootGlobalAccount_Bypasses(t *testing.T) {
|
||||
testAccessKey, testSecretKey, testRegion, testService,
|
||||
)
|
||||
|
||||
req := httptest.NewRequest("GET", "/", nil)
|
||||
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||
req.Host = "localhost:9999"
|
||||
req.Header.Set("Authorization", authHeader)
|
||||
req.Header.Set("X-Amz-Date", timestamp)
|
||||
|
||||
resp := doRequest(handler, req)
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
t.Errorf("Expected status 200 (root bypass), got %d, body: %s", resp.StatusCode, string(body))
|
||||
}
|
||||
@@ -1813,14 +1815,14 @@ func TestCheckPolicy_MissingAccountID_InternalError(t *testing.T) {
|
||||
testAccessKey, testSecretKey, testRegion, testService,
|
||||
)
|
||||
|
||||
req := httptest.NewRequest("GET", "/", nil)
|
||||
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||
req.Host = "localhost:9999"
|
||||
req.Header.Set("Authorization", authHeader)
|
||||
req.Header.Set("X-Amz-Date", timestamp)
|
||||
|
||||
resp := doRequest(handler, req)
|
||||
|
||||
if resp.StatusCode != 500 {
|
||||
if resp.StatusCode != http.StatusInternalServerError {
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
t.Errorf("Expected status 500, got %d, body: %s", resp.StatusCode, string(body))
|
||||
}
|
||||
@@ -1863,14 +1865,14 @@ func TestCheckPolicy_GetUserPoliciesError_InternalError(t *testing.T) {
|
||||
testAccessKey, testSecretKey, testRegion, testService,
|
||||
)
|
||||
|
||||
req := httptest.NewRequest("GET", "/", nil)
|
||||
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||
req.Host = "localhost:9999"
|
||||
req.Header.Set("Authorization", authHeader)
|
||||
req.Header.Set("X-Amz-Date", timestamp)
|
||||
|
||||
resp := doRequest(handler, req)
|
||||
|
||||
if resp.StatusCode != 500 {
|
||||
if resp.StatusCode != http.StatusInternalServerError {
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
t.Errorf("Expected status 500, got %d, body: %s", resp.StatusCode, string(body))
|
||||
}
|
||||
@@ -1915,7 +1917,7 @@ func TestCheckPolicy_RootNonGlobalAccount_StillEvaluated(t *testing.T) {
|
||||
testAccessKey, testSecretKey, testRegion, testService,
|
||||
)
|
||||
|
||||
req := httptest.NewRequest("GET", "/", nil)
|
||||
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||
req.Host = "localhost:9999"
|
||||
req.Header.Set("Authorization", authHeader)
|
||||
req.Header.Set("X-Amz-Date", timestamp)
|
||||
@@ -1924,7 +1926,7 @@ func TestCheckPolicy_RootNonGlobalAccount_StillEvaluated(t *testing.T) {
|
||||
|
||||
// root on non-global account is evaluated by the policy engine like any
|
||||
// other user. With no policies attached, the default deny applies.
|
||||
if resp.StatusCode != 403 {
|
||||
if resp.StatusCode != http.StatusForbidden {
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
t.Errorf("Expected status 403, got %d, body: %s", resp.StatusCode, string(body))
|
||||
}
|
||||
|
||||
@@ -24,11 +24,10 @@ func ValidateDescribeImagesInput(input *ec2.DescribeImagesInput) (err error) {
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
return err
|
||||
}
|
||||
|
||||
func DescribeImages(input *ec2.DescribeImagesInput, natsConn *nats.Conn, accountID string) (output ec2.DescribeImagesOutput, err error) {
|
||||
|
||||
// Validate input
|
||||
err = ValidateDescribeImagesInput(input)
|
||||
|
||||
|
||||
@@ -57,7 +57,10 @@ func RebootInstances(input *ec2.RebootInstancesInput, natsConn *nats.Conn, accou
|
||||
describeInput := &ec2.DescribeInstancesInput{
|
||||
InstanceIds: []*string{&instanceID},
|
||||
}
|
||||
describeData, _ := json.Marshal(describeInput)
|
||||
describeData, err := json.Marshal(describeInput)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("marshal describe input: %w", err)
|
||||
}
|
||||
if reservations := queryInstanceBucket(natsConn, "ec2.DescribeStoppedInstances", describeData, accountID); len(reservations) > 0 {
|
||||
return nil, errors.New(awserrors.ErrorIncorrectInstanceState)
|
||||
}
|
||||
|
||||
@@ -53,15 +53,12 @@ func ValidateRunInstancesInput(input *ec2.RunInstancesInput) (err error) {
|
||||
|
||||
if !strings.HasPrefix(*input.ImageId, "ami-") {
|
||||
return errors.New(awserrors.ErrorInvalidAMIIDMalformed)
|
||||
|
||||
}
|
||||
|
||||
return
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func RunInstances(input *ec2.RunInstancesInput, natsConn *nats.Conn, accountID string) (reservation ec2.Reservation, err error) {
|
||||
|
||||
// Validate input
|
||||
err = ValidateRunInstancesInput(input)
|
||||
|
||||
|
||||
@@ -23,7 +23,6 @@ var defaults = ec2.RunInstancesInput{
|
||||
}
|
||||
|
||||
func TestParseRunInstances(t *testing.T) {
|
||||
|
||||
// Group multiple tests
|
||||
tests := []struct {
|
||||
name string
|
||||
@@ -214,5 +213,4 @@ func TestParseRunInstances(t *testing.T) {
|
||||
}
|
||||
|
||||
// Additional test
|
||||
|
||||
}
|
||||
|
||||
@@ -19,11 +19,10 @@ func ValidateCreateKeyPairInput(input *ec2.CreateKeyPairInput) (err error) {
|
||||
return errors.New(awserrors.ErrorMissingParameter)
|
||||
}
|
||||
|
||||
return
|
||||
return err
|
||||
}
|
||||
|
||||
func CreateKeyPair(input *ec2.CreateKeyPairInput, natsConn *nats.Conn, accountID string) (output ec2.CreateKeyPairOutput, err error) {
|
||||
|
||||
// Validate input
|
||||
err = ValidateCreateKeyPairInput(input)
|
||||
|
||||
|
||||
@@ -20,11 +20,10 @@ func ValidateDeleteKeyPairInput(input *ec2.DeleteKeyPairInput) (err error) {
|
||||
return errors.New(awserrors.ErrorMissingParameter)
|
||||
}
|
||||
|
||||
return
|
||||
return err
|
||||
}
|
||||
|
||||
func DeleteKeyPair(input *ec2.DeleteKeyPairInput, natsConn *nats.Conn, accountID string) (output ec2.DeleteKeyPairOutput, err error) {
|
||||
|
||||
// Validate input
|
||||
err = ValidateDeleteKeyPairInput(input)
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ func ValidateImportKeyPairInput(input *ec2.ImportKeyPairInput) (err error) {
|
||||
return errors.New(awserrors.ErrorMissingParameter)
|
||||
}
|
||||
|
||||
return
|
||||
return err
|
||||
}
|
||||
|
||||
func ImportKeyPair(input *ec2.ImportKeyPairInput, natsConn *nats.Conn, accountID string) (output ec2.ImportKeyPairOutput, err error) {
|
||||
|
||||
@@ -39,7 +39,7 @@ func DetachVolume(input *ec2.DetachVolumeInput, natsConn *nats.Conn, accountID s
|
||||
volumeID := *input.VolumeId
|
||||
|
||||
// Resolve InstanceId if not provided by looking up volume metadata
|
||||
instanceID := ""
|
||||
var instanceID string
|
||||
if input.InstanceId != nil && *input.InstanceId != "" {
|
||||
instanceID = *input.InstanceId
|
||||
} else {
|
||||
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
)
|
||||
|
||||
func DescribeAvailabilityZones(input *ec2.DescribeAvailabilityZonesInput, region string, az string) (output *ec2.DescribeAvailabilityZonesOutput, err error) {
|
||||
|
||||
output = &ec2.DescribeAvailabilityZonesOutput{
|
||||
AvailabilityZones: []*ec2.AvailabilityZone{
|
||||
{
|
||||
@@ -27,7 +26,6 @@ func DescribeAvailabilityZones(input *ec2.DescribeAvailabilityZonesInput, region
|
||||
}
|
||||
|
||||
func DescribeRegions(input *ec2.DescribeRegionsInput, region string) (output *ec2.DescribeRegionsOutput, err error) {
|
||||
|
||||
output = &ec2.DescribeRegionsOutput{
|
||||
Regions: []*ec2.Region{
|
||||
{
|
||||
|
||||
@@ -68,7 +68,6 @@ type ErrorDetail struct {
|
||||
}
|
||||
|
||||
func (gw *GatewayConfig) SetupRoutes() http.Handler {
|
||||
|
||||
var logLevel slog.Level
|
||||
|
||||
if gw.Debug {
|
||||
@@ -159,7 +158,7 @@ func corsMiddleware(next http.Handler) http.Handler {
|
||||
w.Header().Set("Access-Control-Allow-Methods", "GET,POST,PUT,DELETE,HEAD,OPTIONS")
|
||||
w.Header().Set("Access-Control-Allow-Headers", "*")
|
||||
w.Header().Set("Access-Control-Allow-Credentials", "true")
|
||||
if r.Method == "OPTIONS" {
|
||||
if r.Method == http.MethodOptions {
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
return
|
||||
}
|
||||
@@ -173,7 +172,6 @@ func corsMiddleware(next http.Handler) http.Handler {
|
||||
// AWS_ENDPOINT_URL=https://localhost:9999/ aws --no-verify-ssl ec2 describe-instances
|
||||
|
||||
func (gw *GatewayConfig) Request(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// Route the request to the appropriate endpoint (e.g EC2, IAM, etc)
|
||||
svc, err := gw.GetService(r)
|
||||
slog.Info("Request", "service", svc, "method", r.Method, "path", r.URL.Path)
|
||||
@@ -291,7 +289,7 @@ func (gw *GatewayConfig) ErrorHandler(w http.ResponseWriter, r *http.Request, er
|
||||
|
||||
// Get the request ID
|
||||
var requestId = uuid.NewString()
|
||||
if rid := r.Header.Get("x-amz-request-id"); rid != "" {
|
||||
if rid := r.Header.Get("X-Amz-Request-Id"); rid != "" {
|
||||
requestId = rid
|
||||
}
|
||||
|
||||
@@ -346,7 +344,6 @@ func ParseAWSQueryArgs(query string) map[string]string {
|
||||
}
|
||||
|
||||
func GenerateEC2ErrorResponse(code, message, requestID string) (output []byte) {
|
||||
|
||||
errorXml := ErrorResponse{
|
||||
Errors: Errors{
|
||||
Error: ErrorDetail{
|
||||
@@ -405,7 +402,6 @@ func GenerateIAMErrorResponse(code, message, requestID string) (output []byte) {
|
||||
}
|
||||
|
||||
func ParseArgsToStruct(input *any, args map[string]string) (err error) {
|
||||
|
||||
// Generated from input shape: RunInstancesRequest
|
||||
err = awsec2query.QueryParamsToStruct(args, input)
|
||||
|
||||
@@ -414,7 +410,6 @@ func ParseArgsToStruct(input *any, args map[string]string) (err error) {
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
// DiscoverActiveNodes discovers the number of active spinifex daemon nodes in the cluster
|
||||
@@ -487,6 +482,7 @@ func (gw *GatewayConfig) DiscoverActiveNodes() int {
|
||||
// statusWriter wraps http.ResponseWriter to capture the status code.
|
||||
type statusWriter struct {
|
||||
http.ResponseWriter
|
||||
|
||||
status int
|
||||
}
|
||||
|
||||
|
||||
@@ -175,7 +175,7 @@ func TestErrorHandler_IAMService(t *testing.T) {
|
||||
gw.ErrorHandler(w, r, errors.New(awserrors.ErrorIAMNoSuchEntity))
|
||||
})
|
||||
|
||||
req := httptest.NewRequest("GET", "/", nil)
|
||||
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||
resp := doRequest(handler, req)
|
||||
assert.Equal(t, 404, resp.StatusCode)
|
||||
|
||||
@@ -197,7 +197,7 @@ func TestErrorHandler_UnknownError(t *testing.T) {
|
||||
gw.ErrorHandler(w, r, errors.New("SomeCompletelyBogusError"))
|
||||
})
|
||||
|
||||
req := httptest.NewRequest("POST", "/", nil)
|
||||
req := httptest.NewRequest(http.MethodPost, "/", nil)
|
||||
resp := doRequest(handler, req)
|
||||
assert.Equal(t, 500, resp.StatusCode)
|
||||
|
||||
@@ -218,7 +218,7 @@ func TestErrorHandler_EC2Service(t *testing.T) {
|
||||
gw.ErrorHandler(w, r, errors.New(awserrors.ErrorInvalidParameterValue))
|
||||
})
|
||||
|
||||
req := httptest.NewRequest("POST", "/", nil)
|
||||
req := httptest.NewRequest(http.MethodPost, "/", nil)
|
||||
resp := doRequest(handler, req)
|
||||
assert.Equal(t, 400, resp.StatusCode)
|
||||
|
||||
@@ -240,8 +240,8 @@ func TestErrorHandler_UsesRequestIDHeader(t *testing.T) {
|
||||
gw.ErrorHandler(w, r, errors.New(awserrors.ErrorInternalError))
|
||||
})
|
||||
|
||||
req := httptest.NewRequest("POST", "/", nil)
|
||||
req.Header.Set("x-amz-request-id", "custom-req-id-123")
|
||||
req := httptest.NewRequest(http.MethodPost, "/", nil)
|
||||
req.Header.Set("X-Amz-Request-Id", "custom-req-id-123")
|
||||
resp := doRequest(handler, req)
|
||||
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
@@ -257,7 +257,7 @@ func TestErrorHandler_ContentTypeXML(t *testing.T) {
|
||||
gw.ErrorHandler(w, r, errors.New(awserrors.ErrorInternalError))
|
||||
})
|
||||
|
||||
req := httptest.NewRequest("POST", "/", nil)
|
||||
req := httptest.NewRequest(http.MethodPost, "/", nil)
|
||||
resp := doRequest(handler, req)
|
||||
assert.Equal(t, "application/xml", resp.Header.Get("Content-Type"))
|
||||
}
|
||||
@@ -493,7 +493,7 @@ func TestGetService(t *testing.T) {
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
req := httptest.NewRequest("POST", "/", nil)
|
||||
req := httptest.NewRequest(http.MethodPost, "/", nil)
|
||||
if tc.ctxVal != nil {
|
||||
ctx := context.WithValue(req.Context(), ctxService, tc.ctxVal)
|
||||
req = req.WithContext(ctx)
|
||||
@@ -514,7 +514,7 @@ func TestGetService(t *testing.T) {
|
||||
|
||||
func TestRequest_NoServiceContext(t *testing.T) {
|
||||
gw := &GatewayConfig{DisableLogging: true}
|
||||
req := httptest.NewRequest("POST", "/", nil)
|
||||
req := httptest.NewRequest(http.MethodPost, "/", nil)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
gw.Request(w, req)
|
||||
@@ -527,7 +527,7 @@ func TestRequest_NoServiceContext(t *testing.T) {
|
||||
|
||||
func TestRequest_UnsupportedService(t *testing.T) {
|
||||
gw := &GatewayConfig{DisableLogging: true}
|
||||
req := httptest.NewRequest("POST", "/", nil)
|
||||
req := httptest.NewRequest(http.MethodPost, "/", nil)
|
||||
ctx := context.WithValue(req.Context(), ctxService, "s3")
|
||||
req = req.WithContext(ctx)
|
||||
w := httptest.NewRecorder()
|
||||
@@ -542,7 +542,7 @@ func TestRequest_UnsupportedService(t *testing.T) {
|
||||
|
||||
func TestRequest_EC2MissingAction(t *testing.T) {
|
||||
gw := &GatewayConfig{DisableLogging: true}
|
||||
req := httptest.NewRequest("POST", "/", strings.NewReader(""))
|
||||
req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(""))
|
||||
ctx := context.WithValue(req.Context(), ctxService, "ec2")
|
||||
ctx = context.WithValue(ctx, ctxAccountID, "123456789012")
|
||||
req = req.WithContext(ctx)
|
||||
@@ -558,7 +558,7 @@ func TestRequest_EC2MissingAction(t *testing.T) {
|
||||
|
||||
func TestRequest_IAMNilService(t *testing.T) {
|
||||
gw := &GatewayConfig{DisableLogging: true, IAMService: nil}
|
||||
req := httptest.NewRequest("POST", "/", strings.NewReader("Action=CreateUser&UserName=test"))
|
||||
req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader("Action=CreateUser&UserName=test"))
|
||||
ctx := context.WithValue(req.Context(), ctxService, "iam")
|
||||
ctx = context.WithValue(ctx, ctxAccountID, "123456789012")
|
||||
req = req.WithContext(ctx)
|
||||
@@ -574,7 +574,7 @@ func TestRequest_IAMNilService(t *testing.T) {
|
||||
|
||||
func TestRequest_AccountReturns200(t *testing.T) {
|
||||
gw := &GatewayConfig{DisableLogging: true}
|
||||
req := httptest.NewRequest("POST", "/", nil)
|
||||
req := httptest.NewRequest(http.MethodPost, "/", nil)
|
||||
ctx := context.WithValue(req.Context(), ctxService, "account")
|
||||
req = req.WithContext(ctx)
|
||||
w := httptest.NewRecorder()
|
||||
@@ -587,7 +587,7 @@ func TestRequest_AccountReturns200(t *testing.T) {
|
||||
|
||||
// setupEC2Request creates an http.Request with EC2 service context and optional account ID.
|
||||
func setupEC2Request(body string, accountID string) *http.Request {
|
||||
req := httptest.NewRequest("POST", "/", strings.NewReader(body))
|
||||
req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(body))
|
||||
ctx := context.WithValue(req.Context(), ctxService, "ec2")
|
||||
if accountID != "" {
|
||||
ctx = context.WithValue(ctx, ctxAccountID, accountID)
|
||||
@@ -697,7 +697,7 @@ func TestEC2Request_DescribeAvailabilityZones(t *testing.T) {
|
||||
|
||||
func TestCheckPolicy_NilIAMService(t *testing.T) {
|
||||
gw := &GatewayConfig{DisableLogging: true, IAMService: nil}
|
||||
req := httptest.NewRequest("POST", "/", nil)
|
||||
req := httptest.NewRequest(http.MethodPost, "/", nil)
|
||||
|
||||
err := gw.checkPolicy(req, "ec2", "DescribeInstances")
|
||||
assert.NoError(t, err)
|
||||
@@ -705,7 +705,7 @@ func TestCheckPolicy_NilIAMService(t *testing.T) {
|
||||
|
||||
func TestCheckPolicy_NoIdentityInContext(t *testing.T) {
|
||||
gw := &GatewayConfig{DisableLogging: true, IAMService: &mockIAMService{}}
|
||||
req := httptest.NewRequest("POST", "/", nil)
|
||||
req := httptest.NewRequest(http.MethodPost, "/", nil)
|
||||
|
||||
err := gw.checkPolicy(req, "ec2", "DescribeInstances")
|
||||
assert.NoError(t, err)
|
||||
@@ -713,7 +713,7 @@ func TestCheckPolicy_NoIdentityInContext(t *testing.T) {
|
||||
|
||||
func TestCheckPolicy_RootUserGlobalAccount(t *testing.T) {
|
||||
gw := &GatewayConfig{DisableLogging: true, IAMService: &mockIAMService{}}
|
||||
req := httptest.NewRequest("POST", "/", nil)
|
||||
req := httptest.NewRequest(http.MethodPost, "/", nil)
|
||||
ctx := context.WithValue(req.Context(), ctxIdentity, "root")
|
||||
ctx = context.WithValue(ctx, ctxAccountID, "000000000000") // GlobalAccountID
|
||||
req = req.WithContext(ctx)
|
||||
@@ -736,7 +736,7 @@ func TestCheckPolicy_NonRootAllowPolicy(t *testing.T) {
|
||||
},
|
||||
}
|
||||
gw := &GatewayConfig{DisableLogging: true, IAMService: mock}
|
||||
req := httptest.NewRequest("POST", "/", nil)
|
||||
req := httptest.NewRequest(http.MethodPost, "/", nil)
|
||||
ctx := context.WithValue(req.Context(), ctxIdentity, "alice")
|
||||
ctx = context.WithValue(ctx, ctxAccountID, "123456789012")
|
||||
req = req.WithContext(ctx)
|
||||
@@ -759,7 +759,7 @@ func TestCheckPolicy_NonRootDenyPolicy(t *testing.T) {
|
||||
},
|
||||
}
|
||||
gw := &GatewayConfig{DisableLogging: true, IAMService: mock}
|
||||
req := httptest.NewRequest("POST", "/", nil)
|
||||
req := httptest.NewRequest(http.MethodPost, "/", nil)
|
||||
ctx := context.WithValue(req.Context(), ctxIdentity, "alice")
|
||||
ctx = context.WithValue(ctx, ctxAccountID, "123456789012")
|
||||
req = req.WithContext(ctx)
|
||||
@@ -776,7 +776,7 @@ func TestCheckPolicy_NonRootNoPolicies(t *testing.T) {
|
||||
},
|
||||
}
|
||||
gw := &GatewayConfig{DisableLogging: true, IAMService: mock}
|
||||
req := httptest.NewRequest("POST", "/", nil)
|
||||
req := httptest.NewRequest(http.MethodPost, "/", nil)
|
||||
ctx := context.WithValue(req.Context(), ctxIdentity, "alice")
|
||||
ctx = context.WithValue(ctx, ctxAccountID, "123456789012")
|
||||
req = req.WithContext(ctx)
|
||||
@@ -793,7 +793,7 @@ func TestCheckPolicy_GetUserPoliciesError(t *testing.T) {
|
||||
},
|
||||
}
|
||||
gw := &GatewayConfig{DisableLogging: true, IAMService: mock}
|
||||
req := httptest.NewRequest("POST", "/", nil)
|
||||
req := httptest.NewRequest(http.MethodPost, "/", nil)
|
||||
ctx := context.WithValue(req.Context(), ctxIdentity, "alice")
|
||||
ctx = context.WithValue(ctx, ctxAccountID, "123456789012")
|
||||
req = req.WithContext(ctx)
|
||||
@@ -805,7 +805,7 @@ func TestCheckPolicy_GetUserPoliciesError(t *testing.T) {
|
||||
|
||||
func TestCheckPolicy_EmptyIdentity(t *testing.T) {
|
||||
gw := &GatewayConfig{DisableLogging: true, IAMService: &mockIAMService{}}
|
||||
req := httptest.NewRequest("POST", "/", nil)
|
||||
req := httptest.NewRequest(http.MethodPost, "/", nil)
|
||||
ctx := context.WithValue(req.Context(), ctxIdentity, "")
|
||||
ctx = context.WithValue(ctx, ctxAccountID, "123456789012")
|
||||
req = req.WithContext(ctx)
|
||||
@@ -816,7 +816,7 @@ func TestCheckPolicy_EmptyIdentity(t *testing.T) {
|
||||
|
||||
func TestCheckPolicy_MissingAccountID(t *testing.T) {
|
||||
gw := &GatewayConfig{DisableLogging: true, IAMService: &mockIAMService{}}
|
||||
req := httptest.NewRequest("POST", "/", nil)
|
||||
req := httptest.NewRequest(http.MethodPost, "/", nil)
|
||||
ctx := context.WithValue(req.Context(), ctxIdentity, "alice")
|
||||
// No account ID
|
||||
req = req.WithContext(ctx)
|
||||
@@ -835,7 +835,7 @@ func TestCheckPolicy_NATSTransientRetriesAllAttempts(t *testing.T) {
|
||||
},
|
||||
}
|
||||
gw := &GatewayConfig{DisableLogging: true, IAMService: mock}
|
||||
req := httptest.NewRequest("POST", "/", nil)
|
||||
req := httptest.NewRequest(http.MethodPost, "/", nil)
|
||||
ctx := context.WithValue(req.Context(), ctxIdentity, "alice")
|
||||
ctx = context.WithValue(ctx, ctxAccountID, "123456789012")
|
||||
req = req.WithContext(ctx)
|
||||
@@ -865,7 +865,7 @@ func TestCheckPolicy_NATSTransientRetriesThenSucceeds(t *testing.T) {
|
||||
},
|
||||
}
|
||||
gw := &GatewayConfig{DisableLogging: true, IAMService: mock}
|
||||
req := httptest.NewRequest("POST", "/", nil)
|
||||
req := httptest.NewRequest(http.MethodPost, "/", nil)
|
||||
ctx := context.WithValue(req.Context(), ctxIdentity, "alice")
|
||||
ctx = context.WithValue(ctx, ctxAccountID, "123456789012")
|
||||
req = req.WithContext(ctx)
|
||||
@@ -882,7 +882,7 @@ func TestCheckPolicy_NonTransientErrorStillFails(t *testing.T) {
|
||||
},
|
||||
}
|
||||
gw := &GatewayConfig{DisableLogging: true, IAMService: mock}
|
||||
req := httptest.NewRequest("POST", "/", nil)
|
||||
req := httptest.NewRequest(http.MethodPost, "/", nil)
|
||||
ctx := context.WithValue(req.Context(), ctxIdentity, "alice")
|
||||
ctx = context.WithValue(ctx, ctxAccountID, "123456789012")
|
||||
req = req.WithContext(ctx)
|
||||
@@ -904,11 +904,11 @@ func TestCorsMiddleware_GETRequest(t *testing.T) {
|
||||
nextCalled := false
|
||||
next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
nextCalled = true
|
||||
w.WriteHeader(200)
|
||||
w.WriteHeader(http.StatusOK)
|
||||
})
|
||||
|
||||
handler := corsMiddleware(next)
|
||||
req := httptest.NewRequest("GET", "/", nil)
|
||||
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||
w := httptest.NewRecorder()
|
||||
handler.ServeHTTP(w, req)
|
||||
|
||||
@@ -926,7 +926,7 @@ func TestCorsMiddleware_OPTIONSRequest(t *testing.T) {
|
||||
})
|
||||
|
||||
handler := corsMiddleware(next)
|
||||
req := httptest.NewRequest("OPTIONS", "/", nil)
|
||||
req := httptest.NewRequest(http.MethodOptions, "/", nil)
|
||||
w := httptest.NewRecorder()
|
||||
handler.ServeHTTP(w, req)
|
||||
|
||||
@@ -937,7 +937,7 @@ func TestCorsMiddleware_OPTIONSRequest(t *testing.T) {
|
||||
|
||||
func TestCorsMiddleware_LocalIPOrigin(t *testing.T) {
|
||||
next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(200)
|
||||
w.WriteHeader(http.StatusOK)
|
||||
})
|
||||
|
||||
handler := corsMiddleware(next)
|
||||
@@ -948,7 +948,7 @@ func TestCorsMiddleware_LocalIPOrigin(t *testing.T) {
|
||||
if origin == "https://localhost:3000" {
|
||||
continue
|
||||
}
|
||||
req := httptest.NewRequest("GET", "/", nil)
|
||||
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||
req.Header.Set("Origin", origin)
|
||||
w := httptest.NewRecorder()
|
||||
handler.ServeHTTP(w, req)
|
||||
@@ -959,11 +959,11 @@ func TestCorsMiddleware_LocalIPOrigin(t *testing.T) {
|
||||
|
||||
func TestCorsMiddleware_UnknownOrigin(t *testing.T) {
|
||||
next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(200)
|
||||
w.WriteHeader(http.StatusOK)
|
||||
})
|
||||
|
||||
handler := corsMiddleware(next)
|
||||
req := httptest.NewRequest("GET", "/", nil)
|
||||
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||
req.Header.Set("Origin", "https://evil.example.com")
|
||||
w := httptest.NewRecorder()
|
||||
handler.ServeHTTP(w, req)
|
||||
@@ -979,7 +979,7 @@ func TestSlogRequestLogger_CallsNext(t *testing.T) {
|
||||
})
|
||||
|
||||
handler := slogRequestLogger(next)
|
||||
req := httptest.NewRequest("GET", "/test", nil)
|
||||
req := httptest.NewRequest(http.MethodGet, "/test", nil)
|
||||
w := httptest.NewRecorder()
|
||||
handler.ServeHTTP(w, req)
|
||||
|
||||
@@ -993,7 +993,7 @@ func TestSlogRequestLogger_CapturesStatusCode(t *testing.T) {
|
||||
})
|
||||
|
||||
handler := slogRequestLogger(next)
|
||||
req := httptest.NewRequest("GET", "/missing", nil)
|
||||
req := httptest.NewRequest(http.MethodGet, "/missing", nil)
|
||||
w := httptest.NewRecorder()
|
||||
handler.ServeHTTP(w, req)
|
||||
|
||||
|
||||
@@ -167,7 +167,7 @@ func TestIAMRequest_CreateUser_Success(t *testing.T) {
|
||||
}
|
||||
handler := setupIAMRequestHandler(svc)
|
||||
|
||||
req := httptest.NewRequest("POST", "/", strings.NewReader("Action=CreateUser&UserName=alice"))
|
||||
req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader("Action=CreateUser&UserName=alice"))
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
|
||||
resp := doRequest(handler, req)
|
||||
@@ -193,7 +193,7 @@ func TestIAMRequest_ListUsers_Success(t *testing.T) {
|
||||
}
|
||||
handler := setupIAMRequestHandler(svc)
|
||||
|
||||
req := httptest.NewRequest("POST", "/", strings.NewReader("Action=ListUsers"))
|
||||
req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader("Action=ListUsers"))
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
|
||||
resp := doRequest(handler, req)
|
||||
@@ -209,7 +209,7 @@ func TestIAMRequest_ListUsers_Success(t *testing.T) {
|
||||
func TestIAMRequest_UnknownAction(t *testing.T) {
|
||||
handler := setupIAMRequestHandler(&flexMockIAMService{})
|
||||
|
||||
req := httptest.NewRequest("POST", "/", strings.NewReader("Action=DoesNotExist"))
|
||||
req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader("Action=DoesNotExist"))
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
|
||||
resp := doRequest(handler, req)
|
||||
@@ -222,7 +222,7 @@ func TestIAMRequest_UnknownAction(t *testing.T) {
|
||||
func TestIAMRequest_EmptyAction(t *testing.T) {
|
||||
handler := setupIAMRequestHandler(&flexMockIAMService{})
|
||||
|
||||
req := httptest.NewRequest("POST", "/", strings.NewReader("UserName=alice"))
|
||||
req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader("UserName=alice"))
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
|
||||
resp := doRequest(handler, req)
|
||||
@@ -247,7 +247,7 @@ func TestIAMRequest_NilService(t *testing.T) {
|
||||
}
|
||||
})
|
||||
|
||||
req := httptest.NewRequest("POST", "/", strings.NewReader("Action=CreateUser&UserName=alice"))
|
||||
req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader("Action=CreateUser&UserName=alice"))
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
|
||||
resp := doRequest(handler, req)
|
||||
@@ -265,7 +265,7 @@ func TestIAMRequest_ServiceError(t *testing.T) {
|
||||
}
|
||||
handler := setupIAMRequestHandler(svc)
|
||||
|
||||
req := httptest.NewRequest("POST", "/", strings.NewReader("Action=CreateUser&UserName=alice"))
|
||||
req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader("Action=CreateUser&UserName=alice"))
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
|
||||
resp := doRequest(handler, req)
|
||||
@@ -291,7 +291,7 @@ func TestIAMRequest_MissingAccountID(t *testing.T) {
|
||||
}
|
||||
})
|
||||
|
||||
req := httptest.NewRequest("POST", "/", strings.NewReader("Action=CreateUser&UserName=alice"))
|
||||
req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader("Action=CreateUser&UserName=alice"))
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
|
||||
resp := doRequest(handler, req)
|
||||
@@ -305,7 +305,7 @@ func TestIAMRequest_ValidationError(t *testing.T) {
|
||||
// CreateUser with missing UserName should return MissingParameter
|
||||
handler := setupIAMRequestHandler(&flexMockIAMService{})
|
||||
|
||||
req := httptest.NewRequest("POST", "/", strings.NewReader("Action=CreateUser"))
|
||||
req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader("Action=CreateUser"))
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
|
||||
resp := doRequest(handler, req)
|
||||
|
||||
@@ -31,7 +31,7 @@ func EvaluateAccess(identity, action, resource string, policies []handlers_iam.P
|
||||
hasAllow := false
|
||||
for i := range policies {
|
||||
for j := range policies[i].Statement {
|
||||
stmt := &policies[i].Statement[j]
|
||||
stmt := &policies[i].Statement[j] //nolint:gosec // G602 false positive: j is bounded by range
|
||||
|
||||
if !matchesAny(stmt.Action, action) {
|
||||
continue
|
||||
|
||||
@@ -259,8 +259,10 @@ func (s *IGWServiceImpl) AttachInternetGateway(input *ec2.AttachInternetGatewayI
|
||||
InternetGatewayId: igwID,
|
||||
VpcId: vpcID,
|
||||
}
|
||||
eventData, _ := json.Marshal(event)
|
||||
if err := s.natsConn.Publish("vpc.igw-attach", eventData); err != nil {
|
||||
eventData, err := json.Marshal(event)
|
||||
if err != nil {
|
||||
slog.Warn("Failed to marshal IGW attach event", "error", err)
|
||||
} else if err := s.natsConn.Publish("vpc.igw-attach", eventData); err != nil {
|
||||
slog.Warn("Failed to publish IGW attach event", "error", err)
|
||||
}
|
||||
}
|
||||
@@ -315,8 +317,10 @@ func (s *IGWServiceImpl) DetachInternetGateway(input *ec2.DetachInternetGatewayI
|
||||
InternetGatewayId: igwID,
|
||||
VpcId: vpcID,
|
||||
}
|
||||
eventData, _ := json.Marshal(event)
|
||||
if err := s.natsConn.Publish("vpc.igw-detach", eventData); err != nil {
|
||||
eventData, err := json.Marshal(event)
|
||||
if err != nil {
|
||||
slog.Warn("Failed to marshal IGW detach event", "error", err)
|
||||
} else if err := s.natsConn.Publish("vpc.igw-detach", eventData); err != nil {
|
||||
slog.Warn("Failed to publish IGW detach event", "error", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -297,7 +297,6 @@ func (s *InstanceServiceImpl) GenerateVolumes(input *ec2.RunInstancesInput, inst
|
||||
}
|
||||
|
||||
return volumeInfos, nil
|
||||
|
||||
}
|
||||
|
||||
// newViperblock creates a viperblock instance with the service's S3/Predastore credentials.
|
||||
|
||||
@@ -620,7 +620,7 @@ func (s *VolumeServiceImpl) mergeVolumeConfig(configKey string, cfg *viperblock.
|
||||
|
||||
// Full VBState exists -- update VolumeConfig and reconcile VolumeSize
|
||||
state.VolumeConfig = *cfg
|
||||
configSizeBytes := uint64(cfg.VolumeMetadata.SizeGiB) * 1024 * 1024 * 1024
|
||||
configSizeBytes := cfg.VolumeMetadata.SizeGiB * 1024 * 1024 * 1024
|
||||
if configSizeBytes > 0 && configSizeBytes > state.VolumeSize {
|
||||
state.VolumeSize = configSizeBytes
|
||||
}
|
||||
|
||||
@@ -569,7 +569,7 @@ func createVolumeInStoreWithVBState(t *testing.T, store *objectstore.MemoryObjec
|
||||
t.Helper()
|
||||
state := viperblock.VBState{
|
||||
VolumeName: volumeID,
|
||||
VolumeSize: uint64(meta.SizeGiB) * 1024 * 1024 * 1024,
|
||||
VolumeSize: meta.SizeGiB * 1024 * 1024 * 1024,
|
||||
BlockSize: blockSize,
|
||||
SeqNum: seqNum,
|
||||
VolumeConfig: viperblock.VolumeConfig{
|
||||
|
||||
@@ -134,7 +134,10 @@ func (s *VPCServiceImpl) nextVNI() (int64, error) {
|
||||
if errors.Is(err, nats.ErrKeyNotFound) {
|
||||
// First VNI allocation — initialize counter
|
||||
vni := int64(vniStart)
|
||||
data, _ := json.Marshal(vni + 1)
|
||||
data, marshalErr := json.Marshal(vni + 1)
|
||||
if marshalErr != nil {
|
||||
return 0, fmt.Errorf("failed to marshal VNI counter: %w", marshalErr)
|
||||
}
|
||||
if _, err := s.vniKV.Create(vniCounterKey, data); err != nil {
|
||||
return 0, fmt.Errorf("failed to initialize VNI counter: %w", err)
|
||||
}
|
||||
@@ -149,7 +152,10 @@ func (s *VPCServiceImpl) nextVNI() (int64, error) {
|
||||
}
|
||||
|
||||
next := current + 1
|
||||
data, _ := json.Marshal(next)
|
||||
data, marshalErr := json.Marshal(next)
|
||||
if marshalErr != nil {
|
||||
return 0, fmt.Errorf("failed to marshal VNI counter: %w", marshalErr)
|
||||
}
|
||||
if _, err := s.vniKV.Update(vniCounterKey, data, entry.Revision()); err != nil {
|
||||
return 0, fmt.Errorf("failed to update VNI counter (CAS conflict): %w", err)
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ package handlers_iam
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"log/slog"
|
||||
"strings"
|
||||
"testing"
|
||||
@@ -790,7 +789,7 @@ func TestSeedBootstrap_WithAdmin(t *testing.T) {
|
||||
policies, err := svc.GetUserPolicies("000000000001", "admin")
|
||||
require.NoError(t, err)
|
||||
require.Len(t, policies, 1)
|
||||
assert.Equal(t, "Allow", string(policies[0].Statement[0].Effect))
|
||||
assert.Equal(t, "Allow", policies[0].Statement[0].Effect)
|
||||
assert.Equal(t, StringOrArr{"*"}, policies[0].Statement[0].Action)
|
||||
assert.Equal(t, StringOrArr{"*"}, policies[0].Statement[0].Resource)
|
||||
|
||||
@@ -1556,7 +1555,7 @@ func TestSensitiveDataNotLogged_CreateAccessKey(t *testing.T) {
|
||||
logger := slog.New(slog.NewTextHandler(&buf, &slog.HandlerOptions{Level: slog.LevelDebug}))
|
||||
slog.SetDefault(logger)
|
||||
t.Cleanup(func() {
|
||||
slog.SetDefault(slog.New(slog.NewTextHandler(io.Discard, nil)))
|
||||
slog.SetDefault(slog.New(slog.DiscardHandler))
|
||||
})
|
||||
|
||||
akOut, err := svc.CreateAccessKey(testAccountID, &iam.CreateAccessKeyInput{
|
||||
@@ -1584,7 +1583,7 @@ func TestSensitiveDataNotLogged_MasterKey(t *testing.T) {
|
||||
logger := slog.New(slog.NewTextHandler(&buf, &slog.HandlerOptions{Level: slog.LevelDebug}))
|
||||
slog.SetDefault(logger)
|
||||
t.Cleanup(func() {
|
||||
slog.SetDefault(slog.New(slog.NewTextHandler(io.Discard, nil)))
|
||||
slog.SetDefault(slog.New(slog.DiscardHandler))
|
||||
})
|
||||
|
||||
svc := setupTestIAMService(t)
|
||||
|
||||
@@ -21,7 +21,6 @@ type Service interface {
|
||||
}
|
||||
|
||||
func New(btype string, config any) (Service, error) {
|
||||
|
||||
switch btype {
|
||||
case "nats":
|
||||
return nats.New(config)
|
||||
@@ -43,7 +42,6 @@ func New(btype string, config any) (Service, error) {
|
||||
|
||||
case "vpcd":
|
||||
return vpcd.New(config)
|
||||
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("unknown service type: %s", btype)
|
||||
|
||||
@@ -15,12 +15,10 @@ func TestNew(t *testing.T) {
|
||||
services := []string{"nats", "predastore", "viperblock", "spinifex", "awsgw"}
|
||||
|
||||
for _, s := range services {
|
||||
|
||||
var svc Service
|
||||
var err error
|
||||
|
||||
switch s {
|
||||
|
||||
// TODO: Standardize service config handling (use config.Config for all?)
|
||||
case "nats":
|
||||
svc, err = New(s, &nats.Config{})
|
||||
@@ -34,7 +32,6 @@ func TestNew(t *testing.T) {
|
||||
case "awsgw":
|
||||
svc, err = New(s, &config.ClusterConfig{})
|
||||
// No special setup needed
|
||||
|
||||
}
|
||||
|
||||
assert.NoError(t, err)
|
||||
|
||||
@@ -70,7 +70,6 @@ func (svc *Service) Reload() (err error) {
|
||||
}
|
||||
|
||||
func launchService(config *config.ClusterConfig) error {
|
||||
|
||||
nodeConfig := config.Nodes[config.Node]
|
||||
|
||||
// Connect to NATS for service communication. On concurrent startup the
|
||||
|
||||
@@ -72,20 +72,17 @@ func (svc *Service) Reload() (err error) {
|
||||
|
||||
func launchService(config *Config) (err error) {
|
||||
// Create proper server options
|
||||
opts := &server.Options{}
|
||||
var opts *server.Options
|
||||
|
||||
// If configFile set use, otherwise set defaults
|
||||
if config.ConfigFile != "" {
|
||||
|
||||
opts, err = server.ProcessConfigFile(config.ConfigFile)
|
||||
|
||||
if err != nil {
|
||||
slog.Error("Failed to process NATS config file", "err", err)
|
||||
return err
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
opts = &server.Options{
|
||||
ConfigFile: config.ConfigFile,
|
||||
Port: config.Port,
|
||||
@@ -103,7 +100,6 @@ func launchService(config *Config) (err error) {
|
||||
if opts.Host == "" {
|
||||
opts.Host = "0.0.0.0"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
slog.Debug("NATS server options", "opts", opts)
|
||||
@@ -127,5 +123,4 @@ func launchService(config *Config) (err error) {
|
||||
ns.WaitForShutdown()
|
||||
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
@@ -125,7 +125,7 @@ func startPredastoreServer(t *testing.T) *Config {
|
||||
}
|
||||
|
||||
// Create persistent test environment (not using t.TempDir() for shared server)
|
||||
testDir, err := os.MkdirTemp("", "predastore-integration-*")
|
||||
testDir, err := os.MkdirTemp("", "predastore-integration-*") //nolint:usetesting // shared server outlives individual test
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create temp dir: %v", err)
|
||||
}
|
||||
@@ -666,7 +666,7 @@ func TestIntegration_LargeFile(t *testing.T) {
|
||||
downloadedData, err := io.ReadAll(result.Body)
|
||||
result.Body.Close()
|
||||
assert.NoError(t, err, "Reading large file should succeed")
|
||||
assert.Equal(t, size, len(downloadedData), "Downloaded size should match")
|
||||
assert.Len(t, downloadedData, size, "Downloaded size should match")
|
||||
assert.Equal(t, largeData, downloadedData, "Downloaded content should match")
|
||||
|
||||
// Cleanup
|
||||
|
||||
@@ -82,5 +82,4 @@ func launchService(config *config.ClusterConfig, configPath string) (err error)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
@@ -314,6 +314,7 @@ var gzipContentTypes = map[string]bool{
|
||||
// gzipResponseWriter wraps http.ResponseWriter to compress eligible responses.
|
||||
type gzipResponseWriter struct {
|
||||
http.ResponseWriter
|
||||
|
||||
gw *gzip.Writer
|
||||
wroteHeader bool
|
||||
compress bool
|
||||
@@ -370,6 +371,7 @@ const tlsRecordTypeHandshake = 0x16
|
||||
// a plain-HTTP redirect to HTTPS is sent and the connection is closed.
|
||||
type tlsSplitListener struct {
|
||||
net.Listener
|
||||
|
||||
port int
|
||||
tlsCfg *tls.Config
|
||||
}
|
||||
@@ -445,6 +447,7 @@ func (ln *tlsSplitListener) redirectHTTP(conn net.Conn, firstByte byte) {
|
||||
// prefixConn wraps a net.Conn with a reader that replays prefixed bytes.
|
||||
type prefixConn struct {
|
||||
net.Conn
|
||||
|
||||
r io.Reader
|
||||
}
|
||||
|
||||
|
||||
@@ -158,7 +158,6 @@ func (svc *Service) Reload() (err error) {
|
||||
}
|
||||
|
||||
func launchService(cfg *Config) (err error) {
|
||||
|
||||
// Connect to NATS
|
||||
nc, err := utils.ConnectNATS(cfg.NatsHost, cfg.NatsToken)
|
||||
if err != nil {
|
||||
@@ -268,7 +267,12 @@ func launchService(cfg *Config) (err error) {
|
||||
err := json.Unmarshal(msg.Data, &ebsRequest)
|
||||
if err != nil {
|
||||
slog.Error("Failed to unmarshal ebs.unmount message", "err", err)
|
||||
errResp, _ := json.Marshal(types.EBSUnMountResponse{Error: fmt.Sprintf("bad request: %v", err)})
|
||||
errResp, marshalErr := json.Marshal(types.EBSUnMountResponse{Error: fmt.Sprintf("bad request: %v", err)})
|
||||
if marshalErr != nil {
|
||||
slog.Error("Failed to marshal ebs.unmount error response", "err", marshalErr)
|
||||
_ = msg.Respond([]byte(`{"Error":"internal marshal failure"}`))
|
||||
return
|
||||
}
|
||||
if err := msg.Respond(errResp); err != nil {
|
||||
slog.Error("Failed to respond to ebs.unmount request", "err", err)
|
||||
}
|
||||
@@ -279,7 +283,7 @@ func launchService(cfg *Config) (err error) {
|
||||
// then release before calling VB.Close() (which does heavy S3 I/O).
|
||||
var ebsResponse types.EBSUnMountResponse
|
||||
var matched MountedVolume
|
||||
var matchIdx int = -1
|
||||
var matchIdx = -1
|
||||
cfg.mu.Lock()
|
||||
for i, volume := range cfg.MountedVolumes {
|
||||
if volume.Name == ebsRequest.Name {
|
||||
@@ -335,6 +339,7 @@ func launchService(cfg *Config) (err error) {
|
||||
response, err := json.Marshal(ebsResponse)
|
||||
if err != nil {
|
||||
slog.Error("Failed to marshal response", "err", err)
|
||||
_ = msg.Respond([]byte(`{"Error":"internal marshal failure"}`))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -354,7 +359,12 @@ func launchService(cfg *Config) (err error) {
|
||||
var syncRequest types.EBSSyncRequest
|
||||
if err := json.Unmarshal(msg.Data, &syncRequest); err != nil {
|
||||
slog.Error("Failed to unmarshal ebs.sync message", "err", err)
|
||||
errResp, _ := json.Marshal(types.EBSSyncResponse{Error: fmt.Sprintf("bad request: %v", err)})
|
||||
errResp, marshalErr := json.Marshal(types.EBSSyncResponse{Error: fmt.Sprintf("bad request: %v", err)})
|
||||
if marshalErr != nil {
|
||||
slog.Error("Failed to marshal ebs.sync error response", "err", marshalErr)
|
||||
_ = msg.Respond([]byte(`{"Error":"internal marshal failure"}`))
|
||||
return
|
||||
}
|
||||
if err := msg.Respond(errResp); err != nil {
|
||||
slog.Error("Failed to respond to ebs.sync request", "err", err)
|
||||
}
|
||||
@@ -425,7 +435,12 @@ func launchService(cfg *Config) (err error) {
|
||||
err := json.Unmarshal(msg.Data, &ebsRequest)
|
||||
if err != nil {
|
||||
slog.Error("Failed to unmarshal ebs.mount message", "err", err)
|
||||
errResp, _ := json.Marshal(types.EBSMountResponse{Error: fmt.Sprintf("bad request: %v", err)})
|
||||
errResp, marshalErr := json.Marshal(types.EBSMountResponse{Error: fmt.Sprintf("bad request: %v", err)})
|
||||
if marshalErr != nil {
|
||||
slog.Error("Failed to marshal ebs.mount error response", "err", marshalErr)
|
||||
_ = msg.Respond([]byte(`{"Error":"internal marshal failure"}`))
|
||||
return
|
||||
}
|
||||
if err := msg.Respond(errResp); err != nil {
|
||||
slog.Error("Failed to respond to ebs.mount request", "err", err)
|
||||
}
|
||||
@@ -484,7 +499,12 @@ func launchService(cfg *Config) (err error) {
|
||||
if err != nil {
|
||||
ebsResponse.Error = fmt.Sprintf("Failed to connect to Viperblock store: %v", err)
|
||||
// Marshal and send error response immediately
|
||||
response, _ := json.Marshal(ebsResponse)
|
||||
response, marshalErr := json.Marshal(ebsResponse)
|
||||
if marshalErr != nil {
|
||||
slog.Error("Failed to marshal ebs.mount error response", "err", marshalErr)
|
||||
_ = msg.Respond([]byte(`{"Error":"internal marshal failure"}`))
|
||||
return
|
||||
}
|
||||
if err := msg.Respond(response); err != nil {
|
||||
slog.Error("Failed to respond to ebs.mount request", "err", err)
|
||||
}
|
||||
@@ -504,7 +524,12 @@ func launchService(cfg *Config) (err error) {
|
||||
if err != nil {
|
||||
ebsResponse.Error = err.Error()
|
||||
// Marshal and send error response immediately
|
||||
response, _ := json.Marshal(ebsResponse)
|
||||
response, marshalErr := json.Marshal(ebsResponse)
|
||||
if marshalErr != nil {
|
||||
slog.Error("Failed to marshal ebs.mount error response", "err", marshalErr)
|
||||
_ = msg.Respond([]byte(`{"Error":"internal marshal failure"}`))
|
||||
return
|
||||
}
|
||||
if err := msg.Respond(response); err != nil {
|
||||
slog.Error("Failed to respond to ebs.mount request", "err", err)
|
||||
}
|
||||
@@ -521,7 +546,12 @@ func launchService(cfg *Config) (err error) {
|
||||
if err != nil {
|
||||
ebsResponse.Error = err.Error()
|
||||
// Marshal and send error response immediately
|
||||
response, _ := json.Marshal(ebsResponse)
|
||||
response, marshalErr := json.Marshal(ebsResponse)
|
||||
if marshalErr != nil {
|
||||
slog.Error("Failed to marshal ebs.mount error response", "err", marshalErr)
|
||||
_ = msg.Respond([]byte(`{"Error":"internal marshal failure"}`))
|
||||
return
|
||||
}
|
||||
if err := msg.Respond(response); err != nil {
|
||||
slog.Error("Failed to respond to ebs.mount request", "err", err)
|
||||
}
|
||||
@@ -545,7 +575,12 @@ func launchService(cfg *Config) (err error) {
|
||||
portStr, err := viperblock.FindFreePort()
|
||||
if err != nil {
|
||||
ebsResponse.Error = err.Error()
|
||||
response, _ := json.Marshal(ebsResponse)
|
||||
response, marshalErr := json.Marshal(ebsResponse)
|
||||
if marshalErr != nil {
|
||||
slog.Error("Failed to marshal ebs.mount error response", "err", marshalErr)
|
||||
_ = msg.Respond([]byte(`{"Error":"internal marshal failure"}`))
|
||||
return
|
||||
}
|
||||
if err := msg.Respond(response); err != nil {
|
||||
slog.Error("Failed to respond to ebs.mount request", "err", err)
|
||||
}
|
||||
@@ -561,7 +596,12 @@ func launchService(cfg *Config) (err error) {
|
||||
if err != nil {
|
||||
slog.Error("Failed to convert port to int", "err", err)
|
||||
ebsResponse.Error = fmt.Sprintf("failed to parse port: %v", err)
|
||||
response, _ := json.Marshal(ebsResponse)
|
||||
response, marshalErr := json.Marshal(ebsResponse)
|
||||
if marshalErr != nil {
|
||||
slog.Error("Failed to marshal ebs.mount error response", "err", marshalErr)
|
||||
_ = msg.Respond([]byte(`{"Error":"internal marshal failure"}`))
|
||||
return
|
||||
}
|
||||
if err := msg.Respond(response); err != nil {
|
||||
slog.Error("Failed to respond to ebs.mount request", "err", err)
|
||||
}
|
||||
@@ -578,7 +618,12 @@ func launchService(cfg *Config) (err error) {
|
||||
nbdSocket, err = utils.GenerateUniqueSocketFile(ebsRequest.Name)
|
||||
if err != nil {
|
||||
ebsResponse.Error = err.Error()
|
||||
response, _ := json.Marshal(ebsResponse)
|
||||
response, marshalErr := json.Marshal(ebsResponse)
|
||||
if marshalErr != nil {
|
||||
slog.Error("Failed to marshal ebs.mount error response", "err", marshalErr)
|
||||
_ = msg.Respond([]byte(`{"Error":"internal marshal failure"}`))
|
||||
return
|
||||
}
|
||||
if err := msg.Respond(response); err != nil {
|
||||
slog.Error("Failed to respond to ebs.mount request", "err", err)
|
||||
}
|
||||
@@ -597,7 +642,12 @@ func launchService(cfg *Config) (err error) {
|
||||
if err != nil {
|
||||
slog.Error("Failed to generate nbdkit pid file", "err", err)
|
||||
ebsResponse.Error = fmt.Sprintf("failed to generate pid file: %v", err)
|
||||
response, _ := json.Marshal(ebsResponse)
|
||||
response, marshalErr := json.Marshal(ebsResponse)
|
||||
if marshalErr != nil {
|
||||
slog.Error("Failed to marshal ebs.mount error response", "err", marshalErr)
|
||||
_ = msg.Respond([]byte(`{"Error":"internal marshal failure"}`))
|
||||
return
|
||||
}
|
||||
if err := msg.Respond(response); err != nil {
|
||||
slog.Error("Failed to respond to ebs.mount request", "err", err)
|
||||
}
|
||||
@@ -667,7 +717,12 @@ func launchService(cfg *Config) (err error) {
|
||||
if pid == 0 {
|
||||
ebsResponse.Error = "Failed to start nbdkit"
|
||||
// Marshal and send error response immediately
|
||||
response, _ := json.Marshal(ebsResponse)
|
||||
response, marshalErr := json.Marshal(ebsResponse)
|
||||
if marshalErr != nil {
|
||||
slog.Error("Failed to marshal ebs.mount error response", "err", marshalErr)
|
||||
_ = msg.Respond([]byte(`{"Error":"internal marshal failure"}`))
|
||||
return
|
||||
}
|
||||
if err := msg.Respond(response); err != nil {
|
||||
slog.Error("Failed to respond to ebs.mount request", "err", err)
|
||||
}
|
||||
@@ -685,7 +740,12 @@ func launchService(cfg *Config) (err error) {
|
||||
case exitErr := <-exitChan:
|
||||
if exitErr != 0 {
|
||||
ebsResponse.Error = fmt.Sprintf("nbdkit failed: %v", exitErr)
|
||||
response, _ := json.Marshal(ebsResponse)
|
||||
response, marshalErr := json.Marshal(ebsResponse)
|
||||
if marshalErr != nil {
|
||||
slog.Error("Failed to marshal ebs.mount error response", "err", marshalErr)
|
||||
_ = msg.Respond([]byte(`{"Error":"internal marshal failure"}`))
|
||||
return
|
||||
}
|
||||
if err := msg.Respond(response); err != nil {
|
||||
slog.Error("Failed to respond to ebs.mount request", "err", err)
|
||||
}
|
||||
@@ -724,6 +784,7 @@ func launchService(cfg *Config) (err error) {
|
||||
response, err := json.Marshal(ebsResponse)
|
||||
if err != nil {
|
||||
slog.Error("Failed to marshal response", "err", err)
|
||||
_ = msg.Respond([]byte(`{"Error":"internal marshal failure"}`))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -771,5 +832,4 @@ func launchService(cfg *Config) (err error) {
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -65,9 +64,7 @@ func TestGlobalAccountID(t *testing.T) {
|
||||
// startJSNATSServer starts an embedded JetStream-enabled NATS server for testing.
|
||||
func startJSNATSServer(t *testing.T) *server.Server {
|
||||
t.Helper()
|
||||
tmpDir, err := os.MkdirTemp("", "nats-js-test-*")
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() { os.RemoveAll(tmpDir) })
|
||||
tmpDir := t.TempDir()
|
||||
|
||||
ns, err := server.NewServer(&server.Options{
|
||||
Host: "127.0.0.1",
|
||||
|
||||
@@ -138,7 +138,6 @@ var AvailableImages = map[string]Images{
|
||||
|
||||
// AMI / image extraction utils
|
||||
func ExtractDiskImageFromFile(imagepath string, tmpdir string) (diskimage string, err error) {
|
||||
|
||||
var args []string
|
||||
var execCmd string
|
||||
|
||||
@@ -146,7 +145,7 @@ func ExtractDiskImageFromFile(imagepath string, tmpdir string) (diskimage string
|
||||
_, err = os.Stat(imagepath)
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
return diskimage, err
|
||||
}
|
||||
|
||||
// Extract the filepath
|
||||
@@ -154,7 +153,6 @@ func ExtractDiskImageFromFile(imagepath string, tmpdir string) (diskimage string
|
||||
|
||||
// Already in raw/image formt, confirm the file contains a valid disk image/MBR
|
||||
if strings.HasSuffix(imagefile, ".raw") || strings.HasSuffix(imagefile, ".img") || strings.HasSuffix(imagefile, ".qcow2") || strings.HasSuffix(imagefile, ".qcow") {
|
||||
|
||||
path, err := filepath.Abs(imagepath)
|
||||
|
||||
if err != nil {
|
||||
@@ -167,7 +165,6 @@ func ExtractDiskImageFromFile(imagepath string, tmpdir string) (diskimage string
|
||||
// Check error response
|
||||
|
||||
if errors.Is(err, ErrQCOWDetected) {
|
||||
|
||||
extractpath := fmt.Sprintf("%s/%s", tmpdir, imagefile)
|
||||
extractpath = strings.TrimSuffix(extractpath, ".qcow2") + ".raw"
|
||||
|
||||
@@ -192,13 +189,10 @@ func ExtractDiskImageFromFile(imagepath string, tmpdir string) (diskimage string
|
||||
}
|
||||
|
||||
return extractpath, nil
|
||||
|
||||
}
|
||||
|
||||
return path, err
|
||||
|
||||
} else if strings.HasSuffix(imagefile, ".tar.xz") {
|
||||
|
||||
args = []string{
|
||||
"xfvJ",
|
||||
imagepath,
|
||||
@@ -207,9 +201,7 @@ func ExtractDiskImageFromFile(imagepath string, tmpdir string) (diskimage string
|
||||
}
|
||||
|
||||
execCmd = "tar"
|
||||
|
||||
} else if strings.HasSuffix(imagefile, ".tar.gz") || strings.HasSuffix(imagefile, ".tgz") {
|
||||
|
||||
args = []string{
|
||||
"xfvz",
|
||||
imagepath,
|
||||
@@ -218,9 +210,7 @@ func ExtractDiskImageFromFile(imagepath string, tmpdir string) (diskimage string
|
||||
}
|
||||
|
||||
execCmd = "tar"
|
||||
|
||||
} else if strings.HasSuffix(imagefile, ".tar") {
|
||||
|
||||
args = []string{
|
||||
"xfv",
|
||||
imagepath,
|
||||
@@ -229,36 +219,31 @@ func ExtractDiskImageFromFile(imagepath string, tmpdir string) (diskimage string
|
||||
}
|
||||
|
||||
execCmd = "tar"
|
||||
|
||||
} else if strings.HasSuffix(imagefile, ".xz") {
|
||||
|
||||
args = []string{
|
||||
"-dk",
|
||||
imagepath,
|
||||
}
|
||||
|
||||
execCmd = "xz"
|
||||
|
||||
} else {
|
||||
err = errors.New("unsupported filetype")
|
||||
return
|
||||
return diskimage, err
|
||||
}
|
||||
|
||||
cmd := exec.Command(execCmd, args...)
|
||||
output, err := cmd.CombinedOutput()
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
return diskimage, err
|
||||
}
|
||||
|
||||
diskimage, err = extractDiskImagePath(tmpdir, output)
|
||||
|
||||
return
|
||||
|
||||
return diskimage, err
|
||||
}
|
||||
|
||||
func extractDiskImagePath(imagedir string, output []byte) (diskimage string, err error) {
|
||||
|
||||
reader := bytes.NewReader(output)
|
||||
|
||||
r := bufio.NewReader(reader)
|
||||
@@ -286,12 +271,10 @@ func extractDiskImagePath(imagedir string, output []byte) (diskimage string, err
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
|
||||
return diskimage, err
|
||||
}
|
||||
|
||||
func validateDiskImagePath(diskimage string) (err error) {
|
||||
|
||||
args := []string{
|
||||
diskimage,
|
||||
}
|
||||
@@ -305,15 +288,12 @@ func validateDiskImagePath(diskimage string) (err error) {
|
||||
filetype := strings.Split(string(output), ":")
|
||||
|
||||
if len(filetype) > 1 {
|
||||
|
||||
if strings.Contains(filetype[1], "DOS/MBR boot sector") || strings.Contains(filetype[1], "Linux ") {
|
||||
return nil
|
||||
} else if strings.Contains(filetype[1], "QEMU QCOW") {
|
||||
return ErrQCOWDetected
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return errors.New("no valid disk image found")
|
||||
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ import (
|
||||
// GenerateSocketFile generates a socket file path for the given name.
|
||||
// Deprecated: Use GenerateUniqueSocketFile for new code to ensure uniqueness.
|
||||
func GenerateSocketFile(name string) (string, error) {
|
||||
|
||||
if name == "" {
|
||||
return "", errors.New("name is required")
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ import (
|
||||
)
|
||||
|
||||
func ReadPidFile(name string) (int, error) {
|
||||
|
||||
pidPath := pidPath()
|
||||
|
||||
pidFile, err := os.ReadFile(filepath.Join(pidPath, fmt.Sprintf("%s.pid", name)))
|
||||
@@ -28,7 +27,6 @@ func ReadPidFile(name string) (int, error) {
|
||||
}
|
||||
|
||||
func GeneratePidFile(name string) (string, error) {
|
||||
|
||||
if name == "" {
|
||||
return "", errors.New("name is required")
|
||||
}
|
||||
@@ -43,7 +41,6 @@ func GeneratePidFile(name string) (string, error) {
|
||||
}
|
||||
|
||||
func WritePidFile(name string, pid int) error {
|
||||
|
||||
// Write PID to file, check XDG, otherwise user home directory ~/spinifex/
|
||||
pidFilename, err := GeneratePidFile(name)
|
||||
|
||||
@@ -58,7 +55,7 @@ func WritePidFile(name string, pid int) error {
|
||||
}
|
||||
|
||||
defer pidFile.Close()
|
||||
_, err = pidFile.WriteString(fmt.Sprintf("%d", pid))
|
||||
_, err = fmt.Fprintf(pidFile, "%d", pid)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -83,7 +80,7 @@ func WritePidFileTo(dir string, name string, pid int) error {
|
||||
}
|
||||
|
||||
defer pidFile.Close()
|
||||
_, err = pidFile.WriteString(fmt.Sprintf("%d", pid))
|
||||
_, err = fmt.Fprintf(pidFile, "%d", pid)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -134,7 +131,6 @@ func StopProcessAt(dir string, name string) error {
|
||||
}
|
||||
|
||||
func RemovePidFile(serviceName string) error {
|
||||
|
||||
pidPath := pidPath()
|
||||
|
||||
err := os.Remove(filepath.Join(pidPath, fmt.Sprintf("%s.pid", serviceName)))
|
||||
|
||||
@@ -41,7 +41,6 @@ func StopProcess(serviceName string) error {
|
||||
}
|
||||
|
||||
func KillProcess(pid int) error {
|
||||
|
||||
process, err := os.FindProcess(pid)
|
||||
|
||||
if err != nil {
|
||||
@@ -68,7 +67,7 @@ func KillProcess(pid int) error {
|
||||
err = process.Signal(syscall.Signal(0))
|
||||
|
||||
if err != nil {
|
||||
// Process terminated, break
|
||||
// Signal(0) error means the process has terminated — this is the expected outcome.
|
||||
break
|
||||
}
|
||||
|
||||
@@ -86,6 +85,5 @@ func KillProcess(pid int) error {
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
return nil //nolint:nilerr // Signal(0) error means process exited — that's success
|
||||
}
|
||||
|
||||
+2
-18
@@ -41,7 +41,6 @@ func GenerateResourceID(prefix string) string {
|
||||
|
||||
// Convert interface to XML
|
||||
func MarshalToXML(payload any) ([]byte, error) {
|
||||
|
||||
var buf bytes.Buffer
|
||||
enc := xml.NewEncoder(&buf)
|
||||
|
||||
@@ -56,7 +55,6 @@ func MarshalToXML(payload any) ([]byte, error) {
|
||||
}
|
||||
|
||||
return buf.Bytes(), nil
|
||||
|
||||
}
|
||||
|
||||
// wrapWithLocation decorates payload with the requested locationName tag.
|
||||
@@ -106,7 +104,6 @@ func GenerateIAMXMLPayload(action string, payload any) any {
|
||||
|
||||
// Generate JSON Error Payload
|
||||
func GenerateErrorPayload(code string) (jsonResponse []byte) {
|
||||
|
||||
var responseError ec2.ResponseError
|
||||
responseError.Code = aws.String(code)
|
||||
|
||||
@@ -117,13 +114,11 @@ func GenerateErrorPayload(code string) (jsonResponse []byte) {
|
||||
return nil
|
||||
}
|
||||
|
||||
return
|
||||
|
||||
return jsonResponse
|
||||
}
|
||||
|
||||
// Validate the payload is an ec2.ResponseError
|
||||
func ValidateErrorPayload(payload []byte) (responseError ec2.ResponseError, err error) {
|
||||
|
||||
decoder := json.NewDecoder(bytes.NewReader(payload))
|
||||
decoder.DisallowUnknownFields()
|
||||
|
||||
@@ -137,13 +132,11 @@ func ValidateErrorPayload(payload []byte) (responseError ec2.ResponseError, err
|
||||
|
||||
// Either failed to decode (not an error structure) or Code is nil (empty valid response)
|
||||
return responseError, nil
|
||||
|
||||
}
|
||||
|
||||
// Unmarshal payload
|
||||
|
||||
func UnmarshalJsonPayload(input any, jsonData []byte) []byte {
|
||||
|
||||
decoder := json.NewDecoder(bytes.NewReader(jsonData))
|
||||
decoder.DisallowUnknownFields()
|
||||
|
||||
@@ -154,11 +147,9 @@ func UnmarshalJsonPayload(input any, jsonData []byte) []byte {
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
func MarshalJsonPayload(input any, jsonData []byte) []byte {
|
||||
|
||||
decoder := json.NewDecoder(bytes.NewReader(jsonData))
|
||||
decoder.DisallowUnknownFields()
|
||||
|
||||
@@ -169,7 +160,6 @@ func MarshalJsonPayload(input any, jsonData []byte) []byte {
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
// ValidateKeyPairName validates that a key pair name only contains allowed characters:
|
||||
@@ -204,7 +194,6 @@ func ValidateKeyPairName(name string) error {
|
||||
}
|
||||
|
||||
func CreateS3Client(cfg *config.Config) *s3.S3 {
|
||||
|
||||
tr := &http.Transport{
|
||||
TLSClientConfig: &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
@@ -225,13 +214,11 @@ func CreateS3Client(cfg *config.Config) *s3.S3 {
|
||||
}))
|
||||
|
||||
return s3.New(sess)
|
||||
|
||||
}
|
||||
|
||||
// Download helper
|
||||
|
||||
func DownloadFileWithProgress(url string, name string, filename string, timeout time.Duration) (err error) {
|
||||
|
||||
// Context with optional timeout and Ctrl+C cancel
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
if timeout > 0 {
|
||||
@@ -288,10 +275,8 @@ func DownloadFileWithProgress(url string, name string, filename string, timeout
|
||||
}
|
||||
|
||||
pterm.Printf("Saved %s (%s)\n", filename, humanBytes(SafeInt64ToUint64(written)))
|
||||
return
|
||||
|
||||
return err
|
||||
} else {
|
||||
|
||||
// Unknown size: spinner that shows bytes downloaded
|
||||
spin, _ := pterm.DefaultSpinner.
|
||||
WithText("Downloading (size unknown)...").
|
||||
@@ -309,7 +294,6 @@ func DownloadFileWithProgress(url string, name string, filename string, timeout
|
||||
}
|
||||
|
||||
pterm.Printf("Saved %s (%s)\n", filename, humanBytes(SafeInt64ToUint64(written)))
|
||||
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -43,7 +43,6 @@ func TestGenerateResourceID(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestGeneratePidFile(t *testing.T) {
|
||||
|
||||
// Simulate a sample process running (e.g cat)
|
||||
cmd := exec.Command("cat")
|
||||
cmd.Start()
|
||||
@@ -70,12 +69,10 @@ func TestGeneratePidFile(t *testing.T) {
|
||||
//time.Sleep(2 * time.Second)
|
||||
|
||||
// Simulate process ending
|
||||
|
||||
}
|
||||
|
||||
func TestGenerateSocketFile(t *testing.T) {
|
||||
|
||||
socketPath := fmt.Sprintf("%s/%s", os.TempDir(), "utilsunittest")
|
||||
socketPath := fmt.Sprintf("%s/%s", t.TempDir(), "utilsunittest")
|
||||
|
||||
name, err := GenerateSocketFile(socketPath)
|
||||
|
||||
@@ -87,11 +84,9 @@ func TestGenerateSocketFile(t *testing.T) {
|
||||
_, err = GenerateSocketFile("")
|
||||
|
||||
assert.Error(t, err)
|
||||
|
||||
}
|
||||
|
||||
func TestExecProcessAndKill(t *testing.T) {
|
||||
|
||||
// Simulate a sample process running (e.g sleep, 30 secs)
|
||||
cmd := exec.Command("sleep", "30")
|
||||
|
||||
@@ -142,7 +137,6 @@ func TestExecProcessAndKill(t *testing.T) {
|
||||
// Verify process is killed
|
||||
err = cmd.Process.Signal(syscall.Signal(0))
|
||||
assert.Error(t, err) // Should return an error since process is killed
|
||||
|
||||
}
|
||||
|
||||
func TestUnmarshalJsonPayload(t *testing.T) {
|
||||
@@ -593,13 +587,10 @@ func TestStopProcess(t *testing.T) {
|
||||
// Test file extraction process
|
||||
|
||||
func TestExtractDiskImageFromFile(t *testing.T) {
|
||||
|
||||
tmpDir, err := os.MkdirTemp("", "spx-utils-test-*")
|
||||
tmpDir := t.TempDir()
|
||||
|
||||
t.Log("Temp dir:", tmpDir)
|
||||
|
||||
assert.NoError(t, err, "Temp dir should be created")
|
||||
|
||||
// Sample .xz (fail)
|
||||
imagePath, err := ExtractDiskImageFromFile("/tmp/file.xz", tmpDir)
|
||||
|
||||
@@ -631,7 +622,6 @@ func TestExtractDiskImageFromFile(t *testing.T) {
|
||||
_, err = exec.LookPath("tar")
|
||||
|
||||
if err == nil {
|
||||
|
||||
// Sample .tgz
|
||||
imagePath, err = ExtractDiskImageFromFile("../../tests/unit-test-disk-image2.tgz", tmpDir)
|
||||
|
||||
@@ -671,14 +661,12 @@ func TestExtractDiskImageFromFile(t *testing.T) {
|
||||
assert.Contains(t, imagePath, ".raw")
|
||||
|
||||
assert.NoError(t, err, "Should not error")
|
||||
|
||||
} else {
|
||||
t.Skip("tar command not found, skipping archive extraction tests")
|
||||
}
|
||||
|
||||
//err = os.RemoveAll(tmpDir)
|
||||
//assert.NoError(t, err, "Could not remove temp dir")
|
||||
|
||||
}
|
||||
|
||||
func TestIsSocketURI(t *testing.T) {
|
||||
@@ -774,9 +762,8 @@ func TestDirExists(t *testing.T) {
|
||||
assert.False(t, dirExists("/nonexistent/path/should/not/exist"))
|
||||
|
||||
// File (not a directory)
|
||||
tmpFile, err := os.CreateTemp("", "direxists-test-*")
|
||||
tmpFile, err := os.CreateTemp(t.TempDir(), "direxists-test-*")
|
||||
require.NoError(t, err)
|
||||
defer os.Remove(tmpFile.Name())
|
||||
tmpFile.Close()
|
||||
assert.False(t, dirExists(tmpFile.Name()))
|
||||
}
|
||||
|
||||
@@ -135,7 +135,6 @@ type Config struct {
|
||||
}
|
||||
|
||||
func (cfg *Config) Execute() (*exec.Cmd, error) {
|
||||
|
||||
args := []string{}
|
||||
|
||||
if cfg.PIDFile != "" {
|
||||
@@ -154,9 +153,7 @@ func (cfg *Config) Execute() (*exec.Cmd, error) {
|
||||
//slog.Warn("Setting -cpu max, `host` CPU type unavailable")
|
||||
// Use qemu defaults
|
||||
//args = append(args, "-cpu", "max")
|
||||
|
||||
} else {
|
||||
|
||||
if cfg.EnableKVM {
|
||||
args = append(args, "-enable-kvm")
|
||||
}
|
||||
@@ -164,7 +161,6 @@ func (cfg *Config) Execute() (*exec.Cmd, error) {
|
||||
if cfg.CPUType != "" {
|
||||
args = append(args, "-cpu", cfg.CPUType)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if cfg.NoGraphic {
|
||||
@@ -198,7 +194,6 @@ func (cfg *Config) Execute() (*exec.Cmd, error) {
|
||||
}
|
||||
|
||||
for _, drive := range cfg.Drives {
|
||||
|
||||
var opts []string
|
||||
|
||||
//args = append(args, "-drive", fmt.Sprintf("file=%s", drive.File)
|
||||
@@ -228,7 +223,6 @@ func (cfg *Config) Execute() (*exec.Cmd, error) {
|
||||
}
|
||||
|
||||
args = append(args, "-drive", strings.Join(opts, ","))
|
||||
|
||||
}
|
||||
|
||||
for _, device := range cfg.Devices {
|
||||
@@ -267,7 +261,6 @@ func (cfg *Config) Execute() (*exec.Cmd, error) {
|
||||
slog.Warn("UEFI firmware file not found for ARM virt machine. Ensure qemu-efi-aarch64 package is installed.", "path", uefiPath)
|
||||
return nil, fmt.Errorf("UEFI firmware file not found for ARM virt machine")
|
||||
}
|
||||
|
||||
} else if cfg.MachineType != "" {
|
||||
args = append(args, "-M", cfg.MachineType)
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
)
|
||||
|
||||
func TestExecute(t *testing.T) {
|
||||
|
||||
cfg := Config{
|
||||
Name: "test-vm",
|
||||
}
|
||||
@@ -74,7 +73,6 @@ func TestExecute(t *testing.T) {
|
||||
|
||||
assert.Contains(t, cmd.Path, "qemu-system-aarch64")
|
||||
assert.Equal(t, expectedArgs, cmd.Args[1:])
|
||||
|
||||
}
|
||||
|
||||
func TestExecute_IOThreadAndCache(t *testing.T) {
|
||||
|
||||
Reference in New Issue
Block a user