From 060b2cd8828b9796b1ef890963d6c422e00e4959 Mon Sep 17 00:00:00 2001 From: Alex Lovell-Troy Date: Wed, 19 Mar 2025 12:14:40 +0100 Subject: [PATCH 01/12] feat(quackstore): implement QuackStore for persistent storage with tests - Added QuackStore implementation for managing groups, instances, and cluster defaults using Quack for persistence. - Introduced unit tests for QuackStore to ensure functionality and data integrity. - Updated go.mod to include new dependencies and replaced the quack module path. - Enhanced CloudConfigFile JSON marshaling and unmarshaling for better encoding handling. test(memstore): add unit tests for MemStore implementation - Introduced a new test file for MemStore to validate its functionality. - Integrated standard test suite from cistore testing package to ensure comprehensive testing. feat(cloud-init-server): add support for multiple storage backends - Introduced a new command-line flag for selecting the storage backend (mem or quack). - Default storage backend set to memstore, with a new option for quackstore using a specified database path. - Updated the datastore initialization logic to handle both memstore and quackstore, including error handling for quackstore initialization. fix: correct UnmarshalJSON and add MarshalJSON for CloudConfigFile chore: clarify comments in custom marshal/unmarshal funcs test: add unit tests for CloudConfigFile JSON marshal/unmarshal fix(tests): use base64-encoded data for base64 marshal test refactor: Use an alternate Unmarshal implementation chore: add base64 encoding import for future functionality refactor: simplify UnmarshalJSON and add MarshalJSON for CloudConfigFile - Removed base64 encoding handling from UnmarshalJSON for clarity. - Added MarshalJSON implementation to ensure proper JSON serialization of CloudConfigFile. feat(quackstore): implement unique instance ID generation - Added functionality to generate a unique instance ID in the format "i-XXXXXX" using a random 6-digit hexadecimal string. - Updated the comment to clarify the instance ID generation process. fix: correct UnmarshalJSON and add MarshalJSON for CloudConfigFile chore: clarify comments in custom marshal/unmarshal funcs feat(quackstore): implement QuackStore for persistent storage with tests - Added QuackStore implementation for managing groups, instances, and cluster defaults using Quack for persistence. - Introduced unit tests for QuackStore to ensure functionality and data integrity. - Updated go.mod to include new dependencies and replaced the quack module path. - Enhanced CloudConfigFile JSON marshaling and unmarshaling for better encoding handling. fix: correct UnmarshalJSON and add MarshalJSON for CloudConfigFile Update dependencies and remove "replace" statement fro quack/quack feat: add versions property to API documentation and update build settings feat: update Dockerfile for Red Hat base image and add build script for GoReleaser feat: add GoReleaser configuration for Darwin builds and update build-in-container script for Docker support fix: update build-in-container script to use configurable Docker socket path and improve router middleware handling Signed-off-by: Alex Lovell-Troy Signed-off-by: Alex Lovell-Troy Signed-off-by: Alex Lovell-Troy --- .goreleaser-darwin.yaml | 42 ++++++ .goreleaser.yaml | 15 ++- Dockerfile | 44 ++++--- build-in-container.sh | 82 ++++++++++++ cmd/cloud-init-server/main.go | 124 +++++++++--------- docs/docs.go | 6 + docs/swagger.json | 6 + docs/swagger.yaml | 4 + go.mod | 17 +-- go.sum | 32 +---- internal/duckdbstore/ciDuckDBStore.go | 178 ++++++++++++++++++++++++++ internal/memstore/ciMemStore_test.go | 20 +++ internal/quackstore/quackstore.go | 9 +- pkg/cistore/models.go | 135 +++---------------- pkg/cistore/models_test.go | 66 ---------- 15 files changed, 466 insertions(+), 314 deletions(-) create mode 100644 .goreleaser-darwin.yaml create mode 100755 build-in-container.sh create mode 100644 internal/duckdbstore/ciDuckDBStore.go create mode 100644 internal/memstore/ciMemStore_test.go diff --git a/.goreleaser-darwin.yaml b/.goreleaser-darwin.yaml new file mode 100644 index 000000000..0a0eb975d --- /dev/null +++ b/.goreleaser-darwin.yaml @@ -0,0 +1,42 @@ +version: 2 +project_name: cloud-init +before: + hooks: + # You may remove this if you don't use go modules. + - go mod tidy + - go install github.com/swaggo/swag/cmd/swag@latest + - swag init -g cmd/cloud-init-server/main.go + +builds: + - id: darwin + main: ./cmd/cloud-init-server + binary: cloud-init-server + # export GIT_STATE=$(if git diff-index --quiet HEAD --; then echo 'clean'; else echo 'dirty'; fi) + # export BUILD_HOST=$(hostname) + # export GO_VERSION=$(go version | awk '{print $3}') + # export BUILD_USER=$(whoami) + ldflags: + - "-X 'main.GitCommit={{.Commit}}' \ + -X 'main.BuildTime={{.Timestamp}}' \ + -X 'main.Version={{.Version}}' \ + -X 'main.GitBranch={{.Branch}}' \ + -X 'main.GitTag={{.Tag}}' \ + -X 'main.GitState={{ .Env.GIT_STATE }}' \ + -X 'main.BuildHost={{ .Env.BUILD_HOST }}' \ + -X 'main.GoVersion={{ .Env.GO_VERSION }}' \ + -X 'main.BuildUser={{ .Env.BUILD_USER }}'" + goos: + - darwin + goarch: + - amd64 + - arm64 + goamd64: + - v3 + - v1 + env: + - CGO_ENABLED=1 + +# The lines beneath this are called `modelines`. See `:help modeline` +# Feel free to remove those if you don't want/use them. +# yaml-language-server: $schema=https://raw.githubusercontent.com/goreleaser/goreleaser/v2.11.2/www/docs/static/schema.json +# vim: set ts=2 sw=2 tw=0 fo=cnqoj \ No newline at end of file diff --git a/.goreleaser.yaml b/.goreleaser.yaml index 7add4f407..d6398823c 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -1,14 +1,14 @@ - -version: 2.4 +version: 2 project_name: cloud-init before: hooks: # You may remove this if you don't use go modules. - go mod tidy + - go install github.com/swaggo/swag/cmd/swag@latest - swag init -g cmd/cloud-init-server/main.go builds: - - id: cloud-init + - id: linux main: ./cmd/cloud-init-server binary: cloud-init-server # export GIT_STATE=$(if git diff-index --quiet HEAD --; then echo 'clean'; else echo 'dirty'; fi) @@ -27,14 +27,17 @@ builds: -X 'main.BuildUser={{ .Env.BUILD_USER }}'" goos: - linux - - darwin goarch: - amd64 - arm64 goamd64: - v3 + - v1 env: - - CGO_ENABLED=0 + - CGO_ENABLED=1 + - CC={{ if eq .Arch "arm64" }}aarch64-linux-gnu-gcc{{ else }}gcc{{ end }} + - CXX={{ if eq .Arch "arm64" }}aarch64-linux-gnu-g++{{ else }}g++{{ end }} + dockers: - image_templates: @@ -125,5 +128,5 @@ changelog: # The lines beneath this are called `modelines`. See `:help modeline` # Feel free to remove those if you don't want/use them. -# yaml-language-server: $schema=https://goreleaser.com/static/schema.json +# yaml-language-server: $schema=https://raw.githubusercontent.com/goreleaser/goreleaser/v2.11.2/www/docs/static/schema.json # vim: set ts=2 sw=2 tw=0 fo=cnqoj diff --git a/Dockerfile b/Dockerfile index dee27b0ce..e51f99fa0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,30 +1,40 @@ +FROM rockylinux:9 AS builder +RUN dnf update -y && \ + dnf install -y epel-release && \ + dnf install -y tini wireguard-tools && \ + dnf clean all -FROM chainguard/wolfi-base:latest +FROM registry.access.redhat.com/ubi9/ubi-minimal -RUN apk add --no-cache tini +# Copy the wg binary from the builder image +COPY --from=builder /usr/bin/wg /usr/bin/wg +COPY --from=builder /usr/bin/wg-quick /usr/bin/wg-quick +COPY --from=builder /usr/bin/tini /usr/bin/tini -# Include curl in the final image. -RUN set -ex \ - && apk update \ - && apk add --no-cache curl tini wireguard-tools iputils iproute2 \ - && rm -rf /var/cache/apk/* \ - && rm -rf /tmp/* +# Install dnf and certificates +RUN microdnf install -y dnf ca-certificates && dnf clean all -STOPSIGNAL SIGTERM -# Get the boot-script-service from the builder stage. -COPY cloud-init-server /usr/local/bin/ +RUN microdnf install -y \ + iproute \ + iputils \ + libstdc++ \ + libgcc && \ + microdnf clean all +# Copy binary from builder +COPY cloud-init-server /usr/local/bin/cloud-init-server + +# Configuration via environment variables ENV TOKEN_URL="http://opaal:3333/token" ENV SMD_URL="http://smd:27779" ENV LISTEN="0.0.0.0:27777" - -# nobody 65534:65534 +# Set non-root user +# UID 65534 is typically "nobody" on Red Hat systems USER 65534:65534 -# Set up the command to start the service. -CMD /usr/local/bin/cloud-init-server - -ENTRYPOINT ["/sbin/tini", "--"] +# Tini for proper signal forwarding +ENTRYPOINT ["/usr/bin/tini", "--"] +CMD ["/usr/local/bin/cloud-init-server"] diff --git a/build-in-container.sh b/build-in-container.sh new file mode 100755 index 000000000..73c3586fb --- /dev/null +++ b/build-in-container.sh @@ -0,0 +1,82 @@ +#!/bin/bash + +# This script uses the latest Ubuntu 24.04 container to build the project with GoReleaser. It emulates the GitHub Actions environment as closely as possible. +# Before submitting a PR for release/build. please run this script to ensure your PR will pass the build. + +# Name of the container +CONTAINER_NAME="goreleaser-build" + +# Directory where built binaries will be available +OUTPUT_DIR="$(pwd)/dist" + +export GIT_STATE=$(if git diff-index --quiet HEAD --; then echo 'clean'; else echo 'dirty'; fi) +export BUILD_HOST=$(hostname) +export GO_VERSION=$(go version | awk '{print $3}') +export BUILD_USER=$(whoami) + +# Start a new disposable Ubuntu 24.04 container with the current directory mounted +${CONTAINER_CMD:-docker} run --rm -it \ + --name "$CONTAINER_NAME" \ + -v "$(pwd)":/workspace \ + -v ${CONTAINER_SOCK:-/var/run/docker.sock}:/var/run/docker.sock \ + -w /workspace \ + ubuntu:24.04 bash -c " + + # Suppress timezone prompts + export DEBIAN_FRONTEND=noninteractive + export TZ=UTC + + # Update package lists and install dependencies + apt update && apt install -y git gcc g++ make \ + ca-certificates curl gnupg \ + gcc-aarch64-linux-gnu binutils-aarch64-linux-gnu \ + libc6-dev-arm64-cross software-properties-common \ + clang-tools libstdc++-13-dev-arm64-cross + + install -m 0755 -d /etc/apt/keyrings + + curl -fsSL https://download.docker.com/linux/ubuntu/gpg | \ + gpg --dearmor -o /etc/apt/keyrings/docker.gpg + + echo \ + \"deb [arch=\$(dpkg --print-architecture) \ + signed-by=/etc/apt/keyrings/docker.gpg] \ + https://download.docker.com/linux/ubuntu \$(lsb_release -cs) stable\" | \ + tee /etc/apt/sources.list.d/docker.list > /dev/null + + apt update && apt install -y \ + docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin + + # Install Go (match GitHub runner version) + curl -fsSL https://golang.org/dl/go1.21.5.linux-amd64.tar.gz | tar -C /usr/local -xz + export PATH=\$PATH:/usr/local/go/bin + go version # Verify Go installation + + # Set GOPATH and update PATH to include Go binaries + export GOPATH=\$(go env GOPATH) + export PATH=\$PATH:\$GOPATH/bin + echo \"GOPATH: \$GOPATH\" && echo \"PATH: \$PATH\" + + # Install Goreleaser + curl -sL https://github.com/goreleaser/goreleaser/releases/latest/download/goreleaser_Linux_x86_64.tar.gz | tar -xz -C /usr/local/bin + goreleaser --version # Verify Goreleaser installation + + # Setup Docker buildx for multi-platform builds + docker buildx create --use --name goreleaser-builder || true + docker buildx inspect --bootstrap + + # Set Build Environment Variables + export GIT_STATE="$GIT_STATE" + export BUILD_HOST="$BUILD_HOST" + export BUILD_USER="$BUILD_USER" + export GO_VERSION=$(go version | awk '{print $3}') + + # Convince git that our directory is safe + git config --global --add safe.directory /workspace + + # Run Goreleaser + goreleaser release --snapshot --clean --skip archive,publish +" + +# Notify user of success +echo "✅ Build complete! Check the output in: $OUTPUT_DIR" diff --git a/cmd/cloud-init-server/main.go b/cmd/cloud-init-server/main.go index c6a194746..9cb98f67e 100644 --- a/cmd/cloud-init-server/main.go +++ b/cmd/cloud-init-server/main.go @@ -8,7 +8,6 @@ package main import ( "fmt" - "net" "net/http" "os" "strings" @@ -16,6 +15,7 @@ import ( "github.com/OpenCHAMI/cloud-init/internal/memstore" openchami_middleware "github.com/OpenCHAMI/cloud-init/internal/middleware" + "github.com/OpenCHAMI/cloud-init/internal/quackstore" "github.com/OpenCHAMI/cloud-init/internal/smdclient" "github.com/OpenCHAMI/cloud-init/pkg/cistore" "github.com/OpenCHAMI/cloud-init/pkg/wgtunnel" @@ -52,6 +52,8 @@ var ( wireguardOnly bool debug bool wireGuardMiddleware func(http.Handler) http.Handler + storageBackend = "mem" // Default to memstore + dbPath = "cloud-init.db" // Default database path for quackstore store cistore.Store ) @@ -93,6 +95,8 @@ func setupFlags(flags *pflag.FlagSet) { flags.StringVar(&wireguardServer, "wireguard-server", getEnv("WIREGUARD_SERVER", ""), "WireGuard server IP address and network (e.g. 100.97.0.1/16)") flags.BoolVar(&wireguardOnly, "wireguard-only", parseBool(getEnv("WIREGUARD_ONLY", "false")), "Only allow access to the cloud-init functions from the WireGuard subnet") flags.BoolVar(&debug, "debug", parseBool(getEnv("DEBUG", "false")), "Enable debug logging") + flags.StringVar(&storageBackend, "storage-backend", getEnv("STORAGE_BACKEND", "mem"), "Storage backend to use (mem or quack)") + flags.StringVar(&dbPath, "db-path", getEnv("DB_PATH", "cloud-init.db"), "Path to the database file for quackstore backend") } // bindViperToFlags binds each flag to Viper so environment variables work seamlessly. @@ -114,6 +118,8 @@ func bindViperToFlags() { viper.BindEnv("wireguard_server") viper.BindEnv("wireguard_only") viper.BindEnv("debug") + viper.BindEnv("storage_backend") + viper.BindEnv("db_path") } // startServer is where we run our main program logic @@ -137,6 +143,8 @@ func startServer() error { Str("wireguard-server", wireguardServer). Bool("wireguard-only", wireguardOnly). Bool("debug", debug). + Str("storage-backend", storageBackend). + Str("db-path", dbPath). Msg("Resolved configuration") } @@ -145,6 +153,20 @@ func startServer() error { zerolog.ErrorStackMarshaler = pkgerrors.MarshalStack log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}) + // Initialize storage backend + var err error + switch storageBackend { + case "mem": + store = memstore.NewMemStore() + case "quack": + store, err = quackstore.NewQuackStore(dbPath) + if err != nil { + return fmt.Errorf("failed to initialize quackstore: %w", err) + } + default: + return fmt.Errorf("unsupported storage backend: %s", storageBackend) + } + // Setup JWKS if provided var keyset *jwtauth.JWTAuth secureRouteEnable := false @@ -164,61 +186,29 @@ func startServer() error { var sm smdclient.SMDClientInterface if os.Getenv("CLOUD_INIT_SMD_SIMULATOR") == "true" { fmt.Printf("\n\n**********\n\n\tCLOUD_INIT_SMD_SIMULATOR is set to true in your environment.\n\n\tUsing the FakeSMDClient\n\n**********\n\n\n") - fakeSMDEnabled = true - fakeSm := smdclient.NewFakeSMDClient(clusterName, 500) - fakeSm.Summary() - sm = fakeSm + sm = smdclient.NewFakeSMDClient(clusterName, 500) } else { - var err error sm, err = smdclient.NewSMDClient(clusterName, smdEndpoint, tokenEndpoint, accessToken, certPath, insecure) if err != nil { - log.Fatal().Err(err) + return fmt.Errorf("failed to create SMD client: %w", err) } } - // Initialize in-memory store - store = memstore.NewMemStore() - store.SetClusterDefaults(cistore.ClusterDefaults{ - ClusterName: clusterName, - Region: region, - AvailabilityZone: availabilityZone, - CloudProvider: cloudProvider, - BaseUrl: baseUrl, - }) - - // Initialize the cloud-init handler - ciHandler := NewCiHandler(store, sm, clusterName) - - // WireGuard - var wgInterfaceManager *wgtunnel.InterfaceManager - if wireguardServer != "" { - log.Info().Msgf("Initializing WireGuard server with %s", wireguardServer) - wgIp, wgNet, err := net.ParseCIDR(wireguardServer) - if err != nil { - log.Fatal().Err(err).Msgf("Failed to parse WireGuard server IP and netmask from %s. Use format '100.97.0.1/16'", wireguardServer) - } - wgInterfaceManager = wgtunnel.NewInterfaceManager("wg0", wgIp, wgNet) - err = wgInterfaceManager.StartServer() - if err != nil { - log.Fatal().Err(err).Msg("Failed to start the WireGuard server") - } + // Create CI handler + handler := &CiHandler{ + sm: sm, + store: store, } - // If wireguard-only is set, only allow access within the WireGuard subnet - if wireguardOnly && wgInterfaceManager != nil { - log.Info().Msg("WireGuard middleware enabled") + // Setup WireGuard middleware if enabled + if wireguardOnly && wireguardServer != "" { wireGuardMiddleware = openchami_middleware.WireGuardMiddlewareWithInterface("wg0", wireguardServer) - } else { - log.Info().Msg("WireGuard middleware disabled") - wireGuardMiddleware = func(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - next.ServeHTTP(w, r) - }) - } } - // Set up router + // Create router router := chi.NewRouter() + + // Add middleware router.Use( middleware.RequestID, middleware.RealIP, @@ -229,25 +219,30 @@ func startServer() error { openchami_logger.OpenCHAMILogger(log.Logger), ) - // Client sub-router - router_client := chi.NewRouter() - initCiClientRouter(router_client, ciHandler, wgInterfaceManager) - router.Mount("/cloud-init", router_client) + // Add WireGuard middleware if enabled + if wireguardOnly && wireGuardMiddleware != nil { + router.Use(wireGuardMiddleware) + } + + // Setup routes + initCiClientRouter(router, handler, nil) + initCiAdminRouter(router, handler) - // Admin sub-router - router_admin := chi.NewRouter() - if secureRouteEnable { - router_admin.Use( + // Add secure routes if JWKS is configured + if secureRouteEnable && keyset != nil { + secureRouter := chi.NewRouter() + secureRouter.Use( jwtauth.Verifier(keyset), openchami_authenticator.AuthenticatorWithRequiredClaims(keyset, []string{"sub", "iss", "aud"}), ) + + // Add secure routes here if needed + router.Mount("/secure", secureRouter) } - initCiAdminRouter(router_admin, ciHandler) - router.Mount("/cloud-init/admin", router_admin) - log.Info().Msgf("Starting server on %s", ciEndpoint) - log.Fatal().Err(http.ListenAndServe(ciEndpoint, router)).Msg("Server closed") - return nil + // Start server + fmt.Printf("Starting cloud-init server on %s\n", ciEndpoint) + return http.ListenAndServe(ciEndpoint, router) } // Utility to read optional environment variables @@ -267,10 +262,17 @@ func initCiClientRouter(router chi.Router, handler *CiHandler, wgInterfaceManage // Add cloud-init endpoints to router router.Get("/openapi.json", DocsHandler) router.Get("/version", VersionHandler) - router.With(wireGuardMiddleware).Get("/user-data", UserDataHandler) - router.With(wireGuardMiddleware).Get("/meta-data", MetaDataHandler(handler.sm, handler.store)) - router.With(wireGuardMiddleware).Get("/vendor-data", VendorDataHandler(handler.sm, handler.store)) - router.With(wireGuardMiddleware).Get("/{group}.yaml", GroupUserDataHandler(handler.sm, handler.store)) + if wireGuardMiddleware != nil { + router.With(wireGuardMiddleware).Get("/user-data", UserDataHandler) + router.With(wireGuardMiddleware).Get("/meta-data", MetaDataHandler(handler.sm, handler.store)) + router.With(wireGuardMiddleware).Get("/vendor-data", VendorDataHandler(handler.sm, handler.store)) + router.With(wireGuardMiddleware).Get("/{group}.yaml", GroupUserDataHandler(handler.sm, handler.store)) + } else { + router.Get("/user-data", UserDataHandler) + router.Get("/meta-data", MetaDataHandler(handler.sm, handler.store)) + router.Get("/vendor-data", VendorDataHandler(handler.sm, handler.store)) + router.Get("/{group}.yaml", GroupUserDataHandler(handler.sm, handler.store)) + } router.Post("/phone-home/{id}", PhoneHomeHandler(wgInterfaceManager, handler.sm)) router.Post("/wg-init", wgtunnel.AddClientHandler(wgInterfaceManager, handler.sm)) } diff --git a/docs/docs.go b/docs/docs.go index 14ed03d8c..fa047878a 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -718,6 +718,12 @@ const docTemplate = `{ "name": { "type": "string", "example": "compute" + }, + "versions": { + "type": "object", + "additionalProperties": { + "type": "string" + } } } }, diff --git a/docs/swagger.json b/docs/swagger.json index 4f30b1668..8d7188a9a 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -710,6 +710,12 @@ "name": { "type": "string", "example": "compute" + }, + "versions": { + "type": "object", + "additionalProperties": { + "type": "string" + } } } }, diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 169f729ae..f0da644e4 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -59,6 +59,10 @@ definitions: name: example: compute type: string + versions: + additionalProperties: + type: string + type: object type: object cistore.OpenCHAMIInstanceInfo: properties: diff --git a/go.mod b/go.mod index ee7e118aa..b294cded7 100644 --- a/go.mod +++ b/go.mod @@ -7,14 +7,13 @@ require ( github.com/OpenCHAMI/quack v0.0.1 github.com/OpenCHAMI/smd/v2 v2.18.0 github.com/go-chi/chi/v5 v5.2.1 - github.com/lestrrat-go/jwx/v2 v2.1.5 + github.com/lestrrat-go/jwx/v2 v2.1.6 + github.com/marcboeker/go-duckdb v1.8.5 github.com/openchami/chi-middleware/auth v0.0.0-20240812224658-b16b83c70700 github.com/openchami/chi-middleware/log v0.0.0-20240812224658-b16b83c70700 github.com/rs/zerolog v1.34.0 - github.com/spf13/pflag v1.0.6 github.com/stretchr/testify v1.10.0 github.com/swaggo/swag v1.16.4 - gopkg.in/yaml.v3 v3.0.1 ) require ( @@ -34,7 +33,6 @@ require ( github.com/goccy/go-json v0.10.5 // indirect github.com/google/flatbuffers v25.2.10+incompatible // indirect github.com/google/uuid v1.6.0 // indirect - github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/klauspost/compress v1.18.0 // indirect github.com/klauspost/cpuid/v2 v2.2.10 // indirect @@ -44,22 +42,13 @@ require ( github.com/lestrrat-go/iter v1.0.2 // indirect github.com/lestrrat-go/option v1.0.1 // indirect github.com/mailru/easyjson v0.9.0 // indirect - github.com/marcboeker/go-duckdb v1.8.5 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/pelletier/go-toml/v2 v2.2.3 // indirect github.com/pierrec/lz4/v4 v4.1.22 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - github.com/sagikazarmark/locafero v0.7.0 // indirect github.com/segmentio/asm v1.2.0 // 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/subosito/gotenv v1.6.0 // indirect github.com/zeebo/xxh3 v1.0.2 // indirect - go.uber.org/atomic v1.11.0 // indirect - go.uber.org/multierr v1.9.0 // indirect golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 // indirect golang.org/x/mod v0.24.0 // indirect golang.org/x/sync v0.13.0 // indirect @@ -86,8 +75,6 @@ require ( github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/ryanuber/go-glob v1.0.0 // indirect github.com/sirupsen/logrus v1.9.3 // indirect - github.com/spf13/cobra v1.9.1 - github.com/spf13/viper v1.20.1 golang.org/x/crypto v0.37.0 // indirect golang.org/x/net v0.39.0 // indirect golang.org/x/sys v0.32.0 // indirect diff --git a/go.sum b/go.sum index 30af4b3f0..531d10ea7 100644 --- a/go.sum +++ b/go.sum @@ -25,7 +25,6 @@ github.com/apache/thrift v0.21.0/go.mod h1:W1H8aR/QRtYNvrPeFXBtobyRkd0/YVhTc6i07 github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= -github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= @@ -34,8 +33,6 @@ github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 h1:NMZiJj8QnKe1LgsbDayM4UoHwbvw github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40= github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= -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.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= @@ -92,8 +89,6 @@ github.com/hashicorp/hcl v1.0.1-vault-7 h1:ag5OxFVy3QYTFTJODRzTKVZ6xvdfLLCA1cy/Y github.com/hashicorp/hcl v1.0.1-vault-7/go.mod h1:XYhtn6ijBSAj6n4YqAaf7RBPS4I06AItNorpy+MoQNM= github.com/hashicorp/vault/api v1.16.0 h1:nbEYGJiAPGzT9U4oWgaaB0g+Rj8E59QuHKyA5LhwQN4= github.com/hashicorp/vault/api v1.16.0/go.mod h1:KhuUhzOD8lDSk29AtzNjgAu2kxRA9jL9NAbkFlqvkBA= -github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= -github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/klauspost/asmfmt v1.3.2 h1:4Ri7ox3EwapiOjCki+hw14RyKk201CN4rzyCJRFLpK4= @@ -114,8 +109,8 @@ github.com/lestrrat-go/httprc v1.0.6 h1:qgmgIRhpvBqexMJjA/PmwSvhNk679oqD1RbovdCG github.com/lestrrat-go/httprc v1.0.6/go.mod h1:mwwz3JMTPBjHUkkDv/IGJ39aALInZLrhBp0X7KGUZlo= github.com/lestrrat-go/iter v1.0.2 h1:gMXo1q4c2pHmC3dn8LzRhJfP1ceCbgSiT9lUydIzltI= github.com/lestrrat-go/iter v1.0.2/go.mod h1:Momfcq3AnRlRjI5b5O8/G5/BvpzrhoFTZcn06fEOPt4= -github.com/lestrrat-go/jwx/v2 v2.1.5 h1:PQI5gzadLfJ22ckLrejPVX6eGXKM4M4eGi5fW2jjA3o= -github.com/lestrrat-go/jwx/v2 v2.1.5/go.mod h1:Y722kU5r/8mV7fYDifjug0r8FK8mZdw0K0GpJw/l8pU= +github.com/lestrrat-go/jwx/v2 v2.1.6 h1:hxM1gfDILk/l5ylers6BX/Eq1m/pnxe9NBwW6lVfecA= +github.com/lestrrat-go/jwx/v2 v2.1.6/go.mod h1:Y722kU5r/8mV7fYDifjug0r8FK8mZdw0K0GpJw/l8pU= github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU= github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= @@ -141,8 +136,6 @@ github.com/openchami/chi-middleware/auth v0.0.0-20240812224658-b16b83c70700 h1:X github.com/openchami/chi-middleware/auth v0.0.0-20240812224658-b16b83c70700/go.mod h1:kswb9kU5cZAFRAvf1dAUJRWbQyjDEb0qkxW4ncDdEXg= github.com/openchami/chi-middleware/log v0.0.0-20240812224658-b16b83c70700 h1:Gzt5f6RK39CHvY3SJudzBb/RK4tVh/S3CpJ0eQlbNdg= github.com/openchami/chi-middleware/log v0.0.0-20240812224658-b16b83c70700/go.mod h1:UuXvr2loD4MtvZeKr57W0WpBs+gm0KM1kdtcXrE8M6s= -github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= -github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU= github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= @@ -155,45 +148,24 @@ github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUz github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY= github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ= -github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk= github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= -github.com/sagikazarmark/locafero v0.7.0 h1:5MqpDsTGNDhY8sGp0Aowyf0qKsPrhewaLSsFaodPcyo= -github.com/sagikazarmark/locafero v0.7.0/go.mod h1:2za3Cg5rMaTMoG/2Ulr9AwtFaIppKXTRYnozin4aB5k= github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys= github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= -github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= -github.com/spf13/afero v1.12.0 h1:UcOPyRBYczmFn6yvphxkn9ZEOY65cpwGKb5mL36mrqs= -github.com/spf13/afero v1.12.0/go.mod h1:ZTlWwG4/ahT8W7T0WQ5uYmjI9duaLQGy3Q2OAl4sk/4= -github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y= -github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= -github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= -github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= -github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= -github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -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/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= -github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/swaggo/swag v1.16.4 h1:clWJtd9LStiG3VeijiCfOVODP6VpHtKdQy9ELFG3s1A= github.com/swaggo/swag v1.16.4/go.mod h1:VBsHJRsDvfYvqoiMKnsdwhNV9LEMHgEDZcyVYX0sxPg= github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ= github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0= github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA= -go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= -go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= -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= golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 h1:R84qjqJb5nVJMxqWYb3np9L5ZsaDtB+a39EqjV0JSUM= diff --git a/internal/duckdbstore/ciDuckDBStore.go b/internal/duckdbstore/ciDuckDBStore.go new file mode 100644 index 000000000..c7285cae5 --- /dev/null +++ b/internal/duckdbstore/ciDuckDBStore.go @@ -0,0 +1,178 @@ +package duckdbstore + +import ( + "database/sql" + "encoding/json" + "fmt" + "sync" + + "github.com/OpenCHAMI/cloud-init/pkg/cistore" + _ "github.com/marcboeker/go-duckdb" +) + +type DuckDBStore struct { + db *sql.DB + mu sync.RWMutex +} + +func NewDuckDBStore(dsn string) (*DuckDBStore, error) { + db, err := sql.Open("duckdb", dsn) + if err != nil { + return nil, err + } + return &DuckDBStore{db: db}, nil +} + +func (d *DuckDBStore) GetGroups() map[string]cistore.GroupData { + d.mu.RLock() + defer d.mu.RUnlock() + rows, err := d.db.Query("SELECT name, description, data, file, versions FROM groups") + if err != nil { + return nil + } + defer rows.Close() + + groups := make(map[string]cistore.GroupData) + for rows.Next() { + var group cistore.GroupData + var data, file, versions []byte + if err := rows.Scan(&group.Name, &group.Description, &data, &file, &versions); err != nil { + continue + } + json.Unmarshal(data, &group.Data) + group.File.Content = file + json.Unmarshal(versions, &group.Versions) + groups[group.Name] = group + } + return groups +} + +func (d *DuckDBStore) AddGroupData(groupName string, groupData cistore.GroupData) error { + d.mu.Lock() + defer d.mu.Unlock() + data, _ := json.Marshal(groupData.Data) + versions, _ := json.Marshal(groupData.Versions) + _, err := d.db.Exec("INSERT INTO groups (name, description, data, file, versions) VALUES (?, ?, ?, ?, ?)", + groupName, groupData.Description, data, groupData.File.Content, versions) + return err +} + +func (d *DuckDBStore) GetGroupData(groupName string) (cistore.GroupData, error) { + d.mu.RLock() + defer d.mu.RUnlock() + var group cistore.GroupData + var data, file, versions []byte + err := d.db.QueryRow("SELECT name, description, data, file, versions FROM groups WHERE name = ?", groupName). + Scan(&group.Name, &group.Description, &data, &file, &versions) + if err != nil { + return group, err + } + json.Unmarshal(data, &group.Data) + group.File.Content = file + json.Unmarshal(versions, &group.Versions) + return group, nil +} + +func (d *DuckDBStore) UpdateGroupData(groupName string, groupData cistore.GroupData, create bool) error { + d.mu.Lock() + defer d.mu.Unlock() + data, _ := json.Marshal(groupData.Data) + versions, _ := json.Marshal(groupData.Versions) + if create { + _, err := d.db.Exec("INSERT INTO groups (name, description, data, file, versions) VALUES (?, ?, ?, ?, ?)", + groupName, groupData.Description, data, groupData.File.Content, versions) + return err + } + _, err := d.db.Exec("UPDATE groups SET description = ?, data = ?, file = ?, versions = ? WHERE name = ?", + groupData.Description, data, groupData.File.Content, versions, groupName) + return err +} + +func (d *DuckDBStore) RemoveGroupData(groupName string) error { + d.mu.Lock() + defer d.mu.Unlock() + _, err := d.db.Exec("DELETE FROM groups WHERE name = ?", groupName) + return err +} + +func (d *DuckDBStore) GetInstanceInfo(nodeName string) (cistore.OpenCHAMIInstanceInfo, error) { + d.mu.RLock() + defer d.mu.RUnlock() + var instance cistore.OpenCHAMIInstanceInfo + err := d.db.QueryRow("SELECT id, instance_id, local_hostname, hostname, cluster_name, region, availability_zone, cloud_provider, instance_type, cloud_init_base_url, public_keys FROM instances WHERE id = ?", nodeName). + Scan(&instance.ID, &instance.InstanceID, &instance.LocalHostname, &instance.Hostname, &instance.ClusterName, &instance.Region, &instance.AvailabilityZone, &instance.CloudProvider, &instance.InstanceType, &instance.CloudInitBaseURL, &instance.PublicKeys) + return instance, err +} + +func (d *DuckDBStore) SetInstanceInfo(nodeName string, instanceInfo cistore.OpenCHAMIInstanceInfo) error { + d.mu.Lock() + defer d.mu.Unlock() + publicKeys, _ := json.Marshal(instanceInfo.PublicKeys) + _, err := d.db.Exec("INSERT INTO instances (id, instance_id, local_hostname, hostname, cluster_name, region, availability_zone, cloud_provider, instance_type, cloud_init_base_url, public_keys) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT(id) DO UPDATE SET instance_id = ?, local_hostname = ?, hostname = ?, cluster_name = ?, region = ?, availability_zone = ?, cloud_provider = ?, instance_type = ?, cloud_init_base_url = ?, public_keys = ?", + nodeName, instanceInfo.InstanceID, instanceInfo.LocalHostname, instanceInfo.Hostname, instanceInfo.ClusterName, instanceInfo.Region, instanceInfo.AvailabilityZone, instanceInfo.CloudProvider, instanceInfo.InstanceType, instanceInfo.CloudInitBaseURL, publicKeys, + instanceInfo.InstanceID, instanceInfo.LocalHostname, instanceInfo.Hostname, instanceInfo.ClusterName, instanceInfo.Region, instanceInfo.AvailabilityZone, instanceInfo.CloudProvider, instanceInfo.InstanceType, instanceInfo.CloudInitBaseURL, publicKeys) + return err +} + +func (d *DuckDBStore) DeleteInstanceInfo(nodeName string) error { + d.mu.Lock() + defer d.mu.Unlock() + _, err := d.db.Exec("DELETE FROM instances WHERE id = ?", nodeName) + return err +} + +func (d *DuckDBStore) GetClusterDefaults() (cistore.ClusterDefaults, error) { + d.mu.RLock() + defer d.mu.RUnlock() + var defaults cistore.ClusterDefaults + err := d.db.QueryRow("SELECT cloud_provider, region, availability_zone, cluster_name, public_keys, base_url, boot_subnet, wg_subnet, short_name, nid_length FROM cluster_defaults"). + Scan(&defaults.CloudProvider, &defaults.Region, &defaults.AvailabilityZone, &defaults.ClusterName, &defaults.PublicKeys, &defaults.BaseUrl, &defaults.BootSubnet, &defaults.WGSubnet, &defaults.ShortName, &defaults.NidLength) + return defaults, err +} + +func (d *DuckDBStore) SetClusterDefaults(clusterDefaults cistore.ClusterDefaults) error { + d.mu.Lock() + defer d.mu.Unlock() + publicKeys, _ := json.Marshal(clusterDefaults.PublicKeys) + _, err := d.db.Exec("INSERT INTO cluster_defaults (cloud_provider, region, availability_zone, cluster_name, public_keys, base_url, boot_subnet, wg_subnet, short_name, nid_length) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT DO UPDATE SET cloud_provider = ?, region = ?, availability_zone = ?, cluster_name = ?, public_keys = ?, base_url = ?, boot_subnet = ?, wg_subnet = ?, short_name = ?, nid_length = ?", + clusterDefaults.CloudProvider, clusterDefaults.Region, clusterDefaults.AvailabilityZone, clusterDefaults.ClusterName, publicKeys, clusterDefaults.BaseUrl, clusterDefaults.BootSubnet, clusterDefaults.WGSubnet, clusterDefaults.ShortName, clusterDefaults.NidLength, + clusterDefaults.CloudProvider, clusterDefaults.Region, clusterDefaults.AvailabilityZone, clusterDefaults.ClusterName, publicKeys, clusterDefaults.BaseUrl, clusterDefaults.BootSubnet, clusterDefaults.WGSubnet, clusterDefaults.ShortName, clusterDefaults.NidLength) + return err +} + +func (d *DuckDBStore) SerializeToParquet(filePath string) error { + d.mu.Lock() + defer d.mu.Unlock() + tables := []string{"groups", "instances", "cluster_defaults"} + for _, table := range tables { + _, err := d.db.Exec(fmt.Sprintf("COPY (SELECT * FROM %s) TO '%s/%s.parquet' (FORMAT 'parquet')", table, filePath, table)) + if err != nil { + return err + } + } + return nil +} + +func (d *DuckDBStore) LoadFromParquet(filePath string) error { + d.mu.Lock() + defer d.mu.Unlock() + tables := []string{"groups", "instances", "cluster_defaults"} + for _, table := range tables { + _, err := d.db.Exec(fmt.Sprintf("COPY %s FROM '%s/%s.parquet' (FORMAT 'parquet')", table, filePath, table)) + if err != nil { + return err + } + } + return nil +} + +func (d *DuckDBStore) ApplyMigrations(migrations []string) error { + d.mu.Lock() + defer d.mu.Unlock() + for _, migration := range migrations { + if _, err := d.db.Exec(migration); err != nil { + return err + } + } + return nil +} diff --git a/internal/memstore/ciMemStore_test.go b/internal/memstore/ciMemStore_test.go new file mode 100644 index 000000000..fde785ef8 --- /dev/null +++ b/internal/memstore/ciMemStore_test.go @@ -0,0 +1,20 @@ +package memstore + +import ( + "testing" + + storetesting "github.com/OpenCHAMI/cloud-init/pkg/cistore/testing" +) + +func TestMemStore(t *testing.T) { + // Create a new MemStore instance + store := NewMemStore() + + // Create a cleanup function that will be called in all cases + cleanup := func() { + // No cleanup needed for MemStore as it's in-memory + } + + // Run the standard test suite + storetesting.RunStoreTests(t, store, cleanup) +} diff --git a/internal/quackstore/quackstore.go b/internal/quackstore/quackstore.go index 0006fc779..07483dc59 100644 --- a/internal/quackstore/quackstore.go +++ b/internal/quackstore/quackstore.go @@ -1,6 +1,7 @@ package quackstore import ( + "crypto/rand" "database/sql" "encoding/json" "fmt" @@ -348,8 +349,10 @@ func (s *QuackStore) Close() error { return s.db.Close() } -// generateInstanceId generates a unique instance ID +// generateInstanceID generates a unique instance ID in the format "i-XXXXXX", +// where "XXXXXX" is a random 6-digit hexadecimal string. func generateInstanceId() string { - // TODO: Implement proper instance ID generation - return "i-1234567890abcdef" + randBytes := make([]byte, 3) + rand.Read(randBytes) + return fmt.Sprintf("i-%x", randBytes) } diff --git a/pkg/cistore/models.go b/pkg/cistore/models.go index 1c4178af5..56c147e7f 100644 --- a/pkg/cistore/models.go +++ b/pkg/cistore/models.go @@ -3,18 +3,16 @@ package cistore import ( "encoding/json" "errors" - "fmt" - - "gopkg.in/yaml.v3" base "github.com/Cray-HPE/hms-base" ) type GroupData struct { - Name string `json:"name" yaml:"name" example:"compute" description:"Group name"` - Description string `json:"description,omitempty" yaml:"description,omitempty" example:"The compute group" description:"A short description of the group"` - Data map[string]interface{} `json:"meta-data,omitempty" yaml:"meta-data,omitempty" description:"json map of a string (key) to a struct (value) representing group meta-data"` - File CloudConfigFile `json:"file,omitempty" yaml:"file,omitempty" description:"Cloud-Init configuration for group"` + Name string `json:"name" example:"compute" description:"Group name"` + Description string `json:"description,omitempty" example:"The compute group" description:"A short description of the group"` + Data map[string]interface{} `json:"meta-data,omitempty" description:"json map of a string (key) to a struct (value) representing group meta-data"` + File CloudConfigFile `json:"file,omitempty" description:"Cloud-Init configuration for group"` + Versions map[string]string `json:"versions,omitempty" description:"Map of group versions"` } func (g *GroupData) ParseFromJSON(body []byte) error { @@ -33,13 +31,13 @@ func (g *GroupData) ParseFromJSON(body []byte) error { type OpenCHAMIComponent struct { base.Component - MAC string `json:"mac" yaml:"mac"` // MAC address of the inteface used to boot the component - IP string `json:"ip" yaml:"ip"` // IP address of the interface used to boot the component - WGIP string `json:"wgip,omitempty" yaml:"wgip,omitempty"` // Wireguard IP address of the interface used for cloud-init + MAC string `json:"mac"` // MAC address of the inteface used to boot the component + IP string `json:"ip"` // IP address of the interface used to boot the component + WGIP string `json:"wgip,omitempty"` // Wireguard IP address of the interface used for cloud-init } type OpenCHAMIInstanceInfo struct { - ID string `json:"id" yaml:"id" example:"x3000c1b1n1" description:"Node unique identifier, on systems that support xnames, this will be an xname which includes location information"` + ID string `json:"id" example:"x3000c1b1n1" description:"Node unique identifier, on systems that support xnames, this will be an xname which includes location information"` InstanceID string `json:"instance-id" yaml:"instance-id"` LocalHostname string `json:"local-hostname,omitempty" yaml:"local-hostname" example:"compute-1" description:"Node-specific hostname"` Hostname string `json:"hostname,omitempty" yaml:"hostname"` @@ -68,12 +66,12 @@ type ClusterDefaults struct { } type CloudConfigFile struct { - Content []byte `json:"content" yaml:"content" swaggertype:"string" example:"IyMgdGVtcGxhdGU6IGppbmphCiNjbG91ZC1jb25maWcKbWVyZ2VfaG93OgotIG5hbWU6IGxpc3QKICBzZXR0aW5nczogW2FwcGVuZF0KLSBuYW1lOiBkaWN0CiAgc2V0dGluZ3M6IFtub19yZXBsYWNlLCByZWN1cnNlX2xpc3RdCnVzZXJzOgogIC0gbmFtZTogcm9vdAogICAgc3NoX2F1dGhvcml6ZWRfa2V5czoge3sgZHMubWV0YV9kYXRhLmluc3RhbmNlX2RhdGEudjEucHVibGljX2tleXMgfX0KZGlzYWJsZV9yb290OiBmYWxzZQo=" description:"Cloud-Init configuration content whose encoding depends on the value of 'encoding'"` - Name string `json:"filename" yaml:"filename"` - Encoding string `json:"encoding,omitempty" yaml:"encoding,omitempty" enums:"base64,plain"` + Content []byte `json:"content" swaggertype:"string" example:"IyMgdGVtcGxhdGU6IGppbmphCiNjbG91ZC1jb25maWcKbWVyZ2VfaG93OgotIG5hbWU6IGxpc3QKICBzZXR0aW5nczogW2FwcGVuZF0KLSBuYW1lOiBkaWN0CiAgc2V0dGluZ3M6IFtub19yZXBsYWNlLCByZWN1cnNlX2xpc3RdCnVzZXJzOgogIC0gbmFtZTogcm9vdAogICAgc3NoX2F1dGhvcml6ZWRfa2V5czoge3sgZHMubWV0YV9kYXRhLmluc3RhbmNlX2RhdGEudjEucHVibGljX2tleXMgfX0KZGlzYWJsZV9yb290OiBmYWxzZQo=" description:"Cloud-Init configuration content whose encoding depends on the value of 'encoding'"` + Name string `json:"filename"` + Encoding string `json:"encoding,omitempty" enums:"base64,plain"` } -// Custom JSON unmarshaler for CloudConfigFile +// UnmarshalJSON implements json.Unmarshaler func (f *CloudConfigFile) UnmarshalJSON(data []byte) error { // Use an auxiliary struct so that: // @@ -85,7 +83,7 @@ func (f *CloudConfigFile) UnmarshalJSON(data []byte) error { // to f.Content. type Alias CloudConfigFile aux := &struct { - Content string `json:"content" yaml:"content"` + Content string `json:"content"` *Alias }{ Alias: (*Alias)(f), @@ -99,57 +97,11 @@ func (f *CloudConfigFile) UnmarshalJSON(data []byte) error { return nil } -// Custom YAML unmarshaller for CloudConfigFile. This is needed because the yaml -// library cannot unmarshal string to []byte like json can, so it needs to be -// told how to do so. -func (f *CloudConfigFile) UnmarshalYAML(n *yaml.Node) error { - // Use an auxiliary struct so that: - // - // 1. json.Unmarshal doesn't recurse forever and overflow the stack. - // 2. json.Unmarshal doesn't try to base64-decode "content" in the data - // before assigning the bytes to f.Content. Content is unmarshalled - // as a string instead of bytes in order to prevent this. After - // unmarshalling, the string is converted back to bytes and assigned - // to f.Content. - // - // Interestingly, yaml.Unmarshal will not unmarshal into aux's pointer - // to f, so we have to use json.Unmarshal. - type Alias CloudConfigFile - aux := &struct { - Content string `json:"content" yaml:"content"` - *Alias - }{ - Alias: (*Alias)(f), - } - - // Decode YAML document (n) into aux struct, using a map as an - // intermediary (since n.Decode() cannot decode into a []byte). We have - // to use JSON for the unmarshalling (and, by consequence, the - // marshalling) so that f will get written via aux's pointer to it. For - // some reason, the yaml unmarshaller will not do that. - // - // 1. Decode YAML document (n) into map. - // 2. JSON marshal map into bytes. - // 3. JSON unmarshal bytes into aux struct. - // 4. Set f.Content to byte-ified aux.Content. - var mAux map[string]interface{} - if err := n.Decode(&mAux); err != nil { - return err - } - t, err := json.Marshal(mAux) - if err != nil { - return err - } - if err := json.Unmarshal(t, &aux); err != nil { - return err - } - f.Content = []byte(aux.Content) - - return nil -} - -// Custom JSON marshaler for CloudConfigFile +// Custom marshaler for CloudConfigFile func (f CloudConfigFile) MarshalJSON() ([]byte, error) { + // Use temporary struct to marshal so json.Marshal doesn't recurse + // indefinitely. Also to convert Content from bytes to string so + // json.Marshal doesn't try to base64 encode the bytes. // Use an auxiliary struct so that: // // 1. json.Marshal doesn't recurse forever and overflow the stack. @@ -158,60 +110,11 @@ func (f CloudConfigFile) MarshalJSON() ([]byte, error) { // to prevent this. Then, aux gets marshalled instead of f. type Alias CloudConfigFile aux := &struct { - Content string `json:"content" yaml:"content"` + Content string `json:"content"` Alias }{ Alias: (Alias)(f), } - aux.Content = string(f.Content) return json.Marshal(aux) } - -// Custom YAML marshaler for CloudConfigFile. This is needed because the yaml -// library will not marshal []byte into string like json will, so it needs to be -// told how to do so. -func (f CloudConfigFile) MarshalYAML() (interface{}, error) { - // Use an auxiliary struct so that: - // - // 1. yaml.Marshal doesn't recurse forever and overflow the stack. - // 2. yaml.Marshal doesn't try to base64-encode f.Content. f.Content is - // converted from bytes to a string and then assigned to aux.Content - // to prevent this. Then, aux gets marshalled instead of f. - // - // aux is set to the values of f, but has its own string Content, set to - // the stringified f.Content. - type Alias CloudConfigFile - aux := &struct { - Content string `yaml:"content"` - Alias - }{ - Content: string(f.Content), - Alias: (Alias)(f), - } - - // Convert aux into map, which has an "alias" key containing the - // "content" that will be set to the actual string value. The map that - // is mapped to "alias" is what is returned. - // - // 1. YAML marshal aux to bytes. - // 2. YAML unmarshal bytes into map. - // 3. Set content in "alias" to aux.Content. - // 4. Return "alias" map. - t, err := yaml.Marshal(aux) - if err != nil { - return nil, err - } - var nMap map[string]interface{} - if err := yaml.Unmarshal(t, &nMap); err != nil { - return nil, err - } - switch c := (nMap["alias"]).(type) { - case map[string]interface{}: - c["content"] = aux.Content - default: - return nil, fmt.Errorf("cloud config file in map is unknown type: wanted=(map[string]interface{}) actual=(%v)", c) - } - - return nMap["alias"], nil -} diff --git a/pkg/cistore/models_test.go b/pkg/cistore/models_test.go index 10edb1a68..96b2d92ad 100644 --- a/pkg/cistore/models_test.go +++ b/pkg/cistore/models_test.go @@ -3,7 +3,6 @@ package cistore import ( "encoding/base64" "encoding/json" - "gopkg.in/yaml.v3" "testing" "github.com/stretchr/testify/assert" @@ -24,19 +23,6 @@ func TestCloudConfigFile_UnmarshalJSON_Plain(t *testing.T) { assert.Equal(t, []byte("#cloud-config\nusers:\n - name: test"), f.Content) } -func TestCloudConfigFile_UnmarshalYAML_Plain(t *testing.T) { - yamlData := []byte(`filename: myconfig.yaml -encoding: plain -content: "#cloud-config\nusers:\n - name: test"`) - - var f CloudConfigFile - err := yaml.Unmarshal(yamlData, &f) - assert.NoError(t, err) - assert.Equal(t, "myconfig.yaml", f.Name) - assert.Equal(t, "plain", f.Encoding) - assert.Equal(t, []byte("#cloud-config\nusers:\n - name: test"), f.Content) -} - func TestCloudConfigFile_UnmarshalJSON_Base64(t *testing.T) { encodedContent := base64.StdEncoding.EncodeToString([]byte("#cloud-config\nusers:\n - name: test")) jsonData := []byte(`{ @@ -54,20 +40,6 @@ func TestCloudConfigFile_UnmarshalJSON_Base64(t *testing.T) { assert.Equal(t, []byte(encodedContent), f.Content) } -func TestCloudConfigFile_UnmarshalYAML_Base64(t *testing.T) { - encodedContent := base64.StdEncoding.EncodeToString([]byte("#cloud-config\nusers:\n - name: test")) - yamlData := []byte(`filename: myconfig.yaml -encoding: base64 -content: ` + encodedContent) - - var f CloudConfigFile - err := yaml.Unmarshal(yamlData, &f) - assert.NoError(t, err) - assert.Equal(t, "myconfig.yaml", f.Name) - assert.Equal(t, "base64", f.Encoding) - assert.Equal(t, []byte(encodedContent), f.Content) -} - func TestCloudConfigFile_MarshalJSON_Plain(t *testing.T) { f := CloudConfigFile{ Name: "plainconfig.yaml", @@ -86,24 +58,6 @@ func TestCloudConfigFile_MarshalJSON_Plain(t *testing.T) { assert.Equal(t, "#cloud-config\nusers:\n - name: test", out["content"]) } -func TestCloudConfigFile_MarshalYAML_Plain(t *testing.T) { - f := CloudConfigFile{ - Name: "plainconfig.yaml", - Encoding: "plain", - Content: []byte("#cloud-config\nusers:\n - name: test"), - } - - data, err := yaml.Marshal(f) - assert.NoError(t, err) - - var out map[string]interface{} - err = yaml.Unmarshal(data, &out) - assert.NoError(t, err) - assert.Equal(t, "plainconfig.yaml", out["filename"]) - assert.Equal(t, "plain", out["encoding"]) - assert.Equal(t, "#cloud-config\nusers:\n - name: test", out["content"]) -} - func TestCloudConfigFile_MarshalJSON_Base64(t *testing.T) { originalConfig := "#cloud-config\nusers:\n - name: test" b64Config := base64.StdEncoding.EncodeToString([]byte(originalConfig)) @@ -123,23 +77,3 @@ func TestCloudConfigFile_MarshalJSON_Base64(t *testing.T) { assert.Equal(t, "base64", out["encoding"]) assert.Equal(t, b64Config, out["content"]) } - -func TestCloudConfigFile_MarshalYAML_Base64(t *testing.T) { - originalConfig := "#cloud-config\nusers:\n - name: test" - b64Config := base64.StdEncoding.EncodeToString([]byte(originalConfig)) - f := CloudConfigFile{ - Name: "encodedconfig.yaml", - Encoding: "base64", - Content: []byte(b64Config), - } - - data, err := yaml.Marshal(f) - assert.NoError(t, err) - - var out map[string]interface{} - err = yaml.Unmarshal(data, &out) - assert.NoError(t, err) - assert.Equal(t, "encodedconfig.yaml", out["filename"]) - assert.Equal(t, "base64", out["encoding"]) - assert.Equal(t, b64Config, out["content"]) -} From a9a1d8efeebc00686a260c458b186cd022d7d991 Mon Sep 17 00:00:00 2001 From: Alex Lovell-Troy Date: Wed, 20 Aug 2025 17:52:46 -0400 Subject: [PATCH 02/12] Refactor the Dockerfile to be based on Ubuntu Signed-off-by: Alex Lovell-Troy Signed-off-by: Alex Lovell-Troy --- Dockerfile | 30 +++++------------------------- go.mod | 13 +++++++++++++ go.sum | 29 +++++++++++++++++++++++++++++ 3 files changed, 47 insertions(+), 25 deletions(-) diff --git a/Dockerfile b/Dockerfile index e51f99fa0..68799c513 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,27 +1,8 @@ -FROM rockylinux:9 AS builder +FROM ubuntu:24.04 -RUN dnf update -y && \ - dnf install -y epel-release && \ - dnf install -y tini wireguard-tools && \ - dnf clean all - -FROM registry.access.redhat.com/ubi9/ubi-minimal - -# Copy the wg binary from the builder image -COPY --from=builder /usr/bin/wg /usr/bin/wg -COPY --from=builder /usr/bin/wg-quick /usr/bin/wg-quick -COPY --from=builder /usr/bin/tini /usr/bin/tini - -# Install dnf and certificates -RUN microdnf install -y dnf ca-certificates && dnf clean all - - -RUN microdnf install -y \ - iproute \ - iputils \ - libstdc++ \ - libgcc && \ - microdnf clean all +# Optional: slim this down a bit +RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates wireguard-tools tini\ + && rm -rf /var/lib/apt/lists/* # Copy binary from builder COPY cloud-init-server /usr/local/bin/cloud-init-server @@ -32,8 +13,7 @@ ENV SMD_URL="http://smd:27779" ENV LISTEN="0.0.0.0:27777" # Set non-root user -# UID 65534 is typically "nobody" on Red Hat systems -USER 65534:65534 +USER 10001 # Tini for proper signal forwarding ENTRYPOINT ["/usr/bin/tini", "--"] diff --git a/go.mod b/go.mod index b294cded7..1dc0d42ac 100644 --- a/go.mod +++ b/go.mod @@ -12,6 +12,9 @@ require ( github.com/openchami/chi-middleware/auth v0.0.0-20240812224658-b16b83c70700 github.com/openchami/chi-middleware/log v0.0.0-20240812224658-b16b83c70700 github.com/rs/zerolog v1.34.0 + github.com/spf13/cobra v1.9.1 + github.com/spf13/pflag v1.0.7 + github.com/spf13/viper v1.20.1 github.com/stretchr/testify v1.10.0 github.com/swaggo/swag v1.16.4 ) @@ -33,6 +36,7 @@ require ( github.com/goccy/go-json v0.10.5 // indirect github.com/google/flatbuffers v25.2.10+incompatible // indirect github.com/google/uuid v1.6.0 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/klauspost/compress v1.18.0 // indirect github.com/klauspost/cpuid/v2 v2.2.10 // indirect @@ -44,16 +48,25 @@ require ( github.com/mailru/easyjson v0.9.0 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect + github.com/pelletier/go-toml/v2 v2.2.3 // indirect github.com/pierrec/lz4/v4 v4.1.22 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/sagikazarmark/locafero v0.7.0 // indirect github.com/segmentio/asm v1.2.0 // 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/subosito/gotenv v1.6.0 // indirect github.com/zeebo/xxh3 v1.0.2 // indirect + go.uber.org/atomic v1.11.0 // indirect + go.uber.org/multierr v1.9.0 // indirect golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 // indirect golang.org/x/mod v0.24.0 // indirect golang.org/x/sync v0.13.0 // indirect golang.org/x/tools v0.32.0 // indirect golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) require ( diff --git a/go.sum b/go.sum index 531d10ea7..e00c6e4f2 100644 --- a/go.sum +++ b/go.sum @@ -25,6 +25,7 @@ github.com/apache/thrift v0.21.0/go.mod h1:W1H8aR/QRtYNvrPeFXBtobyRkd0/YVhTc6i07 github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= @@ -33,6 +34,8 @@ github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 h1:NMZiJj8QnKe1LgsbDayM4UoHwbvw github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40= github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= +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.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= @@ -89,6 +92,8 @@ github.com/hashicorp/hcl v1.0.1-vault-7 h1:ag5OxFVy3QYTFTJODRzTKVZ6xvdfLLCA1cy/Y github.com/hashicorp/hcl v1.0.1-vault-7/go.mod h1:XYhtn6ijBSAj6n4YqAaf7RBPS4I06AItNorpy+MoQNM= github.com/hashicorp/vault/api v1.16.0 h1:nbEYGJiAPGzT9U4oWgaaB0g+Rj8E59QuHKyA5LhwQN4= github.com/hashicorp/vault/api v1.16.0/go.mod h1:KhuUhzOD8lDSk29AtzNjgAu2kxRA9jL9NAbkFlqvkBA= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/klauspost/asmfmt v1.3.2 h1:4Ri7ox3EwapiOjCki+hw14RyKk201CN4rzyCJRFLpK4= @@ -136,6 +141,8 @@ github.com/openchami/chi-middleware/auth v0.0.0-20240812224658-b16b83c70700 h1:X github.com/openchami/chi-middleware/auth v0.0.0-20240812224658-b16b83c70700/go.mod h1:kswb9kU5cZAFRAvf1dAUJRWbQyjDEb0qkxW4ncDdEXg= github.com/openchami/chi-middleware/log v0.0.0-20240812224658-b16b83c70700 h1:Gzt5f6RK39CHvY3SJudzBb/RK4tVh/S3CpJ0eQlbNdg= github.com/openchami/chi-middleware/log v0.0.0-20240812224658-b16b83c70700/go.mod h1:UuXvr2loD4MtvZeKr57W0WpBs+gm0KM1kdtcXrE8M6s= +github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= +github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU= github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= @@ -148,24 +155,46 @@ github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUz github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY= github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk= github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= +github.com/sagikazarmark/locafero v0.7.0 h1:5MqpDsTGNDhY8sGp0Aowyf0qKsPrhewaLSsFaodPcyo= +github.com/sagikazarmark/locafero v0.7.0/go.mod h1:2za3Cg5rMaTMoG/2Ulr9AwtFaIppKXTRYnozin4aB5k= github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys= github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= +github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= +github.com/spf13/afero v1.12.0 h1:UcOPyRBYczmFn6yvphxkn9ZEOY65cpwGKb5mL36mrqs= +github.com/spf13/afero v1.12.0/go.mod h1:ZTlWwG4/ahT8W7T0WQ5uYmjI9duaLQGy3Q2OAl4sk/4= +github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y= +github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= +github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= +github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/pflag v1.0.7 h1:vN6T9TfwStFPFM5XzjsvmzZkLuaLX+HS+0SeFLRgU6M= +github.com/spf13/pflag v1.0.7/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +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/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= +github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/swaggo/swag v1.16.4 h1:clWJtd9LStiG3VeijiCfOVODP6VpHtKdQy9ELFG3s1A= github.com/swaggo/swag v1.16.4/go.mod h1:VBsHJRsDvfYvqoiMKnsdwhNV9LEMHgEDZcyVYX0sxPg= github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ= github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0= github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA= +go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= +go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= +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= golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 h1:R84qjqJb5nVJMxqWYb3np9L5ZsaDtB+a39EqjV0JSUM= From 795181cfbbda5b0e71daa0dab8989f67f259c7be Mon Sep 17 00:00:00 2001 From: Alex Lovell-Troy Date: Thu, 21 Aug 2025 09:42:37 -0400 Subject: [PATCH 03/12] Reverting USER change. Reverting missing '/admin' path. Signed-off-by: Alex Lovell-Troy --- Dockerfile | 2 +- cmd/cloud-init-server/main.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 68799c513..d2a64db91 100644 --- a/Dockerfile +++ b/Dockerfile @@ -13,7 +13,7 @@ ENV SMD_URL="http://smd:27779" ENV LISTEN="0.0.0.0:27777" # Set non-root user -USER 10001 +USER 65534:65534 # Tini for proper signal forwarding ENTRYPOINT ["/usr/bin/tini", "--"] diff --git a/cmd/cloud-init-server/main.go b/cmd/cloud-init-server/main.go index 9cb98f67e..4b3d973f5 100644 --- a/cmd/cloud-init-server/main.go +++ b/cmd/cloud-init-server/main.go @@ -279,7 +279,7 @@ func initCiClientRouter(router chi.Router, handler *CiHandler, wgInterfaceManage func initCiAdminRouter(router chi.Router, handler *CiHandler) { // admin API subrouter - router.Route("/", func(r chi.Router) { + router.Route("/admin/", func(r chi.Router) { // Cluster Defaults r.Get("/cluster-defaults", GetClusterDataHandler(handler.store)) From 341ea1197e3674995e999bb44ad59b8c808ec366 Mon Sep 17 00:00:00 2001 From: Alex Lovell-Troy Date: Fri, 29 Aug 2025 11:28:13 -0400 Subject: [PATCH 04/12] Adding golang-lint action and fixing linter errors Signed-off-by: Alex Lovell-Troy --- .github/workflows/lint.yaml | 25 +++++++++++++ go.mod | 28 +++++++-------- go.sum | 72 ++++++++++++++++++------------------- pkg/cistore/models.go | 1 + 4 files changed, 76 insertions(+), 50 deletions(-) create mode 100644 .github/workflows/lint.yaml diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml new file mode 100644 index 000000000..b23c435b9 --- /dev/null +++ b/.github/workflows/lint.yaml @@ -0,0 +1,25 @@ +name: Run golangci-lint +on: + push: + branches: + - main + pull_request: + +permissions: + contents: read + # Optional: allow read access to pull requests. Use with `only-new-issues` option. + # pull-requests: read + +jobs: + golangci: + name: lint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version: stable + - name: golangci-lint + uses: golangci/golangci-lint-action@v8 + with: + version: v2.1 \ No newline at end of file diff --git a/go.mod b/go.mod index 1dc0d42ac..dd1da0ce8 100644 --- a/go.mod +++ b/go.mod @@ -4,9 +4,9 @@ go 1.24.0 require ( github.com/OpenCHAMI/jwtauth/v5 v5.0.0-20240321222802-e6cb468a2a18 - github.com/OpenCHAMI/quack v0.0.1 + github.com/OpenCHAMI/quack v0.0.2 github.com/OpenCHAMI/smd/v2 v2.18.0 - github.com/go-chi/chi/v5 v5.2.1 + github.com/go-chi/chi/v5 v5.2.3 github.com/lestrrat-go/jwx/v2 v2.1.6 github.com/marcboeker/go-duckdb v1.8.5 github.com/openchami/chi-middleware/auth v0.0.0-20240812224658-b16b83c70700 @@ -15,15 +15,15 @@ require ( github.com/spf13/cobra v1.9.1 github.com/spf13/pflag v1.0.7 github.com/spf13/viper v1.20.1 - github.com/stretchr/testify v1.10.0 - github.com/swaggo/swag v1.16.4 + github.com/stretchr/testify v1.11.1 + github.com/swaggo/swag v1.16.6 ) require ( github.com/Cray-HPE/hms-base/v2 v2.3.0 // indirect github.com/Cray-HPE/hms-xname v1.4.0 // indirect github.com/KyleBanks/depth v1.2.1 // indirect - github.com/apache/arrow-go/v18 v18.2.0 // indirect + github.com/apache/arrow-go/v18 v18.4.0 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 // indirect @@ -32,14 +32,14 @@ require ( github.com/go-openapi/jsonreference v0.21.0 // indirect github.com/go-openapi/spec v0.21.0 // indirect github.com/go-openapi/swag v0.23.1 // indirect - github.com/go-viper/mapstructure/v2 v2.2.1 // indirect + github.com/go-viper/mapstructure/v2 v2.3.0 // indirect github.com/goccy/go-json v0.10.5 // indirect github.com/google/flatbuffers v25.2.10+incompatible // indirect github.com/google/uuid v1.6.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/klauspost/compress v1.18.0 // indirect - github.com/klauspost/cpuid/v2 v2.2.10 // indirect + github.com/klauspost/cpuid/v2 v2.3.0 // indirect github.com/lestrrat-go/blackmagic v1.0.3 // indirect github.com/lestrrat-go/httpcc v1.0.1 // indirect github.com/lestrrat-go/httprc v1.0.6 // indirect @@ -62,9 +62,9 @@ require ( go.uber.org/atomic v1.11.0 // indirect go.uber.org/multierr v1.9.0 // indirect golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 // indirect - golang.org/x/mod v0.24.0 // indirect - golang.org/x/sync v0.13.0 // indirect - golang.org/x/tools v0.32.0 // indirect + golang.org/x/mod v0.27.0 // indirect + golang.org/x/sync v0.16.0 // indirect + golang.org/x/tools v0.36.0 // indirect golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) @@ -88,10 +88,10 @@ require ( github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/ryanuber/go-glob v1.0.0 // indirect github.com/sirupsen/logrus v1.9.3 // indirect - golang.org/x/crypto v0.37.0 // indirect - golang.org/x/net v0.39.0 // indirect - golang.org/x/sys v0.32.0 // indirect - golang.org/x/text v0.24.0 // indirect + golang.org/x/crypto v0.41.0 // indirect + golang.org/x/net v0.43.0 // indirect + golang.org/x/sys v0.35.0 // indirect + golang.org/x/text v0.28.0 // indirect golang.org/x/time v0.11.0 // indirect gopkg.in/yaml.v2 v2.4.0 ) diff --git a/go.sum b/go.sum index e00c6e4f2..6e407f424 100644 --- a/go.sum +++ b/go.sum @@ -12,16 +12,16 @@ github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= github.com/OpenCHAMI/jwtauth/v5 v5.0.0-20240321222802-e6cb468a2a18 h1:oBPtXp9RVm9lk5zTmDLf+Vh21yDHpulBxUqGJQjwQCk= github.com/OpenCHAMI/jwtauth/v5 v5.0.0-20240321222802-e6cb468a2a18/go.mod h1:ggNHWgLfW/WRXcE8ZZC4S7UwHif16HVmyowOCWdNSN8= -github.com/OpenCHAMI/quack v0.0.1 h1:qOXyXskLiqMOZqaDSEchUJPPe1qDuC2wUG3kNyyHeDI= -github.com/OpenCHAMI/quack v0.0.1/go.mod h1:5oWhAgheSMM+h713DIJEQlcT+ooOyYwj3z/1WSWWvyM= +github.com/OpenCHAMI/quack v0.0.2 h1:N7f4Qs3Mgmaa6kxd0aY5qkWb3w6ssNJo92CD5nftUuw= +github.com/OpenCHAMI/quack v0.0.2/go.mod h1:7vGD4KHpFhQKR8G6mFXFZHhfhaP4c/V7sUXWImTXChI= github.com/OpenCHAMI/smd/v2 v2.18.0 h1:RZ7lq+zkjZnrnc3McLJANnvoU/T7P8wcCFEukWp0FlQ= github.com/OpenCHAMI/smd/v2 v2.18.0/go.mod h1:RtFmMtTYmMUc74gw4FnfZKDpFMTQJeemAVXB0O+Qaa0= -github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA= -github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA= -github.com/apache/arrow-go/v18 v18.2.0 h1:QhWqpgZMKfWOniGPhbUxrHohWnooGURqL2R2Gg4SO1Q= -github.com/apache/arrow-go/v18 v18.2.0/go.mod h1:Ic/01WSwGJWRrdAZcxjBZ5hbApNJ28K96jGYaxzzGUc= -github.com/apache/thrift v0.21.0 h1:tdPmh/ptjE1IJnhbhrcl2++TauVjy242rkV/UzJChnE= -github.com/apache/thrift v0.21.0/go.mod h1:W1H8aR/QRtYNvrPeFXBtobyRkd0/YVhTc6i07XIAgDw= +github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ= +github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY= +github.com/apache/arrow-go/v18 v18.4.0 h1:/RvkGqH517iY8bZKc4FD5/kkdwXJGjxf28JIXbJ/oB0= +github.com/apache/arrow-go/v18 v18.4.0/go.mod h1:Aawvwhj8x2jURIzD9Moy72cF0FyJXOpkYpdmGRHcw14= +github.com/apache/thrift v0.22.0 h1:r7mTJdj51TMDe6RtcmNdQxgn9XcyfGDOzegMDRg47uc= +github.com/apache/thrift v0.22.0/go.mod h1:1e7J/O1Ae6ZQMTYdy9xa3w9k+XHWPfRvdPyJeynQ+/g= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= @@ -39,8 +39,8 @@ github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7z github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= -github.com/go-chi/chi/v5 v5.2.1 h1:KOIHODQj58PmL80G2Eak4WdvUzjSJSm0vG72crDCqb8= -github.com/go-chi/chi/v5 v5.2.1/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops= +github.com/go-chi/chi/v5 v5.2.3 h1:WQIt9uxdsAbgIYgid+BpYc+liqQZGMHRaUwp0JUcvdE= +github.com/go-chi/chi/v5 v5.2.3/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops= github.com/go-jose/go-jose/v4 v4.1.0 h1:cYSYxd3pw5zd2FSXk2vGdn9igQU2PS8MuxrCOCl0FdY= github.com/go-jose/go-jose/v4 v4.1.0/go.mod h1:GG/vqmYm3Von2nYiB2vGTXzdoNKE5tix5tuc6iAd+sw= github.com/go-openapi/jsonpointer v0.21.1 h1:whnzv/pNXtK2FbX/W9yJfRmE2gsmkfahjMKB0fZvcic= @@ -53,13 +53,13 @@ github.com/go-openapi/swag v0.23.1 h1:lpsStH0n2ittzTnbaSloVZLuB5+fvSY/+hnagBjSNZ github.com/go-openapi/swag v0.23.1/go.mod h1:STZs8TbRvEQQKUA+JZNAm3EWlgaOBGpyFDqQnDHMef0= github.com/go-test/deep v1.1.0 h1:WOcxcdHcvdgThNXjw0t76K42FXTU7HpNQWHpA2HHNlg= github.com/go-test/deep v1.1.0/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= -github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss= -github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= +github.com/go-viper/mapstructure/v2 v2.3.0 h1:27XbWsHIqhbdR5TIC911OfYvgSaW93HM+dX7970Q7jk= +github.com/go-viper/mapstructure/v2 v2.3.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= -github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs= +github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/flatbuffers v25.2.10+incompatible h1:F3vclr7C3HpB1k9mxCGRMXq6FdUalZ6H/pNX4FP1v0Q= github.com/google/flatbuffers v25.2.10+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= @@ -100,8 +100,8 @@ github.com/klauspost/asmfmt v1.3.2 h1:4Ri7ox3EwapiOjCki+hw14RyKk201CN4rzyCJRFLpK github.com/klauspost/asmfmt v1.3.2/go.mod h1:AG8TuvYojzulgDAMCnYn50l/5QV3Bs/tp6j0HLHbNSE= github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= -github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE= -github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= +github.com/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.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -181,12 +181,12 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.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/swaggo/swag v1.16.4 h1:clWJtd9LStiG3VeijiCfOVODP6VpHtKdQy9ELFG3s1A= -github.com/swaggo/swag v1.16.4/go.mod h1:VBsHJRsDvfYvqoiMKnsdwhNV9LEMHgEDZcyVYX0sxPg= +github.com/swaggo/swag v1.16.6 h1:qBNcx53ZaX+M5dxVyTrgQ0PJ/ACK+NzhwcbieTt+9yI= +github.com/swaggo/swag v1.16.6/go.mod h1:ngP2etMK5a0P3QBizic5MEwpRmluJZPHjXcMoj4Xesg= github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ= github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0= @@ -195,33 +195,33 @@ go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= 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= -golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= -golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= +golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4= +golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc= golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 h1:R84qjqJb5nVJMxqWYb3np9L5ZsaDtB+a39EqjV0JSUM= golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0/go.mod h1:S9Xr4PYopiDyqSyp5NjCrhFrqg6A5zA2E/iPHPhqnS8= -golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU= -golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= -golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY= -golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E= -golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610= -golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ= +golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc= +golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= +golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= +golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= +golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= -golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= -golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= +golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= +golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= +golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= -golang.org/x/tools v0.32.0 h1:Q7N1vhpkQv7ybVzLFtTjvQya2ewbwNDZzUgfXGqtMWU= -golang.org/x/tools v0.32.0/go.mod h1:ZxrU41P/wAbZD8EDa6dDCa6XfpkhJ7HFMjHJXfBDu8s= +golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg= +golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s= golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da h1:noIWHXmPHxILtqtCOPIhSt0ABwskkZKjD3bXGnZGpNY= golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= -gonum.org/v1/gonum v0.15.1 h1:FNy7N6OUZVUaWG9pTiD+jlhdQ3lMP+/LcTpJ6+a8sQ0= -gonum.org/v1/gonum v0.15.1/go.mod h1:eZTZuRFrzu5pcyjN5wJhcIhnUdNijYxX1T2IcrOGY0o= +gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= +gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/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= diff --git a/pkg/cistore/models.go b/pkg/cistore/models.go index 56c147e7f..629a24633 100644 --- a/pkg/cistore/models.go +++ b/pkg/cistore/models.go @@ -115,6 +115,7 @@ func (f CloudConfigFile) MarshalJSON() ([]byte, error) { }{ Alias: (Alias)(f), } + aux.Content = string(f.Content) return json.Marshal(aux) } From c39db3b7eaeb697675c74ea9fd363f1afbbedf39 Mon Sep 17 00:00:00 2001 From: Alex Lovell-Troy Date: Fri, 29 Aug 2025 11:28:33 -0400 Subject: [PATCH 05/12] chore: fixing linter errors Signed-off-by: Alex Lovell-Troy --- cmd/cloud-init-server/group_handlers.go | 15 +++++--- cmd/cloud-init-server/handlers.go | 12 ++++--- cmd/cloud-init-server/main.go | 36 ++++++++++---------- cmd/cloud-init-server/metadata_handlers.go | 6 ++-- cmd/cloud-init-server/userdata_handlers.go | 6 ++-- cmd/cloud-init-server/vendordata_handlers.go | 4 +-- cmd/cloud-init-server/version.go | 2 +- internal/duckdbstore/ciDuckDBStore.go | 30 +++++++++++----- internal/memstore/ciMemStore.go | 2 +- internal/quackstore/quackstore.go | 6 ++-- internal/quackstore/quackstore_test.go | 28 ++++++++++----- internal/smdclient/FakeSMDClient_handlers.go | 2 +- internal/smdclient/SMDclient.go | 10 ++++-- internal/smdclient/SMDclient_test.go | 8 ++--- pkg/cistore/testing/store_test_suite.go | 6 ++-- pkg/wgtunnel/allocator_test.go | 4 +-- pkg/wgtunnel/handlers.go | 4 ++- pkg/wgtunnel/tunnels.go | 6 ++-- 18 files changed, 116 insertions(+), 71 deletions(-) diff --git a/cmd/cloud-init-server/group_handlers.go b/cmd/cloud-init-server/group_handlers.go index 029be13da..8df016a05 100644 --- a/cmd/cloud-init-server/group_handlers.go +++ b/cmd/cloud-init-server/group_handlers.go @@ -94,11 +94,12 @@ func (h CiHandler) AddGroupHandler(w http.ResponseWriter, r *http.Request) { // @Router /cloud-init/admin/groups/{id} [get] func (h CiHandler) GetGroupHandler(w http.ResponseWriter, r *http.Request) { var ( - id string = chi.URLParam(r, "id") + id string data cistore.GroupData bytes []byte err error ) + id = chi.URLParam(r, "id") data, err = h.store.GetGroupData(id) if err != nil { @@ -111,7 +112,10 @@ func (h CiHandler) GetGroupHandler(w http.ResponseWriter, r *http.Request) { http.Error(w, err.Error(), http.StatusInternalServerError) return } - w.Write(bytes) + if _, err := w.Write(bytes); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } } // UpdateGroupHandler godoc @@ -135,11 +139,13 @@ func (h CiHandler) GetGroupHandler(w http.ResponseWriter, r *http.Request) { // @Router /cloud-init/admin/groups/{name} [put] func (h CiHandler) UpdateGroupHandler(w http.ResponseWriter, r *http.Request) { var ( - groupName string = chi.URLParam(r, "name") + groupName string data cistore.GroupData err error ) + groupName = chi.URLParam(r, "name") + data, err = parseData(r) if err != nil { http.Error(w, err.Error(), http.StatusUnprocessableEntity) @@ -167,9 +173,10 @@ func (h CiHandler) UpdateGroupHandler(w http.ResponseWriter, r *http.Request) { // @Router /cloud-init/admin/groups/{id} [delete] func (h CiHandler) RemoveGroupHandler(w http.ResponseWriter, r *http.Request) { var ( - id string = chi.URLParam(r, "id") + id string err error ) + id = chi.URLParam(r, "id") err = h.store.RemoveGroupData(id) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) diff --git a/cmd/cloud-init-server/handlers.go b/cmd/cloud-init-server/handlers.go index 1e2aa5d00..412e624bc 100644 --- a/cmd/cloud-init-server/handlers.go +++ b/cmd/cloud-init-server/handlers.go @@ -62,7 +62,11 @@ func DocsHandler(w http.ResponseWriter, r *http.Request) { return } bDoc := []byte(doc) - w.Write(bDoc) + if _, err := w.Write(bDoc); err != nil { + log.Error().Msgf("Error writing OpenAPI docs: %v", err) + w.WriteHeader(http.StatusInternalServerError) + return + } } // SetClusterDataHandler godoc @@ -132,7 +136,7 @@ func GetClusterDataHandler(store cistore.Store) http.HandlerFunc { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - w.Write(jsonData) + _, _ = w.Write(jsonData) } } @@ -155,7 +159,7 @@ func InstanceInfoHandler(sm smdclient.SMDClientInterface, store cistore.Store) h return } - var id string = chi.URLParam(r, "id") + id := chi.URLParam(r, "id") var info cistore.OpenCHAMIInstanceInfo body, err := io.ReadAll(r.Body) if err != nil { @@ -245,7 +249,7 @@ func PhoneHomeHandler(wg *wgtunnel.InterfaceManager, sm smdclient.SMDClientInter if wg != nil { go func() { - wg.RemovePeer(peerName) + _ = wg.RemovePeer(peerName) // Explicitly ignoring the error here. There's nothing to do with it within the goroutine. }() w.WriteHeader(http.StatusOK) diff --git a/cmd/cloud-init-server/main.go b/cmd/cloud-init-server/main.go index 4b3d973f5..41a491929 100644 --- a/cmd/cloud-init-server/main.go +++ b/cmd/cloud-init-server/main.go @@ -102,24 +102,24 @@ func setupFlags(flags *pflag.FlagSet) { // bindViperToFlags binds each flag to Viper so environment variables work seamlessly. func bindViperToFlags() { viper.AutomaticEnv() - viper.BindEnv("listen") - viper.BindEnv("token_url") - viper.BindEnv("smd_url") - viper.BindEnv("jwks_url") - viper.BindEnv("access_token") - viper.BindEnv("cluster_name") - viper.BindEnv("region") - viper.BindEnv("az") - viper.BindEnv("cloud_provider") - viper.BindEnv("base_url") - viper.BindEnv("cacert") - viper.BindEnv("insecure") - viper.BindEnv("impersonation") - viper.BindEnv("wireguard_server") - viper.BindEnv("wireguard_only") - viper.BindEnv("debug") - viper.BindEnv("storage_backend") - viper.BindEnv("db_path") + _ = viper.BindEnv("listen") + _ = viper.BindEnv("token_url") + _ = viper.BindEnv("smd_url") + _ = viper.BindEnv("jwks_url") + _ = viper.BindEnv("access_token") + _ = viper.BindEnv("cluster_name") + _ = viper.BindEnv("region") + _ = viper.BindEnv("az") + _ = viper.BindEnv("cloud_provider") + _ = viper.BindEnv("base_url") + _ = viper.BindEnv("cacert") + _ = viper.BindEnv("insecure") + _ = viper.BindEnv("impersonation") + _ = viper.BindEnv("wireguard_server") + _ = viper.BindEnv("wireguard_only") + _ = viper.BindEnv("debug") + _ = viper.BindEnv("storage_backend") + _ = viper.BindEnv("db_path") } // startServer is where we run our main program logic diff --git a/cmd/cloud-init-server/metadata_handlers.go b/cmd/cloud-init-server/metadata_handlers.go index d1bd48881..ee1f864de 100644 --- a/cmd/cloud-init-server/metadata_handlers.go +++ b/cmd/cloud-init-server/metadata_handlers.go @@ -46,8 +46,8 @@ func getActualRequestIP(r *http.Request) string { // @Router /cloud-init/admin/impersonation/{id}/meta-data [get] func MetaDataHandler(smd smdclient.SMDClientInterface, store cistore.Store) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - var urlId string = chi.URLParam(r, "id") - var id = urlId + urlId := chi.URLParam(r, "id") + var id string var err error // If this request includes an id, it can be interrpreted as an impersonation request if urlId == "" { @@ -100,6 +100,6 @@ func MetaDataHandler(smd smdclient.SMDClientInterface, store cistore.Store) http http.Error(w, "Failed to encode metadata to YAML", http.StatusInternalServerError) return } - w.Write(yamlData) + _, _ = w.Write(yamlData) } } diff --git a/cmd/cloud-init-server/userdata_handlers.go b/cmd/cloud-init-server/userdata_handlers.go index 389e9fcc3..8b45812af 100644 --- a/cmd/cloud-init-server/userdata_handlers.go +++ b/cmd/cloud-init-server/userdata_handlers.go @@ -27,7 +27,7 @@ import ( // @Router /cloud-init/admin/impersonation/{id}/user-data [get] func UserDataHandler(w http.ResponseWriter, r *http.Request) { payload := `#cloud-config` - w.Write([]byte(payload)) + _, _ = w.Write([]byte(payload)) } // GroupUserDataHandler godoc @@ -62,7 +62,7 @@ func GroupUserDataHandler(smd smdclient.SMDClientInterface, store cistore.Store) data, err := store.GetGroupData(group) if err != nil { log.Err(err).Msgf("No information stored for group %s. returning an empty #cloud-config", group) - w.Write([]byte("#cloud-config")) + _, _ = w.Write([]byte("#cloud-config")) return } @@ -78,7 +78,7 @@ func GroupUserDataHandler(smd smdclient.SMDClientInterface, store cistore.Store) data.File.Encoding = "plain" } - w.Write(data.File.Content) + _, _ = w.Write(data.File.Content) } } diff --git a/cmd/cloud-init-server/vendordata_handlers.go b/cmd/cloud-init-server/vendordata_handlers.go index 673e26577..89b8946d6 100644 --- a/cmd/cloud-init-server/vendordata_handlers.go +++ b/cmd/cloud-init-server/vendordata_handlers.go @@ -26,7 +26,7 @@ import ( // @Router /cloud-init/admin/impersonation/{id}/vendor-data [get] func VendorDataHandler(smd smdclient.SMDClientInterface, store cistore.Store) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - var urlId string = chi.URLParam(r, "id") + urlId := chi.URLParam(r, "id") var baseUrl string var id = urlId var err error @@ -70,6 +70,6 @@ func VendorDataHandler(smd smdclient.SMDClientInterface, store cistore.Store) ht for _, group_name := range groups { payload += fmt.Sprintf("%s/%s.yaml\n", baseUrl, group_name) } - w.Write([]byte(payload)) + _, _ = w.Write([]byte(payload)) } } diff --git a/cmd/cloud-init-server/version.go b/cmd/cloud-init-server/version.go index 2951942e8..fb18c30be 100644 --- a/cmd/cloud-init-server/version.go +++ b/cmd/cloud-init-server/version.go @@ -104,5 +104,5 @@ func VersionHandler(w http.ResponseWriter, r *http.Request) { BytesAllocated: memstats.HeapAlloc, } - json.NewEncoder(w).Encode(response) + _ = json.NewEncoder(w).Encode(response) } diff --git a/internal/duckdbstore/ciDuckDBStore.go b/internal/duckdbstore/ciDuckDBStore.go index c7285cae5..bf30febaa 100644 --- a/internal/duckdbstore/ciDuckDBStore.go +++ b/internal/duckdbstore/ciDuckDBStore.go @@ -23,14 +23,16 @@ func NewDuckDBStore(dsn string) (*DuckDBStore, error) { return &DuckDBStore{db: db}, nil } -func (d *DuckDBStore) GetGroups() map[string]cistore.GroupData { +func (d *DuckDBStore) GetGroups() (map[string]cistore.GroupData, error) { d.mu.RLock() defer d.mu.RUnlock() rows, err := d.db.Query("SELECT name, description, data, file, versions FROM groups") if err != nil { - return nil + return nil, err } - defer rows.Close() + defer func() { + _ = rows.Close() + }() groups := make(map[string]cistore.GroupData) for rows.Next() { @@ -39,12 +41,18 @@ func (d *DuckDBStore) GetGroups() map[string]cistore.GroupData { if err := rows.Scan(&group.Name, &group.Description, &data, &file, &versions); err != nil { continue } - json.Unmarshal(data, &group.Data) + err := json.Unmarshal(data, &group.Data) + if err != nil { + return nil, err + } group.File.Content = file - json.Unmarshal(versions, &group.Versions) + err = json.Unmarshal(versions, &group.Versions) + if err != nil { + return nil, err + } groups[group.Name] = group } - return groups + return groups, nil } func (d *DuckDBStore) AddGroupData(groupName string, groupData cistore.GroupData) error { @@ -67,9 +75,15 @@ func (d *DuckDBStore) GetGroupData(groupName string) (cistore.GroupData, error) if err != nil { return group, err } - json.Unmarshal(data, &group.Data) + err = json.Unmarshal(data, &group.Data) + if err != nil { + return group, err + } group.File.Content = file - json.Unmarshal(versions, &group.Versions) + err = json.Unmarshal(versions, &group.Versions) + if err != nil { + return group, err + } return group, nil } diff --git a/internal/memstore/ciMemStore.go b/internal/memstore/ciMemStore.go index 1aeb0e85f..f4bea30c5 100644 --- a/internal/memstore/ciMemStore.go +++ b/internal/memstore/ciMemStore.go @@ -180,6 +180,6 @@ func generateInstanceId() string { func generateUniqueID(prefix string) string { b := make([]byte, 4) - rand.Read(b) + _, _ = rand.Read(b) // Read fills b with cryptographically secure random bytes. It never returns an error, and always fills b entirely return fmt.Sprintf("%s-%x", prefix, b) } diff --git a/internal/quackstore/quackstore.go b/internal/quackstore/quackstore.go index 07483dc59..1b25abbc3 100644 --- a/internal/quackstore/quackstore.go +++ b/internal/quackstore/quackstore.go @@ -68,7 +68,9 @@ func (s *QuackStore) GetGroups() map[string]cistore.GroupData { fmt.Printf("Error querying groups: %v\n", err) return groups } - defer rows.Close() + defer func() { + _ = rows.Close() + }() for rows.Next() { var name string @@ -353,6 +355,6 @@ func (s *QuackStore) Close() error { // where "XXXXXX" is a random 6-digit hexadecimal string. func generateInstanceId() string { randBytes := make([]byte, 3) - rand.Read(randBytes) + _, _ = rand.Read(randBytes) return fmt.Sprintf("i-%x", randBytes) } diff --git a/internal/quackstore/quackstore_test.go b/internal/quackstore/quackstore_test.go index 23cc7ef82..7ebf95de0 100644 --- a/internal/quackstore/quackstore_test.go +++ b/internal/quackstore/quackstore_test.go @@ -16,7 +16,9 @@ func TestQuackStore(t *testing.T) { if err != nil { t.Fatalf("Failed to create temp dir: %v", err) } - defer os.RemoveAll(tmpDir) + defer func() { + _ = os.RemoveAll(tmpDir) + }() // Create the database file path dbPath := filepath.Join(tmpDir, "test.db") @@ -25,7 +27,9 @@ func TestQuackStore(t *testing.T) { t.Run("Database Initialization", func(t *testing.T) { store, err := NewQuackStore(dbPath) assert.NoError(t, err) - defer store.Close() + defer func() { + _ = store.Close() + }() // Verify database file was created _, err = os.Stat(dbPath) @@ -45,11 +49,13 @@ func TestQuackStore(t *testing.T) { // Test database cleanup t.Run("Database Cleanup", func(t *testing.T) { // Remove the existing database file to start fresh - os.Remove(dbPath) + _ = os.Remove(dbPath) store, err := NewQuackStore(dbPath) assert.NoError(t, err) - defer store.Close() + defer func() { + _ = store.Close() + }() // Insert some test data _, err = store.db.Exec("INSERT INTO groups (name, data) VALUES (?, ?)", "cleanup-test", "{}") @@ -62,7 +68,9 @@ func TestQuackStore(t *testing.T) { // Reopen the database and verify data persists store, err = NewQuackStore(dbPath) assert.NoError(t, err) - defer store.Close() + defer func() { + _ = store.Close() + }() var count int err = store.db.QueryRow("SELECT COUNT(*) FROM groups").Scan(&count) @@ -71,19 +79,21 @@ func TestQuackStore(t *testing.T) { }) // Remove the database file before running the standard test suite - os.Remove(dbPath) + _ = os.Remove(dbPath) // Create a new QuackStore instance for the standard test suite store, err := NewQuackStore(dbPath) if err != nil { t.Fatalf("Failed to create QuackStore: %v", err) } - defer store.Close() + defer func() { + _ = store.Close() + }() // Create a cleanup function that will be called in all cases cleanup := func() { - store.Close() - os.RemoveAll(tmpDir) + _ = store.Close() + _ = os.RemoveAll(tmpDir) } defer cleanup() diff --git a/internal/smdclient/FakeSMDClient_handlers.go b/internal/smdclient/FakeSMDClient_handlers.go index 7503d7df7..404142718 100644 --- a/internal/smdclient/FakeSMDClient_handlers.go +++ b/internal/smdclient/FakeSMDClient_handlers.go @@ -46,7 +46,7 @@ func ListNodesHandler(f *FakeSMDClient) http.HandlerFunc { nodes := f.ListNodes() w.WriteHeader(http.StatusOK) w.Header().Add("Content-Type", "application/json") - json.NewEncoder(w).Encode(nodes) + _ = json.NewEncoder(w).Encode(nodes) } } diff --git a/internal/smdclient/SMDclient.go b/internal/smdclient/SMDclient.go index 58cb4228a..5596be2ee 100644 --- a/internal/smdclient/SMDclient.go +++ b/internal/smdclient/SMDclient.go @@ -72,10 +72,12 @@ type NodeMapping struct { // and uses the provided JWT server for authentication func NewSMDClient(clusterName, baseurl, jwtURL, accessToken, certPath string, insecure bool) (*SMDClient, error) { var ( - c *http.Client = &http.Client{Timeout: 2 * time.Second} + c *http.Client certPool *x509.CertPool ) + c = &http.Client{Timeout: 2 * time.Second} + // try and load the cert if path is provided first if certPath != "" { cacert, err := os.ReadFile(certPath) @@ -180,7 +182,7 @@ func (s *SMDClient) getSMD(ep string, smd interface{}) error { log.Info().Msg("Cached JWT was rejected by SMD") if !freshToken { log.Info().Msg("Fetching new JWT and retrying...") - s.RefreshToken() + _ = s.RefreshToken() freshToken = true } else { log.Info().Msg("SMD authentication failed, even with a fresh" + @@ -193,7 +195,9 @@ func (s *SMDClient) getSMD(ep string, smd interface{}) error { break } } - defer resp.Body.Close() + defer func() { + _ = resp.Body.Close() + }() body, err := io.ReadAll(resp.Body) if err != nil { log.Error().Err(err).Msg("failed to read response body") diff --git a/internal/smdclient/SMDclient_test.go b/internal/smdclient/SMDclient_test.go index 493be927f..26bbf64aa 100644 --- a/internal/smdclient/SMDclient_test.go +++ b/internal/smdclient/SMDclient_test.go @@ -17,7 +17,7 @@ func TestPopulateNodes(t *testing.T) { assert.Equal(t, "/hsm/v2/Inventory/EthernetInterfaces/", r.URL.Path) w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - w.Write([]byte(`[ + _, _ = w.Write([]byte(`[ { "ComponentID": "x1000", "MACAddress": "00:11:22:33:44:55", @@ -104,7 +104,7 @@ func TestIPfromID(t *testing.T) { assert.Equal(t, "/hsm/v2/Inventory/EthernetInterfaces/", r.URL.Path) w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - w.Write([]byte(`[ + _, _ = w.Write([]byte(`[ { "ComponentID": "x1000", "MACAddress": "00:11:22:33:44:55", @@ -183,7 +183,7 @@ func TestIDfromIP(t *testing.T) { assert.Equal(t, "/hsm/v2/Inventory/EthernetInterfaces/", r.URL.Path) w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - w.Write([]byte(`[ + _, _ = w.Write([]byte(`[ { "ComponentID": "x1000", "MACAddress": "00:11:22:33:44:55", @@ -262,7 +262,7 @@ func TestIDfromMAC(t *testing.T) { assert.Equal(t, "/hsm/v2/Inventory/EthernetInterfaces/", r.URL.Path) w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - w.Write([]byte(`[ + _, _ = w.Write([]byte(`[ { "ComponentID": "x1000", "MACAddress": "00:11:22:33:44:55", diff --git a/pkg/cistore/testing/store_test_suite.go b/pkg/cistore/testing/store_test_suite.go index 2fad4e4b9..f33ff3681 100644 --- a/pkg/cistore/testing/store_test_suite.go +++ b/pkg/cistore/testing/store_test_suite.go @@ -21,7 +21,7 @@ func RunStoreTests(t *testing.T, store cistore.Store, cleanup func()) { // Clear existing data groups := store.GetGroups() for name := range groups { - store.RemoveGroupData(name) + _ = store.RemoveGroupData(name) } testGroupOperations(t, store) }) @@ -30,7 +30,7 @@ func RunStoreTests(t *testing.T, store cistore.Store, cleanup func()) { // Clear existing data groups := store.GetGroups() for name := range groups { - store.RemoveGroupData(name) + _ = store.RemoveGroupData(name) } testInstanceOperations(t, store) }) @@ -39,7 +39,7 @@ func RunStoreTests(t *testing.T, store cistore.Store, cleanup func()) { // Clear existing data groups := store.GetGroups() for name := range groups { - store.RemoveGroupData(name) + _ = store.RemoveGroupData(name) } testClusterDefaultsOperations(t, store) }) diff --git a/pkg/wgtunnel/allocator_test.go b/pkg/wgtunnel/allocator_test.go index 0eb6ba150..704bb13d4 100644 --- a/pkg/wgtunnel/allocator_test.go +++ b/pkg/wgtunnel/allocator_test.go @@ -57,7 +57,7 @@ func TestNextAvailable(t *testing.T) { func TestIsAllocated(t *testing.T) { allocator, _ := NewIPAllocator("192.168.1.0/24") ip := net.IPAddr{IP: net.ParseIP("192.168.1.10")} - allocator.Reserve(ip) + _ = allocator.Reserve(ip) if !allocator.IsAllocated(ip) { t.Fatalf("Expected IP to be allocated") @@ -72,7 +72,7 @@ func TestIsAllocated(t *testing.T) { func TestRelease(t *testing.T) { allocator, _ := NewIPAllocator("192.168.1.0/24") ip := net.IPAddr{IP: net.ParseIP("192.168.1.10")} - allocator.Reserve(ip) + _ = allocator.Reserve(ip) err := allocator.Release(ip) if err != nil { diff --git a/pkg/wgtunnel/handlers.go b/pkg/wgtunnel/handlers.go index dca87156b..f2479531c 100644 --- a/pkg/wgtunnel/handlers.go +++ b/pkg/wgtunnel/handlers.go @@ -54,7 +54,9 @@ func AddClientHandler(im *InterfaceManager, smdClient smdclient.SMDClientInterfa } var req PublicKeyRequest - defer r.Body.Close() + defer func() { + _ = r.Body.Close() + }() if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, "Invalid request body", http.StatusBadRequest) return diff --git a/pkg/wgtunnel/tunnels.go b/pkg/wgtunnel/tunnels.go index eb9bd95d4..9b33abb58 100644 --- a/pkg/wgtunnel/tunnels.go +++ b/pkg/wgtunnel/tunnels.go @@ -76,7 +76,7 @@ func NewInterfaceManager(name string, localIp net.IP, network *net.IPNet) *Inter log.Fatal().Err(err).Msg("Failed to get usable IP") } im.ipAddress = net.IPAddr{IP: wgIp, Zone: ""} - im.ipManager.Reserve(im.ipAddress) + _ = im.ipManager.Reserve(im.ipAddress) return &im } @@ -190,7 +190,9 @@ func (m *InterfaceManager) StartServer() error { if err != nil { return fmt.Errorf("failed to create temporary file for private key: %v", err) } - defer os.Remove(tmpfile.Name()) // Clean up the file afterwards + defer func() { + _ = os.Remove(tmpfile.Name()) + }() if _, err := tmpfile.WriteString(m.privateKey); err != nil { return fmt.Errorf("failed to write private key to temporary file: %v", err) From 1a5d8ad5b579ffed25131eb77dc3c352f91d2c88 Mon Sep 17 00:00:00 2001 From: Alex Lovell-Troy Date: Thu, 4 Sep 2025 14:12:38 -0400 Subject: [PATCH 06/12] feat: add DuckDB home directory setup in Dockerfile Signed-off-by: Alex Lovell-Troy --- Dockerfile | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Dockerfile b/Dockerfile index d2a64db91..b1bd730e5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -12,6 +12,11 @@ ENV TOKEN_URL="http://opaal:3333/token" ENV SMD_URL="http://smd:27779" ENV LISTEN="0.0.0.0:27777" +RUN mkdir -p /tmp/duckdbhome && chown 65534:65534 /tmp/duckdbhome +VOLUME /tmp/duckdbhome +ENV DUCKDB_HOME="/tmp/duckdbhome" + + # Set non-root user USER 65534:65534 From e0e78958cd209c813f63c326fcdcf7b14b7ce860 Mon Sep 17 00:00:00 2001 From: Alex Lovell-Troy Date: Thu, 4 Sep 2025 17:52:55 -0400 Subject: [PATCH 07/12] chore: ignore errors on certain operations where they are deemed non-critical. Add comments where errors are ignored Signed-off-by: Alex Lovell-Troy --- cmd/cloud-init-server/handlers.go | 2 +- cmd/cloud-init-server/main.go | 2 ++ cmd/cloud-init-server/metadata.go | 2 +- cmd/cloud-init-server/metadata_handlers.go | 2 +- cmd/cloud-init-server/userdata_handlers.go | 6 +++--- cmd/cloud-init-server/vendordata_handlers.go | 2 +- cmd/cloud-init-server/version.go | 2 +- internal/duckdbstore/ciDuckDBStore.go | 17 ++++++++--------- internal/middleware/wireguard.go | 2 +- internal/quackstore/quackstore.go | 4 ++-- internal/quackstore/quackstore_test.go | 18 +++++++++--------- internal/smdclient/FakeSMDClient.go | 4 ++-- internal/smdclient/FakeSMDClient_handlers.go | 7 ++++++- internal/smdclient/SMDclient.go | 5 +++-- internal/smdclient/SMDclient_test.go | 4 ++++ pkg/cistore/testing/store_test_suite.go | 6 +++--- pkg/wgtunnel/allocator_test.go | 10 +++++----- pkg/wgtunnel/handlers.go | 2 +- pkg/wgtunnel/tunnels.go | 10 ++++++---- 19 files changed, 60 insertions(+), 47 deletions(-) diff --git a/cmd/cloud-init-server/handlers.go b/cmd/cloud-init-server/handlers.go index 412e624bc..6ee4116db 100644 --- a/cmd/cloud-init-server/handlers.go +++ b/cmd/cloud-init-server/handlers.go @@ -136,7 +136,7 @@ func GetClusterDataHandler(store cistore.Store) http.HandlerFunc { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - _, _ = w.Write(jsonData) + _, _ = w.Write(jsonData) // Not checking error on Write because we're wouldn't do anything about it anyway } } diff --git a/cmd/cloud-init-server/main.go b/cmd/cloud-init-server/main.go index 41a491929..e54e71812 100644 --- a/cmd/cloud-init-server/main.go +++ b/cmd/cloud-init-server/main.go @@ -102,6 +102,8 @@ func setupFlags(flags *pflag.FlagSet) { // bindViperToFlags binds each flag to Viper so environment variables work seamlessly. func bindViperToFlags() { viper.AutomaticEnv() + // Bind each flag to its corresponding environment variable + // Not checking errors because these always succeed _ = viper.BindEnv("listen") _ = viper.BindEnv("token_url") _ = viper.BindEnv("smd_url") diff --git a/cmd/cloud-init-server/metadata.go b/cmd/cloud-init-server/metadata.go index e999e6873..a167a865c 100644 --- a/cmd/cloud-init-server/metadata.go +++ b/cmd/cloud-init-server/metadata.go @@ -119,7 +119,7 @@ func generateMetaData(component cistore.OpenCHAMIComponent, groups []string, s c func generateHostname(clusterName string, shortName string, nidLength int, comp cistore.OpenCHAMIComponent) string { // in the future, we might want to map the hostname to an xname or something else. - nid, _ := comp.NID.Int64() + nid, _ := comp.NID.Int64() // We can ignore the error because NID is always set var sname string var nlen int diff --git a/cmd/cloud-init-server/metadata_handlers.go b/cmd/cloud-init-server/metadata_handlers.go index ee1f864de..010b145c3 100644 --- a/cmd/cloud-init-server/metadata_handlers.go +++ b/cmd/cloud-init-server/metadata_handlers.go @@ -100,6 +100,6 @@ func MetaDataHandler(smd smdclient.SMDClientInterface, store cistore.Store) http http.Error(w, "Failed to encode metadata to YAML", http.StatusInternalServerError) return } - _, _ = w.Write(yamlData) + _, _ = w.Write(yamlData) // Not checking error on Write because we're wouldn't do anything about it anyway } } diff --git a/cmd/cloud-init-server/userdata_handlers.go b/cmd/cloud-init-server/userdata_handlers.go index 8b45812af..6a4abc42f 100644 --- a/cmd/cloud-init-server/userdata_handlers.go +++ b/cmd/cloud-init-server/userdata_handlers.go @@ -27,7 +27,7 @@ import ( // @Router /cloud-init/admin/impersonation/{id}/user-data [get] func UserDataHandler(w http.ResponseWriter, r *http.Request) { payload := `#cloud-config` - _, _ = w.Write([]byte(payload)) + _, _ = w.Write([]byte(payload)) // Not checking error on Write because we're wouldn't do anything about it anyway } // GroupUserDataHandler godoc @@ -62,7 +62,7 @@ func GroupUserDataHandler(smd smdclient.SMDClientInterface, store cistore.Store) data, err := store.GetGroupData(group) if err != nil { log.Err(err).Msgf("No information stored for group %s. returning an empty #cloud-config", group) - _, _ = w.Write([]byte("#cloud-config")) + _, _ = w.Write([]byte("#cloud-config")) // Not checking error on Write because we're wouldn't do anything about it anyway return } @@ -78,7 +78,7 @@ func GroupUserDataHandler(smd smdclient.SMDClientInterface, store cistore.Store) data.File.Encoding = "plain" } - _, _ = w.Write(data.File.Content) + _, _ = w.Write(data.File.Content) // Not checking error on Write because we're wouldn't do anything about it anyway } } diff --git a/cmd/cloud-init-server/vendordata_handlers.go b/cmd/cloud-init-server/vendordata_handlers.go index 89b8946d6..3e2a36873 100644 --- a/cmd/cloud-init-server/vendordata_handlers.go +++ b/cmd/cloud-init-server/vendordata_handlers.go @@ -70,6 +70,6 @@ func VendorDataHandler(smd smdclient.SMDClientInterface, store cistore.Store) ht for _, group_name := range groups { payload += fmt.Sprintf("%s/%s.yaml\n", baseUrl, group_name) } - _, _ = w.Write([]byte(payload)) + _, _ = w.Write([]byte(payload)) // Not checking error on Write because we're wouldn't do anything about it anyway } } diff --git a/cmd/cloud-init-server/version.go b/cmd/cloud-init-server/version.go index fb18c30be..b40cc276d 100644 --- a/cmd/cloud-init-server/version.go +++ b/cmd/cloud-init-server/version.go @@ -104,5 +104,5 @@ func VersionHandler(w http.ResponseWriter, r *http.Request) { BytesAllocated: memstats.HeapAlloc, } - _ = json.NewEncoder(w).Encode(response) + _ = json.NewEncoder(w).Encode(response) // Not checking error on Encode because we're wouldn't do anything about it anyway } diff --git a/internal/duckdbstore/ciDuckDBStore.go b/internal/duckdbstore/ciDuckDBStore.go index bf30febaa..227b88ee5 100644 --- a/internal/duckdbstore/ciDuckDBStore.go +++ b/internal/duckdbstore/ciDuckDBStore.go @@ -31,7 +31,7 @@ func (d *DuckDBStore) GetGroups() (map[string]cistore.GroupData, error) { return nil, err } defer func() { - _ = rows.Close() + _ = rows.Close() // Ignoring error on deferred Close }() groups := make(map[string]cistore.GroupData) @@ -46,8 +46,7 @@ func (d *DuckDBStore) GetGroups() (map[string]cistore.GroupData, error) { return nil, err } group.File.Content = file - err = json.Unmarshal(versions, &group.Versions) - if err != nil { + if err = json.Unmarshal(versions, &group.Versions); err != nil { return nil, err } groups[group.Name] = group @@ -58,8 +57,8 @@ func (d *DuckDBStore) GetGroups() (map[string]cistore.GroupData, error) { func (d *DuckDBStore) AddGroupData(groupName string, groupData cistore.GroupData) error { d.mu.Lock() defer d.mu.Unlock() - data, _ := json.Marshal(groupData.Data) - versions, _ := json.Marshal(groupData.Versions) + data, _ := json.Marshal(groupData.Data) // Ignoring error because Data is always serializable + versions, _ := json.Marshal(groupData.Versions) // Ignoring error because Versions is always serializable _, err := d.db.Exec("INSERT INTO groups (name, description, data, file, versions) VALUES (?, ?, ?, ?, ?)", groupName, groupData.Description, data, groupData.File.Content, versions) return err @@ -90,8 +89,8 @@ func (d *DuckDBStore) GetGroupData(groupName string) (cistore.GroupData, error) func (d *DuckDBStore) UpdateGroupData(groupName string, groupData cistore.GroupData, create bool) error { d.mu.Lock() defer d.mu.Unlock() - data, _ := json.Marshal(groupData.Data) - versions, _ := json.Marshal(groupData.Versions) + data, _ := json.Marshal(groupData.Data) // Ignoring error because Data is always serializable + versions, _ := json.Marshal(groupData.Versions) // Ignoring error because Versions is always serializable if create { _, err := d.db.Exec("INSERT INTO groups (name, description, data, file, versions) VALUES (?, ?, ?, ?, ?)", groupName, groupData.Description, data, groupData.File.Content, versions) @@ -121,7 +120,7 @@ func (d *DuckDBStore) GetInstanceInfo(nodeName string) (cistore.OpenCHAMIInstanc func (d *DuckDBStore) SetInstanceInfo(nodeName string, instanceInfo cistore.OpenCHAMIInstanceInfo) error { d.mu.Lock() defer d.mu.Unlock() - publicKeys, _ := json.Marshal(instanceInfo.PublicKeys) + publicKeys, _ := json.Marshal(instanceInfo.PublicKeys) // Not checking error because PublicKeys is always serializable _, err := d.db.Exec("INSERT INTO instances (id, instance_id, local_hostname, hostname, cluster_name, region, availability_zone, cloud_provider, instance_type, cloud_init_base_url, public_keys) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT(id) DO UPDATE SET instance_id = ?, local_hostname = ?, hostname = ?, cluster_name = ?, region = ?, availability_zone = ?, cloud_provider = ?, instance_type = ?, cloud_init_base_url = ?, public_keys = ?", nodeName, instanceInfo.InstanceID, instanceInfo.LocalHostname, instanceInfo.Hostname, instanceInfo.ClusterName, instanceInfo.Region, instanceInfo.AvailabilityZone, instanceInfo.CloudProvider, instanceInfo.InstanceType, instanceInfo.CloudInitBaseURL, publicKeys, instanceInfo.InstanceID, instanceInfo.LocalHostname, instanceInfo.Hostname, instanceInfo.ClusterName, instanceInfo.Region, instanceInfo.AvailabilityZone, instanceInfo.CloudProvider, instanceInfo.InstanceType, instanceInfo.CloudInitBaseURL, publicKeys) @@ -147,7 +146,7 @@ func (d *DuckDBStore) GetClusterDefaults() (cistore.ClusterDefaults, error) { func (d *DuckDBStore) SetClusterDefaults(clusterDefaults cistore.ClusterDefaults) error { d.mu.Lock() defer d.mu.Unlock() - publicKeys, _ := json.Marshal(clusterDefaults.PublicKeys) + publicKeys, _ := json.Marshal(clusterDefaults.PublicKeys) // Not checking error on Marshall because PublicKeys is always serializable _, err := d.db.Exec("INSERT INTO cluster_defaults (cloud_provider, region, availability_zone, cluster_name, public_keys, base_url, boot_subnet, wg_subnet, short_name, nid_length) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT DO UPDATE SET cloud_provider = ?, region = ?, availability_zone = ?, cluster_name = ?, public_keys = ?, base_url = ?, boot_subnet = ?, wg_subnet = ?, short_name = ?, nid_length = ?", clusterDefaults.CloudProvider, clusterDefaults.Region, clusterDefaults.AvailabilityZone, clusterDefaults.ClusterName, publicKeys, clusterDefaults.BaseUrl, clusterDefaults.BootSubnet, clusterDefaults.WGSubnet, clusterDefaults.ShortName, clusterDefaults.NidLength, clusterDefaults.CloudProvider, clusterDefaults.Region, clusterDefaults.AvailabilityZone, clusterDefaults.ClusterName, publicKeys, clusterDefaults.BaseUrl, clusterDefaults.BootSubnet, clusterDefaults.WGSubnet, clusterDefaults.ShortName, clusterDefaults.NidLength) diff --git a/internal/middleware/wireguard.go b/internal/middleware/wireguard.go index 99267ea1c..03996c296 100644 --- a/internal/middleware/wireguard.go +++ b/internal/middleware/wireguard.go @@ -157,7 +157,7 @@ func WireGuardMiddlewareWithInterface(wireGuardInterface string, wireGuardCIDR s return } for _, iface := range interfaces { - addrs, _ := iface.Addrs() + addrs, _ := iface.Addrs() // Ignoring error on Addrs() as we can still check other interfaces for _, ifaceAddr := range addrs { if ipNet, ok := ifaceAddr.(*net.IPNet); ok && ipNet.IP.Equal(ip) { recievedInterface = iface diff --git a/internal/quackstore/quackstore.go b/internal/quackstore/quackstore.go index 1b25abbc3..0ecd14c5d 100644 --- a/internal/quackstore/quackstore.go +++ b/internal/quackstore/quackstore.go @@ -69,7 +69,7 @@ func (s *QuackStore) GetGroups() map[string]cistore.GroupData { return groups } defer func() { - _ = rows.Close() + _ = rows.Close() // Ignoring error on deferred Close }() for rows.Next() { @@ -355,6 +355,6 @@ func (s *QuackStore) Close() error { // where "XXXXXX" is a random 6-digit hexadecimal string. func generateInstanceId() string { randBytes := make([]byte, 3) - _, _ = rand.Read(randBytes) + _, _ = rand.Read(randBytes) // Read fills randBytes with cryptographically secure random bytes. It never returns an error, and always fills randBytes entirely return fmt.Sprintf("i-%x", randBytes) } diff --git a/internal/quackstore/quackstore_test.go b/internal/quackstore/quackstore_test.go index 7ebf95de0..827aea144 100644 --- a/internal/quackstore/quackstore_test.go +++ b/internal/quackstore/quackstore_test.go @@ -17,7 +17,7 @@ func TestQuackStore(t *testing.T) { t.Fatalf("Failed to create temp dir: %v", err) } defer func() { - _ = os.RemoveAll(tmpDir) + _ = os.RemoveAll(tmpDir) // Clean up the temp directory. Ignoring error on RemoveAll }() // Create the database file path @@ -28,7 +28,7 @@ func TestQuackStore(t *testing.T) { store, err := NewQuackStore(dbPath) assert.NoError(t, err) defer func() { - _ = store.Close() + _ = store.Close() // Ignoring error on deferred Close }() // Verify database file was created @@ -49,12 +49,12 @@ func TestQuackStore(t *testing.T) { // Test database cleanup t.Run("Database Cleanup", func(t *testing.T) { // Remove the existing database file to start fresh - _ = os.Remove(dbPath) + _ = os.Remove(dbPath) // Ignoring error on Remove store, err := NewQuackStore(dbPath) assert.NoError(t, err) defer func() { - _ = store.Close() + _ = store.Close() // Ignoring error on deferred Close }() // Insert some test data @@ -69,7 +69,7 @@ func TestQuackStore(t *testing.T) { store, err = NewQuackStore(dbPath) assert.NoError(t, err) defer func() { - _ = store.Close() + _ = store.Close() // Ignoring error on deferred Close }() var count int @@ -79,7 +79,7 @@ func TestQuackStore(t *testing.T) { }) // Remove the database file before running the standard test suite - _ = os.Remove(dbPath) + _ = os.Remove(dbPath) // Ignoring error on Remove // Create a new QuackStore instance for the standard test suite store, err := NewQuackStore(dbPath) @@ -87,13 +87,13 @@ func TestQuackStore(t *testing.T) { t.Fatalf("Failed to create QuackStore: %v", err) } defer func() { - _ = store.Close() + _ = store.Close() // Ignoring error on deferred Close }() // Create a cleanup function that will be called in all cases cleanup := func() { - _ = store.Close() - _ = os.RemoveAll(tmpDir) + _ = store.Close() // Ignoring error on deferred Close + _ = os.RemoveAll(tmpDir) // Ignoring error on RemoveAll } defer cleanup() diff --git a/internal/smdclient/FakeSMDClient.go b/internal/smdclient/FakeSMDClient.go index 2f12b71f9..63e54761c 100644 --- a/internal/smdclient/FakeSMDClient.go +++ b/internal/smdclient/FakeSMDClient.go @@ -123,7 +123,7 @@ func (f *FakeSMDClient) ComponentInformation(id string) (base.Component, error) log.Debug().Msgf("FakeSMDClient: %d components from %s to %s", len(f.components), f.rosetta_mapping[0].ComponentID, f.rosetta_mapping[len(f.rosetta_mapping)-1].ComponentID) if c, ok := f.components[id]; ok { log.Debug().Msgf("FakeSMDClient: ComponentInformation(%s) found", id) - groups, _ := f.GroupMembership(id) + groups, _ := f.GroupMembership(id) // ignoring error because it can't really happen log.Debug().Msgf("FakeSMDClient: Groups for %s found: %v", id, groups) return c, nil } @@ -251,7 +251,7 @@ func generateFakeComponents(numComponents int, cidr string) (map[string]base.Com for i := 1; i <= numComponents; i++ { mac = incrementMAC(mac) - xname, _ = incrementXname(xname) + xname, _ = incrementXname(xname) // ignoring error because we know the format is correct component := base.Component{ ID: xname, Type: "Node", diff --git a/internal/smdclient/FakeSMDClient_handlers.go b/internal/smdclient/FakeSMDClient_handlers.go index 404142718..8eee3dec8 100644 --- a/internal/smdclient/FakeSMDClient_handlers.go +++ b/internal/smdclient/FakeSMDClient_handlers.go @@ -46,7 +46,12 @@ func ListNodesHandler(f *FakeSMDClient) http.HandlerFunc { nodes := f.ListNodes() w.WriteHeader(http.StatusOK) w.Header().Add("Content-Type", "application/json") - _ = json.NewEncoder(w).Encode(nodes) + err := json.NewEncoder(w).Encode(nodes) + if err != nil { + log.Error().Err(err).Msg("Failed to encode nodes") + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } } } diff --git a/internal/smdclient/SMDclient.go b/internal/smdclient/SMDclient.go index 5596be2ee..2a6df7eb1 100644 --- a/internal/smdclient/SMDclient.go +++ b/internal/smdclient/SMDclient.go @@ -182,7 +182,8 @@ func (s *SMDClient) getSMD(ep string, smd interface{}) error { log.Info().Msg("Cached JWT was rejected by SMD") if !freshToken { log.Info().Msg("Fetching new JWT and retrying...") - _ = s.RefreshToken() + // Try to refresh the token and retry once + _ = s.RefreshToken(). // ignoring error because we will try again and fail if it doesn't work freshToken = true } else { log.Info().Msg("SMD authentication failed, even with a fresh" + @@ -196,7 +197,7 @@ func (s *SMDClient) getSMD(ep string, smd interface{}) error { } } defer func() { - _ = resp.Body.Close() + _ = resp.Body.Close() // ignoring error on deferred Close }() body, err := io.ReadAll(resp.Body) if err != nil { diff --git a/internal/smdclient/SMDclient_test.go b/internal/smdclient/SMDclient_test.go index 26bbf64aa..fc091ceee 100644 --- a/internal/smdclient/SMDclient_test.go +++ b/internal/smdclient/SMDclient_test.go @@ -17,6 +17,7 @@ func TestPopulateNodes(t *testing.T) { assert.Equal(t, "/hsm/v2/Inventory/EthernetInterfaces/", r.URL.Path) w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) + // ignoring error on Write _, _ = w.Write([]byte(`[ { "ComponentID": "x1000", @@ -104,6 +105,7 @@ func TestIPfromID(t *testing.T) { assert.Equal(t, "/hsm/v2/Inventory/EthernetInterfaces/", r.URL.Path) w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) + // ignoring error on Write _, _ = w.Write([]byte(`[ { "ComponentID": "x1000", @@ -183,6 +185,7 @@ func TestIDfromIP(t *testing.T) { assert.Equal(t, "/hsm/v2/Inventory/EthernetInterfaces/", r.URL.Path) w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) + // ignoring error on Write _, _ = w.Write([]byte(`[ { "ComponentID": "x1000", @@ -262,6 +265,7 @@ func TestIDfromMAC(t *testing.T) { assert.Equal(t, "/hsm/v2/Inventory/EthernetInterfaces/", r.URL.Path) w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) + // ignoring error on Write _, _ = w.Write([]byte(`[ { "ComponentID": "x1000", diff --git a/pkg/cistore/testing/store_test_suite.go b/pkg/cistore/testing/store_test_suite.go index f33ff3681..cdd486b64 100644 --- a/pkg/cistore/testing/store_test_suite.go +++ b/pkg/cistore/testing/store_test_suite.go @@ -21,7 +21,7 @@ func RunStoreTests(t *testing.T, store cistore.Store, cleanup func()) { // Clear existing data groups := store.GetGroups() for name := range groups { - _ = store.RemoveGroupData(name) + _ = store.RemoveGroupData(name) // Ignoring error on RemoveGroupData as the test will catch any issues } testGroupOperations(t, store) }) @@ -30,7 +30,7 @@ func RunStoreTests(t *testing.T, store cistore.Store, cleanup func()) { // Clear existing data groups := store.GetGroups() for name := range groups { - _ = store.RemoveGroupData(name) + _ = store.RemoveGroupData(name) // Ignoring error on RemoveGroupData as the test will catch any issues } testInstanceOperations(t, store) }) @@ -39,7 +39,7 @@ func RunStoreTests(t *testing.T, store cistore.Store, cleanup func()) { // Clear existing data groups := store.GetGroups() for name := range groups { - _ = store.RemoveGroupData(name) + _ = store.RemoveGroupData(name). // Ignoring error on RemoveGroupData as the test will catch any issues } testClusterDefaultsOperations(t, store) }) diff --git a/pkg/wgtunnel/allocator_test.go b/pkg/wgtunnel/allocator_test.go index 704bb13d4..4f33353bd 100644 --- a/pkg/wgtunnel/allocator_test.go +++ b/pkg/wgtunnel/allocator_test.go @@ -14,7 +14,7 @@ func TestNewIPAllocator(t *testing.T) { } func TestReserve(t *testing.T) { - allocator, _ := NewIPAllocator("192.168.1.0/24") + allocator, _ := NewIPAllocator("192.168.1.0/24") // ignoring error on NewIPAllocator. We'll catch it on use anyway. ip := net.IPAddr{IP: net.ParseIP("192.168.1.10")} err := allocator.Reserve(ip) if err != nil { @@ -29,7 +29,7 @@ func TestReserve(t *testing.T) { } func TestNextAvailable(t *testing.T) { - allocator, _ := NewIPAllocator("192.168.1.0/24") + allocator, _ := NewIPAllocator("192.168.1.0/24") // ignoring error on NewIPAllocator. We'll catch it on use anyway. // Reserve the first few IPs to test allocation for i := 0; i <= 253; i++ { @@ -55,9 +55,9 @@ func TestNextAvailable(t *testing.T) { } func TestIsAllocated(t *testing.T) { - allocator, _ := NewIPAllocator("192.168.1.0/24") + allocator, _ := NewIPAllocator("192.168.1.0/24") // ignoring error on NewIPAllocator. We'll catch it on use anyway. ip := net.IPAddr{IP: net.ParseIP("192.168.1.10")} - _ = allocator.Reserve(ip) + _ = allocator.Reserve(ip) // ignoring error on Reserve. We'll catch it on use anyway. if !allocator.IsAllocated(ip) { t.Fatalf("Expected IP to be allocated") @@ -72,7 +72,7 @@ func TestIsAllocated(t *testing.T) { func TestRelease(t *testing.T) { allocator, _ := NewIPAllocator("192.168.1.0/24") ip := net.IPAddr{IP: net.ParseIP("192.168.1.10")} - _ = allocator.Reserve(ip) + _ = allocator.Reserve(ip) // ignoring error on Reserve. We'll catch it on use anyway. err := allocator.Release(ip) if err != nil { diff --git a/pkg/wgtunnel/handlers.go b/pkg/wgtunnel/handlers.go index f2479531c..b235310fb 100644 --- a/pkg/wgtunnel/handlers.go +++ b/pkg/wgtunnel/handlers.go @@ -55,7 +55,7 @@ func AddClientHandler(im *InterfaceManager, smdClient smdclient.SMDClientInterfa var req PublicKeyRequest defer func() { - _ = r.Body.Close() + _ = r.Body.Close() // ignoring error on deferred Close }() if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, "Invalid request body", http.StatusBadRequest) diff --git a/pkg/wgtunnel/tunnels.go b/pkg/wgtunnel/tunnels.go index 9b33abb58..8a95796a3 100644 --- a/pkg/wgtunnel/tunnels.go +++ b/pkg/wgtunnel/tunnels.go @@ -76,8 +76,10 @@ func NewInterfaceManager(name string, localIp net.IP, network *net.IPNet) *Inter log.Fatal().Err(err).Msg("Failed to get usable IP") } im.ipAddress = net.IPAddr{IP: wgIp, Zone: ""} - _ = im.ipManager.Reserve(im.ipAddress) - + err = im.ipManager.Reserve(im.ipAddress) + if err != nil { + log.Fatal().Err(err).Msg("Failed to reserve IP address") + } return &im } @@ -176,7 +178,7 @@ func (m *InterfaceManager) StartServer() error { // Step 2: Assign IP address to the WireGuard interface wgIp := m.ipAddress.IP.String() - ones, _ := m.network.Mask.Size() + ones, _ := m.network.Mask.Size() // ignoring error on Mask.Size() as we know it's valid wgCidr := fmt.Sprintf("%s/%d", wgIp, ones) if out, err := exec.Command("ip", "address", "add", "dev", m.interfaceName, wgCidr).CombinedOutput(); err != nil { @@ -191,7 +193,7 @@ func (m *InterfaceManager) StartServer() error { return fmt.Errorf("failed to create temporary file for private key: %v", err) } defer func() { - _ = os.Remove(tmpfile.Name()) + _ = os.Remove(tmpfile.Name()) // ignoring error on Remove as there's nothing we can do about it anyway }() if _, err := tmpfile.WriteString(m.privateKey); err != nil { From 63bdea95c2300de4e1cfa2840b252ceb6b7554fc Mon Sep 17 00:00:00 2001 From: Alex Lovell-Troy Date: Thu, 4 Sep 2025 18:01:03 -0400 Subject: [PATCH 08/12] fixing typos Signed-off-by: Alex Lovell-Troy --- internal/smdclient/SMDclient.go | 2 +- pkg/cistore/testing/store_test_suite.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/smdclient/SMDclient.go b/internal/smdclient/SMDclient.go index 2a6df7eb1..df85e9bcc 100644 --- a/internal/smdclient/SMDclient.go +++ b/internal/smdclient/SMDclient.go @@ -183,7 +183,7 @@ func (s *SMDClient) getSMD(ep string, smd interface{}) error { if !freshToken { log.Info().Msg("Fetching new JWT and retrying...") // Try to refresh the token and retry once - _ = s.RefreshToken(). // ignoring error because we will try again and fail if it doesn't work + _ = s.RefreshToken() // ignoring error because we will try again and fail if it doesn't work freshToken = true } else { log.Info().Msg("SMD authentication failed, even with a fresh" + diff --git a/pkg/cistore/testing/store_test_suite.go b/pkg/cistore/testing/store_test_suite.go index cdd486b64..5d96f938c 100644 --- a/pkg/cistore/testing/store_test_suite.go +++ b/pkg/cistore/testing/store_test_suite.go @@ -39,7 +39,7 @@ func RunStoreTests(t *testing.T, store cistore.Store, cleanup func()) { // Clear existing data groups := store.GetGroups() for name := range groups { - _ = store.RemoveGroupData(name). // Ignoring error on RemoveGroupData as the test will catch any issues + _ = store.RemoveGroupData(name) // Ignoring error on RemoveGroupData as the test will catch any issues } testClusterDefaultsOperations(t, store) }) From 1bb5ba88b0c560b509c9da4b0b920655481418a0 Mon Sep 17 00:00:00 2001 From: Devon Bautista <17506592+synackd@users.noreply.github.com> Date: Mon, 8 Sep 2025 19:59:13 -0600 Subject: [PATCH 09/12] fix: add log messages for some formerly-ignored errors While it's functionally feasible to ignore some errors, from a troubleshooting perspective it's better to log certain errors to pinpoint the cause of an issue in case unusual conditions arise. Signed-off-by: Devon Bautista <17506592+synackd@users.noreply.github.com> --- cmd/cloud-init-server/handlers.go | 4 ++- cmd/cloud-init-server/metadata_handlers.go | 4 ++- cmd/cloud-init-server/userdata_handlers.go | 12 ++++++-- cmd/cloud-init-server/vendordata_handlers.go | 4 ++- cmd/cloud-init-server/version.go | 6 +++- internal/smdclient/FakeSMDClient.go | 5 ++- internal/smdclient/FakeSMDClient_handlers.go | 3 +- internal/smdclient/SMDclient.go | 9 +++++- internal/smdclient/SMDclient_test.go | 32 ++++++++++++-------- pkg/wgtunnel/tunnels.go | 2 +- 10 files changed, 57 insertions(+), 24 deletions(-) diff --git a/cmd/cloud-init-server/handlers.go b/cmd/cloud-init-server/handlers.go index 6ee4116db..67184b408 100644 --- a/cmd/cloud-init-server/handlers.go +++ b/cmd/cloud-init-server/handlers.go @@ -136,7 +136,9 @@ func GetClusterDataHandler(store cistore.Store) http.HandlerFunc { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - _, _ = w.Write(jsonData) // Not checking error on Write because we're wouldn't do anything about it anyway + if _, err = w.Write(jsonData); err != nil { + log.Error().Err(err).Msg("failed to write response") + } } } diff --git a/cmd/cloud-init-server/metadata_handlers.go b/cmd/cloud-init-server/metadata_handlers.go index 010b145c3..802a06b40 100644 --- a/cmd/cloud-init-server/metadata_handlers.go +++ b/cmd/cloud-init-server/metadata_handlers.go @@ -100,6 +100,8 @@ func MetaDataHandler(smd smdclient.SMDClientInterface, store cistore.Store) http http.Error(w, "Failed to encode metadata to YAML", http.StatusInternalServerError) return } - _, _ = w.Write(yamlData) // Not checking error on Write because we're wouldn't do anything about it anyway + if _, err = w.Write(yamlData); err != nil { + log.Error().Err(err).Msg("failed to write response") + } } } diff --git a/cmd/cloud-init-server/userdata_handlers.go b/cmd/cloud-init-server/userdata_handlers.go index 6a4abc42f..ce1acf47c 100644 --- a/cmd/cloud-init-server/userdata_handlers.go +++ b/cmd/cloud-init-server/userdata_handlers.go @@ -27,7 +27,9 @@ import ( // @Router /cloud-init/admin/impersonation/{id}/user-data [get] func UserDataHandler(w http.ResponseWriter, r *http.Request) { payload := `#cloud-config` - _, _ = w.Write([]byte(payload)) // Not checking error on Write because we're wouldn't do anything about it anyway + if _, err := w.Write([]byte(payload)); err != nil { + log.Error().Err(err).Msg("failed to write response") + } } // GroupUserDataHandler godoc @@ -62,7 +64,9 @@ func GroupUserDataHandler(smd smdclient.SMDClientInterface, store cistore.Store) data, err := store.GetGroupData(group) if err != nil { log.Err(err).Msgf("No information stored for group %s. returning an empty #cloud-config", group) - _, _ = w.Write([]byte("#cloud-config")) // Not checking error on Write because we're wouldn't do anything about it anyway + if _, err2 := w.Write([]byte("#cloud-config")); err2 != nil { + log.Error().Err(err).Msg("failed to write response") + } return } @@ -78,7 +82,9 @@ func GroupUserDataHandler(smd smdclient.SMDClientInterface, store cistore.Store) data.File.Encoding = "plain" } - _, _ = w.Write(data.File.Content) // Not checking error on Write because we're wouldn't do anything about it anyway + if _, err = w.Write(data.File.Content); err != nil { + log.Error().Err(err).Msg("failed to write response") + } } } diff --git a/cmd/cloud-init-server/vendordata_handlers.go b/cmd/cloud-init-server/vendordata_handlers.go index 3e2a36873..53bdc9201 100644 --- a/cmd/cloud-init-server/vendordata_handlers.go +++ b/cmd/cloud-init-server/vendordata_handlers.go @@ -70,6 +70,8 @@ func VendorDataHandler(smd smdclient.SMDClientInterface, store cistore.Store) ht for _, group_name := range groups { payload += fmt.Sprintf("%s/%s.yaml\n", baseUrl, group_name) } - _, _ = w.Write([]byte(payload)) // Not checking error on Write because we're wouldn't do anything about it anyway + if _, err = w.Write([]byte(payload)); err != nil { + log.Error().Err(err).Msg("failed to write response") + } } } diff --git a/cmd/cloud-init-server/version.go b/cmd/cloud-init-server/version.go index b40cc276d..66b7f42ac 100644 --- a/cmd/cloud-init-server/version.go +++ b/cmd/cloud-init-server/version.go @@ -8,6 +8,8 @@ import ( "path/filepath" "runtime" "time" + + "github.com/rs/zerolog/log" ) // GitCommit stores the latest Git commit hash. @@ -104,5 +106,7 @@ func VersionHandler(w http.ResponseWriter, r *http.Request) { BytesAllocated: memstats.HeapAlloc, } - _ = json.NewEncoder(w).Encode(response) // Not checking error on Encode because we're wouldn't do anything about it anyway + if err := json.NewEncoder(w).Encode(response); err != nil { + log.Error().Err(err).Msg("failed to encode response") + } } diff --git a/internal/smdclient/FakeSMDClient.go b/internal/smdclient/FakeSMDClient.go index 63e54761c..e3f6d6b88 100644 --- a/internal/smdclient/FakeSMDClient.go +++ b/internal/smdclient/FakeSMDClient.go @@ -123,7 +123,10 @@ func (f *FakeSMDClient) ComponentInformation(id string) (base.Component, error) log.Debug().Msgf("FakeSMDClient: %d components from %s to %s", len(f.components), f.rosetta_mapping[0].ComponentID, f.rosetta_mapping[len(f.rosetta_mapping)-1].ComponentID) if c, ok := f.components[id]; ok { log.Debug().Msgf("FakeSMDClient: ComponentInformation(%s) found", id) - groups, _ := f.GroupMembership(id) // ignoring error because it can't really happen + groups, err := f.GroupMembership(id) + if err != nil { + log.Error().Err(err).Msg("failed to get group membership by ID") + } log.Debug().Msgf("FakeSMDClient: Groups for %s found: %v", id, groups) return c, nil } diff --git a/internal/smdclient/FakeSMDClient_handlers.go b/internal/smdclient/FakeSMDClient_handlers.go index 8eee3dec8..4a57a938e 100644 --- a/internal/smdclient/FakeSMDClient_handlers.go +++ b/internal/smdclient/FakeSMDClient_handlers.go @@ -46,8 +46,7 @@ func ListNodesHandler(f *FakeSMDClient) http.HandlerFunc { nodes := f.ListNodes() w.WriteHeader(http.StatusOK) w.Header().Add("Content-Type", "application/json") - err := json.NewEncoder(w).Encode(nodes) - if err != nil { + if err := json.NewEncoder(w).Encode(nodes); err != nil { log.Error().Err(err).Msg("Failed to encode nodes") http.Error(w, err.Error(), http.StatusInternalServerError) return diff --git a/internal/smdclient/SMDclient.go b/internal/smdclient/SMDclient.go index df85e9bcc..505cc38bb 100644 --- a/internal/smdclient/SMDclient.go +++ b/internal/smdclient/SMDclient.go @@ -183,7 +183,14 @@ func (s *SMDClient) getSMD(ep string, smd interface{}) error { if !freshToken { log.Info().Msg("Fetching new JWT and retrying...") // Try to refresh the token and retry once - _ = s.RefreshToken() // ignoring error because we will try again and fail if it doesn't work + if err2 := s.RefreshToken(); err2 != nil { + // If token refresh fails, refresh will attempt again. + // While effectively we could ignore the error, it helps + // to see why the failure is occurring in case the error + // is unusual (RefreshToken() has a few different failure + // modes). + log.Debug().Err(err).Msg("failed to refresh token") + } freshToken = true } else { log.Info().Msg("SMD authentication failed, even with a fresh" + diff --git a/internal/smdclient/SMDclient_test.go b/internal/smdclient/SMDclient_test.go index fc091ceee..9fe046175 100644 --- a/internal/smdclient/SMDclient_test.go +++ b/internal/smdclient/SMDclient_test.go @@ -17,8 +17,7 @@ func TestPopulateNodes(t *testing.T) { assert.Equal(t, "/hsm/v2/Inventory/EthernetInterfaces/", r.URL.Path) w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - // ignoring error on Write - _, _ = w.Write([]byte(`[ + if _, err := w.Write([]byte(`[ { "ComponentID": "x1000", "MACAddress": "00:11:22:33:44:55", @@ -49,7 +48,10 @@ func TestPopulateNodes(t *testing.T) { "IPAddresses": [{"IPAddr": "192.168.1.6"}], "Description": "Test Node 4 Interface 2" } - ]`)) + ]`)); err != nil { + // If an error occurs here, something is very wrong. + t.Errorf("Write(): unexpected error: %v", err) + } }) server := httptest.NewServer(handler) defer server.Close() @@ -105,8 +107,7 @@ func TestIPfromID(t *testing.T) { assert.Equal(t, "/hsm/v2/Inventory/EthernetInterfaces/", r.URL.Path) w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - // ignoring error on Write - _, _ = w.Write([]byte(`[ + if _, err := w.Write([]byte(`[ { "ComponentID": "x1000", "MACAddress": "00:11:22:33:44:55", @@ -137,7 +138,10 @@ func TestIPfromID(t *testing.T) { "IPAddresses": [{"IPAddr": "192.168.1.6"}], "Description": "Test Node 4 Interface 2" } - ]`)) + ]`)); err != nil { + // If an error occurs here, something is very wrong. + t.Errorf("Write(): unexpected error: %v", err) + } }) server := httptest.NewServer(handler) defer server.Close() @@ -185,8 +189,7 @@ func TestIDfromIP(t *testing.T) { assert.Equal(t, "/hsm/v2/Inventory/EthernetInterfaces/", r.URL.Path) w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - // ignoring error on Write - _, _ = w.Write([]byte(`[ + if _, err := w.Write([]byte(`[ { "ComponentID": "x1000", "MACAddress": "00:11:22:33:44:55", @@ -217,7 +220,10 @@ func TestIDfromIP(t *testing.T) { "IPAddresses": [{"IPAddr": "192.168.1.6"}], "Description": "Test Node 4 Interface 2" } - ]`)) + ]`)); err != nil { + // If an error occurs here, something is very wrong. + t.Errorf("Write(): unexpected error: %v", err) + } }) server := httptest.NewServer(handler) defer server.Close() @@ -265,8 +271,7 @@ func TestIDfromMAC(t *testing.T) { assert.Equal(t, "/hsm/v2/Inventory/EthernetInterfaces/", r.URL.Path) w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - // ignoring error on Write - _, _ = w.Write([]byte(`[ + if _, err := w.Write([]byte(`[ { "ComponentID": "x1000", "MACAddress": "00:11:22:33:44:55", @@ -297,7 +302,10 @@ func TestIDfromMAC(t *testing.T) { "IPAddresses": [{"IPAddr": "192.168.1.6"}], "Description": "Test Node 4 Interface 2" } - ]`)) + ]`)); err != nil { + // If an error occurs here, something is very wrong. + t.Errorf("Write(): unexpected error: %v", err) + } }) server := httptest.NewServer(handler) defer server.Close() diff --git a/pkg/wgtunnel/tunnels.go b/pkg/wgtunnel/tunnels.go index 8a95796a3..1e5aff1b4 100644 --- a/pkg/wgtunnel/tunnels.go +++ b/pkg/wgtunnel/tunnels.go @@ -178,7 +178,7 @@ func (m *InterfaceManager) StartServer() error { // Step 2: Assign IP address to the WireGuard interface wgIp := m.ipAddress.IP.String() - ones, _ := m.network.Mask.Size() // ignoring error on Mask.Size() as we know it's valid + ones, _ := m.network.Mask.Size() // we don't care about the number of bits in the mask wgCidr := fmt.Sprintf("%s/%d", wgIp, ones) if out, err := exec.Command("ip", "address", "add", "dev", m.interfaceName, wgCidr).CombinedOutput(); err != nil { From 812c0b530b22e7e0f08cd8d5f0d347c00bd60053 Mon Sep 17 00:00:00 2001 From: Alex Lovell-Troy Date: Thu, 18 Sep 2025 15:09:25 -0400 Subject: [PATCH 10/12] Refactor Dockerfile to better handle DUCKDB_HOME for installation of plugins Signed-off-by: Alex Lovell-Troy --- Dockerfile | 54 ++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 40 insertions(+), 14 deletions(-) diff --git a/Dockerfile b/Dockerfile index b1bd730e5..22762a3c4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,25 +1,51 @@ -FROM ubuntu:24.04 +# syntax=docker/dockerfile:1.7 +FROM ubuntu:24.04 AS duckdb-ext +ARG TARGETARCH +ARG DUCKDB_VERSION="1.1.3" + +RUN apt-get update \ + && apt-get install -y --no-install-recommends ca-certificates curl unzip \ + && rm -rf /var/lib/apt/lists/* + +# Map TARGETARCH -> DuckDB CLI artifact name +# amd64 => linux-amd64, arm64 => linux-aarch64 +RUN case "${TARGETARCH}" in \ + amd64) CLI_URL="https://github.com/duckdb/duckdb/releases/download/v${DUCKDB_VERSION}/duckdb_cli-linux-amd64.zip" ;; \ + arm64) CLI_URL="https://github.com/duckdb/duckdb/releases/download/v${DUCKDB_VERSION}/duckdb_cli-linux-aarch64.zip" ;; \ + *) echo "unsupported arch: ${TARGETARCH}" && exit 1 ;; \ + esac \ + && curl -fsSL "$CLI_URL" -o /tmp/duckdb.zip \ + && unzip /tmp/duckdb.zip -d /usr/local/bin \ + && chmod +x /usr/local/bin/duckdb \ + && rm -f /tmp/duckdb.zip -# Optional: slim this down a bit -RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates wireguard-tools tini\ - && rm -rf /var/lib/apt/lists/* +ENV DUCKDB_HOME=/duckdb_home +RUN mkdir -p "$DUCKDB_HOME" + +# Preinstall whatever you need +RUN duckdb -c "INSTALL 'json';" \ + && duckdb -c "INSTALL 'parquet';" + +# -------- runtime image -------- +FROM ubuntu:24.04 +RUN apt-get update \ + && apt-get install -y --no-install-recommends ca-certificates wireguard-tools tini \ + && rm -rf /var/lib/apt/lists/* -# Copy binary from builder COPY cloud-init-server /usr/local/bin/cloud-init-server -# Configuration via environment variables +ENV DUCKDB_HOME=/opt/duckdbhome +RUN mkdir -p "$DUCKDB_HOME" && chown 65534:65534 "$DUCKDB_HOME" + +# Copy the preinstalled extensions (built for the current arch) +COPY --from=duckdb-ext /duckdb_home/ ${DUCKDB_HOME}/ +RUN chown -R 65534:65534 ${DUCKDB_HOME} + ENV TOKEN_URL="http://opaal:3333/token" ENV SMD_URL="http://smd:27779" ENV LISTEN="0.0.0.0:27777" -RUN mkdir -p /tmp/duckdbhome && chown 65534:65534 /tmp/duckdbhome -VOLUME /tmp/duckdbhome -ENV DUCKDB_HOME="/tmp/duckdbhome" - - -# Set non-root user USER 65534:65534 - -# Tini for proper signal forwarding ENTRYPOINT ["/usr/bin/tini", "--"] CMD ["/usr/local/bin/cloud-init-server"] + From 264e3967c5b38adc14304c2b50f10422a82b176a Mon Sep 17 00:00:00 2001 From: Alex Lovell-Troy Date: Fri, 19 Sep 2025 13:13:08 -0400 Subject: [PATCH 11/12] feat: update DuckDB version to 1.4.0 and adjust architecture handling in Dockerfile fix: bump quack dependency version to v0.0.3 in go.mod and go.sum Signed-off-by: Alex Lovell-Troy --- Dockerfile | 28 +++++++++++++++++++++------- go.mod | 2 +- go.sum | 4 ++-- 3 files changed, 24 insertions(+), 10 deletions(-) diff --git a/Dockerfile b/Dockerfile index 22762a3c4..d961c8ca7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,18 +1,19 @@ # syntax=docker/dockerfile:1.7 FROM ubuntu:24.04 AS duckdb-ext ARG TARGETARCH -ARG DUCKDB_VERSION="1.1.3" +ARG DUCKDB_VERSION="1.4.0" RUN apt-get update \ && apt-get install -y --no-install-recommends ca-certificates curl unzip \ && rm -rf /var/lib/apt/lists/* -# Map TARGETARCH -> DuckDB CLI artifact name -# amd64 => linux-amd64, arm64 => linux-aarch64 -RUN case "${TARGETARCH}" in \ - amd64) CLI_URL="https://github.com/duckdb/duckdb/releases/download/v${DUCKDB_VERSION}/duckdb_cli-linux-amd64.zip" ;; \ - arm64) CLI_URL="https://github.com/duckdb/duckdb/releases/download/v${DUCKDB_VERSION}/duckdb_cli-linux-aarch64.zip" ;; \ - *) echo "unsupported arch: ${TARGETARCH}" && exit 1 ;; \ +# Map TARGETARCH (docker buildx) or uname -m to DuckDB CLI artifact name +# amd64|x86_64 => linux-amd64, arm64|aarch64 => linux-arm64 (v1.4.0 release assets) +RUN ARCH="${TARGETARCH:-$(uname -m)}" \ + && case "${ARCH}" in \ + amd64|x86_64) CLI_URL="https://github.com/duckdb/duckdb/releases/download/v${DUCKDB_VERSION}/duckdb_cli-linux-amd64.zip" ;; \ + arm64|aarch64) CLI_URL="https://github.com/duckdb/duckdb/releases/download/v${DUCKDB_VERSION}/duckdb_cli-linux-arm64.zip" ;; \ + *) echo "unsupported arch: ${ARCH}" && exit 1 ;; \ esac \ && curl -fsSL "$CLI_URL" -o /tmp/duckdb.zip \ && unzip /tmp/duckdb.zip -d /usr/local/bin \ @@ -22,6 +23,7 @@ RUN case "${TARGETARCH}" in \ ENV DUCKDB_HOME=/duckdb_home RUN mkdir -p "$DUCKDB_HOME" + # Preinstall whatever you need RUN duckdb -c "INSTALL 'json';" \ && duckdb -c "INSTALL 'parquet';" @@ -34,6 +36,12 @@ RUN apt-get update \ COPY cloud-init-server /usr/local/bin/cloud-init-server +#Set the golang modcache +ENV GOMODCACHE=/tmp/.cache +RUN mkdir -p "$GOMODCACHE" && chown 65534:65534 "$GOMODCACHE" +ENV XDG_CACHE_HOME=/tmp/.cache + + ENV DUCKDB_HOME=/opt/duckdbhome RUN mkdir -p "$DUCKDB_HOME" && chown 65534:65534 "$DUCKDB_HOME" @@ -41,6 +49,12 @@ RUN mkdir -p "$DUCKDB_HOME" && chown 65534:65534 "$DUCKDB_HOME" COPY --from=duckdb-ext /duckdb_home/ ${DUCKDB_HOME}/ RUN chown -R 65534:65534 ${DUCKDB_HOME} +# Store data in $DATADIR +ENV DATADIR=/data +RUN mkdir -p "${DATADIR}" +VOLUME "$DATADIR" +RUN chown -R 65534:65534 ${DATADIR} + ENV TOKEN_URL="http://opaal:3333/token" ENV SMD_URL="http://smd:27779" ENV LISTEN="0.0.0.0:27777" diff --git a/go.mod b/go.mod index dd1da0ce8..8d1b5f6be 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.24.0 require ( github.com/OpenCHAMI/jwtauth/v5 v5.0.0-20240321222802-e6cb468a2a18 - github.com/OpenCHAMI/quack v0.0.2 + github.com/OpenCHAMI/quack v0.0.3 github.com/OpenCHAMI/smd/v2 v2.18.0 github.com/go-chi/chi/v5 v5.2.3 github.com/lestrrat-go/jwx/v2 v2.1.6 diff --git a/go.sum b/go.sum index 6e407f424..6635a2168 100644 --- a/go.sum +++ b/go.sum @@ -12,8 +12,8 @@ github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= github.com/OpenCHAMI/jwtauth/v5 v5.0.0-20240321222802-e6cb468a2a18 h1:oBPtXp9RVm9lk5zTmDLf+Vh21yDHpulBxUqGJQjwQCk= github.com/OpenCHAMI/jwtauth/v5 v5.0.0-20240321222802-e6cb468a2a18/go.mod h1:ggNHWgLfW/WRXcE8ZZC4S7UwHif16HVmyowOCWdNSN8= -github.com/OpenCHAMI/quack v0.0.2 h1:N7f4Qs3Mgmaa6kxd0aY5qkWb3w6ssNJo92CD5nftUuw= -github.com/OpenCHAMI/quack v0.0.2/go.mod h1:7vGD4KHpFhQKR8G6mFXFZHhfhaP4c/V7sUXWImTXChI= +github.com/OpenCHAMI/quack v0.0.3 h1:Fov8t+t64hFtBIyU0rPILZ6N8bJ6sWqZ0Sg956/3WcM= +github.com/OpenCHAMI/quack v0.0.3/go.mod h1:7vGD4KHpFhQKR8G6mFXFZHhfhaP4c/V7sUXWImTXChI= github.com/OpenCHAMI/smd/v2 v2.18.0 h1:RZ7lq+zkjZnrnc3McLJANnvoU/T7P8wcCFEukWp0FlQ= github.com/OpenCHAMI/smd/v2 v2.18.0/go.mod h1:RtFmMtTYmMUc74gw4FnfZKDpFMTQJeemAVXB0O+Qaa0= github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ= From ebf96e54c3cf84ca5bbde02ea93b13a20b086c15 Mon Sep 17 00:00:00 2001 From: Devon Bautista <17506592+synackd@users.noreply.github.com> Date: Fri, 19 Sep 2025 13:22:38 -0600 Subject: [PATCH 12/12] docs: remove /cloud-init base path Signed-off-by: Devon Bautista <17506592+synackd@users.noreply.github.com> --- cmd/cloud-init-server/group_handlers.go | 10 +++--- cmd/cloud-init-server/handlers.go | 10 +++--- cmd/cloud-init-server/metadata_handlers.go | 4 +-- cmd/cloud-init-server/userdata_handlers.go | 8 ++--- cmd/cloud-init-server/vendordata_handlers.go | 4 +-- docs/docs.go | 32 ++++++++++---------- docs/swagger.json | 32 ++++++++++---------- docs/swagger.yaml | 32 ++++++++++---------- pkg/wgtunnel/handlers.go | 2 +- 9 files changed, 67 insertions(+), 67 deletions(-) diff --git a/cmd/cloud-init-server/group_handlers.go b/cmd/cloud-init-server/group_handlers.go index 8df016a05..ca0e265b2 100644 --- a/cmd/cloud-init-server/group_handlers.go +++ b/cmd/cloud-init-server/group_handlers.go @@ -18,7 +18,7 @@ import ( // @Produce json // @Success 200 {object} map[string]cistore.ClusterDefaults // @Failure 500 {object} nil -// @Router /cloud-init/admin/groups [get] +// @Router /admin/groups [get] func (h CiHandler) GetGroups(w http.ResponseWriter, r *http.Request) { var ( groups map[string]cistore.GroupData @@ -58,7 +58,7 @@ func (h CiHandler) GetGroups(w http.ResponseWriter, r *http.Request) { // @Failure 422 {object} nil // @Header 201 {string} Location "/groups/{id}" // @Param group body cistore.GroupData true "Group data" -// @Router /cloud-init/admin/groups [post] +// @Router /admin/groups [post] func (h CiHandler) AddGroupHandler(w http.ResponseWriter, r *http.Request) { var ( data cistore.GroupData @@ -91,7 +91,7 @@ func (h CiHandler) AddGroupHandler(w http.ResponseWriter, r *http.Request) { // @Success 200 {object} cistore.GroupData // @Failure 500 {object} nil // @Param id path string true "Group ID" -// @Router /cloud-init/admin/groups/{id} [get] +// @Router /admin/groups/{id} [get] func (h CiHandler) GetGroupHandler(w http.ResponseWriter, r *http.Request) { var ( id string @@ -136,7 +136,7 @@ func (h CiHandler) GetGroupHandler(w http.ResponseWriter, r *http.Request) { // @Header 201 {string} Location "/groups/{name}" // @Param name path string true "Group name" // @Param group_data body cistore.GroupData true "Group data" -// @Router /cloud-init/admin/groups/{name} [put] +// @Router /admin/groups/{name} [put] func (h CiHandler) UpdateGroupHandler(w http.ResponseWriter, r *http.Request) { var ( groupName string @@ -170,7 +170,7 @@ func (h CiHandler) UpdateGroupHandler(w http.ResponseWriter, r *http.Request) { // @Success 200 {object} nil // @Failure 500 {object} nil // @Param id path string true "Group ID" -// @Router /cloud-init/admin/groups/{id} [delete] +// @Router /admin/groups/{id} [delete] func (h CiHandler) RemoveGroupHandler(w http.ResponseWriter, r *http.Request) { var ( id string diff --git a/cmd/cloud-init-server/handlers.go b/cmd/cloud-init-server/handlers.go index 67184b408..6ed501421 100644 --- a/cmd/cloud-init-server/handlers.go +++ b/cmd/cloud-init-server/handlers.go @@ -53,7 +53,7 @@ func parseData(r *http.Request) (cistore.GroupData, error) { // @Produce json // @Success 200 {object} string // @Failure 500 {object} nil -// @Router /cloud-init/openapi.json [get] +// @Router /openapi.json [get] func DocsHandler(w http.ResponseWriter, r *http.Request) { doc, err := swag.ReadDoc() if err != nil { @@ -79,7 +79,7 @@ func DocsHandler(w http.ResponseWriter, r *http.Request) { // @Failure 400 {object} nil // @Failure 500 {object} nil // @Param data body cistore.ClusterDefaults true "Cluster defaults data" -// @Router /cloud-init/admin/cluster-defaults [post] +// @Router /admin/cluster-defaults [post] func SetClusterDataHandler(store cistore.Store) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { @@ -117,7 +117,7 @@ func SetClusterDataHandler(store cistore.Store) http.HandlerFunc { // @Produce json // @Success 200 {object} cistore.ClusterDefaults // @Failure 500 {object} nil -// @Router /cloud-init/admin/cluster-defaults [get] +// @Router /admin/cluster-defaults [get] func GetClusterDataHandler(store cistore.Store) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { data, err := store.GetClusterDefaults() @@ -153,7 +153,7 @@ func GetClusterDataHandler(store cistore.Store) http.HandlerFunc { // @Failure 500 {object} nil // @Param id path string true "Node ID" // @Param instance-info body cistore.OpenCHAMIInstanceInfo true "Instance info data" -// @Router /cloud-init/admin/instance-info/{id} [put] +// @Router /admin/instance-info/{id} [put] func InstanceInfoHandler(sm smdclient.SMDClientInterface, store cistore.Store) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPut { @@ -206,7 +206,7 @@ func InstanceInfoHandler(sm smdclient.SMDClientInterface, store cistore.Store) h // @Param instance_id formData string true "Node's given instance ID" // @Param hostname formData string true "Node's given hostname" // @Param fqdn formData string true "Node's given fully-qualified domain name" -// @Router /cloud-init/phone-home/{id} [post] +// @Router /phone-home/{id} [post] func PhoneHomeHandler(wg *wgtunnel.InterfaceManager, sm smdclient.SMDClientInterface) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { diff --git a/cmd/cloud-init-server/metadata_handlers.go b/cmd/cloud-init-server/metadata_handlers.go index 802a06b40..496c85554 100644 --- a/cmd/cloud-init-server/metadata_handlers.go +++ b/cmd/cloud-init-server/metadata_handlers.go @@ -42,8 +42,8 @@ func getActualRequestIP(r *http.Request) string { // @Failure 422 {object} nil // @Failure 500 {object} nil // @Param id path string false "Node ID" -// @Router /cloud-init/meta-data [get] -// @Router /cloud-init/admin/impersonation/{id}/meta-data [get] +// @Router /meta-data [get] +// @Router /admin/impersonation/{id}/meta-data [get] func MetaDataHandler(smd smdclient.SMDClientInterface, store cistore.Store) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { urlId := chi.URLParam(r, "id") diff --git a/cmd/cloud-init-server/userdata_handlers.go b/cmd/cloud-init-server/userdata_handlers.go index ce1acf47c..1dcbef173 100644 --- a/cmd/cloud-init-server/userdata_handlers.go +++ b/cmd/cloud-init-server/userdata_handlers.go @@ -23,8 +23,8 @@ import ( // @Produce plain // @Success 200 {object} string // @Param id path string false "Node ID" -// @Router /cloud-init/user-data [get] -// @Router /cloud-init/admin/impersonation/{id}/user-data [get] +// @Router /user-data [get] +// @Router /admin/impersonation/{id}/user-data [get] func UserDataHandler(w http.ResponseWriter, r *http.Request) { payload := `#cloud-config` if _, err := w.Write([]byte(payload)); err != nil { @@ -46,8 +46,8 @@ func UserDataHandler(w http.ResponseWriter, r *http.Request) { // @Failure 500 {object} nil // @Param id path string false "Node ID" // @Param group path string true "Group name" -// @Router /cloud-init/{group}.yaml [get] -// @Router /cloud-init/admin/impersonation/{id}/{group}.yaml [get] +// @Router /{group}.yaml [get] +// @Router /admin/impersonation/{id}/{group}.yaml [get] func GroupUserDataHandler(smd smdclient.SMDClientInterface, store cistore.Store) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { id, group, err := getIDAndGroup(r, smd) diff --git a/cmd/cloud-init-server/vendordata_handlers.go b/cmd/cloud-init-server/vendordata_handlers.go index 53bdc9201..9b9e0f3b6 100644 --- a/cmd/cloud-init-server/vendordata_handlers.go +++ b/cmd/cloud-init-server/vendordata_handlers.go @@ -22,8 +22,8 @@ import ( // @Produce plain // @Success 200 {string} string // @Param id path string false "Node ID" -// @Router /cloud-init/vendor-data [get] -// @Router /cloud-init/admin/impersonation/{id}/vendor-data [get] +// @Router /vendor-data [get] +// @Router /admin/impersonation/{id}/vendor-data [get] func VendorDataHandler(smd smdclient.SMDClientInterface, store cistore.Store) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { urlId := chi.URLParam(r, "id") diff --git a/docs/docs.go b/docs/docs.go index fa047878a..3426c593c 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -19,7 +19,7 @@ const docTemplate = `{ "host": "{{.Host}}", "basePath": "{{.BasePath}}", "paths": { - "/cloud-init/admin/cluster-defaults": { + "/admin/cluster-defaults": { "get": { "description": "Get default meta-data values for cluster.", "produces": [ @@ -76,7 +76,7 @@ const docTemplate = `{ } } }, - "/cloud-init/admin/groups": { + "/admin/groups": { "get": { "description": "Get meta-data and cloud-init config for all groups known to\ncloud-init. Note that group membership is managed outside of\nthe cloud-init service, normally in SMD.", "produces": [ @@ -142,7 +142,7 @@ const docTemplate = `{ } } }, - "/cloud-init/admin/groups/{id}": { + "/admin/groups/{id}": { "get": { "description": "Get meta-data and cloud-init config for a single group known to\ncloud-init.", "produces": [ @@ -200,7 +200,7 @@ const docTemplate = `{ } } }, - "/cloud-init/admin/groups/{name}": { + "/admin/groups/{name}": { "put": { "description": "Set meta-data or cloud-init configuration for a specific group,\noverwriting any previous values.\n\nIf successful, a 201 Created status is returned and the\n` + "`" + `Location` + "`" + ` header is set to the new group's groups endpoint,\n` + "`" + `/groups/{group}` + "`" + `. This operation is idempotent and replaces\nany existing content.", "consumes": [ @@ -248,7 +248,7 @@ const docTemplate = `{ } } }, - "/cloud-init/admin/impersonation/{id}/meta-data": { + "/admin/impersonation/{id}/meta-data": { "get": { "description": "Get meta-data for requesting node based on the requesting IP.\n\nIf the impersonation API is enabled, an ID can be provided in\nthe URL path using ` + "`" + `/admin/impersonation` + "`" + `. In this case, the\nmeta-data will be retrieved for the requested ID.", "produces": [ @@ -282,7 +282,7 @@ const docTemplate = `{ } } }, - "/cloud-init/admin/impersonation/{id}/user-data": { + "/admin/impersonation/{id}/user-data": { "get": { "description": "Get user-data for requesting node base on the requesting IP. For\nOpenCHAMI, this will always be ` + "`" + `#cloud-config` + "`" + `.\n\nIf the impersonation API is enabled, an ID can be provided in\nthe URL path using ` + "`" + `/admin/impersonation` + "`" + `. In this case, the\nuser-data will be retrieved for the requested ID.", "produces": [ @@ -307,7 +307,7 @@ const docTemplate = `{ } } }, - "/cloud-init/admin/impersonation/{id}/vendor-data": { + "/admin/impersonation/{id}/vendor-data": { "get": { "description": "For OpenCHAMI, the vendor-data will always be a list of other\n` + "`" + `#cloud-config` + "`" + ` URLs to download and merge.\n\nIf the impersonation API is enabled, an ID can be provided in\nthe URL path using ` + "`" + `/admin/impersonation` + "`" + `. In this case, the\nvendor-data will be retrieved for the requested ID.", "produces": [ @@ -332,7 +332,7 @@ const docTemplate = `{ } } }, - "/cloud-init/admin/impersonation/{id}/{group}.yaml": { + "/admin/impersonation/{id}/{group}.yaml": { "get": { "description": "Get user-data for a particular group based on its name.\n\nIf the impersonation API is enabled, an ID can be provided in\nthe URL path using ` + "`" + `/admin/impersonation` + "`" + `. In this case, the\ngroup user-data will be retrieved for the requested ID.", "produces": [ @@ -370,7 +370,7 @@ const docTemplate = `{ } } }, - "/cloud-init/admin/instance-info/{id}": { + "/admin/instance-info/{id}": { "put": { "description": "Set meta-data for a specific node ID, overwriting relevant group meta-data.", "consumes": [ @@ -412,7 +412,7 @@ const docTemplate = `{ } } }, - "/cloud-init/meta-data": { + "/meta-data": { "get": { "description": "Get meta-data for requesting node based on the requesting IP.\n\nIf the impersonation API is enabled, an ID can be provided in\nthe URL path using ` + "`" + `/admin/impersonation` + "`" + `. In this case, the\nmeta-data will be retrieved for the requested ID.", "produces": [ @@ -438,7 +438,7 @@ const docTemplate = `{ } } }, - "/cloud-init/openapi.json": { + "/openapi.json": { "get": { "produces": [ "application/json" @@ -457,7 +457,7 @@ const docTemplate = `{ } } }, - "/cloud-init/phone-home/{id}": { + "/phone-home/{id}": { "post": { "description": "Signal to the cloud-init server that the specific host has completed running\nthe cloud-init configuration tasks so that, if a WireGuard tunnel is being used,\nit can be torn down. This endpoint should not be manually requested by a user\nbut is only meant to be used by a cloud-init client that has received its\nconfig from an OpenCHAMI cloud-init server.", "tags": [ @@ -525,7 +525,7 @@ const docTemplate = `{ } } }, - "/cloud-init/user-data": { + "/user-data": { "get": { "description": "Get user-data for requesting node base on the requesting IP. For\nOpenCHAMI, this will always be ` + "`" + `#cloud-config` + "`" + `.\n\nIf the impersonation API is enabled, an ID can be provided in\nthe URL path using ` + "`" + `/admin/impersonation` + "`" + `. In this case, the\nuser-data will be retrieved for the requested ID.", "produces": [ @@ -542,7 +542,7 @@ const docTemplate = `{ } } }, - "/cloud-init/vendor-data": { + "/vendor-data": { "get": { "description": "For OpenCHAMI, the vendor-data will always be a list of other\n` + "`" + `#cloud-config` + "`" + ` URLs to download and merge.\n\nIf the impersonation API is enabled, an ID can be provided in\nthe URL path using ` + "`" + `/admin/impersonation` + "`" + `. In this case, the\nvendor-data will be retrieved for the requested ID.", "produces": [ @@ -559,7 +559,7 @@ const docTemplate = `{ } } }, - "/cloud-init/wg-init": { + "/wg-init": { "post": { "description": "Initiate a WireGuard tunnel from a client using its public key\nand peer name (IP address).\n\nThe source IP of the request is read and is used as the peer\nname along with the public key to authenticate unless the\n` + "`" + `X-Forward-For` + "`" + ` header is set. In that case, the value of the\nheader is used as the peer name. If the peer exists in the\ninternal tunnel manager, the IP presented is the one used.\nOtherwise, the next available IP in range is assigned.", "consumes": [ @@ -602,7 +602,7 @@ const docTemplate = `{ } } }, - "/cloud-init/{group}.yaml": { + "/{group}.yaml": { "get": { "description": "Get user-data for a particular group based on its name.\n\nIf the impersonation API is enabled, an ID can be provided in\nthe URL path using ` + "`" + `/admin/impersonation` + "`" + `. In this case, the\ngroup user-data will be retrieved for the requested ID.", "produces": [ diff --git a/docs/swagger.json b/docs/swagger.json index 8d7188a9a..da983b242 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -11,7 +11,7 @@ "version": "1.0.0" }, "paths": { - "/cloud-init/admin/cluster-defaults": { + "/admin/cluster-defaults": { "get": { "description": "Get default meta-data values for cluster.", "produces": [ @@ -68,7 +68,7 @@ } } }, - "/cloud-init/admin/groups": { + "/admin/groups": { "get": { "description": "Get meta-data and cloud-init config for all groups known to\ncloud-init. Note that group membership is managed outside of\nthe cloud-init service, normally in SMD.", "produces": [ @@ -134,7 +134,7 @@ } } }, - "/cloud-init/admin/groups/{id}": { + "/admin/groups/{id}": { "get": { "description": "Get meta-data and cloud-init config for a single group known to\ncloud-init.", "produces": [ @@ -192,7 +192,7 @@ } } }, - "/cloud-init/admin/groups/{name}": { + "/admin/groups/{name}": { "put": { "description": "Set meta-data or cloud-init configuration for a specific group,\noverwriting any previous values.\n\nIf successful, a 201 Created status is returned and the\n`Location` header is set to the new group's groups endpoint,\n`/groups/{group}`. This operation is idempotent and replaces\nany existing content.", "consumes": [ @@ -240,7 +240,7 @@ } } }, - "/cloud-init/admin/impersonation/{id}/meta-data": { + "/admin/impersonation/{id}/meta-data": { "get": { "description": "Get meta-data for requesting node based on the requesting IP.\n\nIf the impersonation API is enabled, an ID can be provided in\nthe URL path using `/admin/impersonation`. In this case, the\nmeta-data will be retrieved for the requested ID.", "produces": [ @@ -274,7 +274,7 @@ } } }, - "/cloud-init/admin/impersonation/{id}/user-data": { + "/admin/impersonation/{id}/user-data": { "get": { "description": "Get user-data for requesting node base on the requesting IP. For\nOpenCHAMI, this will always be `#cloud-config`.\n\nIf the impersonation API is enabled, an ID can be provided in\nthe URL path using `/admin/impersonation`. In this case, the\nuser-data will be retrieved for the requested ID.", "produces": [ @@ -299,7 +299,7 @@ } } }, - "/cloud-init/admin/impersonation/{id}/vendor-data": { + "/admin/impersonation/{id}/vendor-data": { "get": { "description": "For OpenCHAMI, the vendor-data will always be a list of other\n`#cloud-config` URLs to download and merge.\n\nIf the impersonation API is enabled, an ID can be provided in\nthe URL path using `/admin/impersonation`. In this case, the\nvendor-data will be retrieved for the requested ID.", "produces": [ @@ -324,7 +324,7 @@ } } }, - "/cloud-init/admin/impersonation/{id}/{group}.yaml": { + "/admin/impersonation/{id}/{group}.yaml": { "get": { "description": "Get user-data for a particular group based on its name.\n\nIf the impersonation API is enabled, an ID can be provided in\nthe URL path using `/admin/impersonation`. In this case, the\ngroup user-data will be retrieved for the requested ID.", "produces": [ @@ -362,7 +362,7 @@ } } }, - "/cloud-init/admin/instance-info/{id}": { + "/admin/instance-info/{id}": { "put": { "description": "Set meta-data for a specific node ID, overwriting relevant group meta-data.", "consumes": [ @@ -404,7 +404,7 @@ } } }, - "/cloud-init/meta-data": { + "/meta-data": { "get": { "description": "Get meta-data for requesting node based on the requesting IP.\n\nIf the impersonation API is enabled, an ID can be provided in\nthe URL path using `/admin/impersonation`. In this case, the\nmeta-data will be retrieved for the requested ID.", "produces": [ @@ -430,7 +430,7 @@ } } }, - "/cloud-init/openapi.json": { + "/openapi.json": { "get": { "produces": [ "application/json" @@ -449,7 +449,7 @@ } } }, - "/cloud-init/phone-home/{id}": { + "/phone-home/{id}": { "post": { "description": "Signal to the cloud-init server that the specific host has completed running\nthe cloud-init configuration tasks so that, if a WireGuard tunnel is being used,\nit can be torn down. This endpoint should not be manually requested by a user\nbut is only meant to be used by a cloud-init client that has received its\nconfig from an OpenCHAMI cloud-init server.", "tags": [ @@ -517,7 +517,7 @@ } } }, - "/cloud-init/user-data": { + "/user-data": { "get": { "description": "Get user-data for requesting node base on the requesting IP. For\nOpenCHAMI, this will always be `#cloud-config`.\n\nIf the impersonation API is enabled, an ID can be provided in\nthe URL path using `/admin/impersonation`. In this case, the\nuser-data will be retrieved for the requested ID.", "produces": [ @@ -534,7 +534,7 @@ } } }, - "/cloud-init/vendor-data": { + "/vendor-data": { "get": { "description": "For OpenCHAMI, the vendor-data will always be a list of other\n`#cloud-config` URLs to download and merge.\n\nIf the impersonation API is enabled, an ID can be provided in\nthe URL path using `/admin/impersonation`. In this case, the\nvendor-data will be retrieved for the requested ID.", "produces": [ @@ -551,7 +551,7 @@ } } }, - "/cloud-init/wg-init": { + "/wg-init": { "post": { "description": "Initiate a WireGuard tunnel from a client using its public key\nand peer name (IP address).\n\nThe source IP of the request is read and is used as the peer\nname along with the public key to authenticate unless the\n`X-Forward-For` header is set. In that case, the value of the\nheader is used as the peer name. If the peer exists in the\ninternal tunnel manager, the IP presented is the one used.\nOtherwise, the next available IP in range is assigned.", "consumes": [ @@ -594,7 +594,7 @@ } } }, - "/cloud-init/{group}.yaml": { + "/{group}.yaml": { "get": { "description": "Get user-data for a particular group based on its name.\n\nIf the impersonation API is enabled, an ID can be provided in\nthe URL path using `/admin/impersonation`. In this case, the\ngroup user-data will be retrieved for the requested ID.", "produces": [ diff --git a/docs/swagger.yaml b/docs/swagger.yaml index f0da644e4..6bfecceb4 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -206,7 +206,7 @@ info: title: OpenCHAMI Cloud-Init Server API version: 1.0.0 paths: - /cloud-init/{group}.yaml: + /{group}.yaml: get: description: |- Get user-data for a particular group based on its name. @@ -232,7 +232,7 @@ paths: "500": description: Internal Server Error summary: Get user-data for a particular group - /cloud-init/admin/cluster-defaults: + /admin/cluster-defaults: get: description: Get default meta-data values for cluster. produces: @@ -270,7 +270,7 @@ paths: tags: - admin - cluster-defaults - /cloud-init/admin/groups: + /admin/groups: get: description: |- Get meta-data and cloud-init config for all groups known to @@ -328,7 +328,7 @@ paths: tags: - admin - groups - /cloud-init/admin/groups/{id}: + /admin/groups/{id}: delete: description: Delete a group with its meta-data and cloud-init config. parameters: @@ -369,7 +369,7 @@ paths: tags: - admin - groups - /cloud-init/admin/groups/{name}: + /admin/groups/{name}: put: consumes: - application/json @@ -408,7 +408,7 @@ paths: tags: - admin - groups - /cloud-init/admin/impersonation/{id}/{group}.yaml: + /admin/impersonation/{id}/{group}.yaml: get: description: |- Get user-data for a particular group based on its name. @@ -438,7 +438,7 @@ paths: "500": description: Internal Server Error summary: Get user-data for a particular group - /cloud-init/admin/impersonation/{id}/meta-data: + /admin/impersonation/{id}/meta-data: get: description: |- Get meta-data for requesting node based on the requesting IP. @@ -465,7 +465,7 @@ paths: "500": description: Internal Server Error summary: Get meta-data for requesting node - /cloud-init/admin/impersonation/{id}/user-data: + /admin/impersonation/{id}/user-data: get: description: |- Get user-data for requesting node base on the requesting IP. For @@ -487,7 +487,7 @@ paths: schema: type: string summary: Get user-data for requesting node - /cloud-init/admin/impersonation/{id}/vendor-data: + /admin/impersonation/{id}/vendor-data: get: description: |- For OpenCHAMI, the vendor-data will always be a list of other @@ -509,7 +509,7 @@ paths: schema: type: string summary: Get vendor data - /cloud-init/admin/instance-info/{id}: + /admin/instance-info/{id}: put: consumes: - application/json @@ -538,7 +538,7 @@ paths: tags: - admin - instance-data - /cloud-init/meta-data: + /meta-data: get: description: |- Get meta-data for requesting node based on the requesting IP. @@ -560,7 +560,7 @@ paths: "500": description: Internal Server Error summary: Get meta-data for requesting node - /cloud-init/openapi.json: + /openapi.json: get: produces: - application/json @@ -572,7 +572,7 @@ paths: "500": description: Internal Server Error summary: Return JSON-formatted OpenAPI documentation - /cloud-init/phone-home/{id}: + /phone-home/{id}: post: description: |- Signal to the cloud-init server that the specific host has completed running @@ -625,7 +625,7 @@ paths: configuration tags: - phone-home - /cloud-init/user-data: + /user-data: get: description: |- Get user-data for requesting node base on the requesting IP. For @@ -642,7 +642,7 @@ paths: schema: type: string summary: Get user-data for requesting node - /cloud-init/vendor-data: + /vendor-data: get: description: |- For OpenCHAMI, the vendor-data will always be a list of other @@ -659,7 +659,7 @@ paths: schema: type: string summary: Get vendor data - /cloud-init/wg-init: + /wg-init: post: consumes: - application/json diff --git a/pkg/wgtunnel/handlers.go b/pkg/wgtunnel/handlers.go index b235310fb..60498477f 100644 --- a/pkg/wgtunnel/handlers.go +++ b/pkg/wgtunnel/handlers.go @@ -45,7 +45,7 @@ type WGResponse struct { // @Failure 500 {object} nil // @Param pubkey body PublicKeyRequest true "WireGuard public key of client" // @Param X-Forwarded-For header string false "Override source IP" -// @Router /cloud-init/wg-init [post] +// @Router /wg-init [post] func AddClientHandler(im *InterfaceManager, smdClient smdclient.SMDClientInterface) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost {