mirror of
https://github.com/mulgadc/spinifex.git
synced 2026-10-20 11:21:49 +00:00
Merge branch 'main' of github.com:mulgadc/hive
This commit is contained in:
@@ -0,0 +1,43 @@
|
||||
name: E2E Test Hive
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main, e2e ]
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
hive_e2e_test:
|
||||
name: Hive E2E Test
|
||||
runs-on: self-hosted
|
||||
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
path: hive
|
||||
|
||||
- name: Clone Dependencies
|
||||
run: ./hive/scripts/clone-deps.sh
|
||||
|
||||
- name: Cleanup Stale Containers
|
||||
run: docker rm -f hive-e2e-test 2>/dev/null || true
|
||||
|
||||
- name: Build E2E Image
|
||||
run: |
|
||||
docker build \
|
||||
-t hive-e2e:latest \
|
||||
-f hive/tests/e2e/Dockerfile.e2e \
|
||||
.
|
||||
|
||||
- name: Run E2E Tests
|
||||
run: |
|
||||
docker run --privileged --rm \
|
||||
-v /dev/kvm:/dev/kvm \
|
||||
--name hive-e2e-test \
|
||||
hive-e2e:latest
|
||||
|
||||
- name: Cleanup
|
||||
if: always()
|
||||
run: |
|
||||
docker rm -f hive-e2e-test 2>/dev/null || true
|
||||
docker image prune -f
|
||||
+11
-16
@@ -3,23 +3,23 @@ name: Build and Test Hive
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
paths: ["hive/**", ".github/workflows/**", "go.mod", "go.sum"]
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
hive_build_and_test:
|
||||
runs-on: ubuntu-latest
|
||||
name: Hive Build and Test
|
||||
runs-on: ubuntu-24.04
|
||||
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version-file: "go.mod"
|
||||
cache-dependency-path: "go.sum"
|
||||
go-version-file: "go.mod"
|
||||
|
||||
- name: Run Tests
|
||||
run: LOG_IGNORE=1 go test -v -timeout 120s ./...
|
||||
@@ -28,18 +28,13 @@ jobs:
|
||||
run: go vet ./...
|
||||
|
||||
- name: Static Check
|
||||
uses: dominikh/staticcheck-action@v1
|
||||
with:
|
||||
version: 2025.1.1
|
||||
install-go: false
|
||||
checks: "all,-ST1000,-ST1003,-ST1016,-ST1020,-ST1021,-ST1022,-SA1019,-SA9005"
|
||||
run: go tool staticcheck -checks="all,-ST1000,-ST1003,-ST1016,-ST1020,-ST1021,-ST1022,-SA1019,-SA9005" ./...
|
||||
|
||||
- name: Go Vulnerability Check
|
||||
uses: golang/govulncheck-action@v1
|
||||
with:
|
||||
repo-checkout: false
|
||||
run: go tool govulncheck ./...
|
||||
|
||||
# - name: Gosec Security Scanner
|
||||
# run: |
|
||||
# go install github.com/securego/gosec/v2/cmd/gosec@latest
|
||||
# gosec -exclude-generated ./...
|
||||
# run: go tool gosec -exclude-generated ./...
|
||||
|
||||
- name: Build
|
||||
run: make go_build
|
||||
|
||||
+13
-7
@@ -6,9 +6,17 @@ Notes for development environment installation.
|
||||
|
||||
For running on Ubuntu 22.04, 24.04 and 25.10.
|
||||
|
||||
### Quick Install
|
||||
|
||||
```bash
|
||||
make quickinstall
|
||||
```
|
||||
|
||||
### Manual Install
|
||||
|
||||
```bash
|
||||
sudo add-apt-repository universe
|
||||
sudo apt install nbdkit nbdkit-plugin-dev pkg-config qemu-system qemu-utils qemu-kvm libvirt-daemon-system libvirt-clients libvirt-dev make gcc
|
||||
sudo apt install nbdkit nbdkit-plugin-dev pkg-config qemu-system qemu-utils qemu-kvm libvirt-daemon-system libvirt-clients libvirt-dev make gcc unzip xz-utils file
|
||||
```
|
||||
|
||||
Ensure the Go toolkit is installed, recommended to install the latest directly from [https://go.dev/dl/](https://go.dev/dl/).
|
||||
@@ -22,10 +30,12 @@ go version
|
||||
Hive provides AWS API/SDK layer functionality, which requires the AWS CLI tool to be installed to interface with the system.
|
||||
|
||||
```bash
|
||||
sudo apt install awscli
|
||||
curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
|
||||
unzip awscliv2.zip
|
||||
sudo ./aws/install
|
||||
```
|
||||
|
||||
Confirm awscli version > 2.0 is installed which is required by Hive. If your OS package manager installed an older version, e.g 1.X, install the latest version of the AWS CLI tool directly.
|
||||
Confirm awscli version > 2.0 is installed which is required by Hive.
|
||||
|
||||
[https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html)
|
||||
|
||||
@@ -33,10 +43,6 @@ Confirm awscli version > 2.0 is installed which is required by Hive. If your OS
|
||||
aws --version
|
||||
```
|
||||
|
||||
```
|
||||
aws-cli/2.32.4
|
||||
```
|
||||
|
||||
## Build
|
||||
|
||||
Create the base directory for the Hive development environment.
|
||||
|
||||
@@ -12,35 +12,6 @@ else
|
||||
GO_BUILD_MOD :=
|
||||
endif
|
||||
|
||||
# Where to install Go tools
|
||||
GOBIN ?= $(shell go env GOBIN)
|
||||
ifeq ($(GOBIN),)
|
||||
GOBIN := $(shell go env GOPATH)/bin
|
||||
endif
|
||||
|
||||
# Where to install Go tools
|
||||
GOVULNCHECK := $(GOBIN)/govulncheck
|
||||
|
||||
# Install govulncheck only if the binary is missing / out of date
|
||||
$(GOVULNCHECK):
|
||||
go install golang.org/x/vuln/cmd/govulncheck@latest
|
||||
|
||||
GOSECCHECK := $(GOBIN)/gosec
|
||||
|
||||
# Install gosec only if the binary is missing / out of date
|
||||
$(GOSECCHECK):
|
||||
go install github.com/securego/gosec/v2/cmd/gosec@latest
|
||||
|
||||
GOSTATICCHECK := $(GOBIN)/staticcheck
|
||||
|
||||
# Install govulncheck only if the binary is missing / out of date
|
||||
$(GOSTATICCHECK):
|
||||
go install honnef.co/go/tools/cmd/staticcheck@latest
|
||||
|
||||
# Install govulncheck only if the binary is missing / out of date
|
||||
$(GOVULNCHECK):
|
||||
go install golang.org/x/vuln/cmd/govulncheck@latest
|
||||
|
||||
build:
|
||||
$(MAKE) go_build
|
||||
|
||||
@@ -53,10 +24,9 @@ go_run:
|
||||
@echo "\n....Running $(GO_PROJECT_NAME)...."
|
||||
$(GOPATH)/bin/$(GO_PROJECT_NAME)
|
||||
|
||||
test: $(GOVULNCHECK)
|
||||
test:
|
||||
@echo "\n....Running tests for $(GO_PROJECT_NAME)...."
|
||||
LOG_IGNORE=1 go test -v -timeout 120s ./...
|
||||
$(GOVULNCHECK) ./...
|
||||
|
||||
bench:
|
||||
@echo "\n....Running benchmarks for $(GO_PROJECT_NAME)...."
|
||||
@@ -70,20 +40,49 @@ run:
|
||||
clean:
|
||||
rm ./bin/$(GO_PROJECT_NAME)
|
||||
|
||||
security: $(GOVULNCHECK) $(GOSECCHECK) $(GOSTATICCHECK)
|
||||
install-system:
|
||||
@echo "\n....Installing system dependencies...."
|
||||
apt-get update && apt-get install -y \
|
||||
nbdkit nbdkit-plugin-dev pkg-config qemu-system-x86 qemu-utils qemu-kvm \
|
||||
libvirt-daemon-system libvirt-clients libvirt-dev make gcc jq curl \
|
||||
iproute2 netcat-openbsd openssh-client wget git unzip sudo xz-utils file
|
||||
|
||||
install-go:
|
||||
@echo "\n....Installing Go 1.25.5...."
|
||||
@if [ ! -d "/usr/local/go" ]; then \
|
||||
curl -L https://go.dev/dl/go1.25.5.linux-amd64.tar.gz | tar -C /usr/local -xz; \
|
||||
else \
|
||||
echo "Go already installed in /usr/local/go"; \
|
||||
fi
|
||||
@echo "Go version: $$(go version)"
|
||||
|
||||
install-aws:
|
||||
@echo "\n....Installing AWS CLI v2...."
|
||||
@if ! command -v aws >/dev/null 2>&1; then \
|
||||
curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"; \
|
||||
unzip -o awscliv2.zip; \
|
||||
./aws/install; \
|
||||
rm -rf awscliv2.zip aws/; \
|
||||
else \
|
||||
echo "AWS CLI already installed"; \
|
||||
fi
|
||||
|
||||
quickinstall: install-system install-go install-aws
|
||||
@echo "\n✅ Quickinstall complete. Please ensure /usr/local/go/bin is in your PATH."
|
||||
|
||||
security:
|
||||
@echo "\n....Running security checks for $(GO_PROJECT_NAME)...."
|
||||
|
||||
$(GOVULNCHECK) ./... > tests/govulncheck-report.txt || true
|
||||
go tool govulncheck ./... > tests/govulncheck-report.txt || true
|
||||
@echo "Govulncheck report saved to tests/govulncheck-report.txt"
|
||||
|
||||
$(GOSECCHECK) ./... > tests/gosec-report.txt || true
|
||||
go tool gosec ./... > tests/gosec-report.txt || true
|
||||
@echo "Gosec report saved to tests/gosec-report.txt"
|
||||
|
||||
# default config + disable dep warning since we are using aws sdk v1
|
||||
$(GOSTATICCHECK) -checks="all,-ST1000,-ST1003,-ST1016,-ST1020,-ST1021,-ST1022,-SA1019,-SA9005" ./... > tests/staticcheck-report.txt || true
|
||||
go tool staticcheck -checks="all,-ST1000,-ST1003,-ST1016,-ST1020,-ST1021,-ST1022,-SA1019,-SA9005" ./... > tests/staticcheck-report.txt || true
|
||||
@echo "Staticcheck report saved to tests/staticcheck-report.txt"
|
||||
|
||||
go vet ./... 2>&1 | tee tests/govet-report.txt || true
|
||||
@echo "Go vet report saved to tests/govet-report.txt"
|
||||
|
||||
.PHONY: go_build go_run build run test clean
|
||||
.PHONY: build go_build go_run test bench run clean install-system install-go install-aws quickinstall security
|
||||
|
||||
@@ -36,7 +36,7 @@ import (
|
||||
"github.com/mulgadc/hive/hive/utils"
|
||||
"github.com/mulgadc/viperblock/viperblock"
|
||||
"github.com/mulgadc/viperblock/viperblock/backends/s3"
|
||||
vutils "github.com/mulgadc/viperblock/viperblock/utils"
|
||||
"github.com/mulgadc/viperblock/viperblock/v_utils"
|
||||
"github.com/pelletier/go-toml/v2"
|
||||
"github.com/pterm/pterm"
|
||||
"github.com/spf13/cobra"
|
||||
@@ -409,7 +409,7 @@ func runimagesImportCmd(cmd *cobra.Command, args []string) {
|
||||
VolumeConfig: manifest,
|
||||
}
|
||||
|
||||
err = vutils.ImportDiskImage(&s3Config, &vbConfig, extractedImagePath)
|
||||
err = v_utils.ImportDiskImage(&s3Config, &vbConfig, extractedImagePath)
|
||||
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Could not import image to predastore: %v\n", err)
|
||||
|
||||
@@ -32,12 +32,12 @@ authorization {
|
||||
# token: "{{.NatsToken}}"
|
||||
}
|
||||
|
||||
# JetStream for persistent messaging
|
||||
#jetstream {
|
||||
# store_dir: "{{.DataDir}}/nats/"
|
||||
# max_memory_store: 1GB
|
||||
# max_file_store: 10GB
|
||||
#}
|
||||
# JetStream for persistent messaging and KV store
|
||||
jetstream {
|
||||
store_dir: "{{.DataDir}}/nats/"
|
||||
max_memory_store: 1GB
|
||||
max_file_store: 10GB
|
||||
}
|
||||
|
||||
# Logging
|
||||
debug: false
|
||||
|
||||
Regular → Executable
Regular → Executable
Regular → Executable
@@ -8,14 +8,14 @@ require (
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/kdomanski/iso9660 v0.4.0
|
||||
github.com/mulgadc/predastore v0.1.5
|
||||
github.com/mulgadc/viperblock v0.1.3
|
||||
github.com/mulgadc/viperblock v0.1.4
|
||||
github.com/nats-io/nats-server/v2 v2.11.6
|
||||
github.com/nats-io/nats.go v1.43.0
|
||||
github.com/pelletier/go-toml/v2 v2.2.4
|
||||
github.com/pterm/pterm v0.12.82
|
||||
github.com/spf13/cobra v1.9.1
|
||||
github.com/spf13/viper v1.20.1
|
||||
github.com/stretchr/testify v1.10.0
|
||||
github.com/stretchr/testify v1.11.1
|
||||
go.uber.org/automaxprocs v1.6.0
|
||||
gopkg.in/ini.v1 v1.67.0
|
||||
)
|
||||
@@ -24,17 +24,32 @@ 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.121.2 // indirect
|
||||
cloud.google.com/go/auth v0.16.5 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.8.0 // indirect
|
||||
github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c // indirect
|
||||
github.com/andybalholm/brotli v1.1.1 // indirect
|
||||
github.com/anthropics/anthropic-sdk-go v1.19.0 // indirect
|
||||
github.com/ccojocar/zxcvbn-go v1.0.4 // indirect
|
||||
github.com/containerd/console v1.0.5 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/fsnotify/fsnotify v1.8.0 // indirect
|
||||
github.com/go-logr/logr v1.4.3 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
|
||||
github.com/google/go-cmp v0.7.0 // indirect
|
||||
github.com/google/go-tpm v0.9.5 // indirect
|
||||
github.com/gookit/color v1.5.4 // indirect
|
||||
github.com/google/s2a-go v0.1.9 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.15.0 // indirect
|
||||
github.com/gookit/color v1.6.0 // indirect
|
||||
github.com/gorilla/websocket v1.5.3 // indirect
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
||||
github.com/klauspost/compress v1.18.0 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
||||
github.com/lithammer/fuzzysearch v1.1.8 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
@@ -43,24 +58,54 @@ require (
|
||||
github.com/nats-io/jwt/v2 v2.7.4 // indirect
|
||||
github.com/nats-io/nkeys v0.4.11 // indirect
|
||||
github.com/nats-io/nuid v1.0.1 // indirect
|
||||
github.com/openai/openai-go/v3 v3.8.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
github.com/sagikazarmark/locafero v0.7.0 // indirect
|
||||
github.com/securego/gosec/v2 v2.22.11 // indirect
|
||||
github.com/sourcegraph/conc v0.3.0 // indirect
|
||||
github.com/spf13/afero v1.12.0 // indirect
|
||||
github.com/spf13/cast v1.7.1 // indirect
|
||||
github.com/spf13/pflag v1.0.6 // indirect
|
||||
github.com/subosito/gotenv v1.6.0 // indirect
|
||||
github.com/tidwall/gjson v1.18.0 // indirect
|
||||
github.com/tidwall/match v1.1.1 // indirect
|
||||
github.com/tidwall/pretty v1.2.1 // indirect
|
||||
github.com/tidwall/sjson v1.2.5 // indirect
|
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||
github.com/valyala/fasthttp v1.57.0 // indirect
|
||||
github.com/valyala/tcplisten v1.0.0 // indirect
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect
|
||||
go.opentelemetry.io/otel v1.37.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.37.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.37.0 // indirect
|
||||
go.uber.org/atomic v1.9.0 // indirect
|
||||
go.uber.org/multierr v1.9.0 // indirect
|
||||
golang.org/x/crypto v0.39.0 // indirect
|
||||
golang.org/x/sys v0.33.0 // indirect
|
||||
golang.org/x/term v0.32.0 // indirect
|
||||
golang.org/x/text v0.26.0 // indirect
|
||||
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||
golang.org/x/crypto v0.45.0 // indirect
|
||||
golang.org/x/exp/typeparams v0.0.0-20231108232855-2478ac86f678 // indirect
|
||||
golang.org/x/mod v0.30.0 // indirect
|
||||
golang.org/x/net v0.47.0 // indirect
|
||||
golang.org/x/sync v0.18.0 // indirect
|
||||
golang.org/x/sys v0.38.0 // indirect
|
||||
golang.org/x/telemetry v0.0.0-20251111182119-bc8e575c7b54 // indirect
|
||||
golang.org/x/term v0.37.0 // indirect
|
||||
golang.org/x/text v0.31.0 // indirect
|
||||
golang.org/x/time v0.12.0 // indirect
|
||||
golang.org/x/tools v0.39.0 // indirect
|
||||
golang.org/x/vuln v1.1.4 // indirect
|
||||
google.golang.org/genai v1.37.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c // indirect
|
||||
google.golang.org/grpc v1.75.0 // indirect
|
||||
google.golang.org/protobuf v1.36.8 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
honnef.co/go/tools v0.6.1 // indirect
|
||||
)
|
||||
|
||||
tool (
|
||||
github.com/securego/gosec/v2/cmd/gosec
|
||||
golang.org/x/vuln/cmd/govulncheck
|
||||
honnef.co/go/tools/cmd/staticcheck
|
||||
)
|
||||
|
||||
@@ -6,6 +6,14 @@ atomicgo.dev/keyboard v0.2.9 h1:tOsIid3nlPLZ3lwgG8KZMp/SFmr7P0ssEN5JUsm78K8=
|
||||
atomicgo.dev/keyboard v0.2.9/go.mod h1:BC4w9g00XkxH/f1HXhW2sXmJFOCWbKn9xrOunSFtExQ=
|
||||
atomicgo.dev/schedule v0.1.0 h1:nTthAbhZS5YZmgYbb2+DH8uQIZcTlIrd4eYr3UQxEjs=
|
||||
atomicgo.dev/schedule v0.1.0/go.mod h1:xeUa3oAkiuHYh8bKiQBRojqAMq3PXXbJujjb0hw8pEU=
|
||||
cloud.google.com/go v0.121.2 h1:v2qQpN6Dx9x2NmwrqlesOt3Ys4ol5/lFZ6Mg1B7OJCg=
|
||||
cloud.google.com/go v0.121.2/go.mod h1:nRFlrHq39MNVWu+zESP2PosMWA0ryJw8KUBZ2iZpxbw=
|
||||
cloud.google.com/go/auth v0.16.5 h1:mFWNQ2FEVWAliEQWpAdH80omXFokmrnbDhUS9cBywsI=
|
||||
cloud.google.com/go/auth v0.16.5/go.mod h1:utzRfHMP+Vv0mpOkTRQoWD2q3BatTOoWbA7gCc2dUhQ=
|
||||
cloud.google.com/go/compute/metadata v0.8.0 h1:HxMRIbao8w17ZX6wBnjhcDkW6lTFpgcaobyVfZWqRLA=
|
||||
cloud.google.com/go/compute/metadata v0.8.0/go.mod h1:sYOGTp851OV9bOFJ9CH7elVvyzopvWQFNNghtDQ/Biw=
|
||||
github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c h1:pxW6RcqyfI9/kWtOwnv/G+AzdKuy2ZrqINhenH4HyNs=
|
||||
github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
||||
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=
|
||||
github.com/MarvinJWendt/testza v0.2.8/go.mod h1:nwIcjmr0Zz+Rcwfh3/4UhBp7ePKVhuBExvZqnKYWlII=
|
||||
@@ -15,13 +23,19 @@ 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/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA=
|
||||
github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA=
|
||||
github.com/anthropics/anthropic-sdk-go v1.19.0 h1:mO6E+ffSzLRvR/YUH9KJC0uGw0uV8GjISIuzem//3KE=
|
||||
github.com/anthropics/anthropic-sdk-go v1.19.0/go.mod h1:WTz31rIUHUHqai2UslPpw5CwXrQP3geYBioRV4WOLvE=
|
||||
github.com/antithesishq/antithesis-sdk-go v0.4.3-default-no-op h1:+OSa/t11TFhqfrX0EOSqQBDJ0YlpmK0rDSiB19dg9M0=
|
||||
github.com/antithesishq/antithesis-sdk-go v0.4.3-default-no-op/go.mod h1:IUpT2DPAKh6i/YhSbt6Gl3v2yvUZjmKncl7U91fup7E=
|
||||
github.com/atomicgo/cursor v0.0.1/go.mod h1:cBON2QmmrysudxNBFthvMtN32r3jxVRIvzkUiF/RuIk=
|
||||
github.com/aws/aws-sdk-go v1.55.8 h1:JRmEUbU52aJQZ2AjX4q4Wu7t4uZjOu71uyNmaWlUkJQ=
|
||||
github.com/aws/aws-sdk-go v1.55.8/go.mod h1:ZkViS9AqA6otK+JBBNH2++sx1sgxrPKcSzPPvQkUtXk=
|
||||
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/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U=
|
||||
github.com/containerd/console v1.0.5 h1:R0ymNeydRqH2DmakFNdmjR2k0t7UPuiOV/N/27/qqsc=
|
||||
github.com/containerd/console v1.0.5/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk=
|
||||
@@ -29,24 +43,51 @@ github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6N
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/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.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M=
|
||||
github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/go-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.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
|
||||
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||
github.com/gofiber/fiber/v2 v2.52.9 h1:YjKl5DOiyP3j0mO61u3NTmK7or8GzzWzCFzkboyP5cw=
|
||||
github.com/gofiber/fiber/v2 v2.52.9/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
github.com/google/go-cmdtest v0.4.1-0.20220921163831-55ab3332a786 h1:rcv+Ippz6RAtvaGgKxc+8FQIpxHgsF+HBzPyYL2cyVU=
|
||||
github.com/google/go-cmdtest v0.4.1-0.20220921163831-55ab3332a786/go.mod h1:apVn/GCasLZUVpAJ6oWAuyP7Ne7CEsQbTnc0plM3m+o=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/go-tpm v0.9.5 h1:ocUmnDebX54dnW+MQWGQRbdaAcJELsa6PqZhJ48KwVU=
|
||||
github.com/google/go-tpm v0.9.5/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY=
|
||||
github.com/google/pprof v0.0.0-20250820193118-f64d9cf942d6 h1:EEHtgt9IwisQ2AZ4pIsMjahcegHh6rmhqxzIRQIyepY=
|
||||
github.com/google/pprof v0.0.0-20250820193118-f64d9cf942d6/go.mod h1:I6V7YzU0XDpsHqbsyrghnFZLO1gwK6NPTNvmetQIk9U=
|
||||
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.6 h1:GW/XbdyBFQ8Qe+YAmFU9uHLo7OnF5tL52HFAgMmyrf4=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.6/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA=
|
||||
github.com/googleapis/gax-go/v2 v2.15.0 h1:SyjDc1mGgZU5LncH8gimWo9lW1DtIfPibOG81vgd/bo=
|
||||
github.com/googleapis/gax-go/v2 v2.15.0/go.mod h1:zVVkkxAQHa1RQpg9z2AUCMnKhi0Qld9rcmyfL1OZhoc=
|
||||
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.5.4 h1:FZmqs7XOyGgCAxmWyPslpiok1k05wmY3SJTytgvYFs0=
|
||||
github.com/gookit/color v1.5.4/go.mod h1:pZJOeOS8DM43rXbp4AZo1n9zCU2qjpcRko0b6/QJi9w=
|
||||
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/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
@@ -62,8 +103,8 @@ github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYW
|
||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.0.10/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=
|
||||
github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=
|
||||
github.com/klauspost/cpuid/v2 v2.2.3 h1:sxCkb+qR91z4vsqw4vGGZlDgPz3G7gjaLyK3V8y70BU=
|
||||
github.com/klauspost/cpuid/v2 v2.2.3/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
@@ -85,12 +126,8 @@ github.com/minio/highwayhash v1.0.3 h1:kbnuUMoHYyVl7szWjSxJnxw11k2U709jqFPPmIUyD
|
||||
github.com/minio/highwayhash v1.0.3/go.mod h1:GGYsuwP/fPD6Y9hMiXuapVvlIUEhFhMTh0rxU3ik1LQ=
|
||||
github.com/mulgadc/predastore v0.1.5 h1:7SySkQQ5MQ1NTcKcB14pCUIJipq0YVgCJZeKM6ytrLM=
|
||||
github.com/mulgadc/predastore v0.1.5/go.mod h1:BhMymRV3zMTu2S+5pJnNDDm5+AGakjhSc/PKX0q8Xfg=
|
||||
github.com/mulgadc/viperblock v0.1.2 h1:h5yAha2CIsSp/x7oPvqByqlsYzZYSLLim15AL1o7Dm0=
|
||||
github.com/mulgadc/viperblock v0.1.2/go.mod h1:zCjdQ76fi2Qs2a5ign9A/Uqgbz77xts5WV0OR1be48I=
|
||||
github.com/mulgadc/viperblock v0.1.3-0.20260116051939-cca6f6a8a562 h1:uXQUXuHsnZZW0NBYiv6bFDRKIluWKKpQGPrfsDJme9k=
|
||||
github.com/mulgadc/viperblock v0.1.3-0.20260116051939-cca6f6a8a562/go.mod h1:eFolZVfX4iBFBnHAfOoYILfiIjNoI/NhmKht3OIPRR8=
|
||||
github.com/mulgadc/viperblock v0.1.3 h1:xooK1uTUI0RLK23PHAuVInirC3TrBo6/6ezkt+1vMJo=
|
||||
github.com/mulgadc/viperblock v0.1.3/go.mod h1:eFolZVfX4iBFBnHAfOoYILfiIjNoI/NhmKht3OIPRR8=
|
||||
github.com/mulgadc/viperblock v0.1.4 h1:YnRHEC6XNbhnSk6S+ET3X7DTMRezdO3o1O3nO/R9Syg=
|
||||
github.com/mulgadc/viperblock v0.1.4/go.mod h1:JNooDHb6ZuPvNPWaf0gChlft/D8xs5jHJFln+P3B9lQ=
|
||||
github.com/nats-io/jwt/v2 v2.7.4 h1:jXFuDDxs/GQjGDZGhNgH4tXzSUK6WQi2rsj4xmsNOtI=
|
||||
github.com/nats-io/jwt/v2 v2.7.4/go.mod h1:me11pOkwObtcBNR8AiMrUbtVOUGkqYjMQZ6jnSdVUIA=
|
||||
github.com/nats-io/nats-server/v2 v2.11.6 h1:4VXRjbTUFKEB+7UoaKL3F5Y83xC7MxPoIONOnGgpkHw=
|
||||
@@ -101,6 +138,12 @@ github.com/nats-io/nkeys v0.4.11 h1:q44qGV008kYd9W1b1nEBkNzvnWxtRSQ7A8BoqRrcfa0=
|
||||
github.com/nats-io/nkeys v0.4.11/go.mod h1:szDimtgmfOi9n25JpfIdGw12tZFYXqhGxjhVxsatHVE=
|
||||
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.27.2 h1:LzwLj0b89qtIy6SSASkzlNvX6WktqurSHwkk2ipF/Ns=
|
||||
github.com/onsi/ginkgo/v2 v2.27.2/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo=
|
||||
github.com/onsi/gomega v1.38.2 h1:eZCjf2xjZAqe+LeWvKb5weQ+NcPwX84kqJ0cZNxok2A=
|
||||
github.com/onsi/gomega v1.38.2/go.mod h1:W2MJcYxRGV63b418Ai34Ud0hEdTVXq9NW9+Sx6uXf3k=
|
||||
github.com/openai/openai-go/v3 v3.8.1 h1:b+YWsmwqXnbpSHWQEntZAkKciBZ5CJXwL68j+l59UDg=
|
||||
github.com/openai/openai-go/v3 v3.8.1/go.mod h1:UOpNxkqC9OdNXNUfpNByKOtB4jAL0EssQXq5p8gO0Xs=
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
||||
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
@@ -119,11 +162,15 @@ github.com/pterm/pterm v0.12.82/go.mod h1:TyuyrPjnxfwP+ccJdBTeWHtd/e0ybQHkOS/Tak
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
||||
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/sagikazarmark/locafero v0.7.0 h1:5MqpDsTGNDhY8sGp0Aowyf0qKsPrhewaLSsFaodPcyo=
|
||||
github.com/sagikazarmark/locafero v0.7.0/go.mod h1:2za3Cg5rMaTMoG/2Ulr9AwtFaIppKXTRYnozin4aB5k=
|
||||
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.22.11 h1:tW+weM/hCM/GX3iaCV91d5I6hqaRT2TPsFM1+USPXwg=
|
||||
github.com/securego/gosec/v2 v2.22.11/go.mod h1:KE4MW/eH0GLWztkbt4/7XpyH0zJBBnu7sYB4l6Wn7Mw=
|
||||
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/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
|
||||
@@ -139,14 +186,26 @@ github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An
|
||||
github.com/spf13/viper v1.20.1 h1:ZMi+z/lvLyPSCoNtFCpqjy0S4kPbirhpTMwl8BkW9X4=
|
||||
github.com/spf13/viper v1.20.1/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqjJvu4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
||||
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
||||
github.com/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 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
|
||||
github.com/tidwall/match v1.1.1/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/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
github.com/valyala/fasthttp v1.57.0 h1:Xw8SjWGEP/+wAAgyy5XTvgrWlOD1+TxbbvNADYCm1Tg=
|
||||
@@ -159,27 +218,51 @@ github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJu
|
||||
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
|
||||
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q=
|
||||
go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ=
|
||||
go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I=
|
||||
go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE=
|
||||
go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E=
|
||||
go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI=
|
||||
go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps=
|
||||
go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4=
|
||||
go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=
|
||||
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
|
||||
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=
|
||||
go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8=
|
||||
go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI=
|
||||
go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ=
|
||||
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
||||
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
|
||||
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
|
||||
golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561 h1:MDc5xs78ZrZr3HMQugiXOAkSZtfTpbJLDr/lwfgO53E=
|
||||
golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
|
||||
golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
|
||||
golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
|
||||
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-20231108232855-2478ac86f678 h1:1P7xPZEwZMoBoz0Yze5Nx2/4pxj6nw9ZqHWXqP0iRgQ=
|
||||
golang.org/x/exp/typeparams v0.0.0-20231108232855-2478ac86f678/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
|
||||
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.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk=
|
||||
golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
|
||||
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
|
||||
golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -194,32 +277,53 @@ golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
||||
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
|
||||
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/telemetry v0.0.0-20251111182119-bc8e575c7b54 h1:E2/AqCUMZGgd73TQkxUMcMla25GB9i/5HOdLr+uH7Vo=
|
||||
golang.org/x/telemetry v0.0.0-20251111182119-bc8e575c7b54/go.mod h1:hKdjCMrbv9skySur+Nek8Hd0uJ0GuxJIoIX2payrIdQ=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg=
|
||||
golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ=
|
||||
golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU=
|
||||
golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
|
||||
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
|
||||
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
|
||||
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
|
||||
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
|
||||
golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ=
|
||||
golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ=
|
||||
golang.org/x/tools/go/expect v0.1.1-deprecated h1:jpBZDwmgPhXsKZC6WhL20P4b/wmnpsEAGHaNy0n/rJM=
|
||||
golang.org/x/tools/go/expect v0.1.1-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY=
|
||||
golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated h1:1h2MnaIAIXISqTFKdENegdpAgUXz6NrPEsbIeWaBRvM=
|
||||
golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated/go.mod h1:RVAQXBGNv1ib0J382/DPCRS/BPnsGebyM1Gj5VSDpG8=
|
||||
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=
|
||||
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/genai v1.37.0 h1:dgp71k1wQ+/+APdZrN3LFgAGnVnr5IdTF1Oj0Dg+BQc=
|
||||
google.golang.org/genai v1.37.0/go.mod h1:A3kkl0nyBjyFlNjgxIwKq70julKbIxpSxqKO5gw/gmk=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c h1:qXWI/sQtv5UKboZ/zUk7h+mrf/lXORyI+n9DKDAusdg=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c/go.mod h1:gw1tLEfykwDz2ET4a12jcXt4couGAm7IwsVaTy0Sflo=
|
||||
google.golang.org/grpc v1.75.0 h1:+TW+dqTd2Biwe6KKfhE5JpiYIBWq865PhKGSXiivqt4=
|
||||
google.golang.org/grpc v1.75.0/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ=
|
||||
google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc=
|
||||
google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
||||
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
@@ -230,3 +334,5 @@ 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.6.1 h1:R094WgE8K4JirYjBaOpz/AvTyUu/3wbmAoskKN/pxTI=
|
||||
honnef.co/go/tools v0.6.1/go.mod h1:3puzxxljPCe8RGJX7BIy1plGbxEOZni5mR2aXe3/uk4=
|
||||
|
||||
+149
-10
@@ -30,6 +30,7 @@ import (
|
||||
handlers_ec2_image "github.com/mulgadc/hive/hive/handlers/ec2/image"
|
||||
handlers_ec2_instance "github.com/mulgadc/hive/hive/handlers/ec2/instance"
|
||||
handlers_ec2_key "github.com/mulgadc/hive/hive/handlers/ec2/key"
|
||||
handlers_ec2_volume "github.com/mulgadc/hive/hive/handlers/ec2/volume"
|
||||
"github.com/mulgadc/hive/hive/qmp"
|
||||
"github.com/mulgadc/hive/hive/utils"
|
||||
"github.com/mulgadc/hive/hive/vm"
|
||||
@@ -76,6 +77,7 @@ type Daemon struct {
|
||||
instanceService *handlers_ec2_instance.InstanceServiceImpl
|
||||
keyService *handlers_ec2_key.KeyServiceImpl
|
||||
imageService *handlers_ec2_image.ImageServiceImpl
|
||||
volumeService *handlers_ec2_volume.VolumeServiceImpl
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
shutdownWg sync.WaitGroup
|
||||
@@ -91,6 +93,9 @@ type Daemon struct {
|
||||
startTime time.Time
|
||||
configPath string
|
||||
|
||||
// JetStream manager for KV state storage (nil if JetStream disabled)
|
||||
jsManager *JetStreamManager
|
||||
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
@@ -403,8 +408,19 @@ func (d *Daemon) Start() error {
|
||||
|
||||
log.Printf("Connected to NATS server at %s", d.config.NATS.Host)
|
||||
|
||||
// Load existing state for VMs
|
||||
// Load state from disk
|
||||
// Initialize JetStream for KV state storage (falls back to disk if unavailable)
|
||||
d.jsManager, err = NewJetStreamManager(d.natsConn)
|
||||
if err != nil {
|
||||
slog.Warn("Failed to init JetStream, falling back to file", "error", err)
|
||||
d.jsManager = nil
|
||||
} else if err := d.jsManager.InitKVBucket(); err != nil {
|
||||
slog.Warn("Failed to init KV bucket, falling back to file", "error", err)
|
||||
d.jsManager = nil
|
||||
} else {
|
||||
slog.Info("JetStream KV store initialized successfully")
|
||||
}
|
||||
|
||||
// Load existing state for VMs from JetStream or disk
|
||||
err = d.LoadState()
|
||||
if err != nil {
|
||||
slog.Warn("Failed to load state from disk, continuing with empty state", "error", err)
|
||||
@@ -461,6 +477,9 @@ func (d *Daemon) Start() error {
|
||||
// Create image service for handling EC2 AMI operations
|
||||
d.imageService = handlers_ec2_image.NewImageServiceImpl(d.config)
|
||||
|
||||
// Create volume service for handling EC2 EBS volume operations
|
||||
d.volumeService = handlers_ec2_volume.NewVolumeServiceImpl(d.config)
|
||||
|
||||
log.Printf("Subscribing to subject pattern: %s", "ec2.launch")
|
||||
|
||||
// Subscribe to EC2 events with queue group (legacy topic for backward compatibility)
|
||||
@@ -526,6 +545,15 @@ func (d *Daemon) Start() error {
|
||||
return fmt.Errorf("failed to subscribe to NATS ec2.DescribeImages: %w", err)
|
||||
}
|
||||
|
||||
log.Printf("Subscribing to subject pattern: %s", "ec2.DescribeVolumes")
|
||||
|
||||
// Subscribe to EC2 DescribeVolumes with queue group
|
||||
d.natsSubscriptions["ec2.DescribeVolumes"], err = d.natsConn.QueueSubscribe("ec2.DescribeVolumes", "hive-workers", d.handleEC2DescribeVolumes)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to subscribe to NATS ec2.DescribeVolumes: %w", err)
|
||||
}
|
||||
|
||||
log.Printf("Subscribing to subject pattern: %s", "ec2.DescribeInstances")
|
||||
|
||||
// Subscribe to EC2 DescribeInstances - no queue group for multi-node fan-out
|
||||
@@ -759,8 +787,20 @@ func (d *Daemon) ClusterManager() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Write the state to disk
|
||||
// WriteState writes the instance state to JetStream KV store (if enabled) with disk fallback
|
||||
func (d *Daemon) WriteState() error {
|
||||
if d.jsManager != nil {
|
||||
if err := d.jsManager.WriteState(d.node, &d.Instances); err != nil {
|
||||
slog.Error("JetStream write failed, falling back to disk", "error", err)
|
||||
return d.writeStateToDisk()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return d.writeStateToDisk()
|
||||
}
|
||||
|
||||
// writeStateToDisk writes the instance state to disk (original WriteState behavior)
|
||||
func (d *Daemon) writeStateToDisk() error {
|
||||
d.Instances.Mu.Lock()
|
||||
defer d.Instances.Mu.Unlock()
|
||||
|
||||
@@ -814,8 +854,37 @@ func (d *Daemon) InitaliseVMs() {
|
||||
// Step 2: Loop through each instance and start it
|
||||
}
|
||||
|
||||
// Load state from disk
|
||||
// LoadState loads the instance state from JetStream KV store (if enabled) with disk fallback
|
||||
func (d *Daemon) LoadState() error {
|
||||
if d.jsManager != nil {
|
||||
instances, err := d.jsManager.LoadState(d.node)
|
||||
if err != nil {
|
||||
slog.Warn("JetStream load failed, trying disk", "error", err)
|
||||
return d.loadStateFromDisk()
|
||||
}
|
||||
|
||||
// If JetStream returned empty state, check if disk has data to migrate
|
||||
if len(instances.VMS) == 0 {
|
||||
diskErr := d.loadStateFromDisk()
|
||||
if diskErr == nil && len(d.Instances.VMS) > 0 {
|
||||
slog.Info("Migrating state from disk to JetStream", "instances", len(d.Instances.VMS))
|
||||
// Write existing disk state to JetStream for future use
|
||||
if writeErr := d.jsManager.WriteState(d.node, &d.Instances); writeErr != nil {
|
||||
slog.Warn("Failed to migrate state to JetStream", "error", writeErr)
|
||||
}
|
||||
return nil // Use the disk state we just loaded
|
||||
}
|
||||
}
|
||||
|
||||
// Copy only the VMS map, not the mutex
|
||||
d.Instances.VMS = instances.VMS
|
||||
return nil
|
||||
}
|
||||
return d.loadStateFromDisk()
|
||||
}
|
||||
|
||||
// loadStateFromDisk loads the instance state from disk (original LoadState behavior)
|
||||
func (d *Daemon) loadStateFromDisk() error {
|
||||
configPath := fmt.Sprintf("%s/%s", d.config.BaseDir, "instances.json")
|
||||
jsonData, err := os.ReadFile(configPath)
|
||||
if err != nil {
|
||||
@@ -1040,6 +1109,34 @@ func (d *Daemon) handleEC2Events(msg *nats.Msg) {
|
||||
|
||||
}
|
||||
|
||||
// If a stop command, clean up resources but keep instance state
|
||||
if command.Attributes.StopInstance && command.QMPCommand.Execute == "system_powerdown" {
|
||||
slog.Info("Stopping instance", "id", command.ID)
|
||||
|
||||
// Update status to stopping
|
||||
d.Instances.Mu.Lock()
|
||||
instance.Status = "stopping"
|
||||
d.Instances.Mu.Unlock()
|
||||
|
||||
// Stop the instance (keep volumes)
|
||||
stopInstance := make(map[string]*vm.VM)
|
||||
stopInstance[instance.ID] = instance
|
||||
err = d.stopInstance(stopInstance, false)
|
||||
|
||||
if err != nil {
|
||||
slog.Error("Failed to stop instance", "err", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Update status to stopped
|
||||
d.Instances.Mu.Lock()
|
||||
instance.Status = "stopped"
|
||||
instance.Attributes = command.Attributes
|
||||
d.Instances.Mu.Unlock()
|
||||
|
||||
slog.Info("Instance stopped", "id", command.ID)
|
||||
}
|
||||
|
||||
// If a terminate command, clean up resources
|
||||
if command.Attributes.TerminateInstance {
|
||||
slog.Info("Terminating instance", "id", command.ID)
|
||||
@@ -1406,6 +1503,12 @@ func (d *Daemon) setupShutdown() {
|
||||
|
||||
}
|
||||
|
||||
// Write state to JetStream before closing NATS connection
|
||||
err := d.WriteState()
|
||||
if err != nil {
|
||||
slog.Error("Failed to write state", "err", err)
|
||||
}
|
||||
|
||||
// Close NATS connection
|
||||
d.natsConn.Close()
|
||||
|
||||
@@ -1417,12 +1520,6 @@ func (d *Daemon) setupShutdown() {
|
||||
}
|
||||
}
|
||||
|
||||
// Write the state to disk
|
||||
err := d.WriteState()
|
||||
if err != nil {
|
||||
slog.Error("Failed to write state to disk", "err", err)
|
||||
}
|
||||
|
||||
// Wait for any ongoing operations to complete
|
||||
// TODO: Implement cleanup of running instances
|
||||
log.Println("Shutdown complete")
|
||||
@@ -2095,6 +2192,48 @@ func (d *Daemon) handleEC2DescribeImages(msg *nats.Msg) {
|
||||
slog.Info("handleEC2DescribeImages completed", "count", len(output.Images))
|
||||
}
|
||||
|
||||
// handleEC2DescribeVolumes processes incoming EC2 DescribeVolumes requests
|
||||
func (d *Daemon) handleEC2DescribeVolumes(msg *nats.Msg) {
|
||||
log.Printf("Received message on subject: %s", msg.Subject)
|
||||
log.Printf("Message data: %s", string(msg.Data))
|
||||
|
||||
// Initialize describeVolumesInput before unmarshaling into it
|
||||
describeVolumesInput := &ec2.DescribeVolumesInput{}
|
||||
var errResp []byte
|
||||
|
||||
errResp = utils.UnmarshalJsonPayload(describeVolumesInput, msg.Data)
|
||||
|
||||
if errResp != nil {
|
||||
msg.Respond(errResp)
|
||||
slog.Error("Request does not match DescribeVolumesInput")
|
||||
return
|
||||
}
|
||||
|
||||
slog.Info("Processing DescribeVolumes request")
|
||||
|
||||
// Delegate to volume service for business logic (S3 listing)
|
||||
output, err := d.volumeService.DescribeVolumes(describeVolumesInput)
|
||||
|
||||
if err != nil {
|
||||
slog.Error("handleEC2DescribeVolumes service.DescribeVolumes failed", "err", err)
|
||||
errResp = utils.GenerateErrorPayload(err.Error())
|
||||
msg.Respond(errResp)
|
||||
return
|
||||
}
|
||||
|
||||
// Respond to NATS with DescribeVolumesOutput
|
||||
jsonResponse, err := json.Marshal(output)
|
||||
if err != nil {
|
||||
slog.Error("handleEC2DescribeVolumes failed to marshal output", "err", err)
|
||||
errResp = utils.GenerateErrorPayload(awserrors.ErrorServerInternal)
|
||||
msg.Respond(errResp)
|
||||
return
|
||||
}
|
||||
msg.Respond(jsonResponse)
|
||||
|
||||
slog.Info("handleEC2DescribeVolumes completed", "count", len(output.Volumes))
|
||||
}
|
||||
|
||||
// handleEC2DescribeInstanceTypes processes incoming EC2 DescribeInstanceTypes requests
|
||||
// This handler responds with instance types that can currently be provisioned on this node
|
||||
// based on available resources (CPU and memory not already allocated to running instances)
|
||||
|
||||
@@ -0,0 +1,140 @@
|
||||
package daemon
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"log/slog"
|
||||
|
||||
"github.com/mulgadc/hive/hive/vm"
|
||||
"github.com/nats-io/nats.go"
|
||||
)
|
||||
|
||||
const (
|
||||
// InstanceStateBucket is the name of the KV bucket for storing instance state
|
||||
InstanceStateBucket = "hive-instance-state"
|
||||
// InstanceStatePrefix is the key prefix for instance state entries
|
||||
InstanceStatePrefix = "node."
|
||||
)
|
||||
|
||||
// JetStreamManager manages JetStream KV store operations for instance state
|
||||
type JetStreamManager struct {
|
||||
js nats.JetStreamContext
|
||||
kv nats.KeyValue
|
||||
}
|
||||
|
||||
// NewJetStreamManager creates a new JetStreamManager from a NATS connection
|
||||
func NewJetStreamManager(nc *nats.Conn) (*JetStreamManager, error) {
|
||||
js, err := nc.JetStream()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &JetStreamManager{
|
||||
js: js,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// InitKVBucket initializes the KV bucket, creating it if it doesn't exist
|
||||
func (m *JetStreamManager) InitKVBucket() error {
|
||||
// Try to get the existing bucket first
|
||||
kv, err := m.js.KeyValue(InstanceStateBucket)
|
||||
if err != nil {
|
||||
if errors.Is(err, nats.ErrBucketNotFound) {
|
||||
// Bucket doesn't exist, create it
|
||||
slog.Info("Creating JetStream KV bucket", "bucket", InstanceStateBucket)
|
||||
kv, err = m.js.CreateKeyValue(&nats.KeyValueConfig{
|
||||
Bucket: InstanceStateBucket,
|
||||
Description: "Hive instance state storage",
|
||||
History: 1, // Only keep latest value
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
slog.Info("Connected to existing JetStream KV bucket", "bucket", InstanceStateBucket)
|
||||
}
|
||||
|
||||
m.kv = kv
|
||||
return nil
|
||||
}
|
||||
|
||||
// WriteState writes the instance state to the KV store for the given node
|
||||
func (m *JetStreamManager) WriteState(nodeID string, instances *vm.Instances) error {
|
||||
if m.kv == nil {
|
||||
return errors.New("KV bucket not initialized")
|
||||
}
|
||||
|
||||
instances.Mu.Lock()
|
||||
defer instances.Mu.Unlock()
|
||||
|
||||
// Create a struct without the mutex to avoid copying the lock
|
||||
state := struct {
|
||||
VMS map[string]*vm.VM `json:"vms"`
|
||||
}{
|
||||
VMS: instances.VMS,
|
||||
}
|
||||
|
||||
jsonData, err := json.Marshal(state)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
key := InstanceStatePrefix + nodeID
|
||||
_, err = m.kv.Put(key, jsonData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
slog.Debug("Wrote state to JetStream KV", "key", key, "instances", len(instances.VMS))
|
||||
return nil
|
||||
}
|
||||
|
||||
// LoadState loads the instance state from the KV store for the given node
|
||||
func (m *JetStreamManager) LoadState(nodeID string) (*vm.Instances, error) {
|
||||
if m.kv == nil {
|
||||
return nil, errors.New("KV bucket not initialized")
|
||||
}
|
||||
|
||||
key := InstanceStatePrefix + nodeID
|
||||
entry, err := m.kv.Get(key)
|
||||
if err != nil {
|
||||
if errors.Is(err, nats.ErrKeyNotFound) {
|
||||
// Key doesn't exist, return empty state
|
||||
slog.Debug("No existing state in JetStream KV, returning empty state", "key", key)
|
||||
return &vm.Instances{VMS: make(map[string]*vm.VM)}, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var instances vm.Instances
|
||||
if err := json.Unmarshal(entry.Value(), &instances); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Ensure the VMS map is initialized
|
||||
if instances.VMS == nil {
|
||||
instances.VMS = make(map[string]*vm.VM)
|
||||
}
|
||||
|
||||
slog.Debug("Loaded state from JetStream KV", "key", key, "instances", len(instances.VMS))
|
||||
return &instances, nil
|
||||
}
|
||||
|
||||
// DeleteState removes the instance state from the KV store for the given node
|
||||
func (m *JetStreamManager) DeleteState(nodeID string) error {
|
||||
if m.kv == nil {
|
||||
return errors.New("KV bucket not initialized")
|
||||
}
|
||||
|
||||
key := InstanceStatePrefix + nodeID
|
||||
err := m.kv.Delete(key)
|
||||
if err != nil && !errors.Is(err, nats.ErrKeyNotFound) {
|
||||
return err
|
||||
}
|
||||
|
||||
slog.Debug("Deleted state from JetStream KV", "key", key)
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,386 @@
|
||||
package daemon
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/mulgadc/hive/hive/vm"
|
||||
"github.com/nats-io/nats-server/v2/server"
|
||||
"github.com/nats-io/nats.go"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// startTestNATSServerWithJetStream starts an embedded NATS server with JetStream enabled for testing
|
||||
func startTestNATSServerWithJetStream(t *testing.T) (*server.Server, string) {
|
||||
// Create a temporary directory for JetStream storage
|
||||
tmpDir, err := os.MkdirTemp("", "nats-js-test-*")
|
||||
require.NoError(t, err, "Failed to create temp directory for JetStream")
|
||||
|
||||
t.Cleanup(func() {
|
||||
os.RemoveAll(tmpDir)
|
||||
})
|
||||
|
||||
opts := &server.Options{
|
||||
Host: "127.0.0.1",
|
||||
Port: -1, // Let NATS auto-allocate an available port
|
||||
JetStream: true,
|
||||
StoreDir: tmpDir,
|
||||
NoLog: true,
|
||||
NoSigs: true,
|
||||
}
|
||||
|
||||
ns, err := server.NewServer(opts)
|
||||
require.NoError(t, err, "Failed to create NATS server with JetStream")
|
||||
|
||||
// Start server in a goroutine
|
||||
go ns.Start()
|
||||
|
||||
// Wait for server to be ready
|
||||
if !ns.ReadyForConnections(5 * time.Second) {
|
||||
t.Fatal("NATS server with JetStream failed to start")
|
||||
}
|
||||
|
||||
// Get the actual URL that was assigned
|
||||
url := ns.ClientURL()
|
||||
t.Logf("Test NATS server with JetStream started at: %s", url)
|
||||
|
||||
return ns, url
|
||||
}
|
||||
|
||||
// TestJetStreamManager_WriteAndLoadState tests round-trip write and load of instance state
|
||||
func TestJetStreamManager_WriteAndLoadState(t *testing.T) {
|
||||
ns, natsURL := startTestNATSServerWithJetStream(t)
|
||||
defer ns.Shutdown()
|
||||
|
||||
// Connect to NATS
|
||||
nc, err := nats.Connect(natsURL)
|
||||
require.NoError(t, err, "Failed to connect to NATS")
|
||||
defer nc.Close()
|
||||
|
||||
// Create JetStreamManager
|
||||
jsm, err := NewJetStreamManager(nc)
|
||||
require.NoError(t, err, "Failed to create JetStreamManager")
|
||||
|
||||
// Initialize the KV bucket
|
||||
err = jsm.InitKVBucket()
|
||||
require.NoError(t, err, "Failed to init KV bucket")
|
||||
|
||||
// Create test instances
|
||||
testNodeID := "test-node-1"
|
||||
testInstances := &vm.Instances{
|
||||
VMS: map[string]*vm.VM{
|
||||
"i-test-001": {
|
||||
ID: "i-test-001",
|
||||
Status: "running",
|
||||
InstanceType: "t3.micro",
|
||||
},
|
||||
"i-test-002": {
|
||||
ID: "i-test-002",
|
||||
Status: "stopped",
|
||||
InstanceType: "t3.small",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Write state
|
||||
err = jsm.WriteState(testNodeID, testInstances)
|
||||
require.NoError(t, err, "Failed to write state")
|
||||
|
||||
// Load state
|
||||
loadedInstances, err := jsm.LoadState(testNodeID)
|
||||
require.NoError(t, err, "Failed to load state")
|
||||
require.NotNil(t, loadedInstances, "Loaded instances should not be nil")
|
||||
|
||||
// Verify the loaded state matches
|
||||
assert.Len(t, loadedInstances.VMS, 2, "Should have 2 instances")
|
||||
assert.NotNil(t, loadedInstances.VMS["i-test-001"], "Should have i-test-001")
|
||||
assert.NotNil(t, loadedInstances.VMS["i-test-002"], "Should have i-test-002")
|
||||
assert.Equal(t, "running", loadedInstances.VMS["i-test-001"].Status)
|
||||
assert.Equal(t, "stopped", loadedInstances.VMS["i-test-002"].Status)
|
||||
assert.Equal(t, "t3.micro", loadedInstances.VMS["i-test-001"].InstanceType)
|
||||
assert.Equal(t, "t3.small", loadedInstances.VMS["i-test-002"].InstanceType)
|
||||
}
|
||||
|
||||
// TestJetStreamManager_LoadState_KeyNotFound tests that LoadState returns empty state when key doesn't exist
|
||||
func TestJetStreamManager_LoadState_KeyNotFound(t *testing.T) {
|
||||
ns, natsURL := startTestNATSServerWithJetStream(t)
|
||||
defer ns.Shutdown()
|
||||
|
||||
nc, err := nats.Connect(natsURL)
|
||||
require.NoError(t, err)
|
||||
defer nc.Close()
|
||||
|
||||
jsm, err := NewJetStreamManager(nc)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = jsm.InitKVBucket()
|
||||
require.NoError(t, err)
|
||||
|
||||
// Load state for a non-existent node
|
||||
instances, err := jsm.LoadState("non-existent-node")
|
||||
require.NoError(t, err, "Should not error when key not found")
|
||||
require.NotNil(t, instances, "Should return non-nil instances")
|
||||
assert.Empty(t, instances.VMS, "Should return empty VMS map")
|
||||
}
|
||||
|
||||
// TestJetStreamManager_BucketCreation tests that InitKVBucket creates the bucket when it doesn't exist
|
||||
func TestJetStreamManager_BucketCreation(t *testing.T) {
|
||||
ns, natsURL := startTestNATSServerWithJetStream(t)
|
||||
defer ns.Shutdown()
|
||||
|
||||
nc, err := nats.Connect(natsURL)
|
||||
require.NoError(t, err)
|
||||
defer nc.Close()
|
||||
|
||||
jsm, err := NewJetStreamManager(nc)
|
||||
require.NoError(t, err)
|
||||
|
||||
// InitKVBucket should create the bucket
|
||||
err = jsm.InitKVBucket()
|
||||
require.NoError(t, err, "Should create bucket without error")
|
||||
|
||||
// Verify the bucket exists by checking jsm.kv is set
|
||||
assert.NotNil(t, jsm.kv, "KV bucket should be initialized")
|
||||
}
|
||||
|
||||
// TestJetStreamManager_BucketReconnection tests that InitKVBucket connects to existing bucket
|
||||
func TestJetStreamManager_BucketReconnection(t *testing.T) {
|
||||
ns, natsURL := startTestNATSServerWithJetStream(t)
|
||||
defer ns.Shutdown()
|
||||
|
||||
// First connection - create the bucket
|
||||
nc1, err := nats.Connect(natsURL)
|
||||
require.NoError(t, err)
|
||||
|
||||
jsm1, err := NewJetStreamManager(nc1)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = jsm1.InitKVBucket()
|
||||
require.NoError(t, err, "First InitKVBucket should succeed")
|
||||
|
||||
// Write some test data
|
||||
testInstances := &vm.Instances{
|
||||
VMS: map[string]*vm.VM{
|
||||
"i-persist": {
|
||||
ID: "i-persist",
|
||||
Status: "running",
|
||||
},
|
||||
},
|
||||
}
|
||||
err = jsm1.WriteState("persist-node", testInstances)
|
||||
require.NoError(t, err)
|
||||
|
||||
nc1.Close()
|
||||
|
||||
// Second connection - should connect to existing bucket
|
||||
nc2, err := nats.Connect(natsURL)
|
||||
require.NoError(t, err)
|
||||
defer nc2.Close()
|
||||
|
||||
jsm2, err := NewJetStreamManager(nc2)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = jsm2.InitKVBucket()
|
||||
require.NoError(t, err, "Second InitKVBucket should succeed (reconnect)")
|
||||
|
||||
// Verify data persisted
|
||||
loadedInstances, err := jsm2.LoadState("persist-node")
|
||||
require.NoError(t, err)
|
||||
assert.NotEmpty(t, loadedInstances.VMS, "Should have persisted instances")
|
||||
assert.NotNil(t, loadedInstances.VMS["i-persist"], "Should have i-persist")
|
||||
assert.Equal(t, "running", loadedInstances.VMS["i-persist"].Status)
|
||||
}
|
||||
|
||||
// TestJetStreamManager_DeleteState tests deleting state from the KV store
|
||||
func TestJetStreamManager_DeleteState(t *testing.T) {
|
||||
ns, natsURL := startTestNATSServerWithJetStream(t)
|
||||
defer ns.Shutdown()
|
||||
|
||||
nc, err := nats.Connect(natsURL)
|
||||
require.NoError(t, err)
|
||||
defer nc.Close()
|
||||
|
||||
jsm, err := NewJetStreamManager(nc)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = jsm.InitKVBucket()
|
||||
require.NoError(t, err)
|
||||
|
||||
// Write state
|
||||
testNodeID := "delete-test-node"
|
||||
testInstances := &vm.Instances{
|
||||
VMS: map[string]*vm.VM{
|
||||
"i-delete-me": {
|
||||
ID: "i-delete-me",
|
||||
Status: "running",
|
||||
},
|
||||
},
|
||||
}
|
||||
err = jsm.WriteState(testNodeID, testInstances)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Verify state exists
|
||||
loadedInstances, err := jsm.LoadState(testNodeID)
|
||||
require.NoError(t, err)
|
||||
assert.NotEmpty(t, loadedInstances.VMS)
|
||||
|
||||
// Delete state
|
||||
err = jsm.DeleteState(testNodeID)
|
||||
require.NoError(t, err, "Should delete state without error")
|
||||
|
||||
// Verify state is gone (should return empty state)
|
||||
loadedInstances, err = jsm.LoadState(testNodeID)
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, loadedInstances.VMS, "Should return empty state after deletion")
|
||||
}
|
||||
|
||||
// TestJetStreamManager_DeleteState_NonExistent tests deleting state that doesn't exist
|
||||
func TestJetStreamManager_DeleteState_NonExistent(t *testing.T) {
|
||||
ns, natsURL := startTestNATSServerWithJetStream(t)
|
||||
defer ns.Shutdown()
|
||||
|
||||
nc, err := nats.Connect(natsURL)
|
||||
require.NoError(t, err)
|
||||
defer nc.Close()
|
||||
|
||||
jsm, err := NewJetStreamManager(nc)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = jsm.InitKVBucket()
|
||||
require.NoError(t, err)
|
||||
|
||||
// Delete non-existent state should not error
|
||||
err = jsm.DeleteState("non-existent-node")
|
||||
require.NoError(t, err, "Deleting non-existent state should not error")
|
||||
}
|
||||
|
||||
// TestJetStreamManager_WriteState_UpdateExisting tests that writing state updates existing entry
|
||||
func TestJetStreamManager_WriteState_UpdateExisting(t *testing.T) {
|
||||
ns, natsURL := startTestNATSServerWithJetStream(t)
|
||||
defer ns.Shutdown()
|
||||
|
||||
nc, err := nats.Connect(natsURL)
|
||||
require.NoError(t, err)
|
||||
defer nc.Close()
|
||||
|
||||
jsm, err := NewJetStreamManager(nc)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = jsm.InitKVBucket()
|
||||
require.NoError(t, err)
|
||||
|
||||
testNodeID := "update-test-node"
|
||||
|
||||
// Write initial state
|
||||
initialInstances := &vm.Instances{
|
||||
VMS: map[string]*vm.VM{
|
||||
"i-initial": {
|
||||
ID: "i-initial",
|
||||
Status: "running",
|
||||
},
|
||||
},
|
||||
}
|
||||
err = jsm.WriteState(testNodeID, initialInstances)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Update state with different instances
|
||||
updatedInstances := &vm.Instances{
|
||||
VMS: map[string]*vm.VM{
|
||||
"i-initial": {
|
||||
ID: "i-initial",
|
||||
Status: "stopped", // Changed status
|
||||
},
|
||||
"i-new": { // Added new instance
|
||||
ID: "i-new",
|
||||
Status: "running",
|
||||
},
|
||||
},
|
||||
}
|
||||
err = jsm.WriteState(testNodeID, updatedInstances)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Load and verify updated state
|
||||
loadedInstances, err := jsm.LoadState(testNodeID)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, loadedInstances.VMS, 2, "Should have 2 instances")
|
||||
assert.Equal(t, "stopped", loadedInstances.VMS["i-initial"].Status, "Status should be updated")
|
||||
assert.NotNil(t, loadedInstances.VMS["i-new"], "Should have new instance")
|
||||
}
|
||||
|
||||
// TestJetStreamManager_MultipleNodes tests storing state for multiple nodes
|
||||
func TestJetStreamManager_MultipleNodes(t *testing.T) {
|
||||
ns, natsURL := startTestNATSServerWithJetStream(t)
|
||||
defer ns.Shutdown()
|
||||
|
||||
nc, err := nats.Connect(natsURL)
|
||||
require.NoError(t, err)
|
||||
defer nc.Close()
|
||||
|
||||
jsm, err := NewJetStreamManager(nc)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = jsm.InitKVBucket()
|
||||
require.NoError(t, err)
|
||||
|
||||
// Write state for node-1
|
||||
node1Instances := &vm.Instances{
|
||||
VMS: map[string]*vm.VM{
|
||||
"i-node1-001": {ID: "i-node1-001", Status: "running"},
|
||||
},
|
||||
}
|
||||
err = jsm.WriteState("node-1", node1Instances)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Write state for node-2
|
||||
node2Instances := &vm.Instances{
|
||||
VMS: map[string]*vm.VM{
|
||||
"i-node2-001": {ID: "i-node2-001", Status: "stopped"},
|
||||
"i-node2-002": {ID: "i-node2-002", Status: "running"},
|
||||
},
|
||||
}
|
||||
err = jsm.WriteState("node-2", node2Instances)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Load and verify node-1 state
|
||||
loadedNode1, err := jsm.LoadState("node-1")
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, loadedNode1.VMS, 1)
|
||||
assert.NotNil(t, loadedNode1.VMS["i-node1-001"])
|
||||
|
||||
// Load and verify node-2 state
|
||||
loadedNode2, err := jsm.LoadState("node-2")
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, loadedNode2.VMS, 2)
|
||||
assert.NotNil(t, loadedNode2.VMS["i-node2-001"])
|
||||
assert.NotNil(t, loadedNode2.VMS["i-node2-002"])
|
||||
|
||||
// Verify node isolation - node-1 doesn't have node-2's instances
|
||||
_, exists := loadedNode1.VMS["i-node2-001"]
|
||||
assert.False(t, exists, "Node-1 should not have node-2's instances")
|
||||
}
|
||||
|
||||
// TestJetStreamManager_KVNotInitialized tests error handling when KV is not initialized
|
||||
func TestJetStreamManager_KVNotInitialized(t *testing.T) {
|
||||
ns, natsURL := startTestNATSServerWithJetStream(t)
|
||||
defer ns.Shutdown()
|
||||
|
||||
nc, err := nats.Connect(natsURL)
|
||||
require.NoError(t, err)
|
||||
defer nc.Close()
|
||||
|
||||
// Create JetStreamManager but don't call InitKVBucket
|
||||
jsm, err := NewJetStreamManager(nc)
|
||||
require.NoError(t, err)
|
||||
|
||||
testInstances := &vm.Instances{VMS: make(map[string]*vm.VM)}
|
||||
err = jsm.WriteState("test-node", testInstances)
|
||||
assert.Error(t, err, "WriteState should error when KV not initialized")
|
||||
|
||||
_, err = jsm.LoadState("test-node")
|
||||
assert.Error(t, err, "LoadState should error when KV not initialized")
|
||||
|
||||
err = jsm.DeleteState("test-node")
|
||||
assert.Error(t, err, "DeleteState should error when KV not initialized")
|
||||
}
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
gateway_ec2_instance "github.com/mulgadc/hive/hive/gateway/ec2/instance"
|
||||
gateway_ec2_key "github.com/mulgadc/hive/hive/gateway/ec2/key"
|
||||
gateway_ec2_regions "github.com/mulgadc/hive/hive/gateway/ec2/regions"
|
||||
gateway_ec2_volume "github.com/mulgadc/hive/hive/gateway/ec2/volume"
|
||||
"github.com/mulgadc/hive/hive/utils"
|
||||
)
|
||||
|
||||
@@ -301,6 +302,29 @@ func (gw *GatewayConfig) EC2_Request(ctx *fiber.Ctx) error {
|
||||
if err != nil {
|
||||
return errors.New("failed to marshal response to XML")
|
||||
}
|
||||
|
||||
case "DescribeVolumes":
|
||||
var input = &ec2.DescribeVolumesInput{}
|
||||
err = awsec2query.QueryParamsToStruct(queryArgs, input)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
output, err := gateway_ec2_volume.DescribeVolumes(input, gw.NATSConn)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Convert to XML
|
||||
payload := utils.GenerateXMLPayload("DescribeVolumesResponse", output)
|
||||
xmlOutput, err = utils.MarshalToXML(payload)
|
||||
|
||||
if err != nil {
|
||||
return errors.New("failed to marshal response to XML")
|
||||
}
|
||||
|
||||
default:
|
||||
err = errors.New("InvalidAction")
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
)
|
||||
|
||||
// StopInstances sends stop commands to specified instances via NATS
|
||||
// Uses system_powerdown without stop_instance attribute to allow restart
|
||||
// Uses system_powerdown with stop_instance attribute to prevent auto-restart on daemon boot
|
||||
func StopInstances(input *ec2.StopInstancesInput, natsConn *nats.Conn) (*ec2.StopInstancesOutput, error) {
|
||||
if len(input.InstanceIds) == 0 {
|
||||
return nil, fmt.Errorf("no instance IDs provided")
|
||||
@@ -30,7 +30,7 @@ func StopInstances(input *ec2.StopInstancesInput, natsConn *nats.Conn) (*ec2.Sto
|
||||
instanceID := *instanceIDPtr
|
||||
|
||||
// Build the QMP command to stop the instance
|
||||
// Note: system_powerdown without stop_instance allows restart
|
||||
// Note: system_powerdown with stop_instance=true prevents auto-restart on daemon boot
|
||||
command := qmp.Command{
|
||||
ID: instanceID,
|
||||
QMPCommand: qmp.QMPCommand{
|
||||
@@ -38,7 +38,7 @@ func StopInstances(input *ec2.StopInstancesInput, natsConn *nats.Conn) (*ec2.Sto
|
||||
Arguments: map[string]any{},
|
||||
},
|
||||
Attributes: qmp.Attributes{
|
||||
StopInstance: false, // Allow restart on daemon/node restart
|
||||
StopInstance: true, // Don't auto-restart on daemon boot
|
||||
TerminateInstance: false,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
package gateway_ec2_volume
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
handlers_ec2_volume "github.com/mulgadc/hive/hive/handlers/ec2/volume"
|
||||
"github.com/nats-io/nats.go"
|
||||
)
|
||||
|
||||
// ValidateDescribeVolumesInput validates the input parameters
|
||||
func ValidateDescribeVolumesInput(input *ec2.DescribeVolumesInput) error {
|
||||
if input == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate VolumeId format if provided
|
||||
if input.VolumeIds != nil {
|
||||
for _, volumeId := range input.VolumeIds {
|
||||
if volumeId != nil && !strings.HasPrefix(*volumeId, "vol-") {
|
||||
return errors.New("InvalidVolume.Malformed")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DescribeVolumes handles the DescribeVolumes API call
|
||||
func DescribeVolumes(input *ec2.DescribeVolumesInput, natsConn *nats.Conn) (ec2.DescribeVolumesOutput, error) {
|
||||
var output ec2.DescribeVolumesOutput
|
||||
|
||||
// Validate input
|
||||
err := ValidateDescribeVolumesInput(input)
|
||||
if err != nil {
|
||||
return output, err
|
||||
}
|
||||
|
||||
// Create NATS service and call handler
|
||||
volumeService := handlers_ec2_volume.NewNATSVolumeService(natsConn)
|
||||
result, err := volumeService.DescribeVolumes(input)
|
||||
|
||||
if err != nil {
|
||||
return output, err
|
||||
}
|
||||
|
||||
// Return result
|
||||
output = *result
|
||||
return output, nil
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
package gateway_ec2_volume
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestValidateDescribeVolumesInput(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input *ec2.DescribeVolumesInput
|
||||
wantErr bool
|
||||
errMsg string
|
||||
}{
|
||||
{
|
||||
name: "NilInput",
|
||||
input: nil,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "EmptyInput",
|
||||
input: &ec2.DescribeVolumesInput{},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "ValidSingleVolumeId",
|
||||
input: &ec2.DescribeVolumesInput{
|
||||
VolumeIds: []*string{aws.String("vol-0123456789abcdef0")},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "ValidMultipleVolumeIds",
|
||||
input: &ec2.DescribeVolumesInput{
|
||||
VolumeIds: []*string{
|
||||
aws.String("vol-abc123"),
|
||||
aws.String("vol-def456"),
|
||||
aws.String("vol-ghi789"),
|
||||
},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "InvalidVolumeId_NoPrefix",
|
||||
input: &ec2.DescribeVolumesInput{
|
||||
VolumeIds: []*string{aws.String("invalid-id")},
|
||||
},
|
||||
wantErr: true,
|
||||
errMsg: "InvalidVolume.Malformed",
|
||||
},
|
||||
{
|
||||
name: "InvalidVolumeId_WrongPrefix",
|
||||
input: &ec2.DescribeVolumesInput{
|
||||
VolumeIds: []*string{aws.String("ami-0123456789abcdef0")},
|
||||
},
|
||||
wantErr: true,
|
||||
errMsg: "InvalidVolume.Malformed",
|
||||
},
|
||||
{
|
||||
name: "InvalidVolumeId_EmptyString",
|
||||
input: &ec2.DescribeVolumesInput{
|
||||
VolumeIds: []*string{aws.String("")},
|
||||
},
|
||||
wantErr: true,
|
||||
errMsg: "InvalidVolume.Malformed",
|
||||
},
|
||||
{
|
||||
name: "MixedValidAndInvalid",
|
||||
input: &ec2.DescribeVolumesInput{
|
||||
VolumeIds: []*string{
|
||||
aws.String("vol-valid123"),
|
||||
aws.String("invalid-id"),
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
errMsg: "InvalidVolume.Malformed",
|
||||
},
|
||||
{
|
||||
name: "NilVolumeIdInList",
|
||||
input: &ec2.DescribeVolumesInput{
|
||||
VolumeIds: []*string{nil, aws.String("vol-abc123")},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := ValidateDescribeVolumesInput(tt.input)
|
||||
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, tt.errMsg, err.Error())
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -346,7 +346,7 @@ func (s *InstanceServiceImpl) cloneAMIToVolume(input *ec2.RunInstancesInput, siz
|
||||
|
||||
// Read 1MB
|
||||
data, err := amiVb.ReadAt(block*uint64(destVb.BlockSize), uint64(destVb.BlockSize)*1024)
|
||||
if err != nil && err != viperblock.ZeroBlock {
|
||||
if err != nil && err != viperblock.ErrZeroBlock {
|
||||
slog.Error("Failed to read block from AMI source", "err", err)
|
||||
return errors.New(awserrors.ErrorServerInternal)
|
||||
}
|
||||
|
||||
@@ -242,7 +242,7 @@ func formatFingerprint(hash []byte, algorithm string) string {
|
||||
// generateKeyPairID generates a unique key pair ID (similar to AWS key-xxxxx format)
|
||||
func generateKeyPairID() string {
|
||||
hash := sha1.New() //#nosec G401 - need sha1 for AWS compatibility
|
||||
hash.Write([]byte(fmt.Sprintf("%d", time.Now().UnixNano())))
|
||||
hash.Write(fmt.Appendf(nil, "%d", time.Now().UnixNano()))
|
||||
return fmt.Sprintf("%x", hash.Sum(nil))[:16]
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
package handlers_ec2_volume
|
||||
|
||||
import "github.com/aws/aws-sdk-go/service/ec2"
|
||||
|
||||
// VolumeService defines the interface for EBS volume operations
|
||||
type VolumeService interface {
|
||||
DescribeVolumes(input *ec2.DescribeVolumesInput) (*ec2.DescribeVolumesOutput, error)
|
||||
}
|
||||
@@ -0,0 +1,246 @@
|
||||
package handlers_ec2_volume
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/awserr"
|
||||
"github.com/aws/aws-sdk-go/aws/credentials"
|
||||
"github.com/aws/aws-sdk-go/aws/session"
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
"github.com/aws/aws-sdk-go/service/s3"
|
||||
"github.com/mulgadc/hive/hive/awserrors"
|
||||
"github.com/mulgadc/hive/hive/config"
|
||||
"github.com/mulgadc/viperblock/viperblock"
|
||||
)
|
||||
|
||||
// VolumeServiceImpl handles EBS volume operations with S3 storage
|
||||
type VolumeServiceImpl struct {
|
||||
config *config.Config
|
||||
s3Client *s3.S3
|
||||
}
|
||||
|
||||
// NewVolumeServiceImpl creates a new daemon-side volume service
|
||||
func NewVolumeServiceImpl(cfg *config.Config) *VolumeServiceImpl {
|
||||
// Create HTTP client with TLS verification disabled for local S3-compatible endpoints
|
||||
httpClient := &http.Client{
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Create AWS SDK S3 client configured for Predastore endpoint
|
||||
sess := session.Must(session.NewSession(&aws.Config{
|
||||
Endpoint: aws.String(cfg.Predastore.Host),
|
||||
Region: aws.String(cfg.Predastore.Region),
|
||||
Credentials: credentials.NewStaticCredentials(cfg.Predastore.AccessKey, cfg.Predastore.SecretKey, ""),
|
||||
S3ForcePathStyle: aws.Bool(true),
|
||||
HTTPClient: httpClient,
|
||||
}))
|
||||
|
||||
s3Client := s3.New(sess)
|
||||
|
||||
return &VolumeServiceImpl{
|
||||
config: cfg,
|
||||
s3Client: s3Client,
|
||||
}
|
||||
}
|
||||
|
||||
// DescribeVolumes lists EBS volumes by reading config.json files from S3
|
||||
func (s *VolumeServiceImpl) DescribeVolumes(input *ec2.DescribeVolumesInput) (*ec2.DescribeVolumesOutput, error) {
|
||||
if input == nil {
|
||||
input = &ec2.DescribeVolumesInput{}
|
||||
}
|
||||
|
||||
slog.Info("Describing volumes", "volumeIds", input.VolumeIds)
|
||||
|
||||
var volumes []*ec2.Volume
|
||||
|
||||
// Fast path: if specific volume IDs are requested, fetch them directly
|
||||
if len(input.VolumeIds) > 0 {
|
||||
volumes = s.fetchVolumesByIDs(input.VolumeIds)
|
||||
slog.Info("DescribeVolumes completed", "count", len(volumes))
|
||||
return &ec2.DescribeVolumesOutput{Volumes: volumes}, nil
|
||||
}
|
||||
|
||||
// Slow path: list all volumes (no filter provided)
|
||||
result, err := s.s3Client.ListObjects(&s3.ListObjectsInput{
|
||||
Bucket: aws.String(s.config.Predastore.Bucket),
|
||||
Delimiter: aws.String("/"),
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
slog.Error("Failed to list S3 objects", "err", err)
|
||||
return nil, errors.New(awserrors.ErrorServerInternal)
|
||||
}
|
||||
|
||||
// Iterate over CommonPrefixes to find vol-* directories
|
||||
for _, prefix := range result.CommonPrefixes {
|
||||
if prefix.Prefix == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
prefixStr := *prefix.Prefix
|
||||
slog.Debug("Processing S3 prefix", "prefix", prefixStr)
|
||||
|
||||
// Only check prefixes that match pattern: vol-<id>/
|
||||
if !strings.HasPrefix(prefixStr, "vol-") {
|
||||
continue
|
||||
}
|
||||
|
||||
// Extract volume ID from prefix (remove trailing slash)
|
||||
volumeID := strings.TrimSuffix(prefixStr, "/")
|
||||
|
||||
vol, err := s.getVolumeByID(volumeID)
|
||||
if err != nil {
|
||||
slog.Debug("Failed to get volume", "volumeId", volumeID, "err", err)
|
||||
continue
|
||||
}
|
||||
|
||||
volumes = append(volumes, vol)
|
||||
}
|
||||
|
||||
slog.Info("DescribeVolumes completed", "count", len(volumes))
|
||||
|
||||
return &ec2.DescribeVolumesOutput{
|
||||
Volumes: volumes,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// fetchVolumesByIDs fetches multiple volumes in parallel by their IDs
|
||||
func (s *VolumeServiceImpl) fetchVolumesByIDs(volumeIDs []*string) []*ec2.Volume {
|
||||
var (
|
||||
volumes []*ec2.Volume
|
||||
mu sync.Mutex
|
||||
wg sync.WaitGroup
|
||||
)
|
||||
|
||||
for _, volumeID := range volumeIDs {
|
||||
if volumeID == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
wg.Add(1)
|
||||
go func(volID string) {
|
||||
defer wg.Done()
|
||||
|
||||
vol, err := s.getVolumeByID(volID)
|
||||
if err != nil {
|
||||
slog.Debug("Volume not found", "volumeId", volID, "err", err)
|
||||
return
|
||||
}
|
||||
|
||||
mu.Lock()
|
||||
volumes = append(volumes, vol)
|
||||
mu.Unlock()
|
||||
}(*volumeID)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
return volumes
|
||||
}
|
||||
|
||||
// getVolumeByID fetches a single volume's config from S3 and builds an EC2 Volume
|
||||
func (s *VolumeServiceImpl) getVolumeByID(volumeID string) (*ec2.Volume, error) {
|
||||
configKey := volumeID + "/config.json"
|
||||
|
||||
getResult, err := s.s3Client.GetObject(&s3.GetObjectInput{
|
||||
Bucket: aws.String(s.config.Predastore.Bucket),
|
||||
Key: aws.String(configKey),
|
||||
})
|
||||
if err != nil {
|
||||
if aerr, ok := err.(awserr.Error); ok && (aerr.Code() == s3.ErrCodeNoSuchKey || aerr.Code() == "NotFound") {
|
||||
slog.Debug("Config file not found", "key", configKey)
|
||||
return nil, errors.New("volume not found")
|
||||
}
|
||||
slog.Debug("Failed to get config file", "key", configKey, "err", err)
|
||||
return nil, err
|
||||
}
|
||||
defer getResult.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(getResult.Body)
|
||||
if err != nil {
|
||||
slog.Debug("Failed to read config body", "key", configKey, "err", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var vbConfig struct {
|
||||
VolumeConfig viperblock.VolumeConfig `json:"VolumeConfig"`
|
||||
}
|
||||
if err := json.Unmarshal(body, &vbConfig); err != nil {
|
||||
slog.Debug("Failed to unmarshal config", "key", configKey, "err", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
volMeta := vbConfig.VolumeConfig.VolumeMetadata
|
||||
|
||||
if volMeta.VolumeID == "" {
|
||||
slog.Debug("Volume ID is empty in config", "key", configKey)
|
||||
return nil, errors.New("volume ID is empty")
|
||||
}
|
||||
|
||||
state := volMeta.State
|
||||
if state == "" {
|
||||
state = "available"
|
||||
}
|
||||
volumeType := volMeta.VolumeType
|
||||
if volumeType == "" {
|
||||
volumeType = "gp3"
|
||||
}
|
||||
|
||||
volume := &ec2.Volume{
|
||||
VolumeId: aws.String(volMeta.VolumeID),
|
||||
Size: aws.Int64(int64(volMeta.SizeGiB)),
|
||||
State: aws.String(state),
|
||||
AvailabilityZone: aws.String(volMeta.AvailabilityZone),
|
||||
CreateTime: aws.Time(volMeta.CreatedAt),
|
||||
VolumeType: aws.String(volumeType),
|
||||
Encrypted: aws.Bool(volMeta.IsEncrypted),
|
||||
}
|
||||
|
||||
if volMeta.IOPS > 0 {
|
||||
volume.Iops = aws.Int64(int64(volMeta.IOPS))
|
||||
}
|
||||
|
||||
if volMeta.SnapshotID != "" {
|
||||
volume.SnapshotId = aws.String(volMeta.SnapshotID)
|
||||
}
|
||||
|
||||
if volMeta.AttachedInstance != "" {
|
||||
attachState := "attached"
|
||||
if volMeta.State != "in-use" {
|
||||
attachState = "detached"
|
||||
}
|
||||
volume.Attachments = []*ec2.VolumeAttachment{
|
||||
{
|
||||
VolumeId: aws.String(volMeta.VolumeID),
|
||||
InstanceId: aws.String(volMeta.AttachedInstance),
|
||||
Device: aws.String(volMeta.DeviceName),
|
||||
State: aws.String(attachState),
|
||||
DeleteOnTermination: aws.Bool(true),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if len(volMeta.Tags) > 0 {
|
||||
tags := make([]*ec2.Tag, 0, len(volMeta.Tags))
|
||||
for key, value := range volMeta.Tags {
|
||||
tags = append(tags, &ec2.Tag{
|
||||
Key: aws.String(key),
|
||||
Value: aws.String(value),
|
||||
})
|
||||
}
|
||||
volume.Tags = tags
|
||||
}
|
||||
|
||||
return volume, nil
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package handlers_ec2_volume
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
"github.com/mulgadc/hive/hive/utils"
|
||||
"github.com/nats-io/nats.go"
|
||||
)
|
||||
|
||||
// NATSVolumeService handles volume operations via NATS messaging
|
||||
type NATSVolumeService struct {
|
||||
natsConn *nats.Conn
|
||||
}
|
||||
|
||||
// NewNATSVolumeService creates a new NATS-based volume service
|
||||
func NewNATSVolumeService(conn *nats.Conn) VolumeService {
|
||||
return &NATSVolumeService{natsConn: conn}
|
||||
}
|
||||
|
||||
// DescribeVolumes sends a request via NATS and waits for response
|
||||
func (s *NATSVolumeService) DescribeVolumes(input *ec2.DescribeVolumesInput) (*ec2.DescribeVolumesOutput, error) {
|
||||
// Marshal input to JSON
|
||||
jsonData, err := json.Marshal(input)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to marshal input: %w", err)
|
||||
}
|
||||
|
||||
// Send NATS request with 30 second timeout
|
||||
msg, err := s.natsConn.Request("ec2.DescribeVolumes", jsonData, 30*time.Second)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("NATS request failed: %w", err)
|
||||
}
|
||||
|
||||
// Validate error response
|
||||
responseError, err := utils.ValidateErrorPayload(msg.Data)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("daemon returned error: %s", *responseError.Code)
|
||||
}
|
||||
|
||||
// Unmarshal successful response
|
||||
var output ec2.DescribeVolumesOutput
|
||||
err = json.Unmarshal(msg.Data, &output)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal response: %w", err)
|
||||
}
|
||||
|
||||
return &output, nil
|
||||
}
|
||||
@@ -79,6 +79,7 @@ func launchService(config *Config) (err error) {
|
||||
Debug: config.Debug,
|
||||
LogFile: config.LogFile,
|
||||
JetStream: config.JetStream,
|
||||
StoreDir: config.DataDir,
|
||||
}
|
||||
|
||||
// Set defaults if not provided
|
||||
|
||||
@@ -36,7 +36,7 @@ fi
|
||||
|
||||
|
||||
if [ -d "/mnt/ramdisk" ]; then
|
||||
|
||||
|
||||
# Check if /mnt/ramdisk is mounted, if not mount it as tmpfs
|
||||
if ! mountpoint -q /mnt/ramdisk; then
|
||||
echo "💾 Mounting /mnt/ramdisk as tmpfs"
|
||||
@@ -111,12 +111,16 @@ check_service() {
|
||||
done
|
||||
|
||||
echo " ⚠️ $name may not be responding on port $port (continuing anyway)"
|
||||
|
||||
|
||||
}
|
||||
|
||||
# Pre-flight, compile latest
|
||||
echo "1️✈️ Pre-flight, compiling latest..."
|
||||
make build
|
||||
if [ "$HIVE_SKIP_BUILD" != "true" ]; then
|
||||
echo "1️✈️ Pre-flight, compiling latest..."
|
||||
make build
|
||||
else
|
||||
echo "1️✈️ Skipping build (HIVE_SKIP_BUILD=true)"
|
||||
fi
|
||||
|
||||
# 1️⃣ Start NATS server
|
||||
echo ""
|
||||
@@ -268,4 +272,4 @@ echo ""
|
||||
|
||||
# This will only be reached if air/daemon exits normally
|
||||
#echo ""
|
||||
#echo "🛑 Hive development environment stopped"
|
||||
#echo "🛑 Hive development environment stopped"
|
||||
|
||||
Regular → Executable
@@ -0,0 +1,62 @@
|
||||
FROM ubuntu:24.04
|
||||
|
||||
# Avoid prompts
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
# Install make first so we can use the Makefile
|
||||
RUN apt-get update && apt-get install -y make sudo && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Set up working directory for Makefile targets
|
||||
WORKDIR /tmp/hive-setup
|
||||
|
||||
# Copy only the Makefile for dependency installation
|
||||
COPY hive/Makefile .
|
||||
|
||||
# Install system dependencies in separate layers for better caching
|
||||
RUN make install-system
|
||||
RUN make install-go
|
||||
RUN make install-aws
|
||||
|
||||
# Ensure Go is in the PATH
|
||||
ENV PATH="/usr/local/go/bin:/root/go/bin:${PATH}"
|
||||
|
||||
# Set up working directory structure to match development layout
|
||||
WORKDIR /root/Development/mulga
|
||||
|
||||
# 1. Pre-copy dependency manifests for cache layer
|
||||
COPY hive/go.mod hive/go.sum ./hive/
|
||||
COPY viperblock/go.mod viperblock/go.sum ./viperblock/
|
||||
COPY viperblock/nbd/go.mod viperblock/nbd/go.sum ./viperblock/nbd/
|
||||
COPY viperblock/nbd/libguestfs.org/nbdkit/go.mod ./viperblock/nbd/libguestfs.org/nbdkit/
|
||||
COPY predastore/go.mod predastore/go.sum ./predastore/
|
||||
|
||||
# 2. Download dependencies (using cache mount)
|
||||
RUN --mount=type=cache,target=/root/go/pkg/mod \
|
||||
(cd hive && go mod download) && \
|
||||
(cd viperblock && go mod download) && \
|
||||
(cd viperblock/nbd && go mod download) && \
|
||||
(cd viperblock/nbd/libguestfs.org/nbdkit && go mod download) && \
|
||||
(cd predastore && go mod download)
|
||||
|
||||
# 3. Copy the rest of the source code
|
||||
COPY hive/ ./hive/
|
||||
COPY viperblock/ ./viperblock/
|
||||
COPY predastore/ ./predastore/
|
||||
|
||||
# Set working directory to hive
|
||||
WORKDIR /root/Development/mulga/hive
|
||||
|
||||
# Ensure scripts are executable
|
||||
RUN chmod +x ./scripts/*.sh ./tests/e2e/run-e2e.sh
|
||||
|
||||
# 4. Build everything (using cache mounts)
|
||||
RUN --mount=type=cache,target=/root/.cache/go-build \
|
||||
--mount=type=cache,target=/root/go/pkg/mod \
|
||||
./scripts/dev-setup.sh
|
||||
|
||||
# Set environment variables for runtime
|
||||
ENV PATH="/root/Development/mulga/hive/bin:${PATH}"
|
||||
ENV HOME="/root"
|
||||
|
||||
# Run the E2E test runner
|
||||
CMD ["./tests/e2e/run-e2e.sh"]
|
||||
Executable
+262
@@ -0,0 +1,262 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
# Ensure services are stopped on exit and print logs on failure
|
||||
cleanup() {
|
||||
EXIT_CODE=$?
|
||||
if [ $EXIT_CODE -ne 0 ]; then
|
||||
echo "=== Hive Service Log ==="
|
||||
if [ -f ~/hive/logs/hive.log ]; then
|
||||
cat ~/hive/logs/hive.log 2>/dev/null
|
||||
fi
|
||||
fi
|
||||
./scripts/stop-dev.sh
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
# Use Hive profile
|
||||
export AWS_PROFILE=hive
|
||||
|
||||
# Ensure we are in the project root
|
||||
cd "$(dirname "$0")/../.."
|
||||
|
||||
# Phase 1: Environment Setup
|
||||
echo "Phase 1: Environment Setup"
|
||||
|
||||
# Check for KVM support inside the container
|
||||
echo "Checking for KVM support..."
|
||||
if [ -e /dev/kvm ]; then
|
||||
echo "✅ /dev/kvm exists"
|
||||
if [ -w /dev/kvm ]; then
|
||||
echo "✅ /dev/kvm is writable"
|
||||
else
|
||||
echo "❌ /dev/kvm is NOT writable. QEMU will fail."
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo "❌ /dev/kvm does NOT exist. Ensure --privileged and -v /dev/kvm:/dev/kvm are used."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
./bin/hive admin init --region ap-southeast-2 --az ap-southeast-2a --node node1 --nodes 1
|
||||
|
||||
# Start all services
|
||||
# Ensure logs directory exists for start-dev.sh
|
||||
mkdir -p ~/hive/logs
|
||||
mkdir -p /mnt/ramdisk
|
||||
HIVE_SKIP_BUILD=true ./scripts/start-dev.sh
|
||||
|
||||
# Wait for health checks on https://localhost:9999 (AWS Gateway)
|
||||
echo "Waiting for AWS Gateway..."
|
||||
MAX_RETRIES=30
|
||||
COUNT=0
|
||||
# Using -k because of self-signed certs
|
||||
until curl -k -s https://localhost:9999 > /dev/null || [ $COUNT -eq $MAX_RETRIES ]; do
|
||||
echo "Waiting for gateway... ($COUNT/$MAX_RETRIES)"
|
||||
sleep 2
|
||||
COUNT=$((COUNT + 1))
|
||||
done
|
||||
|
||||
if [ $COUNT -eq $MAX_RETRIES ]; then
|
||||
echo "Gateway failed to start"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Define common AWS CLI args
|
||||
AWS_EC2="aws --endpoint-url https://localhost:9999 ec2"
|
||||
|
||||
# Phase 2: Discovery & Metadata
|
||||
echo "Phase 2: Discovery & Metadata"
|
||||
# Verify describe-regions (just ensure it returns at least one region)
|
||||
$AWS_EC2 describe-regions | jq -e '.Regions | length > 0'
|
||||
|
||||
# Discover available instance types from Hive
|
||||
# Hive generates these based on the host CPU (e.g., m7i.micro, m8g.small, etc.)
|
||||
echo "Discovering available instance types..."
|
||||
AVAILABLE_TYPES=$($AWS_EC2 describe-instance-types --query 'InstanceTypes[*].InstanceType' --output text)
|
||||
echo "Available instance types: $AVAILABLE_TYPES"
|
||||
|
||||
# Pick the nano instance type for minimal resource usage in tests
|
||||
INSTANCE_TYPE=$(echo $AVAILABLE_TYPES | tr ' ' '\n' | grep -m1 'nano')
|
||||
if [ -z "$INSTANCE_TYPE" ] || [ "$INSTANCE_TYPE" == "None" ]; then
|
||||
echo "No instance types found!"
|
||||
exit 1
|
||||
fi
|
||||
echo "Selected instance type for test: $INSTANCE_TYPE"
|
||||
|
||||
# Get architecture for the selected instance type
|
||||
ARCH=$($AWS_EC2 describe-instance-types --instance-types "$INSTANCE_TYPE" --query 'InstanceTypes[0].ProcessorInfo.SupportedArchitectures[0]' --output text)
|
||||
echo "Detected architecture for $INSTANCE_TYPE: $ARCH"
|
||||
|
||||
# Verify describe-instance-types (ensure the chosen type is available)
|
||||
$AWS_EC2 describe-instance-types | jq -e ".InstanceTypes[] | select(.InstanceType==\"$INSTANCE_TYPE\")"
|
||||
|
||||
# Phase 3: SSH Key Management
|
||||
echo "Phase 3: SSH Key Management"
|
||||
# Create test-key-1 (create-key-pair) and verify private key material is returned
|
||||
KEY_MATERIAL=$($AWS_EC2 create-key-pair --key-name test-key-1 --query 'KeyMaterial' --output text)
|
||||
if [ -z "$KEY_MATERIAL" ] || [ "$KEY_MATERIAL" == "None" ]; then
|
||||
echo "Failed to create key pair test-key-1"
|
||||
exit 1
|
||||
fi
|
||||
echo "$KEY_MATERIAL" > test-key-1.pem
|
||||
chmod 600 test-key-1.pem
|
||||
|
||||
# Generate a local RSA key and import it as test-key-2 (import-key-pair)
|
||||
ssh-keygen -t rsa -b 2048 -f test-key-2-local -N ""
|
||||
$AWS_EC2 import-key-pair --key-name test-key-2 --public-key-material "fileb://test-key-2-local.pub"
|
||||
|
||||
# Verify both keys are present (describe-key-pairs)
|
||||
$AWS_EC2 describe-key-pairs --query 'KeyPairs[*].KeyName' --output text | grep test-key-1
|
||||
$AWS_EC2 describe-key-pairs --query 'KeyPairs[*].KeyName' --output text | grep test-key-2
|
||||
|
||||
# Delete test-key-2 (delete-key-pair) and verify only one remains
|
||||
$AWS_EC2 delete-key-pair --key-name test-key-2
|
||||
REMAINING_KEYS=$($AWS_EC2 describe-key-pairs --query 'KeyPairs[*].KeyName' --output text)
|
||||
echo "Remaining keys: $REMAINING_KEYS"
|
||||
echo "$REMAINING_KEYS" | grep test-key-1
|
||||
if echo "$REMAINING_KEYS" | grep -q test-key-2; then
|
||||
echo "test-key-2 was not deleted"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Phase 4: Image Management
|
||||
echo "Phase 4: Image Management"
|
||||
# Detect correct image name based on architecture
|
||||
if [ "$ARCH" = "x86_64" ]; then
|
||||
IMAGE_NAME="ubuntu-24.04-x86_64"
|
||||
else
|
||||
IMAGE_NAME="ubuntu-24.04-arm64"
|
||||
fi
|
||||
echo "Using image: $IMAGE_NAME"
|
||||
|
||||
# Import a reliable Ubuntu image and capture the AMI ID from the output
|
||||
echo "Importing image $IMAGE_NAME..."
|
||||
echo "May appear stalled here, just takes a while to import..."
|
||||
IMPORT_LOG=$(./bin/hive admin images import --name "$IMAGE_NAME" --force 2>/dev/null)
|
||||
AMI_ID=$(echo "$IMPORT_LOG" | grep -o 'ami-[a-z0-9]\+')
|
||||
|
||||
if [ -z "$AMI_ID" ]; then
|
||||
echo "Failed to capture AMI ID from import command"
|
||||
exit 1
|
||||
fi
|
||||
echo "Captured AMI ID: $AMI_ID"
|
||||
|
||||
# Verify the AMI exists using its ID (describe-images)
|
||||
echo "Verifying AMI availability..."
|
||||
$AWS_EC2 describe-images --image-ids "$AMI_ID" | jq -e ".Images[0] | select(.ImageId==\"$AMI_ID\")"
|
||||
|
||||
# Phase 5: Instance Lifecycle
|
||||
echo "Phase 5: Instance Lifecycle"
|
||||
# Launch a VM (run-instances)
|
||||
# INSTANCE_ID=$($AWS_EC2 run-instances --image-id "$AMI_ID" --instance-type "$INSTANCE_TYPE" --key-name test-key-1 --query 'Instances[0].InstanceId' --output text)
|
||||
# if [ -z "$INSTANCE_ID" ] || [ "$INSTANCE_ID" == "None" ]; then
|
||||
# echo "Failed to launch instance"
|
||||
# exit 1
|
||||
# fi
|
||||
# echo "Launched Instance ID: $INSTANCE_ID"
|
||||
|
||||
# Poll until state is running (describe-instances)
|
||||
# echo "Polling for instance running state..."
|
||||
# COUNT=0
|
||||
# STATE="unknown"
|
||||
# while [ $COUNT -lt 60 ]; do
|
||||
# # Capture full output to check if instance even exists in the response
|
||||
# DESCRIBE_OUTPUT=$($AWS_EC2 describe-instances --instance-ids "$INSTANCE_ID") || {
|
||||
# echo "⚠️ Gateway request failed, retrying... ($COUNT/60)"
|
||||
# sleep 5
|
||||
# COUNT=$((COUNT + 1))
|
||||
# continue
|
||||
# }
|
||||
|
||||
# if [ -z "$DESCRIBE_OUTPUT" ]; then
|
||||
# echo "⚠️ Gateway returned empty response, retrying..."
|
||||
# sleep 5
|
||||
# COUNT=$((COUNT + 1))
|
||||
# continue
|
||||
# fi
|
||||
|
||||
# # Extract state using jq
|
||||
# STATE=$(echo "$DESCRIBE_OUTPUT" | jq -r '.Reservations[0].Instances[0].State.Name // "not-found"')
|
||||
|
||||
# echo "Instance state: $STATE"
|
||||
# if [ "$STATE" == "running" ]; then
|
||||
# break
|
||||
# fi
|
||||
|
||||
# if [ "$STATE" == "terminated" ]; then
|
||||
# echo "❌ Instance terminated unexpectedly!"
|
||||
# exit 1
|
||||
# fi
|
||||
|
||||
# sleep 5
|
||||
# COUNT=$((COUNT + 1))
|
||||
# done
|
||||
|
||||
# if [ "$STATE" != "running" ]; then
|
||||
# echo "Instance failed to reach running state"
|
||||
# exit 1
|
||||
# fi
|
||||
|
||||
# # Verify root volume attached to the instance (describe-volumes)
|
||||
# VOLUME_ID=$($AWS_EC2 describe-volumes --query 'Volumes[0].VolumeId' --output text)
|
||||
# if [ -z "$VOLUME_ID" ] || [ "$VOLUME_ID" == "None" ]; then
|
||||
# echo "Failed to find volume for instance $INSTANCE_ID"
|
||||
# exit 1
|
||||
# fi
|
||||
# echo "Volume ID: $VOLUME_ID"
|
||||
|
||||
# # Stop instance (stop-instances) and verify transition to stopped (describe-instances)
|
||||
# echo "Stopping instance..."
|
||||
# $AWS_EC2 stop-instances --instance-ids "$INSTANCE_ID"
|
||||
# COUNT=0
|
||||
# while [ $COUNT -lt 30 ]; do
|
||||
# STATE=$($AWS_EC2 describe-instances --instance-ids "$INSTANCE_ID" --query 'Reservations[0].Instances[0].State.Name' --output text)
|
||||
# echo "Instance state: $STATE"
|
||||
# if [ "$STATE" == "stopped" ]; then
|
||||
# break
|
||||
# fi
|
||||
# sleep 5
|
||||
# COUNT=$((COUNT + 1))
|
||||
# done
|
||||
|
||||
# if [ "$STATE" != "stopped" ]; then
|
||||
# echo "Instance failed to reach stopped state"
|
||||
# exit 1
|
||||
# fi
|
||||
|
||||
# # Start instance (start-instances) and verify transition back to running (describe-instances)
|
||||
# echo "Starting instance..."
|
||||
# $AWS_EC2 start-instances --instance-ids "$INSTANCE_ID"
|
||||
# COUNT=0
|
||||
# while [ $COUNT -lt 30 ]; do
|
||||
# STATE=$($AWS_EC2 describe-instances --instance-ids "$INSTANCE_ID" --query 'Reservations[0].Instances[0].State.Name' --output text)
|
||||
# echo "Instance state: $STATE"
|
||||
# if [ "$STATE" == "running" ]; then
|
||||
# break
|
||||
# fi
|
||||
# sleep 5
|
||||
# COUNT=$((COUNT + 1))
|
||||
# done
|
||||
|
||||
# if [ "$STATE" != "running" ]; then
|
||||
# echo "Instance failed to reach running state after restart"
|
||||
# exit 1
|
||||
# fi
|
||||
|
||||
# # Terminate instance (terminate-instances) and verify termination (describe-instances)
|
||||
# echo "Terminating instance..."
|
||||
# $AWS_EC2 terminate-instances --instance-ids "$INSTANCE_ID"
|
||||
# COUNT=0
|
||||
# while [ $COUNT -lt 30 ]; do
|
||||
# STATE=$($AWS_EC2 describe-instances --instance-ids "$INSTANCE_ID" --query 'Reservations[0].Instances[0].State.Name' --output text)
|
||||
# echo "Instance state: $STATE"
|
||||
# if [ "$STATE" == "terminated" ] || [ "$STATE" == "None" ]; then
|
||||
# break
|
||||
# fi
|
||||
# sleep 5
|
||||
# COUNT=$((COUNT + 1))
|
||||
# done
|
||||
|
||||
echo "E2E Test Completed Successfully"
|
||||
exit 0
|
||||
Reference in New Issue
Block a user