From 593a15417bb220974c1394cf55db3416febe858c Mon Sep 17 00:00:00 2001 From: Cristian Magherusan-Stanciu Date: Sun, 27 Sep 2026 10:54:07 +0200 Subject: [PATCH 1/4] build(deps): split from monorepo and pin published shared modules Standalone the CLI as its own repository and consume the published shared modules instead of local replacements. - module path: github.com/LeanerCloud/CUDly -> github.com/LeanerCloud/cloud-commitments-cli - go.mod: drop the unpublished v0.0.0 pins and the replace directives, and pin cloud-commitments-go/pkg v0.0.0-20260925082912-43ab778da7ac plus providers/{aws,azure,gcp} v0.0.0-20260926232454-692cacc627d1 - go.work: use only this module, so the checkout builds standalone The recorded go.mod versions (grpc 1.83.2, x/net 0.58.0) are unchanged from what MVS already selected through the local shared modules; the effective dependency graph is identical to the pre-pin baseline apart from the pins themselves, with no modules added or removed. Verified: make build, make vet, make test-unit (-race -short), a clean go mod tidy -diff, an effective-graph comparison against the pre-pin baseline, and the full pre-commit gate. --- .github/workflows/ci.yml | 670 +----------------- .github/workflows/pre-commit.yml | 122 +--- .pre-commit-config.yaml | 13 +- Makefile | 229 +------ README.md | 757 +-------------------- cmd/helpers.go | 4 +- cmd/helpers_count_override.go | 2 +- cmd/helpers_count_override_rescale_test.go | 2 +- cmd/helpers_instance_limit_rescale_test.go | 2 +- cmd/helpers_test.go | 2 +- cmd/helpers_typed_nil_details_test.go | 2 +- cmd/main.go | 26 +- cmd/main_test.go | 2 +- cmd/multi_service.go | 12 +- cmd/multi_service_coverage_test.go | 2 +- cmd/multi_service_csv.go | 4 +- cmd/multi_service_csv_cap.go | 4 +- cmd/multi_service_csv_test.go | 2 +- cmd/multi_service_engine_versions.go | 2 +- cmd/multi_service_engine_versions_test.go | 2 +- cmd/multi_service_filters.go | 6 +- cmd/multi_service_filters_test.go | 2 +- cmd/multi_service_helpers.go | 8 +- cmd/multi_service_helpers_test.go | 2 +- cmd/multi_service_max_instances_test.go | 2 +- cmd/multi_service_stats.go | 2 +- cmd/multi_service_stats_helpers.go | 2 +- cmd/multi_service_stats_test.go | 2 +- cmd/multi_service_test.go | 4 +- cmd/multi_service_test_common_test.go | 2 +- cmd/validators.go | 2 +- cmd/validators_test.go | 2 +- go.mod | 135 +--- go.sum | 207 +----- go.work | 9 +- scripts/gofmt-hook.sh | 45 ++ scripts/gosec-hook.sh | 8 +- 37 files changed, 272 insertions(+), 2029 deletions(-) create mode 100644 scripts/gofmt-hook.sh diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ccfb791..6a0851d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -27,11 +27,8 @@ on: env: GO_VERSION: '1.26.6' - DOCKER_BUILDKIT: 1 - COMPOSE_DOCKER_CLI_BUILD: 1 jobs: - # Go code linting lint: name: Lint Code runs-on: ubuntu-latest @@ -46,7 +43,7 @@ jobs: - name: Set up Go uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 with: - go-version: ${{ env.GO_VERSION }} + go-version-file: go.mod cache: true - name: Run golangci-lint @@ -78,22 +75,6 @@ jobs: fi echo "✅ All functions have acceptable cyclomatic complexity (≤10)" - # GitHub Actions workflow linting - # - # Nothing else in this repo reads .github/workflows/ for defects. govulncheck - # and gosec are Go source scanners, trivy-config targets Terraform/Dockerfile/ - # Kubernetes, and check-yaml only proves the YAML parses. That gap is why the - # rollback.yml and deploy-aws-lambda.yml expression injections (#1542, #1649), - # both reaching production cloud credentials, passed every CI run. - # - # Both linters are needed and neither substitutes for the other: - # - actionlint catches workflow-level defects and, via shellcheck, shell - # bugs inside run: blocks. - # - zizmor has a template-injection audit that names the injection itself. - # Measured against the pre-fix rollback.yml, actionlint exited 1 only on - # unrelated SC2086 noise and never flagged the injected heredoc at all; - # zizmor flagged that exact line high severity, high confidence. actionlint - # alone would not have caught the bug this job exists to prevent. workflow-lint: name: Lint Workflows runs-on: ubuntu-latest @@ -201,52 +182,24 @@ jobs: - name: Set up Go uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 with: - go-version: ${{ env.GO_VERSION }} + go-version-file: go.mod cache: true - name: Download dependencies run: | - # Multi-module repo: `go mod download` resolves the current module - # only, so running it at the root left pkg/ and providers/* unverified - # (issue #1751). Mirror the govulncheck per-module loop below. set -e - for mod in . pkg providers/aws providers/azure providers/gcp tests/e2e; do + for mod in .; do echo "==> go mod download/verify in $mod" (cd "$mod" && go mod download && go mod verify) done - name: Run unit tests (all modules) run: | - # Multi-module repo: each ./... only walks the current module, so - # running from the root alone never compiled or asserted pkg/, - # providers/* or tests/e2e -- roughly 1600 test functions that had - # never gated a merge (issue #1751). Mirror the govulncheck and gosec - # per-module loops, collect one coverage profile per module, then - # merge for the upload steps below. - # - # A failing module must not abort the loop. Aborting would hide every - # later module's result behind the first failure, which is the same - # shape as the gosec bug in issue #1717. Guard each run, record the - # status, and fail at the end so every module is reported. set -uo pipefail status=0 - for mod in . pkg providers/aws providers/azure providers/gcp tests/e2e; do + for mod in .; do tag=$(echo "$mod" | tr './' '--' | sed 's/^-/root/') log="$RUNNER_TEMP/unit-${tag}.log" - # tests/e2e holds only //go:build e2e files, so `go test ./...` - # there matches no packages and exits 1 -- it cannot be run like the - # others. Its tests need the running stack and are executed by the - # e2e-tests job over docker compose; what this job can add is a - # type-check under that tag, which nothing else here does. Handled - # by name, not by a pattern, so the difference is visible in review. - if [ "$mod" = "tests/e2e" ]; then - echo "==> type-check $mod under -tags=e2e (tests run in the e2e-tests job)" - if ! (cd "$mod" && go vet -tags=e2e ./...) 2>&1 | tee "$log"; then - echo "::error::$mod failed to type-check under -tags=e2e" - status=1 - fi - continue - fi echo "==> unit tests in $mod" # Profiles go to $RUNNER_TEMP, not the checkout: never leave working # files in the repository root (repo coding guideline). @@ -267,19 +220,12 @@ jobs: fi done - # Merge the per-module profiles into the single file the steps below - # expect. A Go profile is one "mode:" header followed by block lines, - # so keep one header and concatenate the bodies. shopt -s nullglob profiles=("$RUNNER_TEMP"/coverage-*.out) if [ "${#profiles[@]}" -eq 0 ]; then echo "::error::no module produced a coverage profile" >&2 exit 1 fi - # Take the mode header from the first profile rather than hardcoding - # it: go test picks the default covermode itself (atomic whenever - # -race is on), so a literal here would silently mislabel the merge if - # the flags change. merged="$RUNNER_TEMP/coverage.out" head -n 1 "${profiles[0]}" > "$merged" for p in "${profiles[@]}"; do @@ -317,73 +263,12 @@ jobs: echo "::warning::Coverage is below 80% (current: ${coverage}%)" fi - mcp-build: - name: Build MCP (${{ matrix.os }}) - runs-on: ${{ matrix.os }} - permissions: - contents: read - strategy: - fail-fast: false - matrix: - include: - - os: ubuntu-latest - binary_format: ELF - - os: macos-latest - binary_format: Mach-O - steps: - - name: Checkout code - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 - with: - persist-credentials: false - - - name: Set up Go - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 - with: - go-version: ${{ env.GO_VERSION }} - cache: true - - - name: Assert MCP artifact is absent - run: test ! -e bin/cudly-mcp - - - name: Build MCP - run: make build-mcp VERSION=v0.0.0-version-test - - - name: Assert host-native binary format - run: file bin/cudly-mcp | grep -F '${{ matrix.binary_format }}' - - - name: Verify Make-built MCP version - env: - CUDLY_MCP_TEST_BINARY: ${{ github.workspace }}/bin/cudly-mcp - run: go test ./cmd/cudly-mcp -run '^TestBuiltBinaryReportsInjectedVersion$' -count=1 - - - name: Assert clean target includes MCP artifact - run: make -n clean | grep -Fx 'rm -f cudly bootstrap bin/cudly-server bin/cudly-mcp' - - # Integration tests with real PostgreSQL integration-tests: name: Integration Tests runs-on: ubuntu-latest permissions: contents: read - services: - postgres: - # Pinned by digest for the same reason as the linter images below: a - # floating tag lets the service container change under an unchanged - # repo, which is how #1695 turned main red. - image: postgres:16-alpine@sha256:cf78e76683b9ca8c5733cbbdce6c9262b45b6767934dd0a95e671f9a0fc20685 - env: - POSTGRES_DB: cudly_test - POSTGRES_USER: cudly_test - POSTGRES_PASSWORD: test_password # CI-only throwaway password — not used in any real environment - options: >- - --health-cmd pg_isready - --health-interval 10s - --health-timeout 5s - --health-retries 5 - ports: - - 5432:5432 - steps: - name: Checkout code uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 @@ -393,65 +278,16 @@ jobs: - name: Set up Go uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 with: - go-version: ${{ env.GO_VERSION }} + go-version-file: go.mod cache: true - - name: Install golang-migrate - run: | - # -tags 'pgx5', matching the Dockerfile and the library import in - # internal/database/postgres/migrations: the postgres tag links - # lib/pq, which carries unfixable advisories (issue #1849). No - # @version suffix: installed as a package of this module, so the - # version and dependency set come from go.mod. - go install -tags 'pgx5' github.com/golang-migrate/migrate/v4/cmd/migrate - - - name: Run database migrations - env: - DB_HOST: localhost - DB_PORT: 5432 - DB_NAME: cudly_test - DB_USER: cudly_test - DB_PASSWORD: test_password # CI-only throwaway password — not used in any real environment - run: | - if [ -d "internal/database/postgres/migrations" ]; then - migrate -path internal/database/postgres/migrations \ - -database "pgx5://${DB_USER}:${DB_PASSWORD}@${DB_HOST}:${DB_PORT}/${DB_NAME}?sslmode=disable" \ - up - fi - - name: Run integration tests - env: - DB_HOST: localhost - DB_PORT: 5432 - DB_NAME: cudly_test - DB_USER: cudly_test - DB_PASSWORD: test_password # CI-only throwaway password — not used in any real environment - DB_SSL_MODE: disable run: | - # Same multi-module gap as the unit job (issue #1751): a bare ./... - # here only ever walked the root module. Mirror the same loop. - # - # Note that -tags=integration ADDS a build tag rather than selecting - # only tagged files, so each module runs its untagged tests too. Today - # every //go:build integration file lives in the root module, so for - # pkg/ and providers/* this run is a compile-under-tag check plus a - # re-run of their unit tests. That is the point: it is what makes an - # integration test added to those modules later actually gate. set -uo pipefail status=0 - for mod in . pkg providers/aws providers/azure providers/gcp tests/e2e; do + for mod in .; do tag=$(echo "$mod" | tr './' '--' | sed 's/^-/root/') log="$RUNNER_TEMP/integration-${tag}.log" - # See the unit job: tests/e2e has no package to test without its own - # tag, so it gets a type-check here too rather than a run. - if [ "$mod" = "tests/e2e" ]; then - echo "==> type-check $mod under -tags=e2e (tests run in the e2e-tests job)" - if ! (cd "$mod" && go vet -tags=e2e ./...) 2>&1 | tee "$log"; then - echo "::error::$mod failed to type-check under -tags=e2e" - status=1 - fi - continue - fi echo "==> integration tests in $mod" if ! (cd "$mod" && go test -v -race -tags=integration \ -coverprofile="$RUNNER_TEMP/coverage-integration-${tag}.out" \ @@ -467,7 +303,6 @@ jobs: fi done - # Merge per-module profiles for the upload step (see the unit job). shopt -s nullglob profiles=("$RUNNER_TEMP"/coverage-integration-*.out) if [ "${#profiles[@]}" -eq 0 ]; then @@ -489,126 +324,6 @@ jobs: path: ${{ runner.temp }}/coverage-integration.out retention-days: 30 - # Docker image build test - docker-build: - name: Build Docker Image - runs-on: ubuntu-latest - permissions: - contents: read - steps: - - name: Checkout code - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 - with: - persist-credentials: false - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 - - - name: Build Docker image - uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7.2.0 - with: - context: . - push: false - # Export the built image into the local docker image store so the - # scan step below inspects the artifact this job just produced, - # rather than rebuilding one and hoping it is the same. - load: true - tags: cudly:${{ github.sha }} - cache-from: type=gha - cache-to: type=gha,mode=max - build-args: | - VERSION=${{ github.sha }} - - - name: Test Docker image - run: | - docker build -t cudly:test . - docker run --rm cudly:test /app/cudly --version || true - docker run --rm cudly:test /app/cudly --help || true - - # Nothing else in the pipeline looks at the artifact: govulncheck runs in - # source mode, and Trivy runs with scan-type fs and config, all against - # the repository. A vulnerable binary baked into the image was invisible - # by construction, which is how #1833 shipped twice (issue #1836). These - # steps live in this job rather than a new one so the image is scanned - # without being built a third time; docker-build is already in - # ci-success's needs, so the gate is wired. - - name: Set up Go - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 - with: - go-version: ${{ env.GO_VERSION }} - - - name: Install govulncheck - run: | - # Same pin as the security-scan job: a govulncheck release with new - # detection logic must not silently change this gate's verdict - # between PRs. - go install golang.org/x/vuln/cmd/govulncheck@v1.1.4 - - - name: Run image scan self-tests - # Asserts both directions of the verdict against recorded govulncheck - # output, so a scanner that can no longer fail cannot ship as coverage. - run: bash scripts/test-scan-shipped-image.sh - - - name: Scan the shipped image for Go advisories - run: bash scripts/scan-shipped-image.sh cudly:${{ github.sha }} - - # Terraform validation for all environments - terraform-validate: - name: Validate Terraform (${{ matrix.cloud }}) - runs-on: ubuntu-latest - permissions: - contents: read - strategy: - matrix: - cloud: [aws, gcp, azure] - fail-fast: false - - steps: - - name: Checkout code - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 - with: - persist-credentials: false - - - name: Setup Terraform - uses: hashicorp/setup-terraform@dfe3c3f87815947d99a8997f908cb6525fc44e9e # v4.0.1 - with: - # Must satisfy `required_version = ">= 1.10.0"` declared by every - # terraform/environments/*/main.tf -- pinning below 1.10 makes - # `terraform init`/`validate` abort with "Unsupported Terraform - # Core version". Matches the pin in pre-commit.yml so both - # workflows resolve to the same binary. - terraform_version: 1.10.5 - - - name: Terraform Format Check - run: terraform fmt -check -recursive terraform/ - - - name: Terraform Init - run: | - cd terraform/environments/${{ matrix.cloud }} - terraform init -backend=false - - - name: Terraform Validate - run: | - cd terraform/environments/${{ matrix.cloud }} - terraform validate - - - name: Validate environment tfvars files - run: | - cd terraform/environments/${{ matrix.cloud }} - for env in dev staging prod; do - # Check local tfvars if present - if [ -f "${env}.tfvars" ]; then - echo "Checking ${env}.tfvars syntax..." - terraform fmt -check "${env}.tfvars" || echo "Note: ${env}.tfvars may need formatting" - fi - # Check GitHub CI/CD tfvars - if [ -f "github-${env}.tfvars" ]; then - echo "Checking github-${env}.tfvars syntax..." - terraform fmt -check "github-${env}.tfvars" || echo "Note: github-${env}.tfvars may need formatting" - fi - done - - # Security scanning security-scan: name: Security Scanning runs-on: ubuntu-latest @@ -627,16 +342,10 @@ jobs: id: setup_go uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 with: - go-version: ${{ env.GO_VERSION }} + go-version-file: go.mod - name: Run govulncheck CVE scanner (all modules) - # always(): each scanner step below is independent evidence for the - # Security tab. Without this, one scanner failing (e.g. a live npm - # advisory) skips every scanner after it in the same job, silently - # disabling Go SAST and Terraform IaC coverage repo-wide. The job - # still fails overall if any scanner step here fails. Still requires - # checkout and Go setup to have actually succeeded -- an infra - # failure there must not be papered over as "scanner found nothing". + # Independent scanners still run after another scanner fails. if: always() && steps.checkout.outcome == 'success' && steps.setup_go.outcome == 'success' run: | # Pinned (not @latest): a govulncheck release with new @@ -644,28 +353,16 @@ jobs: # between PRs without an intentional bump in this repo. # Bumping is a deliberate review item, not a drift. go install golang.org/x/vuln/cmd/govulncheck@v1.1.4 - # Multi-module repo: each ./... only walks the current module, - # so scanning the root would silently miss pkg/ and providers/*. - # Walk every module independently and fail on any HIGH/CRITICAL. + # Scan each owned module independently. set -e - for mod in . pkg providers/aws providers/azure providers/gcp tests/e2e; do + for mod in .; do echo "==> govulncheck in $mod" (cd "$mod" && govulncheck ./...) done - - name: Run npm audit (frontend) - # always(): must not skip the scanners below it just because - # govulncheck failed, and its own failure must not skip gosec/Trivy. - # Still requires checkout to have succeeded (see govulncheck above). - if: always() && steps.checkout.outcome == 'success' && steps.setup_go.outcome == 'success' - run: | - if [ -f frontend/package.json ]; then - cd frontend && npm audit --audit-level=high - fi - - name: Run gosec Security Scanner id: gosec - # always(): don't let an earlier scanner's failure (e.g. npm audit) + # always(): don't let an earlier scanner's failure (e.g. govulncheck) # skip Go SAST coverage. The job still fails if gosec itself fails. # Still requires checkout and Go setup to have succeeded. if: always() && steps.checkout.outcome == 'success' && steps.setup_go.outcome == 'success' @@ -674,20 +371,10 @@ jobs: # The securego/gosec Docker action bundles its own Go toolchain which # cannot satisfy the module's go directive, causing a toolchain mismatch. go install github.com/securego/gosec/v2/cmd/gosec@v2.28.0 - # Multi-module repo: each ./... only walks the current module so scanning root - # alone silently misses pkg/ and providers/*. Mirror the govulncheck per-module - # loop, collect per-module SARIF, then merge for the upload step. - # - # gosec exits non-zero both for real findings and for a processing - # error. A bare `set -e` loop aborts at the first non-zero module, - # skipping the merge below entirely -- the upload step then fails - # on a missing file instead of surfacing the actual finding - # (issue #1717). Guarding the gosec call in `if ! ( ... )` exempts - # it from errexit so every module still gets scanned and merged; - # `status` records whether the job should still fail at the end. + # Scan each owned module independently. set -e status=0 - for mod in . pkg providers/aws providers/azure providers/gcp tests/e2e; do + for mod in .; do tag=$(echo "$mod" | tr './' '--' | sed 's/^-/root/') out="$RUNNER_TEMP/gosec-${tag}.sarif" echo "==> gosec in $mod" @@ -738,10 +425,7 @@ jobs: - name: Run Trivy vulnerability scanner (filesystem) id: trivy_fs - # always(): don't let an earlier scanner's failure skip Trivy fs - # coverage. This scan uses the default exit-code 0 (see the IaC - # scan comment below), so it does not gate the job on its own. - # Still requires checkout and Go setup to have succeeded. + # Filesystem findings remain advisory; scanner execution is required. if: always() && steps.checkout.outcome == 'success' && steps.setup_go.outcome == 'success' uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0 with: @@ -752,12 +436,7 @@ jobs: # save working files in the repository root). output: '${{ runner.temp }}/trivy-results.sarif' severity: 'CRITICAL,HIGH' - # Pin the Trivy binary independently of the action SHA. v0.36.0 is - # the latest trivy-action release but bundles Trivy v0.70.0, which - # panics in adaptDefaultTags on terraform/environments/aws/main.tf - # (null default_tags vars). Fixed in Trivy >= v0.72.0. The action - # forwards this input to aquasecurity/setup-trivy, so both scan - # steps run the same pinned binary. Keep both steps on this version. + # Keep the existing scanner binary pinned. version: 'v0.72.0' - name: Upload Trivy results to GitHub Security @@ -767,45 +446,6 @@ jobs: with: sarif_file: '${{ runner.temp }}/trivy-results.sarif' - # Terraform IaC misconfiguration scanning. Replaces the deprecated - # aquasecurity/tfsec-action, whose bundled HCL parser rejects Terraform - # 1.5+ `check {}` blocks (e.g. terraform/modules/deployment-checks/main.tf) - # with a hard "scan failed" parse error that soft_fail does not suppress - # (soft_fail only downgrades findings, not scan errors). Trivy is tfsec's - # official successor, parses `check {}` blocks, and (like the fs scan - # above) uses the default exit-code 0 so misconfig findings are reported - # to the Security tab without gating the job -- matching tfsec's prior - # soft_fail: true behaviour while preserving Terraform IaC coverage. - - name: Run Trivy IaC misconfiguration scanner (Terraform) - id: trivy_iac - # always(): don't let an earlier scanner's failure skip Terraform - # IaC coverage. Still requires checkout and Go setup to have - # succeeded. - if: always() && steps.checkout.outcome == 'success' && steps.setup_go.outcome == 'success' - uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0 - with: - scan-type: 'config' - scan-ref: 'terraform/' - format: 'sarif' - # $RUNNER_TEMP, not the checkout root (repo coding guideline: never - # save working files in the repository root). - output: '${{ runner.temp }}/trivy-config-results.sarif' - severity: 'CRITICAL,HIGH' - # Same pinned Trivy binary as the filesystem scan above (>= v0.72.0 - # avoids the adaptDefaultTags panic on null default_tags vars). - version: 'v0.72.0' - - - name: Upload Trivy IaC results to GitHub Security - # Tolerate a missing SARIF only when the Trivy IaC step never ran. - if: always() && steps.checkout.outcome == 'success' && steps.setup_go.outcome == 'success' && steps.trivy_iac.outcome != 'skipped' - uses: github/codeql-action/upload-sarif@7211b7c8077ea37d8641b6271f6a365a22a5fbfa # v4.36.0 - with: - sarif_file: '${{ runner.temp }}/trivy-config-results.sarif' - # Distinct category so this IaC analysis does not overwrite the - # filesystem Trivy analysis uploaded above (both report as "Trivy"). - category: 'trivy-iac' - - # Snyk security scanning snyk-scan: name: Snyk Security Scan runs-on: ubuntu-latest @@ -822,7 +462,7 @@ jobs: - name: Set up Go uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 with: - go-version: ${{ env.GO_VERSION }} + go-version-file: go.mod - name: Run Snyk to check for vulnerabilities uses: snyk/actions/golang@b98d498629f1c368650224d6d212bf7dfa89e4bf # 0.4.0 @@ -832,272 +472,25 @@ jobs: with: args: --severity-threshold=high - # Docker Compose E2E tests: build the app + test-runner images, bring up - # postgres + cudly-app, then run the black-box suite in tests/e2e against - # the app over HTTP. The test-runner service is profile-gated, so every - # compose invocation needs --profile test (issue #1180). - e2e-tests: - name: E2E Tests - runs-on: ubuntu-latest - permissions: - contents: read - - steps: - - name: Checkout code - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 - with: - persist-credentials: false - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 - - - name: Build images - run: | - docker compose -f docker-compose.test.yml --profile test build - - - name: Run E2E tests with docker compose - run: | - docker compose -f docker-compose.test.yml --profile test up --abort-on-container-exit --exit-code-from test-runner - env: - COMPOSE_INTERACTIVE_NO_CLI: 1 - - - name: Cleanup - if: always() - run: | - docker compose -f docker-compose.test.yml --profile test down -v - - # Assert that the Azure custom-role actions list is identical in the TF module - # and the ARM onboarding template. Fast (shell + jq only), so it always runs. - # Path changes that trigger drift will be caught regardless of PR context. - azure-role-parity: - name: Azure role actions parity (ARM vs TF) - runs-on: ubuntu-latest - permissions: - contents: read - - steps: - - name: Checkout code - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 - with: - persist-credentials: false - - - name: Assert ARM/TF actions parity - run: bash scripts/check-azure-role-parity.sh - - - name: Run parity script self-tests - run: bash scripts/test-azure-role-parity.sh - - # Assert that the AWS IAM action lists are identical across the CFN stack, - # the TF lambda/fargate modules, and the federation CFN/TF/CLI templates. - # Fast (shell only), so it always runs. - aws-iam-parity: - name: AWS IAM actions parity (CFN vs TF) - runs-on: ubuntu-latest - permissions: - contents: read - - steps: - - name: Checkout code - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 - with: - persist-credentials: false - - - name: Assert CFN/TF actions parity - run: bash scripts/check-aws-iam-parity.sh - - - name: Run parity script self-tests - run: bash scripts/test-aws-iam-parity.sh - - # Assert that no Terraform file grants a Secret Manager role at project, - # folder or organization scope; those scopes hand the member every secret in - # the scope. Fast (shell only), so it always runs. - gcp-secret-scope: - name: GCP Secret Manager grant scope - runs-on: ubuntu-latest - permissions: - contents: read - - steps: - - name: Checkout code - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 - with: - persist-credentials: false - - - name: Assert no scope-wide Secret Manager grants - run: bash scripts/check-gcp-secret-scope.sh - - - name: Run guard script self-tests - run: bash scripts/test-gcp-secret-scope.sh - - # Assert that the ECR repository selector used by destroy-fargate-dev.yml and - # cleanup-staging.yml picks the repository each state owns and nothing else. - # The consumers force-delete what the selector prints, so both directions are - # asserted: over-matching deletes images the workflow does not own, and - # matching nothing leaves that state's repository behind. The suite also - # asserts the wiring, which is what #1592 and #1820 each escaped: all three - # destroy steps still call scripts/force-delete-owned-ecr-repo.sh, that script - # still deletes only what the selector yields, and nothing else under - # .github/workflows or scripts/ runs `aws ecr delete-repository` unguarded. - # That last claim is checked over a GLOB of both directories, not a list of - # known files, so a script added later is covered without anyone remembering - # to name it; the sweep is asserted to have opened a non-zero number of files - # first, since an empty swept set has no violations either. - # Fast (shell only), so it always runs. - ecr-delete-selection: - name: ECR delete selection scope + cli-build: + name: Build CLI runs-on: ubuntu-latest - # This job checks out the tree and runs a shell script against it, so - # `contents: read` is all it needs. Same shape as security-scan above, - # which adds `security-events: write` only because it uploads SARIF. - # ci.yml now also declares `contents: read` at workflow level, so this - # block narrows nothing on its own; it is kept explicit so the job states - # its own requirement rather than inheriting silently. permissions: contents: read - - steps: - - name: Checkout code - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 - with: - persist-credentials: false - - - name: Run selector self-tests - run: bash scripts/test-ecr-delete-selection.sh - - # Assert that the RDS instance selector used by destroy-fargate-dev.yml and - # cleanup-staging.yml unprotects the instance each state owns and nothing - # else. Deletion protection is the last line of defence on a database, so - # stripping it from an instance a state does not own leaves that database - # exposed to the next destroy that does match it. Both directions are - # asserted: a prefix near-miss is refused, and the owned instance is still - # selected, since a selector matching nothing passes every refusal assertion - # while leaving the destroy broken. The suite also asserts the wiring, which - # is what #1592, #1820 and #1821 each escaped: all three destroy steps call - # scripts/disable-owned-rds-deletion-protection.sh, that script unprotects - # only what the selector yields and swallows nothing, and nothing else under - # .github/workflows or scripts/ runs `aws rds modify-db-instance` unguarded. - # That last claim is checked over a GLOB of both directories, not a list of - # known files, so a script added later is covered without anyone remembering - # to name it; the sweep is asserted to have opened a non-zero number of files - # first, since an empty swept set has no violations either. - # It additionally runs the script end to end against stubbed terraform and aws, - # so "a failed strip is not swallowed" is asserted as behaviour, not as text. - # Fast (shell only), so it always runs. - rds-deletion-protection-scope: - name: RDS deletion protection scope - runs-on: ubuntu-latest - # Same shape as ecr-delete-selection above: this job checks out the tree and - # runs a shell script against it, so `contents: read` is all it needs. - permissions: - contents: read - steps: - name: Checkout code uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: persist-credentials: false - - - name: Run RDS scope self-tests - run: bash scripts/test-rds-deletion-protection-scope.sh - - # Assert that the Cloud SQL instance selector used by cleanup-staging.yml's - # destroy-gcp job deletes the instance the staging state owns and nothing - # else. deletion_protection = true on that instance means a selector that - # matches nothing leaves it behind and the destroy fails on it; a selector - # that over-matches deletes a database this state never owned. Both - # directions are asserted: every near-miss name is refused, and the owned - # instance is still selected out of a hostile listing first, since a - # selector matching nothing passes every refusal assertion while leaving - # the destroy broken. The suite also asserts the wiring, which is what - # #1592, #1820 and #1821 each escaped on other resources: the destroy step - # calls scripts/delete-owned-cloud-sql-instance.sh, that script lists - # instances with no `--filter` and deletes only what the selector yields, - # the three `terraform state rm` calls are module-qualified and run only - # after a successful delete, and nothing else under .github/workflows or - # scripts/ runs `gcloud sql instances delete` unguarded. That last claim is - # checked over a GLOB of both directories, not a list of known files, so a - # script added later is covered without anyone remembering to name it; the - # sweep is asserted to have opened a non-zero number of files first, since - # an empty swept set has no violations either. - # It additionally runs the script end to end against stubbed terraform and - # gcloud, so "a failed delete is not swallowed" and "state is removed only - # after a successful delete" are asserted as behaviour, not as text. - # Fast (shell only), so it always runs. - cloud-sql-delete-scope: - name: Cloud SQL delete selection scope - runs-on: ubuntu-latest - # Same shape as ecr-delete-selection above: this job checks out the tree and - # runs a shell script against it, so `contents: read` is all it needs. - permissions: - contents: read - - steps: - - name: Checkout code - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 - with: - persist-credentials: false - - - name: Run Cloud SQL scope self-tests - run: bash scripts/test-cloud-sql-delete-scope.sh - - # Assert that every job applying a `compute_platform` writes the Terraform - # state namespace that platform owns: `compute_platform=lambda` into - # github-/, `compute_platform=fargate` into github-fargate-/. The - # AWS environment is one Terraform root applied twice into two state objects, - # nothing in Terraform ties the backend key to the platform, and a job that - # pairs them wrongly initialises, plans and applies cleanly while rewriting - # the other platform's stack and recording it in the wrong state file. That - # was #1811, where rollback.yml's Fargate rollback keyed on the Lambda - # namespace, so both rollback jobs wrote the same object. A workflow run - # proves nothing about this, so the pairing is asserted as text. - # Both directions are asserted, and the positive one first: the seven real - # pairings are named and checked before any absence, since a scan that - # recognizes no state-writing job has no violations either. The negative half - # is checked over a GLOB of .github/workflows and scripts/, not a list of - # known files, so a job added later is covered without anyone naming it. - # Fast (shell only), so it always runs. - aws-tfstate-platform-key: - name: AWS Terraform state namespace per platform - runs-on: ubuntu-latest - # Same shape as ecr-delete-selection above: this job checks out the tree and - # runs a shell script against it, so `contents: read` is all it needs. - permissions: - contents: read - - steps: - - name: Checkout code - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 - with: - persist-credentials: false - - - name: Run state namespace self-tests - run: bash scripts/test-aws-tfstate-platform-key.sh - - # Assert that no Terraform file declares an azurerm_key_vault_access_policy - # resource. This project's only Key Vault sets enable_rbac_authorization = - # true, and an RBAC-enabled vault ignores access policies entirely, so such a - # grant applies cleanly and then does nothing at runtime (#1621). The nested - # `access_policy` block form is deliberately not covered; #1839 tracks it. - # Fast (shell only), so it always runs. - azure-kv-access-policy: - name: Azure Key Vault grant model - runs-on: ubuntu-latest - permissions: - contents: read - - steps: - - name: Checkout code - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + - name: Set up Go + uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 with: - persist-credentials: false - - - name: Assert no Key Vault access policies - run: bash scripts/check-azure-kv-access-policy.sh - - - name: Run guard script self-tests - run: bash scripts/test-azure-kv-access-policy.sh + go-version-file: go.mod + - name: Build CLI + run: make build + - name: Check CLI help + run: ./cudly --help - # Summary job - all checks must pass ci-success: name: CI Success runs-on: ubuntu-latest @@ -1105,20 +498,9 @@ jobs: - lint - workflow-lint - unit-tests - - mcp-build - integration-tests - - docker-build - - terraform-validate - security-scan - - e2e-tests - - azure-role-parity - - aws-iam-parity - - gcp-secret-scope - - ecr-delete-selection - - rds-deletion-protection-scope - - cloud-sql-delete-scope - - aws-tfstate-platform-key - - azure-kv-access-policy + - cli-build if: always() permissions: contents: read diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml index 8e91b95..2e07902 100644 --- a/.github/workflows/pre-commit.yml +++ b/.github/workflows/pre-commit.yml @@ -43,81 +43,6 @@ jobs: uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: "24" - # Cache the npm download cache keyed on the frontend lockfile. - # Saves ~30-40s per run vs an uncached `npm ci`. Same pattern - # already used in frontend-build-sentinel.yml. - cache: "npm" - cache-dependency-path: frontend/package-lock.json - - - name: Set up Terraform - # Required by the terraform_fmt + terraform_validate pre-commit hooks. - # terraform_validate calls `terraform init` per module, which the - # action wraps with HTTP-cached provider downloads. - # - # Pin must satisfy `required_version = ">= 1.10.0"` declared by every - # `terraform/environments/*/main.tf` — pinning to a sub-1.10 version - # makes init abort before validate even runs. Action major matches - # `.github/workflows/ci.yml` so both workflows resolve to the same - # Terraform binary; otherwise a behavioural drift between the two - # could pass one and fail the other. - uses: hashicorp/setup-terraform@dfe3c3f87815947d99a8997f908cb6525fc44e9e # v4.0.1 - with: - terraform_version: "1.10.5" - terraform_wrapper: false - - - name: Install tflint - # Pinned to a release tag (not master) so a malicious or accidental - # change to install_linux.sh on master can't silently land on this - # CI runner. `curl -fsSL` makes transport errors fail loudly - # instead of writing an HTML error page to stdin and feeding it - # to bash. - env: - TFLINT_VERSION: v0.55.0 - run: | - set -euo pipefail - curl -fsSL -o /tmp/tflint-install.sh \ - "https://raw.githubusercontent.com/terraform-linters/tflint/${TFLINT_VERSION}/install_linux.sh" - bash /tmp/tflint-install.sh - - # Cache the tflint ruleset plugins (aws/azurerm/google) that - # `tflint --init` downloads from the GitHub Releases API. Without - # this cache EVERY run re-downloads all three plugins and is exposed - # to transient GitHub release-API 503s — a sustained 503 outrode the - # GITHUB_TOKEN auth + 3-attempt pre-commit retry below and reddened - # this job repo-wide (blocking every PR). Keyed on .tflint.hcl so a - # plugin-version bump re-downloads; restore-keys seeds a warm start. - - name: Cache tflint plugins - uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 - with: - path: ~/.tflint.d/plugins - key: tflint-plugins-${{ runner.os }}-${{ hashFiles('.tflint.hcl') }} - restore-keys: | - tflint-plugins-${{ runner.os }}- - - # Pre-populate the plugin cache with a dedicated, authenticated, - # retried `tflint --init` BEFORE pre-commit runs. On a cache hit this - # is a fast no-op (tflint skips download when the pinned plugin - # versions are already present — no API call, so immune to the 503). - # On a cache miss (version bump / cold cache) the retry loop rides - # out transient release-API 503s at the init level instead of - # re-running every hook via the coarse outer retry. GITHUB_TOKEN - # raises the release-API limit above the 60/hr anonymous ceiling. - - name: Initialize tflint plugins - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - set -uo pipefail - for attempt in 1 2 3 4 5 6; do - if tflint --init --config="${GITHUB_WORKSPACE}/.tflint.hcl"; then - echo "tflint --init succeeded (attempt ${attempt})" - exit 0 - fi - wait=$((attempt * 20)) - echo "tflint --init failed (attempt ${attempt}/6); retrying in ${wait}s..." >&2 - sleep "${wait}" - done - echo "tflint --init failed after 6 attempts — GitHub release API likely unavailable" >&2 - exit 1 # Cache the installed tool binaries (gosec, gocyclo). Keyed on the # pinned version strings so a tool-version bump still triggers a @@ -159,7 +84,7 @@ jobs: # # The installer itself is fetched from the same pinned release tag # (not the mutable `main` branch) and downloaded to a file before - # execution, matching the tflint step above: a malicious or + # execution: a malicious or # accidental change to install.sh on main can't silently execute # on this CI runner, and `curl -fsSL` makes transport errors fail # loudly instead of piping an HTML error page into sh. @@ -232,50 +157,15 @@ jobs: restore-keys: | go-build-${{ runner.os }}- - - name: Install frontend deps - run: | - if [ -f frontend/package-lock.json ]; then - cd frontend && npm ci - fi - - name: Run pre-commit - # SKIP=terraform_validate: that hook calls `terraform init` per - # module, which creates `.terraform.lock.hcl` files. Those are - # gitignored, so on a fresh CI checkout they don't exist and the - # init step "modifies files", which pre-commit reports as a - # failure. Local pre-commit runs work because lock files persist - # between invocations. terraform_fmt and terraform_tflint still - # run and catch the syntax/style issues that terraform_validate - # would catch; the deeper schema validation runs in - # `terraform plan` during deploy workflows. - # - # GITHUB_TOKEN is passed so terraform_tflint's `tflint --init` - # step authenticates against the GitHub API (5000/hr per-token) - # when it downloads ruleset plugin releases. Without the token, - # tflint goes anonymous and hits the 60/hr per-IP limit shared - # across every workflow on the runner's NAT IP, which trips - # intermittently when PRs land in the same hour (issue #564). - # + # SKIP the local per-changed-package gosec hook in CI: --all-files + # feeds every Go file at once, while the dedicated Security Scanning + # job remains the authoritative per-module gosec v2.28.0 gate. # nick-fields/retry wraps the run with up to 3 attempts and a - # 90-second wait so transient flakes (GitHub Releases blips, - # tflint plugin download timeouts, etc.) do not require a - # manual rerun. The GITHUB_TOKEN fix above is the primary fix; - # the retry wrapper is the cheap defense-in-depth for the - # residual flakes that token alone cannot eliminate. + # 90-second wait so transient flakes do not require a manual rerun. uses: nick-fields/retry@ce71cc2ab81d554ebbe88c79ab5975992d79ba08 # v3.0.2 env: - # SKIP the `gosec` pre-commit hook in CI: it is designed to scan - # only the changed packages of a local commit, but `pre-commit run - # --all-files` (this CI job) feeds it EVERY .go file across all six - # modules at once, and gosec's whole-repo analysis exhausts the - # runner's memory — the job dies with "The runner has received a - # shutdown signal" at this step on every run. gosec is NOT dropped: - # the dedicated `Security Scanning` job in ci.yml runs gosec v2.28.0 - # per-module (SARIF) as the authoritative gate, so this only removes - # the duplicate that OOMs CI — the same dedup rationale as the - # already-skipped `terraform_validate` (covered by Validate Terraform). - # Local developers still get the fast per-changed-package gosec hook. - SKIP: terraform_validate,gosec + SKIP: gosec GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: timeout_minutes: 10 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index af6a3f9..bb8fb3a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -3,14 +3,21 @@ # Setup: pre-commit install repos: - # Go formatting and linting - - repo: https://github.com/dnephin/pre-commit-golang - rev: v0.5.1 + # Go formatting and module checks + - repo: local hooks: - id: go-fmt name: Run gofmt + entry: bash scripts/gofmt-hook.sh + language: system + files: \.go$ - id: go-mod-tidy name: Run go mod tidy + entry: make tidy-check + language: system + pass_filenames: false + require_serial: true + files: '(\.go$|(^|/)go\.(mod|sum)$)' - repo: local hooks: diff --git a/Makefile b/Makefile index fbc0d39..01d3209 100644 --- a/Makefile +++ b/Makefile @@ -1,253 +1,108 @@ -.PHONY: build clean test deploy help all build-server build-lambda build-mcp test-unit test-integration \ - test-coverage full-test security-scan terraform-validate docker-build \ - fmt vet lint complexity complexity-report security-scan-go security-scan-docker \ - security-scan-terraform terraform-fmt terraform-fmt-check iac-arm docker-test pre-commit \ - setup-git-secrets security-scan-snyk security-scan-all ci docker-compose-test \ - install-dev-tools +.PHONY: all build clean test test-unit test-integration test-coverage full-test fmt vet lint \ + complexity complexity-report security-scan security-scan-go security-scan-snyk \ + security-scan-all pre-commit setup-git-secrets install-dev-tools help ci tidy-check -# Variables VERSION?=dev -BUILD_TIME?=$(shell date -u '+%Y-%m-%dT%H:%M:%SZ') -GIT_SHA?=$(shell git rev-parse --short HEAD 2>/dev/null || echo unknown) - -# Dev tool versions - keep in sync with the CI pins in -# .github/workflows/ci.yml, pre-commit.yml and database-migration.yml GOLANGCI_LINT_VERSION?=v2.10.1 GOSEC_VERSION?=v2.28.0 GOCYCLO_VERSION?=v0.6.0 -# golang-migrate deliberately has no version variable: it is installed as a -# package of this module (see install-tools), so its version and its whole -# dependency set come from go.mod. See issue #1849. -# staticcheck has no CI pin; it is used by scripts/security-scan.sh STATICCHECK_VERSION?=v0.7.0 -LDFLAGS=-ldflags "-s -w -X main.Version=$(VERSION) -X main.BuildTime=$(BUILD_TIME) -X main.GitSHA=$(GIT_SHA)" -# Default target all: build -help: ## Display available targets +help: @echo "Available targets:" @echo " build - Build the CLI" - @echo " build-server - Build the unified server" - @echo " build-lambda - Build for AWS Lambda" - @echo " build-mcp - Build the MCP server (cmd/cudly-mcp)" - @echo " test - Run all unit tests" - @echo " test-unit - Run unit tests only" + @echo " test-unit - Run unit tests" @echo " test-integration - Run integration tests with testcontainers" @echo " test-coverage - Run tests with coverage report" - @echo " clean - Remove build artifacts" + @echo " clean - Remove CLI build artifacts" @echo " fmt - Format Go code" @echo " lint - Run golangci-lint" @echo " complexity - Check cyclomatic complexity" - @echo " complexity-report - Generate detailed complexity report" - @echo " security-scan - Run security scanners (gosec, trivy, tfsec)" - @echo " security-scan-all - Run all security scanners including Snyk" - @echo " setup-git-secrets - Set up git-secrets for preventing credential leaks" - @echo " terraform-validate - Validate Terraform configurations" - @echo " docker-build - Build Docker image" - @echo " docker-compose-test - Run E2E tests with docker-compose" + @echo " security-scan - Run Go security scanners" + @echo " security-scan-snyk - Run Snyk" @echo " ci - Run CI pipeline locally" -# Build the CLI build: go build -o cudly ./cmd -# Build the unified server -build-server: - CGO_ENABLED=0 go build $(LDFLAGS) -o bin/cudly-server ./cmd/server - -# Build for Lambda (backward compatible) -build-lambda: - CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -ldflags="-s -w" -o bootstrap ./cmd/lambda - -# Build the MCP server (see mcp/README.md). Uses the same $(LDFLAGS)/$(VERSION) -# as build-server so a tagged release reports its version in the MCP -# initialize response instead of "dev" (see cmd/cudly-mcp/main.go). -build-mcp: - mkdir -p bin - CGO_ENABLED=0 go build $(LDFLAGS) -o bin/cudly-mcp ./cmd/cudly-mcp - -# Run unit tests test: test-unit test-unit: @echo "Running unit tests..." go test -v -race -short ./... -# Run integration tests (requires testcontainers) test-integration: @echo "Running integration tests..." go test -v -race -tags=integration ./... -# Run tests with coverage test-coverage: @echo "Generating coverage report..." go test -v -race -coverprofile=coverage.out -covermode=atomic ./... go tool cover -html=coverage.out -o coverage.html - @echo "Coverage report: coverage.html" - @go tool cover -func=coverage.out | grep total + go tool cover -func=coverage.out | grep total -# Run full test suite full-test: test-unit test-integration test-coverage -# Clean build artifacts clean: - rm -f cudly bootstrap bin/cudly-server bin/cudly-mcp - rm -f coverage.out coverage.html - rm -f gosec-report.json trivy-report.json tfsec-report.json + rm -f cudly coverage.out coverage.html gosec-report.json complexity-report.txt go clean -# Deploy (requires AWS credentials and terraform profiles) -deploy: - ./scripts/tf-deploy.sh aws dev - -# Format code fmt: go fmt ./... - terraform fmt -recursive terraform/ -# Lint code -lint: - @echo "Running golangci-lint..." - @if command -v golangci-lint > /dev/null; then \ - golangci-lint run --timeout=5m; \ - else \ - echo "golangci-lint not installed. Install: make install-dev-tools"; \ +tidy-check: + @version=$$(awk '/^[[:space:]]*go([[:space:]]|$$)/ { if (NF != 2) { print "__malformed__"; next } print $$2 }' go.mod); \ + count=$$(printf '%s\n' "$$version" | awk 'NF { n++ } END { print n + 0 }'); \ + if [ "$$count" -ne 1 ] || ! printf '%s\n' "$$version" | awk '$$0 !~ /^[0-9]+\.[0-9]+\.[0-9]+$$/ { exit 1 }'; then \ + echo "expected exactly one patch-level Go version in go.mod" >&2; exit 1; \ + fi; \ + if ! GOTOOLCHAIN="go$$version" GOWORK=off go mod tidy -diff; then \ + echo "go mod tidy check failed for module ." >&2; exit 1; \ fi -# Go vet vet: go vet ./... -# Check cyclomatic complexity +lint: + @command -v golangci-lint >/dev/null || { echo "golangci-lint not installed. Install: make install-dev-tools" >&2; exit 1; } + golangci-lint run --timeout=5m + complexity: - @echo "Checking cyclomatic complexity (threshold: 10)..." - @if command -v gocyclo > /dev/null; then \ - COMPLEXITY_ISSUES=$$(gocyclo -over 10 . 2>&1 || true); \ - if [ -n "$$COMPLEXITY_ISSUES" ]; then \ - echo "❌ Found functions with cyclomatic complexity over 10:"; \ - echo "$$COMPLEXITY_ISSUES"; \ - echo ""; \ - echo "⚠️ Please refactor these functions to reduce complexity."; \ - echo "📖 Tip: Extract helper functions, use early returns, or simplify logic."; \ - exit 1; \ - else \ - echo "✅ All functions have acceptable cyclomatic complexity (≤10)"; \ - fi \ - else \ - echo "gocyclo not installed. Install: make install-dev-tools"; \ + @command -v gocyclo >/dev/null || { echo "gocyclo not installed. Install: make install-dev-tools" >&2; exit 1; } + @if ! issues="$$(gocyclo -over 10 -ignore '.*_test\.go' .)"; then echo "gocyclo failed" >&2; exit 1; fi; \ + if [ -n "$$issues" ]; then \ + echo "Found functions with cyclomatic complexity over 10:" >&2; \ + echo "$$issues" >&2; \ exit 1; \ fi -# Generate detailed complexity report complexity-report: - @echo "Generating cyclomatic complexity report..." - @if command -v gocyclo > /dev/null; then \ - gocyclo -top 20 . | tee complexity-report.txt; \ - echo ""; \ - echo "📊 Top 20 most complex functions saved to: complexity-report.txt"; \ - else \ - echo "gocyclo not installed. Install: make install-dev-tools"; \ - fi + @command -v gocyclo >/dev/null || { echo "gocyclo not installed. Install: make install-dev-tools" >&2; exit 1; } + gocyclo -top 20 -ignore '.*_test\.go' . > complexity-report.txt && cat complexity-report.txt -# Security scanning -security-scan: security-scan-go security-scan-docker security-scan-terraform +security-scan: security-scan-go security-scan-go: - @echo "Running gosec..." - @if command -v gosec > /dev/null; then \ - gosec -fmt=json -out=gosec-report.json -exclude=G101,G104,G115,G204,G301,G304,G402,G505 ./...; \ - echo "✓ Go security scan complete: gosec-report.json"; \ - else \ - echo "gosec not installed. Install: make install-dev-tools"; \ - fi - -security-scan-docker: - @echo "Running trivy..." - @if command -v trivy > /dev/null; then \ - trivy fs --security-checks vuln,config . --format json --output trivy-report.json; \ - echo "✓ Container security scan complete: trivy-report.json"; \ - else \ - echo "trivy not installed. Install: https://aquasecurity.github.io/trivy/"; \ - fi - -security-scan-terraform: - @echo "Running tfsec..." - @if command -v tfsec > /dev/null; then \ - tfsec terraform/ --format json --out tfsec-report.json; \ - echo "✓ Terraform security scan complete: tfsec-report.json"; \ - else \ - echo "tfsec not installed. Install: https://aquasecurity.github.io/tfsec/"; \ - fi - -# Terraform validation -terraform-validate: - @echo "Validating Terraform configurations..." - @for dir in terraform/environments/*/dev; do \ - echo "Validating $$dir..."; \ - (cd $$dir && terraform init -backend=false && terraform validate) || exit 1; \ - done - @echo "✓ Terraform validation complete" - -terraform-fmt: - terraform fmt -recursive terraform/ - -terraform-fmt-check: - terraform fmt -check -recursive terraform/ + @command -v gosec >/dev/null || { echo "gosec not installed. Install: make install-dev-tools" >&2; exit 1; } + gosec -fmt=json -out=gosec-report.json -exclude=G101,G104,G115,G204,G301,G304,G402,G505 ./... -# Regenerate the committed ARM JSON from the Bicep source. CI verifies sync via -# `make iac-arm && git diff --exit-code`. -iac-arm: - az bicep build \ - --file iac/federation/azure-target/bicep/azure-wif.bicep \ - --outfile iac/federation/azure-target/bicep/azure-wif.arm.json - -# Docker -docker-build: - @echo "Building Docker image..." - docker build -t cudly:$(VERSION) -t cudly:latest --build-arg VERSION=$(VERSION) . - @echo "✓ Docker image built: cudly:$(VERSION)" +security-scan-snyk: + @command -v snyk >/dev/null || { echo "snyk not installed. Install: npm install -g snyk" >&2; exit 1; } + snyk test --severity-threshold=high -docker-test: docker-build - @echo "Testing Docker image..." - docker run --rm cudly:$(VERSION) /app/cudly --help || true +security-scan-all: security-scan security-scan-snyk -# CI pipeline -ci: fmt vet complexity test-unit security-scan terraform-validate - @echo "✓ CI pipeline complete" +ci: fmt vet complexity test-unit security-scan -# Pre-commit checks pre-commit: fmt vet complexity test-unit - @echo "✓ Pre-commit checks complete" -# Git secrets setup setup-git-secrets: @echo "Setting up git-secrets..." @bash scripts/setup-git-secrets.sh -# Snyk security scanning -security-scan-snyk: - @echo "Running Snyk security scan..." - @if command -v snyk > /dev/null; then \ - snyk test --severity-threshold=high; \ - echo "✓ Snyk scan complete"; \ - else \ - echo "snyk not installed. Install: npm install -g snyk"; \ - fi - -# Run all security scanners including Snyk -security-scan-all: security-scan security-scan-snyk - @echo "✓ All security scans complete" - -# Docker Compose E2E tests -docker-compose-test: - @echo "Running E2E tests with docker-compose..." - docker compose -f docker-compose.test.yml up --abort-on-container-exit --exit-code-from test-runner - docker compose -f docker-compose.test.yml down -v - -# Install development dependencies install-dev-tools: - @echo "Installing development tools..." @echo "Installing golangci-lint $(GOLANGCI_LINT_VERSION)..." @go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@$(GOLANGCI_LINT_VERSION) @echo "Installing gosec $(GOSEC_VERSION)..." @@ -256,13 +111,3 @@ install-dev-tools: @go install honnef.co/go/tools/cmd/staticcheck@$(STATICCHECK_VERSION) @echo "Installing gocyclo $(GOCYCLO_VERSION)..." @go install github.com/fzipp/gocyclo/cmd/gocyclo@$(GOCYCLO_VERSION) - @echo "Installing golang-migrate $$(go list -m -f '{{.Version}}' github.com/golang-migrate/migrate/v4)..." - @go install -tags 'pgx5' github.com/golang-migrate/migrate/v4/cmd/migrate - @echo "✓ Development tools installed" - @echo "" - @echo "Additional tools to install manually:" - @echo " - trivy: https://aquasecurity.github.io/trivy/" - @echo " - tfsec: https://aquasecurity.github.io/tfsec/" - @echo " - git-secrets: https://github.com/awslabs/git-secrets" - @echo " - snyk: npm install -g snyk" - @echo " - pre-commit: pip install pre-commit" diff --git a/README.md b/README.md index 1876890..7aa5553 100644 --- a/README.md +++ b/README.md @@ -1,756 +1,57 @@ -# CUDly - Multi-Cloud Commitment & Usage Discount Manager +# CUDly CLI -[![License: OSL-3.0](https://img.shields.io/badge/License-OSL--3.0-blue.svg)](https://opensource.org/licenses/OSL-3.0) -[![Go Version](https://img.shields.io/badge/Go-1.25+-00ADD8.svg)](https://go.dev/) +The CLI discovers cloud commitment recommendations and can purchase AWS Reserved Instances, Savings Plans, and selected Azure and GCP commitments. Amazon RDS and ElastiCache are the tested AWS service paths. Other AWS services, Azure, and GCP support remain experimental. -CUDly is a comprehensive CLI tool for managing cloud cost commitments across AWS, Azure, and GCP. It helps organizations optimize cloud spending by automating the discovery, analysis, and purchase of multiple Reserved Instances, Savings Plans, and Committed Use Discounts by running a single command. +The CLI depends on the published shared Go modules in [cloud-commitments-go](https://github.com/LeanerCloud/cloud-commitments-go), pinned to fixed versions in `go.mod`. No sibling checkout or parent workspace is needed for local development. -## CLI Reference +## Build -Full flag documentation, examples, and subcommand reference: [docs/cli/README.md](docs/cli/README.md) - -Topic pages: - -- [Filtering](docs/cli/filtering.md) - account, region, engine, instance-type, SP-type, and threshold filters -- [Purchase Safety](docs/cli/purchase-safety.md) - dry-run, audit log, idempotency window, and guardrails -- [Cloud Setup](docs/cli/cloud-setup.md) - `configure-azure` and `configure-gcp` self-hosted credential bootstrap - -## MCP Server - -CUDly also ships an MCP server (`cudly-mcp`) that lets Claude and other MCP clients search recommendations and drive RI, Savings Plan, and CUD purchases across AWS, Azure, and GCP, with the same dry-run-by-default safety as the CLI. - -Setup and usage: [mcp/README.md](mcp/README.md) - -## Key Features - -- **Multi-Cloud Support** - Unified interface for AWS (production), Azure (experimental), and GCP (experimental) -- **Intelligent Recommendations** - Fetches and analyzes commitment recommendations from cloud provider APIs -- **Safe Purchase Automation** - Execute purchases with built-in safety controls (dry-run by default) -- **Flexible Coverage Control** - Purchase only a percentage of recommendations for gradual adoption -- **CSV Workflow** - Generate recommendations, review offline, then execute purchases -- **Advanced Filtering** - Filter by region, instance type, engine, and account -- **Comprehensive Reporting** - Detailed cost estimates, savings calculations, and audit trails - -## Supported Cloud Providers & Services - -### AWS Services - -| Service | Commitment Type | Description | -|---------|----------------|-------------| -| Amazon RDS | Reserved Instances | MySQL, PostgreSQL, MariaDB, Oracle, SQL Server, Aurora | -| Amazon ElastiCache | Reserved Nodes | Redis, Memcached | -| Amazon EC2 | Reserved Instances | All instance families | -| Amazon OpenSearch | Reserved Instances | Search domain instances | -| Amazon Redshift | Reserved Nodes | DC2 and RA3 node types | -| Amazon MemoryDB | Reserved Nodes | Memory-optimized nodes | -| Savings Plans | Hourly Commitments | Compute, EC2 Instance, SageMaker, Database | - -### Azure Services (Experimental) - -| Service | Commitment Type | -|---------|----------------| -| Azure SQL Database | Reserved Capacity | -| Azure Virtual Machines | Reserved Instances | -| Azure Cache for Redis | Reserved Capacity | -| Azure Cosmos DB | Reserved Capacity | -| Azure Cognitive Search | Reserved Capacity | -| Azure Managed Redis | Reserved Capacity | -| Azure Savings Plans | Hourly Commitments | -| Azure Synapse Analytics | Reserved Capacity | - -### GCP Services (Experimental) - -| Service | Commitment Type | -|---------|----------------| -| Compute Engine | Committed Use Discounts | -| Cloud SQL | Committed Use Discounts | -| Memorystore | Committed Use Discounts | - -### AWS CLI Support Matrix - -**Tested** means the service has been exercised end-to-end with real AWS -accounts and validated in production workloads. **Experimental** means the -implementation exists and is functional, but needs real-world validation -- -contributions and testers are very welcome. - -| AWS Service | CLI Flag | Status | -| ----------- | -------- | ------ | -| Amazon RDS | `rds` | **Tested** | -| Amazon ElastiCache | `elasticache` | **Tested** | -| Amazon EC2 (Reserved Instances) | `ec2` | Experimental (seeking testers) | -| Amazon OpenSearch | `opensearch` | Experimental (seeking testers) | -| Amazon Redshift | `redshift` | Experimental (seeking testers) | -| Amazon MemoryDB | `memorydb` | Experimental (seeking testers) | -| Savings Plans (Compute, EC2 Instance, SageMaker, Database) | `savingsplans` | Experimental (seeking testers) | - -## Installation - -### From Source - -```bash -git clone https://github.com/LeanerCloud/CUDly.git -cd CUDly -go build -o cudly cmd/*.go -``` - -### Using Go Install - -```bash -go install github.com/LeanerCloud/CUDly/cmd@latest -``` - -## Quick Start - -### 1. Get Recommendations (Dry Run) - -```bash -# Get RDS recommendations with default settings (3-year, no-upfront, 80% coverage) -./cudly --services rds - -# Get recommendations for multiple services -./cudly --services rds,elasticache,ec2 - -# Get recommendations for all supported services -./cudly --all-services -``` - -### 2. Review and Refine - -```bash -# Apply filters to narrow down recommendations -./cudly --services rds \ - --include-regions us-east-1,eu-west-1 \ - --exclude-instance-types db.t2.micro \ - --coverage 50 -``` - -### 3. Execute Purchases - -```bash -# Purchase from generated CSV (requires explicit --purchase flag) -./cudly --input-csv cudly-dryrun-*.csv --purchase - -# Skip confirmation prompt -./cudly --input-csv cudly-dryrun-*.csv --purchase --yes -``` - -## Command Reference - -### Service Selection - -| Flag | Description | Default | -|------|-------------|---------| -| `-s, --services` | Comma-separated service list. Per-RI services: `rds`, `elasticache`, `ec2`, `opensearch`, `redshift`, `memorydb`. Per-plan-type Savings Plans: `savings-plans-compute`, `savings-plans-ec2instance`, `savings-plans-sagemaker`, `savings-plans-database`. Fan-out aliases: `savingsplans`, `savings-plans`, and `sp` expand to all four SP plan types. | rds | -| `--all-services` | Process all supported services | false | - -### Purchase Configuration - -| Flag | Description | Default | -|------|-------------|---------| -| `-p, --payment` | Payment option: `all-upfront`, `partial-upfront`, `no-upfront` | no-upfront | -| `-t, --term` | Term in years: `1` or `3` | 3 | -| `-c, --coverage` | Coverage percentage (0-100) — % of each recommendation's instance count to purchase | 80 | -| `-u, --target-coverage` | Target % (0-100) of historical demand to cover with commitments; the rest spills to on-demand. Sizes counts so projected coverage approximates target, projected utilization stays near 100%. Overrides `--coverage`. | 0 (disabled) | -| `--max-instances` | Maximum instances to purchase (0 = unlimited) | 0 | -| `--override-count` | Override recommended count with specific value | 0 | - -> **`--coverage` vs `--target-coverage`**: two related but distinct -> sizing levers. `--coverage` scales each AWS recommendation's instance -> count by a fixed fraction (`rec.Count * coverage/100`). -> `--target-coverage` sizes against historical average hourly usage -> instead (`floor(avg * target/100)`), so the resulting count reflects -> real demand rather than AWS's recommended count. Both lean the same -> direction (higher value = more RIs, lower value = fewer), but -> `--target-coverage` is the right lever when the historical-usage -> signal is what you want to size by and you're explicitly leaving -> on-demand headroom for growth or bursts. - -### Execution Control - -| Flag | Description | Default | -| ----------------- | --------------------------------------------- | -------------- | -| `--purchase` | Execute actual purchases (dry-run by default) | false | -| `--yes` | Skip confirmation prompts | false | -| `-i, --input-csv` | Input CSV file with recommendations | - | -| `-o, --output` | Output CSV file path | auto-generated | - -### Filtering - -| Flag | Description | -| ---------------------------- | --------------------------------------------------------------------------------- | -| `--include-regions` | Only include these regions | -| `--exclude-regions` | Exclude these regions | -| `--include-instance-types` | Only include these instance types | -| `--exclude-instance-types` | Exclude these instance types | -| `--include-engines` | Only include these database engines | -| `--exclude-engines` | Exclude these database engines | -| `--include-accounts` | Only include these account names | -| `--exclude-accounts` | Exclude these account names | -| `--include-extended-support` | Include instances on extended support engine versions (see below) | -| `--include-sp-types` | Only include these Savings Plan types (Compute, EC2Instance, SageMaker, Database) | -| `--exclude-sp-types` | Exclude these Savings Plan types | - -### Extended Support Filtering - -By default, CUDly excludes instances running on database engine versions that are in AWS Extended Support. This is because Extended Support incurs additional per-vCPU-hour charges that may offset RI savings. - -For example, MySQL 5.7 and PostgreSQL 11 are in Extended Support. Instances running these versions are automatically excluded from RI recommendations. - -**Note:** This feature requires the `--validation-profile` flag to specify an AWS profile with permissions to describe RDS instances across all member accounts in your organization. +Use the Go version declared in `go.mod`. ```bash -# Extended support filtering with validation profile -./cudly --services rds --validation-profile my-org-reader-profile - -# Include extended support instances (skip filtering) -./cudly --services rds --include-extended-support +make build +./cudly --help ``` -This is useful if you plan to upgrade the database version before the RI term ends, or if the Extended Support charges are acceptable for your use case. - -### Duplicate Purchase Prevention - -CUDly automatically checks for Reserved Instances purchased within the last 24 hours and adjusts recommendations to avoid duplicate purchases. This is useful when running the tool multiple times in quick succession or when recovering from partial purchase failures. - -For example, if you purchase 5 db.r6g.large RIs and run CUDly again within 24 hours, those 5 instances will be subtracted from the recommendation count to prevent double-purchasing. - -### Authentication - -| Flag | Description | -| ---------------------- | ---------------------------------------- | -| `--profile` | AWS profile to use | -| `--validation-profile` | AWS profile for instance type validation | +`make build` creates `./cudly` from `./cmd`. The build does not deploy or configure a cloud account. -## Usage Examples +Read the [CLI reference](docs/cli/README.md) for commands. See the guides for [cloud setup](docs/cli/cloud-setup.md), [filtering](docs/cli/filtering.md), and [purchase safety](docs/cli/purchase-safety.md). -### Example 1: Conservative RDS Adoption +## Common workflows -Purchase 50% of 1-year partial-upfront RDS recommendations: +Preview RDS recommendations before enabling a purchase: ```bash -./cudly --services rds \ - --payment partial-upfront \ - --term 1 \ - --coverage 50 +./cudly --services rds --profile default ``` -### Example 2: Multi-Service with Different Coverage +Use `--purchase` to enable a purchase operation. Use `--yes` to skip its confirmation prompt. A terminal prompt is not an automation boundary. Read the purchase-safety guide before using this mode. The CLI's `--idempotency-window` flag does not prevent duplicate purchases in the CLI path; review the dry-run output and audit log before retrying. -Apply different coverage percentages per service: +Export a reviewable report when you need to share results: ```bash -./cudly \ - --services rds,elasticache,ec2 \ - --rds-coverage 50 \ - --elasticache-coverage 80 \ - --ec2-coverage 100 \ - --payment no-upfront \ - --term 3 +./cudly --services rds --profile default --output recommendations.csv ``` -### Example 3: Regional Focus - -Only process specific regions with instance limits: - -```bash -./cudly --services ec2 \ - --include-regions us-east-1,us-west-2 \ - --max-instances 50 \ - --payment all-upfront \ - --term 3 -``` - -### Example 4: CSV-Based Workflow - -```bash -# Step 1: Generate recommendations -./cudly --all-services --output recommendations.csv - -# Step 2: Review CSV file externally - -# Step 3: Purchase with filters -./cudly \ - --input-csv recommendations.csv \ - --include-regions us-east-1 \ - --exclude-instance-types db.t2.micro,cache.t2.micro \ - --coverage 75 \ - --purchase -``` - -### Example 5: Exclude Small Instances - -```bash -./cudly --services rds,elasticache \ - --exclude-instance-types db.t2.micro,db.t2.small,db.t3.micro,cache.t2.micro \ - --payment partial-upfront \ - --term 3 -``` - -### Example 6: Database Savings Plans Only - -```bash -# Get only Database Savings Plans recommendations using the per-plan-type slug -./cudly --services savings-plans-database \ - --term 1 \ - --coverage 80 -``` - -### Example 7: Compute + EC2 Instance Savings Plans - -```bash -# Pick exactly the SP plan-types you want by listing per-plan-type slugs -./cudly --services savings-plans-compute,savings-plans-ec2instance \ - --term 3 \ - --coverage 80 -``` - -### Example 8: All Savings Plans via Fan-out Alias - -```bash -# `savingsplans` (and `savings-plans`, `sp`) is shorthand that fans out to every SP -# plan type -- equivalent to listing all four per-plan-type slugs. -./cudly --services savingsplans \ - --term 3 \ - --coverage 80 -``` - -> **Per-plan-type vs alias**: prefer the explicit per-plan-type slugs -> (`savings-plans-compute`, `savings-plans-ec2instance`, -> `savings-plans-sagemaker`, `savings-plans-database`) when you want -> precise scope. Use the `savingsplans` / `savings-plans` / `sp` alias only when you -> intentionally want all four SP plan types together. - -## Coverage Percentage - -The coverage percentage controls what portion of recommendations to act on: - -| Coverage | Description | Use Case | -| -------- | ----------- | -------- | -| 100% | All recommended instances | Maximum savings, stable workloads | -| 75% | Three-quarters of recommendations | Balanced approach | -| 50% | Half of recommendations | Conservative adoption | -| 25% | Quarter of recommendations | Testing/validation | -| 0% | Skip service entirely | Exclude from processing | - -## Per-Account Service Overrides - -Per-account service overrides let you tweak the global Settings → Purchasing -defaults (term, payment, coverage) on a per-account, per-service basis. - -### When to use them - -Use overrides when an account's purchasing policy differs from the rest of -your fleet: - -- **Dev/staging accounts**: prefer 1-year, no-upfront for RDS to keep - flexibility cheap, while production uses the global 3-year all-upfront. -- **Workload-shape outliers**: an account that runs a steady ElastiCache - cluster can override to 3-year all-upfront for ElastiCache only, while - the same account inherits the global default for everything else. -- **Pilot rollouts**: enable a new SP plan-type on one account first, - leave it disabled globally, then promote when proven. - -If every account should get the same change, edit the **global** Settings -> -Purchasing card instead; that propagates without per-account work. - -### How to create an override (web UI) - -1. Open the dashboard → **Settings → Accounts**. -2. Find the account row → click the row to expand it → click - **Service overrides**. -3. The override modal opens. Pick the (provider, service) pair you want - to override and fill in any of `term`, `payment`, `coverage`, - `enabled`. Fields you leave blank inherit the global default (see - "What 'Inherit' means" below). -4. Click **Save**. The override row appears under the account; the - recommendation engine reads it on the next refresh. - -> **All providers supported**: the override modal lists services for -> the account's provider (AWS, Azure, GCP). The UI and the backend -> both accept overrides for any provider. - -### How to edit an existing override - -- All override rows support **inline edit** of Term, Payment, Coverage, and - Enabled directly on the row. Each field persists immediately on change. -- **Delete** removes the override entirely; the account falls back to - the global default for that (provider, service) pair. - -### What "Inherit" means - -A blank field on an override is **not stored** as a sentinel value. The -PUT request omits the field, the row stays sparse, and the recommendation -engine reads the global default at evaluation time. So if you set the -global default from `3yr no-upfront` to `1yr no-upfront`, every override -that left `term` blank starts producing 1-year recommendations -automatically. Overrides that explicitly set `term: 3yr` keep that. - -### API parity - -The override modal targets the same endpoint as scripted setups: -`PUT /api/accounts/{id}/service-overrides/{provider}/{service}`. Existing -automation continues to work without change; the UI and the API write to -the same `account_service_overrides` row. - -## Safety Features - -CUDly includes multiple safety mechanisms to prevent unintended purchases: - -1. **Dry-run by default** - No purchases without explicit `--purchase` flag -2. **Interactive confirmation** - Prompts before actual purchases (unless `--yes`) -3. **CSV workflow** - Review recommendations before purchasing -4. **Coverage control** - Purchase only what you need -5. **Instance limits** - Cap total purchases with `--max-instances` -6. **Duplicate prevention** - Checks for existing commitments -7. **Instance type validation** - Validates against known types -8. **Detailed logging** - Full audit trail of operations -9. **CSV exports** - Permanent record of all recommendations and purchases - -## Cloud Provider Authentication - -### AWS - -CUDly uses the standard AWS SDK credential chain: - -1. Environment variables (`AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`) -2. Shared credentials file (`~/.aws/credentials`) -3. AWS config file (`~/.aws/config`) -4. IAM instance role (EC2/ECS) - -#### Required IAM Permissions - -```json -{ - "Version": "2012-10-17", - "Statement": [ - { - "Sid": "CostExplorer", - "Effect": "Allow", - "Action": [ - "ce:GetReservationPurchaseRecommendation", - "ce:GetReservationUtilization", - "ce:GetReservationCoverage", - "ce:GetSavingsPlansPurchaseRecommendation" - ], - "Resource": "*" - }, - { - "Sid": "ReservedInstanceOperations", - "Effect": "Allow", - "Action": [ - "rds:DescribeReservedDBInstancesOfferings", - "rds:DescribeReservedDBInstances", - "rds:PurchaseReservedDBInstancesOffering", - "elasticache:DescribeReservedCacheNodesOfferings", - "elasticache:DescribeReservedCacheNodes", - "elasticache:PurchaseReservedCacheNodesOffering", - "ec2:DescribeReservedInstancesOfferings", - "ec2:DescribeReservedInstances", - "ec2:PurchaseReservedInstancesOffering", - "es:DescribeReservedInstanceOfferings", - "es:DescribeReservedInstances", - "es:PurchaseReservedInstanceOffering", - "redshift:DescribeReservedNodeOfferings", - "redshift:DescribeReservedNodes", - "redshift:PurchaseReservedNodeOffering", - "memorydb:DescribeReservedNodesOfferings", - "memorydb:DescribeReservedNodes", - "memorydb:PurchaseReservedNodesOffering", - "savingsplans:DescribeSavingsPlans", - "savingsplans:CreateSavingsPlan" - ], - "Resource": "*" - }, - { - "Sid": "RegionDiscovery", - "Effect": "Allow", - "Action": [ - "ec2:DescribeRegions", - "ec2:DescribeInstanceTypeOfferings" - ], - "Resource": "*" - }, - { - "Sid": "AccountDiscovery", - "Effect": "Allow", - "Action": [ - "sts:GetCallerIdentity", - "organizations:ListAccounts" - ], - "Resource": "*" - } - ] -} -``` - -### Azure (Experimental) - -Uses Azure SDK DefaultAzureCredential: - -1. Azure CLI (`az login`) -2. Environment variables (`AZURE_TENANT_ID`, `AZURE_CLIENT_ID`, `AZURE_CLIENT_SECRET`) -3. Managed Identity (Azure VM) - -### GCP (Experimental) - -Uses Google Cloud SDK credential chain: - -1. Service account JSON (`GOOGLE_APPLICATION_CREDENTIALS`) -2. Application Default Credentials -3. gcloud CLI authentication - -## Output Format - -CUDly generates CSV files with comprehensive details: - -```csv -Timestamp,Status,Service,Provider,Account,Region,ResourceType,Count,Term,PaymentOption,UpfrontCost,RecurringCost,TotalCost,EstimatedSavings,PurchaseID -``` - -### File Naming Convention - -- Dry run: `cudly-dryrun-YYYYMMDD-HHMMSS.csv` -- Purchase: `cudly-purchase-YYYYMMDD-HHMMSS.csv` - -## Architecture - -```text -CUDly/ -├── cmd/ # CLI entry point and orchestration -├── pkg/ # Shared multi-cloud packages -│ ├── common/ # Cloud-agnostic types and interfaces -│ └── provider/ # Provider abstraction layer -└── providers/ # Cloud-specific implementations - ├── aws/ # AWS provider (production) - │ ├── services/ # Service clients (RDS, EC2, etc.) - │ └── recommendations/ # Cost Explorer integration - ├── azure/ # Azure provider (experimental) - │ └── services/ # Azure service clients - └── gcp/ # GCP provider (experimental) - └── services/ # GCP service clients -``` - -### Design Principles - -- **Interface-driven** - All implementations follow defined interfaces for testability -- **Multi-cloud abstraction** - Unified types and behaviors across providers -- **Plugin architecture** - Services registered and discovered at runtime -- **Safety-first** - Multiple layers of protection against unintended purchases - -## Web Interface (Experimental) - -> **Note: The web GUI is experimental.** It is under active development and -> has not been validated at scale. Use the CLI for production workloads. - -In addition to the CLI, this branch ships a browser-based dashboard. The same -Go binary that runs the CLI also acts as the application server: it serves the -pre-built TypeScript/Webpack frontend as static files (controlled by the -`STATIC_DIR` environment variable) and exposes a REST API at `/api/`. There is -no separate web server process. - -### What the dashboard provides - -| Area | What you can do | -| ---- | --------------- | -| **Dashboard** | Summary of active commitments, upcoming expirations, and savings trends | -| **Recommendations** | Browse and refresh commitment recommendations; trigger purchases from the UI | -| **Purchase plans** | Create, approve, pause, resume, and delete planned-purchase workflows; view execution history | -| **History** | Full purchase history with analytics and cost-breakdown views | -| **Inventory & Coverage** | List active commitments across accounts; view per-provider, per-service coverage breakdown | -| **RI Exchange** | AWS Convertible RI exchange: reshape recommendations, quote, and execute exchanges | -| **Settings** | Application configuration, cloud account credentials, user/group management, API keys | - -### Capabilities and limitations - -The web interface is included in `main`. The dashboard is operational for AWS workloads; Azure -and GCP support in the web UI follows the same maturity as the CLI providers -(both are experimental). Specifically: - -- **AWS**: recommendations, purchases, RI exchange, inventory, and coverage - views are all wired and backed by real AWS APIs (Cost Explorer, EC2, RDS, - etc.). This is the primary tested path. -- **Azure**: reservation recommendations and purchases are implemented in the - API handlers (see `internal/api/handler_recommendations.go`, - `providers/azure/`), but Azure support is experimental. The RI Exchange - feature covers Azure Convertible RIs as a distinct code path. -- **GCP**: GCP commitment recommendations and purchases are experimental. The - handler routing exists, but end-to-end coverage is limited compared to AWS. -- The RI Exchange feature currently targets AWS Convertible EC2 Reserved - Instances only. -- Multi-account support (AWS Organizations) is implemented; Azure/GCP - multi-account federation is in progress. - -### Deployment (self-hosted via Terraform) - -CUDly is **self-hosted only**. You deploy it into your own cloud account using -the Terraform configurations under `terraform/environments/`. The Terraform -modules build and push a Docker container image, provision the database, -secrets, and networking, and deploy the application to one of the supported -runtimes. - -| Cloud | Runtime | Terraform environment | -| ----- | ------- | --------------------- | -| AWS | Lambda (default) or Fargate (ECS) | `terraform/environments/aws/` | -| GCP | Cloud Run | `terraform/environments/gcp/` | -| Azure | Container Apps | `terraform/environments/azure/` | - -#### Prerequisites - -- Terraform >= 1.6.0 -- Docker with buildx -- Go 1.26.6+ -- Cloud CLI authenticated: `aws`, `gcloud`, or `az` - -#### Quick deploy (using the helper script) - -```bash -# AWS dev -./scripts/tf-deploy.sh aws dev - -# GCP dev -./scripts/tf-deploy.sh gcp dev - -# Azure dev -./scripts/tf-deploy.sh azure dev -``` - -#### Manual Terraform (AWS example) - -```bash -cd terraform/environments/aws -cp dev.tfvars.example dev.tfvars # edit with your values -terraform init -backend-config=backends/dev.tfbackend -terraform plan -var-file=dev.tfvars -terraform apply -var-file=dev.tfvars -``` - -See [`docs/DEPLOYMENT.md`](docs/DEPLOYMENT.md) for the full deployment guide, -including Azure and GCP details, CDN/CloudFront configuration, remote state -backends, and CI/CD integration. - -**Key `tfvars` fields** - -| Variable | Purpose | -| -------- | ------- | -| `admin_email` | Email address for the initial administrator account | -| `admin_password` | Initial admin password (leave unset to auto-generate and store in Secrets Manager) | -| `compute_platform` | AWS only: `"lambda"` (default, scale-to-zero) or `"fargate"` (always-warm ECS) | - -The Terraform apply also handles Docker image build/push and database -migrations automatically on each apply. - -### Accessing the dashboard - -After `terraform apply` completes, retrieve the application URL from the -Terraform outputs: - -```bash -# AWS Lambda -terraform -chdir=terraform/environments/aws output lambda_function_url - -# AWS Fargate (ALB) -terraform -chdir=terraform/environments/aws output fargate_api_url - -# GCP Cloud Run -terraform -chdir=terraform/environments/gcp output cloud_run_service_url - -# Azure Container Apps -terraform -chdir=terraform/environments/azure output container_app_url -``` - -Open that URL in your browser. On a fresh deployment the login page includes a -one-time **"Set up admin"** step. Provide the `admin_email` you configured in -`tfvars` and either the password you set or the one retrieved from Secrets -Manager: - -```bash -# Retrieve the auto-generated admin password (AWS) -aws secretsmanager get-secret-value \ - --secret-id "$(terraform -chdir=terraform/environments/aws output -raw admin_password_secret_name)" \ - --query SecretString --output text -``` - -After the admin account is created, log in with that email and password. You -can then add more users, configure cloud account credentials, and begin using -the dashboard. - -## Development - -See [`docs/DEVELOPMENT.md`](docs/DEVELOPMENT.md) for the full development -guide, including the local Docker environment, database migrations, hot -reload, and debugging workflows. - -### Prerequisites - -- Go 1.26.6 or later -- AWS/Azure/GCP credentials for integration testing - -### Building - -```bash -# Build binary -go build -o cudly cmd/*.go - -# Run tests -go test ./... - -# Run tests with coverage -go test -cover ./... - -# Run specific package tests -go test ./providers/aws/... -``` - -### Project Structure - -| Directory | Purpose | -| --------- | ------- | -| `cmd/` | CLI implementation, flag parsing, orchestration | -| `pkg/common/` | Cloud-agnostic types (Provider, Service, Commitment) | -| `pkg/provider/` | Provider interface, registry, factory | -| `providers/aws/` | AWS implementation with 8 service clients | -| `providers/azure/` | Azure implementation (experimental) | -| `providers/gcp/` | GCP implementation (experimental) | - -## Contributing - -Contributions are welcome! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines. - -### Areas for Contribution - -- Additional commitment-eligible services (any provider service that offers reserved capacity, savings plans, or committed-use discounts) -- Azure and GCP service implementations -- Enhanced reporting and analytics -- Web UI dashboard - -## License - -This project is licensed under the Open Software License 3.0 (OSL-3.0). See the [LICENSE](LICENSE) file for details. - -The OSL-3.0 is an OSI-approved open source license that: - -- Allows commercial use, modification, and distribution -- Requires attribution and license preservation -- Includes a patent grant -- Requires derivative works to be licensed under OSL-3.0 +All purchase operations can spend money. Check the account, region, quantity, and selected commitment before confirming. -## Disclaimer +## Credentials and provider status -**This tool can make actual cloud commitment purchases when used with the `--purchase` flag.** +Use the provider's supported credential chain. For AWS, select a profile with `--profile` and validate access before a purchase. Follow the cloud setup guide for Azure and GCP. -- Always verify recommendations before purchasing -- Test thoroughly in dry-run mode first -- Start with low coverage percentages -- Use instance limits for safety -- The authors are not responsible for unintended purchases or financial commitments +- Amazon RDS and ElastiCache are the tested AWS service paths. +- Other AWS service paths can change and are not covered by the same maturity claim. +- Azure and GCP support is experimental and can vary by service and account. +- Recommendation data and purchase APIs can change outside this repository. -## Support +## Related components -- **Issues**: [GitHub Issues](https://github.com/LeanerCloud/CUDly/issues) -- **Discussions**: [GitHub Discussions](https://github.com/LeanerCloud/CUDly/discussions) +- [Shared Go libraries](https://github.com/LeanerCloud/cloud-commitments-go) provide provider and common packages. +- [MCP server](https://github.com/LeanerCloud/cloud-commitments-mcp) exposes a separate tool interface. +- [Self-hosted platform](https://github.com/LeanerCloud/cloud-commitments-platform) owns the API, dashboard, and deployment paths. -## Shameless Plug +This component owns the CLI under `cmd` and its CLI documentation. It does not own the web dashboard, MCP server, or deployment infrastructure. -This tool is brought to you by [LeanerCloud](https://github.com/LeanerCloud). We help companies reduce their cloud costs using a mix of services and tools such as [AutoSpotting](https://github.com/LeanerCloud/AutoSpotting). +## License and attribution -Running at significant scale on AWS and looking for cost optimization help? We can help you avoid committing to suboptimal resources by rightsizing and other optimizations before purchasing commitments. [Contact us](https://leanercloud.com). +CUDly is maintained by [LeanerCloud](https://github.com/LeanerCloud) and licensed under the [Open Software License 3.0](LICENSE). See the repository license and attribution files for third-party notices. diff --git a/cmd/helpers.go b/cmd/helpers.go index 4d23354..ebb0a0e 100644 --- a/cmd/helpers.go +++ b/cmd/helpers.go @@ -9,8 +9,8 @@ import ( "strings" "sync" - "github.com/LeanerCloud/CUDly/pkg/common" - "github.com/LeanerCloud/CUDly/pkg/recfilter" + "github.com/LeanerCloud/cloud-commitments-go/pkg/common" + "github.com/LeanerCloud/cloud-commitments-go/pkg/recfilter" "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/organizations" "golang.org/x/term" diff --git a/cmd/helpers_count_override.go b/cmd/helpers_count_override.go index b4f2e21..3a4d8f5 100644 --- a/cmd/helpers_count_override.go +++ b/cmd/helpers_count_override.go @@ -1,7 +1,7 @@ package main import ( - "github.com/LeanerCloud/CUDly/pkg/common" + "github.com/LeanerCloud/cloud-commitments-go/pkg/common" ) // ApplyCountOverride replaces the count on every count-denominated diff --git a/cmd/helpers_count_override_rescale_test.go b/cmd/helpers_count_override_rescale_test.go index 1871aa6..3e23b85 100644 --- a/cmd/helpers_count_override_rescale_test.go +++ b/cmd/helpers_count_override_rescale_test.go @@ -4,7 +4,7 @@ import ( "math" "testing" - "github.com/LeanerCloud/CUDly/pkg/common" + "github.com/LeanerCloud/cloud-commitments-go/pkg/common" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/cmd/helpers_instance_limit_rescale_test.go b/cmd/helpers_instance_limit_rescale_test.go index 1235864..a778f15 100644 --- a/cmd/helpers_instance_limit_rescale_test.go +++ b/cmd/helpers_instance_limit_rescale_test.go @@ -4,7 +4,7 @@ import ( "math" "testing" - "github.com/LeanerCloud/CUDly/pkg/common" + "github.com/LeanerCloud/cloud-commitments-go/pkg/common" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/cmd/helpers_test.go b/cmd/helpers_test.go index 357d012..980488c 100644 --- a/cmd/helpers_test.go +++ b/cmd/helpers_test.go @@ -9,7 +9,7 @@ import ( "testing" "time" - "github.com/LeanerCloud/CUDly/pkg/common" + "github.com/LeanerCloud/cloud-commitments-go/pkg/common" "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/organizations" "github.com/aws/aws-sdk-go-v2/service/organizations/types" diff --git a/cmd/helpers_typed_nil_details_test.go b/cmd/helpers_typed_nil_details_test.go index b45214f..9ff50db 100644 --- a/cmd/helpers_typed_nil_details_test.go +++ b/cmd/helpers_typed_nil_details_test.go @@ -3,7 +3,7 @@ package main import ( "testing" - "github.com/LeanerCloud/CUDly/pkg/common" + "github.com/LeanerCloud/cloud-commitments-go/pkg/common" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/cmd/main.go b/cmd/main.go index 509b1d5..1fcde5d 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -8,19 +8,19 @@ import ( "strings" "time" - "github.com/LeanerCloud/CUDly/pkg/common" - "github.com/LeanerCloud/CUDly/pkg/provider" - _ "github.com/LeanerCloud/CUDly/providers/aws" - "github.com/LeanerCloud/CUDly/providers/aws/recommendations" - "github.com/LeanerCloud/CUDly/providers/aws/services/ec2" - "github.com/LeanerCloud/CUDly/providers/aws/services/elasticache" - "github.com/LeanerCloud/CUDly/providers/aws/services/memorydb" - "github.com/LeanerCloud/CUDly/providers/aws/services/opensearch" - "github.com/LeanerCloud/CUDly/providers/aws/services/rds" - "github.com/LeanerCloud/CUDly/providers/aws/services/redshift" - "github.com/LeanerCloud/CUDly/providers/aws/services/savingsplans" - _ "github.com/LeanerCloud/CUDly/providers/azure" - _ "github.com/LeanerCloud/CUDly/providers/gcp" + "github.com/LeanerCloud/cloud-commitments-go/pkg/common" + "github.com/LeanerCloud/cloud-commitments-go/pkg/provider" + _ "github.com/LeanerCloud/cloud-commitments-go/providers/aws" + "github.com/LeanerCloud/cloud-commitments-go/providers/aws/recommendations" + "github.com/LeanerCloud/cloud-commitments-go/providers/aws/services/ec2" + "github.com/LeanerCloud/cloud-commitments-go/providers/aws/services/elasticache" + "github.com/LeanerCloud/cloud-commitments-go/providers/aws/services/memorydb" + "github.com/LeanerCloud/cloud-commitments-go/providers/aws/services/opensearch" + "github.com/LeanerCloud/cloud-commitments-go/providers/aws/services/rds" + "github.com/LeanerCloud/cloud-commitments-go/providers/aws/services/redshift" + "github.com/LeanerCloud/cloud-commitments-go/providers/aws/services/savingsplans" + _ "github.com/LeanerCloud/cloud-commitments-go/providers/azure" + _ "github.com/LeanerCloud/cloud-commitments-go/providers/gcp" "github.com/aws/aws-sdk-go-v2/aws" "github.com/google/uuid" "github.com/spf13/cobra" diff --git a/cmd/main_test.go b/cmd/main_test.go index 4432a74..c685a03 100644 --- a/cmd/main_test.go +++ b/cmd/main_test.go @@ -5,7 +5,7 @@ import ( "strings" "testing" - "github.com/LeanerCloud/CUDly/pkg/common" + "github.com/LeanerCloud/cloud-commitments-go/pkg/common" "github.com/aws/aws-sdk-go-v2/aws" "github.com/stretchr/testify/assert" ) diff --git a/cmd/multi_service.go b/cmd/multi_service.go index bd5394a..bd38474 100644 --- a/cmd/multi_service.go +++ b/cmd/multi_service.go @@ -9,12 +9,12 @@ import ( "sync/atomic" "time" - "github.com/LeanerCloud/CUDly/internal/reporter" - "github.com/LeanerCloud/CUDly/pkg/common" - "github.com/LeanerCloud/CUDly/pkg/provider" - "github.com/LeanerCloud/CUDly/pkg/scorer" - awsprovider "github.com/LeanerCloud/CUDly/providers/aws" - "github.com/LeanerCloud/CUDly/providers/aws/recommendations" + "github.com/LeanerCloud/cloud-commitments-go/pkg/common" + "github.com/LeanerCloud/cloud-commitments-go/pkg/provider" + "github.com/LeanerCloud/cloud-commitments-go/pkg/reporter" + "github.com/LeanerCloud/cloud-commitments-go/pkg/scorer" + awsprovider "github.com/LeanerCloud/cloud-commitments-go/providers/aws" + "github.com/LeanerCloud/cloud-commitments-go/providers/aws/recommendations" "github.com/aws/aws-sdk-go-v2/aws" awsconfig "github.com/aws/aws-sdk-go-v2/config" "github.com/google/uuid" diff --git a/cmd/multi_service_coverage_test.go b/cmd/multi_service_coverage_test.go index ad82791..c66e46d 100644 --- a/cmd/multi_service_coverage_test.go +++ b/cmd/multi_service_coverage_test.go @@ -7,7 +7,7 @@ import ( "testing" "time" - "github.com/LeanerCloud/CUDly/pkg/common" + "github.com/LeanerCloud/cloud-commitments-go/pkg/common" "github.com/aws/aws-sdk-go-v2/aws" awsrds "github.com/aws/aws-sdk-go-v2/service/rds" rdstypes "github.com/aws/aws-sdk-go-v2/service/rds/types" diff --git a/cmd/multi_service_csv.go b/cmd/multi_service_csv.go index b230d56..297b871 100644 --- a/cmd/multi_service_csv.go +++ b/cmd/multi_service_csv.go @@ -10,8 +10,8 @@ import ( "sort" "time" - "github.com/LeanerCloud/CUDly/pkg/common" - "github.com/LeanerCloud/CUDly/providers/aws/recommendations" + "github.com/LeanerCloud/cloud-commitments-go/pkg/common" + "github.com/LeanerCloud/cloud-commitments-go/providers/aws/recommendations" ) // determineCSVCoverage determines the coverage percentage to use for CSV mode. diff --git a/cmd/multi_service_csv_cap.go b/cmd/multi_service_csv_cap.go index 163c0a6..d482779 100644 --- a/cmd/multi_service_csv_cap.go +++ b/cmd/multi_service_csv_cap.go @@ -5,8 +5,8 @@ import ( "sort" "strings" - "github.com/LeanerCloud/CUDly/pkg/common" - "github.com/LeanerCloud/CUDly/pkg/scorer" + "github.com/LeanerCloud/cloud-commitments-go/pkg/common" + "github.com/LeanerCloud/cloud-commitments-go/pkg/scorer" ) // scoreAndLimitCSVRecs enforces --min-count and the run-wide --max-instances diff --git a/cmd/multi_service_csv_test.go b/cmd/multi_service_csv_test.go index 6fc24ea..c62f20a 100644 --- a/cmd/multi_service_csv_test.go +++ b/cmd/multi_service_csv_test.go @@ -9,7 +9,7 @@ import ( "testing" "time" - "github.com/LeanerCloud/CUDly/pkg/common" + "github.com/LeanerCloud/cloud-commitments-go/pkg/common" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/cmd/multi_service_engine_versions.go b/cmd/multi_service_engine_versions.go index d6f395b..85c1bef 100644 --- a/cmd/multi_service_engine_versions.go +++ b/cmd/multi_service_engine_versions.go @@ -9,7 +9,7 @@ import ( "sync" "time" - "github.com/LeanerCloud/CUDly/pkg/common" + "github.com/LeanerCloud/cloud-commitments-go/pkg/common" "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/config" awsec2 "github.com/aws/aws-sdk-go-v2/service/ec2" diff --git a/cmd/multi_service_engine_versions_test.go b/cmd/multi_service_engine_versions_test.go index d127357..ec55ba6 100644 --- a/cmd/multi_service_engine_versions_test.go +++ b/cmd/multi_service_engine_versions_test.go @@ -5,7 +5,7 @@ import ( "testing" "time" - "github.com/LeanerCloud/CUDly/pkg/common" + "github.com/LeanerCloud/cloud-commitments-go/pkg/common" rdstypes "github.com/aws/aws-sdk-go-v2/service/rds/types" "github.com/stretchr/testify/assert" ) diff --git a/cmd/multi_service_filters.go b/cmd/multi_service_filters.go index 66eb43e..c66ea10 100644 --- a/cmd/multi_service_filters.go +++ b/cmd/multi_service_filters.go @@ -4,9 +4,9 @@ import ( "log" "strings" - "github.com/LeanerCloud/CUDly/pkg/common" - "github.com/LeanerCloud/CUDly/pkg/recfilter" - awsprovider "github.com/LeanerCloud/CUDly/providers/aws" + "github.com/LeanerCloud/cloud-commitments-go/pkg/common" + "github.com/LeanerCloud/cloud-commitments-go/pkg/recfilter" + awsprovider "github.com/LeanerCloud/cloud-commitments-go/providers/aws" ) // filtersFromConfig maps the CLI Config's dimension-filter and min-pool-size diff --git a/cmd/multi_service_filters_test.go b/cmd/multi_service_filters_test.go index 58a6661..f5982ab 100644 --- a/cmd/multi_service_filters_test.go +++ b/cmd/multi_service_filters_test.go @@ -6,7 +6,7 @@ import ( "testing" "time" - "github.com/LeanerCloud/CUDly/pkg/common" + "github.com/LeanerCloud/cloud-commitments-go/pkg/common" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/cmd/multi_service_helpers.go b/cmd/multi_service_helpers.go index ca5df21..fc605f5 100644 --- a/cmd/multi_service_helpers.go +++ b/cmd/multi_service_helpers.go @@ -8,10 +8,10 @@ import ( "strings" "time" - "github.com/LeanerCloud/CUDly/pkg/common" - "github.com/LeanerCloud/CUDly/pkg/provider" - "github.com/LeanerCloud/CUDly/providers/aws/recommendations" - azureprovider "github.com/LeanerCloud/CUDly/providers/azure" + "github.com/LeanerCloud/cloud-commitments-go/pkg/common" + "github.com/LeanerCloud/cloud-commitments-go/pkg/provider" + "github.com/LeanerCloud/cloud-commitments-go/providers/aws/recommendations" + azureprovider "github.com/LeanerCloud/cloud-commitments-go/providers/azure" "github.com/aws/aws-sdk-go-v2/aws" awsec2 "github.com/aws/aws-sdk-go-v2/service/ec2" ) diff --git a/cmd/multi_service_helpers_test.go b/cmd/multi_service_helpers_test.go index d8f6a02..d6d4476 100644 --- a/cmd/multi_service_helpers_test.go +++ b/cmd/multi_service_helpers_test.go @@ -7,7 +7,7 @@ import ( "testing" "time" - "github.com/LeanerCloud/CUDly/pkg/common" + "github.com/LeanerCloud/cloud-commitments-go/pkg/common" "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/ec2" "github.com/aws/aws-sdk-go-v2/service/ec2/types" diff --git a/cmd/multi_service_max_instances_test.go b/cmd/multi_service_max_instances_test.go index 2b113a9..7c11bd5 100644 --- a/cmd/multi_service_max_instances_test.go +++ b/cmd/multi_service_max_instances_test.go @@ -5,7 +5,7 @@ import ( "path/filepath" "testing" - "github.com/LeanerCloud/CUDly/pkg/common" + "github.com/LeanerCloud/cloud-commitments-go/pkg/common" "github.com/aws/aws-sdk-go-v2/aws" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" diff --git a/cmd/multi_service_stats.go b/cmd/multi_service_stats.go index fe7ee02..62ceb92 100644 --- a/cmd/multi_service_stats.go +++ b/cmd/multi_service_stats.go @@ -1,7 +1,7 @@ package main import ( - "github.com/LeanerCloud/CUDly/pkg/common" + "github.com/LeanerCloud/cloud-commitments-go/pkg/common" ) // ServiceProcessingStats holds statistics for each service. diff --git a/cmd/multi_service_stats_helpers.go b/cmd/multi_service_stats_helpers.go index 62f23aa..9f34225 100644 --- a/cmd/multi_service_stats_helpers.go +++ b/cmd/multi_service_stats_helpers.go @@ -1,7 +1,7 @@ package main import ( - "github.com/LeanerCloud/CUDly/pkg/common" + "github.com/LeanerCloud/cloud-commitments-go/pkg/common" ) // SPTypeBreakdown holds savings information broken down by Savings Plan type. diff --git a/cmd/multi_service_stats_test.go b/cmd/multi_service_stats_test.go index 32c7839..1bcb83d 100644 --- a/cmd/multi_service_stats_test.go +++ b/cmd/multi_service_stats_test.go @@ -8,7 +8,7 @@ import ( "os" "testing" - "github.com/LeanerCloud/CUDly/pkg/common" + "github.com/LeanerCloud/cloud-commitments-go/pkg/common" "github.com/stretchr/testify/assert" ) diff --git a/cmd/multi_service_test.go b/cmd/multi_service_test.go index eb8ae88..5b81979 100644 --- a/cmd/multi_service_test.go +++ b/cmd/multi_service_test.go @@ -13,8 +13,8 @@ import ( "testing" "time" - "github.com/LeanerCloud/CUDly/pkg/common" - "github.com/LeanerCloud/CUDly/pkg/scorer" + "github.com/LeanerCloud/cloud-commitments-go/pkg/common" + "github.com/LeanerCloud/cloud-commitments-go/pkg/scorer" "github.com/aws/aws-sdk-go-v2/aws" rdstypes "github.com/aws/aws-sdk-go-v2/service/rds/types" "github.com/stretchr/testify/assert" diff --git a/cmd/multi_service_test_common_test.go b/cmd/multi_service_test_common_test.go index 24097be..3f6a5cb 100644 --- a/cmd/multi_service_test_common_test.go +++ b/cmd/multi_service_test_common_test.go @@ -4,7 +4,7 @@ import ( "context" "sync" - "github.com/LeanerCloud/CUDly/pkg/common" + "github.com/LeanerCloud/cloud-commitments-go/pkg/common" "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/ec2" "github.com/aws/aws-sdk-go-v2/service/organizations" diff --git a/cmd/validators.go b/cmd/validators.go index 36b22ec..e16588a 100644 --- a/cmd/validators.go +++ b/cmd/validators.go @@ -7,7 +7,7 @@ import ( "path/filepath" "strings" - "github.com/LeanerCloud/CUDly/pkg/common" + "github.com/LeanerCloud/cloud-commitments-go/pkg/common" "github.com/spf13/cobra" ) diff --git a/cmd/validators_test.go b/cmd/validators_test.go index 201e7bb..202f862 100644 --- a/cmd/validators_test.go +++ b/cmd/validators_test.go @@ -6,7 +6,7 @@ import ( "strings" "testing" - "github.com/LeanerCloud/CUDly/pkg/common" + "github.com/LeanerCloud/cloud-commitments-go/pkg/common" "github.com/spf13/cobra" ) diff --git a/go.mod b/go.mod index 7371654..d5af033 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/LeanerCloud/CUDly +module github.com/LeanerCloud/cloud-commitments-cli go 1.26.6 @@ -7,11 +7,11 @@ require ( github.com/aws/aws-sdk-go-v2/config v1.29.12 github.com/aws/aws-sdk-go-v2/service/costexplorer v1.61.0 // indirect github.com/aws/aws-sdk-go-v2/service/ec2 v1.251.2 - github.com/aws/aws-sdk-go-v2/service/elasticache v1.50.3 - github.com/aws/aws-sdk-go-v2/service/memorydb v1.31.4 - github.com/aws/aws-sdk-go-v2/service/opensearch v1.52.3 + github.com/aws/aws-sdk-go-v2/service/elasticache v1.50.3 // indirect + github.com/aws/aws-sdk-go-v2/service/memorydb v1.31.4 // indirect + github.com/aws/aws-sdk-go-v2/service/opensearch v1.52.3 // indirect github.com/aws/aws-sdk-go-v2/service/rds v1.97.3 - github.com/aws/aws-sdk-go-v2/service/redshift v1.58.3 + github.com/aws/aws-sdk-go-v2/service/redshift v1.58.3 // indirect github.com/spf13/cobra v1.8.0 github.com/stretchr/testify v1.11.1 ) @@ -28,28 +28,28 @@ require ( github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1 github.com/Azure/azure-sdk-for-go/sdk/internal v1.12.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/advisor/armadvisor v1.2.0 // indirect - github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v5 v5.4.0 + github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v5 v5.4.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/consumption/armconsumption v1.1.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/redis/armredis/v3 v3.0.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armsubscriptions v1.3.0 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/sql/armsql v1.2.0 // indirect github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0 // indirect - github.com/aws/aws-sdk-go-v2/credentials v1.17.65 + github.com/aws/aws-sdk-go-v2/credentials v1.17.65 // indirect github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.30 // indirect github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.21 // indirect github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.21 // indirect github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 // indirect github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7 // indirect github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.21 // indirect - github.com/aws/aws-sdk-go-v2/service/savingsplans v1.31.0 + github.com/aws/aws-sdk-go-v2/service/savingsplans v1.31.0 // indirect github.com/aws/aws-sdk-go-v2/service/sso v1.25.2 // indirect github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.0 // indirect - github.com/aws/smithy-go v1.24.2 + github.com/aws/smithy-go v1.24.2 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect - github.com/golang-jwt/jwt/v5 v5.3.1 + github.com/golang-jwt/jwt/v5 v5.3.1 // indirect github.com/google/s2a-go v0.1.9 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.14 // indirect github.com/googleapis/gax-go/v2 v2.21.0 // indirect @@ -64,98 +64,55 @@ require ( go.opentelemetry.io/otel v1.44.0 // indirect go.opentelemetry.io/otel/metric v1.44.0 // indirect go.opentelemetry.io/otel/trace v1.44.0 // indirect - golang.org/x/crypto v0.56.0 - golang.org/x/net v0.57.0 // indirect + golang.org/x/crypto v0.56.0 // indirect + golang.org/x/net v0.58.0 // indirect golang.org/x/oauth2 v0.36.0 - golang.org/x/sync v0.22.0 + golang.org/x/sync v0.22.0 // indirect golang.org/x/sys v0.47.0 // indirect golang.org/x/text v0.41.0 // indirect golang.org/x/time v0.15.0 // indirect google.golang.org/api v0.274.0 google.golang.org/genproto v0.0.0-20260319201613-d00831a3d3e7 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20260414002931-afd174a4e478 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478 // indirect - google.golang.org/grpc v1.82.1 - google.golang.org/protobuf v1.36.11 - gopkg.in/yaml.v3 v3.0.1 + google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa // indirect + google.golang.org/grpc v1.83.2 // indirect + google.golang.org/protobuf v1.36.11 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) require ( - cloud.google.com/go/kms v1.29.0 - cloud.google.com/go/secretmanager v1.16.0 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/authorization/armauthorization/v2 v2.2.0 - github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/billingbenefits/armbillingbenefits v1.0.0 - github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/reservations/armreservations v1.1.0 - github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.2.0 - github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.4.0 - github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v1.4.0 - github.com/LeanerCloud/CUDly/pkg v0.0.0 - github.com/LeanerCloud/CUDly/providers/aws v0.0.0 - github.com/LeanerCloud/CUDly/providers/azure v0.0.0 - github.com/LeanerCloud/CUDly/providers/gcp v0.0.0 - github.com/aws/aws-lambda-go v1.47.0 - github.com/aws/aws-sdk-go-v2/service/kms v1.50.4 - github.com/aws/aws-sdk-go-v2/service/lambda v1.89.0 + github.com/LeanerCloud/cloud-commitments-go/pkg v0.0.0-20260925082912-43ab778da7ac + github.com/LeanerCloud/cloud-commitments-go/providers/aws v0.0.0-20260926232454-692cacc627d1 + github.com/LeanerCloud/cloud-commitments-go/providers/azure v0.0.0-20260926232454-692cacc627d1 + github.com/LeanerCloud/cloud-commitments-go/providers/gcp v0.0.0-20260926232454-692cacc627d1 github.com/aws/aws-sdk-go-v2/service/organizations v1.45.3 github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.40.3 - github.com/aws/aws-sdk-go-v2/service/sesv2 v1.42.0 - github.com/aws/aws-sdk-go-v2/service/sns v1.34.0 - github.com/aws/aws-sdk-go-v2/service/sts v1.33.17 - github.com/coreos/go-oidc/v3 v3.18.0 - github.com/go-jose/go-jose/v4 v4.1.4 - github.com/golang-migrate/migrate/v4 v4.19.1 - github.com/google/jsonschema-go v0.4.3 github.com/google/uuid v1.6.0 - github.com/jackc/pgx/v5 v5.9.2 github.com/microsoftgraph/msgraph-sdk-go v1.99.0 - github.com/modelcontextprotocol/go-sdk v1.6.1 - github.com/pashagolub/pgxmock/v4 v4.9.0 - github.com/testcontainers/testcontainers-go v0.42.0 - github.com/testcontainers/testcontainers-go/modules/postgres v0.42.0 golang.org/x/term v0.45.0 ) require ( - cel.dev/expr v0.25.1 // indirect + cel.dev/expr v0.25.2 // indirect cloud.google.com/go/auth v0.18.2 // indirect cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect cloud.google.com/go/monitoring v1.24.3 // indirect cloud.google.com/go/redis v1.18.3 // indirect cloud.google.com/go/storage v1.59.2 // indirect - dario.cat/mergo v1.0.2 // indirect + github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/billingbenefits/armbillingbenefits v1.0.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/cosmos/armcosmos/v2 v2.7.0 // indirect + github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/reservations/armreservations v1.1.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/search/armsearch v1.4.0 // indirect - github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.2.0 // indirect - github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect - github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.32.0 // indirect + github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.33.0 // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.54.0 // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.54.0 // indirect - github.com/Microsoft/go-winio v0.6.2 // indirect - github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.8 // indirect - github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.22 // indirect - github.com/cenkalti/backoff/v4 v4.3.0 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.33.17 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2 // indirect - github.com/containerd/errdefs v1.0.0 // indirect - github.com/containerd/errdefs/pkg v0.3.0 // indirect - github.com/containerd/log v0.1.0 // indirect - github.com/containerd/platforms v0.2.1 // indirect - github.com/cpuguy83/dockercfg v0.3.2 // indirect - github.com/distribution/reference v0.6.0 // indirect - github.com/docker/docker v28.5.1+incompatible // indirect - github.com/docker/go-connections v0.6.0 // indirect - github.com/docker/go-units v0.5.0 // indirect - github.com/ebitengine/purego v0.10.0 // indirect github.com/envoyproxy/go-control-plane/envoy v1.37.0 // indirect github.com/envoyproxy/protoc-gen-validate v1.3.3 // indirect - github.com/go-ole/go-ole v1.2.6 // indirect - github.com/jackc/pgerrcode v0.0.0-20220416144525-469b46aa5efa // indirect - github.com/jackc/pgpassfile v1.0.0 // indirect - github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect - github.com/jackc/puddle/v2 v2.2.2 // indirect - github.com/klauspost/compress v1.18.7 // indirect - github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect - github.com/magiconair/properties v1.8.10 // indirect + github.com/go-jose/go-jose/v4 v4.1.4 // indirect github.com/microsoft/kiota-abstractions-go v1.9.4 // indirect github.com/microsoft/kiota-authentication-azure-go v1.3.1 // indirect github.com/microsoft/kiota-http-go v1.5.6 // indirect @@ -164,39 +121,11 @@ require ( github.com/microsoft/kiota-serialization-multipart-go v1.1.2 // indirect github.com/microsoft/kiota-serialization-text-go v1.1.3 // indirect github.com/microsoftgraph/msgraph-sdk-go-core v1.4.1 // indirect - github.com/moby/docker-image-spec v1.3.1 // indirect - github.com/moby/go-archive v0.3.0 // indirect - github.com/moby/moby/api v1.54.1 // indirect - github.com/moby/moby/client v0.4.0 // indirect - github.com/moby/patternmatcher v0.6.1 // indirect - github.com/moby/sys/sequential v0.7.0 // indirect - github.com/moby/sys/user v0.4.1 // indirect - github.com/moby/sys/userns v0.1.0 // indirect - github.com/moby/term v0.5.2 // indirect - github.com/opencontainers/go-digest v1.0.0 // indirect - github.com/opencontainers/image-spec v1.1.1 // indirect github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect - github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect - github.com/segmentio/asm v1.1.3 // indirect - github.com/segmentio/encoding v0.5.4 // indirect - github.com/shirou/gopsutil/v4 v4.26.3 // indirect - github.com/sirupsen/logrus v1.9.4 // indirect - github.com/spiffe/go-spiffe/v2 v2.6.0 // indirect + github.com/spiffe/go-spiffe/v2 v2.7.0 // indirect github.com/std-uritemplate/std-uritemplate/go/v2 v2.0.3 // indirect - github.com/tklauser/go-sysconf v0.3.16 // indirect - github.com/tklauser/numcpus v0.11.0 // indirect - github.com/yosida95/uritemplate/v3 v3.0.2 // indirect - github.com/yusufpapurcu/wmi v1.2.4 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect - go.opentelemetry.io/contrib/detectors/gcp v1.43.0 // indirect - go.opentelemetry.io/otel/sdk v1.43.0 // indirect - go.opentelemetry.io/otel/sdk/metric v1.43.0 // indirect + go.opentelemetry.io/contrib/detectors/gcp v1.44.0 // indirect + go.opentelemetry.io/otel/sdk v1.44.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.44.0 // indirect ) - -replace github.com/LeanerCloud/CUDly/pkg => ./pkg - -replace github.com/LeanerCloud/CUDly/providers/aws => ./providers/aws - -replace github.com/LeanerCloud/CUDly/providers/azure => ./providers/azure - -replace github.com/LeanerCloud/CUDly/providers/gcp => ./providers/gcp diff --git a/go.sum b/go.sum index ddedaf1..a1e3f5b 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -cel.dev/expr v0.25.1 h1:1KrZg61W6TWSxuNZ37Xy49ps13NUovb66QLprthtwi4= -cel.dev/expr v0.25.1/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= +cel.dev/expr v0.25.2 h1:K6j46C81hXtZQfuX60cVWQFBJahKSE2gfRbNuvr5bFs= +cel.dev/expr v0.25.2/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= cloud.google.com/go v0.123.0 h1:2NAUJwPR47q+E35uaJeYoNhuNEM9kM8SjgRgdeOJUSE= cloud.google.com/go v0.123.0/go.mod h1:xBoMV08QcqUGuPW65Qfm1o9Y4zKZBpGS+7bImXLTAZU= cloud.google.com/go/auth v0.18.2 h1:+Nbt5Ev0xEqxlNjd6c+yYUeosQ5TtEUaNcN/3FozlaM= @@ -12,8 +12,6 @@ cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdB cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10= cloud.google.com/go/iam v1.7.0 h1:JD3zh0C6LHl16aCn5Akff0+GELdp1+4hmh6ndoFLl8U= cloud.google.com/go/iam v1.7.0/go.mod h1:tetWZW1PD/m6vcuY2Zj/aU0eCHNPuxedbnbRTyKXvdY= -cloud.google.com/go/kms v1.29.0 h1:bAW1C5FQf+6GhPkywQzPlsULALCG7c16qpXLFGV9ivY= -cloud.google.com/go/kms v1.29.0/go.mod h1:YIyXZym11R5uovJJt4oN5eUL3oPmirF3yKeIh6QAf4U= cloud.google.com/go/logging v1.13.2 h1:qqlHCBvieJT9Cdq4QqYx1KPadCQ2noD4FK02eNqHAjA= cloud.google.com/go/logging v1.13.2/go.mod h1:zaybliM3yun1J8mU2dVQ1/qDzjbOqEijZCn6hSBtKak= cloud.google.com/go/longrunning v0.9.0 h1:0EzbDEGsAvOZNbqXopgniY0w0a1phvu5IdUFq8grmqY= @@ -26,16 +24,10 @@ cloud.google.com/go/redis v1.18.3 h1:6LI8zSt+vmE3WQ7hE5GsJ13CbJBLV1qUw6B7CY31Wcw cloud.google.com/go/redis v1.18.3/go.mod h1:x8HtXZbvMBDNT6hMHaQ022Pos5d7SP7YsUH8fCJ2Wm4= cloud.google.com/go/resourcemanager v1.10.7 h1:oPZKIdjyVTuag+D4HF7HO0mnSqcqgjcuA18xblwA0V0= cloud.google.com/go/resourcemanager v1.10.7/go.mod h1:rScGkr6j2eFwxAjctvOP/8sqnEpDbQ9r5CKwKfomqjs= -cloud.google.com/go/secretmanager v1.16.0 h1:19QT7ZsLJ8FSP1k+4esQvuCD7npMJml6hYzilxVyT+k= -cloud.google.com/go/secretmanager v1.16.0/go.mod h1://C/e4I8D26SDTz1f3TQcddhcmiC3rMEl0S1Cakvs3Q= cloud.google.com/go/storage v1.59.2 h1:gmOAuG1opU8YvycMNpP+DvHfT9BfzzK5Cy+arP+Nocw= cloud.google.com/go/storage v1.59.2/go.mod h1:cMWbtM+anpC74gn6qjLh+exqYcfmB9Hqe5z6adx+CLI= cloud.google.com/go/trace v1.11.7 h1:kDNDX8JkaAG3R2nq1lIdkb7FCSi1rCmsEtKVsty7p+U= cloud.google.com/go/trace v1.11.7/go.mod h1:TNn9d5V3fQVf6s4SCveVMIBS2LJUqo73GACmq/Tky0s= -dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8= -dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA= -github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk= -github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.1 h1:jHb/wfvRikGdxMXYV3QG/SzUOPYN9KEUUuC0Yd0/vC0= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.1/go.mod h1:pzBXCYn05zvYIrwLgtK8Ap8QcjRg+0i76tMQdWN6wOk= github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1 h1:Hk5QBxZQC1jb2Fwj6mpzme37xbCDdNTxU7O9eb5+LB4= @@ -62,8 +54,6 @@ github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/internal/v2 v2.0.0 h1:PTFG github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/internal/v2 v2.0.0/go.mod h1:LRr2FzBTQlONPPa5HREE5+RjSCTXl7BwOvYOaWTqCaI= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/internal/v3 v3.1.0 h1:2qsIIvxVT+uE6yrNldntJKlLRgxGbZ85kgtz5SNBhMw= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/internal/v3 v3.1.0/go.mod h1:AW8VEadnhw9xox+VaVd9sP7NjzOAnaZBLRH6Tq3cJ38= -github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/managementgroups/armmanagementgroups v1.0.0 h1:pPvTJ1dY0sA35JOeFq6TsY2xj6Z85Yo23Pj4wCCvu4o= -github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/managementgroups/armmanagementgroups v1.0.0/go.mod h1:mLfWfj8v3jfWKsL9G4eoBoXVcsqcIUTapmdKy7uGOp0= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/redis/armredis/v3 v3.0.0 h1:zp+znRAHKLSewbw+WWKIMgCaFNxEXt9AwjxmW5fCnck= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/redis/armredis/v3 v3.0.0/go.mod h1:nEvLUni7GO5ukfEYtmrUfz08Puqd2FP9d8sCZazm5W4= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/reservations/armreservations v1.1.0 h1:0OO/3K+SKt45gXiOU4gHRILOLeNOUZdqeNO47Mq6iN8= @@ -76,34 +66,28 @@ github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/search/armsearch v1.4.0 h1 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/search/armsearch v1.4.0/go.mod h1:Y2Q3nB3UfSnG9nALOpPAjflXPM3jL/n2ZmYIu2Occ9g= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/sql/armsql v1.2.0 h1:S087deZ0kP1RUg4pU7w9U9xpUedTCbOtz+mnd0+hrkQ= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/sql/armsql v1.2.0/go.mod h1:B4cEyXrWBmbfMDAPnpJ1di7MAt5DKP57jPEObAvZChg= -github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.4.0 h1:E4MgwLBGeVB5f2MdcIVD3ELVAWpr+WD6MUe1i+tM/PA= -github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.4.0/go.mod h1:Y2b/1clN4zsAoUd/pgNAQHjLDnTis/6ROkUfyob6psM= -github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v1.4.0 h1:/g8S6wk65vfC6m3FIxJ+i5QDyN9JWwXI8Hb0Img10hU= -github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v1.4.0/go.mod h1:gpl+q95AzZlKVI3xSoseF9QPrypk0hQqBiJYeB/cR/I= -github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.2.0 h1:nCYfgcSyHZXJI8J0IWE5MsCGlb2xp9fJiXyxWgmOFg4= -github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.2.0/go.mod h1:ucUjca2JtSZboY8IoUqyQyuuXvwbMBVwFOm0vdQPNhA= -github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg= -github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJTmL004Abzc5wDB5VtZG2PJk5ndYDgVacGqfirKxjM= github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE= github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0 h1:XRzhVemXdgvJqCH0sFfrBUTnUJSBrBf7++ypk+twtRs= github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0/go.mod h1:HKpQxkWaGLJ+D/5H8QRpyQXA1eKjxkFlOMwck5+33Jk= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.32.0 h1:rIkQfkCOVKc1OiRCNcSDD8ml5RJlZbH/Xsq7lbpynwc= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.32.0/go.mod h1:RD2SsorTmYhF6HkTmDw7KmPYQk8OBYwTkuasChwv7R4= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.33.0 h1:l7+6kwRMJNwdCvYdDl7Eax+wzEYHSnNY7zrrfbhDdTA= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.33.0/go.mod h1:pJTkW8hEUIIi3Pf65lPZOnn4Y81yCllX6IWk2jNXdkM= github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.54.0 h1:lhhYARPUu3LmHysQ/igznQphfzynnqI3D75oUyw1HXk= github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.54.0/go.mod h1:l9rva3ApbBpEJxSNYnwT9N4CDLrWgtq3u8736C5hyJw= github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.54.0 h1:xfK3bbi6F2RDtaZFtUdKO3osOBIhNb+xTs8lFW6yx9o= github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.54.0/go.mod h1:vB2GH9GAYYJTO3mEn8oYwzEdhlayZIdQz6zdzgUIRvA= github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.54.0 h1:s0WlVbf9qpvkh1c/uDAPElam0WrL7fHRIidgZJ7UqZI= github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.54.0/go.mod h1:Mf6O40IAyB9zR/1J8nGDDPirZQQPbYJni8Yisy7NTMc= -github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= -github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= -github.com/aws/aws-lambda-go v1.47.0 h1:0H8s0vumYx/YKs4sE7YM0ktwL2eWse+kfopsRI1sXVI= -github.com/aws/aws-lambda-go v1.47.0/go.mod h1:dpMpZgvWx5vuQJfBt0zqBha60q7Dd7RfgJv23DymV8A= +github.com/LeanerCloud/cloud-commitments-go/pkg v0.0.0-20260925082912-43ab778da7ac h1:g/Q+tSCha9WdDmoNIGjb/w8XSNtYFfQjL22u5p/e8/c= +github.com/LeanerCloud/cloud-commitments-go/pkg v0.0.0-20260925082912-43ab778da7ac/go.mod h1:pYpkdSOCe6cnmhe7t+3B6S0Txjnbm/2hyc/EAlQn8PI= +github.com/LeanerCloud/cloud-commitments-go/providers/aws v0.0.0-20260926232454-692cacc627d1 h1:DRUrRE6t3hNVIcmx8A4wN0IZpV+jiDY7Oj4iRQdXpx0= +github.com/LeanerCloud/cloud-commitments-go/providers/aws v0.0.0-20260926232454-692cacc627d1/go.mod h1:CTwoaiQJNefXp5W0AoQcGokMMcCDJQ9m+ML5PUHU6KQ= +github.com/LeanerCloud/cloud-commitments-go/providers/azure v0.0.0-20260926232454-692cacc627d1 h1:EgyVBX+pnrV/HqY1nfzSYd5LCpk7tGWZr6EA/n7Wm3I= +github.com/LeanerCloud/cloud-commitments-go/providers/azure v0.0.0-20260926232454-692cacc627d1/go.mod h1:SPzd/neHw+jTyyKY0rrCVgh38DsYSs0VVQAvc2OJy+s= +github.com/LeanerCloud/cloud-commitments-go/providers/gcp v0.0.0-20260926232454-692cacc627d1 h1:GGfRkY/Eo2T39VHBSOBcygAt1d8uZStByHkyRNu25G0= +github.com/LeanerCloud/cloud-commitments-go/providers/gcp v0.0.0-20260926232454-692cacc627d1/go.mod h1:KNRux6gPe5WpG0U3P0wCEEe6+Su00+dnaXpooNBKQGk= github.com/aws/aws-sdk-go-v2 v1.41.5 h1:dj5kopbwUsVUVFgO4Fi5BIT3t4WyqIDjGKCangnV/yY= github.com/aws/aws-sdk-go-v2 v1.41.5/go.mod h1:mwsPRE8ceUUpiTgF7QmQIJ7lgsKUPQOUl3o72QBrE1o= -github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.8 h1:eBMB84YGghSocM7PsjmmPffTa+1FBUeNvGvFou6V/4o= -github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.8/go.mod h1:lyw7GFp3qENLh7kwzf7iMzAxDn+NzjXEAGjKS2UOKqI= github.com/aws/aws-sdk-go-v2/config v1.29.12 h1:Y/2a+jLPrPbHpFkpAAYkVEtJmxORlXoo5k2g1fa2sUo= github.com/aws/aws-sdk-go-v2/config v1.29.12/go.mod h1:xse1YTjmORlb/6fhkWi8qJh3cvZi4JoVNhc+NbJt4kI= github.com/aws/aws-sdk-go-v2/credentials v1.17.65 h1:q+nV2yYegofO/SUXruT+pn4KxkxmaQ++1B/QedcKBFM= @@ -116,8 +100,6 @@ github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.21 h1:PEgGVtPoB6NTpPrBgq github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.21/go.mod h1:p+hz+PRAYlY3zcpJhPwXlLC4C+kqn70WIHwnzAfs6ps= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 h1:bIqFDwgGXXN1Kpp99pDOdKMTTb5d2KyU5X/BZxjOkRo= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3/go.mod h1:H5O/EsxDWyU+LP/V8i5sm8cxoZgc2fdNR9bxlOFrQTo= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.22 h1:rWyie/PxDRIdhNf4DzRk0lvjVOqFJuNnO8WwaIRVxzQ= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.22/go.mod h1:zd/JsJ4P7oGfUhXn1VyLqaRZwPmZwg44Jf2dS84Dm3Y= github.com/aws/aws-sdk-go-v2/service/costexplorer v1.61.0 h1:T9Ms/lReZ3iRFdAtXS9IlhLbWoM2fKUOjJwcgmjT7ig= github.com/aws/aws-sdk-go-v2/service/costexplorer v1.61.0/go.mod h1:AFQ/jaLX9hhiVPxyNKowOchXlpwIYSfYg8bzuXi2gBA= github.com/aws/aws-sdk-go-v2/service/ec2 v1.251.2 h1:6TssXFfLHcwUS5E3MdYKkCFeOrYVBlDhJjs5kRJp0ic= @@ -128,10 +110,6 @@ github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7 h1:5EniKhL github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7/go.mod h1:x0nZssQ3qZSnIcePWLvcoFisRXJzcTVvYpAAdYX8+GI= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.21 h1:c31//R3xgIJMSC8S6hEVq+38DcvUlgFY0FM6mSI5oto= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.21/go.mod h1:r6+pf23ouCB718FUxaqzZdbpYFyDtehyZcmP5KL9FkA= -github.com/aws/aws-sdk-go-v2/service/kms v1.50.4 h1:PgD1y0ZagPokGIZPmejCBUySBzOFDN+leZxCOfb1OEQ= -github.com/aws/aws-sdk-go-v2/service/kms v1.50.4/go.mod h1:FfXDb5nXrsoGgxsBFxwxr3vdHXheC2tV+6lmuLghhjQ= -github.com/aws/aws-sdk-go-v2/service/lambda v1.89.0 h1:e4NAllPs/ygQ7W4dTlAuP5N7QpCT+rTij3S8UOv2DD4= -github.com/aws/aws-sdk-go-v2/service/lambda v1.89.0/go.mod h1:6HBXRyFFqOw+ALkJ6YGHfrr20/YXYv6X9pcZErXRvCA= github.com/aws/aws-sdk-go-v2/service/memorydb v1.31.4 h1:MUW9N/0Y/Wkl4Jt5l9xDWB+nZjaEUwUm56ViraOiBks= github.com/aws/aws-sdk-go-v2/service/memorydb v1.31.4/go.mod h1:xTkekmoJ/62dew9BDNBsl3DPrDZh4eOZtxiJsi+ocas= github.com/aws/aws-sdk-go-v2/service/opensearch v1.52.3 h1:lHnod6e9i7gBkixiA3Wqoj3hX3a/NQELZl1/yPpPXpE= @@ -146,10 +124,6 @@ github.com/aws/aws-sdk-go-v2/service/savingsplans v1.31.0 h1:cGxQBpfDQZNtMjGlCd2 github.com/aws/aws-sdk-go-v2/service/savingsplans v1.31.0/go.mod h1:Osfg3coILx7t46vKS5OWoov989SA4fCoGwSuYrztNEg= github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.40.3 h1:QYBY43OlvzRPww1gSZ1kihyqzXg32rweA3fql5ubSLA= github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.40.3/go.mod h1:STWNrwWdskQ0J7amsVBxHM6DPrpNgJS2GBcUhC7pDeU= -github.com/aws/aws-sdk-go-v2/service/sesv2 v1.42.0 h1:lXhspff64u6oJb07kZXD4BEtPWwXMJ6If9z9tuGCB/Y= -github.com/aws/aws-sdk-go-v2/service/sesv2 v1.42.0/go.mod h1:Z+Z0h55/LLphBV9tYCYMoDxoe3Tgqqq2w+bjsHT9ktw= -github.com/aws/aws-sdk-go-v2/service/sns v1.34.0 h1:8yQWCA0+6TG7uTq8GyRif8RNhPj7vkGs0ld736zHEjA= -github.com/aws/aws-sdk-go-v2/service/sns v1.34.0/go.mod h1:PJtxxMdj747j8DeZENRTTYAz/lx/pADn/U0k7YNNiUY= github.com/aws/aws-sdk-go-v2/service/sso v1.25.2 h1:pdgODsAhGo4dvzC3JAG5Ce0PX8kWXrTZGx+jxADD+5E= github.com/aws/aws-sdk-go-v2/service/sso v1.25.2/go.mod h1:qs4a9T5EMLl/Cajiw2TcbNt2UNo/Hqlyp+GiuG4CFDI= github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.0 h1:90uX0veLKcdHVfvxhkWUQSCi5VabtwMLFutYiRke4oo= @@ -158,42 +132,13 @@ github.com/aws/aws-sdk-go-v2/service/sts v1.33.17 h1:PZV5W8yk4OtH1JAuhV2PXwwO9v5 github.com/aws/aws-sdk-go-v2/service/sts v1.33.17/go.mod h1:cQnB8CUnxbMU82JvlqjKR2HBOm3fe9pWorWBza6MBJ4= github.com/aws/smithy-go v1.24.2 h1:FzA3bu/nt/vDvmnkg+R8Xl46gmzEDam6mZ1hzmwXFng= github.com/aws/smithy-go v1.24.2/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc= -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/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2 h1:aBangftG7EVZoUb69Os8IaYg++6uMOdKK83QtkkvJik= github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2/go.mod h1:qwXFYgsP6T7XnJtbKlf1HP8AjxZZyzxMmc+Lq5GjlU4= -github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI= -github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= -github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE= -github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk= -github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= -github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= -github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A= -github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw= -github.com/coreos/go-oidc/v3 v3.18.0 h1:V9orjXynvu5wiC9SemFTWnG4F45v403aIcjWo0d41+A= -github.com/coreos/go-oidc/v3 v3.18.0/go.mod h1:DYCf24+ncYi+XkIH97GY1+dqoRlbaSI26KVTCI9SrY4= -github.com/cpuguy83/dockercfg v0.3.2 h1:DlJTyZGBDlXqUZ2Dk2Q3xHs/FtnooJJVaad2S9GKorA= -github.com/cpuguy83/dockercfg v0.3.2/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s= -github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dhui/dktest v0.4.6 h1:+DPKyScKSEp3VLtbMDHcUq6V5Lm5zfZZVb0Sk7Ahom4= -github.com/dhui/dktest v0.4.6/go.mod h1:JHTSYDtKkvFNFHJKqCzVzqXecyv+tKt8EzceOmQOgbU= -github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= -github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/docker/docker v28.5.1+incompatible h1:Bm8DchhSD2J6PsFzxC35TZo4TLGR2PdW/E69rU45NhM= -github.com/docker/docker v28.5.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94= -github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE= -github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= -github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= -github.com/ebitengine/purego v0.10.0 h1:QIw4xfpWT6GWTzaW5XEKy3HXoqrJGx1ijYHzTF0/ISU= -github.com/ebitengine/purego v0.10.0/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= github.com/envoyproxy/go-control-plane v0.14.0 h1:hbG2kr4RuFj222B6+7T83thSPqLjwBIfQawTkC++2HA= github.com/envoyproxy/go-control-plane v0.14.0/go.mod h1:NcS5X47pLl/hfqxU70yPwL9ZMkUlwlKxtAohpi2wBEU= github.com/envoyproxy/go-control-plane/envoy v1.37.0 h1:u3riX6BoYRfF4Dr7dwSOroNfdSbEPe9Yyl09/B6wBrQ= @@ -211,19 +156,12 @@ github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= -github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY= github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= -github.com/golang-migrate/migrate/v4 v4.19.1 h1:OCyb44lFuQfYXYLx1SCxPZQGU7mcaZ7gH9yH4jSFbBA= -github.com/golang-migrate/migrate/v4 v4.19.1/go.mod h1:CTcgfjxhaUtsLipnLoQRWCrjYXycRz/g5+RWDuYgPrE= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= -github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= -github.com/google/jsonschema-go v0.4.3 h1:/DBOLZTfDow7pe2GmaJNhltueGTtDKICi8V8p+DQPd0= -github.com/google/jsonschema-go v0.4.3/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE= github.com/google/martian/v3 v3.3.3 h1:DIhPTQrbPkgs2yJYdXU/eNACCG5DVQjySNRNlflZ9Fc= github.com/google/martian/v3 v3.3.3/go.mod h1:iEPrYcgCF7jA9OtScMFQyAlZZ4YXTKEtJ1E6RWzmBA0= github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0= @@ -236,34 +174,14 @@ github.com/googleapis/gax-go/v2 v2.21.0 h1:h45NjjzEO3faG9Lg/cFrBh2PgegVVgzqKzuZl github.com/googleapis/gax-go/v2 v2.21.0/go.mod h1:But/NJU6TnZsrLai/xBAQLLz+Hc7fHZJt/hsCz3Fih4= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/jackc/pgerrcode v0.0.0-20220416144525-469b46aa5efa h1:s+4MhCQ6YrzisK6hFJUX53drDT4UsSW3DEhKn0ifuHw= -github.com/jackc/pgerrcode v0.0.0-20220416144525-469b46aa5efa/go.mod h1:a/s9Lp5W7n/DD0VrVoyJ00FbP2ytTPDVOivvn2bMlds= -github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= -github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= -github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= -github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= -github.com/jackc/pgx/v5 v5.9.2 h1:3ZhOzMWnR4yJ+RW1XImIPsD1aNSz4T4fyP7zlQb56hw= -github.com/jackc/pgx/v5 v5.9.2/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4= -github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= -github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/keybase/go-keychain v0.0.1 h1:way+bWYa6lDppZoZcgMbYsvC7GxljxrskdNInRtuthU= github.com/keybase/go-keychain v0.0.1/go.mod h1:PdEILRW3i9D8JcdM+FmY6RwkHGnhHxXwkPPMeUgOK1k= -github.com/klauspost/compress v1.18.7 h1:aUyZsS4kH3QTKurYhAOwAHxllVPnOthb3vPfnF1Ehjw= -github.com/klauspost/compress v1.18.7/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ= 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= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= -github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= -github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= -github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE= -github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= -github.com/mdelapenya/tlscert v0.2.0 h1:7H81W6Z/4weDvZBNOfQte5GpIMo0lGYEeWbkGp5LJHI= -github.com/mdelapenya/tlscert v0.2.0/go.mod h1:O4njj3ELLnJjGdkN7M/vIVCpZ+Cf0L6muqOG4tLSl8o= github.com/microsoft/kiota-abstractions-go v1.9.4 h1:VI3UVzSCQHHhRswe3jyaAQHUQWIFhUMp0z5mtZbTbcs= github.com/microsoft/kiota-abstractions-go v1.9.4/go.mod h1:f06pl3qSyvUHEfVNkiRpXPkafx7khZqQEb71hN/pmuU= github.com/microsoft/kiota-authentication-azure-go v1.3.1 h1:AGta92S6IL1E6ZMDb8YYB7NVNTIFUakbtLKUdY5RTuw= @@ -282,87 +200,31 @@ github.com/microsoftgraph/msgraph-sdk-go v1.99.0 h1:FRR4RcbuhKBQP3klg4jCp05ntz/N github.com/microsoftgraph/msgraph-sdk-go v1.99.0/go.mod h1:qxzY5SaoPigY6/Dpyfg4uigQjNDvL+sZl6fzD6EpWeQ= github.com/microsoftgraph/msgraph-sdk-go-core v1.4.1 h1:k3YIaJm57ufoEX0KdsEY4l1X9BAMxEqrwr4a7WMRDzY= github.com/microsoftgraph/msgraph-sdk-go-core v1.4.1/go.mod h1:yNqPNhXee2w9cZzkJW5mL1utVMSInsQSo/TyEB5sup8= -github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= -github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= -github.com/moby/go-archive v0.3.0 h1:nos4BtzzUIqB406BgQnWGMI4qib9BZ8XUHU+ucv/n1c= -github.com/moby/go-archive v0.3.0/go.mod h1:Npdv43fFqlhZW7Xo8fbm3ZMYFvAGNviUPqX21VERbcE= -github.com/moby/moby/api v1.54.1 h1:TqVzuJkOLsgLDDwNLmYqACUuTehOHRGKiPhvH8V3Nn4= -github.com/moby/moby/api v1.54.1/go.mod h1:+RQ6wluLwtYaTd1WnPLykIDPekkuyD/ROWQClE83pzs= -github.com/moby/moby/client v0.4.0 h1:S+2XegzHQrrvTCvF6s5HFzcrywWQmuVnhOXe2kiWjIw= -github.com/moby/moby/client v0.4.0/go.mod h1:QWPbvWchQbxBNdaLSpoKpCdf5E+WxFAgNHogCWDoa7g= -github.com/moby/patternmatcher v0.6.1 h1:qlhtafmr6kgMIJjKJMDmMWq7WLkKIo23hsrpR3x084U= -github.com/moby/patternmatcher v0.6.1/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= -github.com/moby/sys/sequential v0.7.0 h1:ASQNGNROJSuOO6LL6bPHbKvuZu6NU8P4ldPWk31zj/8= -github.com/moby/sys/sequential v0.7.0/go.mod h1:NfSTAp6V3fw4tmkD62PEcOKeZKquXT8VKCkf7aVR79o= -github.com/moby/sys/user v0.4.1 h1:RgjRlaDKi/Xmyrz4t8lyzXT6v2ooFeO/7xtchmhVWE0= -github.com/moby/sys/user v0.4.1/go.mod h1:E9QsW5WRe1kUAf7kW8hXKwu1uhsZEAdPLYHYSDudF4Y= -github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g= -github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28= -github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ= -github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc= -github.com/modelcontextprotocol/go-sdk v1.6.1 h1:0zOSupjKUxPKSocPT1Wtago+mUHU2/uZ4xSOY0FGReU= -github.com/modelcontextprotocol/go-sdk v1.6.1/go.mod h1:kzm3kzFL1/+AziGOE0nUs3gvPoNxMCvkxokMkuFapXQ= -github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= -github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= -github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= -github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= -github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= -github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M= -github.com/pashagolub/pgxmock/v4 v4.9.0 h1:itlO8nrVRnzkdMBXLs8pWUyyB2PC3Gku0WGIj/gGl7I= -github.com/pashagolub/pgxmock/v4 v4.9.0/go.mod h1:9L57pC193h2aKRHVyiiE817avasIPZnPwPlw3JczWvM= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo= github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU= -github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/segmentio/asm v1.1.3 h1:WM03sfUOENvvKexOLp+pCqgb/WDjsi7EK8gIsICtzhc= -github.com/segmentio/asm v1.1.3/go.mod h1:Ld3L4ZXGNcSLRg4JBsZ3//1+f/TjYl0Mzen/DQy1EJg= -github.com/segmentio/encoding v0.5.4 h1:OW1VRern8Nw6ITAtwSZ7Idrl3MXCFwXHPgqESYfvNt0= -github.com/segmentio/encoding v0.5.4/go.mod h1:HS1ZKa3kSN32ZHVZ7ZLPLXWvOVIiZtyJnO1gPH1sKt0= -github.com/shirou/gopsutil/v4 v4.26.3 h1:2ESdQt90yU3oXF/CdOlRCJxrP+Am1aBYubTMTfxJ1qc= -github.com/shirou/gopsutil/v4 v4.26.3/go.mod h1:LZ6ewCSkBqUpvSOf+LsTGnRinC6iaNUNMGBtDkJBaLQ= -github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w= -github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g= github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spiffe/go-spiffe/v2 v2.6.0 h1:l+DolpxNWYgruGQVV0xsfeya3CsC7m8iBzDnMpsbLuo= -github.com/spiffe/go-spiffe/v2 v2.6.0/go.mod h1:gm2SeUoMZEtpnzPNs2Csc0D/gX33k1xIx7lEzqblHEs= +github.com/spiffe/go-spiffe/v2 v2.7.0 h1:uXe1MflJoHw58wAUvxVlcM7WpKtijWG7I1UidcGh6g4= +github.com/spiffe/go-spiffe/v2 v2.7.0/go.mod h1:47Q0Q9/AqGha8QLHp+kxpH4Wca7X7EnOtlIJy3mxZ3U= github.com/std-uritemplate/std-uritemplate/go/v2 v2.0.3 h1:7hth9376EoQEd1hH4lAp3vnaLP2UMyxuMMghLKzDHyU= github.com/std-uritemplate/std-uritemplate/go/v2 v2.0.3/go.mod h1:Z5KcoM0YLC7INlNhEezeIZ0TZNYf7WSNO0Lvah4DSeQ= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.5.3 h1:jmXUvGomnU1o3W/V5h2VEradbpJDwGrzugQQvL0POH4= github.com/stretchr/objx v0.5.3/go.mod h1:rDQraq+vQZU7Fde9LOZLr8Tax6zZvy4kuNKF+QYS+U0= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 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/testcontainers/testcontainers-go v0.42.0 h1:He3IhTzTZOygSXLJPMX7n44XtK+qhjat1nI9cneBbUY= -github.com/testcontainers/testcontainers-go v0.42.0/go.mod h1:vZjdY1YmUA1qEForxOIOazfsrdyORJAbhi0bp8plN30= -github.com/testcontainers/testcontainers-go/modules/postgres v0.42.0 h1:GCbb1ndrF7OTDiIvxXyItaDab4qkzTFJ48LKFdM7EIo= -github.com/testcontainers/testcontainers-go/modules/postgres v0.42.0/go.mod h1:IRPBaI8jXdrNfD0e4Zm7Fbcgaz5shKxOQv4axiL09xs= -github.com/tklauser/go-sysconf v0.3.16 h1:frioLaCQSsF5Cy1jgRBrzr6t502KIIwQ0MArYICU0nA= -github.com/tklauser/go-sysconf v0.3.16/go.mod h1:/qNL9xxDhc7tx3HSRsLWNnuzbVfh3e7gh/BmM179nYI= -github.com/tklauser/numcpus v0.11.0 h1:nSTwhKH5e1dMNsCdVBukSZrURJRoHbSEQjdEbY+9RXw= -github.com/tklauser/numcpus v0.11.0/go.mod h1:z+LwcLq54uWZTX0u/bGobaV34u6V7KNlTZejzM6/3MQ= -github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4= -github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4= -github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= -github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= -go.opentelemetry.io/contrib/detectors/gcp v1.43.0 h1:62yY3dT7/ShwOxzA0RsKRgshBmfElKI4d/Myu2OxDFU= -go.opentelemetry.io/contrib/detectors/gcp v1.43.0/go.mod h1:RyaZMFY7yi1kAs45S6mbFGz8O8rqB0dTY14uzvG4LCs= +go.opentelemetry.io/contrib/detectors/gcp v1.44.0 h1:NmLfL734pJhM0JKaYd2Y28+nY9dPRWYAAbxhRCrKXPw= +go.opentelemetry.io/contrib/detectors/gcp v1.44.0/go.mod h1:tNAsgd8avTGke1+MndXlU5Cru4PQ9Ai/cCNWQv/ZJ/s= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 h1:YH4g8lQroajqUwWbq/tr2QX1JFmEXaDLgG+ew9bLMWo= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0/go.mod h1:fvPi2qXDqFs8M4B4fmJhE92TyQs9Ydjlg3RvfUp+NbQ= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus= @@ -373,23 +235,22 @@ go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.38.0 h1:wm/Q0GAAykXv83 go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.38.0/go.mod h1:ra3Pa40+oKjvYh+ZD3EdxFZZB0xdMfuileHAm4nNN7w= go.opentelemetry.io/otel/metric v1.44.0 h1:1w0gILTcHdr3YI+ixLyjemwrVnsMURbTZFrSYCdDdmc= go.opentelemetry.io/otel/metric v1.44.0/go.mod h1:8O7hanEPBNgEMmybD3s2VBKcgWOCsA6tzHBPODAiquo= -go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg= -go.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg= -go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw= -go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A= +go.opentelemetry.io/otel/metric/x v0.66.0 h1:YkCrx1zLOChi9ZcZ6euupOcsgzbVlec7D/xoEU1+cTA= +go.opentelemetry.io/otel/metric/x v0.66.0/go.mod h1:d1+BDj9t96do0/1LoU1ayfCv79ZgNE41qbhBvnMOBZk= +go.opentelemetry.io/otel/sdk v1.44.0 h1:nHYwb9lK+fJPU/dnT6s7W7Z8itMWyqrnVfbheVYrZ58= +go.opentelemetry.io/otel/sdk v1.44.0/go.mod h1:Osuydd3Se74nqjAKxid74N5eC+jfEqfTegHRnq58oK0= +go.opentelemetry.io/otel/sdk/metric v1.44.0 h1:3LlKgI+VjbVsjNRFZJZAJ30WjXC5VkNRks6si09iEfI= +go.opentelemetry.io/otel/sdk/metric v1.44.0/go.mod h1:5B5pMARnXxKhltooO4xUuCBorl65a4EpnTalObqOigA= go.opentelemetry.io/otel/trace v1.44.0 h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk= go.opentelemetry.io/otel/trace v1.44.0/go.mod h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUSvW53sW1Q8PIE= golang.org/x/crypto v0.56.0 h1:GUh5Ii4J5jtcseSMiRqr1jXCNHoxjeV9Fmekc2oLy6Y= golang.org/x/crypto v0.56.0/go.mod h1:OMW5y6CY9l38uPLmxU6l6pwcXp1obtLo3e6gT7gQR2I= -golang.org/x/net v0.57.0 h1:K5+3DljvIuDG9/Jv9rvyMywYNFCQ9RSUY6OOTTkT+tE= -golang.org/x/net v0.57.0/go.mod h1:KpXc8iv+r3XplLAG/f7Jsf9RPszJzdR0f58q9vGOuEU= +golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To= +golang.org/x/net v0.58.0/go.mod h1:YwCddHnFlT7eLQqVprV19OnhLGtc5xOKgE0RyqgfWAU= golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs= golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q= golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= -golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs= golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= @@ -399,30 +260,22 @@ golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8= golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M= golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U= golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno= -golang.org/x/tools v0.48.0 h1:3+hClM1aLL5mjMKm5ovokw9epgRXPuu2tILgismM6RE= -golang.org/x/tools v0.48.0/go.mod h1:08xX0orndb/F7jJxGDicx061tyd5pcMto75YMAXr6lk= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= google.golang.org/api v0.274.0 h1:aYhycS5QQCwxHLwfEHRRLf9yNsfvp1JadKKWBE54RFA= google.golang.org/api v0.274.0/go.mod h1:JbAt7mF+XVmWu6xNP8/+CTiGH30ofmCmk9nM8d8fHew= google.golang.org/genproto v0.0.0-20260319201613-d00831a3d3e7 h1:XzmzkmB14QhVhgnawEVsOn6OFsnpyxNPRY9QV01dNB0= google.golang.org/genproto v0.0.0-20260319201613-d00831a3d3e7/go.mod h1:L43LFes82YgSonw6iTXTxXUX1OlULt4AQtkik4ULL/I= -google.golang.org/genproto/googleapis/api v0.0.0-20260414002931-afd174a4e478 h1:yQugLulqltosq0B/f8l4w9VryjV+N/5gcW0jQ3N8Qec= -google.golang.org/genproto/googleapis/api v0.0.0-20260414002931-afd174a4e478/go.mod h1:C6ADNqOxbgdUUeRTU+LCHDPB9ttAMCTff6auwCVa4uc= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478 h1:RmoJA1ujG+/lRGNfUnOMfhCy5EipVMyvUE+KNbPbTlw= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= -google.golang.org/grpc v1.82.1 h1:NnAxzGRA0677vCa4BUkOAnO5+FfQqVl9iUXeD0IqcGE= -google.golang.org/grpc v1.82.1/go.mod h1:yzTZ1TB1Z3SG+LIYaI+WiE8D5+PZ3ArnrSp8zF3+/ZA= +google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa h1:Kjn0N0tCrDgiAFW+lGO4JZ3ck44CehvJQMAwj9QF0G8= +google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa/go.mod h1:q4lMZS6kskjT5HvCPrnnypcDPVJqT/f4nfxmkE7gryY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa h1:mZHHdPZl0dbGHCflZgAq/Q468DWVFcU2whhB2KAo8fk= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= +google.golang.org/grpc v1.83.2 h1:EManeRomTObA0BU7I8vXgg/78uE5MJ9M8B39EX2WscU= +google.golang.org/grpc v1.83.2/go.mod h1:YPI1hK3kDked6iHvgX3tR0y+nX/qpMFKhPgFsokw1S8= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= 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= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q= -gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA= -pgregory.net/rapid v1.2.0 h1:keKAYRcjm+e1F0oAuU5F5+YPAWcyxNNRK2wud503Gnk= -pgregory.net/rapid v1.2.0/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04= diff --git a/go.work b/go.work index 3b2ce4f..72eaf6f 100644 --- a/go.work +++ b/go.work @@ -1,10 +1,3 @@ go 1.26.6 -use ( - . - ./pkg - ./providers/aws - ./providers/azure - ./providers/gcp - ./tests/e2e -) +use . diff --git a/scripts/gofmt-hook.sh b/scripts/gofmt-hook.sh new file mode 100644 index 0000000..66ca668 --- /dev/null +++ b/scripts/gofmt-hook.sh @@ -0,0 +1,45 @@ +#!/usr/bin/env bash +set -euo pipefail + +if [[ "$#" -eq 0 ]]; then + exit 0 +fi + +version_file="go.mod" +if [[ ! -f "$version_file" ]]; then + echo "missing Go version source: $version_file" >&2 + exit 1 +fi + +versions="$(awk ' + /^[[:space:]]*go([[:space:]]|$)/ { + if (NF != 2) { print "__malformed__"; next } + print $2 + } +' "$version_file")" +version_count="$(printf '%s\n' "$versions" | awk 'NF { count++ } END { print count + 0 }')" +if [[ "$version_count" -ne 1 || ! "$versions" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "expected exactly one patch-level Go version in $version_file" >&2 + exit 1 +fi + +if ! goroot="$(GOTOOLCHAIN="go${versions}" go env GOROOT)"; then + echo "failed to resolve GOROOT for Go ${versions}" >&2 + exit 1 +fi +if [[ -z "$goroot" || ! -x "$goroot/bin/gofmt" ]]; then + echo "selected GOROOT has no executable gofmt: ${goroot:-}" >&2 + exit 1 +fi + +status=0 +if output="$("$goroot/bin/gofmt" -l "$@")"; then + : +else + status=$? +fi +if [[ -n "$output" ]]; then + printf '%s\n' "$output" + [[ "$status" -eq 0 ]] && status=1 +fi +exit "$status" diff --git a/scripts/gosec-hook.sh b/scripts/gosec-hook.sh index d6ba767..2334c76 100755 --- a/scripts/gosec-hook.sh +++ b/scripts/gosec-hook.sh @@ -37,11 +37,9 @@ GOSEC_BIN="${HOME}/.cache/pre-commit-gosec/v${GOSEC_VERSION}/gosec" GOSEC_EXCLUDE="G101,G104,G115,G117,G118,G122,G204,G301,G304,G402,G505,G702,G703,G705,G706" -# Module roots in longest-prefix order (so "providers/azure" is checked before -# a hypothetical "providers" root). Must mirror the per-module loop in -# .github/workflows/ci.yml (root, pkg, providers/{aws,azure,gcp}, tests/e2e) -# so a changed file under tests/e2e is scanned from within its own module. -MODULE_DIRS="tests/e2e providers/azure providers/aws providers/gcp pkg" +# This repository has a single Go module, so every changed Go file belongs to +# the root module. +MODULE_DIRS="" # ---- helpers ---------------------------------------------------------------- From c8ecf9c5a69eccd85414da29db628c55ffee0d3b Mon Sep 17 00:00:00 2001 From: Cristian Magherusan-Stanciu Date: Sun, 27 Sep 2026 19:12:05 +0200 Subject: [PATCH 2/4] docs(cli): repoint maintainer commands at cloud-commitments-cli The gh run/pr/issue invocations in CLAUDE.md targeted LeanerCloud/CUDly, which does not carry this repository's workflows or pull requests, and the Go module notes still described pkg/ as a local module behind a replace directive. Both now name cloud-commitments-cli and the published shared modules that go.mod pins. --- CLAUDE.md | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 8cd91dc..35ba2aa 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -49,9 +49,8 @@ ## Go Module Notes - This project does NOT use a vendor directory. Do not use `go mod vendor`. -- The `pkg/` directory is a separate Go module (`github.com/LeanerCloud/CUDly/pkg`) with a `replace` directive in the root `go.mod`. -- Build and test normally with `go build ./...` and `go test ./...` from the root. -- Run `go test ./pkg/...` from the `pkg/` directory when working on the submodule. +- The shared libraries and cloud providers come from `github.com/LeanerCloud/cloud-commitments-go`, pinned to fixed versions in `go.mod`. There are no `replace` directives and no sibling checkout is needed. +- Build and test with `make build` and `make test-unit` from the repository root. ## Build & Test @@ -103,7 +102,7 @@ Mechanics: 1. After `git push`, list the runs the push triggered: ```bash - gh run list --repo LeanerCloud/CUDly --commit "$(git rev-parse HEAD)" \ + gh run list --repo LeanerCloud/cloud-commitments-cli --commit "$(git rev-parse HEAD)" \ --limit 10 --json databaseId,workflowName,status ``` @@ -179,10 +178,10 @@ CodeRabbit: ```bash # Right after `gh pr create ...` returns the PR URL: # Derive PR_NUM from the current branch context (avoids brittle hand-copying). -PR_NUM=$(gh pr view "$(git rev-parse --abbrev-ref HEAD)" --repo LeanerCloud/CUDly --json number --jq '.number') +PR_NUM=$(gh pr view "$(git rev-parse --abbrev-ref HEAD)" --repo LeanerCloud/cloud-commitments-cli --json number --jq '.number') ISSUE_NUM= -LABELS=$(gh issue view "$ISSUE_NUM" --repo LeanerCloud/CUDly --json labels \ +LABELS=$(gh issue view "$ISSUE_NUM" --repo LeanerCloud/cloud-commitments-cli --json labels \ --jq '[.labels[].name | select(test("^(priority|severity|urgency|impact|effort|type)/")) ] + (if [.labels[].name] | any(. == "triaged") then ["triaged"] else [] end) | join(",")') @@ -191,7 +190,7 @@ LABELS=$(gh issue view "$ISSUE_NUM" --repo LeanerCloud/CUDly --json labels \ # silently break this MANDATORY flow. If the closing issue has no triage # labels in the selected classes, surface the gap deterministically instead. if [ -n "$LABELS" ]; then - gh pr edit "$PR_NUM" --repo LeanerCloud/CUDly --add-label "$LABELS" + gh pr edit "$PR_NUM" --repo LeanerCloud/cloud-commitments-cli --add-label "$LABELS" else echo "WARN: issue #$ISSUE_NUM has no priority/severity/urgency/impact/effort/type labels" echo " Triage the issue first, then re-run the label-mirror step." @@ -199,7 +198,7 @@ else fi # Verify -gh pr view "$PR_NUM" --repo LeanerCloud/CUDly --json labels \ +gh pr view "$PR_NUM" --repo LeanerCloud/cloud-commitments-cli --json labels \ --jq '[.labels[].name] | sort | join(",")' ``` From 9420c175dcc8e617ac0a79e8c605fc5fdee305d6 Mon Sep 17 00:00:00 2001 From: Cristian Magherusan-Stanciu Date: Sun, 27 Sep 2026 20:01:38 +0200 Subject: [PATCH 3/4] docs(cli): rewrite CONTRIBUTING for the standalone repository The guide was a verbatim copy of the monorepo's, so its setup steps could not work here: the clone and upstream URLs named CUDly, the go.work example listed ./pkg and ./providers/*, the test example ran ./providers/aws/..., and the project tree and the service/provider authoring steps all pointed at modules that now live in cloud-commitments-go. Each region is rewritten against this repository's actual layout. Policy sections are unchanged, and no section was dropped. --- CONTRIBUTING.md | 98 +++++++++++++++++++++---------------------------- 1 file changed, 41 insertions(+), 57 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 491072b..61b05e4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,6 +1,6 @@ -# Contributing to CUDly +# Contributing to the CUDly CLI -Thank you for your interest in contributing to CUDly! This document provides guidelines and instructions for contributing. +Thank you for your interest in contributing to CUDly! This document provides guidelines and instructions for contributing to the CLI component. ## Code of Conduct @@ -49,36 +49,31 @@ By participating in this project, you agree to maintain a respectful and inclusi ### Prerequisites - Go 1.26.6 or later (the floor set by the `go` directive in `go.mod`) -- AWS/Azure/GCP credentials for integration testing - Git ### Getting Started ```bash # Clone your fork -git clone https://github.com/YOUR_USERNAME/CUDly.git -cd CUDly +git clone https://github.com/YOUR_USERNAME/cloud-commitments-cli.git +cd cloud-commitments-cli # Add upstream remote -git remote add upstream https://github.com/LeanerCloud/CUDly.git +git remote add upstream https://github.com/LeanerCloud/cloud-commitments-cli.git # Install dependencies go mod download -# Build the project -go build -o cudly cmd/*.go +# Build +make build -# Run tests -go test ./... +# Run the unit tests +make test-unit ``` ### Go workspace and worktrees (gopls setup) -The repo ships a `go.work` that lists every module in this repository (the -root module, `pkg`, the three provider modules, and `tests/e2e`). This is -enough for standard clones. When you are working across multiple git worktrees -simultaneously, gopls needs each worktree's module added to the workspace or it -flags every file in the sibling trees with `BrokenImport` / `undefined: `. +The repo ships a `go.work` that lists only this repository's own module. The shared libraries and providers are consumed at the versions pinned in `go.mod`, so there is nothing else to set up for a standard clone. When you are working across multiple git worktrees simultaneously, gopls needs each worktree's module added to the workspace or it flags every file in the sibling trees with `BrokenImport` / `undefined: `. **Do not edit the committed `go.work`** for local paths -- they vary per developer and per session. @@ -94,13 +89,8 @@ go 1.26.6 use ( . - ./pkg - ./providers/aws - ./providers/azure - ./providers/gcp - ./tests/e2e - ../.worktrees/CUDly/fix-516 - ../.worktrees/CUDly/feat-something + ../.worktrees/cloud-commitments-cli/fix-516 + ../.worktrees/cloud-commitments-cli/feat-something ) ``` @@ -130,14 +120,17 @@ The committed `go.work` (listing only this repository's own modules) keeps ### Running Tests ```bash -# Run all tests -go test ./... +# Run the unit tests +make test-unit + +# The same suite, invoked directly +go test -short -race ./... # Run tests with coverage go test -cover ./... # Run tests for a specific package -go test ./providers/aws/... +go test ./cmd/... # Run tests with verbose output go test -v ./... @@ -152,10 +145,7 @@ We aim to maintain the following minimum test coverage: | Package | Minimum Coverage | |---------|-----------------| -| Service clients | 80% | -| Provider implementations | 70% | -| Common/shared packages | 80% | -| CLI/cmd | 60% | +| CLI commands | 60% | ## Coding Standards @@ -200,38 +190,31 @@ We aim to maintain the following minimum test coverage: ## Project Structure ```text -CUDly/ -├── cmd/ # CLI entry point -├── pkg/ # Shared packages -│ ├── common/ # Cloud-agnostic types -│ └── provider/ # Provider abstraction -├── providers/ # Cloud implementations -│ ├── aws/ # AWS provider -│ │ ├── services/ # Service clients -│ │ └── internal/ # Internal packages -│ ├── azure/ # Azure provider -│ └── gcp/ # GCP provider -└── internal/ # Private packages +cloud-commitments-cli/ +├── cmd/ # CLI entry point, commands, and helpers +├── docs/ # CLI documentation +├── scripts/ # Repository hook and helper scripts +├── go.mod # Pins the shared modules from cloud-commitments-go +└── Makefile # build, test, vet, and lint targets ``` ### Adding a New Service -1. Create the service client in `providers//services/` -2. Implement the `ServiceClient` interface from `pkg/provider` -3. Register the service in the provider's `GetServiceClient` method -4. Add recommendations support if applicable -5. Write comprehensive tests -6. Update documentation +Service clients live in `github.com/LeanerCloud/cloud-commitments-go`. In this +repository: + +1. Add the service's command and flags in `cmd/` +2. Reuse the service client from the pinned provider module +3. Register the service in the CLI's service selection +4. Write comprehensive tests +5. Update `docs/` ### Adding a New Cloud Provider -1. Create a new directory under `providers/` -2. Implement the `Provider` interface from `pkg/provider` -3. Implement required service clients -4. Register the provider using `provider.RegisterProvider()` in `init()` -5. Add authentication documentation -6. Write comprehensive tests -7. Update README with new provider information +Provider implementations live in `github.com/LeanerCloud/cloud-commitments-go` +(`providers/aws`, `providers/azure`, `providers/gcp`). Open the change there. +This repository consumes providers at the versions pinned in `go.mod`, so +bumping that pin is the only change needed here. ## Commit Guidelines @@ -295,9 +278,10 @@ the first page. Now properly iterates all pages. ## Known Issues Sweep -The `known_issues/` directory tracks open tech debt, deferred fixes, and -surfaced bugs that are out of scope for the current PR. To stay useful, it -needs periodic housekeeping. +This repository has no `known_issues/` directory. Deferred work found while +reviewing a change here belongs in this repository's GitHub issues. The +cross-component sweep, which covers `known_issues/` in the platform repository, +is documented there. ### Entry format From 285eabf758ab562c2cec004cb7196e6a11888867 Mon Sep 17 00:00:00 2001 From: Cristian Magherusan-Stanciu Date: Sun, 27 Sep 2026 22:42:38 +0200 Subject: [PATCH 4/4] docs(cli): drop monorepo residue from docs and tooling config The CLI was carved out of the CUDly monorepo but its docs, CHANGELOG, NOTICE, and CI/pre-commit config still described the old repo: a `go build ./...` command that fails (cmd/ is the only main package), deploy workflows that don't exist here, a full monorepo changelog and frontend license notices, dead pre-commit hooks targeting removed terraform/internal/frontend paths, an unused GO_VERSION workflow var, and CLI docs referencing binaries this repo doesn't ship. Co-Authored-By: claude-flow --- .github/workflows/README.md | 659 +++--------------------------------- .github/workflows/ci.yml | 6 - .pre-commit-config.yaml | 85 +---- CHANGELOG.md | 171 +--------- CLAUDE.md | 77 ++--- CONTRIBUTING.md | 8 +- NOTICE | 25 -- docs/cli/README.md | 2 - 8 files changed, 88 insertions(+), 945 deletions(-) diff --git a/.github/workflows/README.md b/.github/workflows/README.md index bf781e3..3abc610 100644 --- a/.github/workflows/README.md +++ b/.github/workflows/README.md @@ -1,23 +1,15 @@ # GitHub Actions Workflows -This directory contains CI/CD workflows for the CUDly project, providing automated testing, deployment, and operations across AWS, GCP, and Azure. +This directory contains CI for the CUDly CLI: automated build, test, lint, +and security checks. There are no deployment workflows here -- this repo +ships a CLI binary, not a running service. -## 📋 Workflows Overview +## Workflows Overview -| Workflow | Purpose | Trigger | Duration | -|----------|---------|---------|----------| -| [ci.yml](#ci-workflow) | Continuous Integration | PR, Push to main | ~10 min | -| [deploy-aws-lambda.yml](#aws-lambda-deployment) | Deploy to AWS Lambda | Push to main, Manual | ~8 min | -| [deploy-aws-fargate.yml](#aws-fargate-deployment) | Deploy to AWS Fargate | Manual | ~10 min | -| [deploy-gcp.yml](#gcp-deployment) | Deploy to GCP Cloud Run | Manual | ~8 min | -| [deploy-azure.yml](#azure-deployment) | Deploy to Azure Container Apps | Manual | ~10 min | -| [deploy-all.yml](#multi-cloud-deployment) | Deploy to all clouds | Manual, Release | ~15 min | -| [database-migration.yml](#database-migrations) | Run DB migrations | Manual | ~5 min | -| [rollback.yml](#rollback) | Rollback deployment | Manual | ~5 min | - -> **Note:** Frontend deployment is handled automatically via Terraform as part of the backend deployment workflows. - ---- +| Workflow | Purpose | Trigger | Required secrets | +|----------|---------|---------|-------------------| +| [ci.yml](#ci-workflow) | Build, test, lint, and security-scan the CLI | PR to `main`/`develop`, push to `main`/`develop`, manual dispatch | `SNYK_TOKEN` (optional) | +| [pre-commit.yml](#pre-commit-workflow) | Run the same hooks as local `pre-commit` against the whole tree | PR to `main`, push to `main` | None | ## CI Workflow @@ -25,19 +17,19 @@ This directory contains CI/CD workflows for the CUDly project, providing automat ### Purpose -Runs comprehensive quality checks on every pull request and push to main branch. +Runs comprehensive quality checks on every pull request and push to `main` +or `develop`. ### Jobs -1. **Lint** - golangci-lint, go vet -2. **Unit Tests** - Go tests with race detection, coverage reporting -3. **Integration Tests** - Tests with real PostgreSQL -4. **Docker Build** - Build and test Docker image -5. **Terraform Validate** - Validate all Terraform configs (AWS, GCP, Azure) -6. **Security Scan** - gosec, trivy, tfsec -7. **Snyk Scan** - Dependency vulnerability scanning -8. **E2E Tests** - Docker Compose end-to-end tests -9. **Cost Estimate** - Infracost cost estimation (PR only) +1. **lint** - golangci-lint, `go vet`, and a gocyclo complexity gate (>10) +2. **workflow-lint** - actionlint and zizmor against `.github/workflows/` +3. **unit-tests** - `go test -race -short ./...` with coverage upload to Codecov +4. **integration-tests** - `go test -race -tags=integration ./...` +5. **security-scan** - govulncheck, gosec (SARIF upload), Trivy filesystem scan +6. **snyk-scan** - Snyk dependency scan (skipped on forked PRs without the token) +7. **cli-build** - `make build`, then `./cudly --help` as a smoke check +8. **ci-success** - gate job that requires every job above to have succeeded ### Triggers @@ -45,642 +37,85 @@ Runs comprehensive quality checks on every pull request and push to main branch. - Pushes to `main` or `develop` - Manual dispatch -### Required Secrets - -- `SNYK_TOKEN` (optional - for Snyk scanning) -- `INFRACOST_API_KEY` (optional - for cost estimation) - -### Required Variables +### Required secrets -- `GO_VERSION` (default: 1.26.6) +- `SNYK_TOKEN` (optional -- Snyk scanning is skipped without it) ### Example ```bash -# Automatically runs on PR +# Runs automatically on push/PR git push origin feature-branch # Or trigger manually gh workflow run ci.yml ``` ---- +## pre-commit Workflow -## AWS Lambda Deployment - -**File:** `deploy-aws-lambda.yml` +**File:** `pre-commit.yml` ### Purpose -Deploy CUDly to AWS Lambda with Function URL. Serverless, event-driven platform. - -### Jobs - -1. **Prepare** - Determine environment and image tag -2. **Build & Push** - Build Docker image, push to ECR -3. **Deploy** - Deploy with Terraform -4. **Test** - Health check and smoke tests +Runs the repository's `.pre-commit-config.yaml` hooks (gofmt, go vet, +go mod tidy, general file hygiene, hadolint, actionlint, zizmor, +markdownlint, gocyclo, git-secrets, gosec, Trivy config) against every file +on pushes and pull requests to `main`. This mirrors what `pre-commit run +--all-files` does locally, so a clean local run should also pass here. ### Triggers -- Push to `main` (deploys to dev) -- Release creation (deploys to prod) -- Manual dispatch with environment selection +- Pull requests to `main` +- Pushes to `main` -### Required Secrets +### Required secrets -- `AWS_ACCESS_KEY_ID` -- `AWS_SECRET_ACCESS_KEY` - -### Required Variables - -- `AWS_REGION` (default: us-east-1) -- `AWS_ACCOUNT_ID` -- `ECR_REPOSITORY` (default: cudly) +None. ### Example ```bash -# Deploy to dev -gh workflow run deploy-aws-lambda.yml -f environment=dev - -# Push to main also deploys to dev -git push origin main - -# Deploy to prod -gh release create v1.0.0 +# Run the same checks locally before pushing +pip install 'pre-commit==4.0.1' +pre-commit install +pre-commit run --all-files ``` -### Output - -- Function URL: `https://.lambda-url.us-east-1.on.aws` -- Deployment info artifact - ---- - -## AWS Fargate Deployment - -**File:** `deploy-aws-fargate.yml` - -### Purpose - -Deploy CUDly to AWS ECS Fargate with ALB. Always-on containerized platform. - -### Jobs - -1. **Build & Push** - Build Docker image, push to ECR -2. **Deploy** - Deploy with Terraform (Fargate mode) -3. **Test** - Health check verification - -### Triggers - -- Manual dispatch only - -### Required Secrets - -- Same as AWS Lambda - -### Example - -```bash -# Deploy to staging with Fargate -gh workflow run deploy-aws-fargate.yml -f environment=staging -``` - ---- - -## GCP Deployment - -**File:** `deploy-gcp.yml` - -### Purpose - -Deploy CUDly to GCP Cloud Run. Serverless container platform. - -### Jobs - -1. **Build & Deploy** - Build, push to Artifact Registry, deploy with Terraform -2. **Test** - Health check and smoke tests - -### Triggers - -- Manual dispatch -- Called by deploy-all.yml - -### Required Secrets - -- `GCP_SA_KEY` (Service Account JSON with permissions) -- `GCP_PROJECT_ID` - -### Required Variables - -- `GCP_REGION` (default: us-central1) -- `ARTIFACT_REGISTRY_REPO` (default: cudly) - -### Example - -```bash -# Deploy to GCP dev -gh workflow run deploy-gcp.yml -f environment=dev -``` - -### Output - -- Service URL: `https://cudly--uc.a.run.app` - ---- - -## Azure Deployment - -**File:** `deploy-azure.yml` - -### Purpose - -Deploy CUDly to Azure Container Apps. Serverless container platform with built-in HTTPS. - -### Jobs - -1. **Build & Deploy** - Build, push to ACR, deploy with Terraform -2. **Test** - Health check and smoke tests - -### Triggers - -- Manual dispatch -- Called by deploy-all.yml - -### Required Secrets - -- `AZURE_CREDENTIALS` (Service Principal JSON) -- `AZURE_SUBSCRIPTION_ID` - -### Required Variables - -- `AZURE_LOCATION` (default: eastus) -- `ACR_NAME` (default: cudlyacr) -- `RESOURCE_GROUP` (default: cudly-rg) - -### Example - -```bash -# Deploy to Azure staging -gh workflow run deploy-azure.yml -f environment=staging -``` - -### Output - -- App URL: `https://..azurecontainerapps.io` - ---- - -## Multi-Cloud Deployment - -**File:** `deploy-all.yml` - -### Purpose - -Orchestrate deployment to multiple cloud providers in parallel. - -### Jobs - -1. **Determine Strategy** - Choose which clouds to deploy to -2. **Deploy AWS Lambda** - Parallel deployment -3. **Deploy AWS Fargate** - Parallel deployment (optional) -4. **Deploy GCP** - Parallel deployment -5. **Deploy Azure** - Parallel deployment -6. **Notify** - Aggregate results - -### Triggers - -- Manual dispatch with provider selection -- Release creation (deploys to all clouds in prod) - -### Required Secrets - -- All secrets from individual deployment workflows - -### Deployment Options - -- `all` - Deploy to AWS, GCP, and Azure -- `aws-only` - AWS Lambda only -- `gcp-only` - GCP Cloud Run only -- `azure-only` - Azure Container Apps only -- `aws-gcp` - AWS and GCP -- `aws-azure` - AWS and Azure -- `gcp-azure` - GCP and Azure - -### Example - -```bash -# Deploy to all clouds (staging) -gh workflow run deploy-all.yml -f environment=staging -f deploy_to=all - -# Deploy to AWS and GCP (prod) -gh workflow run deploy-all.yml -f environment=prod -f deploy_to=aws-gcp - -# Automatic on release -gh release create v1.0.0 -``` - -### Benefits - -- **Disaster Recovery** - Multi-cloud redundancy -- **Cost Optimization** - Compare costs across providers -- **Testing** - Validate across all platforms -- **Global Reach** - Deploy to optimal regions per cloud - ---- - -## Database Migrations - -**File:** `database-migration.yml` - -### Purpose - -Apply or rollback database schema migrations across cloud providers. - -### Jobs - -1. **Validate** - Safety checks -2. **Migrate AWS** - Run golang-migrate on Aurora -3. **Migrate GCP** - Run golang-migrate on Cloud SQL -4. **Migrate Azure** - Run golang-migrate on Flexible Server - -### Triggers - -- Manual dispatch only (safety measure) -- Can be called by deployment workflows - -### Required Secrets - -- `DB_PASSWORD_AWS` -- `DB_PASSWORD_GCP` -- `DB_PASSWORD_AZURE` -- Cloud credentials (same as deployment workflows) - -### Required Variables - -- Database endpoints per environment - -### Migration Directions - -- `up` - Apply migrations (default) -- `down` - Rollback migrations (DANGEROUS) - -### Example - -```bash -# Apply all migrations to AWS dev -gh workflow run database-migration.yml \ - -f cloud=aws \ - -f environment=dev \ - -f direction=up - -# Rollback last 2 migrations on GCP staging -gh workflow run database-migration.yml \ - -f cloud=gcp \ - -f environment=staging \ - -f direction=down \ - -f steps=2 - -# Rollback 1 migration on AWS prod (requires typed confirmation) -gh workflow run database-migration.yml \ - -f cloud=aws \ - -f environment=prod \ - -f direction=down \ - -f steps=1 \ - -f confirm=rollback-prod - -# Apply to all clouds -gh workflow run database-migration.yml \ - -f cloud=all \ - -f environment=prod \ - -f direction=up -``` - -### Safety Features - -- **Validation** - Checks migration files exist before running -- **Explicit steps required** - `direction=down` requires an explicit positive `steps` value; `steps=0` (the default, which would run `down -all` and drop the entire schema) is rejected -- **Production confirmation** - `direction=down` on `environment=prod` additionally requires typing `rollback-prod` in the `confirm` input; omitting or mistyping it blocks the run -- **Defense in depth** - each migrate job independently re-validates the positive-steps constraint, so a future validate regression cannot reach `down -all` -- **Audit Trail** - Records all migrations in the step summary - ---- - -## Rollback - -**File:** `rollback.yml` - -### Purpose - -Quickly rollback to a previous deployment version by redeploying a known-good Docker image. - -### Jobs - -1. **Validate** - Validate image tag and construct image URI -2. **Rollback** - Confirm the image exists in the registry, then deploy it with Terraform -3. **Summary** - Create audit record - -Image existence is verified *inside* each rollback job rather than in a -standalone job. A separate verify job would have to assume the same cloud -deploy role while carrying no `environment:` binding, which is exactly the -ungated-but-credentialed shape that made the workflow exploitable. The -tradeoff is that a rollback to a nonexistent tag now fails after the -environment approval rather than before it. - -### Triggers - -- Manual dispatch only (safety measure) - -### Required Secrets - -- Cloud credentials (same as deployment workflows) - -### Example - -```bash -# Rollback AWS Lambda production to previous version -gh workflow run rollback.yml \ - -f cloud=aws-lambda \ - -f environment=prod \ - -f image_tag=sha-abc123 \ - -f reason="Critical bug in v1.2.3" - -# Rollback GCP staging -gh workflow run rollback.yml \ - -f cloud=gcp \ - -f environment=staging \ - -f image_tag=v1.2.2 -``` - -### Safety Features - -- **Image Verification** - Confirms image exists before deploying -- **Audit Trail** - Records all rollbacks (365 day retention) -- **Reason Tracking** - Requires reason for accountability -- **Manual Only** - Cannot be triggered automatically - -### Finding Image Tags - -```bash -# AWS ECR -aws ecr list-images --repository-name cudly - -# GCP Artifact Registry -gcloud artifacts docker images list -docker.pkg.dev///cudly - -# Azure ACR -az acr repository show-tags --name cudlyacr --repository cudly -``` - ---- - -## Setup Guide - -### 1. Configure GitHub Secrets - -**AWS:** - -```bash -# Create secrets -gh secret set AWS_ACCESS_KEY_ID -gh secret set AWS_SECRET_ACCESS_KEY -gh secret set DB_PASSWORD_AWS -``` - -**GCP:** - -```bash -# Create service account and download JSON -gcloud iam service-accounts create cudly-cicd --project= - -# Grant permissions -gcloud projects add-iam-policy-binding \ - --member="serviceAccount:cudly-cicd@.iam.gserviceaccount.com" \ - --role="roles/run.admin" - -# Create and download key -gcloud iam service-accounts keys create key.json \ - --iam-account=cudly-cicd@.iam.gserviceaccount.com - -# Set secrets -gh secret set GCP_SA_KEY < key.json -gh secret set GCP_PROJECT_ID -b"" -gh secret set DB_PASSWORD_GCP -``` - -**Azure:** - -```bash -# Create service principal -az ad sp create-for-rbac --name cudly-cicd --sdk-auth > azure-credentials.json - -# Set secrets -gh secret set AZURE_CREDENTIALS < azure-credentials.json -gh secret set AZURE_SUBSCRIPTION_ID -b"" -gh secret set DB_PASSWORD_AZURE -``` - -**Optional:** - -```bash -gh secret set SNYK_TOKEN -gh secret set INFRACOST_API_KEY -``` +## Troubleshooting -### 2. Configure GitHub Variables +**Lint or vet fails:** ```bash -# AWS -gh variable set AWS_REGION -b"us-east-1" -gh variable set AWS_ACCOUNT_ID -b"123456789012" -gh variable set ECR_REPOSITORY -b"cudly" - -# GCP -gh variable set GCP_REGION -b"us-central1" -gh variable set ARTIFACT_REGISTRY_REPO -b"cudly" - -# Azure -gh variable set AZURE_LOCATION -b"eastus" -gh variable set ACR_NAME -b"cudlyacr" -gh variable set RESOURCE_GROUP -b"cudly-rg" - -# Frontend -gh variable set CLOUD_PROVIDER -b"aws" -gh variable set FRONTEND_BUCKET -b"cudly-frontend-prod" -gh variable set CLOUDFRONT_DISTRIBUTION_ID -b"E1234567890" -gh variable set API_URL -b"https://api.cudly.example.com" +make lint +make vet ``` -### 3. Set Up Environments - -GitHub Environments provide deployment protection and environment-specific secrets: - -1. Go to **Settings** → **Environments** -2. Create environments: - - `aws-lambda-dev`, `aws-lambda-staging`, `aws-lambda-prod` - - `aws-fargate-dev`, `aws-fargate-staging`, `aws-fargate-prod` - - `gcp-dev`, `gcp-staging`, `gcp-prod` - - `azure-dev`, `azure-staging`, `azure-prod` - - `frontend-aws-dev`, etc. - -3. Configure protection rules: - - **Production**: Require approvals, restrict to main branch - - **Staging**: Optional approvals - - **Dev**: No restrictions - ---- - -## Troubleshooting - -### CI Workflow Fails - **Unit tests fail:** ```bash -# Run locally make test-unit ``` -**Integration tests fail:** +**Complexity gate fails:** ```bash -# Run with testcontainers -make test-integration +make complexity ``` **Security scan fails:** ```bash -# Run locally -make security-scan-all -``` - -### Deployment Fails - -**AWS - Image not found:** - -```bash -# Check ECR -aws ecr describe-images --repository-name cudly --region us-east-1 - -# Re-push image -docker push .dkr.ecr.us-east-1.amazonaws.com/cudly:latest -``` - -**GCP - Permission denied:** - -```bash -# Check service account permissions -gcloud projects get-iam-policy - -# Grant missing roles -gcloud projects add-iam-policy-binding \ - --member="serviceAccount:@.iam.gserviceaccount.com" \ - --role="roles/run.admin" -``` - -**Azure - Resource not found:** - -```bash -# Verify resource group exists -az group show --name cudly-rg - -# Create if missing -az group create --name cudly-rg --location eastus -``` - -### Database Migration Fails - -**Connection timeout:** - -- Check database security groups/firewall rules -- Verify VPN/bastion access if required -- Check database is running - -**Migration already applied:** - -```bash -# Check current version -migrate -path migrations -database version - -# Force version (use with caution) -migrate -path migrations -database force +make security-scan ``` ---- - -## Best Practices - -### 1. Branch Protection - -- Require CI to pass before merging -- Require code reviews -- Restrict direct pushes to main - -### 2. Environment Strategy - -- **Dev**: Auto-deploy on push to develop branch -- **Staging**: Auto-deploy on push to main -- **Prod**: Manual approval required, deploy on release - -### 3. Rollback Strategy - -- Keep last 10 images in each registry -- Document rollback procedures -- Test rollback in staging first - -### 4. Monitoring - -- Set up CloudWatch/Cloud Logging alerts -- Monitor deployment success rates -- Track deployment frequency - -### 5. Security - -- Rotate secrets regularly -- Use environment protection rules -- Enable secret scanning -- Review security scan results - ---- - -## Metrics & Monitoring - -### Workflow Success Rate +**pre-commit fails:** ```bash -# View recent workflow runs -gh run list --limit 50 - -# View specific workflow -gh run list --workflow=ci.yml --limit 20 +pre-commit run --all-files ``` -### Deployment Frequency - -- Target: Multiple deployments per day -- Track via GitHub Actions insights - -### Mean Time to Recovery (MTTR) - -- Use rollback workflow for quick recovery -- Target: < 15 minutes - -### CI Duration - -- Unit tests: ~5 min -- Integration tests: ~3 min -- Security scans: ~2 min -- Total: ~10 min target - ---- - ## Additional Resources - [GitHub Actions Documentation](https://docs.github.com/en/actions) -- [AWS ECR Documentation](https://docs.aws.amazon.com/ecr/) -- [GCP Artifact Registry](https://cloud.google.com/artifact-registry/docs) -- [Azure Container Registry](https://docs.microsoft.com/en-us/azure/container-registry/) -- [golang-migrate](https://github.com/golang-migrate/migrate) -- [Terraform Cloud](https://www.terraform.io/cloud) +- [golangci-lint](https://golangci-lint.run/) +- [pre-commit](https://pre-commit.com/) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6a0851d..8aff74d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -5,9 +5,6 @@ # # Required GitHub Secrets: None (all checks run without cloud credentials) # -# Required GitHub Variables: -# - GO_VERSION: Go version to use (default: 1.26.6) -# # Triggered by: # - Pull requests to main/develop # - Pushes to main/develop @@ -25,9 +22,6 @@ on: branches: [main, develop] workflow_dispatch: -env: - GO_VERSION: '1.26.6' - jobs: lint: name: Lint Code diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index bb8fb3a..d381762 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -28,19 +28,6 @@ repos: pass_filenames: false files: \.go$ - # Terraform formatting - - repo: https://github.com/antonbabenko/pre-commit-terraform - rev: v1.105.0 - hooks: - - id: terraform_fmt - name: Terraform format - - id: terraform_validate - name: Terraform validate - - id: terraform_tflint - name: Terraform lint - args: - - --args=--config=__GIT_WORKING_DIR__/.tflint.hcl - # General file checks - repo: https://github.com/pre-commit/pre-commit-hooks rev: v6.0.0 @@ -204,55 +191,19 @@ repos: language: system pass_filenames: false - # Migration checks - - repo: local - hooks: - - id: check-migration-conflicts - name: Check for conflicting migration numbers - entry: bash -c 'dups=$(ls internal/database/postgres/migrations/*.up.sql 2>/dev/null | sed "s/.*\///" | cut -c1-6 | sort | uniq -d); if [ -n "$dups" ]; then echo "Duplicate migration number(s) found:"; echo "$dups"; exit 1; fi' - language: system - pass_filenames: false - files: ^internal/database/postgres/migrations/ - - # Permissions codegen: regenerate frontend/src/permissions.generated.ts - # from internal/auth/types.go and fail if the committed copy is stale. - # Triggers on changes to the backend defaults or the generator itself, - # plus the generated file (in case a dev hand-edits it). - - repo: local - hooks: - - id: permissions-codegen - name: Regenerate frontend permissions from Go defaults - entry: bash -c 'go run ./cmd/gen-permissions && git diff --exit-code -- frontend/src/permissions.generated.ts || { echo "permissions.generated.ts is stale. Run go run ./cmd/gen-permissions and commit the result."; exit 1; }' - language: system - pass_filenames: false - files: ^(internal/auth/types\.go|cmd/gen-permissions/.*\.go|frontend/src/permissions\.generated\.ts)$ - # Heavy test execution: pre-push stage only. # - # These three hooks rebuild + run the full Go and frontend test suites, - # which is ~6-7 min of work and the bulk of the CI pre-commit job's - # runtime. They are *redundant in CI* — the same suites are run by - # dedicated workflows that PRs and pushes already trigger: - # - # - go-test (-short -race ./...) : ci.yml `unit-tests` runs the same - # suite with -race AND an integration - # pass with -tags=integration. - # - frontend-build (npm run build): frontend-build.yml runs npm run - # typecheck + npm run build on PRs; - # frontend-build-sentinel.yml runs - # the build on every push to main / - # feat/**. - # - frontend-test (jest) : frontend-build-sentinel.yml runs - # `npx jest --no-coverage --silent` - # on every push to feat/** (which - # fires on every PR-branch update). + # This rebuilds + runs the full Go test suite, which is the bulk of the + # CI pre-commit job's runtime. It is *redundant in CI* -- the same suite + # is run by ci.yml's `unit-tests` job with -race AND an integration pass + # with -tags=integration. # - # Moving them to the pre-push stage keeps the local safety net (devs - # who run `pre-commit install --hook-type pre-push` still get these - # tests on `git push`) while letting the CI pre-commit workflow stay - # focused on style/security/syntax. Pre-commit's default stage filter - # is `pre-commit`, so the CI workflow's `pre-commit run --all-files` - # skips these hooks automatically. + # Moving it to the pre-push stage keeps the local safety net (devs who + # run `pre-commit install --hook-type pre-push` still get it on `git + # push`) while letting the CI pre-commit workflow stay focused on + # style/security/syntax. Pre-commit's default stage filter is + # `pre-commit`, so the CI workflow's `pre-commit run --all-files` skips + # this hook automatically. - repo: local hooks: - id: go-test @@ -263,22 +214,6 @@ repos: files: \.go$ stages: [pre-push] - - id: frontend-build - name: Build frontend (pre-push only; CI covers via frontend-build.yml) - entry: bash -c 'cd frontend && npm run build' - language: system - pass_filenames: false - files: ^frontend/src/ - stages: [pre-push] - - - id: frontend-test - name: Run frontend tests (pre-push only; CI covers via frontend-build-sentinel.yml) - entry: bash -c 'cd frontend && npx jest --no-coverage --silent' - language: system - pass_filenames: false - files: ^frontend/src/ - stages: [pre-push] - # Global configuration default_stages: [pre-commit, pre-push] fail_fast: false diff --git a/CHANGELOG.md b/CHANGELOG.md index 643c1df..f4d9879 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,173 +1,14 @@ # Changelog -All notable changes to CUDly are documented in this file. +All notable changes to the CUDly CLI are documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/). ## [Unreleased] -### Notices - -- **Federation IaC bundles downloaded before 2026-04-22 need to be - re-downloaded** to get zero-touch registration. Older bundles silently - skip auto-registration unless manually edited (Terraform `registration.tf` - gated `do_register` on `cudly_api_url`; CLI shell scripts included the - registration call only when `CUDlyAPIURL` was present at render time; - CloudFormation deploy scripts had no registration call at all). - Re-download the bundle from the CUDly UI and the new copy will register - your account automatically with no manual edits required. -- **Federation IaC bundles deployed before #1219 need to be re-applied** to - pick up reconciled IAM action grants. Older bundles silently degrade on - federated accounts: the Cost Explorer `Get*Coverage` actions are missing - (coverage-targeted sizing assumes zero existing coverage), and the - cross-account CloudFormation flavor lacks the optional `EnableOrgDiscovery` - parameter and the legacy CE statement that the original `CUDly-CrossAccount` - template carried. Re-download the federation bundle from the CUDly UI and - re-apply (`terraform apply` / `aws cloudformation update-stack`) -- no - manual edits required, no changes to existing CUDly resources. Customers - on the runtime CloudFormation stack or Terraform lambda/fargate modules - also need an update to pick up the new `ec2:*ReservedInstancesExchangeQuote` - actions, `ec2:DescribeRegions`, `rds:DescribeDBInstances`, and the - new-style `es:*ReservedInstance*` OpenSearch actions (replacing the legacy - `es:*ReservedElasticsearch*` names). - -### Fixed - -- Remove debug console.log from frontend recommendation handler -- Align pre-commit gocyclo threshold (10) with CI pipeline -- Pin tool versions in GitHub Actions for reproducible builds -- Update README Go version badge to match go.mod (1.25+) - -## [0.9.0] - 2026-03-06 - -### Added - -- RI Exchange feature: reshape analysis with normalization factors, API - endpoints, and frontend page for managing convertible Reserved Instances -- RI utilization tracking from Cost Explorer with pagination support -- Convertible RI listing in EC2 client -- Security headers on all Lambda responses -- Admin password resolution from cloud secret managers - -### Fixed - -- Harden RI exchange handlers with validation and error sanitization -- Fix async race conditions and input validation in RI exchange frontend -- Fix base64 encoding for saveProfile and resetPassword -- Remove duplicate logout event handler -- Guard DNS zone outputs against missing resources (GCP, Azure) -- Fix Azure CDN redirect type and SPA routing -- Add network policies and resource quotas to AKS module -- Add security headers to Azure Front Door and GCP load balancer -- Wire admin password secrets through all cloud environment root modules - -## [0.8.0] - 2026-02-01 - -### Added - -- Deployment health check blocks for AWS, Azure, and GCP Terraform modules -- GCP self-signed cert for dev HTTPS -- Azure Front Door API routing and custom domain support -- Cross-provider deployment test harness script -- Azure ACR resource and registry authentication - -### Fixed - -- Enforce SSL-only connections on GCP Cloud SQL -- Migrate GCP load balancer to EXTERNAL_MANAGED with SPA routing -- Fix Azure Container Apps config and CDN delivery rule names -- Fix GCP frontend build trigger and database password generation -- Expand frontend CSP connect-src for Azure and GCP API origins -- Fix Fargate EventBridge container name -- Capture migration exit code correctly in entrypoint.sh - ### Changed -- Convert AWS database from Aurora Serverless v2 to standalone RDS -- Move GCP Secret Manager out of database module -- Replace Azure Container App Jobs with Logic Apps scheduled tasks -- Simplify Azure database module - -## [0.7.0] - 2026-01-15 - -### Added - -- Full Terraform infrastructure for AWS (Fargate, Lambda, CloudFront, RDS), - Azure (Container Apps, AKS, Front Door, PostgreSQL), and GCP (Cloud Run, - GKE, Cloud SQL) with CI-specific tfvars -- PostgreSQL database with connection pool, migrations, and secret resolvers -- Authentication service with RBAC and API key support -- REST API with rate limiting, CORS, and middleware stack -- Email service with SMTP sender and cloud credential resolution -- Analytics collector, purchase execution, and scheduled task runner -- Docker containerization with multi-stage builds and compose configs -- GitHub Actions CI/CD pipeline (lint, test, security scan, Docker build, - Terraform validate, E2E tests, Infracost) -- Frontend web dashboard with TypeScript, webpack, Chart.js - -### Fixed - -- Sanitize user input in dashboard and recommendations (XSS prevention) -- Add connection pool limits and graceful shutdown to server -- Add nil checks across Azure service clients -- Enforce 12-char minimum password with complexity requirements -- Use hidden-source-map for production frontend builds -- Use rightmost X-Forwarded-For IP for client identification -- Add SHA256 checksum verification for migrate binary in Docker -- Tighten git-secrets patterns to reduce false positives - -## [0.6.0] - 2025-11-01 - -### Added - -- Database Savings Plans support and SP type filtering -- OSL-3.0 license and contributing guidelines - -### Fixed - -- RDS RI purchase failing on details assertion and invalid reservation ID -- OpenSearch RI resource type and offering lookup -- Deduplicate reservation ID sanitization into pkg/common - -### Changed - -- Refactor internal packages to providers (aws, azure, gcp) -- Add provider-specific mocking infrastructure and tests - -## [0.5.0] - 2025-09-01 - -### Added - -- Multi-cloud support (Azure experimental, GCP experimental) -- API-based RDS extended support detection -- Instance type validation system -- CSV reader for recommendation import -- Duplicate RI purchase prevention -- Account alias lookup -- Confirmation prompt and instance limit features - -### Changed - -- Replace global variables with Config struct pattern -- Improve rate limiting and test performance -- Refactor all purchase clients with enhanced error handling - -## [0.4.0] - 2025-07-01 - -### Added - -- Multi-service RI support: EC2, ElastiCache, MemoryDB, OpenSearch, Redshift -- Multi-service orchestration and CLI -- Comprehensive test coverage (80%+ across packages) - -### Fixed - -- CSV pricing calculations to use AWS-provided cost data - -## [0.3.0] - 2025-05-01 - -### Added - -- Initial CLI tool for RDS Reserved Instance purchasing -- Recommendations fetching from AWS Cost Explorer -- CSV output for analysis results -- Go module setup with AWS SDK v2 +- Split the CLI out of the CUDly monorepo into its own module, + `github.com/LeanerCloud/cloud-commitments-cli`. The shared client and + provider code it depends on now lives in + `github.com/LeanerCloud/cloud-commitments-go`, pinned in `go.mod`. + History predating the split lives in the monorepo's changelog. diff --git a/CLAUDE.md b/CLAUDE.md index 35ba2aa..e153a92 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -19,15 +19,11 @@ ## File Organization - NEVER save working files to the root folder; use the directories below -- `cmd/`: CLI and server entry points (main packages) -- `internal/`: backend application code (API, auth, purchase, scheduler, ...) -- `pkg/`: shared library code (separate Go module, see Go Module Notes) -- `providers/`: cloud provider integrations (AWS, Azure, GCP) -- `frontend/`: TypeScript web frontend (webpack + jest) -- `terraform/`, `cloudformation/`, `arm/`, `iac/`: infrastructure as code -- `docs/`: documentation and markdown files -- `scripts/`: utility scripts -- `tests/`: end-to-end tests (Go unit tests live next to the code they test) +- `cmd/`: the single CLI main package (builds the `cudly` binary); Go unit + tests live next to the code they test +- `docs/`: CLI reference and topic documentation +- `scripts/`: repository hook and helper scripts +- `.github/`: CI workflows (`ci.yml`, `pre-commit.yml`) ## Project Architecture @@ -54,23 +50,20 @@ ## Build & Test -The root of the repo is a Go project; the npm scripts live in `frontend/`. +The whole repo is a single Go module rooted at `cmd/`. ```bash -# Build (backend, from the repo root) -go build ./... # or: make build +# Build +make build # or: go build -o cudly ./cmd + # (go build ./... fails: cmd/ is the only main + # package and its default output name collides + # with the cmd directory itself) -# Test (backend) -go test ./... # or: make test-unit +# Test +go test ./... # or: make test-unit -# Lint (backend) -make lint # golangci-lint; also: make vet, make fmt - -# Frontend (run from frontend/) -cd frontend && npm ci -npm run build # webpack production build -npm test # jest --coverage -npm run lint # eslint src/**/*.ts +# Lint +make lint # golangci-lint; also: make vet, make fmt ``` - ALWAYS run tests after making code changes @@ -78,12 +71,10 @@ npm run lint # eslint src/**/*.ts ## Known Issues -The `known_issues/` directory tracks deferred tech debt and surfaced bugs. -When a referenced GitHub issue is closed, move the corresponding doc to -`known_issues/resolved/` (do not delete it) so the rationale is preserved. -Do this in the same PR that closes the issue. A full sweep of the directory -should happen at the start of each sprint. Full convention and entry format -are in `CONTRIBUTING.md` under "Known Issues Sweep". +This repository has no `known_issues/` directory. Deferred tech debt or +surfaced bugs found while working here go into this repository's GitHub +issues instead. See `CONTRIBUTING.md` under "Known Issues Sweep" for the +full convention (including the cross-component sweep in the platform repo). ## Post-push CI watcher (MANDATORY — even for one-line fix commits) @@ -230,36 +221,6 @@ set for multi-close PRs). - Always sanitize file paths to prevent directory traversal - Run `npx @claude-flow/cli@latest security scan` after security-related changes -## CI/CD IAM — bootstrap vs runtime split - -The per-cloud `terraform/environments/*/ci-cd-permissions/` modules provision -the CI/CD deploy identities and are **applied once, manually, by a privileged -human** — not by the CI workflow itself. The main deploy workflow assumes a -deploy SA already exists and only has permission to manage workloads. Keep -this split when adding new IAM: - -- **Bootstrap-only permissions** (AWS `iam:*`, Azure RBAC role assignments, - GCP `roles/iam.roleAdmin`, `roles/resourcemanager.projectIamAdmin`, - `roles/cloudkms.admin`) live in `ci-cd-permissions/`. They let the deploy - SA manage its own downstream grants but are not granted to anything - ephemeral. -- **Runtime permissions** for the Lambda / Cloud Run / Container App service - accounts are defined in the per-cloud compute module (`modules/compute/ - {aws,gcp,azure}/...`) with the **narrowest possible scope**. Prefer custom - roles (GCP `google_project_iam_custom_role`) or prefixed resource ARNs - (AWS `arn:aws:iam::*:role/{prefix}*`) over broad predefined roles like - `roles/compute.admin` or `Resource = "*"`. -- **No silent fallbacks to over-privileged roles.** If a runtime grant - requires a bootstrap permission the deploy SA doesn't have, the apply - SHOULD 403 — that's the signal to re-run the bootstrap, not to paper over - with a wider grant. Fallback flags are allowed only as short-term - workarounds and must be removed once the bootstrap has been re-applied. -- **GCP WIF attribute_condition** in `ci-cd-permissions/github_oidc.tf` - restricts which branch can impersonate the deploy SA. Re-applying the - module with a different `deploy_ref` (or the default) resets the - condition. Pin `deploy_ref` in `terraform.tfvars` (gitignored, per-env) - to avoid silently locking out the current feature branch. - ## Concurrency: 1 MESSAGE = ALL RELATED OPERATIONS - All operations MUST be concurrent/parallel in a single message diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 61b05e4..e3f5c7a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -12,7 +12,7 @@ By participating in this project, you agree to maintain a respectful and inclusi 1. **Search existing issues** - Check if the bug has already been reported 2. **Create a detailed report** including: - - CUDly version (`./cudly --version`) + - CUDly version (`./cudly --help`) - Go version (`go version`) - Operating system and architecture - Cloud provider and service affected @@ -115,7 +115,11 @@ git worktree list --porcelain | sed -n 's/^worktree //p' | tail -n +2 | ``` The committed `go.work` (listing only this repository's own modules) keeps -`go build ./...` and CI clean for everyone without requiring any local setup. +`go vet ./...`, `go test ./...`, and CI clean for everyone without requiring +any local setup. Note that `go build ./...` is not a valid way to build this +repo: `cmd/` is the only main package, and Go's default output name for a +lone main package collides with the `cmd` directory itself. Use `make build` +or `go build -o cudly ./cmd` instead (see "Getting Started" above). ### Running Tests diff --git a/NOTICE b/NOTICE index 32ee960..398ef56 100644 --- a/NOTICE +++ b/NOTICE @@ -66,28 +66,3 @@ Go standard library extensions BSD-3-Clause YAML v3 MIT / Apache-2.0 gopkg.in/yaml.v3 https://github.com/go-yaml/yaml/blob/v3/LICENSE - -======================================================================== -Frontend Dependencies (from frontend/package.json) -======================================================================== - -Chart.js MIT - https://github.com/chartjs/Chart.js/blob/master/LICENSE.md - -webpack MIT - https://github.com/webpack/webpack/blob/main/LICENSE - -TypeScript Apache-2.0 - https://github.com/microsoft/TypeScript/blob/main/LICENSE.txt - -Jest MIT - https://github.com/jestjs/jest/blob/main/LICENSE - -Testing Library MIT - https://github.com/testing-library/dom-testing-library/blob/main/LICENSE - -ESLint MIT - https://github.com/eslint/eslint/blob/main/LICENSE - -Babel MIT - https://github.com/babel/babel/blob/main/LICENSE diff --git a/docs/cli/README.md b/docs/cli/README.md index 8b4cdd4..23e63fc 100644 --- a/docs/cli/README.md +++ b/docs/cli/README.md @@ -6,8 +6,6 @@ This section documents the full CLI surface of the `cudly` binary. The Makefile - **configure-azure** - bootstrap Azure Service Principal credentials - **configure-gcp** - bootstrap GCP Service Account credentials -`rekey` and `server` are separate binaries with their own entry points and are not covered here. - ## Topic pages | Page | Covers |