diff --git a/.github/actions/cache-go-dependencies/action.yaml b/.github/actions/cache-go-dependencies/action.yaml index 43900bbb10a3f..b4696c0ecd0b4 100644 --- a/.github/actions/cache-go-dependencies/action.yaml +++ b/.github/actions/cache-go-dependencies/action.yaml @@ -15,8 +15,25 @@ runs: - name: Determine Go cache paths id: cache-paths run: | - echo "GOCACHE=$(go env GOCACHE)" >> "$GITHUB_OUTPUT" - echo "GOARCH=$(go env GOARCH)" >> "$GITHUB_OUTPUT" + # Keep assignments separate from echo so Go failures are not masked. + if ! gocache=$(go env GOCACHE); then + echo "::error::Failed to determine GOCACHE: go env GOCACHE failed. See the Go error above." + exit 1 + fi + if [[ -z "$gocache" ]]; then + echo "::error::go env GOCACHE returned an empty value. Cannot configure the Go build cache." + exit 1 + fi + if ! goarch=$(go env GOARCH); then + echo "::error::Failed to determine GOARCH: go env GOARCH failed. See the Go error above." + exit 1 + fi + if [[ -z "$goarch" ]]; then + echo "::error::go env GOARCH returned an empty value. Cannot configure the Go build cache." + exit 1 + fi + echo "GOCACHE=$gocache" >> "$GITHUB_OUTPUT" + echo "GOARCH=$goarch" >> "$GITHUB_OUTPUT" echo "TAG=${{ github.sha }}" >> "$GITHUB_OUTPUT" # Hash go.mod once for use in cache keys. When dependencies change, # the GOCACHE must be invalidated because Go's module index caches diff --git a/.github/docker/images.hcl b/.github/docker/images.hcl index 4e9de4284681e..b4b6d47ac7760 100644 --- a/.github/docker/images.hcl +++ b/.github/docker/images.hcl @@ -28,7 +28,7 @@ group "databases" { targets = ["central-db", "scanner-v4-db"] } -# Central database (PostgreSQL 15) +# Central database (PostgreSQL 16) target "central-db" { context = "image/postgres" dockerfile = "Dockerfile" diff --git a/.github/test-images/postgres.dockerfile b/.github/test-images/postgres.dockerfile index a1556f5177f31..bb498f0494622 100644 --- a/.github/test-images/postgres.dockerfile +++ b/.github/test-images/postgres.dockerfile @@ -1,3 +1,3 @@ -# Postgres 15 image for unit test CI. Referenced by unit-tests.yaml. +# Postgres 16 image for unit test CI. Referenced by unit-tests.yaml. # Dependabot auto-bumps this digest via the docker ecosystem config. -FROM quay.io/sclorg/postgresql-15-c9s@sha256:77c1e962234c598f3ed2b5e581f0845fca333bd262e9b0ee542bf8fa96fbc03d +FROM quay.io/sclorg/postgresql-16-c9s@sha256:d33717fd33c1a4b11afef94568d0c20812a19d62fb8c633fadee1e491e2edd4a diff --git a/.github/workflows/add-new-pr-to-oss-triaging.yml b/.github/workflows/add-new-pr-to-oss-triaging.yml index dfb5b52a95100..9d7a10996440f 100644 --- a/.github/workflows/add-new-pr-to-oss-triaging.yml +++ b/.github/workflows/add-new-pr-to-oss-triaging.yml @@ -9,7 +9,7 @@ env: jobs: check-pr-if-external: name: Add external label to pull request if outside StackRox - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 env: GH_TOKEN: ${{ github.token }} BASE_REPO: ${{ github.repository }} diff --git a/.github/workflows/add-vms-to-cluster.yml b/.github/workflows/add-vms-to-cluster.yml index 58eb968bd3a87..0ace3f5f384e7 100644 --- a/.github/workflows/add-vms-to-cluster.yml +++ b/.github/workflows/add-vms-to-cluster.yml @@ -1,5 +1,5 @@ -name: "Add VMs to Cluster" -run-name: "Add ${{ inputs.num-vms }} ${{ inputs.vm-os }} VMs to ${{ inputs.cluster-name }}" +name: "Ensure VMs on Cluster" +run-name: "Ensure ${{ inputs.num-vms }} ${{ inputs.vm-os }} VMs on ${{ inputs.cluster-name }}" on: workflow_dispatch: @@ -9,7 +9,7 @@ on: required: true type: string num-vms: - description: "Number of VMs to deploy" + description: "Desired count of {os}-1..N, not how many to add. Existing VMs are reused; 2 is a no-op if rhel9-1 and rhel9-2 already exist." required: false default: 1 type: number @@ -19,6 +19,7 @@ on: default: "rhel9" type: choice options: + - rhel8 - rhel9 - rhel10 ssh-public-key: @@ -29,7 +30,7 @@ on: jobs: add-vms: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 timeout-minutes: 60 env: INFRA_TOKEN: ${{ secrets.INFRA_TOKEN }} diff --git a/.github/workflows/audit-backport-prs.yml b/.github/workflows/audit-backport-prs.yml index 08ceefc68c375..b220cb7c4e725 100644 --- a/.github/workflows/audit-backport-prs.yml +++ b/.github/workflows/audit-backport-prs.yml @@ -29,7 +29,7 @@ env: jobs: properties: name: Read repository properties - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 outputs: slack-channel: ${{ fromJSON(format('["{0}","{1}"]', steps.properties.outputs.slack-channel, steps.properties.outputs.dry-slack-channel))[(github.event_name == 'workflow_dispatch' && inputs.dry_run) || github.event_name == 'pull_request'] }} steps: @@ -40,7 +40,7 @@ jobs: run: gh api -H "$ACCEPT_RAW" "$PROPERTIES_URL" >> "$GITHUB_OUTPUT" audit: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 needs: [properties] permissions: contents: read diff --git a/.github/workflows/auto-merge.yml b/.github/workflows/auto-merge.yml index 5ae4329f1151d..1a18592aebca3 100644 --- a/.github/workflows/auto-merge.yml +++ b/.github/workflows/auto-merge.yml @@ -16,7 +16,7 @@ concurrency: jobs: auto-merge: name: Enable auto-merge for eligible PRs - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 if: github.repository_owner == 'stackrox' steps: diff --git a/.github/workflows/backport-pr.yaml b/.github/workflows/backport-pr.yaml index 817043cc41ea0..dc54e31818fce 100644 --- a/.github/workflows/backport-pr.yaml +++ b/.github/workflows/backport-pr.yaml @@ -16,7 +16,7 @@ defaults: jobs: backport: name: Backport - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 # Only react to merged PRs for security reasons. # See https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request_target. if: > diff --git a/.github/workflows/batch-load-test-metrics.yml b/.github/workflows/batch-load-test-metrics.yml index 8168c7cfd63fd..f798db49af93d 100644 --- a/.github/workflows/batch-load-test-metrics.yml +++ b/.github/workflows/batch-load-test-metrics.yml @@ -10,7 +10,7 @@ concurrency: Batch load test metrics jobs: batch-load-test-metrics: if: ${{ github.repository_owner == 'stackrox' }} - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 container: image: quay.io/stackrox-io/apollo-ci:stackrox-test-0.5.11@sha256:df3459cf038e73775314b3a5d36d14dda41e6df438a0273c9efa8d23e0f2358a # ratchet:quay.io/stackrox-io/apollo-ci:stackrox-test-0.5.11 steps: diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 6776ef11d814c..607c34fe7803f 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -164,7 +164,7 @@ jobs: ROX_PRODUCT_BRANDING: ${{ matrix.branding }} # For consistency with `image/rhel/konflux.Dockerfile`, see comment there. UI_PKG_INSTALL_EXTRA_ARGS: --ignore-scripts - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - name: Checkout uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # ratchet:actions/checkout@v7.0.0 @@ -205,7 +205,7 @@ jobs: pre-build-cli: needs: define-job-matrix - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 env: BUILD_TAG: ${{ needs.define-job-matrix.outputs.build-tag }} SHORTCOMMIT: ${{ needs.define-job-matrix.outputs.short-commit }} @@ -283,7 +283,7 @@ jobs: # race-condition-debug - built with -race matrix: ${{ fromJson(needs.define-job-matrix.outputs.matrix).pre_build_go_binaries }} needs: define-job-matrix - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 env: BUILD_TAG: ${{ needs.define-job-matrix.outputs.build-tag }} SHORTCOMMIT: ${{ needs.define-job-matrix.outputs.short-commit }} @@ -363,7 +363,7 @@ jobs: compression-level: 1 pre-build-docs: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - name: Checkout uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # ratchet:actions/checkout@v7.0.0 @@ -394,7 +394,7 @@ jobs: image/rhel/docs pre-build-oss-notice: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - name: Checkout uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # ratchet:actions/checkout@v7.0.0 @@ -435,7 +435,7 @@ jobs: path: image/rhel/THIRD_PARTY_NOTICES build-and-push-db: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 needs: - define-job-matrix if: ${{ !cancelled() }} @@ -498,7 +498,7 @@ jobs: - name: Build and push DB images in parallel with bake if: | github.event_name == 'push' || !github.event.pull_request.head.repo.fork - uses: docker/bake-action@d3418bd7d0e9324001bca92fa8ba175ea7e6dc9b # ratchet:docker/bake-action@v7.3.0 + uses: docker/bake-action@018cb6412ab401ebaa809aa5f85966b74628600f # ratchet:docker/bake-action@v7.4.0 with: source: . files: .github/docker/images.hcl @@ -541,7 +541,7 @@ jobs: "docker://quay.io/rhacs-eng/scanner-v4-db:${{ env.BUILD_TAG }}" build-operator-bundle: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 env: QUAY_ORG: rhacs-eng QUAY_RHACS_ENG_RW_PASSWORD: ${{ secrets.QUAY_RHACS_ENG_RW_PASSWORD }} @@ -579,7 +579,7 @@ jobs: build-and-push-main: # Use native arm64 runner to avoid QEMU emulation overhead (~2x slower). - runs-on: ${{ (matrix.arch == 'arm64' && 'ubuntu-24.04-arm') || 'ubuntu-latest' }} + runs-on: ${{ (matrix.arch == 'arm64' && 'ubuntu-24.04-arm') || 'ubuntu-24.04' }} needs: - define-job-matrix - pre-build-cli @@ -755,7 +755,7 @@ jobs: push_main_image_set "$push_context" "${{ env.ROX_PRODUCT_BRANDING }}" "${{ matrix.arch }}" push-matching-collector-scanner: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 needs: - define-job-matrix if: ${{ !cancelled() }} @@ -788,7 +788,7 @@ jobs: push_matching_collector_scanner_images "${{ env.ROX_PRODUCT_BRANDING }}" push-main-manifests: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 needs: - define-job-matrix - build-and-push-main @@ -911,7 +911,7 @@ jobs: message-path: build-comment.md build-and-push-operator: - runs-on: ${{ (matrix.arch == 'arm64' && 'ubuntu-24.04-arm') || 'ubuntu-latest' }} + runs-on: ${{ (matrix.arch == 'arm64' && 'ubuntu-24.04-arm') || 'ubuntu-24.04' }} needs: - define-job-matrix - pre-build-go-binaries @@ -1027,7 +1027,7 @@ jobs: echo "Operator image push completed successfully" build-and-push-scanner: - runs-on: ${{ (matrix.arch == 'arm64' && 'ubuntu-24.04-arm') || 'ubuntu-latest' }} + runs-on: ${{ (matrix.arch == 'arm64' && 'ubuntu-24.04-arm') || 'ubuntu-24.04' }} needs: - define-job-matrix - build-and-push-db @@ -1111,7 +1111,7 @@ jobs: push_scanner_image_set "${{ matrix.registry }}" "${{ matrix.arch }}" push-scanner-manifests: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 needs: - define-job-matrix - build-and-push-scanner @@ -1171,7 +1171,7 @@ jobs: push_scanner_image_manifest_lists "${{ matrix.registry }}" "$architectures" push-operator-manifests: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 needs: - define-job-matrix - build-and-push-operator @@ -1228,7 +1228,7 @@ jobs: contains(github.event.pull_request.labels.*.name, 'scan-go-binaries') env: ARTIFACT_DIR: junit-reports/ - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 needs: - pre-build-cli - pre-build-go-binaries @@ -1289,7 +1289,7 @@ jobs: contains(github.event.pull_request.labels.*.name, 'ci-test-github-action-slack-messages') ) name: Post failure message to Slack - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 needs: - build-and-push-db - build-and-push-main diff --git a/.github/workflows/check-crd-compatibility.yaml b/.github/workflows/check-crd-compatibility.yaml index 95ba18adc4a75..d5d2ba0ffd62b 100644 --- a/.github/workflows/check-crd-compatibility.yaml +++ b/.github/workflows/check-crd-compatibility.yaml @@ -23,7 +23,7 @@ jobs: check-crd-compatibility: needs: detect-changes if: ${{ !needs.detect-changes.outputs.ui_only }} - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 container: image: quay.io/stackrox-io/apollo-ci:stackrox-test-0.5.11@sha256:df3459cf038e73775314b3a5d36d14dda41e6df438a0273c9efa8d23e0f2358a # ratchet:quay.io/stackrox-io/apollo-ci:stackrox-test-0.5.11 outputs: @@ -88,19 +88,17 @@ jobs: - name: Run crdify for platform operator run: | - set -e + set -euo pipefail old_path="$PWD/.old-stackrox/operator/config/crd/bases" new_path="$PWD/operator/config/crd/bases" - crdify="go run -C operator/tools/crdify sigs.k8s.io/crdify --config=config.yaml" - set -x - $crdify "file://${old_path}/platform.stackrox.io_securedclusters.yaml" "file://${new_path}/platform.stackrox.io_securedclusters.yaml" - $crdify "file://${old_path}/platform.stackrox.io_centrals.yaml" "file://${new_path}/platform.stackrox.io_centrals.yaml" + crdify=".github/workflows/scripts/crdify.sh" + $crdify "${old_path}/platform.stackrox.io_securedclusters.yaml" "${new_path}/platform.stackrox.io_securedclusters.yaml" + $crdify "${old_path}/platform.stackrox.io_centrals.yaml" "${new_path}/platform.stackrox.io_centrals.yaml" - name: Run crdify for config operator run: | - set -e + set -euo pipefail old_path="$PWD/.old-stackrox/config-controller/config/crd/bases" new_path="$PWD/config-controller/config/crd/bases" - crdify="go run -C operator/tools/crdify sigs.k8s.io/crdify --config=config.yaml" - set -x - $crdify "file://${old_path}/config.stackrox.io_securitypolicies.yaml" "file://${new_path}/config.stackrox.io_securitypolicies.yaml" + crdify=".github/workflows/scripts/crdify.sh" + $crdify "${old_path}/config.stackrox.io_securitypolicies.yaml" "${new_path}/config.stackrox.io_securitypolicies.yaml" diff --git a/.github/workflows/check-crd-frozen-on-release.yaml b/.github/workflows/check-crd-frozen-on-release.yaml index fa64eb4493aec..981253e8cfb15 100644 --- a/.github/workflows/check-crd-frozen-on-release.yaml +++ b/.github/workflows/check-crd-frozen-on-release.yaml @@ -35,7 +35,7 @@ permissions: jobs: check-crds-frozen: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - name: Checkout stackrox/stackrox uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # ratchet:actions/checkout@v7.0.0 diff --git a/.github/workflows/check-pr-title.yaml b/.github/workflows/check-pr-title.yaml index 5212dfd3ac09b..49afd75bf89a4 100644 --- a/.github/workflows/check-pr-title.yaml +++ b/.github/workflows/check-pr-title.yaml @@ -6,7 +6,7 @@ on: jobs: check-title: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 env: PR_TITLE: ${{ github.event.pull_request.title }} steps: diff --git a/.github/workflows/ci-failures-report.yml b/.github/workflows/ci-failures-report.yml index 9be7d94707cf8..a2e0e03d9a7bf 100644 --- a/.github/workflows/ci-failures-report.yml +++ b/.github/workflows/ci-failures-report.yml @@ -18,7 +18,7 @@ on: jobs: report-e2e-failures-to-slack: if: ${{ github.repository_owner == 'stackrox' }} - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - name: Checkout uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # ratchet:actions/checkout@v7.0.0 diff --git a/.github/workflows/create-clusters.yml b/.github/workflows/create-clusters.yml index 5249b320bacbf..2693c27c9311c 100644 --- a/.github/workflows/create-clusters.yml +++ b/.github/workflows/create-clusters.yml @@ -42,7 +42,7 @@ concurrency: Release automation ${{ inputs.version }} jobs: trim-cluster-names: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 outputs: cluster-with-fake-load-name-cleaned: ${{ steps.cluster-names.outputs.cluster-with-fake-load-name-cleaned }} cluster-with-real-load-name-cleaned: ${{ steps.cluster-names.outputs.cluster-with-real-load-name-cleaned }} @@ -64,7 +64,7 @@ jobs: clusters: name: Setup demo clusters needs: trim-cluster-names - uses: stackrox/actions/.github/workflows/create-demo-clusters.yml@df8c8f5830fc09d37908be1a9744365a93c5b3cc # ratchet:stackrox/actions/.github/workflows/create-demo-clusters.yml@v1 + uses: stackrox/actions/.github/workflows/create-demo-clusters.yml@f3893761af7aff7477e3dde7a4d9656b173fc687 # ratchet:stackrox/actions/.github/workflows/create-demo-clusters.yml@v1 secrets: inherit with: version: ${{github.event.inputs.version}} diff --git a/.github/workflows/cut-rc.yml b/.github/workflows/cut-rc.yml index 402dda5b0e6a2..d28a3ce950815 100644 --- a/.github/workflows/cut-rc.yml +++ b/.github/workflows/cut-rc.yml @@ -37,6 +37,8 @@ on: default: true type: boolean +cache-mode: read + env: main_branch: ${{github.event.repository.default_branch}} script_url: /repos/${{github.repository}}/contents/.github/workflows/scripts/common.sh?ref=${{ github.ref_name }} @@ -60,7 +62,7 @@ concurrency: Release automation ${{ inputs.version }} jobs: properties: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 outputs: slack-channel: ${{ fromJSON(format('["{0}","{1}"]', steps.fetch.outputs.dry-slack-channel, steps.fetch.outputs.slack-channel))[github.event.inputs.dry-run != 'true'] }} jira-project: ${{ steps.fetch.outputs.jira-project }} @@ -73,7 +75,7 @@ jobs: run-parameters: name: Run parameters - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - run: | [ "$DRY_RUN" = "true" ] && echo "::warning::This is a dry run" @@ -96,7 +98,7 @@ jobs: check-jira: name: Check Jira tickets for release needs: [variables, properties] - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - name: Query JIRA # Checking unequal with "false" because closed milestones have unset input values. @@ -117,7 +119,7 @@ jobs: postpone-prs: name: Postpone open PRs needs: variables - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - name: Check open PRs id: check @@ -173,7 +175,7 @@ jobs: cut-rc: name: Tag RC for milestone ${{needs.variables.outputs.milestone}} - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 needs: [variables, properties, postpone-prs, check-jira] steps: - name: Check out code @@ -308,7 +310,7 @@ jobs: :arrow_right: Once all checks pass and you're ready for release, run the <${{ github.server_url }}/${{ github.repository }}/actions/workflows/finish-release.yml|Finish Release> workflow and delete the `${{ needs.variables.outputs.next-milestone }}` milestone to avoid confusion. trim-cluster-names: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 outputs: cluster-with-fake-load-name-cleaned: ${{ steps.cluster-names.outputs.cluster-with-fake-load-name-cleaned }} cluster-with-real-load-name-cleaned: ${{ steps.cluster-names.outputs.cluster-with-real-load-name-cleaned }} @@ -331,7 +333,7 @@ jobs: name: Setup demo clusters secrets: inherit needs: [variables, cut-rc, trim-cluster-names] - uses: stackrox/actions/.github/workflows/create-demo-clusters.yml@df8c8f5830fc09d37908be1a9744365a93c5b3cc # ratchet:stackrox/actions/.github/workflows/create-demo-clusters.yml@v1 + uses: stackrox/actions/.github/workflows/create-demo-clusters.yml@f3893761af7aff7477e3dde7a4d9656b173fc687 # ratchet:stackrox/actions/.github/workflows/create-demo-clusters.yml@v1 if: >- # Skip if no clusters are going to be created. Checking unequal with "false" because closed milestones have unset input values. github.event.inputs.create-k8s-cluster != 'false' || github.event.inputs.create-os4-cluster != 'false' || diff --git a/.github/workflows/detect-changes.yml b/.github/workflows/detect-changes.yml index b36dab630fc9f..8cced67cfd949 100644 --- a/.github/workflows/detect-changes.yml +++ b/.github/workflows/detect-changes.yml @@ -39,7 +39,7 @@ on: jobs: detect: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 outputs: files: ${{ steps.list.outputs.files }} ui_only: ${{ steps.list.outputs.ui_only }} diff --git a/.github/workflows/e2e-byodb-tests.yaml b/.github/workflows/e2e-byodb-tests.yaml index 7b698cd05a5a1..f9a52ad5547a6 100644 --- a/.github/workflows/e2e-byodb-tests.yaml +++ b/.github/workflows/e2e-byodb-tests.yaml @@ -29,7 +29,7 @@ defaults: jobs: gke-byodb-qa-e2e-tests: if: github.event_name == 'workflow_dispatch' || inputs.should-run - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 env: MAIN_IMAGE_TAG: ${{ inputs.tag }} BUILD_TAG: ${{ inputs.tag }} @@ -39,6 +39,7 @@ jobs: LOAD_BALANCER: lb ORCHESTRATOR_FLAVOR: k8s KUBERNETES_PROVIDER: gke + SCANNER_V4_DB_STORAGE_CLASS: stackrox-gke-ssd POSTGRES_VERSION: "17" LONGTERM_LICENSE: "unused" BYODB_TEST: "true" @@ -75,7 +76,7 @@ jobs: - name: Create GKE cluster env: MACHINE_TYPE: e2-standard-8 - NUM_NODES: "3" + NUM_NODES: "1" GKE_SPOT: "false" timeout-minutes: 20 run: | @@ -98,6 +99,16 @@ jobs: - name: Create artifact directory run: mkdir -p "$ARTIFACT_DIR" + - name: Retrieve roxie version from repository + id: roxie-version + run: echo "version=$(cat ROXIE_VERSION)" >> "$GITHUB_OUTPUT" + + - name: Install roxie + uses: stackrox/actions/roxie/install-cli@bcda9b2d6d2b644c267e699479656973e84ef7fa # ratchet:stackrox/actions@v1 + with: + version: "v${{ steps.roxie-version.outputs.version }}" + gh-token: ${{ github.token }} + - name: Run pre-test shell: python env: @@ -141,6 +152,7 @@ jobs: CLOUD_SOURCES_TEST_OCM_CLIENT_ID: ${{ secrets.CLOUD_SOURCES_TEST_OCM_CLIENT_ID }} CLOUD_SOURCES_TEST_OCM_CLIENT_SECRET: ${{ secrets.CLOUD_SOURCES_TEST_OCM_CLIENT_SECRET }} BUILD_ID: ${{ github.run_id }} + USE_ROXIE_DEPLOY: true timeout-minutes: 245 run: tests/byodb/run.sh /tmp/byodb-test-logs diff --git a/.github/workflows/e2e-db-backup-restore-tests.yaml b/.github/workflows/e2e-db-backup-restore-tests.yaml index eff0bfcb79fda..7ea0636f763b1 100644 --- a/.github/workflows/e2e-db-backup-restore-tests.yaml +++ b/.github/workflows/e2e-db-backup-restore-tests.yaml @@ -29,15 +29,13 @@ defaults: jobs: gke-db-backup-restore-tests: if: github.event_name == 'workflow_dispatch' || inputs.should-run - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 env: MAIN_IMAGE_TAG: ${{ inputs.tag }} BUILD_TAG: ${{ inputs.tag }} CI_JOB_NAME: gke-db-backup-restore-tests ARTIFACT_DIR: ./junit-reports - LOAD_BALANCER: lb ROX_SCANNER_V4: "false" - STORAGE: pvc STORAGE_CLASS: faster PYTHONUNBUFFERED: "1" timeout-minutes: 60 @@ -62,7 +60,7 @@ jobs: - name: Create GKE cluster env: MACHINE_TYPE: e2-standard-8 - NUM_NODES: "3" + NUM_NODES: "1" GKE_SPOT: "false" timeout-minutes: 20 run: | @@ -75,6 +73,16 @@ jobs: make roxctl_linux-amd64 echo "$(pwd)/bin/linux_amd64" >> "$GITHUB_PATH" + - name: Retrieve roxie version from repository + id: roxie-version + run: echo "version=$(cat ROXIE_VERSION)" >> "$GITHUB_OUTPUT" + + - name: Install roxie + uses: stackrox/actions/roxie/install-cli@bcda9b2d6d2b644c267e699479656973e84ef7fa # ratchet:stackrox/actions@v1 + with: + version: "v${{ steps.roxie-version.outputs.version }}" + gh-token: ${{ secrets.GITHUB_TOKEN }} + - name: Verify kubectl access and wait for cluster to stabilize timeout-minutes: 5 run: | @@ -101,16 +109,24 @@ jobs: QUAY_RHACS_ENG_RO_PASSWORD: ${{ secrets.QUAY_RHACS_ENG_RO_PASSWORD }} REGISTRY_USERNAME: ${{ secrets.QUAY_RHACS_ENG_RO_USERNAME }} REGISTRY_PASSWORD: ${{ secrets.QUAY_RHACS_ENG_RO_PASSWORD }} - ORCHESTRATOR_FLAVOR: k8s run: | source tests/e2e/lib.sh source tests/scripts/setup-certs.sh source scripts/ci/sensor-wait.sh + export_test_environment setup_deployment_env false false - deploy_stackrox - # Persist API_ENDPOINT for post-test steps (ci_export only sets it in this shell) - echo "API_ENDPOINT=${API_ENDPOINT}" >> "$GITHUB_ENV" + + roxie_config="$(mktemp)" + merge_yaml "$roxie_config" <<'EOF' + central: + resourceProfile: ci + securedCluster: + resourceProfile: ci + EOF + deploy_stackrox_with_roxie_compat "$roxie_config" + rm -f "$roxie_config" + db_backup_and_restore_test /tmp/db-backup-restore-test - name: Run post-test diff --git a/.github/workflows/e2e-dispatch.yaml b/.github/workflows/e2e-dispatch.yaml index 178a967d166e7..4335fefeb05cb 100644 --- a/.github/workflows/e2e-dispatch.yaml +++ b/.github/workflows/e2e-dispatch.yaml @@ -108,10 +108,11 @@ jobs: Job(name='e2e-byodb-tests', require_pr_label=True), Job(name='e2e-db-backup-restore-tests'), Job(name='e2e-gke-upgrade-tests'), - Job(name='e2e-nongroovy-tests'), + Job(name='e2e-nongroovy-tests-gke'), + Job(name='e2e-nongroovy-tests-gke-konflux', require_konflux_build=True), Job(name='e2e-qa-tests-gke', require_pr_label=True), Job(name='e2e-qa-tests-gke-konflux', require_konflux_build=True), - Job(name='e2e-qa-tests-ocp-4-22', require_pr_label=True), + Job(name='e2e-qa-tests-ocp-fips-konflux', require_konflux_build=True), ] with open(os.environ['GITHUB_OUTPUT'], mode='at', encoding='utf-8') as file: @@ -135,7 +136,7 @@ jobs: wait-for-images: needs: [ should-dispatch ] if: needs.should-dispatch.outputs.wait-for-gha-images == 'true' - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 env: MAIN_IMAGE_TAG_DIR: "." IMAGE_PREFIX: "" @@ -198,7 +199,7 @@ jobs: wait-for-konflux-images: needs: [ should-dispatch ] if: needs.should-dispatch.outputs.wait-for-konflux-images == 'true' - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 env: MAIN_IMAGE_TAG_DIR: "operator" IMAGE_PREFIX: "release-" @@ -262,15 +263,26 @@ jobs: group: e2e-gke-upgrade-tests-${{ github.workflow }}-${{ github.head_ref || github.run_id }} cancel-in-progress: true - e2e-nongroovy-tests: + e2e-nongroovy-tests-gke: needs: [ wait-for-images, should-dispatch ] - uses: ./.github/workflows/e2e-nongroovy-tests.yaml + uses: ./.github/workflows/e2e-nongroovy-tests-gke.yaml with: tag: ${{ needs.wait-for-images.outputs.tag }} - should-run: ${{ fromJSON(needs.should-dispatch.outputs.jobs).e2e-nongroovy-tests }} + should-run: ${{ fromJSON(needs.should-dispatch.outputs.jobs).e2e-nongroovy-tests-gke }} secrets: inherit concurrency: - group: e2e-nongroovy-tests-${{ github.workflow }}-${{ github.head_ref || github.run_id }} + group: e2e-nongroovy-tests-gke-${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + + e2e-nongroovy-tests-gke-konflux: + needs: [ wait-for-konflux-images, should-dispatch ] + uses: ./.github/workflows/e2e-nongroovy-tests-gke-konflux.yaml + with: + tag: ${{ needs.wait-for-konflux-images.outputs.tag }}${{ needs.wait-for-konflux-images.outputs.tag_suffix }} + should-run: ${{ fromJSON(needs.should-dispatch.outputs.jobs).e2e-nongroovy-tests-gke-konflux }} + secrets: inherit + concurrency: + group: e2e-nongroovy-tests-gke-konflux-${{ github.workflow }}-${{ github.head_ref || github.run_id }} cancel-in-progress: true e2e-qa-tests-gke: @@ -284,15 +296,15 @@ jobs: group: e2e-qa-tests-gke-${{ github.workflow }}-${{ github.head_ref || github.run_id }} cancel-in-progress: true - e2e-qa-tests-ocp-4-22: - needs: [ wait-for-images, should-dispatch ] - uses: ./.github/workflows/e2e-qa-tests-ocp-4-22.yaml + e2e-qa-tests-ocp-fips-konflux: + needs: [ wait-for-konflux-images, should-dispatch ] + uses: ./.github/workflows/e2e-qa-tests-ocp-fips-konflux.yaml with: - tag: ${{ needs.wait-for-images.outputs.tag }} - should-run: ${{ fromJSON(needs.should-dispatch.outputs.jobs).e2e-qa-tests-ocp-4-22 }} + tag: ${{ needs.wait-for-konflux-images.outputs.tag }}${{ needs.wait-for-konflux-images.outputs.tag_suffix }} + should-run: ${{ fromJSON(needs.should-dispatch.outputs.jobs).e2e-qa-tests-ocp-fips-konflux }} secrets: inherit concurrency: - group: e2e-qa-tests-ocp-4-22-${{ github.workflow }}-${{ github.head_ref || github.run_id }} + group: e2e-qa-tests-ocp-fips-konflux-${{ github.workflow }}-${{ github.head_ref || github.run_id }} cancel-in-progress: true e2e-qa-tests-gke-konflux: diff --git a/.github/workflows/e2e-gke-upgrade-tests.yaml b/.github/workflows/e2e-gke-upgrade-tests.yaml index 40f394eb5c724..bf779c3a02626 100644 --- a/.github/workflows/e2e-gke-upgrade-tests.yaml +++ b/.github/workflows/e2e-gke-upgrade-tests.yaml @@ -31,7 +31,7 @@ jobs: # Verifies Central DB upgrades/rollbacks. gke-upgrade-tests-central: if: github.event_name == 'workflow_dispatch' || inputs.should-run - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 env: MAIN_IMAGE_TAG: ${{ inputs.tag }} BUILD_TAG: ${{ inputs.tag }} @@ -39,7 +39,6 @@ jobs: # gke-upgrade-tests expect all sensor, central and postgres tests to run together in a single sequence. CI_JOB_NAME: gke-upgrade-tests-central ARTIFACT_DIR: ./junit-reports - SCANNER_V4_DB_STORAGE_CLASS: faster # Make sure Python steps don't buffer stdout/stderr. PYTHONUNBUFFERED: "1" timeout-minutes: 180 # Keep in sync with cluster's lifespan @@ -81,6 +80,16 @@ jobs: - name: Build roxctl run: make roxctl_linux-amd64 + - name: Retrieve roxie version from repository + id: roxie-version + run: echo "version=$(cat ROXIE_VERSION)" >> "$GITHUB_OUTPUT" + + - name: Install roxie + uses: stackrox/actions/roxie/install-cli@bcda9b2d6d2b644c267e699479656973e84ef7fa # ratchet:stackrox/actions@v1 + with: + version: "v${{ steps.roxie-version.outputs.version }}" + gh-token: ${{ secrets.GITHUB_TOKEN }} + - name: Verify kubectl access and wait for cluster to stabilize timeout-minutes: 5 run: | @@ -109,7 +118,6 @@ jobs: - name: Run central upgrade test env: - LONGTERM_LICENSE: todo - remove me QUAY_RHACS_ENG_RO_USERNAME: ${{ secrets.QUAY_RHACS_ENG_RO_USERNAME }} QUAY_RHACS_ENG_RO_PASSWORD: ${{ secrets.QUAY_RHACS_ENG_RO_PASSWORD }} REGISTRY_USERNAME: ${{ secrets.QUAY_RHACS_ENG_RO_USERNAME }} @@ -118,7 +126,6 @@ jobs: GOOGLE_ARTIFACT_REGISTRY_SERVICE_ACCOUNT_V2: ${{ secrets.GOOGLE_ARTIFACT_REGISTRY_SERVICE_ACCOUNT_V2 }} REDHAT_USERNAME: ${{ secrets.RH_REGISTRY_USERNAME_RO }} REDHAT_PASSWORD: ${{ secrets.RH_REGISTRY_PASSWORD_RO }} - LOAD_BALANCER: lb run: | tests/upgrade/postgres_run.sh /tmp/postgres-upgrade-test-logs # This makes sure Stackrox_Deployment / Was Stackrox deployed to the cluster? test does not fail. @@ -201,7 +208,7 @@ jobs: gke-upgrade-tests-postgres: needs: check-postgres-upgrade if: needs.check-postgres-upgrade.outputs.required == 'true' - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 env: MAIN_IMAGE_TAG: ${{ inputs.tag }} BUILD_TAG: ${{ inputs.tag }} @@ -250,6 +257,16 @@ jobs: - name: Build roxctl run: make roxctl_linux-amd64 + - name: Retrieve roxie version from repository + id: roxie-version + run: echo "version=$(cat ROXIE_VERSION)" >> "$GITHUB_OUTPUT" + + - name: Install roxie + uses: stackrox/actions/roxie/install-cli@bcda9b2d6d2b644c267e699479656973e84ef7fa # ratchet:stackrox/actions@v1 + with: + version: "v${{ steps.roxie-version.outputs.version }}" + gh-token: ${{ secrets.GITHUB_TOKEN }} + - name: Verify kubectl access and wait for cluster to stabilize timeout-minutes: 5 run: | @@ -278,7 +295,6 @@ jobs: - name: Run postgres version upgrade test env: - LONGTERM_LICENSE: todo - remove me QUAY_RHACS_ENG_RO_USERNAME: ${{ secrets.QUAY_RHACS_ENG_RO_USERNAME }} QUAY_RHACS_ENG_RO_PASSWORD: ${{ secrets.QUAY_RHACS_ENG_RO_PASSWORD }} REGISTRY_USERNAME: ${{ secrets.QUAY_RHACS_ENG_RO_USERNAME }} @@ -286,7 +302,6 @@ jobs: GOOGLE_CREDENTIALS_GCR_SCANNER_V2: ${{ secrets.GOOGLE_CREDENTIALS_GCR_SCANNER_V2 }} REDHAT_USERNAME: ${{ secrets.RH_REGISTRY_USERNAME_RO }} REDHAT_PASSWORD: ${{ secrets.RH_REGISTRY_PASSWORD_RO }} - LOAD_BALANCER: lb run: | tests/upgrade/postgres_upgrade_run.sh /tmp/postgres-version-upgrade-test-logs # This makes sure Stackrox_Deployment / Was Stackrox deployed to the cluster? test does not fail. @@ -352,7 +367,7 @@ jobs: # Verifies Sensor/Secured Cluster `upgrader` (applicable to manifest installations only). gke-upgrade-tests-sensor: if: github.event_name == 'workflow_dispatch' || inputs.should-run - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 env: MAIN_IMAGE_TAG: ${{ inputs.tag }} BUILD_TAG: ${{ inputs.tag }} @@ -379,7 +394,7 @@ jobs: - name: Create GKE cluster env: MACHINE_TYPE: e2-standard-8 - NUM_NODES: "3" + NUM_NODES: "1" GKE_SPOT: "false" timeout-minutes: 20 run: | diff --git a/.github/workflows/e2e-nongroovy-tests-gke-konflux.yaml b/.github/workflows/e2e-nongroovy-tests-gke-konflux.yaml new file mode 100644 index 0000000000000..1a7c511cb7970 --- /dev/null +++ b/.github/workflows/e2e-nongroovy-tests-gke-konflux.yaml @@ -0,0 +1,38 @@ +name: e2e-nongroovy-tests-gke-konflux + +on: + workflow_call: + inputs: + tag: + description: | + Image tag to test. The caller needs to make sure that the tag carries the + "-fast" Konflux suffix if required (e.g. for development builds). + type: string + required: true + should-run: + description: true if should really run the job. false - otherwise, i.e. the job should be skipped. + type: boolean + required: true + workflow_dispatch: + inputs: + tag: + description: | + Image tag to test. You need to make sure that the tag carries the + "-fast" Konflux suffix if required (e.g. for development builds). + type: string + required: true + +permissions: + contents: read + pull-requests: read + +jobs: + run: + uses: ./.github/workflows/e2e-nongroovy-tests.yaml + with: + should-run: ${{ github.event_name == 'workflow_dispatch' || inputs.should-run }} + tag: ${{ inputs.tag }} + ci-job-name: gke-nongroovy-e2e-tests-konflux + extra-env-vars: | + USE_KONFLUX_IMAGES=true + secrets: inherit diff --git a/.github/workflows/e2e-nongroovy-tests-gke.yaml b/.github/workflows/e2e-nongroovy-tests-gke.yaml new file mode 100644 index 0000000000000..d0370a2f6ace0 --- /dev/null +++ b/.github/workflows/e2e-nongroovy-tests-gke.yaml @@ -0,0 +1,32 @@ +name: e2e-nongroovy-tests-gke + +on: + workflow_call: + inputs: + tag: + description: Image tag to test. + type: string + required: true + should-run: + description: true if should really run the job. false - otherwise, i.e. the job should be skipped. + type: boolean + required: true + workflow_dispatch: + inputs: + tag: + description: Image tag to test. + type: string + required: true + +permissions: + contents: read + pull-requests: read + +jobs: + run: + uses: ./.github/workflows/e2e-nongroovy-tests.yaml + with: + should-run: ${{ github.event_name == 'workflow_dispatch' || inputs.should-run }} + tag: ${{ inputs.tag }} + ci-job-name: gke-nongroovy-e2e-tests + secrets: inherit diff --git a/.github/workflows/e2e-nongroovy-tests.yaml b/.github/workflows/e2e-nongroovy-tests.yaml index d7a1cead3ca7d..768d5dfea1439 100644 --- a/.github/workflows/e2e-nongroovy-tests.yaml +++ b/.github/workflows/e2e-nongroovy-tests.yaml @@ -11,12 +11,14 @@ on: description: true if should really run the job. false - otherwise, i.e. the job should be skipped. type: boolean required: true - workflow_dispatch: - inputs: - tag: - description: Image tag to test. + ci-job-name: + description: Unique job name, e.g. gke-nongroovy-e2e-tests-konflux. Propagated to tests as CI_JOB_NAME for test dispatching. type: string required: true + extra-env-vars: + description: Additional environment variables as multi-line KEY=VALUE pairs (e.g., USE_ROXIE_DEPLOY=true) + type: string + required: false permissions: contents: read @@ -28,13 +30,13 @@ defaults: jobs: gke-nongroovy-e2e-tests: - if: github.event_name == 'workflow_dispatch' || inputs.should-run - runs-on: ubuntu-latest + if: inputs.should-run + runs-on: ubuntu-24.04 env: MAIN_IMAGE_TAG: ${{ inputs.tag }} BUILD_TAG: ${{ inputs.tag }} GCP_SERVICE_ACCOUNT_STACKROX_CI: ${{ secrets.GCP_SERVICE_ACCOUNT_STACKROX_CI }} - CI_JOB_NAME: gke-nongroovy-e2e-tests + CI_JOB_NAME: ${{ inputs.ci-job-name }} ARTIFACT_DIR: ./junit-reports LOAD_BALANCER: lb ORCHESTRATOR_FLAVOR: k8s @@ -42,9 +44,9 @@ jobs: SENSOR_SCANNER_V4_SUPPORT: "true" ROX_DEPLOY_SENSOR_WITH_CRS: "true" SENSOR_HELM_MANAGED: "true" - SCANNER_V4_DB_STORAGE_CLASS: faster STORAGE: pvc STORAGE_CLASS: faster + SCANNER_V4_DB_STORAGE_CLASS: faster ROX_SCANNER_V4: "true" PYTHONUNBUFFERED: "1" timeout-minutes: 361 @@ -61,6 +63,17 @@ jobs: with: gcp-account: ${{ secrets.GCP_SERVICE_ACCOUNT_STACKROX_CI }} + - name: Set additional env vars + if: inputs.extra-env-vars != '' + env: + EXTRA_ENV_VARS: ${{ inputs.extra-env-vars }} + run: | + while IFS= read -r line; do + if [[ -n "$line" ]]; then + echo "$line" >> "$GITHUB_ENV" + fi + done <<< "$EXTRA_ENV_VARS" + - name: Docker login to Quay.io env: REGISTRY_USERNAME: ${{ secrets.QUAY_RHACS_ENG_RO_USERNAME }} @@ -75,13 +88,22 @@ jobs: - name: Create GKE cluster env: MACHINE_TYPE: e2-standard-8 - NUM_NODES: "3" + NUM_NODES: "1" GKE_SPOT: "false" timeout-minutes: 20 run: | scripts/ci/gke.sh provision_gke_cluster nongroovy echo "KUBECONFIG=${HOME}/.kube/config" >> "$GITHUB_ENV" + - name: Retrieving roxie version from repository + id: roxie-version + run: echo "version=$(cat ROXIE_VERSION)" >> "$GITHUB_OUTPUT" + + - name: Install roxie + uses: stackrox/actions/roxie/install-cli@43bb707aa4f32ead5bfb8d5ab11fb409f971ffc8 # ratchet:stackrox/actions@v1 + with: + version: "v${{ steps.roxie-version.outputs.version }}" + - name: Build roxctl # roxctl is used for generate (deploy) and CLI e2e tests (token-file, etc). # Copy to /usr/local/bin so it survives bats tests which mv the binary out of bin/. diff --git a/.github/workflows/e2e-qa-tests-gke-konflux.yaml b/.github/workflows/e2e-qa-tests-gke-konflux.yaml index cf816fcc4232a..8b050adcc6ef9 100644 --- a/.github/workflows/e2e-qa-tests-gke-konflux.yaml +++ b/.github/workflows/e2e-qa-tests-gke-konflux.yaml @@ -35,4 +35,6 @@ jobs: ORCHESTRATOR_FLAVOR=k8s KUBERNETES_PROVIDER=gke USE_KONFLUX_IMAGES=true + STORAGE_CLASS=stackrox-gke-ssd + SCANNER_V4_DB_STORAGE_CLASS=stackrox-gke-ssd secrets: inherit diff --git a/.github/workflows/e2e-qa-tests-gke.yaml b/.github/workflows/e2e-qa-tests-gke.yaml index b575161117c70..6fb0aca91414d 100644 --- a/.github/workflows/e2e-qa-tests-gke.yaml +++ b/.github/workflows/e2e-qa-tests-gke.yaml @@ -30,4 +30,6 @@ jobs: extra-env-vars: | ORCHESTRATOR_FLAVOR=k8s KUBERNETES_PROVIDER=gke + STORAGE_CLASS=stackrox-gke-ssd + SCANNER_V4_DB_STORAGE_CLASS=stackrox-gke-ssd secrets: inherit diff --git a/.github/workflows/e2e-qa-tests-ocp-4-22.yaml b/.github/workflows/e2e-qa-tests-ocp-fips-konflux.yaml similarity index 62% rename from .github/workflows/e2e-qa-tests-ocp-4-22.yaml rename to .github/workflows/e2e-qa-tests-ocp-fips-konflux.yaml index 45d41397f18c4..7670ec997452d 100644 --- a/.github/workflows/e2e-qa-tests-ocp-4-22.yaml +++ b/.github/workflows/e2e-qa-tests-ocp-fips-konflux.yaml @@ -1,10 +1,12 @@ -name: e2e-qa-tests-ocp-4-22 +name: e2e-qa-tests-ocp-fips-konflux on: workflow_call: inputs: tag: - description: Image tag to test. + description: | + Image tag to test. The caller needs to make sure that the tag carries the + "-fast" Konflux suffix if required (e.g. for development builds). type: string required: true should-run: @@ -14,7 +16,9 @@ on: workflow_dispatch: inputs: tag: - description: Image tag to test. + description: | + Image tag to test. You need to make sure that the tag carries the + "-fast" Konflux suffix if required (e.g. for development builds). type: string required: true @@ -24,18 +28,19 @@ jobs: with: should-run: ${{ github.event_name == 'workflow_dispatch' || inputs.should-run }} tag: ${{ inputs.tag }} - ci-job-name: ocp-4-22-qa-e2e-tests - cluster-name-prefix: e2e-qa-tests-ocp422 + ci-job-name: ocp-qa-e2e-tests-fips-konflux + cluster-name-prefix: e2e-qa-ocp-fps-knflx cluster-provisioning-method: infra - cluster-wait-timeout-minutes: 50 + cluster-wait-timeout-minutes: 60 infra-flavor: openshift-4 infra-args: | - openshift-version=ocp/stable-4.22 + openshift-version=ocp/stable master-node-type=e2-standard-4 master-node-count=3 worker-node-type=e2-standard-8 worker-node-count=3 ssd-storage-class=true + fips-enabled=true infra-lifespan: 5h # TODO(ROX-36332): setup workload identities # SETUP_WORKLOAD_IDENTITIES=true @@ -43,4 +48,5 @@ jobs: ORCHESTRATOR_FLAVOR=openshift KUBERNETES_PROVIDER=ocp SFA_AGENT=true + USE_KONFLUX_IMAGES=true secrets: inherit diff --git a/.github/workflows/e2e-qa-tests.yaml b/.github/workflows/e2e-qa-tests.yaml index fe63f141908e0..543d5288be977 100644 --- a/.github/workflows/e2e-qa-tests.yaml +++ b/.github/workflows/e2e-qa-tests.yaml @@ -72,7 +72,7 @@ defaults: jobs: qa-e2e-tests: if: inputs.should-run - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 env: MAIN_IMAGE_TAG: ${{ inputs.tag }} BUILD_TAG: ${{ inputs.tag }} @@ -136,7 +136,7 @@ jobs: - name: Trigger cluster creation via infra if: inputs.cluster-provisioning-method == 'infra' - uses: stackrox/actions/infra/create-cluster@9238e423c3ae1ac4eb0f254cbb98da9daae24d86 # ratchet:stackrox/actions/infra/create-cluster@v1 + uses: stackrox/actions/infra/create-cluster@d624eeae7073b3acb838aab7705730b2de58a4a0 # ratchet:stackrox/actions/infra/create-cluster@v1 with: token: ${{ secrets.INFRA_TOKEN }} flavor: ${{ inputs.infra-flavor }} @@ -189,9 +189,10 @@ jobs: run: echo "version=$(cat ROXIE_VERSION)" >> "$GITHUB_OUTPUT" - name: Install roxie - uses: stackrox/actions/roxie/install-cli@6691d5c300d4af17546145bdbbb9862c53975af4 # ratchet:stackrox/actions@v1 + uses: stackrox/actions/roxie/install-cli@bcda9b2d6d2b644c267e699479656973e84ef7fa # ratchet:stackrox/actions@v1 with: version: "v${{ steps.roxie-version.outputs.version }}" + gh-token: ${{ github.token }} - name: Install oc if: startsWith(inputs.infra-flavor, 'openshift') diff --git a/.github/workflows/emailsender-central-compatibility.yaml b/.github/workflows/emailsender-central-compatibility.yaml index 9600d3cf78c94..f5d3f3952dae0 100644 --- a/.github/workflows/emailsender-central-compatibility.yaml +++ b/.github/workflows/emailsender-central-compatibility.yaml @@ -27,7 +27,7 @@ jobs: e2e-test-on-kind: timeout-minutes: 60 name: "Test on kind cluster" - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 if: ${{ !github.event.pull_request.head.repo.fork }} # do not run for PRs from forks permissions: id-token: write @@ -68,12 +68,13 @@ jobs: run: echo "version=$(cat stackrox/ROXIE_VERSION)" >> "$GITHUB_OUTPUT" - name: Install roxie - uses: stackrox/actions/roxie/install-cli@6691d5c300d4af17546145bdbbb9862c53975af4 # ratchet:stackrox/actions@v1 + uses: stackrox/actions/roxie/install-cli@bcda9b2d6d2b644c267e699479656973e84ef7fa # ratchet:stackrox/actions@v1 with: version: "v${{ steps.roxie-version.outputs.version }}" + gh-token: ${{ github.token }} - name: Create Kind cluster - uses: helm/kind-action@ef37e7f390d99f746eb8b610417061a60e82a6cc # ratchet:helm/kind-action@v1 + uses: helm/kind-action@06c1ae10762d3b9c1644e7fe69596ae519e015a2 # ratchet:helm/kind-action@v1.15.0 with: cluster_name: kind version: v0.32.0 diff --git a/.github/workflows/finish-release.yml b/.github/workflows/finish-release.yml index 480a4b67d6bfc..3e7a22ecaeed7 100644 --- a/.github/workflows/finish-release.yml +++ b/.github/workflows/finish-release.yml @@ -34,7 +34,7 @@ concurrency: Release automation ${{ inputs.version }} jobs: properties: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 outputs: slack-channel: ${{ fromJSON(format('["{0}","{1}"]', steps.fetch.outputs.dry-slack-channel, steps.fetch.outputs.slack-channel))[github.event.inputs.dry-run != 'true'] }} steps: @@ -47,7 +47,7 @@ jobs: run-parameters: if: github.event_name == 'workflow_dispatch' name: Run parameters - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - run: | [ "$DRY_RUN" = "true" ] && echo "::warning::This is a dry run" @@ -70,7 +70,7 @@ jobs: preflight-checks: needs: [variables] - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - name: Verify that this release is listed in scanner’s RELEASE_VERSION file on master run: | @@ -81,7 +81,7 @@ jobs: publish-release: name: Tag Release ${{ needs.variables.outputs.named-release-patch }} - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 needs: [variables, properties, preflight-checks] steps: - name: Check out code @@ -183,7 +183,7 @@ jobs: update-infra-demo-version: name: Update infra demo default versions - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 needs: [publish-release] env: BRANCH: "bump-demo-versions-${{ inputs.version }}" diff --git a/.github/workflows/fixxxer.yaml b/.github/workflows/fixxxer.yaml index d581a1ae15229..f2706f6355ef5 100644 --- a/.github/workflows/fixxxer.yaml +++ b/.github/workflows/fixxxer.yaml @@ -15,7 +15,7 @@ jobs: name: Run PR Fixxxer # TODO: support leading and trailing whitespace too if: ${{ github.event.issue.pull_request && github.event.comment.body == '/fixxx' }} - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 container: image: quay.io/stackrox-io/apollo-ci:stackrox-test-0.5.11@sha256:df3459cf038e73775314b3a5d36d14dda41e6df438a0273c9efa8d23e0f2358a # ratchet:quay.io/stackrox-io/apollo-ci:stackrox-test-0.5.11 steps: diff --git a/.github/workflows/labeler.yaml b/.github/workflows/labeler.yaml index 3a48fb21bfee2..53e135c2eebf5 100644 --- a/.github/workflows/labeler.yaml +++ b/.github/workflows/labeler.yaml @@ -8,7 +8,7 @@ on: jobs: label-pr: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 if: ${{ github.actor != 'dependabot[bot]' && !github.event.pull_request.head.repo.fork }} steps: - uses: actions/labeler@b8dd2d9be0f68b860e7dae5dae7d772984eacd6d # ratchet:actions/labeler@v6 diff --git a/.github/workflows/nightly-tag.yml b/.github/workflows/nightly-tag.yml index f40edc3724b0e..6ef44bae5fd8f 100644 --- a/.github/workflows/nightly-tag.yml +++ b/.github/workflows/nightly-tag.yml @@ -6,7 +6,7 @@ on: jobs: create-nightly-tag: if: ${{ github.repository_owner == 'stackrox' }} - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - name: Checkout uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # ratchet:actions/checkout@v7.0.0 @@ -20,7 +20,7 @@ jobs: slack-on-failure: needs: create-nightly-tag if: failure() - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - name: Slack on failure uses: slackapi/slack-github-action@45a88b9581bfab2566dc881e2cd66d334e621e2c # ratchet:slackapi/slack-github-action@v3.0.3 diff --git a/.github/workflows/notify-milestone-change.yml b/.github/workflows/notify-milestone-change.yml index a6a648b50dec0..dc327ead0b241 100644 --- a/.github/workflows/notify-milestone-change.yml +++ b/.github/workflows/notify-milestone-change.yml @@ -18,7 +18,7 @@ env: jobs: properties: name: Read repository properties - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 outputs: docs-repository: ${{ steps.properties.outputs.docs-repository }} slack-channel: ${{ fromJSON(format('["{0}","{1}"]', steps.properties.outputs.dry-slack-channel, steps.properties.outputs.slack-channel))[github.repository == 'stackrox/stackrox'] }} @@ -31,7 +31,7 @@ jobs: run: gh api -H "$ACCEPT_RAW" "$PROPERTIES_URL" >> "$GITHUB_OUTPUT" notify: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 needs: [properties] steps: - name: Determine Jira context for PR diff --git a/.github/workflows/performance-tests.yml b/.github/workflows/performance-tests.yml index c1561db61871e..3e33fc821ee70 100644 --- a/.github/workflows/performance-tests.yml +++ b/.github/workflows/performance-tests.yml @@ -10,7 +10,7 @@ concurrency: performance-tests-${{ github.ref }} jobs: wait-for-images: name: Wait for images - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 if: ${{ contains(github.event.pull_request.labels.*.name, 'ci-performance-tests') }} steps: - name: Checkout repo @@ -33,7 +33,7 @@ jobs: run-k6-performance-tests: needs: [wait-for-images] name: Run k6 performance tests - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 if: ${{ contains(github.event.pull_request.labels.*.name, 'ci-performance-tests') }} env: USE_GKE_GCLOUD_AUTH_PLUGIN: "True" @@ -57,9 +57,19 @@ jobs: repository: "stackrox/workflow" path: workflow + - name: Retrieve roxie version from repository + id: roxie-version + run: echo "version=$(cat ROXIE_VERSION)" >> "$GITHUB_OUTPUT" + + - name: Install roxie + uses: stackrox/actions/roxie/install-cli@bcda9b2d6d2b644c267e699479656973e84ef7fa # ratchet:stackrox/actions@v1 + with: + version: "v${{ steps.roxie-version.outputs.version }}" + gh-token: ${{ secrets.GITHUB_TOKEN }} + # Create scale cluster with larger nodes. - name: Create testing cluster - uses: stackrox/actions/infra/create-cluster@9238e423c3ae1ac4eb0f254cbb98da9daae24d86 # ratchet:stackrox/actions/infra/create-cluster@v1 + uses: stackrox/actions/infra/create-cluster@d624eeae7073b3acb838aab7705730b2de58a4a0 # ratchet:stackrox/actions/infra/create-cluster@v1 with: token: ${{ secrets.INFRA_TOKEN }} flavor: gke-default @@ -97,12 +107,8 @@ jobs: - name: Clean previous installation run: | source ./workflow/env.sh - ./scripts/k8s/cleanup.sh stackrox - - # Delete all namespaces created by scale script. - for i in $(seq 1 "${SCALE_WORKLOAD_CLUSTERS}"); do - ./scripts/k8s/cleanup.sh "stackrox${i}" - done + roxie teardown central --single-namespace + ./scale/dev/kill-sensors.sh - name: Get PR image tag run: | @@ -112,37 +118,29 @@ jobs: # Install Central + CentralDB - name: Launch central services env: - LOAD_BALANCER: lb - ROX_POSTGRES_DATASTORE: "true" - MONITORING_SUPPORT: "false" REGISTRY_USERNAME: ${{ secrets.QUAY_RHACS_ENG_RO_USERNAME }} REGISTRY_PASSWORD: ${{ secrets.QUAY_RHACS_ENG_RO_PASSWORD }} run: | - source ./workflow/env.sh + set -euo pipefail - # ROX-29641: We are facing issues with workload generation on GKE when Scanner v4 is enabled. - export ROX_SCANNER_V4=false ./scale/dev/launch_central.sh - + source .roxie.env # Required for k6 tests to run. - echo "ROX_ADMIN_PASSWORD=$(cat ./deploy/k8s/central-deploy/password)" >> "$GITHUB_ENV" - echo "ROX_PASSWORD=$(cat ./deploy/k8s/central-deploy/password)" >> "$GITHUB_ENV" - echo "::add-mask::$(cat ./deploy/k8s/central-deploy/password)" + echo "ROX_ADMIN_PASSWORD=${ROX_ADMIN_PASSWORD}" >> "$GITHUB_ENV" + echo "ROX_PASSWORD=${ROX_ADMIN_PASSWORD}" >> "$GITHUB_ENV" + echo "::add-mask::${ROX_ADMIN_PASSWORD}" - LB_IP="$(kubectl -n stackrox get svc/central-loadbalancer -o json | jq -r '.status.loadBalancer.ingress[0] | .ip // .hostname')" - echo "HOST=https://${LB_IP}:443" >> "$GITHUB_ENV" - echo "API_ENDPOINT=${LB_IP}:443" >> "$GITHUB_ENV" - echo "::add-mask::${LB_IP}" + echo "HOST=${ROX_BASE_URL}" >> "$GITHUB_ENV" + echo "API_ENDPOINT=${API_ENDPOINT}" >> "$GITHUB_ENV" + echo "::add-mask::${API_ENDPOINT}" - name: Generate workload env: - LOAD_BALANCER: lb - ROX_POSTGRES_DATASTORE: "true" - MONITORING_SUPPORT: "false" REGISTRY_USERNAME: ${{ secrets.QUAY_RHACS_ENG_RO_USERNAME }} REGISTRY_PASSWORD: ${{ secrets.QUAY_RHACS_ENG_RO_PASSWORD }} run: | source ./workflow/env.sh + source .roxie.env ./scale/dev/run-many.sh "${SCALE_WORKLOAD_TYPE}" "${SCALE_WORKLOAD_CLUSTERS}" num_deployments=$(yq '.deploymentWorkload[] | select(.deploymentType == "Deployment") | .numDeployments' "./scale/workloads/${SCALE_WORKLOAD_TYPE}.yaml") diff --git a/.github/workflows/release-ci.yaml b/.github/workflows/release-ci.yaml index ea7cc7238d2b6..7a631fd36b3ad 100644 --- a/.github/workflows/release-ci.yaml +++ b/.github/workflows/release-ci.yaml @@ -9,19 +9,23 @@ on: jobs: run-parameters: name: Run parameters - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - run: | echo "Event: ${{ github.event_name }}" >> "$GITHUB_STEP_SUMMARY" echo "Ref: ${{ github.ref_name }}" >> "$GITHUB_STEP_SUMMARY" build: + permissions: + actions: read + contents: read + pull-requests: write name: Build uses: ./.github/workflows/build.yaml secrets: inherit check-is-release: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 outputs: is_release: ${{ steps.is_release_check.outputs.is_release }} steps: @@ -40,7 +44,7 @@ jobs: fi check-collector-version: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - name: Checkout uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # ratchet:actions/checkout@v7.0.0 @@ -53,7 +57,7 @@ jobs: check_collector_version check-fact-version: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - name: Checkout uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # ratchet:actions/checkout@v7.0.0 @@ -68,7 +72,7 @@ jobs: publish-helm-charts: needs: [build, check-collector-version, check-fact-version, check-is-release] if: needs.check-is-release.outputs.is_release == 'true' - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - name: Checkout uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # ratchet:actions/checkout@v7.0.0 @@ -97,7 +101,7 @@ jobs: publish-cli: needs: [build, check-collector-version, check-fact-version, check-is-release] if: needs.check-is-release.outputs.is_release == 'true' - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - name: Checkout uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # ratchet:actions/checkout@v7.0.0 @@ -123,7 +127,7 @@ jobs: publish-openapispec: needs: [build, check-collector-version, check-fact-version, check-is-release] if: needs.check-is-release.outputs.is_release == 'true' - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - name: Checkout uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # ratchet:actions/checkout@v7.0.0 diff --git a/.github/workflows/retest_periodic.yml b/.github/workflows/retest_periodic.yml index d30e223e91813..3c371a6102804 100644 --- a/.github/workflows/retest_periodic.yml +++ b/.github/workflows/retest_periodic.yml @@ -10,7 +10,7 @@ jobs: retest: if: ${{ github.repository_owner == 'stackrox' }} timeout-minutes: 10 - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - name: Checkout uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # ratchet:actions/checkout@v7.0.0 diff --git a/.github/workflows/scan-image-vulnerabilities.yaml b/.github/workflows/scan-image-vulnerabilities.yaml index 2ee393ab6b219..bb4befe0852d1 100644 --- a/.github/workflows/scan-image-vulnerabilities.yaml +++ b/.github/workflows/scan-image-vulnerabilities.yaml @@ -21,7 +21,7 @@ run-name: ${{ format('Scan images for vulnerabilities ({0})', inputs.version || jobs: run-parameters: name: Run parameters - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 outputs: wait-for-images-limit: ${{ steps.run-parameters.outputs.wait-for-images-limit }} version: ${{ steps.run-parameters.outputs.version }} @@ -47,7 +47,7 @@ jobs: scan-image-vulnerabilities-upstream: name: "Upstream: ${{ matrix.image }}:${{ needs.run-parameters.outputs.version }}" - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 needs: [run-parameters] permissions: # Needed for stackrox/central-login to create the JWT token. @@ -99,7 +99,7 @@ jobs: scan-image-vulnerabilities-konflux: name: "Konflux: ${{ matrix.image }}:${{ needs.run-parameters.outputs.version }}" - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 needs: [run-parameters] permissions: # Needed for stackrox/central-login to create the JWT token. diff --git a/.github/workflows/scanner-db-integration-tests.yaml b/.github/workflows/scanner-db-integration-tests.yaml index 785f47924b715..effbe1190271e 100644 --- a/.github/workflows/scanner-db-integration-tests.yaml +++ b/.github/workflows/scanner-db-integration-tests.yaml @@ -20,7 +20,7 @@ jobs: db-integration-tests: needs: detect-changes if: ${{ !needs.detect-changes.outputs.ui_only }} - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 container: image: quay.io/stackrox-io/apollo-ci:scanner-test-0.5.11@sha256:cea78f2b3a5a38c9ccda678712daaed90b7c372d6b5df963feb758b32dccebe1 # ratchet:quay.io/stackrox-io/apollo-ci:scanner-test-0.5.11 steps: diff --git a/.github/workflows/scanner-e2e-test.yaml b/.github/workflows/scanner-e2e-test.yaml index e4ee1333e3964..b2d0e9670e165 100644 --- a/.github/workflows/scanner-e2e-test.yaml +++ b/.github/workflows/scanner-e2e-test.yaml @@ -14,7 +14,7 @@ jobs: if: > github.event_name != 'pull_request' || contains(github.event.pull_request.labels.*.name, 'scanner-functional-tests') - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - name: Checkout repo uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # ratchet:actions/checkout@v7.0.0 @@ -36,7 +36,7 @@ jobs: name: Deploy and run needs: - scanner-ft-images - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 env: SCANNER_E2E_QUAY_USERNAME: ${{ secrets.QUAY_RHACS_ENG_RO_USERNAME }} SCANNER_E2E_QUAY_PASSWORD: ${{ secrets.QUAY_RHACS_ENG_RO_PASSWORD }} diff --git a/.github/workflows/scanner-mapping-update.yaml b/.github/workflows/scanner-mapping-update.yaml index 9883bf11b4c04..236c427937429 100644 --- a/.github/workflows/scanner-mapping-update.yaml +++ b/.github/workflows/scanner-mapping-update.yaml @@ -7,7 +7,7 @@ on: jobs: update-scanner-mappings: if: ${{ github.repository_owner == 'stackrox' }} - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 env: REPOMAPPING_DIR: repomapping steps: @@ -37,7 +37,7 @@ jobs: send-notification: needs: update-scanner-mappings if: failure() - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 permissions: contents: read checks: read diff --git a/.github/workflows/scanner-nvd-update.yaml b/.github/workflows/scanner-nvd-update.yaml index 0a29f375363c7..8294ffb27ab71 100644 --- a/.github/workflows/scanner-nvd-update.yaml +++ b/.github/workflows/scanner-nvd-update.yaml @@ -12,7 +12,7 @@ on: jobs: fetch-nvd-feeds: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 if: > github.repository_owner == 'stackrox' && github.event_name == 'schedule' || @@ -51,7 +51,7 @@ jobs: if-no-files-found: error fetch-nvd-api: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 if: > github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' || @@ -94,7 +94,7 @@ jobs: upload-pr-nvd-feeds: needs: - fetch-nvd-feeds - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 if: github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'pr-update-scanner-nvd') steps: # Checkout master to get the latest local actions @@ -153,7 +153,7 @@ jobs: upload-nvd-feeds: needs: - fetch-nvd-feeds - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 if: github.event_name != 'pull_request' steps: - name: Checkout repository @@ -182,7 +182,7 @@ jobs: upload-nvd-api: needs: - fetch-nvd-api - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 if: github.event_name != 'pull_request' steps: - name: Checkout repository @@ -212,7 +212,7 @@ jobs: needs: - upload-nvd-feeds - upload-nvd-api - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 if: ${{ failure() && github.ref_name == 'master' }} permissions: contents: read diff --git a/.github/workflows/scanner-offline-bundle-update.yaml b/.github/workflows/scanner-offline-bundle-update.yaml index 657246451eb02..10213e553e435 100644 --- a/.github/workflows/scanner-offline-bundle-update.yaml +++ b/.github/workflows/scanner-offline-bundle-update.yaml @@ -11,7 +11,7 @@ on: jobs: offline-bundle-matrix: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 if: > github.repository_owner == 'stackrox' && github.event_name == 'schedule' || @@ -36,7 +36,7 @@ jobs: offline-bundle-generate: needs: offline-bundle-matrix - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 strategy: fail-fast: false matrix: @@ -69,7 +69,7 @@ jobs: if-no-files-found: error offline-bundle-upload: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 needs: - offline-bundle-generate steps: @@ -125,7 +125,7 @@ jobs: - offline-bundle-matrix - offline-bundle-generate - offline-bundle-upload - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 if: github.event_name == 'schedule' && failure() permissions: contents: read diff --git a/.github/workflows/scanner-updater-bundle.yaml b/.github/workflows/scanner-updater-bundle.yaml index 8f26a5e8e6ba4..de4e98dc7c40a 100644 --- a/.github/workflows/scanner-updater-bundle.yaml +++ b/.github/workflows/scanner-updater-bundle.yaml @@ -21,7 +21,7 @@ permissions: jobs: build-matrix: if: github.repository_owner == 'stackrox' - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 outputs: streams: ${{ steps.emit.outputs.streams }} steps: @@ -40,7 +40,7 @@ jobs: bundle: needs: build-matrix - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 container: image: quay.io/stackrox-io/apollo-ci:scanner-test-0.5.11@sha256:cea78f2b3a5a38c9ccda678712daaed90b7c372d6b5df963feb758b32dccebe1 volumes: @@ -216,7 +216,7 @@ jobs: notify: needs: [bundle] if: always() && needs.bundle.result == 'failure' - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 permissions: contents: read checks: read diff --git a/.github/workflows/scanner-updater-export.yaml b/.github/workflows/scanner-updater-export.yaml index 91e3862ab8ba4..cbe0b3a3fa41c 100644 --- a/.github/workflows/scanner-updater-export.yaml +++ b/.github/workflows/scanner-updater-export.yaml @@ -40,7 +40,7 @@ permissions: jobs: recheck: if: ${{ !inputs.skip_freshness_check }} - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 outputs: result: ${{ steps.check.outputs.result }} concurrency: @@ -89,7 +89,7 @@ jobs: update: needs: recheck if: always() && (needs.recheck.result == 'skipped' || needs.recheck.outputs.result == 'run') - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 concurrency: group: scanner-updater-export-${{ inputs.object }} cancel-in-progress: false @@ -195,7 +195,7 @@ jobs: !inputs.skip_freshness_check && always() && (needs.recheck.result == 'failure' || needs.update.result == 'failure') - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 permissions: contents: read checks: read diff --git a/.github/workflows/scanner-updater-schedule.yaml b/.github/workflows/scanner-updater-schedule.yaml index 7999ec6045cbc..7cdf390562a86 100644 --- a/.github/workflows/scanner-updater-schedule.yaml +++ b/.github/workflows/scanner-updater-schedule.yaml @@ -16,12 +16,15 @@ permissions: jobs: build-matrix: + permissions: + contents: read + actions: read if: >- github.repository_owner == 'stackrox' && ( github.event_name != 'pull_request' || contains(github.event.pull_request.labels.*.name, 'pr-update-scanner-vulns') ) - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 outputs: needs-notification: ${{ steps.matrix-scheduled.outputs.has_errors }} steps: @@ -48,6 +51,9 @@ jobs: SOURCE_CONFIG: scanner/updater/ci/source-config.yaml run: | set -euo pipefail + # Keep cadence eligibility stable if this job is queued or rerun. + schedule_time=$(GH_TOKEN="${{ github.token }}" gh api "repos/${{ github.repository }}/actions/runs/${{ github.run_id }}" --jq '.created_at') + export SCHEDULE_TIME="$schedule_time" result=$(./.github/workflows/scripts/scanner-updater-matrix.sh) { echo "has_errors=$(echo "$result" | jq -r '.has_errors')" @@ -98,7 +104,7 @@ jobs: plan: needs: build-matrix - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 outputs: has_due: ${{ steps.emit.outputs.has_due }} matrix: ${{ steps.emit.outputs.matrix }} @@ -152,7 +158,7 @@ jobs: if: >- github.event_name != 'pull_request' && (failure() || needs.build-matrix.outputs.needs-notification == 'true') - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 permissions: contents: read checks: read diff --git a/.github/workflows/scanner-versioned-definitions-update.yaml b/.github/workflows/scanner-versioned-definitions-update.yaml index 3216f74a5bcf5..c9ba26d32df8f 100644 --- a/.github/workflows/scanner-versioned-definitions-update.yaml +++ b/.github/workflows/scanner-versioned-definitions-update.yaml @@ -43,7 +43,7 @@ on: jobs: parse-versions: if: github.repository_owner == 'stackrox' && github.event_name != 'pull_request' || contains(github.event.pull_request.labels.*.name, 'pr-update-scanner-vulns') - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 outputs: versions: ${{ steps.set-versions.outputs.versions }} steps: @@ -102,7 +102,7 @@ jobs: prepare-environment: if: github.event_name != 'pull_request' || contains(github.event.pull_request.labels.*.name, 'pr-update-scanner-vulns') - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 outputs: manual_url: ${{ steps.set-manual-url.outputs.manual_url }} steps: @@ -187,7 +187,7 @@ jobs: needs: - parse-versions - prepare-environment - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 container: image: quay.io/stackrox-io/apollo-ci:scanner-test-0.5.11@sha256:cea78f2b3a5a38c9ccda678712daaed90b7c372d6b5df963feb758b32dccebe1 # ratchet:quay.io/stackrox-io/apollo-ci:scanner-test-0.5.11 volumes: @@ -340,7 +340,7 @@ jobs: if: github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'pr-update-scanner-vulns') needs: - prepare-environment - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 container: image: quay.io/stackrox-io/apollo-ci:scanner-test-0.5.11@sha256:cea78f2b3a5a38c9ccda678712daaed90b7c372d6b5df963feb758b32dccebe1 # ratchet:quay.io/stackrox-io/apollo-ci:scanner-test-0.5.11 volumes: @@ -430,7 +430,7 @@ jobs: needs: - build-and-run if: ${{ (failure() || success()) && (github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && github.event.inputs.dry_run == 'false')) }} - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: # Checkout to run ./.github/actions/download-artifact-with-retry - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # ratchet:actions/checkout@v7.0.0 @@ -510,7 +510,7 @@ jobs: needs: - build-and-run - upload-definitions - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 if: ${{ failure() && github.ref_name == 'master' && (github.event_name == 'schedule' || github.event.inputs.dry_run == 'false') }} permissions: checks: read diff --git a/.github/workflows/scripts/crdify.sh b/.github/workflows/scripts/crdify.sh new file mode 100755 index 0000000000000..37e9ce64b545f --- /dev/null +++ b/.github/workflows/scripts/crdify.sh @@ -0,0 +1,75 @@ +#!/usr/bin/env bash +# Run crdify on copies of CRDs after stripping out known false positives. +# Usage: +# crdify.sh [crdify flags] + +set -euo pipefail + +ROOT_DIR="$(dirname "${BASH_SOURCE[0]}")/../../.." +readonly ROOT_DIR + +if [[ $# -lt 2 ]]; then + echo "usage: $0 [crdify flags]" >&2 + exit 2 +fi + +old_crd="${1:?missing required argument 'old-crd'}" +new_crd="${2:?missing required argument 'new-crd'}" +shift +shift + +if [[ ! -f "$old_crd" ]]; then + echo "::error::CRD file not found: $old_crd" >&2 + exit 1 +fi + +if [[ ! -f "$new_crd" ]]; then + echo "::error::CRD file not found: $new_crd" >&2 + exit 1 +fi + + +# The addition of this particular validation rule, even though considered a breaking change by definition +# (https://github.com/kubernetes/community/blob/main/contributors/devel/sig-architecture/api_changes.md#backward-compatibility-gotchas) +# is fine in practice, since the `excludeByKind` branch is being added in the same release cycle as the rule itself, +# meaning no production CRS could have existed that violated the validation. +filter_spec_exclusions_one_of() { + local input="$1" + local output="$2" + + local message='at most one of the fields in [deployment excludeByKind] may be set' + local rule='(has(self.deployment)?1:0)+(has(self.excludeByKind)?1:0) <= 1' + + # Remove matching rule, and the validations list if empty afterwards. + message="${message}" rule="${rule}" \ + go run -C "${ROOT_DIR}/operator/tools/yq" github.com/mikefarah/yq/v4 eval ' + del( + .spec.versions[].schema.openAPIV3Schema.properties.spec.properties.exclusions.items."x-kubernetes-validations"[] + | select(.message == strenv(message) and .rule == strenv(rule)) + ) + | del( + .spec.versions[].schema.openAPIV3Schema.properties.spec.properties.exclusions.items."x-kubernetes-validations" + | select(length == 0) + ) + ' "$input" > "$output" +} + + +crd_basename="$(basename "${old_crd}")" +case "$crd_basename" in +config.stackrox.io_securitypolicies.yaml) + filtered_dir="$(mktemp -d)" + echo >&2 "Filtering known false positives from ${old_crd} and ${new_crd}" + filter_spec_exclusions_one_of "${old_crd}" "${filtered_dir}/old-${crd_basename}" + filter_spec_exclusions_one_of "${new_crd}" "${filtered_dir}/new-${crd_basename}" + old_crd="${filtered_dir}/old-${crd_basename}" + new_crd="${filtered_dir}/new-${crd_basename}" + ;; +*) + echo >&2 "No filtering rules for ${old_crd}, running crdify on original CRDs." + ;; +esac + +set -x +go run -C "${ROOT_DIR}/operator/tools/crdify" sigs.k8s.io/crdify --config=config.yaml "$@" \ + "file://${old_crd}" "file://${new_crd}" diff --git a/.github/workflows/scripts/scanner-updater-config.sh b/.github/workflows/scripts/scanner-updater-config.sh index 6af97f972a0bd..95a63e7200a3a 100755 --- a/.github/workflows/scripts/scanner-updater-config.sh +++ b/.github/workflows/scripts/scanner-updater-config.sh @@ -11,6 +11,7 @@ # source-object GCS object path for a source. # bundle-object GCS object path for a bundle. # interval-secs Updater refresh interval in seconds. +# attempt-cadence-secs Attempt cadence in seconds; 0 if unset. # # Inputs (environment): # SOURCE_CONFIG — path to source-config.yaml @@ -26,7 +27,7 @@ if [[ ! -f "$config" ]]; then exit 1 fi -if ! yq '.' "$config" > /dev/null 2>&1; then +if ! yq eval '.' "$config" > /dev/null 2>&1; then echo "::error::config file is not valid YAML: $config" >&2 exit 1 fi @@ -52,34 +53,48 @@ command="${1:?missing required argument 'command'}"; shift case "$command" in bucket) - yq '.storage.bucket' "$config" + yq eval '.storage.bucket' "$config" ;; prefix) - yq '.storage.prefix' "$config" + yq eval '.storage.prefix' "$config" ;; sources) stream="${1:?missing required argument 'stream'}"; shift - yq ".bundles.\"${stream}\"[]" "$config" + yq eval ".bundles.\"${stream}\"[]" "$config" ;; source-object) stream="${1:?missing required argument 'stream'}"; shift source="${1:?missing required argument 'source'}"; shift - bucket=$(yq '.storage.bucket' "$config") - prefix=$(yq '.storage.prefix' "$config") + bucket=$(yq eval '.storage.bucket' "$config") + prefix=$(yq eval '.storage.prefix' "$config") echo "gs://${bucket}/${prefix}/${stream}/sources/${source}.json.zst" ;; bundle-object) stream="${1:?missing required argument 'stream'}"; shift - bucket=$(yq '.storage.bucket' "$config") - prefix=$(yq '.storage.prefix' "$config") + bucket=$(yq eval '.storage.bucket' "$config") + prefix=$(yq eval '.storage.prefix' "$config") echo "gs://${bucket}/${prefix}/${stream}/vulnerabilities.zip" ;; interval-secs) source="${1:?missing required argument 'source'}"; shift - default_interval=$(yq '.updaters.default.interval // "4h"' "$config") - interval=$(yq ".updaters.\"${source}\".interval // \"$default_interval\"" "$config") + default_interval=$(yq eval '.updaters.default.interval // "4h"' "$config") + interval=$(yq eval ".updaters.\"${source}\".interval // \"$default_interval\"" "$config") duration_to_seconds "$interval" ;; + attempt-cadence-secs) + source="${1:?missing required argument 'source'}"; shift + cadence=$(yq eval ".updaters.\"${source}\".attempt_cadence // .updaters.default.attempt_cadence // \"\"" "$config") + if [[ -z "$cadence" ]]; then + echo 0 + exit 0 + fi + cadence_secs=$(duration_to_seconds "$cadence") + if (( cadence_secs <= 0 || cadence_secs % 3600 != 0 )); then + echo "::error::attempt_cadence must be a positive whole-hour duration: $cadence" >&2 + exit 1 + fi + echo "$cadence_secs" + ;; *) echo "::error::unknown command: $command" >&2 exit 1 diff --git a/.github/workflows/scripts/scanner-updater-matrix.sh b/.github/workflows/scripts/scanner-updater-matrix.sh index a4fd4b0e1a744..4e99bfd923e37 100755 --- a/.github/workflows/scripts/scanner-updater-matrix.sh +++ b/.github/workflows/scripts/scanner-updater-matrix.sh @@ -10,6 +10,7 @@ # # Inputs (environment): # SOURCE_CONFIG — path to source-config.yaml +# SCHEDULE_TIME — workflow run creation time; defaults to the current time. # # Outputs (stdout): # JSON object with "has_due", "has_errors", "errors", and "matrix" @@ -32,6 +33,9 @@ sc="$(dirname "$0")/scanner-updater-config.sh" bucket=$("$sc" bucket) prefix=$("$sc" prefix) +schedule_epoch=$(date -u -d "${SCHEDULE_TIME:-now}" +%s) +schedule_hour=$(( schedule_epoch / 3600 )) + # Expand stream/ref/updater triples, check freshness, emit due pairs. ./.github/workflows/scripts/scanner-get-released-tags.sh \ | jq -r '.[] | "\(.version) \(.ref)"' \ @@ -44,10 +48,17 @@ prefix=$("$sc" prefix) | while read -r stream ref updater; do object="gs://${bucket}/${prefix}/${stream}/sources/${updater}.json.zst" interval_secs=$("$sc" interval-secs "$updater") + cadence_secs=$("$sc" attempt-cadence-secs "$updater") + if (( cadence_secs > 0 )); then + cadence_hours=$(( cadence_secs / 3600 )) + if (( schedule_hour % cadence_hours != 0 )); then + continue + fi + fi fresh=$(GCS_OBJECT="$object" \ ./.github/workflows/scripts/scanner-updater-check-freshness.sh --max-age "$interval_secs") || { - echo "::error::${object}: freshness check failed" + echo "::error::${object}: freshness check failed" >&2 jq -n --arg msg "${object}: freshness check failed" '{error: $msg}' continue } diff --git a/.github/workflows/scripts/verify-release.sh b/.github/workflows/scripts/verify-release.sh index 6e9f0f0b2c200..528cf5c4660ad 100644 --- a/.github/workflows/scripts/verify-release.sh +++ b/.github/workflows/scripts/verify-release.sh @@ -102,7 +102,7 @@ validate_images() { validate_docs() { RELEASE="$1" - check_url_page_exists "https://docs.openshift.com/acs/${RELEASE}/welcome/index.html" + check_url_page_exists "https://docs.redhat.com/en/documentation/red_hat_advanced_cluster_security_for_kubernetes/${RELEASE}" } validate_jira_release() { diff --git a/.github/workflows/signing-key-bundle-update.yaml b/.github/workflows/signing-key-bundle-update.yaml index d456185299106..fda57fde6bd44 100644 --- a/.github/workflows/signing-key-bundle-update.yaml +++ b/.github/workflows/signing-key-bundle-update.yaml @@ -18,7 +18,7 @@ concurrency: jobs: validate-and-upload: if: ${{ github.repository_owner == 'stackrox' }} - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - name: Checkout repository uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # ratchet:actions/checkout@v7.0.0 @@ -48,7 +48,7 @@ jobs: send-notification: needs: - validate-and-upload - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 if: failure() permissions: contents: read diff --git a/.github/workflows/start-release.yml b/.github/workflows/start-release.yml index 34116d543db82..d23caf47e4a8b 100644 --- a/.github/workflows/start-release.yml +++ b/.github/workflows/start-release.yml @@ -42,7 +42,7 @@ concurrency: Release automation ${{ inputs.version }} jobs: start-sha: name: Find commit for release branch - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 outputs: start-sha: ${{ fromJSON(format('["{0}","{1}"]', env.USER_REF, steps.find.outputs.merge_base))[env.USER_REF == ''] }} steps: @@ -67,7 +67,7 @@ jobs: properties: name: Read repository properties - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 outputs: docs-repository: ${{ steps.properties.outputs.docs-repository }} slack-channel: ${{ fromJSON(format('["{0}","{1}"]', steps.properties.outputs.dry-slack-channel, steps.properties.outputs.slack-channel))[github.event.inputs.dry-run != 'true'] }} @@ -81,7 +81,7 @@ jobs: run-parameters: name: Run parameters - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - run: | [ "$DRY_RUN" = "true" ] && echo "::warning::This is a dry run" @@ -102,7 +102,7 @@ jobs: check-jira: name: Check Jira release needs: [variables, properties] - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 outputs: release-date: ${{steps.check-jira-release.outputs.date}} steps: @@ -122,7 +122,7 @@ jobs: branch: name: Prepare release branch needs: [variables, start-sha, check-jira] - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - name: Check out code (normal release) if: needs.variables.outputs.patch == 0 @@ -193,7 +193,7 @@ jobs: name: Configure OpenShift CI jobs needs: [variables, branch] if: needs.variables.outputs.patch == 0 - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 env: RELEASE: "${{needs.variables.outputs.release}}" BRANCH: "stackrox-release-${{needs.variables.outputs.release}}" @@ -262,7 +262,7 @@ jobs: name: Patch CHANGELOG.md on the default branch needs: [variables, branch, start-sha] if: needs.variables.outputs.patch == 0 - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - name: Check out code uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # ratchet:actions/checkout@v7.0.0 @@ -288,7 +288,7 @@ jobs: patch-scanner-versions: name: Patch Scanner updater configuration needs: [variables, branch] - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - name: Check out code uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # ratchet:actions/checkout@v7.0.0 @@ -310,7 +310,7 @@ jobs: milestone: name: Create milestone needs: [variables] - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - name: Create ${{needs.variables.outputs.next-milestone}} milestone if: env.DRY_RUN == 'false' @@ -336,7 +336,7 @@ jobs: notify: name: Notify everybody needs: [variables, properties, branch, milestone] - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - name: Post to Slack (normal release) if: needs.variables.outputs.patch == 0 diff --git a/.github/workflows/style.yaml b/.github/workflows/style.yaml index 0cfe9f54c7978..3989115a09bab 100644 --- a/.github/workflows/style.yaml +++ b/.github/workflows/style.yaml @@ -27,7 +27,7 @@ jobs: if: ${{ !needs.detect-changes.outputs.ui_only }} env: ARTIFACT_DIR: junit-reports/ - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - name: Checkout uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # ratchet:actions/checkout@v7.0.0 @@ -54,7 +54,7 @@ jobs: misc-checks: env: ARTIFACT_DIR: junit-reports/ - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - name: Checkout uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # ratchet:actions/checkout@v7.0.0 @@ -97,7 +97,7 @@ jobs: run: if [ -s scripts/ci/jobs/check-dependabot-gomod.sh ]; then scripts/ci/jobs/check-dependabot-gomod.sh; fi style-check: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - name: Checkout uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # ratchet:actions/checkout@v7.0.0 @@ -159,7 +159,7 @@ jobs: needs: detect-changes if: ${{ !needs.detect-changes.outputs.ui_only }} timeout-minutes: 240 - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - name: Checkout uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # ratchet:actions/checkout@v7.0.0 @@ -208,7 +208,7 @@ jobs: # This job ensures that COLLECTOR_VERSION or FACT_VERSION files cannot be updated to a version # for which the image was not successfully built on Konflux (suffix "-fast"). It also verifies that GHA-built image # is there (no suffix) so that the failure also happens in this job. - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - name: Checkout repo uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # ratchet:actions/checkout@v7.0.0 @@ -253,7 +253,7 @@ jobs: if: >- !needs.detect-changes.outputs.files || contains(needs.detect-changes.outputs.files, '^.github/') - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # ratchet:actions/checkout@v7.0.0 - uses: ./.github/actions/job-preamble @@ -267,7 +267,7 @@ jobs: if: >- !needs.detect-changes.outputs.files || contains(needs.detect-changes.outputs.files, '^.github/') - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # ratchet:actions/checkout@v7.0.0 - uses: ./.github/actions/job-preamble @@ -281,7 +281,7 @@ jobs: if: >- !needs.detect-changes.outputs.files || contains(needs.detect-changes.outputs.files, '^.openshift-ci/') - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - name: Checkout uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # ratchet:actions/checkout@v7.0.0 @@ -313,7 +313,7 @@ jobs: contains(github.event.pull_request.labels.*.name, 'ci-test-github-action-slack-messages') ) name: Post failure message to Slack - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 needs: - check-generated-files - misc-checks diff --git a/.github/workflows/tag-more-reviewers.yaml b/.github/workflows/tag-more-reviewers.yaml index f6ce74aac7c50..4b94157c38a6d 100644 --- a/.github/workflows/tag-more-reviewers.yaml +++ b/.github/workflows/tag-more-reviewers.yaml @@ -18,7 +18,7 @@ jobs: github.event.pull_request.user.login != 'red-hat-konflux[bot]' env: GH_TOKEN: ${{ secrets.RHACS_BOT_GITHUB_TOKEN }} - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - name: Tag Konflux Maintainers for review run: | diff --git a/.github/workflows/tagged-ci.yaml b/.github/workflows/tagged-ci.yaml index 028412d071aa8..e936f4f4ea0ca 100644 --- a/.github/workflows/tagged-ci.yaml +++ b/.github/workflows/tagged-ci.yaml @@ -8,7 +8,7 @@ on: jobs: slack-tagged-notice: name: Slack Tagged Notice - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - name: Checkout uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # ratchet:actions/checkout@v7.0.0 diff --git a/.github/workflows/triage-report_periodic.yml b/.github/workflows/triage-report_periodic.yml index 4843660c8ab0b..2df7169befde0 100644 --- a/.github/workflows/triage-report_periodic.yml +++ b/.github/workflows/triage-report_periodic.yml @@ -6,7 +6,7 @@ on: jobs: triage-report: if: ${{ github.repository_owner == 'stackrox' }} - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - name: Checkout uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # ratchet:actions/checkout@v7.0.0 diff --git a/.github/workflows/unit-tests.yaml b/.github/workflows/unit-tests.yaml index d5ca5b28812be..48ea27c9d0873 100644 --- a/.github/workflows/unit-tests.yaml +++ b/.github/workflows/unit-tests.yaml @@ -41,7 +41,7 @@ jobs: gotags: [ 'GOTAGS=""', 'GOTAGS=release' ] exclude: - gotags: ${{ github.event_name == 'pull_request' && !contains(github.event.pull_request.labels.*.name, 'ci-release-build') && 'GOTAGS=release' }} - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 outputs: new-jiras: ${{ steps.junit2jira.outputs.new-jiras }} steps: @@ -61,7 +61,7 @@ jobs: - name: Go Unit Tests run: ${{ matrix.gotags }} make go-unit-tests - - uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # ratchet:codecov/codecov-action@v7.0.0 + - uses: codecov/codecov-action@303a32d7a59b442fa8d48b6a1cc6825c09c847a5 # ratchet:codecov/codecov-action@v7.1.1 with: token: ${{ secrets.CODECOV_TOKEN }} flags: go-unit-tests @@ -112,11 +112,10 @@ jobs: fail-fast: false matrix: gotags: [ 'GOTAGS=""', 'GOTAGS=release' ] - pg: [ 'default', '15' ] + pg: [ '16' ] exclude: - gotags: ${{ github.event_name == 'pull_request' && !contains(github.event.pull_request.labels.*.name, 'ci-release-build') && 'GOTAGS=release' }} - - pg: ${{ github.event_name == 'pull_request' && !contains(github.event.pull_request.labels.*.name, 'ci-postgres-tests') && 'default' }} - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 outputs: new-jiras: ${{ steps.junit2jira.outputs.new-jiras }} env: @@ -173,7 +172,7 @@ jobs: - name: Go Unit Tests run: ${{ matrix.gotags }} make go-postgres-unit-tests - - uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # ratchet:codecov/codecov-action@v7.0.0 + - uses: codecov/codecov-action@303a32d7a59b442fa8d48b6a1cc6825c09c847a5 # ratchet:codecov/codecov-action@v7.1.1 with: token: ${{ secrets.CODECOV_TOKEN }} flags: go-unit-tests @@ -205,7 +204,7 @@ jobs: go-bench: needs: detect-changes if: ${{ !needs.detect-changes.outputs.ui_only }} - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - name: Checkout uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # ratchet:actions/checkout@v7.0.0 @@ -264,7 +263,7 @@ jobs: !needs.detect-changes.outputs.files || contains(needs.detect-changes.outputs.files, '^ui/') || contains(needs.detect-changes.outputs.files, '^.github/actions/cache-ui-dependencies/') - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 outputs: new-jiras: ${{ steps.junit2jira.outputs.new-jiras }} steps: @@ -318,7 +317,7 @@ jobs: !needs.detect-changes.outputs.files || contains(needs.detect-changes.outputs.files, '^ui/') || contains(needs.detect-changes.outputs.files, '^.github/actions/cache-ui-dependencies/') - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 outputs: new-jiras: ${{ steps.junit2jira.outputs.new-jiras }} steps: @@ -380,7 +379,7 @@ jobs: local-roxctl-tests: needs: detect-changes if: ${{ !needs.detect-changes.outputs.ui_only }} - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 outputs: new-jiras: ${{ steps.junit2jira.outputs.new-jiras }} container: @@ -426,7 +425,7 @@ jobs: shell-unit-tests: needs: detect-changes if: ${{ !needs.detect-changes.outputs.ui_only }} - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 outputs: new-jiras: ${{ steps.junit2jira.outputs.new-jiras }} container: @@ -464,7 +463,7 @@ jobs: openshift-ci-unit-tests: needs: detect-changes if: ${{ !needs.detect-changes.outputs.ui_only }} - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 container: image: quay.io/stackrox-io/apollo-ci:stackrox-test-0.5.11@sha256:df3459cf038e73775314b3a5d36d14dda41e6df438a0273c9efa8d23e0f2358a # ratchet:quay.io/stackrox-io/apollo-ci:stackrox-test-0.5.11 steps: @@ -486,7 +485,7 @@ jobs: if: ${{ !needs.detect-changes.outputs.ui_only }} env: KUBECONFIG: "/tmp/kubeconfig" - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 outputs: new-jiras: ${{ steps.junit2jira.outputs.new-jiras }} steps: @@ -508,7 +507,7 @@ jobs: password: ${{ secrets.QUAY_RHACS_ENG_RO_PASSWORD }} - name: Create k8s Kind Cluster - uses: helm/kind-action@ef37e7f390d99f746eb8b610417061a60e82a6cc # ratchet:helm/kind-action@v1 + uses: helm/kind-action@06c1ae10762d3b9c1644e7fe69596ae519e015a2 # ratchet:helm/kind-action@v1.15.0 with: kubeconfig: "${{ env.KUBECONFIG }}" @@ -565,7 +564,7 @@ jobs: contains(github.event.pull_request.labels.*.name, 'ci-test-github-action-slack-messages') ) name: Post failure message to Slack - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 needs: - go - go-bench diff --git a/.github/workflows/update_collector_periodic.yaml b/.github/workflows/update_collector_periodic.yaml index aca8fd0cf0f3e..6eec999095b3d 100644 --- a/.github/workflows/update_collector_periodic.yaml +++ b/.github/workflows/update_collector_periodic.yaml @@ -6,7 +6,7 @@ on: jobs: update-collector: if: ${{ github.repository_owner == 'stackrox' }} - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # ratchet:actions/checkout@v7.0.0 with: diff --git a/.github/workflows/update_fact_periodic.yaml b/.github/workflows/update_fact_periodic.yaml index 12ec44dd22b65..1ba1644308290 100644 --- a/.github/workflows/update_fact_periodic.yaml +++ b/.github/workflows/update_fact_periodic.yaml @@ -6,7 +6,7 @@ on: jobs: update-fact: if: ${{ github.repository_owner == 'stackrox' }} - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # ratchet:actions/checkout@v7.0.0 with: diff --git a/.github/workflows/upgrade-test.yml b/.github/workflows/upgrade-test.yml index 78ddcbeb0c98d..71e46fe283973 100644 --- a/.github/workflows/upgrade-test.yml +++ b/.github/workflows/upgrade-test.yml @@ -40,7 +40,7 @@ concurrency: Release automation ${{ inputs.milestone }} jobs: properties: name: Read repository properties - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 outputs: slack-channel: ${{ fromJSON(format('["{0}","{1}"]', steps.properties.outputs.dry-slack-channel, steps.properties.outputs.slack-channel))[inputs.dry-run != 'true'] }} steps: @@ -52,7 +52,7 @@ jobs: run-parameters: name: Run parameters - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - run: | [ "$DRY_RUN" = "true" ] && echo "::warning::This is a dry run" @@ -66,13 +66,13 @@ jobs: create-clusters: needs: [properties] if: github.event.inputs.dry-run != 'true' - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 strategy: matrix: name: [test1, test2] steps: - name: Create the ${{ matrix.name }} cluster - uses: stackrox/actions/infra/create-cluster@9238e423c3ae1ac4eb0f254cbb98da9daae24d86 # ratchet:stackrox/actions/infra/create-cluster@v1 + uses: stackrox/actions/infra/create-cluster@d624eeae7073b3acb838aab7705730b2de58a4a0 # ratchet:stackrox/actions/infra/create-cluster@v1 with: token: ${{ secrets.INFRA_TOKEN }} flavor: gke-default @@ -83,7 +83,7 @@ jobs: prepare-clusters: needs: [properties, create-clusters] - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - name: Check out code uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # ratchet:actions/checkout@v7.0.0 @@ -136,7 +136,7 @@ jobs: name: Notify about failed cluster creation needs: [properties, prepare-clusters] if: always() && needs.prepare-clusters.result == 'failure' - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - name: Post to Slack uses: slackapi/slack-github-action@45a88b9581bfab2566dc881e2cd66d334e621e2c # ratchet:slackapi/slack-github-action@v3.0.3 diff --git a/.github/workflows/variables.yml b/.github/workflows/variables.yml index f574eec48faa4..6fedd4d7f5546 100644 --- a/.github/workflows/variables.yml +++ b/.github/workflows/variables.yml @@ -56,7 +56,7 @@ on: jobs: parse: name: Parse ${{inputs.version}} - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 outputs: release: ${{steps.parse.outputs.release}} major: ${{steps.parse.outputs.major}} diff --git a/.github/workflows/verify-release.yml b/.github/workflows/verify-release.yml index 5751a5121b968..3792e9c80958b 100644 --- a/.github/workflows/verify-release.yml +++ b/.github/workflows/verify-release.yml @@ -32,7 +32,7 @@ concurrency: Release automation ${{ inputs.version }} jobs: run-parameters: name: Run parameters - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - run: | { @@ -52,7 +52,7 @@ jobs: properties: name: Read repository properties - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 outputs: jira-project: ${{ steps.properties.outputs.jira-project }} steps: @@ -64,7 +64,7 @@ jobs: verify-release: name: Verify all artifacts for release ${{ needs.variables.outputs.named-release-patch }} are published - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 needs: [variables, properties] env: JIRA_USER: ${{ vars.RHACS_BOT_GITHUB_EMAIL }} diff --git a/.openshift-ci/post_tests.py b/.openshift-ci/post_tests.py index 10f8ed4947ace..64f27674b5204 100644 --- a/.openshift-ci/post_tests.py +++ b/.openshift-ci/post_tests.py @@ -34,6 +34,7 @@ class PostTestsConstants: DIAGNOSTIC_OUTPUT = "diagnostic-bundle" CENTRAL_DATA_OUTPUT = "central-data" STACKROX_LOG_DIR = "/tmp/stackrox-logs" + VM_GUEST_LOG_DIR = "/tmp/vm-diagnostics" class NullPostTest: @@ -151,12 +152,13 @@ def _store_osci_artifact(self, source): class PostClusterTest(StoreArtifacts): """The standard cluster test suite of debug gathering and analysis""" - # pylint: disable=too-many-arguments + # pylint: disable=too-many-arguments,too-many-positional-arguments def __init__( self, collect_collector_metrics=True, collect_central_artifacts=True, collect_service_logs=True, + collect_vm_guest_logs=False, check_stackrox_logs=False, artifact_destination_prefix=None, ): @@ -169,6 +171,7 @@ def __init__( self.service_logs_destination = self.K8S_LOG_DIR self._collect_collector_metrics = collect_collector_metrics self._collect_service_logs = collect_service_logs + self._collect_vm_guest_logs = collect_vm_guest_logs self._check_stackrox_logs = check_stackrox_logs self.k8s_namespaces = [ "stackrox", @@ -202,6 +205,8 @@ def run(self, test_outputs=None): self.grab_central_data() if self._collect_service_logs: self.collect_service_logs() + if self._collect_vm_guest_logs: + self.collect_vm_guest_logs() if self._check_stackrox_logs: self.check_stackrox_logs() self.store_artifacts(test_outputs) @@ -232,6 +237,23 @@ def collect_service_logs(self): ) self.data_to_store.append(self.service_logs_destination) + def collect_vm_guest_logs(self): + # Per-VM virtctl ssh is capped in the script. Cleanup is a second + # process because a collect timeout SIGKILLs the first before it can + # delete namespaces. + self.run_with_best_effort( + [ + "scripts/ci/collect-vm-guest-logs.sh", + self.VM_GUEST_LOG_DIR, + ], + timeout=10 * 60, + ) + self.data_to_store.append(self.VM_GUEST_LOG_DIR) + self.run_with_best_effort( + ["scripts/ci/delete-vm-ns.sh"], + timeout=60, + ) + def collect_collector_metrics(self): self.run_with_best_effort( [ diff --git a/.tekton/basic-component-pipeline.yaml b/.tekton/basic-component-pipeline.yaml index 412e2460acc16..642db21c6996b 100644 --- a/.tekton/basic-component-pipeline.yaml +++ b/.tekton/basic-component-pipeline.yaml @@ -35,7 +35,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:a33d6eabbae15539dbaf5996110010cc4515bad7aac01969280496c76ab4b9e5 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:18defc6b58a3792ef4d1c7268ce22167c833e7846523b97e0f3219094df0d510 - name: kind value: task resolver: bundles @@ -211,7 +211,7 @@ spec: - name: name value: determine-image-expiration - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:a33d6eabbae15539dbaf5996110010cc4515bad7aac01969280496c76ab4b9e5 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:18defc6b58a3792ef4d1c7268ce22167c833e7846523b97e0f3219094df0d510 - name: kind value: task resolver: bundles @@ -227,7 +227,7 @@ spec: - name: name value: determine-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:a33d6eabbae15539dbaf5996110010cc4515bad7aac01969280496c76ab4b9e5 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:18defc6b58a3792ef4d1c7268ce22167c833e7846523b97e0f3219094df0d510 - name: kind value: task resolver: bundles diff --git a/.tekton/checks.yaml b/.tekton/checks.yaml index 0a32b36258dc3..5e7ce9e0f1912 100644 --- a/.tekton/checks.yaml +++ b/.tekton/checks.yaml @@ -66,7 +66,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:a33d6eabbae15539dbaf5996110010cc4515bad7aac01969280496c76ab4b9e5 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:18defc6b58a3792ef4d1c7268ce22167c833e7846523b97e0f3219094df0d510 - name: kind value: task resolver: bundles diff --git a/.tekton/create-custom-snapshot.yaml b/.tekton/create-custom-snapshot.yaml index d82ad2a8b5bbb..b96b14d7b28b3 100644 --- a/.tekton/create-custom-snapshot.yaml +++ b/.tekton/create-custom-snapshot.yaml @@ -75,7 +75,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:a33d6eabbae15539dbaf5996110010cc4515bad7aac01969280496c76ab4b9e5 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:18defc6b58a3792ef4d1c7268ce22167c833e7846523b97e0f3219094df0d510 - name: kind value: task resolver: bundles @@ -132,7 +132,7 @@ spec: - name: SHOULD_REALLY_WORK steps: - name: determine - image: registry.redhat.io/ubi9-minimal:latest@sha256:beeada7dd17903dfb69fd5f6916c054720bf28a52daaa2f7a1910a1394244bd2 + image: registry.redhat.io/ubi9-minimal:latest@sha256:c4c285d42941ebb2bd4da878479008e1e4536bb599aad1dbe568f89de9053661 imagePullPolicy: IfNotPresent # The script expresses the same condition as pipelinesascode.tekton.dev/on-cel-expression in other pipelines # but in a more verbose way because, as it turns out, both syntax and fields of CEL expressions are slightly @@ -224,7 +224,7 @@ spec: - name: name value: determine-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:a33d6eabbae15539dbaf5996110010cc4515bad7aac01969280496c76ab4b9e5 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:18defc6b58a3792ef4d1c7268ce22167c833e7846523b97e0f3219094df0d510 - name: kind value: task resolver: bundles @@ -242,7 +242,7 @@ spec: - name: name value: wait-for-image - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:a33d6eabbae15539dbaf5996110010cc4515bad7aac01969280496c76ab4b9e5 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:18defc6b58a3792ef4d1c7268ce22167c833e7846523b97e0f3219094df0d510 - name: kind value: task resolver: bundles @@ -315,7 +315,7 @@ spec: - name: name value: create-snapshot-from-bundle - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:a33d6eabbae15539dbaf5996110010cc4515bad7aac01969280496c76ab4b9e5 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:18defc6b58a3792ef4d1c7268ce22167c833e7846523b97e0f3219094df0d510 - name: kind value: task resolver: bundles diff --git a/.tekton/main-pipeline.yaml b/.tekton/main-pipeline.yaml index 787b6c7506c9e..fb022d858b32c 100644 --- a/.tekton/main-pipeline.yaml +++ b/.tekton/main-pipeline.yaml @@ -35,7 +35,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:a33d6eabbae15539dbaf5996110010cc4515bad7aac01969280496c76ab4b9e5 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:18defc6b58a3792ef4d1c7268ce22167c833e7846523b97e0f3219094df0d510 - name: kind value: task resolver: bundles @@ -212,7 +212,7 @@ spec: - name: name value: determine-image-expiration - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:a33d6eabbae15539dbaf5996110010cc4515bad7aac01969280496c76ab4b9e5 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:18defc6b58a3792ef4d1c7268ce22167c833e7846523b97e0f3219094df0d510 - name: kind value: task resolver: bundles @@ -228,7 +228,7 @@ spec: - name: name value: determine-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:a33d6eabbae15539dbaf5996110010cc4515bad7aac01969280496c76ab4b9e5 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:18defc6b58a3792ef4d1c7268ce22167c833e7846523b97e0f3219094df0d510 - name: kind value: task resolver: bundles @@ -248,7 +248,7 @@ spec: - name: name value: fetch-external-networks - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:a33d6eabbae15539dbaf5996110010cc4515bad7aac01969280496c76ab4b9e5 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:18defc6b58a3792ef4d1c7268ce22167c833e7846523b97e0f3219094df0d510 - name: kind value: task resolver: bundles diff --git a/.tekton/operator-bundle-pipeline.yaml b/.tekton/operator-bundle-pipeline.yaml index 08cf42b987678..f7da2ba4be86b 100644 --- a/.tekton/operator-bundle-pipeline.yaml +++ b/.tekton/operator-bundle-pipeline.yaml @@ -35,7 +35,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:a33d6eabbae15539dbaf5996110010cc4515bad7aac01969280496c76ab4b9e5 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:18defc6b58a3792ef4d1c7268ce22167c833e7846523b97e0f3219094df0d510 - name: kind value: task resolver: bundles @@ -272,7 +272,7 @@ spec: - name: name value: determine-image-expiration - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:a33d6eabbae15539dbaf5996110010cc4515bad7aac01969280496c76ab4b9e5 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:18defc6b58a3792ef4d1c7268ce22167c833e7846523b97e0f3219094df0d510 - name: kind value: task resolver: bundles @@ -288,7 +288,7 @@ spec: - name: name value: determine-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:a33d6eabbae15539dbaf5996110010cc4515bad7aac01969280496c76ab4b9e5 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:18defc6b58a3792ef4d1c7268ce22167c833e7846523b97e0f3219094df0d510 - name: kind value: task resolver: bundles @@ -329,7 +329,7 @@ spec: - name: name value: wait-for-image - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:a33d6eabbae15539dbaf5996110010cc4515bad7aac01969280496c76ab4b9e5 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:18defc6b58a3792ef4d1c7268ce22167c833e7846523b97e0f3219094df0d510 - name: kind value: task resolver: bundles diff --git a/.tekton/operator-index-build.yaml b/.tekton/operator-index-build.yaml index a3c1aa9ceff8f..3d4a96d8d9a0b 100644 --- a/.tekton/operator-index-build.yaml +++ b/.tekton/operator-index-build.yaml @@ -98,7 +98,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:a33d6eabbae15539dbaf5996110010cc4515bad7aac01969280496c76ab4b9e5 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:18defc6b58a3792ef4d1c7268ce22167c833e7846523b97e0f3219094df0d510 - name: kind value: task resolver: bundles @@ -214,7 +214,7 @@ spec: - name: name value: determine-image-expiration - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:a33d6eabbae15539dbaf5996110010cc4515bad7aac01969280496c76ab4b9e5 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:18defc6b58a3792ef4d1c7268ce22167c833e7846523b97e0f3219094df0d510 - name: kind value: task resolver: bundles @@ -230,7 +230,7 @@ spec: - name: name value: determine-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:a33d6eabbae15539dbaf5996110010cc4515bad7aac01969280496c76ab4b9e5 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:18defc6b58a3792ef4d1c7268ce22167c833e7846523b97e0f3219094df0d510 - name: kind value: task resolver: bundles @@ -249,7 +249,7 @@ spec: description: Full operator-index image tag with v prefix and -fast suffix to avoid collision with GHA builds steps: - name: compute-index-tag - image: registry.access.redhat.com/ubi9-minimal:latest@sha256:beeada7dd17903dfb69fd5f6916c054720bf28a52daaa2f7a1910a1394244bd2 + image: registry.access.redhat.com/ubi9-minimal:latest@sha256:c4c285d42941ebb2bd4da878479008e1e4536bb599aad1dbe568f89de9053661 imagePullPolicy: IfNotPresent script: | #!/bin/bash @@ -276,7 +276,7 @@ spec: - name: name value: wait-for-image - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:a33d6eabbae15539dbaf5996110010cc4515bad7aac01969280496c76ab4b9e5 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:18defc6b58a3792ef4d1c7268ce22167c833e7846523b97e0f3219094df0d510 - name: kind value: task resolver: bundles @@ -334,7 +334,7 @@ spec: - use - $(params.SOURCE_ARTIFACT)=/var/workdir/source - name: build-index-content - image: registry.access.redhat.com/ubi9/go-toolset:9.8-1790174511@sha256:0a4666f7a4eb0644c97a73cba198eb268691b270d97831822689e7a2088f87be + image: registry.access.redhat.com/ubi9/go-toolset:9.8-1792010711@sha256:60fd6fb2998c40ad0d6561c34e83588b986a0cf7771f64e5e5c295ba87101f9b imagePullPolicy: IfNotPresent workingDir: /var/workdir/source/operator securityContext: diff --git a/.tekton/retag-pipeline.yaml b/.tekton/retag-pipeline.yaml index 63b3467b416a7..023b634a93706 100644 --- a/.tekton/retag-pipeline.yaml +++ b/.tekton/retag-pipeline.yaml @@ -35,7 +35,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:a33d6eabbae15539dbaf5996110010cc4515bad7aac01969280496c76ab4b9e5 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:18defc6b58a3792ef4d1c7268ce22167c833e7846523b97e0f3219094df0d510 - name: kind value: task resolver: bundles @@ -136,7 +136,7 @@ spec: - name: name value: determine-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:a33d6eabbae15539dbaf5996110010cc4515bad7aac01969280496c76ab4b9e5 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:18defc6b58a3792ef4d1c7268ce22167c833e7846523b97e0f3219094df0d510 - name: kind value: task resolver: bundles @@ -154,7 +154,7 @@ spec: - name: name value: determine-dependency-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:a33d6eabbae15539dbaf5996110010cc4515bad7aac01969280496c76ab4b9e5 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:18defc6b58a3792ef4d1c7268ce22167c833e7846523b97e0f3219094df0d510 - name: kind value: task resolver: bundles @@ -170,7 +170,7 @@ spec: - name: name value: wait-for-image - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:a33d6eabbae15539dbaf5996110010cc4515bad7aac01969280496c76ab4b9e5 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:18defc6b58a3792ef4d1c7268ce22167c833e7846523b97e0f3219094df0d510 - name: kind value: task resolver: bundles @@ -195,7 +195,7 @@ spec: - name: name value: retag-image - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:a33d6eabbae15539dbaf5996110010cc4515bad7aac01969280496c76ab4b9e5 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:18defc6b58a3792ef4d1c7268ce22167c833e7846523b97e0f3219094df0d510 - name: kind value: task resolver: bundles diff --git a/.tekton/scanner-v4-pipeline.yaml b/.tekton/scanner-v4-pipeline.yaml index bc8c0f64addec..05368bff8ca56 100644 --- a/.tekton/scanner-v4-pipeline.yaml +++ b/.tekton/scanner-v4-pipeline.yaml @@ -35,7 +35,7 @@ spec: - name: name value: post-bigquery-metrics - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:a33d6eabbae15539dbaf5996110010cc4515bad7aac01969280496c76ab4b9e5 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:18defc6b58a3792ef4d1c7268ce22167c833e7846523b97e0f3219094df0d510 - name: kind value: task resolver: bundles @@ -211,7 +211,7 @@ spec: - name: name value: determine-image-expiration - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:a33d6eabbae15539dbaf5996110010cc4515bad7aac01969280496c76ab4b9e5 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:18defc6b58a3792ef4d1c7268ce22167c833e7846523b97e0f3219094df0d510 - name: kind value: task resolver: bundles @@ -227,7 +227,7 @@ spec: - name: name value: determine-image-tag - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:a33d6eabbae15539dbaf5996110010cc4515bad7aac01969280496c76ab4b9e5 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:18defc6b58a3792ef4d1c7268ce22167c833e7846523b97e0f3219094df0d510 - name: kind value: task resolver: bundles @@ -247,7 +247,7 @@ spec: - name: name value: fetch-scanner-v4-vuln-mappings - name: bundle - value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:a33d6eabbae15539dbaf5996110010cc4515bad7aac01969280496c76ab4b9e5 + value: quay.io/rhacs-eng/konflux-tasks:latest@sha256:18defc6b58a3792ef4d1c7268ce22167c833e7846523b97e0f3219094df0d510 - name: kind value: task resolver: bundles diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d2a12a8afb7e..ec4b4f4196c90 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,11 @@ Changes should still be described appropriately in JIRA/doc input pages, for inc - ROX-36534: roxctl binaries in the Central image are now stored as `.tar.gz` archives. Central extracts and streams the binary on download, so the user-facing download behavior is unchanged. - ROX-35137: Central now prunes deployments whose cluster no longer exists in the database. - ROX-37048: Central writes its version in `Rh-Central-Version` gRPC response header to authenticated clients. `roxctl` compares this version with its own version and reports a warning to stderr if the version skew is outside of a supported range. +- ROX-37315: Image exclusions no longer turn off deploy-time checks. Previously, an image exclusion on a policy with Build and Deploy stages stopped that policy from raising deploy-time violations or blocking any deployment. Image exclusions now apply only at Build, so after upgrading, affected policies raise deploy-time violations again and block deployments if enforcement is on. To skip apps at deploy time, use deployment exclusions. +- ROX-37265: Feature flag `ROX_SCANNER_V4_RED_HAT_LAYERS_RED_HAT_VULNS_ONLY` will now correctly filter non Red Hat vulnerabilities from RH image layers in newer Konflux built images where it previously had no affect. The flag must now be set on Central INSTEAD of Scanner V4 Matcher. +- ROX-37385: Scanner V4 node scans now record the operating system (for example `rhcos:9.6`) for OpenShift 4.19 and later nodes. RHCOS switched to RHEL-based dotted versions such as `9.6.20250701-0` in OpenShift 4.19, and Central previously stored an empty operating system for these nodes. + +- ROX-37149: `roxctl` now honors the `HTTP_PROXY`/`HTTPS_PROXY`/`NO_PROXY` environment variables when connecting to Central over HTTP, so it works where direct egress to Central is blocked but a proxy is available. ## [5.0.0] @@ -42,6 +47,7 @@ Changes should still be described appropriately in JIRA/doc input pages, for inc - ROX-34488: Added support for cosign signature discovery via OCI 1.1 referrers, including DSSE envelope verification for sigstore bundle-format signatures. - ROX-36858: Added more supported labels to the image and node vulnerability central custom metrics. +- Pruning (garbage collection) and vulnerability report scheduling now run in a separate `central-worker` deployment by default instead of inside Central. Disable with the Helm value `centralWorker.enabled=false` or `spec.centralWorker.enabled: false` in the Central CR. ### Removed Features @@ -67,6 +73,7 @@ Changes should still be described appropriately in JIRA/doc input pages, for inc - ROX-34535: Fixes an issue where if ScannerV2 is disabled or unavailable on initial startup the central deployment leaks GRPC connections until the scanner becomes available. - ROX-36509: Improved Central memory efficiency by optimizing process filter data structures in high-cardinality scenarios. The `ROX_PROCESS_FILTER_FAN_OUT_LEVELS` environment variable now accepts values up to 255; higher values are automatically clamped with a warning. +- ROX-36432: Fixed node scan results (`scan.scanTime`) silently freezing on installs whose default Scanner V4 integration lacked the `NODE_SCANNER` category. Central now restores the default categories on startup. Affected nodes move from Scanner V2 results to Scanner V4 results, so their vulnerability counts may change. ## [4.11.0] diff --git a/COLLECTOR_VERSION b/COLLECTOR_VERSION index aa4e1ea278487..12a33ce182018 100644 --- a/COLLECTOR_VERSION +++ b/COLLECTOR_VERSION @@ -1 +1 @@ -3.25.0-161-g8542ffdf98 +3.25.0-188-g237a0275dc diff --git a/Makefile b/Makefile index 7ab67e2cd9c67..54fca4db3901d 100644 --- a/Makefile +++ b/Makefile @@ -138,7 +138,7 @@ $(call go-tool, MOCKGEN_BIN, go.uber.org/mock/mockgen) $(call go-tool, GO_JUNIT_REPORT_BIN, github.com/jstemmer/go-junit-report/v2, tools/test) $(call go-tool, PROTOLOCK_BIN, github.com/nilslice/protolock/cmd/protolock, tools/linters) $(call go-tool, RATCHET_BIN, github.com/sethvargo/ratchet, tools/linters) -$(call go-tool, ACTIONLINT_BIN, github.com/rhysd/actionlint/cmd/actionlint, tools/linters) +$(call go-tool, ACTIONLINT_BIN, actionlint.kjanat.dev/cmd/actionlint, tools/linters) $(call go-tool, GOVULNCHECK_BIN, golang.org/x/vuln/cmd/govulncheck, tools/linters) $(call go-tool, IMAGE_PREFETCHER_DEPLOY_BIN, github.com/stackrox/image-prefetcher/deploy, tools/test) $(call go-tool, PROMETHEUS_METRIC_PARSER_BIN, github.com/stackrox/prometheus-metric-parser, tools/test) @@ -624,6 +624,7 @@ shell-unit-tests: @echo "+ $@" $(SILENT)mkdir -p shell-test-output bats --print-output-on-failure --verbose-run --recursive --report-formatter junit --output shell-test-output \ + scanner/image/db/bats \ scripts \ tests/e2e/bats diff --git a/ROXIE_VERSION b/ROXIE_VERSION index 5f749c13663ae..1f77169993b92 100644 --- a/ROXIE_VERSION +++ b/ROXIE_VERSION @@ -1 +1 @@ -0.4.11 +0.4.13 diff --git a/central/auth/openshift/metrics_auth.go b/central/auth/openshift/metrics_auth.go new file mode 100644 index 0000000000000..98dc0928c3830 --- /dev/null +++ b/central/auth/openshift/metrics_auth.go @@ -0,0 +1,237 @@ +package openshift + +import ( + "context" + + groupDataStore "github.com/stackrox/rox/central/group/datastore" + rolePkg "github.com/stackrox/rox/central/role" + roleDataStore "github.com/stackrox/rox/central/role/datastore" + "github.com/stackrox/rox/central/tlsconfig" + "github.com/stackrox/rox/generated/storage" + "github.com/stackrox/rox/pkg/auth/authproviders" + "github.com/stackrox/rox/pkg/auth/authproviders/userpki" + permissionsUtils "github.com/stackrox/rox/pkg/auth/permissions/utils" + "github.com/stackrox/rox/pkg/declarativeconfig" + "github.com/stackrox/rox/pkg/env" + "github.com/stackrox/rox/pkg/k8scfgwatch" + "github.com/stackrox/rox/pkg/k8sutil" + "github.com/stackrox/rox/pkg/logging" + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" +) + +const ( + authProviderID = "b3070020-ecc3-4f34-a3f6-ad28ffc9b80f" + permissionSetID = "b3070020-ecc3-4f34-a3f6-ad28ffc9b80e" + + // Auth provider is hidden from the login screen but visible in Access Control. + // It trusts the Kubernetes client CA from extension-apiserver-authentication, + // which signs Prometheus client certs. This adds the CA to userTrustRoots on the + // public :8443 endpoint (optional client cert verification). + authProviderName = "OpenShift Platform Client Certificates" + roleName = "OpenShift Prometheus Metrics Reader" + permissionSetName = "OpenShift Prometheus Metrics Reader" +) + +var log = logging.LoggerForModule() + +// SeedMetricsAuthProvider ensures that the userpki auth provider, permission set, role, +// and group mapping required for OpenShift Prometheus to scrape /metrics exist. +// +// The auth provider uses the Kubernetes client CA from the +// extension-apiserver-authentication ConfigMap (same CA that signs Prometheus client +// certs). A ConfigMap watcher keeps the CA current across rotations. +// +// The permission set and role use IMPERATIVE origin. The auth provider and group +// use DEFAULT origin so they cannot be modified through the user-facing APIs. +func SeedMetricsAuthProvider(ctx context.Context, registry authproviders.Registry, roleDS roleDataStore.DataStore, groupDS groupDataStore.DataStore) { + if !tlsconfig.OpenShiftTLSConfigured() { + return + } + + clientset, err := newK8sClient() + if err != nil { + log.Warnf("Cannot create Kubernetes client for OpenShift metrics auth: %v", err) + return + } + + onCA := func(caPEM string) { + ensurePermissionSet(ctx, roleDS) + ensureRole(ctx, roleDS) + ensureAuthProvider(ctx, registry, caPEM) + ensureGroup(ctx, groupDS) + } + + if caPEM := readClientCA(ctx, clientset); caPEM != "" { + onCA(caPEM) + } + + watchClientCA(ctx, clientset, onCA) +} + +func newK8sClient() (kubernetes.Interface, error) { + config, err := k8sutil.GetK8sInClusterConfig() + if err != nil { + return nil, err + } + return kubernetes.NewForConfig(config) +} + +func readClientCA(ctx context.Context, clientset kubernetes.Interface) string { + cm, err := clientset.CoreV1().ConfigMaps( + env.SecureMetricsClientCANamespace.Setting(), + ).Get(ctx, env.SecureMetricsClientCAConfigMap.Setting(), metav1.GetOptions{}) + if err != nil { + log.Warnf("Cannot read client CA ConfigMap %s/%s: %v", + env.SecureMetricsClientCANamespace.Setting(), + env.SecureMetricsClientCAConfigMap.Setting(), err) + return "" + } + pem, ok := cm.Data[env.SecureMetricsClientCAKey.Setting()] + if !ok || pem == "" { + log.Warnf("Client CA key %q not found in ConfigMap %s/%s", + env.SecureMetricsClientCAKey.Setting(), + env.SecureMetricsClientCANamespace.Setting(), + env.SecureMetricsClientCAConfigMap.Setting()) + return "" + } + return pem +} + +func watchClientCA(ctx context.Context, clientset kubernetes.Interface, onCA func(string)) { + watcher := k8scfgwatch.NewConfigMapWatcher(clientset, func(cm *v1.ConfigMap) { + if cm == nil { + return + } + pem, ok := cm.Data[env.SecureMetricsClientCAKey.Setting()] + if !ok || pem == "" { + return + } + onCA(pem) + }) + watcher.Watch( + ctx, + env.SecureMetricsClientCANamespace.Setting(), + env.SecureMetricsClientCAConfigMap.Setting(), + ) +} + +func refreshCA(ctx context.Context, registry authproviders.Registry, provider authproviders.Provider, caPEM string) { + config := provider.StorageView().GetConfig() + if config[userpki.ConfigKeys] == caPEM { + return + } + log.Info("Client CA changed, updating OpenShift metrics auth provider") + config[userpki.ConfigKeys] = caPEM + + factory := provider.BackendFactory() + if factory == nil { + log.Warn("Auth provider has no backend factory, cannot apply the new client CA") + return + } + // WithConfig on its own only rewrites the stored config. The provider keeps its + // existing backend, which parsed the certificates once at construction time, and the + // registry then re-registers those same stale certificates with the client CA + // manager. Central would keep advertising the superseded CA until restart, so the + // backend has to be rebuilt from the new config as part of the same update. + if _, err := registry.UpdateProvider(declarativeconfig.WithModifyDefaultResource(ctx), authProviderID, + authproviders.WithConfig(config), + authproviders.WithBackendFromFactory(ctx, factory), + ); err != nil { + log.Warnf("Failed to update client CA in auth provider: %v", err) + } +} + +func ensurePermissionSet(ctx context.Context, roleDS roleDataStore.DataStore) { + if _, exists, err := roleDS.GetPermissionSet(ctx, permissionSetID); err != nil { + log.Warnf("Failed to check OpenShift metrics permission set: %v", err) + return + } else if exists { + return + } + + ps := &storage.PermissionSet{ + Id: permissionSetID, + Name: permissionSetName, + Description: "For OpenShift Prometheus: read access to Administration for /metrics scraping", + // Let's give no permissions by default to avoid unexpected sensitive data exposure. + ResourceToAccess: permissionsUtils.FromResourcesWithAccess(), + } + if err := roleDS.AddPermissionSet(ctx, ps); err != nil { + log.Warnf("Failed to create OpenShift metrics permission set: %v", err) + } +} + +func ensureRole(ctx context.Context, roleDS roleDataStore.DataStore) { + if _, exists, err := roleDS.GetRole(ctx, roleName); err != nil { + log.Warnf("Failed to check OpenShift metrics role: %v", err) + return + } else if exists { + return + } + + role := &storage.Role{ + Name: roleName, + Description: "Maps OpenShift Prometheus service account to /metrics read access", + PermissionSetId: permissionSetID, + AccessScopeId: rolePkg.AccessScopeIncludeAll.GetId(), + } + if err := roleDS.AddRole(ctx, role); err != nil { + log.Warnf("Failed to create OpenShift metrics role: %v", err) + } +} + +func ensureAuthProvider(ctx context.Context, registry authproviders.Registry, caPEM string) { + if existing := registry.GetProvider(authProviderID); existing != nil { + refreshCA(ctx, registry, existing, caPEM) + return + } + + _, err := registry.CreateProvider(declarativeconfig.WithModifyDefaultResource(ctx), + authproviders.WithType(userpki.TypeName), + authproviders.WithID(authProviderID), + authproviders.WithName(authProviderName), + authproviders.WithEnabled(true), + authproviders.WithActive(true), + authproviders.WithVisibility(storage.Traits_HIDDEN), + authproviders.WithOrigin(storage.Traits_DEFAULT), + authproviders.WithConfig(map[string]string{ + userpki.ConfigKeys: caPEM, + }), + ) + if err != nil { + log.Warnf("Failed to create OpenShift metrics auth provider: %v", err) + } +} + +func ensureGroup(ctx context.Context, groupDS groupDataStore.DataStore) { + cn := env.SecureMetricsClientCertCN.Setting() + existing, err := groupDS.GetFiltered(ctx, func(g *storage.Group) bool { + p := g.GetProps() + return p.GetAuthProviderId() == authProviderID && p.GetKey() == "name" && p.GetValue() == cn + }) + if err != nil { + log.Warnf("Failed to check OpenShift metrics group: %v", err) + return + } + if len(existing) > 0 { + return + } + group := &storage.Group{ + Props: &storage.GroupProperties{ + AuthProviderId: authProviderID, + Key: "name", + Value: cn, + Traits: &storage.Traits{ + Origin: storage.Traits_DEFAULT, + }, + }, + RoleName: roleName, + } + // Group uses DEFAULT origin so a user can't repoint or delete the mapping and thereby + // orphan (and delete) the role out from under it. + if err := groupDS.Add(declarativeconfig.WithModifyDefaultResource(ctx), group); err != nil { + log.Warnf("Failed to create OpenShift metrics group: %v", err) + } +} diff --git a/central/auth/openshift/metrics_auth_test.go b/central/auth/openshift/metrics_auth_test.go new file mode 100644 index 0000000000000..9355236e8d6d4 --- /dev/null +++ b/central/auth/openshift/metrics_auth_test.go @@ -0,0 +1,264 @@ +package openshift + +import ( + "context" + "crypto/x509" + "errors" + "testing" + + groupMocks "github.com/stackrox/rox/central/group/datastore/mocks" + roleMocks "github.com/stackrox/rox/central/role/datastore/mocks" + "github.com/stackrox/rox/generated/storage" + "github.com/stackrox/rox/pkg/auth/authproviders" + providerMocks "github.com/stackrox/rox/pkg/auth/authproviders/mocks" + "github.com/stackrox/rox/pkg/auth/authproviders/userpki" + "github.com/stackrox/rox/pkg/auth/permissions" + "github.com/stackrox/rox/pkg/auth/tokens" + "github.com/stackrox/rox/pkg/certgen" + "github.com/stackrox/rox/pkg/declarativeconfig" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/mock/gomock" +) + +const testCAPEM = "-----BEGIN CERTIFICATE-----\ntest\n-----END CERTIFICATE-----\n" + +func setupMocks(t *testing.T) (*providerMocks.MockRegistry, *roleMocks.MockDataStore, *groupMocks.MockDataStore) { + ctrl := gomock.NewController(t) + return providerMocks.NewMockRegistry(ctrl), + roleMocks.NewMockDataStore(ctrl), + groupMocks.NewMockDataStore(ctrl) +} + +func TestSeed_FirstBoot_CreatesAllObjects(t *testing.T) { + registry, roleDS, groupDS := setupMocks(t) + ctx := context.Background() + + roleDS.EXPECT().GetPermissionSet(gomock.Any(), permissionSetID).Return(nil, false, nil) + roleDS.EXPECT().AddPermissionSet(gomock.Any(), gomock.Any()).Return(nil) + roleDS.EXPECT().GetRole(gomock.Any(), roleName).Return(nil, false, nil) + roleDS.EXPECT().AddRole(gomock.Any(), gomock.Any()).Return(nil) + + providerCreated := false + registry.EXPECT().GetProvider(authProviderID).Return(nil) + registry.EXPECT().CreateProvider(gomock.Any(), gomock.Any()).DoAndReturn( + func(_ context.Context, _ ...interface{}) (interface{}, error) { + providerCreated = true + return nil, nil + }) + groupDS.EXPECT().GetFiltered(gomock.Any(), gomock.Any()).Return(nil, nil) + groupDS.EXPECT().Add(gomock.Any(), gomock.Any()).DoAndReturn( + func(_ context.Context, _ *storage.Group) error { + if !providerCreated { + t.Error("group Add called before provider creation") + } + return nil + }) + + ensurePermissionSet(ctx, roleDS) + ensureRole(ctx, roleDS) + ensureAuthProvider(ctx, registry, testCAPEM) + ensureGroup(ctx, groupDS) +} + +func TestSeed_SubsequentBoot_CAUnchanged_NoUpdate(t *testing.T) { + registry, _, _ := setupMocks(t) + ctx := context.Background() + + existing := providerMocks.NewMockProvider(gomock.NewController(t)) + existing.EXPECT().StorageView().Return(&storage.AuthProvider{ + Config: map[string]string{userpki.ConfigKeys: testCAPEM}, + }) + registry.EXPECT().GetProvider(authProviderID).Return(existing) + + ensureAuthProvider(ctx, registry, testCAPEM) +} + +func TestSeed_PartialRecovery_PermissionSetExists_RoleMissing(t *testing.T) { + registry, roleDS, groupDS := setupMocks(t) + ctx := context.Background() + + roleDS.EXPECT().GetPermissionSet(gomock.Any(), permissionSetID).Return(&storage.PermissionSet{}, true, nil) + roleDS.EXPECT().GetRole(gomock.Any(), roleName).Return(nil, false, nil) + roleDS.EXPECT().AddRole(gomock.Any(), gomock.Any()).Return(nil) + registry.EXPECT().GetProvider(authProviderID).Return(nil) + registry.EXPECT().CreateProvider(gomock.Any(), gomock.Any()).Return(nil, nil) + groupDS.EXPECT().GetFiltered(gomock.Any(), gomock.Any()).Return(nil, nil) + groupDS.EXPECT().Add(gomock.Any(), gomock.Any()).Return(nil) + + ensurePermissionSet(ctx, roleDS) + ensureRole(ctx, roleDS) + ensureAuthProvider(ctx, registry, testCAPEM) + ensureGroup(ctx, groupDS) +} + +func TestSeed_Group_IsDefaultOriginAndImmutable(t *testing.T) { + _, _, groupDS := setupMocks(t) + ctx := context.Background() + + groupDS.EXPECT().GetFiltered(gomock.Any(), gomock.Any()).Return(nil, nil) + groupDS.EXPECT().Add(gomock.Any(), gomock.Any()).DoAndReturn( + func(addCtx context.Context, group *storage.Group) error { + if group.GetProps().GetTraits().GetOrigin() != storage.Traits_DEFAULT { + t.Errorf("expected group to use DEFAULT origin, got %v", group.GetProps().GetTraits().GetOrigin()) + } + assert.False(t, declarativeconfig.CanModifyResource(ctx, group.GetProps())) + if !declarativeconfig.CanModifyResource(addCtx, group.GetProps()) { + t.Error("expected ensureGroup to pass a context that can modify DEFAULT-origin resources") + } + return nil + }) + + ensureGroup(ctx, groupDS) +} + +func TestSeed_SubsequentBoot_GroupExists_NotOverwritten(t *testing.T) { + _, _, groupDS := setupMocks(t) + ctx := context.Background() + + groupDS.EXPECT().GetFiltered(gomock.Any(), gomock.Any()).Return([]*storage.Group{{ + Props: &storage.GroupProperties{AuthProviderId: authProviderID, Key: "name", Value: "system:serviceaccount:openshift-monitoring:prometheus-k8s"}, + RoleName: "User Modified Role", + }}, nil) + + ensureGroup(ctx, groupDS) +} + +// TestSeed_CARotation_RegistersNewCA runs against a real auth provider registry because +// the interesting part of a rotation happens past the registry: a mocked registry only +// shows that UpdateProvider was called, not whether the new CA reached the client CA +// manager. Persisting the bundle without also rebuilding the provider backend leaves +// Central advertising the superseded CA until restart, breaking Prometheus scrapes +// roughly monthly when OpenShift rotates its CSR signer. +func TestSeed_CARotation_RegistersNewCA(t *testing.T) { + ctx := context.Background() + caManager := &recordingCAManager{} + store := &inMemoryAuthProviderStore{providers: map[string]*storage.AuthProvider{}} + + registry := authproviders.NewStoreBackedRegistry("/sso/", "/auth/response/generic", + store, noopIssuerFactory{}, noopRoleMapperFactory{}, nil) + require.NoError(t, registry.RegisterBackendFactory(ctx, userpki.TypeName, + userpki.NewFactoryFactory(caManager))) + require.NoError(t, registry.Init()) + + oldCAPEM, oldCA := generateCA(t) + newCAPEM, newCA := generateCA(t) + + ensureAuthProvider(ctx, registry, oldCAPEM) + require.Equal(t, []string{oldCA.Subject.String()}, registeredSubjects(caManager.certs)) + + ensureAuthProvider(ctx, registry, newCAPEM) + + provider := registry.GetProvider(authProviderID) + require.NotNil(t, provider) + assert.Equal(t, storage.Traits_DEFAULT, provider.StorageView().GetTraits().GetOrigin()) + assert.Equal(t, newCAPEM, provider.StorageView().GetConfig()[userpki.ConfigKeys], + "rotated CA should be persisted in the auth provider config") + assert.Equal(t, []string{newCA.Subject.String()}, registeredSubjects(caManager.certs), + "rotated CA should also be registered with the client CA manager") +} + +func generateCA(t *testing.T) (string, *x509.Certificate) { + ca, err := certgen.GenerateCA() + require.NoError(t, err) + return string(ca.CertPEM()), ca.Certificate() +} + +func registeredSubjects(certs []*x509.Certificate) []string { + subjects := make([]string, 0, len(certs)) + for _, cert := range certs { + subjects = append(subjects, cert.Subject.String()) + } + return subjects +} + +// recordingCAManager stands in for central/tlsconfig.Manager, which is what Central wires +// into the userpki backend factory. It records the certificates the backend hands over, +// i.e. what ends up in the client CA pool advertised during the TLS handshake. +type recordingCAManager struct { + certs []*x509.Certificate +} + +func (c *recordingCAManager) RegisterAuthProvider(_ authproviders.Provider, certs []*x509.Certificate) { + c.certs = certs +} + +func (c *recordingCAManager) UnregisterAuthProvider(authproviders.Provider) { + c.certs = nil +} + +func (c *recordingCAManager) GetProviderForFingerprint(string) authproviders.Provider { + return nil +} + +type inMemoryAuthProviderStore struct { + providers map[string]*storage.AuthProvider +} + +func (s *inMemoryAuthProviderStore) GetAuthProvider(_ context.Context, id string) (*storage.AuthProvider, bool, error) { + provider, found := s.providers[id] + return provider, found, nil +} + +func (s *inMemoryAuthProviderStore) ForEachAuthProvider(_ context.Context, fn func(*storage.AuthProvider) error) error { + for _, provider := range s.providers { + if err := fn(provider); err != nil { + return err + } + } + return nil +} + +func (s *inMemoryAuthProviderStore) GetAuthProvidersFiltered(_ context.Context, + filter func(*storage.AuthProvider) bool) ([]*storage.AuthProvider, error) { + var filtered []*storage.AuthProvider + for _, provider := range s.providers { + if filter(provider) { + filtered = append(filtered, provider) + } + } + return filtered, nil +} + +func (s *inMemoryAuthProviderStore) AuthProviderExistsWithName(_ context.Context, name string) (bool, error) { + for _, provider := range s.providers { + if provider.GetName() == name { + return true, nil + } + } + return false, nil +} + +func (s *inMemoryAuthProviderStore) AddAuthProvider(ctx context.Context, provider *storage.AuthProvider) error { + if !declarativeconfig.CanModifyResource(ctx, provider) { + return errors.New("cannot create provider with this origin context") + } + s.providers[provider.GetId()] = provider.CloneVT() + return nil +} + +func (s *inMemoryAuthProviderStore) UpdateAuthProvider(ctx context.Context, provider *storage.AuthProvider) error { + if !declarativeconfig.CanModifyResource(ctx, s.providers[provider.GetId()]) || + !declarativeconfig.CanModifyResource(ctx, provider) { + return errors.New("cannot update provider with this origin context") + } + s.providers[provider.GetId()] = provider.CloneVT() + return nil +} + +func (s *inMemoryAuthProviderStore) RemoveAuthProvider(_ context.Context, id string, _ bool) error { + delete(s.providers, id) + return nil +} + +type noopIssuerFactory struct{} + +func (noopIssuerFactory) CreateIssuer(tokens.Source, ...tokens.Option) (tokens.Issuer, error) { + return nil, nil +} + +func (noopIssuerFactory) UnregisterSource(tokens.Source) error { return nil } + +type noopRoleMapperFactory struct{} + +func (noopRoleMapperFactory) GetRoleMapper(string) permissions.RoleMapper { return nil } diff --git a/central/baseimage/service/service_impl.go b/central/baseimage/service/service_impl.go index daa0e46b4a470..2c163a6817495 100644 --- a/central/baseimage/service/service_impl.go +++ b/central/baseimage/service/service_impl.go @@ -5,6 +5,8 @@ import ( "errors" "fmt" "path" + "strings" + "unicode" "github.com/distribution/reference" "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" @@ -189,12 +191,28 @@ func (s *serviceImpl) validateBaseImageRepository(ctx context.Context, repoPath return nil } -// isValidTagPattern checks if the given string is a valid [path.Match] glob pattern. +// maxTagPatternLength bounds a tag pattern to the maximum length of a container image +// tag: one leading character plus up to 127 more (see [reference.TagRegexp]). +const maxTagPatternLength = 128 + +// isValidTagPattern checks that the given string is a glob pattern that could match a +// container image tag. Besides validating the [path.Match] glob syntax, it rejects +// characters that are illegal in a tag ('/', ':', '@', whitespace). Their presence +// signals that the repository path or a registry port leaked into the tag field - +// common when the registry includes a port and the user mistypes the input. Such a +// pattern can never match a real tag, so it is rejected here instead of silently +// matching nothing. func isValidTagPattern(tagPattern string) (bool, error) { // Reject empty tag patterns. Use * to match all tags. if tagPattern == "" { return false, errox.InvalidArgs.New("tag pattern cannot be empty") } + if len(tagPattern) > maxTagPatternLength { + return false, errox.InvalidArgs.Newf("tag pattern is too long: %d bytes (max %d)", len(tagPattern), maxTagPatternLength) + } + if strings.ContainsAny(tagPattern, "/:@") || strings.ContainsFunc(tagPattern, unicode.IsSpace) { + return false, errox.InvalidArgs.Newf("tag pattern '%s' must not contain '/', ':', '@' or whitespace - for a registry with a port, set baseImageRepoPath to 'registry:port/repo' and baseImageTagPattern to the tag pattern (e.g. '1.*')", tagPattern) + } // path.Match validates the pattern and returns an error for malformed input. _, err := path.Match(tagPattern, "") if err != nil { diff --git a/central/baseimage/service/service_impl_test.go b/central/baseimage/service/service_impl_test.go index 8064d7a8753db..5c94a73ec53f6 100644 --- a/central/baseimage/service/service_impl_test.go +++ b/central/baseimage/service/service_impl_test.go @@ -233,6 +233,42 @@ func (suite *ServiceTestSuite) TestIsValidTagPattern() { expectedValid: true, expectedErrMsg: "", }, + { + description: "rejects pattern with slash (repo path leaked into tag field)", + input: "5000/library/ubuntu*", + expectedValid: false, + expectedErrMsg: "set baseImageRepoPath to 'registry:port/repo' and baseImageTagPattern to the tag pattern", + }, + { + description: "rejects pattern with colon (registry port leaked into tag field)", + input: "5000:latest", + expectedValid: false, + expectedErrMsg: "must not contain", + }, + { + description: "rejects pattern with digest separator", + input: "sha256@abc", + expectedValid: false, + expectedErrMsg: "must not contain", + }, + { + description: "rejects pattern with whitespace", + input: "1. *", + expectedValid: false, + expectedErrMsg: "must not contain", + }, + { + description: "rejects pattern with non-ASCII whitespace", + input: "1.\u00a0*", + expectedValid: false, + expectedErrMsg: "must not contain", + }, + { + description: "rejects pattern longer than max tag length", + input: strings.Repeat("a", maxTagPatternLength+1), + expectedValid: false, + expectedErrMsg: "129 bytes (max 128)", + }, } for _, tt := range tests { diff --git a/central/baseimage/service/singleton.go b/central/baseimage/service/singleton.go index a3ad7d955ed19..6b7f5ef540112 100644 --- a/central/baseimage/service/singleton.go +++ b/central/baseimage/service/singleton.go @@ -27,6 +27,7 @@ func initialize() { scanwaiter.Singleton(), scanwaiterv2.Singleton(), sachelper.NewClusterNamespaceSacHelper(clusterDataStore.Singleton(), namespaceDataStore.Singleton()), + clusterDataStore.Singleton(), ) as = New(repository.Singleton(), imageintegration.Set(), scanDelegator) diff --git a/central/baseimage/watcher/singleton.go b/central/baseimage/watcher/singleton.go index 0cb56659572ed..5d48ef671fa25 100644 --- a/central/baseimage/watcher/singleton.go +++ b/central/baseimage/watcher/singleton.go @@ -32,6 +32,7 @@ func Singleton() Watcher { scanwaiter.Singleton(), scanwaiterv2.Singleton(), sachelper.NewClusterNamespaceSacHelper(clusterDataStore.Singleton(), namespaceDataStore.Singleton()), + clusterDataStore.Singleton(), ) watcher = New( diff --git a/central/cluster/lifecycle/gate.go b/central/cluster/lifecycle/gate.go index 7eab76a27beb7..cb1513f543220 100644 --- a/central/cluster/lifecycle/gate.go +++ b/central/cluster/lifecycle/gate.go @@ -53,17 +53,17 @@ func (g *Gate) Enter(clusterID string) (active bool, release func()) { } } -// BeginDeletion prevents new active writes and waits for existing writes to finish. +// BeginDeletion prevents new active writes and waits for in-flight writes to finish. // The returned function must be called once cluster cleanup completes. func (g *Gate) BeginDeletion(clusterID string) func() { e := g.retain(clusterID) concurrency.WithLock(&e.stateMu, func() { e.deleting = true }) - e.mu.Lock() + // Drain in-flight read locks, then release. + concurrency.WithLock(&e.mu, func() {}) return func() { - concurrency.UnsafeUnlock(&e.mu) concurrency.WithLock(&e.stateMu, func() { e.deleting = false }) diff --git a/central/cluster/lifecycle/gate_test.go b/central/cluster/lifecycle/gate_test.go index 7cd8e67424006..5ad0da55a097f 100644 --- a/central/cluster/lifecycle/gate_test.go +++ b/central/cluster/lifecycle/gate_test.go @@ -1,6 +1,7 @@ package lifecycle import ( + "runtime" "testing" "time" @@ -8,12 +9,14 @@ import ( ) func TestGateBlocksDeletionUntilActiveWriteCompletes(t *testing.T) { + clusterID := "cluster-id" + gate := New() writeStarted := make(chan struct{}) releaseWrite := make(chan struct{}) writeFinished := make(chan struct{}) go func() { - active, release := gate.Enter("cluster-id") + active, release := gate.Enter(clusterID) if !active { t.Error("active write was rejected") return @@ -25,23 +28,45 @@ func TestGateBlocksDeletionUntilActiveWriteCompletes(t *testing.T) { }() <-writeStarted - deletionStarted := make(chan func()) - go func() { deletionStarted <- gate.BeginDeletion("cluster-id") }() - require.Never(t, func() bool { - select { - case <-deletionStarted: - return true - default: - return false + deletionStarted := make(chan func(), 1) + go func() { deletionStarted <- gate.BeginDeletion(clusterID) }() + require.Eventually(t, func() bool { + active, release := gate.Enter(clusterID) + if release != nil { + release() } - }, 20*time.Millisecond, time.Millisecond) + + return !active + }, time.Second, 5*time.Millisecond) + + // yield -> to give additional cycles to BeginDeletion. + runtime.Gosched() + require.Empty(t, deletionStarted) close(releaseWrite) <-writeFinished releaseDeletion := <-deletionStarted releaseDeletion() - active, release := gate.Enter("cluster-id") + active, release := gate.Enter(clusterID) require.True(t, active) release() } + +// TestBeginDeletionDropsWriteLockBeforeCleanup checks that BeginDeletion only +// holds the write lock long enough to drain in-flight leases. The dev mutex +// watchdog aborts the process if that lock stays held through cleanup. +func TestBeginDeletionDropsWriteLockBeforeCleanup(t *testing.T) { + gate := New() + releaseDeletion := gate.BeginDeletion("cluster-id") + t.Cleanup(releaseDeletion) + + e := gate.entries["cluster-id"] + require.NotNil(t, e) + require.True(t, e.mu.TryLock(), "write lock still held after BeginDeletion returned") + e.mu.Unlock() + + active, release := gate.Enter("cluster-id") + require.False(t, active) + require.Nil(t, release) +} diff --git a/central/delegatedregistryconfig/delegator/delegator.go b/central/delegatedregistryconfig/delegator/delegator.go index 00af344df8377..c1e128ad0ed97 100644 --- a/central/delegatedregistryconfig/delegator/delegator.go +++ b/central/delegatedregistryconfig/delegator/delegator.go @@ -6,6 +6,7 @@ import ( "time" "github.com/pkg/errors" + clusterDS "github.com/stackrox/rox/central/cluster/datastore" "github.com/stackrox/rox/central/delegatedregistryconfig/datastore" deleConnection "github.com/stackrox/rox/central/delegatedregistryconfig/util/connection" centralMetrics "github.com/stackrox/rox/central/metrics" @@ -31,13 +32,14 @@ var ( ) // New creates a new delegator. -func New(deleRegConfigDS datastore.DataStore, connManager connection.Manager, scanWaiterManager waiter.Manager[*storage.Image], scanWaiterManagerV2 waiter.Manager[*storage.ImageV2], namespaceSACHelper sachelper.ClusterNamespaceSacHelper) *delegatorImpl { +func New(deleRegConfigDS datastore.DataStore, connManager connection.Manager, scanWaiterManager waiter.Manager[*storage.Image], scanWaiterManagerV2 waiter.Manager[*storage.ImageV2], namespaceSACHelper sachelper.ClusterNamespaceSacHelper, clusterDataStore clusterDS.DataStore) *delegatorImpl { return &delegatorImpl{ deleRegConfigDS: deleRegConfigDS, connManager: connManager, scanWaiterManager: scanWaiterManager, scanWaiterManagerV2: scanWaiterManagerV2, namespaceSACHelper: namespaceSACHelper, + clusterDataStore: clusterDataStore, } } @@ -48,6 +50,10 @@ type delegatorImpl struct { // namespaceSACHelper for confirming namespace exists and user has access. namespaceSACHelper sachelper.ClusterNamespaceSacHelper + // clusterDataStore for resolving cluster names in user-facing messages within + // the requester's scope. + clusterDataStore clusterDS.DataStore + // connManager for sending scan requests to secured clusters and ensuring // clusters are valid for delegation. connManager connection.Manager @@ -79,7 +85,7 @@ func (d *delegatorImpl) GetDelegateClusterID(ctx context.Context, imgName *stora return "", true, delegatedregistry.ErrNoClusterSpecified } - if err := d.ValidateCluster(clusterID); err != nil { + if err := d.ValidateCluster(ctx, clusterID); err != nil { return "", true, err } @@ -264,19 +270,34 @@ func (d *delegatorImpl) shouldDelegate(imgName *storage.ImageName, config *stora } // ValidateCluster returns nil if a cluster is a valid target for delegation, otherwise returns an error. -func (d *delegatorImpl) ValidateCluster(clusterID string) error { +func (d *delegatorImpl) ValidateCluster(ctx context.Context, clusterID string) error { conn := d.connManager.GetConnection(clusterID) if conn == nil { - return errors.Errorf("no connection to %q", clusterID) + return errors.Errorf("no connection to cluster %q (%s), verify the cluster is healthy and connected", d.clusterName(ctx, clusterID), clusterID) } if !deleConnection.ValidForDelegation(conn) { - return errors.Errorf("cluster %q does not support delegated scanning", clusterID) + return errors.Errorf("cluster %q (%s) does not support delegated scanning", d.clusterName(ctx, clusterID), clusterID) } return nil } +// clusterName resolves a human-readable cluster name for user-facing messages, +// falling back to the cluster ID when the name cannot be resolved within the +// requester's scope. +func (d *delegatorImpl) clusterName(ctx context.Context, clusterID string) string { + // GetClusterName enforces a cluster-read SAC check against the passed context, so + // resolving under the requester's own (non-elevated) context reveals the name only + // to callers allowed to view this cluster. Everyone else keeps the opaque ID. + name, exists, err := d.clusterDataStore.GetClusterName(ctx, clusterID) + if err != nil || !exists { + return clusterID + } + + return name +} + // withAdminRead elevates a context to include admin read access. func withAdminRead(ctx context.Context) context.Context { elevatedCtx := sac.WithGlobalAccessScopeChecker( diff --git a/central/delegatedregistryconfig/delegator/delegator_test.go b/central/delegatedregistryconfig/delegator/delegator_test.go index 3f955add7c913..9532d6a952e9b 100644 --- a/central/delegatedregistryconfig/delegator/delegator_test.go +++ b/central/delegatedregistryconfig/delegator/delegator_test.go @@ -6,6 +6,7 @@ import ( "fmt" "testing" + clusterDSMocks "github.com/stackrox/rox/central/cluster/datastore/mocks" deleDSMocks "github.com/stackrox/rox/central/delegatedregistryconfig/datastore/mocks" sacHelperMocks "github.com/stackrox/rox/central/role/sachelper/mocks" connMocks "github.com/stackrox/rox/central/sensor/service/connection/mocks" @@ -51,7 +52,9 @@ func TestGetDelegateClusterID(t *testing.T) { connMgr = connMocks.NewMockManager(ctrl) waiterMgr = waiterMocks.NewMockManager[*storage.Image](ctrl) deleClusterDS = deleDSMocks.NewMockDataStore(ctrl) - d = New(deleClusterDS, connMgr, waiterMgr, nil, nil) + clusterDS := clusterDSMocks.NewMockDataStore(ctrl) + clusterDS.EXPECT().GetClusterName(gomock.Any(), gomock.Any()).Return("fake-cluster-name", true, nil).AnyTimes() + d = New(deleClusterDS, connMgr, waiterMgr, nil, nil, clusterDS) } t.Run("error get config", func(t *testing.T) { @@ -111,7 +114,7 @@ func TestGetDelegateClusterID(t *testing.T) { connMgr.EXPECT().GetConnection(gomock.Any()).Return(nil) _, shouldDelegate, err := d.GetDelegateClusterID(ctxBG, nil) assert.True(t, shouldDelegate) - assert.ErrorContains(t, err, "no connection") + assert.ErrorContains(t, err, "no connection to cluster") }) t.Run("all def cluster id conn no cap", func(t *testing.T) { @@ -263,7 +266,7 @@ func TestDelegateEnrichImage(t *testing.T) { namespaceSACHelper = sacHelperMocks.NewMockClusterNamespaceSacHelper(ctrl) waiter.EXPECT().ID().Return(fakeWaiterID).AnyTimes() - d = New(deleClusterDS, connMgr, waiterMgr, nil, namespaceSACHelper) + d = New(deleClusterDS, connMgr, waiterMgr, nil, namespaceSACHelper, nil) } t.Run("empty cluster id", func(t *testing.T) { @@ -357,7 +360,7 @@ func TestDelegateScanImageV2(t *testing.T) { namespaceSACHelper = sacHelperMocks.NewMockClusterNamespaceSacHelper(ctrl) waiter.EXPECT().ID().Return(fakeWaiterID).AnyTimes() - d = New(deleClusterDS, connMgr, nil, waiterMgr, namespaceSACHelper) + d = New(deleClusterDS, connMgr, nil, waiterMgr, namespaceSACHelper, nil) } t.Run("empty cluster id", func(t *testing.T) { @@ -440,7 +443,7 @@ func TestInferNamespace(t *testing.T) { ctrl := gomock.NewController(t) namespaceSACHelper = sacHelperMocks.NewMockClusterNamespaceSacHelper(ctrl) - d = New(nil, nil, nil, nil, namespaceSACHelper) + d = New(nil, nil, nil, nil, namespaceSACHelper, nil) } t.Run("no namespace on error", func(t *testing.T) { diff --git a/central/deployment/datastore/datastore.go b/central/deployment/datastore/datastore.go index 67210757b8e23..8b9bb033ff99c 100644 --- a/central/deployment/datastore/datastore.go +++ b/central/deployment/datastore/datastore.go @@ -34,6 +34,8 @@ type DataStore interface { ListDeployment(ctx context.Context, id string) (*storage.ListDeployment, bool, error) GetDeployment(ctx context.Context, id string) (*storage.Deployment, bool, error) + // DeploymentExists reports whether the deployment is stored, without fetching or copying it. + DeploymentExists(ctx context.Context, id string) (bool, error) GetDeployments(ctx context.Context, ids []string) ([]*storage.Deployment, error) CountDeployments(ctx context.Context) (int, error) // UpsertDeployment adds or updates a deployment. If the deployment exists, the tags in the deployment are taken from diff --git a/central/deployment/datastore/datastore_impl.go b/central/deployment/datastore/datastore_impl.go index 4ee891e512bfa..4c69aa96b9a0b 100644 --- a/central/deployment/datastore/datastore_impl.go +++ b/central/deployment/datastore/datastore_impl.go @@ -227,6 +227,11 @@ func (ds *datastoreImpl) GetDeployment(ctx context.Context, id string) (*storage return deployment, true, nil } +// DeploymentExists +func (ds *datastoreImpl) DeploymentExists(ctx context.Context, id string) (bool, error) { + return ds.deploymentStore.Exists(ctx, id) +} + // GetDeployments func (ds *datastoreImpl) GetDeployments(ctx context.Context, ids []string) ([]*storage.Deployment, error) { var deployments []*storage.Deployment diff --git a/central/deployment/datastore/internal/store/mocks/store.go b/central/deployment/datastore/internal/store/mocks/store.go index 455b7c14e2d08..10a0a236b9c4c 100644 --- a/central/deployment/datastore/internal/store/mocks/store.go +++ b/central/deployment/datastore/internal/store/mocks/store.go @@ -73,6 +73,21 @@ func (mr *MockStoreMockRecorder) Delete(ctx, id any) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockStore)(nil).Delete), ctx, id) } +// Exists mocks base method. +func (m *MockStore) Exists(ctx context.Context, id string) (bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Exists", ctx, id) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Exists indicates an expected call of Exists. +func (mr *MockStoreMockRecorder) Exists(ctx, id any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Exists", reflect.TypeOf((*MockStore)(nil).Exists), ctx, id) +} + // Get mocks base method. func (m *MockStore) Get(ctx context.Context, id string) (*storage.Deployment, bool, error) { m.ctrl.T.Helper() diff --git a/central/deployment/datastore/internal/store/store.go b/central/deployment/datastore/internal/store/store.go index 19a7d3ea50ca5..b2f155aa1cf6e 100644 --- a/central/deployment/datastore/internal/store/store.go +++ b/central/deployment/datastore/internal/store/store.go @@ -18,6 +18,7 @@ type Store interface { SearchListDeployments(ctx context.Context, q *v1.Query) ([]*storage.ListDeployment, error) Get(ctx context.Context, id string) (*storage.Deployment, bool, error) + Exists(ctx context.Context, id string) (bool, error) GetMany(ctx context.Context, ids []string) ([]*storage.Deployment, []int, error) Walk(ctx context.Context, fn func(deployment *storage.Deployment) error) error WalkByQuery(ctx context.Context, query *v1.Query, fn func(deployment *storage.Deployment) error) error diff --git a/central/deployment/datastore/mocks/datastore.go b/central/deployment/datastore/mocks/datastore.go index 4f4ec023ee7c9..206d20c2f17fd 100644 --- a/central/deployment/datastore/mocks/datastore.go +++ b/central/deployment/datastore/mocks/datastore.go @@ -74,6 +74,21 @@ func (mr *MockDataStoreMockRecorder) CountDeployments(ctx any) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CountDeployments", reflect.TypeOf((*MockDataStore)(nil).CountDeployments), ctx) } +// DeploymentExists mocks base method. +func (m *MockDataStore) DeploymentExists(ctx context.Context, id string) (bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeploymentExists", ctx, id) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// DeploymentExists indicates an expected call of DeploymentExists. +func (mr *MockDataStoreMockRecorder) DeploymentExists(ctx, id any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeploymentExists", reflect.TypeOf((*MockDataStore)(nil).DeploymentExists), ctx, id) +} + // GetContainerImageViews mocks base method. func (m *MockDataStore) GetContainerImageViews(ctx context.Context, q *v1.Query) ([]*views.ContainerImageView, error) { m.ctrl.T.Helper() diff --git a/central/detection/alertmanager/alert_manager_impl_test.go b/central/detection/alertmanager/alert_manager_impl_test.go index d31dc247d79f5..aecff5c1961c8 100644 --- a/central/detection/alertmanager/alert_manager_impl_test.go +++ b/central/detection/alertmanager/alert_manager_impl_test.go @@ -814,6 +814,29 @@ func (suite *AlertManagerTestSuite) TestMergedRuntimeAlertMarkedInactiveWhenDepl suite.True(modified.Contains("dep-gone")) } +func (suite *AlertManagerTestSuite) TestUnchangedRuntimeAlertMarkedInactiveWhenDeploymentGone() { + previous := runtimeDeploymentAlert("runtime-alert-1", "dep-gone", false, yesterdayProcess) + incoming := runtimeDeploymentAlert("runtime-alert-incoming", "dep-gone", false, yesterdayProcess) + + suite.alertsMock.EXPECT().SearchAlertMatchKeys(suite.ctx, gomock.Any(), true). + Return(alertsToMatchKeys([]*storage.Alert{previous}), nil) + suite.alertsMock.EXPECT().SearchRawAlerts(suite.ctx, gomock.Any(), false). + Return([]*storage.Alert{previous.CloneVT()}, nil) + suite.runtimeDetectorMock.EXPECT().DeploymentInactive("dep-gone").Return(true) + suite.alertsMock.EXPECT().SearchRawAlerts(suite.ctx, gomock.Any(), false). + Return([]*storage.Alert{previous.CloneVT()}, nil) + suite.alertsMock.EXPECT().UpsertAlert(suite.ctx, gomock.Any()).DoAndReturn(func(_ context.Context, stored *storage.Alert) error { + suite.Equal(previous.GetId(), stored.GetId()) + suite.True(stored.GetDeployment().GetInactive(), "stored runtime alert should be marked inactive") + return nil + }) + suite.notifierMock.EXPECT().ProcessAlert(gomock.Any(), gomock.Any()).Return() + + modified, err := suite.alertManager.AlertAndNotify(suite.ctx, []*storage.Alert{incoming}) + suite.NoError(err) + suite.True(modified.Contains("dep-gone")) +} + func (suite *AlertManagerTestSuite) TestResolvedDeploymentAlertReturnsDeploymentID() { alerts := getAlerts() diff --git a/central/detection/runtime/detector_impl.go b/central/detection/runtime/detector_impl.go index c7cbc63af811c..5746c5927b238 100644 --- a/central/detection/runtime/detector_impl.go +++ b/central/detection/runtime/detector_impl.go @@ -50,7 +50,7 @@ func (d *detectorImpl) DeploymentWhitelistedForPolicy(deploymentID, policyID str } func (d *detectorImpl) DeploymentInactive(deploymentID string) bool { - _, exists, err := d.deployments.ListDeployment(detectorCtx, deploymentID) + exists, err := d.deployments.DeploymentExists(detectorCtx, deploymentID) if err != nil { log.Errorf("Couldn't determine inactive state of deployment %q: %v", deploymentID, err) return false diff --git a/central/detection/runtime/detector_impl_test.go b/central/detection/runtime/detector_impl_test.go new file mode 100644 index 0000000000000..53cd5d5f228d2 --- /dev/null +++ b/central/detection/runtime/detector_impl_test.go @@ -0,0 +1,42 @@ +package runtime + +import ( + "errors" + "testing" + + "github.com/stackrox/rox/central/deployment/datastore/mocks" + "github.com/stretchr/testify/assert" + "go.uber.org/mock/gomock" +) + +func TestDeploymentInactive(t *testing.T) { + tests := map[string]struct { + exists bool + err error + inactive bool + }{ + "present deployment": { + exists: true, + inactive: false, + }, + "missing deployment": { + exists: false, + inactive: true, + }, + // A failed lookup must not mark live deployments' alerts inactive. + "datastore error": { + err: errors.New("database unavailable"), + inactive: false, + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + deployments := mocks.NewMockDataStore(gomock.NewController(t)) + deployments.EXPECT().DeploymentExists(gomock.Any(), "deployment-id").Return(test.exists, test.err) + + detector := NewDetector(nil, deployments) + assert.Equal(t, test.inactive, detector.DeploymentInactive("deployment-id")) + }) + } +} diff --git a/central/enrichment/singleton.go b/central/enrichment/singleton.go index ba236d20af19c..598731bd43634 100644 --- a/central/enrichment/singleton.go +++ b/central/enrichment/singleton.go @@ -57,6 +57,7 @@ func initialize() { scanwaiterv2.Singleton(), sachelper.NewClusterNamespaceSacHelper(clusterDataStore.Singleton(), namespaceDataStore.Singleton()), + clusterDataStore.Singleton(), ) if !features.FlattenImageData.Enabled() { diff --git a/central/graphql/generator/codegen/typewalk.go b/central/graphql/generator/codegen/typewalk.go index 80c5cb3a0140b..b1a1bcb84d58b 100644 --- a/central/graphql/generator/codegen/typewalk.go +++ b/central/graphql/generator/codegen/typewalk.go @@ -126,6 +126,9 @@ func (ctx *walkState) walkField(td *typeData, p reflect.Type, sf reflect.StructF if !td.IsInputType && protoreflect.IsInternalGeneratorField(sf) { return } + if _, ok := sf.Tag.Lookup("protobuf_oneof"); ok { + return + } if strings.HasPrefix(sf.Name, "DEPRECATED") { return } diff --git a/central/graphql/resolvers/generated.go b/central/graphql/resolvers/generated.go index 38b997da3faf7..1e9ccedb56b07 100644 --- a/central/graphql/resolvers/generated.go +++ b/central/graphql/resolvers/generated.go @@ -669,17 +669,27 @@ func registerGeneratedTypes(builder generator.SchemaBuilder) { generator.RegisterProtoEnum(builder, reflect.TypeOf(storage.EventSource(0))) utils.Must(builder.AddType("Exclusion", []string{ "deployment: Exclusion_Deployment", + "excludeByKind: Exclusion_ExcludeByKind", "expiration: Time", "image: Exclusion_Image", "name: String!", + "matcher: ExclusionMatcher", + })) + utils.Must(builder.AddUnionType("ExclusionMatcher", []string{ + "Exclusion_Deployment", + "Exclusion_ExcludeByKind", })) utils.Must(builder.AddType("Exclusion_Deployment", []string{ "name: String!", "scope: Scope", })) + utils.Must(builder.AddType("Exclusion_ExcludeByKind", []string{ + "kinds: [Exclusion_WorkloadKind!]!", + })) utils.Must(builder.AddType("Exclusion_Image", []string{ "name: String!", })) + generator.RegisterProtoEnum(builder, reflect.TypeOf(storage.Exclusion_WorkloadKind(0))) utils.Must(builder.AddType("Exploit", []string{ "dateAdded: String!", "dueDate: String!", @@ -8033,6 +8043,11 @@ func (resolver *exclusionResolver) Deployment(ctx context.Context) (*exclusion_D return resolver.root.wrapExclusion_Deployment(value, true, nil) } +func (resolver *exclusionResolver) ExcludeByKind(ctx context.Context) (*exclusion_ExcludeByKindResolver, error) { + value := resolver.data.GetExcludeByKind() + return resolver.root.wrapExclusion_ExcludeByKind(value, true, nil) +} + func (resolver *exclusionResolver) Expiration(ctx context.Context) (*graphql.Time, error) { value := resolver.data.GetExpiration() return protocompat.ConvertTimestampToGraphqlTimeOrError(value) @@ -8048,6 +8063,34 @@ func (resolver *exclusionResolver) Name(ctx context.Context) string { return value } +type exclusionMatcherResolver struct { + resolver interface{} +} + +func (resolver *exclusionResolver) Matcher() *exclusionMatcherResolver { + if val := resolver.data.GetDeployment(); val != nil { + return &exclusionMatcherResolver{ + resolver: &exclusion_DeploymentResolver{root: resolver.root, data: val}, + } + } + if val := resolver.data.GetExcludeByKind(); val != nil { + return &exclusionMatcherResolver{ + resolver: &exclusion_ExcludeByKindResolver{root: resolver.root, data: val}, + } + } + return nil +} + +func (resolver *exclusionMatcherResolver) ToExclusion_Deployment() (*exclusion_DeploymentResolver, bool) { + res, ok := resolver.resolver.(*exclusion_DeploymentResolver) + return res, ok +} + +func (resolver *exclusionMatcherResolver) ToExclusion_ExcludeByKind() (*exclusion_ExcludeByKindResolver, bool) { + res, ok := resolver.resolver.(*exclusion_ExcludeByKindResolver) + return res, ok +} + type exclusion_DeploymentResolver struct { ctx context.Context root *Resolver @@ -8100,6 +8143,53 @@ func (resolver *exclusion_DeploymentResolver) Scope(ctx context.Context) (*scope return resolver.root.wrapScope(value, true, nil) } +type exclusion_ExcludeByKindResolver struct { + ctx context.Context + root *Resolver + data *storage.Exclusion_ExcludeByKind +} + +func (resolver *Resolver) wrapExclusion_ExcludeByKind(value *storage.Exclusion_ExcludeByKind, ok bool, err error) (*exclusion_ExcludeByKindResolver, error) { + if !ok || err != nil || value == nil { + return nil, err + } + return &exclusion_ExcludeByKindResolver{root: resolver, data: value}, nil +} + +func (resolver *Resolver) wrapExclusion_ExcludeByKinds(values []*storage.Exclusion_ExcludeByKind, err error) ([]*exclusion_ExcludeByKindResolver, error) { + if err != nil || len(values) == 0 { + return nil, err + } + output := make([]*exclusion_ExcludeByKindResolver, len(values)) + for i, v := range values { + output[i] = &exclusion_ExcludeByKindResolver{root: resolver, data: v} + } + return output, nil +} + +func (resolver *Resolver) wrapExclusion_ExcludeByKindWithContext(ctx context.Context, value *storage.Exclusion_ExcludeByKind, ok bool, err error) (*exclusion_ExcludeByKindResolver, error) { + if !ok || err != nil || value == nil { + return nil, err + } + return &exclusion_ExcludeByKindResolver{ctx: ctx, root: resolver, data: value}, nil +} + +func (resolver *Resolver) wrapExclusion_ExcludeByKindsWithContext(ctx context.Context, values []*storage.Exclusion_ExcludeByKind, err error) ([]*exclusion_ExcludeByKindResolver, error) { + if err != nil || len(values) == 0 { + return nil, err + } + output := make([]*exclusion_ExcludeByKindResolver, len(values)) + for i, v := range values { + output[i] = &exclusion_ExcludeByKindResolver{ctx: ctx, root: resolver, data: v} + } + return output, nil +} + +func (resolver *exclusion_ExcludeByKindResolver) Kinds(ctx context.Context) []string { + value := resolver.data.GetKinds() + return stringSlice(value) +} + type exclusion_ImageResolver struct { ctx context.Context root *Resolver @@ -8147,6 +8237,24 @@ func (resolver *exclusion_ImageResolver) Name(ctx context.Context) string { return value } +func toExclusion_WorkloadKind(value *string) storage.Exclusion_WorkloadKind { + if value != nil { + return storage.Exclusion_WorkloadKind(storage.Exclusion_WorkloadKind_value[*value]) + } + return storage.Exclusion_WorkloadKind(0) +} + +func toExclusion_WorkloadKinds(values *[]string) []storage.Exclusion_WorkloadKind { + if values == nil { + return nil + } + output := make([]storage.Exclusion_WorkloadKind, len(*values)) + for i, v := range *values { + output[i] = toExclusion_WorkloadKind(&v) + } + return output +} + type exploitResolver struct { ctx context.Context root *Resolver diff --git a/central/imageintegration/datastore/singleton.go b/central/imageintegration/datastore/singleton.go index a3d683a1b9258..f54a5acc8fd93 100644 --- a/central/imageintegration/datastore/singleton.go +++ b/central/imageintegration/datastore/singleton.go @@ -2,6 +2,7 @@ package datastore import ( "context" + "slices" "github.com/pkg/errors" "github.com/stackrox/rox/central/globaldb" @@ -132,19 +133,46 @@ func setupScannerV4Integration(ctx context.Context, iiStore store.Store, iis []* } // createDefaultScannerV4Integration will create the default Scanner V4 integration if it does -// not currently exist. +// not currently exist, or reconcile its categories if they differ from the default. func createDefaultScannerV4Integration(ctx context.Context, iiStore store.Store) { - if _, exists, err := iiStore.Get(ctx, store.DefaultScannerV4Integration.GetId()); err != nil { + defaultID := store.DefaultScannerV4Integration.GetId() + existing, exists, err := iiStore.Get(ctx, defaultID) + if err != nil { utils.Should(errors.Wrap(err, "unable to detect if default Scanner V4 integration exists")) return - } else if exists { - // Nothing to do, integration exists. + } + + if !exists { + log.Infof("Upserting default Scanner V4 integration %q (%s)", store.DefaultScannerV4Integration.GetName(), defaultID) + err := iiStore.Upsert(ctx, store.DefaultScannerV4Integration) + utils.Should(errors.Wrap(err, "unable to upsert default ScannerV4 integration")) + return + } + + // Restore default categories on records that predate them. The API rejects category changes, + // so this only repairs older data. Preserve other settings. + if categoriesMatch(existing.GetCategories(), store.DefaultScannerV4Integration.GetCategories()) { return } - log.Infof("Upserting default Scanner V4 integration %q (%s)", store.DefaultScannerV4Integration.GetName(), store.DefaultScannerV4Integration.GetId()) - err := iiStore.Upsert(ctx, store.DefaultScannerV4Integration) - utils.Should(errors.Wrap(err, "unable to upsert default ScannerV4 integration")) + log.Warnf("Reconciling categories for default Scanner V4 integration %q (%s): updating from %v to %v", + existing.GetName(), defaultID, existing.GetCategories(), store.DefaultScannerV4Integration.GetCategories()) + + existing.Categories = store.DefaultScannerV4Integration.GetCategories() + err = iiStore.Upsert(ctx, existing) + utils.Should(errors.Wrap(err, "unable to reconcile default ScannerV4 integration categories")) +} + +// categoriesMatch returns true if two category slices contain the same elements (order-independent). +func categoriesMatch(a, b []storage.ImageIntegrationCategory) bool { + if len(a) != len(b) { + return false + } + a = slices.Clone(a) + b = slices.Clone(b) + slices.Sort(a) + slices.Sort(b) + return slices.Equal(a, b) } // deleteScannerV4Integrations will delete all Scanner V4 integrations except for diff --git a/central/imageintegration/datastore/singleton_test.go b/central/imageintegration/datastore/singleton_test.go index cbbf1e712e097..94b79b4e8b4d2 100644 --- a/central/imageintegration/datastore/singleton_test.go +++ b/central/imageintegration/datastore/singleton_test.go @@ -149,11 +149,23 @@ func TestSetupScannerV4Integration(t *testing.T) { } }) - t.Run("do not try upsert if default integration exists", func(t *testing.T) { + t.Run("do not try upsert if default integration exists with correct categories", func(t *testing.T) { testutils.MustUpdateFeature(t, features.ScannerV4, true) s := mockIIStore.NewMockStore(gomock.NewController(t)) - s.EXPECT().Get(ctx, defID).Return(nil, true, nil) + existingCorrect := &storage.ImageIntegration{ + Id: defID, + Name: "Scanner V4", + Type: scannerTypes.ScannerV4, + Categories: []storage.ImageIntegrationCategory{ + storage.ImageIntegrationCategory_SCANNER, + storage.ImageIntegrationCategory_NODE_SCANNER, + }, + IntegrationConfig: &storage.ImageIntegration_ScannerV4{ + ScannerV4: &storage.ScannerV4Config{}, + }, + } + s.EXPECT().Get(ctx, defID).Return(existingCorrect, true, nil) s.EXPECT().PruneMany(ctx, []string{"ID-Other-ScannerV4"}) setupScannerV4Integration(ctx, s, iis) }) @@ -212,3 +224,177 @@ func TestSetupLegacyScannerIntegration(t *testing.T) { setupLegacyScannerIntegration(ctx, s, h, iis) }) } + +func TestCreateDefaultScannerV4Integration(t *testing.T) { + ctx := context.Background() + defaultID := store.DefaultScannerV4Integration.GetId() + + t.Run("create default integration when it does not exist", func(t *testing.T) { + s := mockIIStore.NewMockStore(gomock.NewController(t)) + + s.EXPECT().Get(ctx, defaultID).Return(nil, false, nil) + s.EXPECT().Upsert(ctx, store.DefaultScannerV4Integration).Return(nil) + + createDefaultScannerV4Integration(ctx, s) + }) + + t.Run("reconcile categories when existing integration has SCANNER only", func(t *testing.T) { + s := mockIIStore.NewMockStore(gomock.NewController(t)) + + existingScannerOnly := &storage.ImageIntegration{ + Id: defaultID, + Name: "Scanner V4", + Type: scannerTypes.ScannerV4, + Categories: []storage.ImageIntegrationCategory{ + storage.ImageIntegrationCategory_SCANNER, + }, + IntegrationConfig: &storage.ImageIntegration_ScannerV4{ + ScannerV4: &storage.ScannerV4Config{ + IndexerEndpoint: "custom-indexer:8443", + MatcherEndpoint: "custom-matcher:8443", + }, + }, + } + + s.EXPECT().Get(ctx, defaultID).Return(existingScannerOnly, true, nil) + s.EXPECT().Upsert(ctx, gomock.Any()).DoAndReturn(func(_ context.Context, ii *storage.ImageIntegration) error { + assert.ElementsMatch(t, store.DefaultScannerV4Integration.GetCategories(), ii.GetCategories()) + assert.Equal(t, "custom-indexer:8443", ii.GetScannerV4().GetIndexerEndpoint()) + assert.Equal(t, "custom-matcher:8443", ii.GetScannerV4().GetMatcherEndpoint()) + return nil + }) + + createDefaultScannerV4Integration(ctx, s) + }) + + t.Run("reconcile categories when existing integration has wrong categories", func(t *testing.T) { + s := mockIIStore.NewMockStore(gomock.NewController(t)) + + existingNodeOnly := &storage.ImageIntegration{ + Id: defaultID, + Name: "Scanner V4", + Type: scannerTypes.ScannerV4, + Categories: []storage.ImageIntegrationCategory{ + storage.ImageIntegrationCategory_NODE_SCANNER, + }, + IntegrationConfig: &storage.ImageIntegration_ScannerV4{ + ScannerV4: &storage.ScannerV4Config{ + IndexerEndpoint: "indexer:8080", + MatcherEndpoint: "matcher:8080", + }, + }, + } + + s.EXPECT().Get(ctx, defaultID).Return(existingNodeOnly, true, nil) + s.EXPECT().Upsert(ctx, gomock.Any()).DoAndReturn(func(_ context.Context, ii *storage.ImageIntegration) error { + assert.ElementsMatch(t, store.DefaultScannerV4Integration.GetCategories(), ii.GetCategories()) + assert.Equal(t, "indexer:8080", ii.GetScannerV4().GetIndexerEndpoint()) + return nil + }) + + createDefaultScannerV4Integration(ctx, s) + }) + + t.Run("reconcile categories when existing integration has nil categories", func(t *testing.T) { + s := mockIIStore.NewMockStore(gomock.NewController(t)) + + existingNilCategories := &storage.ImageIntegration{ + Id: defaultID, + Name: "Scanner V4", + Type: scannerTypes.ScannerV4, + Categories: nil, + IntegrationConfig: &storage.ImageIntegration_ScannerV4{ + ScannerV4: &storage.ScannerV4Config{ + IndexerEndpoint: "legacy-indexer:8443", + }, + }, + } + + s.EXPECT().Get(ctx, defaultID).Return(existingNilCategories, true, nil) + s.EXPECT().Upsert(ctx, gomock.Any()).DoAndReturn(func(_ context.Context, ii *storage.ImageIntegration) error { + assert.ElementsMatch(t, store.DefaultScannerV4Integration.GetCategories(), ii.GetCategories()) + assert.Equal(t, "legacy-indexer:8443", ii.GetScannerV4().GetIndexerEndpoint()) + return nil + }) + + createDefaultScannerV4Integration(ctx, s) + }) + + t.Run("do nothing when Get returns error", func(t *testing.T) { + s := mockIIStore.NewMockStore(gomock.NewController(t)) + + s.EXPECT().Get(ctx, defaultID).Return(nil, false, errors.New("database error")) + + if buildinfo.ReleaseBuild { + createDefaultScannerV4Integration(ctx, s) + } else { + assert.Panics(t, func() { createDefaultScannerV4Integration(ctx, s) }) + } + }) +} + +func TestCategoriesMatch(t *testing.T) { + testCases := map[string]struct { + a []storage.ImageIntegrationCategory + b []storage.ImageIntegrationCategory + expected bool + }{ + "empty slices match": { + []storage.ImageIntegrationCategory{}, + []storage.ImageIntegrationCategory{}, + true, + }, + "nil and empty slice match": { + nil, + []storage.ImageIntegrationCategory{}, + true, + }, + "same single element": { + []storage.ImageIntegrationCategory{storage.ImageIntegrationCategory_SCANNER}, + []storage.ImageIntegrationCategory{storage.ImageIntegrationCategory_SCANNER}, + true, + }, + "same multiple elements in same order": { + []storage.ImageIntegrationCategory{ + storage.ImageIntegrationCategory_SCANNER, + storage.ImageIntegrationCategory_NODE_SCANNER, + }, + []storage.ImageIntegrationCategory{ + storage.ImageIntegrationCategory_SCANNER, + storage.ImageIntegrationCategory_NODE_SCANNER, + }, + true, + }, + "same multiple elements in different order": { + []storage.ImageIntegrationCategory{ + storage.ImageIntegrationCategory_NODE_SCANNER, + storage.ImageIntegrationCategory_SCANNER, + }, + []storage.ImageIntegrationCategory{ + storage.ImageIntegrationCategory_SCANNER, + storage.ImageIntegrationCategory_NODE_SCANNER, + }, + true, + }, + "different lengths do not match": { + []storage.ImageIntegrationCategory{storage.ImageIntegrationCategory_SCANNER}, + []storage.ImageIntegrationCategory{ + storage.ImageIntegrationCategory_SCANNER, + storage.ImageIntegrationCategory_NODE_SCANNER, + }, + false, + }, + "different elements do not match": { + []storage.ImageIntegrationCategory{storage.ImageIntegrationCategory_SCANNER}, + []storage.ImageIntegrationCategory{storage.ImageIntegrationCategory_NODE_SCANNER}, + false, + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + result := categoriesMatch(tc.a, tc.b) + assert.Equal(t, tc.expected, result) + }) + } +} diff --git a/central/imageintegration/service/service_impl.go b/central/imageintegration/service/service_impl.go index 9ba22efa473cb..57eccf6e553c7 100644 --- a/central/imageintegration/service/service_impl.go +++ b/central/imageintegration/service/service_impl.go @@ -2,6 +2,7 @@ package service import ( "context" + "slices" "sort" "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" @@ -131,6 +132,18 @@ func sortCategories(categories []storage.ImageIntegrationCategory) { }) } +// sameCategories returns true if both slices contain the same categories, ignoring order. +func sameCategories(a, b []storage.ImageIntegrationCategory) bool { + if len(a) != len(b) { + return false + } + a = slices.Clone(a) + b = slices.Clone(b) + slices.Sort(a) + slices.Sort(b) + return slices.Equal(a, b) +} + func (s *serviceImpl) validateTestAndNormalize(ctx context.Context, request *storage.ImageIntegration) error { if err := s.validateIntegration(ctx, request); err != nil { return err @@ -404,6 +417,9 @@ func (s *serviceImpl) reconcileUpdateImageIntegrationRequest(ctx context.Context if newType != oldType && (newType == scannerTypes.ScannerV4 || oldType == scannerTypes.ScannerV4) { return errors.Wrap(errox.InvalidArgs, "cannot change integration type to/from scanner V4") } + if oldType == scannerTypes.ScannerV4 && !sameCategories(integration.GetCategories(), updateRequest.GetConfig().GetCategories()) { + return errors.Wrap(errox.InvalidArgs, "cannot change categories of scanner V4 integration") + } // Note that integrations of type "azure" support both `DockerConfig` (deprecated in 4.7) and `AzureConfig`. // Here we migrate requests with type "azure" and `DockerConfig` on the fly to type `AzureConfig`. diff --git a/central/imageintegration/service/service_impl_test.go b/central/imageintegration/service/service_impl_test.go index 623dc7ed4a152..7d930eb0e97a8 100644 --- a/central/imageintegration/service/service_impl_test.go +++ b/central/imageintegration/service/service_impl_test.go @@ -513,6 +513,44 @@ func TestScannerV4Restrictions(t *testing.T) { _, err := s.UpdateImageIntegration(context.Background(), &v1.UpdateImageIntegrationRequest{Config: iiNew}) assert.ErrorContains(t, err, "scanner V4") }) + + t.Run("prevent category change on scannerv4", func(t *testing.T) { + ctrl := gomock.NewController(t) + iiDS := integrationMocks.NewMockDataStore(ctrl) + s := &serviceImpl{datastore: iiDS} + + iiNew := ii.CloneVT() + iiNew.Categories = append(iiNew.Categories, storage.ImageIntegrationCategory_NODE_SCANNER) + + iiDS.EXPECT().GetImageIntegrations(gomock.Any(), gomock.Any()).Return([]*storage.ImageIntegration{ii}, nil) + iiDS.EXPECT().GetImageIntegration(gomock.Any(), gomock.Any()).Return(ii, true, nil) + + _, err := s.UpdateImageIntegration(context.Background(), &v1.UpdateImageIntegrationRequest{Config: iiNew}) + assert.ErrorIs(t, err, errox.InvalidArgs) + assert.ErrorContains(t, err, "categories of scanner V4") + }) +} + +func TestSameCategories(t *testing.T) { + scanner := storage.ImageIntegrationCategory_SCANNER + nodeScanner := storage.ImageIntegrationCategory_NODE_SCANNER + + cases := map[string]struct { + a, b []storage.ImageIntegrationCategory + expected bool + }{ + "both empty": {expected: true}, + "same order": {a: []storage.ImageIntegrationCategory{scanner, nodeScanner}, b: []storage.ImageIntegrationCategory{scanner, nodeScanner}, expected: true}, + "different order": {a: []storage.ImageIntegrationCategory{nodeScanner, scanner}, b: []storage.ImageIntegrationCategory{scanner, nodeScanner}, expected: true}, + "category added": {a: []storage.ImageIntegrationCategory{scanner}, b: []storage.ImageIntegrationCategory{scanner, nodeScanner}, expected: false}, + "category removed": {a: []storage.ImageIntegrationCategory{scanner, nodeScanner}, b: []storage.ImageIntegrationCategory{scanner}, expected: false}, + "category swapped": {a: []storage.ImageIntegrationCategory{scanner}, b: []storage.ImageIntegrationCategory{nodeScanner}, expected: false}, + } + for name, c := range cases { + t.Run(name, func(t *testing.T) { + assert.Equal(t, c.expected, sameCategories(c.a, c.b)) + }) + } } func TestGCRCreationBlocked(t *testing.T) { diff --git a/central/main.go b/central/main.go index 1a2d2a2c1d37a..cb3bb5d8a53a4 100755 --- a/central/main.go +++ b/central/main.go @@ -24,6 +24,7 @@ import ( "github.com/stackrox/rox/central/audit" authDS "github.com/stackrox/rox/central/auth/datastore" internalTokenAuthService "github.com/stackrox/rox/central/auth/internaltokens/service" + openshiftAuth "github.com/stackrox/rox/central/auth/openshift" authService "github.com/stackrox/rox/central/auth/service" "github.com/stackrox/rox/central/auth/userpass" authProviderRegistry "github.com/stackrox/rox/central/authprovider/registry" @@ -603,6 +604,8 @@ func startGRPCServer() { basicAuthProvider := userpass.RegisterAuthProviderOrPanic(authProviderRegisteringCtx, basicAuthMgr, registry) + openshiftAuth.SeedMetricsAuthProvider(authProviderRegisteringCtx, registry, roleDataStore.Singleton(), groupDataStore.Singleton()) + clusterInitBackend := backend.Singleton() serviceMTLSExtractor, err := service.NewExtractorWithCertValidation(clusterInitBackend) if err != nil { diff --git a/central/node/datastore/store/postgres/store_test.go b/central/node/datastore/store/postgres/store_test.go index 07ff3e3018783..8c3ca0de4cc41 100644 --- a/central/node/datastore/store/postgres/store_test.go +++ b/central/node/datastore/store/postgres/store_test.go @@ -284,7 +284,7 @@ func (s *NodesStoreSuite) TestStore_OrphanedCVEs() { s.NoError(err) s.NotEmpty(orphanedCVEs) for _, cve := range orphanedCVEs { - s.NotNil(cve.OrphanedTime) + s.NotNil(cve.GetOrphanedTime()) s.True(vulnNames.Contains(cve.GetCveBaseInfo().GetCve())) } @@ -311,8 +311,8 @@ func (s *NodesStoreSuite) TestStore_OrphanedCVEs() { s.NoError(err) s.NotEmpty(nodeCVEs) for _, cve := range nodeCVEs { - s.False(cve.Orphaned) - s.Nil(cve.OrphanedTime) + s.False(cve.GetOrphaned()) + s.Nil(cve.GetOrphanedTime()) val, ok := orphanedCveIDToCve[cve.GetId()] s.True(ok) s.Equal(val.GetCveBaseInfo().GetCreatedAt(), cve.GetCveBaseInfo().GetCreatedAt()) diff --git a/central/policy/customresource/custom_resource_test.go b/central/policy/customresource/custom_resource_test.go index 94cf6c6f9e545..b5462769f16bb 100644 --- a/central/policy/customresource/custom_resource_test.go +++ b/central/policy/customresource/custom_resource_test.go @@ -24,6 +24,29 @@ func TestConvertToCR(t *testing.T) { assert.YAMLEq(t, templateFile, converted) } +func TestConvertExcludeByKind(t *testing.T) { + policy := fixtures.GetPolicy() + policy.Exclusions = []*storage.Exclusion{ + { + Name: "skip-batch", + Matcher: &storage.Exclusion_ExcludeByKind_{ + ExcludeByKind: &storage.Exclusion_ExcludeByKind{ + Kinds: []storage.Exclusion_WorkloadKind{ + storage.Exclusion_CRON_JOB, + storage.Exclusion_JOB, + }, + }, + }, + }, + } + cr := ConvertPolicyToCustomResource(policy) + require.Len(t, cr.SecurityPolicySpec.Exclusions, 1) + exclusion := cr.SecurityPolicySpec.Exclusions[0] + require.NotNil(t, exclusion.ExcludeByKind) + assert.Equal(t, []string{"CRON_JOB", "JOB"}, exclusion.ExcludeByKind.Kinds) + assert.Nil(t, exclusion.Deployment) +} + func getTestPolicy() *storage.Policy { p := fixtures.GetPolicy() p.Notifiers = []string{ @@ -45,8 +68,7 @@ func getTestPolicy() *storage.Policy { } p.Exclusions = []*storage.Exclusion{ { - Name: "exclusionName1", - Deployment: &storage.Exclusion_Deployment{ + Name: "exclusionName1", Matcher: &storage.Exclusion_Deployment_{Deployment: &storage.Exclusion_Deployment{ Name: "deployment1", Scope: &storage.Scope{ Cluster: "cluster1", @@ -56,12 +78,10 @@ func getTestPolicy() *storage.Policy { Value: "value1", }, }, - }, - Expiration: protocompat.GetProtoTimestampFromSeconds(2334221123), + }}, Expiration: protocompat.GetProtoTimestampFromSeconds(2334221123), }, { - Name: "exclusionName2", - Deployment: &storage.Exclusion_Deployment{ + Name: "exclusionName2", Matcher: &storage.Exclusion_Deployment_{Deployment: &storage.Exclusion_Deployment{ Name: "deployment2", Scope: &storage.Scope{ Cluster: "cluster2", @@ -71,13 +91,16 @@ func getTestPolicy() *storage.Policy { Value: "value2", }, }, - }, + }}, }, } p.Scope = append(p.Scope, &storage.Scope{ ClusterLabel: &storage.Scope_Label{Key: "env", Value: "prod"}, NamespaceLabel: &storage.Scope_Label{Key: "team", Value: "platform"}, }) + p.EvaluationFilter = &storage.EvaluationFilter{ + SkipContainerTypes: []storage.ContainerType{storage.ContainerType_REGULAR, storage.ContainerType_INIT}, + } return p } diff --git a/central/policy/customresource/policy.go b/central/policy/customresource/policy.go index 233b500a5c66c..e4f6dae68366d 100644 --- a/central/policy/customresource/policy.go +++ b/central/policy/customresource/policy.go @@ -23,11 +23,11 @@ func convertScope(p *storage.Scope) *Scope { } return &Scope{ - Cluster: p.Cluster, - Namespace: p.Namespace, - Label: p.Label, - ClusterLabel: p.ClusterLabel, - NamespaceLabel: p.NamespaceLabel, + Cluster: p.GetCluster(), + Namespace: p.GetNamespace(), + Label: p.GetLabel(), + ClusterLabel: p.GetClusterLabel(), + NamespaceLabel: p.GetNamespaceLabel(), } } @@ -44,17 +44,34 @@ func convertExclusion_Deployment(p *storage.Exclusion_Deployment) *Exclusion_Dep } return &Exclusion_Deployment{ - Name: p.Name, - Scope: convertScope(p.Scope), + Name: p.GetName(), + Scope: convertScope(p.GetScope()), + } +} + +// Exclusion_ExcludeByKind represents storage.Exclusion_ExcludeByKind in the Custom Resource. +type Exclusion_ExcludeByKind struct { + Kinds []string `yaml:",omitempty"` +} + +// convertExclusion_ExcludeByKind Converts storage.Exclusion_ExcludeByKind to *Exclusion_ExcludeByKind +func convertExclusion_ExcludeByKind(p *storage.Exclusion_ExcludeByKind) *Exclusion_ExcludeByKind { + if p == nil { + return nil + } + + return &Exclusion_ExcludeByKind{ + Kinds: sliceutils.StringSlice(p.GetKinds()...), } } // Exclusion represents storage.Exclusion in the Custom Resource. type Exclusion struct { - Name string `yaml:",omitempty"` - Deployment *Exclusion_Deployment `yaml:",omitempty"` - Image *storage.Exclusion_Image `yaml:",omitempty"` - Expiration string `yaml:",omitempty"` + Name string `yaml:",omitempty"` + Image *storage.Exclusion_Image `yaml:",omitempty"` + Expiration string `yaml:",omitempty"` + Deployment *Exclusion_Deployment `yaml:",omitempty"` + ExcludeByKind *Exclusion_ExcludeByKind `yaml:"excludeByKind,omitempty"` } // convertExclusion Converts storage.Exclusion to *Exclusion @@ -64,10 +81,11 @@ func convertExclusion(p *storage.Exclusion) *Exclusion { } return &Exclusion{ - Name: p.Name, - Deployment: convertExclusion_Deployment(p.Deployment), - Image: p.Image, - Expiration: timestampToFormatRFC3339(p.Expiration), + Name: p.GetName(), + Image: p.GetImage(), + Expiration: timestampToFormatRFC3339(p.GetExpiration()), + Deployment: convertExclusion_Deployment(p.GetDeployment()), + ExcludeByKind: convertExclusion_ExcludeByKind(p.GetExcludeByKind()), } } @@ -86,10 +104,10 @@ func convertPolicyGroup(p *storage.PolicyGroup) *PolicyGroup { } return &PolicyGroup{ - FieldName: p.FieldName, - BooleanOperator: p.BooleanOperator.String(), - Negate: p.Negate, - Values: p.Values, + FieldName: p.GetFieldName(), + BooleanOperator: p.GetBooleanOperator().String(), + Negate: p.GetNegate(), + Values: p.GetValues(), } } @@ -106,8 +124,24 @@ func convertPolicySection(p *storage.PolicySection) *PolicySection { } return &PolicySection{ - SectionName: p.SectionName, - PolicyGroups: sliceutils.ConvertSlice(p.PolicyGroups, convertPolicyGroup), + SectionName: p.GetSectionName(), + PolicyGroups: sliceutils.ConvertSlice(p.GetPolicyGroups(), convertPolicyGroup), + } +} + +// EvaluationFilter represents storage.EvaluationFilter in the Custom Resource. +type EvaluationFilter struct { + SkipContainerTypes []string `yaml:"skipContainerTypes,omitempty"` +} + +// convertEvaluationFilter Converts storage.EvaluationFilter to *EvaluationFilter +func convertEvaluationFilter(p *storage.EvaluationFilter) *EvaluationFilter { + if p == nil { + return nil + } + + return &EvaluationFilter{ + SkipContainerTypes: sliceutils.StringSlice(p.GetSkipContainerTypes()...), } } @@ -131,7 +165,7 @@ type Policy struct { CriteriaLocked bool `yaml:"criteriaLocked"` MitreVectorsLocked bool `yaml:"mitreVectorsLocked"` IsDefault bool `yaml:"isDefault"` - EvaluationFilter *storage.EvaluationFilter `yaml:"evaluationFilter,omitempty"` + EvaluationFilter *EvaluationFilter `yaml:"evaluationFilter,omitempty"` } // convertPolicy Converts storage.Policy to *Policy @@ -141,25 +175,25 @@ func convertPolicy(p *storage.Policy) *Policy { } return &Policy{ - Name: p.Name, - Description: p.Description, - Rationale: p.Rationale, - Remediation: p.Remediation, - Disabled: p.Disabled, - Categories: p.Categories, - LifecycleStages: sliceutils.StringSlice(p.LifecycleStages...), - EventSource: p.EventSource.String(), - Exclusions: sliceutils.ConvertSlice(p.Exclusions, convertExclusion), - Scope: sliceutils.ConvertSlice(p.Scope, convertScope), - Severity: p.Severity.String(), - EnforcementActions: sliceutils.StringSlice(p.EnforcementActions...), - Notifiers: p.Notifiers, - PolicySections: sliceutils.ConvertSlice(p.PolicySections, convertPolicySection), - MitreAttackVectors: p.MitreAttackVectors, - CriteriaLocked: p.CriteriaLocked, - MitreVectorsLocked: p.MitreVectorsLocked, - IsDefault: p.IsDefault, - EvaluationFilter: p.EvaluationFilter, + Name: p.GetName(), + Description: p.GetDescription(), + Rationale: p.GetRationale(), + Remediation: p.GetRemediation(), + Disabled: p.GetDisabled(), + Categories: p.GetCategories(), + LifecycleStages: sliceutils.StringSlice(p.GetLifecycleStages()...), + EventSource: p.GetEventSource().String(), + Exclusions: sliceutils.ConvertSlice(p.GetExclusions(), convertExclusion), + Scope: sliceutils.ConvertSlice(p.GetScope(), convertScope), + Severity: p.GetSeverity().String(), + EnforcementActions: sliceutils.StringSlice(p.GetEnforcementActions()...), + Notifiers: p.GetNotifiers(), + PolicySections: sliceutils.ConvertSlice(p.GetPolicySections(), convertPolicySection), + MitreAttackVectors: p.GetMitreAttackVectors(), + CriteriaLocked: p.GetCriteriaLocked(), + MitreVectorsLocked: p.GetMitreVectorsLocked(), + IsDefault: p.GetIsDefault(), + EvaluationFilter: convertEvaluationFilter(p.GetEvaluationFilter()), } } diff --git a/central/policy/customresource/testdata/custom_resource.yaml b/central/policy/customresource/testdata/custom_resource.yaml index 4de610ec37a80..bce167fd2d230 100644 --- a/central/policy/customresource/testdata/custom_resource.yaml +++ b/central/policy/customresource/testdata/custom_resource.yaml @@ -147,3 +147,7 @@ spec: criteriaLocked: false mitreVectorsLocked: false isDefault: false + evaluationFilter: + skipContainerTypes: + - INIT + - REGULAR diff --git a/central/policy/datastore/datastore_impl_test.go b/central/policy/datastore/datastore_impl_test.go index 607c4fb1f32fc..e8008bf94966c 100644 --- a/central/policy/datastore/datastore_impl_test.go +++ b/central/policy/datastore/datastore_impl_test.go @@ -318,12 +318,11 @@ func (s *PolicyDatastoreTestSuite) TestRemoveScopesAndNotifiers() { }, }, Exclusions: []*storage.Exclusion{ - { - Deployment: &storage.Exclusion_Deployment{ - Scope: &storage.Scope{ - Cluster: clusterName, - }, + {Matcher: &storage.Exclusion_Deployment_{Deployment: &storage.Exclusion_Deployment{ + Scope: &storage.Scope{ + Cluster: clusterName, }, + }}, }, }, Notifiers: []string{notifierName}, @@ -369,12 +368,11 @@ func (s *PolicyDatastoreTestSuite) TestDoesNotRemoveScopesAndNotifiers() { }, }, Exclusions: []*storage.Exclusion{ - { - Deployment: &storage.Exclusion_Deployment{ - Scope: &storage.Scope{ - Cluster: clusterID, - }, + {Matcher: &storage.Exclusion_Deployment_{Deployment: &storage.Exclusion_Deployment{ + Scope: &storage.Scope{ + Cluster: clusterID, }, + }}, }, }, Notifiers: []string{notifierName}, diff --git a/central/policy/matcher/cluster.go b/central/policy/matcher/cluster.go index b1b57f1bf151a..7196d41589c9b 100644 --- a/central/policy/matcher/cluster.go +++ b/central/policy/matcher/cluster.go @@ -48,6 +48,12 @@ func (m *clusterMatcher) anyExclusionMatches(exclusions []*storage.Exclusion) bo } func (m *clusterMatcher) exclusionMatches(exclusion *storage.Exclusion) bool { + // A kind exclusion does not take the policy off the cluster. Image-only exclusions have no + // deployment scope, and a nil scope would otherwise match every cluster. + if exclusion.GetDeployment() == nil { + return false + } + cs, err := scopecomp.CompileScope(exclusion.GetDeployment().GetScope(), nil, nil) if err != nil { utils.Should(errors.Wrap(err, "could not compile excluded scopes")) diff --git a/central/policy/matcher/cluster_test.go b/central/policy/matcher/cluster_test.go index 6ac6fe74ad1aa..e0b5532d19e4e 100644 --- a/central/policy/matcher/cluster_test.go +++ b/central/policy/matcher/cluster_test.go @@ -138,12 +138,11 @@ func TestClusterMatcherWithExclusion(t *testing.T) { }, }, Exclusions: []*storage.Exclusion{ - { - Deployment: &storage.Exclusion_Deployment{ - Scope: &storage.Scope{ - Namespace: "ns.*", - }, + {Matcher: &storage.Exclusion_Deployment_{Deployment: &storage.Exclusion_Deployment{ + Scope: &storage.Scope{ + Namespace: "ns.*", }, + }}, }, }, }, @@ -167,12 +166,11 @@ func TestClusterMatcherWithExclusion(t *testing.T) { }, }, Exclusions: []*storage.Exclusion{ - { - Deployment: &storage.Exclusion_Deployment{ - Scope: &storage.Scope{ - Namespace: "ns.*", - }, + {Matcher: &storage.Exclusion_Deployment_{Deployment: &storage.Exclusion_Deployment{ + Scope: &storage.Scope{ + Namespace: "ns.*", }, + }}, }, }, }, @@ -195,12 +193,11 @@ func TestClusterMatcherWithExclusion(t *testing.T) { }, }, Exclusions: []*storage.Exclusion{ - { - Deployment: &storage.Exclusion_Deployment{ - Scope: &storage.Scope{ - Cluster: "cluster1", - }, + {Matcher: &storage.Exclusion_Deployment_{Deployment: &storage.Exclusion_Deployment{ + Scope: &storage.Scope{ + Cluster: "cluster1", }, + }}, }, }, }, @@ -218,12 +215,11 @@ func TestClusterMatcherWithExclusion(t *testing.T) { }, policy: &storage.Policy{ Exclusions: []*storage.Exclusion{ - { - Deployment: &storage.Exclusion_Deployment{ - Scope: &storage.Scope{ - Namespace: "ns2.*", - }, + {Matcher: &storage.Exclusion_Deployment_{Deployment: &storage.Exclusion_Deployment{ + Scope: &storage.Scope{ + Namespace: "ns2.*", }, + }}, }, }, }, diff --git a/central/policy/matcher/deployment.go b/central/policy/matcher/deployment.go index 6058438bc79e8..86e035585a65f 100644 --- a/central/policy/matcher/deployment.go +++ b/central/policy/matcher/deployment.go @@ -56,6 +56,15 @@ func (m *deploymentMatcher) anyExclusionMatches(ctx context.Context, exclusions } func (m *deploymentMatcher) exclusionMatches(ctx context.Context, exclusion *storage.Exclusion) bool { + if !appliesToDeployments(exclusion) { + return false + } + // Kind exclusions have no deployment scope. Falling through would treat the nil scope as + // matching every deployment. + if exclusion.GetExcludeByKind() != nil { + return kindExclusionMatches(exclusion, m.deployment.GetType()) + } + // If excluded scope does not match the deployment then no need to check for deployment name if !m.scopeMatches(ctx, exclusion.GetDeployment().GetScope()) { return false diff --git a/central/policy/matcher/deployment_test.go b/central/policy/matcher/deployment_test.go index 5512d0339120d..1221d49a70e1b 100644 --- a/central/policy/matcher/deployment_test.go +++ b/central/policy/matcher/deployment_test.go @@ -101,12 +101,11 @@ func TestDeploymentWithExclusion(t *testing.T) { }, }, Exclusions: []*storage.Exclusion{ - { - Deployment: &storage.Exclusion_Deployment{ - Scope: &storage.Scope{ - Namespace: "ns.*", - }, + {Matcher: &storage.Exclusion_Deployment_{Deployment: &storage.Exclusion_Deployment{ + Scope: &storage.Scope{ + Namespace: "ns.*", }, + }}, }, }, }, @@ -126,13 +125,12 @@ func TestDeploymentWithExclusion(t *testing.T) { }, }, Exclusions: []*storage.Exclusion{ - { - Deployment: &storage.Exclusion_Deployment{ - Name: "deployment2", - Scope: &storage.Scope{ - Namespace: "ns.*", - }, + {Matcher: &storage.Exclusion_Deployment_{Deployment: &storage.Exclusion_Deployment{ + Name: "deployment2", + Scope: &storage.Scope{ + Namespace: "ns.*", }, + }}, }, }, }, @@ -151,18 +149,16 @@ func TestDeploymentWithExclusion(t *testing.T) { }, }, Exclusions: []*storage.Exclusion{ - { - Deployment: &storage.Exclusion_Deployment{ - Name: "deployment2", - Scope: &storage.Scope{ - Namespace: "ns1", - }, + {Matcher: &storage.Exclusion_Deployment_{Deployment: &storage.Exclusion_Deployment{ + Name: "deployment2", + Scope: &storage.Scope{ + Namespace: "ns1", }, + }}, }, - { - Deployment: &storage.Exclusion_Deployment{ - Name: "deployment1", - }, + {Matcher: &storage.Exclusion_Deployment_{Deployment: &storage.Exclusion_Deployment{ + Name: "deployment1", + }}, }, }, }, @@ -181,20 +177,18 @@ func TestDeploymentWithExclusion(t *testing.T) { }, }, Exclusions: []*storage.Exclusion{ - { - Deployment: &storage.Exclusion_Deployment{ - Name: "deployment2", - Scope: &storage.Scope{ - Namespace: "ns1", - }, + {Matcher: &storage.Exclusion_Deployment_{Deployment: &storage.Exclusion_Deployment{ + Name: "deployment2", + Scope: &storage.Scope{ + Namespace: "ns1", }, + }}, }, - { - Deployment: &storage.Exclusion_Deployment{ - Scope: &storage.Scope{ - Namespace: "ns1", - }, + {Matcher: &storage.Exclusion_Deployment_{Deployment: &storage.Exclusion_Deployment{ + Scope: &storage.Scope{ + Namespace: "ns1", }, + }}, }, }, }, diff --git a/central/policy/matcher/matcher.go b/central/policy/matcher/matcher.go index a80cd6c64ad54..c94097cd287c7 100644 --- a/central/policy/matcher/matcher.go +++ b/central/policy/matcher/matcher.go @@ -4,6 +4,8 @@ import ( "context" "github.com/stackrox/rox/generated/storage" + "github.com/stackrox/rox/pkg/features" + "github.com/stackrox/rox/pkg/kubernetes" ) // Matcher provides functionality to evaluate whether or not policies are applicable to an entity. @@ -11,3 +13,32 @@ type Matcher interface { FilterApplicablePolicies(ctx context.Context, policies []*storage.Policy) (applicable []*storage.Policy, notApplicable []*storage.Policy) IsPolicyApplicable(ctx context.Context, policy *storage.Policy) bool } + +// appliesToDeployments reports whether the exclusion can take a deployment out of policy scope. +// A deployment name/scope exclusion and a workload-kind exclusion both can. An image-only exclusion +// cannot: its nil deployment scope matches every deployment and would make the policy look +// inapplicable everywhere. +func appliesToDeployments(exclusion *storage.Exclusion) bool { + return exclusion.GetDeployment() != nil || exclusion.GetExcludeByKind() != nil +} + +// kindExclusionMatches reports whether a kind exclusion excludes this deployment. +// The feature flag gates it. A disabled flag leaves the policy applicable, matching detection. +func kindExclusionMatches(exclusion *storage.Exclusion, deploymentType string) bool { + if !features.PolicyWorkloadKindExclusion.Enabled() { + return false + } + for _, kind := range exclusion.GetExcludeByKind().GetKinds() { + switch kind { + case storage.Exclusion_CRON_JOB: + if deploymentType == kubernetes.CronJob { + return true + } + case storage.Exclusion_JOB: + if deploymentType == kubernetes.Job { + return true + } + } + } + return false +} diff --git a/central/policy/matcher/matcher_test.go b/central/policy/matcher/matcher_test.go new file mode 100644 index 0000000000000..0642a5308378c --- /dev/null +++ b/central/policy/matcher/matcher_test.go @@ -0,0 +1,79 @@ +package matcher + +import ( + "context" + "testing" + + "github.com/stackrox/rox/generated/storage" + "github.com/stackrox/rox/pkg/features" + "github.com/stackrox/rox/pkg/kubernetes" + "github.com/stackrox/rox/pkg/testutils" + "github.com/stretchr/testify/assert" +) + +// TestImageOnlyExclusionDoesNotExcludeEntities checks that an image-only exclusion leaves the policy +// applicable to every deployment, namespace, and cluster, while a deployment exclusion still excludes. +func TestImageOnlyExclusionDoesNotExcludeEntities(t *testing.T) { + ctx := context.Background() + deployment := &storage.Deployment{Name: "payments", ClusterId: "cluster1", Namespace: "shop"} + namespace := &storage.NamespaceMetadata{Name: "shop", ClusterId: "cluster1"} + cluster := &storage.Cluster{Id: "cluster1"} + + matchers := map[string]Matcher{ + "deployment": NewDeploymentMatcher(deployment, nil, nil), + "namespace": NewNamespaceMatcher(namespace), + "cluster": NewClusterMatcher(cluster, []*storage.NamespaceMetadata{namespace}), + } + + imageOnly := &storage.Policy{Exclusions: []*storage.Exclusion{ + {Image: &storage.Exclusion_Image{Name: "docker.io/library/nginx"}}, + }} + clusterExclusion := &storage.Policy{Exclusions: []*storage.Exclusion{ + {Matcher: &storage.Exclusion_Deployment_{Deployment: &storage.Exclusion_Deployment{Scope: &storage.Scope{Cluster: "cluster1"}}}}, + }} + + for name, m := range matchers { + t.Run(name, func(t *testing.T) { + assert.True(t, m.IsPolicyApplicable(ctx, imageOnly), "image-only exclusion should not exclude") + assert.False(t, m.IsPolicyApplicable(ctx, clusterExclusion), "cluster-scoped exclusion should exclude") + }) + } +} + +func TestKindExclusionExcludesMatchingDeploymentsOnly(t *testing.T) { + testutils.MustUpdateFeature(t, features.PolicyWorkloadKindExclusion, true) + ctx := context.Background() + namespace := &storage.NamespaceMetadata{Name: "shop", ClusterId: "cluster1"} + cluster := &storage.Cluster{Id: "cluster1"} + policy := &storage.Policy{Exclusions: []*storage.Exclusion{{ + Matcher: &storage.Exclusion_ExcludeByKind_{ + ExcludeByKind: &storage.Exclusion_ExcludeByKind{ + Kinds: []storage.Exclusion_WorkloadKind{storage.Exclusion_JOB, storage.Exclusion_CRON_JOB}, + }, + }, + }}} + + job := &storage.Deployment{Name: "batch", Type: kubernetes.Job, ClusterId: "cluster1", Namespace: "shop"} + cronJob := &storage.Deployment{Name: "nightly", Type: kubernetes.CronJob, ClusterId: "cluster1", Namespace: "shop"} + deploy := &storage.Deployment{Name: "web", Type: kubernetes.Deployment, ClusterId: "cluster1", Namespace: "shop"} + + assert.False(t, NewDeploymentMatcher(job, nil, nil).IsPolicyApplicable(ctx, policy)) + assert.False(t, NewDeploymentMatcher(cronJob, nil, nil).IsPolicyApplicable(ctx, policy)) + assert.True(t, NewDeploymentMatcher(deploy, nil, nil).IsPolicyApplicable(ctx, policy)) + assert.True(t, NewNamespaceMatcher(namespace).IsPolicyApplicable(ctx, policy)) + assert.True(t, NewClusterMatcher(cluster, []*storage.NamespaceMetadata{namespace}).IsPolicyApplicable(ctx, policy)) +} + +func TestKindExclusionFlagOffLeavesPolicyApplicable(t *testing.T) { + testutils.MustUpdateFeature(t, features.PolicyWorkloadKindExclusion, false) + job := &storage.Deployment{Name: "batch", Type: kubernetes.Job, ClusterId: "cluster1", Namespace: "shop"} + policy := &storage.Policy{Exclusions: []*storage.Exclusion{{ + Matcher: &storage.Exclusion_ExcludeByKind_{ + ExcludeByKind: &storage.Exclusion_ExcludeByKind{ + Kinds: []storage.Exclusion_WorkloadKind{storage.Exclusion_JOB}, + }, + }, + }}} + + assert.True(t, NewDeploymentMatcher(job, nil, nil).IsPolicyApplicable(context.Background(), policy)) +} diff --git a/central/policy/matcher/namespace.go b/central/policy/matcher/namespace.go index 1cf53532e9f72..6bd6ee7e7f2aa 100644 --- a/central/policy/matcher/namespace.go +++ b/central/policy/matcher/namespace.go @@ -46,6 +46,12 @@ func (m *namespaceMatcher) anyExclusionMatches(exclusions []*storage.Exclusion) } func (m *namespaceMatcher) exclusionMatches(exclusion *storage.Exclusion) bool { + // A kind exclusion does not take the policy off the namespace: other workload kinds there + // are still in scope. Image-only exclusions have no deployment scope, and a nil scope would + // otherwise match every namespace. + if exclusion.GetDeployment() == nil { + return false + } return m.scopeMatches(exclusion.GetDeployment().GetScope()) } diff --git a/central/policy/matcher/namespace_test.go b/central/policy/matcher/namespace_test.go index 4ca67b8d2755c..23bc5edb1b26a 100644 --- a/central/policy/matcher/namespace_test.go +++ b/central/policy/matcher/namespace_test.go @@ -114,12 +114,11 @@ func TestNamespaceMatcherWithWhitespace(t *testing.T) { }, }, Exclusions: []*storage.Exclusion{ - { - Deployment: &storage.Exclusion_Deployment{ - Scope: &storage.Scope{ - Namespace: "ns.*", - }, + {Matcher: &storage.Exclusion_Deployment_{Deployment: &storage.Exclusion_Deployment{ + Scope: &storage.Scope{ + Namespace: "ns.*", }, + }}, }, }, }, @@ -137,13 +136,12 @@ func TestNamespaceMatcherWithWhitespace(t *testing.T) { }, }, Exclusions: []*storage.Exclusion{ - { - Deployment: &storage.Exclusion_Deployment{ - Scope: &storage.Scope{ - Cluster: "cluster1", - Namespace: "ns1", - }, + {Matcher: &storage.Exclusion_Deployment_{Deployment: &storage.Exclusion_Deployment{ + Scope: &storage.Scope{ + Cluster: "cluster1", + Namespace: "ns1", }, + }}, }, }, }, @@ -161,12 +159,11 @@ func TestNamespaceMatcherWithWhitespace(t *testing.T) { }, }, Exclusions: []*storage.Exclusion{ - { - Deployment: &storage.Exclusion_Deployment{ - Scope: &storage.Scope{ - Cluster: "cluster1", - }, + {Matcher: &storage.Exclusion_Deployment_{Deployment: &storage.Exclusion_Deployment{ + Scope: &storage.Scope{ + Cluster: "cluster1", }, + }}, }, }, }, diff --git a/central/policy/service/validator.go b/central/policy/service/validator.go index 90f6dc87ddc97..38c93dec4bb9a 100644 --- a/central/policy/service/validator.go +++ b/central/policy/service/validator.go @@ -286,7 +286,7 @@ func (s *policyValidator) validateExclusions(policy *storage.Policy) error { } func (s *policyValidator) validateExclusion(policy *storage.Policy, exclusion *storage.Exclusion) error { - if exclusion.GetDeployment() == nil && exclusion.GetImage() == nil { + if exclusion.GetDeployment() == nil && exclusion.GetImage() == nil && exclusion.GetExcludeByKind() == nil { return errors.New("all excluded scopes must have some criteria to match on") } if exclusion.GetDeployment() != nil { @@ -297,6 +297,17 @@ func (s *policyValidator) validateExclusion(policy *storage.Policy, exclusion *s return err } } + if exclusion.GetExcludeByKind() != nil { + if !features.PolicyWorkloadKindExclusion.Enabled() { + return errors.New("exclude_by_kind requires feature flag ROX_POLICY_WORKLOAD_KIND_EXCLUSION to be enabled") + } + if !policies.AppliesAtDeployTime(policy) && !policies.AppliesAtRunTime(policy) { + return errors.New("excluding by workload kind is only valid during the DEPLOY and RUNTIME lifecycles") + } + if err := validateExcludeByKind(exclusion.GetExcludeByKind()); err != nil { + return err + } + } if exclusion.GetImage() != nil { if !policies.AppliesAtBuildTime(policy) { return errors.New("excluding an image is only valid during the BUILD lifecycle") @@ -308,6 +319,22 @@ func (s *policyValidator) validateExclusion(policy *storage.Policy, exclusion *s return nil } +func validateExcludeByKind(excludeByKind *storage.Exclusion_ExcludeByKind) error { + kinds := excludeByKind.GetKinds() + if len(kinds) == 0 { + return errors.New("exclude_by_kind must specify at least one workload kind") + } + for _, kind := range kinds { + if kind == storage.Exclusion_WORKLOAD_KIND_UNSPECIFIED { + return errors.New("exclude_by_kind cannot include WORKLOAD_KIND_UNSPECIFIED") + } + if kind != storage.Exclusion_CRON_JOB && kind != storage.Exclusion_JOB { + return errors.Errorf("exclude_by_kind contains unknown workload kind %s", kind) + } + } + return nil +} + func (s *policyValidator) validateDeploymentExclusion(exclusion *storage.Exclusion) error { deployment := exclusion.GetDeployment() if deployment.GetScope() == nil && deployment.GetName() == "" { @@ -510,14 +537,15 @@ func (s *policyValidator) validateEvaluationFilter(policy *storage.Policy) error if len(policy.GetEvaluationFilter().GetSkipContainerTypes()) == 0 { return nil } - if policies.AppliesAtBuildTime(policy) && !policies.AppliesAtDeployTime(policy) && !policies.AppliesAtRunTime(policy) { - return errors.New("container type filters in the evaluation filter are not applicable to build-only policies") - } - if s.isAuditEventPolicy(policy) { - return errors.New("container type filters in the evaluation filter are not applicable to audit log event policies") - } - if s.isNodeEventPolicy(policy) { - return errors.New("container type filters in the evaluation filter are not applicable to node event policies") + if !containerTypeFilterApplicable(policy) { + return errors.New("container type filters apply only to deploy-time policies and runtime policies with a deployment event source") } return nil } + +func containerTypeFilterApplicable(policy *storage.Policy) bool { + if policies.AppliesAtDeployTime(policy) { + return true + } + return policies.AppliesAtRunTime(policy) && policy.GetEventSource() == storage.EventSource_DEPLOYMENT_EVENT +} diff --git a/central/policy/service/validator_test.go b/central/policy/service/validator_test.go index 1e317de9785e4..784601206355f 100644 --- a/central/policy/service/validator_test.go +++ b/central/policy/service/validator_test.go @@ -614,9 +614,7 @@ func (s *PolicyValidatorTestSuite) TestValidateExclusions() { deployment := &storage.Exclusion_Deployment{ Name: "that phat cluster", } - deploymentExclusion := &storage.Exclusion{ - Deployment: deployment, - } + deploymentExclusion := &storage.Exclusion{Matcher: &storage.Exclusion_Deployment_{Deployment: deployment}} policy = &storage.Policy{ LifecycleStages: []storage.LifecycleStage{ storage.LifecycleStage_DEPLOY, @@ -661,14 +659,13 @@ func (s *PolicyValidatorTestSuite) TestValidateExclusions() { err = s.validator.validateExclusions(policy) s.Error(err, "excluded scope requires either container or deployment configuration") - emptyLabelExclusion := &storage.Exclusion{ - Deployment: &storage.Exclusion_Deployment{ - Scope: &storage.Scope{ - Label: &storage.Scope_Label{ - Key: "", - }, + emptyLabelExclusion := &storage.Exclusion{Matcher: &storage.Exclusion_Deployment_{Deployment: &storage.Exclusion_Deployment{ + Scope: &storage.Scope{ + Label: &storage.Scope_Label{ + Key: "", }, }, + }}, } policy = &storage.Policy{ Exclusions: []*storage.Exclusion{ @@ -678,15 +675,14 @@ func (s *PolicyValidatorTestSuite) TestValidateExclusions() { err = s.validator.validateExclusions(policy) s.Error(err, "label regex in excluded scope, if not nil, must be non-empty") - anyKeyLabelExclusion := &storage.Exclusion{ - Deployment: &storage.Exclusion_Deployment{ - Scope: &storage.Scope{ - Label: &storage.Scope_Label{ - Key: ".*", - Value: "", - }, + anyKeyLabelExclusion := &storage.Exclusion{Matcher: &storage.Exclusion_Deployment_{Deployment: &storage.Exclusion_Deployment{ + Scope: &storage.Scope{ + Label: &storage.Scope_Label{ + Key: ".*", + Value: "", }, }, + }}, } policy = &storage.Policy{ LifecycleStages: []storage.LifecycleStage{ @@ -698,15 +694,14 @@ func (s *PolicyValidatorTestSuite) TestValidateExclusions() { } s.NoError(s.validator.validateExclusions(policy)) - anyLabelExclusion := &storage.Exclusion{ - Deployment: &storage.Exclusion_Deployment{ - Scope: &storage.Scope{ - Label: &storage.Scope_Label{ - Key: ".*", - Value: ".*", - }, + anyLabelExclusion := &storage.Exclusion{Matcher: &storage.Exclusion_Deployment_{Deployment: &storage.Exclusion_Deployment{ + Scope: &storage.Scope{ + Label: &storage.Scope_Label{ + Key: ".*", + Value: ".*", }, }, + }}, } policy = &storage.Policy{ LifecycleStages: []storage.LifecycleStage{ @@ -719,6 +714,79 @@ func (s *PolicyValidatorTestSuite) TestValidateExclusions() { s.NoError(s.validator.validateExclusions(policy)) } +func (s *PolicyValidatorTestSuite) TestValidateExcludeByKind() { + kindExclusion := func(kinds ...storage.Exclusion_WorkloadKind) *storage.Exclusion { + return &storage.Exclusion{ + Matcher: &storage.Exclusion_ExcludeByKind_{ + ExcludeByKind: &storage.Exclusion_ExcludeByKind{Kinds: kinds}, + }, + } + } + deployPolicy := func(exclusion *storage.Exclusion) *storage.Policy { + return &storage.Policy{ + LifecycleStages: []storage.LifecycleStage{storage.LifecycleStage_DEPLOY}, + Exclusions: []*storage.Exclusion{exclusion}, + } + } + + s.ErrorContains(s.validator.validateExclusions(deployPolicy(kindExclusion(storage.Exclusion_JOB))), "ROX_POLICY_WORKLOAD_KIND_EXCLUSION") + + testutils.MustUpdateFeature(s.T(), features.PolicyWorkloadKindExclusion, true) + + for name, tc := range map[string]struct { + policy *storage.Policy + errContains string + }{ + "JOB is valid": { + policy: deployPolicy(kindExclusion(storage.Exclusion_JOB)), + }, + "CRON_JOB is valid": { + policy: deployPolicy(kindExclusion(storage.Exclusion_CRON_JOB)), + }, + "JOB and CRON_JOB are valid": { + policy: deployPolicy(kindExclusion(storage.Exclusion_CRON_JOB, storage.Exclusion_JOB)), + }, + "empty kind list is rejected": { + policy: deployPolicy(kindExclusion()), + errContains: "at least one workload kind", + }, + "UNSPECIFIED is rejected": { + policy: deployPolicy(kindExclusion(storage.Exclusion_WORKLOAD_KIND_UNSPECIFIED)), + errContains: "WORKLOAD_KIND_UNSPECIFIED", + }, + "unknown kind is rejected": { + policy: deployPolicy(kindExclusion(storage.Exclusion_WorkloadKind(99))), + errContains: "unknown workload kind", + }, + "mixed valid and unknown kinds are rejected": { + policy: deployPolicy(kindExclusion(storage.Exclusion_JOB, storage.Exclusion_WorkloadKind(99))), + errContains: "unknown workload kind", + }, + "RUNTIME lifecycle is valid": { + policy: &storage.Policy{ + LifecycleStages: []storage.LifecycleStage{storage.LifecycleStage_RUNTIME}, + Exclusions: []*storage.Exclusion{kindExclusion(storage.Exclusion_JOB)}, + }, + }, + "BUILD lifecycle is rejected": { + policy: &storage.Policy{ + LifecycleStages: []storage.LifecycleStage{storage.LifecycleStage_BUILD}, + Exclusions: []*storage.Exclusion{kindExclusion(storage.Exclusion_JOB)}, + }, + errContains: "DEPLOY and RUNTIME", + }, + } { + s.T().Run(name, func(t *testing.T) { + err := s.validator.validateExclusions(tc.policy) + if tc.errContains == "" { + assert.NoError(t, err) + } else { + assert.ErrorContains(t, err, tc.errContains) + } + }) + } +} + func (s *PolicyValidatorTestSuite) TestValidateExclusionRejectsLabels() { for name, tc := range map[string]struct { exclusion *storage.Exclusion @@ -726,44 +794,40 @@ func (s *PolicyValidatorTestSuite) TestValidateExclusionRejectsLabels() { errContains string }{ "cluster_label on exclusion scope is rejected": { - exclusion: &storage.Exclusion{ - Deployment: &storage.Exclusion_Deployment{ - Scope: &storage.Scope{ - ClusterLabel: &storage.Scope_Label{Key: "env", Value: "prod"}, - }, + exclusion: &storage.Exclusion{Matcher: &storage.Exclusion_Deployment_{Deployment: &storage.Exclusion_Deployment{ + Scope: &storage.Scope{ + ClusterLabel: &storage.Scope_Label{Key: "env", Value: "prod"}, }, + }}, }, errExpected: true, errContains: "cluster labels", }, "namespace_label on exclusion scope is rejected": { - exclusion: &storage.Exclusion{ - Deployment: &storage.Exclusion_Deployment{ - Scope: &storage.Scope{ - NamespaceLabel: &storage.Scope_Label{Key: "team", Value: "backend"}, - }, + exclusion: &storage.Exclusion{Matcher: &storage.Exclusion_Deployment_{Deployment: &storage.Exclusion_Deployment{ + Scope: &storage.Scope{ + NamespaceLabel: &storage.Scope_Label{Key: "team", Value: "backend"}, }, + }}, }, errExpected: true, errContains: "namespace labels", }, "deployment label on exclusion scope is allowed": { - exclusion: &storage.Exclusion{ - Deployment: &storage.Exclusion_Deployment{ - Scope: &storage.Scope{ - Label: &storage.Scope_Label{Key: "app", Value: "nginx"}, - }, + exclusion: &storage.Exclusion{Matcher: &storage.Exclusion_Deployment_{Deployment: &storage.Exclusion_Deployment{ + Scope: &storage.Scope{ + Label: &storage.Scope_Label{Key: "app", Value: "nginx"}, }, + }}, }, errExpected: false, }, "cluster ID on exclusion scope is allowed": { - exclusion: &storage.Exclusion{ - Deployment: &storage.Exclusion_Deployment{ - Scope: &storage.Scope{ - Cluster: "cluster-1", - }, + exclusion: &storage.Exclusion{Matcher: &storage.Exclusion_Deployment_{Deployment: &storage.Exclusion_Deployment{ + Scope: &storage.Scope{ + Cluster: "cluster-1", }, + }}, }, errExpected: false, }, @@ -1391,19 +1455,25 @@ func (s *PolicyValidatorTestSuite) TestValidateEvaluationFilter() { lifecycleStages: []storage.LifecycleStage{storage.LifecycleStage_DEPLOY}, skipContainerTypes: []storage.ContainerType{storage.ContainerType_INIT}, }, + "deploy with skip regular is allowed": { + lifecycleStages: []storage.LifecycleStage{storage.LifecycleStage_DEPLOY}, + skipContainerTypes: []storage.ContainerType{storage.ContainerType_REGULAR}, + }, "build + deploy with skip init is allowed": { lifecycleStages: []storage.LifecycleStage{storage.LifecycleStage_BUILD, storage.LifecycleStage_DEPLOY}, skipContainerTypes: []storage.ContainerType{storage.ContainerType_INIT}, }, - "runtime with skip init is allowed": { - lifecycleStages: []storage.LifecycleStage{storage.LifecycleStage_RUNTIME}, - skipContainerTypes: []storage.ContainerType{storage.ContainerType_INIT}, - }, "build-only with no filter is allowed": { lifecycleStages: []storage.LifecycleStage{storage.LifecycleStage_BUILD}, }, - "no lifecycle stages with skip init is allowed": { + "runtime without event source with skip init is rejected": { + lifecycleStages: []storage.LifecycleStage{storage.LifecycleStage_RUNTIME}, + skipContainerTypes: []storage.ContainerType{storage.ContainerType_INIT}, + expectError: true, + }, + "no lifecycle stages with skip init is rejected": { skipContainerTypes: []storage.ContainerType{storage.ContainerType_INIT}, + expectError: true, }, "audit log event with skip init is rejected": { lifecycleStages: []storage.LifecycleStage{storage.LifecycleStage_RUNTIME}, @@ -1422,6 +1492,11 @@ func (s *PolicyValidatorTestSuite) TestValidateEvaluationFilter() { eventSource: storage.EventSource_DEPLOYMENT_EVENT, skipContainerTypes: []storage.ContainerType{storage.ContainerType_INIT}, }, + "deployment event with skip regular is allowed": { + lifecycleStages: []storage.LifecycleStage{storage.LifecycleStage_RUNTIME}, + eventSource: storage.EventSource_DEPLOYMENT_EVENT, + skipContainerTypes: []storage.ContainerType{storage.ContainerType_REGULAR}, + }, } for name, tc := range tests { diff --git a/central/processlisteningonport/datastore/datastore.go b/central/processlisteningonport/datastore/datastore.go index d7744f88b2b32..f7308a5a418ed 100644 --- a/central/processlisteningonport/datastore/datastore.go +++ b/central/processlisteningonport/datastore/datastore.go @@ -29,12 +29,9 @@ type DataStore interface { deployment string, ) ([]*storage.ProcessListeningOnPort, error) WalkAll(ctx context.Context, fn WalkFn) error - RemoveProcessListeningOnPort(ctx context.Context, ids []string) error RemovePlopsByPod(ctx context.Context, id string) error PruneOrphanedPLOPs(ctx context.Context, orphanWindow time.Duration) int64 - PruneOrphanedPLOPsByProcessIndicators(ctx context.Context, orphanWindow time.Duration) RemovePLOPsWithoutProcessIndicatorOrProcessInfo(ctx context.Context) (int64, error) - RemovePLOPsWithoutPodUID(ctx context.Context) (int64, error) } // New creates a data store object to access the database. Since some diff --git a/central/processlisteningonport/datastore/datastore_impl.go b/central/processlisteningonport/datastore/datastore_impl.go index cd8219320a726..3783a97139f7b 100644 --- a/central/processlisteningonport/datastore/datastore_impl.go +++ b/central/processlisteningonport/datastore/datastore_impl.go @@ -306,25 +306,6 @@ func (ds *datastoreImpl) WalkAll(ctx context.Context, fn WalkFn) error { return ds.storage.Walk(ctx, fn) } -func (ds *datastoreImpl) RemoveProcessListeningOnPort(ctx context.Context, ids []string) error { - if ok, err := plopSAC.WriteAllowed(ctx); err != nil { - return err - } else if !ok { - return sac.ErrResourceAccessDenied - } - - return ds.removePLOP(ctx, ids) -} - -func (ds *datastoreImpl) removePLOP(ctx context.Context, ids []string) error { - - if len(ids) == 0 { - return nil - } - - return ds.storage.DeleteMany(ctx, ids) -} - func (ds *datastoreImpl) fetchExistingPLOPs( ctx context.Context, indicatorIds []string, @@ -596,27 +577,6 @@ func (ds *datastoreImpl) PruneOrphanedPLOPs(ctx context.Context, orphanWindow ti return commandTag.RowsAffected() } -// PruneOrphanedPLOPsByProcessIndicators prunes PLOPs that match process indicators without pods -func (ds *datastoreImpl) PruneOrphanedPLOPsByProcessIndicators(ctx context.Context, orphanWindow time.Duration) { - // TODO(ROX-22443): Once it is guaranteed that all listening endpoints have PodUIDs, remove this function - ds.mutex.Lock() - defer ds.mutex.Unlock() - - // Delete processes listening on ports orphaned because process indicators are orphaned due to - // missing deployments - query := fmt.Sprintf(deleteOrphanedPLOPDeploymentsAndPI, int(orphanWindow.Minutes())) - if _, err := ds.pool.Exec(ctx, query); err != nil { - log.Errorf("failed to prune process listening on ports by deployment: %v", err) - } - - // Delete processes listening on ports orphaned because process indicators are orphaned due to - // missing pods. - query = fmt.Sprintf(deleteOrphanedPLOPPods, int(orphanWindow.Minutes())) - if _, err := ds.pool.Exec(ctx, query); err != nil { - log.Errorf("failed to prune process listening on ports by pods: %v", err) - } -} - func (ds *datastoreImpl) readRowsToFindPLOPsWithNoProcessInformation(rows pgx.Rows) ([]string, error) { var ids []string @@ -672,89 +632,3 @@ func (ds *datastoreImpl) RemovePLOPsWithoutProcessIndicatorOrProcessInfo(ctx con return int64(len(plopsToDelete)), nil } - -// Removes PLOPs without poduids between a range of ids. -func (ds *datastoreImpl) removePLOPsWithoutPodUIDOnePage(ctx context.Context, prevId string, nextId string) (int64, error) { - ds.mutex.Lock() - defer ds.mutex.Unlock() - - query := fmt.Sprintf(deletePLOPsWithoutPoduidInPage, prevId, nextId) - commandTag, err := ds.pool.Exec(ctx, query) - - if err != nil { - return 0, err - } - - return commandTag.RowsAffected(), nil -} - -// Given a set of rows with ids, returns the id of the last row. This is useful for pagination. -func (ds *datastoreImpl) getLastIdFromRows(ctx context.Context, rows pgx.Rows) (string, error) { - id := "" - - for rows.Next() { - if err := rows.Scan(&id); err != nil { - return "", pgutils.ErrNilIfNoRows(err) - } - } - - return id, rows.Err() -} - -// Given an id and a limit, returns the id a limit number of rows after the given id. This is useful -// for efficient pagination. -func (ds *datastoreImpl) getNextPageId(ctx context.Context, prevId string, limit int) (string, error) { - ds.mutex.Lock() - defer ds.mutex.Unlock() - - query := fmt.Sprintf(getLastIdFromPage, prevId, limit) - rows, err := ds.pool.Query(ctx, query) - - if err != nil { - // Do not be alarmed if the error is simply NoRows - err = pgutils.ErrNilIfNoRows(err) - if err != nil { - log.Warnf("%s: %s", query, err) - } - return "", err - } - defer rows.Close() - - nextId, err := ds.getLastIdFromRows(ctx, rows) - - if err != nil { - return "", err - } - - return nextId, nil -} - -func (ds *datastoreImpl) retryableRemovePLOPsWithoutPodUID(ctx context.Context) (int64, error) { - limit := 10000 - totalRows := int64(0) - - prevId := "00000000-0000-0000-0000-000000000000" - for { - nextId, err := ds.getNextPageId(ctx, prevId, limit) - if err != nil { - return totalRows, err - } - if nextId == "" { - break - } - nrows, err := ds.removePLOPsWithoutPodUIDOnePage(ctx, prevId, nextId) - if err != nil { - return totalRows, err - } - totalRows += nrows - prevId = nextId - } - - return totalRows, nil -} - -func (ds *datastoreImpl) RemovePLOPsWithoutPodUID(ctx context.Context) (int64, error) { - return pgutils.Retry2(ctx, func() (int64, error) { - return ds.retryableRemovePLOPsWithoutPodUID(ctx) - }) -} diff --git a/central/processlisteningonport/datastore/datastore_impl_test.go b/central/processlisteningonport/datastore/datastore_impl_test.go index 6acfb31cf64af..66d5619a04dac 100644 --- a/central/processlisteningonport/datastore/datastore_impl_test.go +++ b/central/processlisteningonport/datastore/datastore_impl_test.go @@ -6,7 +6,6 @@ import ( "context" "fmt" "math/rand" - "slices" "testing" "time" @@ -2500,174 +2499,6 @@ func (suite *PLOPDataStoreTestSuite) TestRemoveOrphanedPLOPs() { } } -func newIndicatorWithDeployment(id string, age time.Duration, deploymentID string) *storage.ProcessIndicator { - return &storage.ProcessIndicator{ - Id: id, - DeploymentId: deploymentID, - ContainerName: "", - PodId: "", - Signal: &storage.ProcessSignal{ - Time: protoconv.NowMinus(age), - }, - } -} - -func newIndicatorWithDeploymentAndPod(id string, age time.Duration, deploymentID, podUID string) *storage.ProcessIndicator { - indicator := newIndicatorWithDeployment(id, age, deploymentID) - indicator.PodUid = podUID - return indicator -} - -func (suite *PLOPDataStoreTestSuite) TestRemoveOrphanedPLOPsByProcesses() { - orphanWindow := 30 * time.Minute - - cases := []struct { - name string - initialProcesses []*storage.ProcessIndicator - initialPlops []*storage.ProcessListeningOnPortStorage - deployments set.FrozenStringSet - pods set.FrozenStringSet - expectedPlopDeletions []string - }{ - { - name: "no deployments nor pods - remove all old indicators", - initialProcesses: []*storage.ProcessIndicator{ - newIndicatorWithDeploymentAndPod(fixtureconsts.ProcessIndicatorID1, 1*time.Hour, fixtureconsts.Deployment6, fixtureconsts.PodUID1), - newIndicatorWithDeploymentAndPod(fixtureconsts.ProcessIndicatorID2, 1*time.Hour, fixtureconsts.Deployment5, fixtureconsts.PodUID2), - newIndicatorWithDeploymentAndPod(fixtureconsts.ProcessIndicatorID3, 1*time.Hour, fixtureconsts.Deployment3, fixtureconsts.PodUID3), - }, - initialPlops: []*storage.ProcessListeningOnPortStorage{ - fixtures.GetPlopStorage1(), - fixtures.GetPlopStorage2(), - fixtures.GetPlopStorage3(), - fixtures.GetPlopStorage4(), - fixtures.GetPlopStorage5(), - fixtures.GetPlopStorage6(), - }, - deployments: set.NewFrozenStringSet(), - pods: set.NewFrozenStringSet(), - expectedPlopDeletions: []string{ - fixtureconsts.PlopUID1, - fixtureconsts.PlopUID2, - fixtureconsts.PlopUID3, - fixtureconsts.PlopUID4, - fixtureconsts.PlopUID5, - fixtureconsts.PlopUID6, - }, - }, - { - name: "no deployments nor pods - remove no new orphaned indicators", - initialProcesses: []*storage.ProcessIndicator{ - newIndicatorWithDeploymentAndPod(fixtureconsts.ProcessIndicatorID1, 20*time.Minute, fixtureconsts.Deployment6, fixtureconsts.PodUID1), - newIndicatorWithDeploymentAndPod(fixtureconsts.ProcessIndicatorID2, 20*time.Minute, fixtureconsts.Deployment5, fixtureconsts.PodUID2), - newIndicatorWithDeploymentAndPod(fixtureconsts.ProcessIndicatorID3, 20*time.Minute, fixtureconsts.Deployment3, fixtureconsts.PodUID3), - }, - initialPlops: []*storage.ProcessListeningOnPortStorage{ - fixtures.GetPlopStorage1(), - fixtures.GetPlopStorage2(), - fixtures.GetPlopStorage3(), - }, - deployments: set.NewFrozenStringSet(), - pods: set.NewFrozenStringSet(), - expectedPlopDeletions: nil, - }, - { - name: "all pods separate deployments - remove no indicators", - initialProcesses: []*storage.ProcessIndicator{ - newIndicatorWithDeploymentAndPod(fixtureconsts.ProcessIndicatorID1, 1*time.Hour, fixtureconsts.Deployment6, fixtureconsts.PodUID1), - newIndicatorWithDeploymentAndPod(fixtureconsts.ProcessIndicatorID2, 1*time.Hour, fixtureconsts.Deployment5, fixtureconsts.PodUID2), - newIndicatorWithDeploymentAndPod(fixtureconsts.ProcessIndicatorID3, 1*time.Hour, fixtureconsts.Deployment3, fixtureconsts.PodUID3), - }, - initialPlops: []*storage.ProcessListeningOnPortStorage{ - fixtures.GetPlopStorage1(), - fixtures.GetPlopStorage2(), - fixtures.GetPlopStorage3(), - }, - deployments: set.NewFrozenStringSet(fixtureconsts.Deployment6, fixtureconsts.Deployment5, fixtureconsts.Deployment3), - pods: set.NewFrozenStringSet(fixtureconsts.PodUID1, fixtureconsts.PodUID2, fixtureconsts.PodUID3), - expectedPlopDeletions: nil, - }, - { - name: "all pods same deployment - remove no indicators", - initialProcesses: []*storage.ProcessIndicator{ - newIndicatorWithDeploymentAndPod(fixtureconsts.ProcessIndicatorID1, 1*time.Hour, fixtureconsts.Deployment6, fixtureconsts.PodUID1), - newIndicatorWithDeploymentAndPod(fixtureconsts.ProcessIndicatorID2, 1*time.Hour, fixtureconsts.Deployment6, fixtureconsts.PodUID2), - newIndicatorWithDeploymentAndPod(fixtureconsts.ProcessIndicatorID3, 1*time.Hour, fixtureconsts.Deployment6, fixtureconsts.PodUID3), - }, - deployments: set.NewFrozenStringSet(fixtureconsts.Deployment6), - pods: set.NewFrozenStringSet(fixtureconsts.PodUID1, fixtureconsts.PodUID2, fixtureconsts.PodUID3), - expectedPlopDeletions: nil, - }, - { - name: "some pods separate deployments - remove some indicators", - initialProcesses: []*storage.ProcessIndicator{ - newIndicatorWithDeploymentAndPod(fixtureconsts.ProcessIndicatorID1, 1*time.Hour, fixtureconsts.Deployment6, fixtureconsts.PodUID1), - newIndicatorWithDeploymentAndPod(fixtureconsts.ProcessIndicatorID2, 20*time.Minute, fixtureconsts.Deployment5, fixtureconsts.PodUID2), - newIndicatorWithDeploymentAndPod(fixtureconsts.ProcessIndicatorID3, 1*time.Hour, fixtureconsts.Deployment3, fixtureconsts.PodUID3), - }, - initialPlops: []*storage.ProcessListeningOnPortStorage{ - fixtures.GetPlopStorage1(), - fixtures.GetPlopStorage2(), - fixtures.GetPlopStorage3(), - }, - deployments: set.NewFrozenStringSet(fixtureconsts.Deployment3), - pods: set.NewFrozenStringSet(fixtureconsts.PodUID3), - expectedPlopDeletions: []string{fixtureconsts.PlopUID1}, - }, - { - name: "some pods same deployment - remove some indicators", - initialProcesses: []*storage.ProcessIndicator{ - newIndicatorWithDeploymentAndPod(fixtureconsts.ProcessIndicatorID1, 1*time.Hour, fixtureconsts.Deployment6, fixtureconsts.PodUID1), - newIndicatorWithDeploymentAndPod(fixtureconsts.ProcessIndicatorID2, 20*time.Minute, fixtureconsts.Deployment6, fixtureconsts.PodUID2), - newIndicatorWithDeploymentAndPod(fixtureconsts.ProcessIndicatorID3, 1*time.Hour, fixtureconsts.Deployment6, fixtureconsts.PodUID3), - }, - initialPlops: []*storage.ProcessListeningOnPortStorage{ - fixtures.GetPlopStorage1(), - fixtures.GetPlopStorage2(), - fixtures.GetPlopStorage3(), - }, - deployments: set.NewFrozenStringSet(fixtureconsts.Deployment6), - pods: set.NewFrozenStringSet(fixtureconsts.PodUID3), - expectedPlopDeletions: []string{fixtureconsts.PlopUID1}, - }, - } - for _, c := range cases { - suite.T().Run(c.name, func(t *testing.T) { - suite.SetupTest() - // Add deployments if necessary - deploymentDS, err := deploymentStore.GetTestPostgresDataStore(suite.T(), suite.postgres.DB) - suite.Nil(err) - for deploymentID := range c.deployments.All() { - suite.NoError(deploymentDS.UpsertDeployment(suite.hasAllCtx, &storage.Deployment{Id: deploymentID, ClusterId: fixtureconsts.Cluster1})) - } - - for podID := range c.pods.All() { - insertPod := fmt.Sprintf("INSERT INTO pods (id, clusterid) VALUES ('%s', '%s')", podID, fixtureconsts.Cluster1) - _, err := suite.postgres.DB.Exec(suite.hasWriteCtx, insertPod) - suite.Nil(err) - } - - suite.NoError(suite.indicatorDataStore.AddProcessIndicators(suite.hasWriteCtx, c.initialProcesses...)) - countFromDB, err := suite.indicatorDataStore.Count(suite.hasAllCtx, nil) - suite.NoError(err) - suite.Equal(len(c.initialProcesses), countFromDB) - - err = suite.store.UpsertMany(suite.hasWriteCtx, c.initialPlops) - suite.NoError(err) - plopCount, err := suite.store.Count(suite.hasReadCtx, search.EmptyQuery()) - suite.NoError(err) - suite.Equal(len(c.initialPlops), plopCount) - - suite.datastore.PruneOrphanedPLOPsByProcessIndicators(suite.hasAllCtx, orphanWindow) - - plopCount, err = suite.store.Count(suite.hasReadCtx, search.EmptyQuery()) - suite.NoError(err) - suite.Equal(len(c.initialPlops)-len(c.expectedPlopDeletions), plopCount) - - }) - } -} - // RemovePLOPsWithoutProcessIndicatorOrProcessInfo Adds a PLOP with a matching process indicator. // The process indicator is then deleted. This means that the PLOP has no matching // process indicator and does not have any process information. A pruning function @@ -2702,176 +2533,3 @@ func (suite *PLOPDataStoreTestSuite) RemovePLOPsWithoutProcessIndicatorOrProcess newPlopsFromDB := suite.getPlopsFromDB() suite.Len(newPlopsFromDB, 0) } - -func (suite *PLOPDataStoreTestSuite) TestRemovePLOPsWithoutPodUID() { - initialPlops := []*storage.ProcessListeningOnPortStorage{ - fixtures.GetPlopStorage1(), - fixtures.GetPlopStorage2(), - fixtures.GetPlopStorage3(), - fixtures.GetPlopStorage4(), - fixtures.GetPlopStorage5(), - fixtures.GetPlopStorage6(), - } - expectedPlopDeletions := []string{fixtureconsts.PlopUID1, fixtureconsts.PlopUID2, fixtureconsts.PlopUID3} - - err := suite.store.UpsertMany(suite.hasWriteCtx, initialPlops) - suite.NoError(err) - plopCount, err := suite.store.Count(suite.hasReadCtx, search.EmptyQuery()) - suite.NoError(err) - suite.Equal(len(initialPlops), plopCount) - - prunedCount, err := suite.datastore.RemovePLOPsWithoutPodUID(suite.hasWriteCtx) - suite.NoError(err) - suite.Equal(int64(3), prunedCount) - - plopCount, err = suite.store.Count(suite.hasReadCtx, search.EmptyQuery()) - suite.NoError(err) - suite.Equal(len(initialPlops)-len(expectedPlopDeletions), plopCount) - - ids, err := suite.store.GetIDs(suite.hasReadCtx) - suite.NoError(err) - for id := range ids { - suite.NotContains(expectedPlopDeletions, id) - } -} - -func (suite *PLOPDataStoreTestSuite) addTooMany(plops []*storage.ProcessListeningOnPortFromSensor) error { - batchSize := 30000 - - for plopBatch := range slices.Chunk(plops, batchSize) { - // Per-batch deadline so ContextWithTimeoutIfNotExists skips the - // 60-second default without sharing one budget across all batches. - ctx, cancel := context.WithTimeout(suite.hasWriteCtx, 5*time.Minute) - err := suite.datastore.AddProcessListeningOnPort(ctx, fixtureconsts.Cluster1, plopBatch...) - cancel() - if err != nil { - return err - } - } - return nil -} - -func (suite *PLOPDataStoreTestSuite) RemovePLOPsWithoutPodUIDScale(nport int, nprocess int, npod int) { - plopObjects := makeRandomPlops(nport, nprocess, npod, fixtureconsts.Deployment1) - - plopsWithoutPodUids := 0 - for _, plop := range plopObjects { - p := rand.Float32() - if p < 0.5 { - plop.PodUid = "" - plopsWithoutPodUids++ - } - } - - // Add the PLOPs - suite.Require().NoError(suite.addTooMany(plopObjects)) - - plopCount, err := suite.store.Count(suite.hasReadCtx, search.EmptyQuery()) - suite.Equal(plopCount, 2*nport*nprocess*npod) - suite.NoError(err) - - start := time.Now() - prunedCount, err := suite.datastore.RemovePLOPsWithoutPodUID(suite.hasWriteCtx) - suite.Equal(int64(plopsWithoutPodUids), prunedCount) - duration := time.Since(start) - suite.NoError(err) - log.Infof("Pruning %d plops took %s", prunedCount, duration) - _, err = suite.datastore.RemovePLOPsWithoutPodUID(suite.hasWriteCtx) - suite.NoError(err) -} - -func (suite *PLOPDataStoreTestSuite) TestRemovePLOPsWithoutPodUIDScale27K() { - nport := 30 - nprocess := 30 - npod := 30 - - suite.RemovePLOPsWithoutPodUIDScale(nport, nprocess, npod) -} - -func (suite *PLOPDataStoreTestSuite) TestRemovePLOPsWithoutPodUIDScale125K() { - nport := 50 - nprocess := 50 - npod := 50 - - suite.RemovePLOPsWithoutPodUIDScale(nport, nprocess, npod) -} - -func (suite *PLOPDataStoreTestSuite) TestRemovePLOPsWithoutPodUIDScaleRaceCondition() { - var mutex sync.RWMutex - var wg sync.WaitGroup - wg.Add(1) - - running := true - plopsWithoutPodUids := 0 - go func() { - defer wg.Done() - iterations := 3 - for range iterations { - nport := 30 - nprocess := 30 - npod := 30 - plopObjects := makeRandomPlops(nport, nprocess, npod, fixtureconsts.Deployment1) - - for _, plop := range plopObjects { - p := rand.Float32() - if p < 0.5 { - plop.PodUid = "" - plopsWithoutPodUids += 1 - } else { - plop.PodUid = uuid.NewV4().String() - } - } - - // Add the open PLOPs - suite.NoError(suite.addTooMany(plopObjects)) - - // Close the PLOPs - // This is so that UpsertMany will trigger deletes - for _, plop := range plopObjects { - plop.CloseTimestamp = protoconv.ConvertTimeToTimestamp(time.Now()) - } - - // Add the closed PLOPs - suite.NoError(suite.addTooMany(plopObjects)) - } - }() - - totalPrunedCount := 0 - - var wgPrune sync.WaitGroup - wgPrune.Go(func() { - for { - mutex.Lock() - stop := !running - mutex.Unlock() - - if stop { - break - } - - prunedCount, err := suite.datastore.RemovePLOPsWithoutPodUID(suite.hasWriteCtx) - suite.NoError(err) - totalPrunedCount += int(prunedCount) - } - }) - - wg.Wait() - - mutex.Lock() - running = false - mutex.Unlock() - - wgPrune.Wait() - - prunedCount, err := suite.datastore.RemovePLOPsWithoutPodUID(suite.hasWriteCtx) - suite.NoError(err) - totalPrunedCount += int(prunedCount) - - // Each PLOP is opened and closed. If a PLOP is opened then pruned, then closed, - // and pruned again, then it will be pruned twice. If a PLOP is opened, then closed, - // and pruned, then it will be pruned once. Therefore the number of rows pruned will - // be between the number of PLOPs that don't have poduids that were added and twice - // that number. - suite.GreaterOrEqual(plopsWithoutPodUids, totalPrunedCount/2) - suite.LessOrEqual(plopsWithoutPodUids, totalPrunedCount) -} diff --git a/central/processlisteningonport/datastore/mocks/datastore.go b/central/processlisteningonport/datastore/mocks/datastore.go index bc71a23f3157e..053977e9f8ed7 100644 --- a/central/processlisteningonport/datastore/mocks/datastore.go +++ b/central/processlisteningonport/datastore/mocks/datastore.go @@ -91,33 +91,6 @@ func (mr *MockDataStoreMockRecorder) PruneOrphanedPLOPs(ctx, orphanWindow any) * return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PruneOrphanedPLOPs", reflect.TypeOf((*MockDataStore)(nil).PruneOrphanedPLOPs), ctx, orphanWindow) } -// PruneOrphanedPLOPsByProcessIndicators mocks base method. -func (m *MockDataStore) PruneOrphanedPLOPsByProcessIndicators(ctx context.Context, orphanWindow time.Duration) { - m.ctrl.T.Helper() - m.ctrl.Call(m, "PruneOrphanedPLOPsByProcessIndicators", ctx, orphanWindow) -} - -// PruneOrphanedPLOPsByProcessIndicators indicates an expected call of PruneOrphanedPLOPsByProcessIndicators. -func (mr *MockDataStoreMockRecorder) PruneOrphanedPLOPsByProcessIndicators(ctx, orphanWindow any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PruneOrphanedPLOPsByProcessIndicators", reflect.TypeOf((*MockDataStore)(nil).PruneOrphanedPLOPsByProcessIndicators), ctx, orphanWindow) -} - -// RemovePLOPsWithoutPodUID mocks base method. -func (m *MockDataStore) RemovePLOPsWithoutPodUID(ctx context.Context) (int64, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "RemovePLOPsWithoutPodUID", ctx) - ret0, _ := ret[0].(int64) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// RemovePLOPsWithoutPodUID indicates an expected call of RemovePLOPsWithoutPodUID. -func (mr *MockDataStoreMockRecorder) RemovePLOPsWithoutPodUID(ctx any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RemovePLOPsWithoutPodUID", reflect.TypeOf((*MockDataStore)(nil).RemovePLOPsWithoutPodUID), ctx) -} - // RemovePLOPsWithoutProcessIndicatorOrProcessInfo mocks base method. func (m *MockDataStore) RemovePLOPsWithoutProcessIndicatorOrProcessInfo(ctx context.Context) (int64, error) { m.ctrl.T.Helper() @@ -147,20 +120,6 @@ func (mr *MockDataStoreMockRecorder) RemovePlopsByPod(ctx, id any) *gomock.Call return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RemovePlopsByPod", reflect.TypeOf((*MockDataStore)(nil).RemovePlopsByPod), ctx, id) } -// RemoveProcessListeningOnPort mocks base method. -func (m *MockDataStore) RemoveProcessListeningOnPort(ctx context.Context, ids []string) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "RemoveProcessListeningOnPort", ctx, ids) - ret0, _ := ret[0].(error) - return ret0 -} - -// RemoveProcessListeningOnPort indicates an expected call of RemoveProcessListeningOnPort. -func (mr *MockDataStoreMockRecorder) RemoveProcessListeningOnPort(ctx, ids any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RemoveProcessListeningOnPort", reflect.TypeOf((*MockDataStore)(nil).RemoveProcessListeningOnPort), ctx, ids) -} - // WalkAll mocks base method. func (m *MockDataStore) WalkAll(ctx context.Context, fn datastore.WalkFn) error { m.ctrl.T.Helper() diff --git a/central/processlisteningonport/datastore/pruning.go b/central/processlisteningonport/datastore/pruning.go index 1d4da65fcbc26..0b9a484590e9e 100644 --- a/central/processlisteningonport/datastore/pruning.go +++ b/central/processlisteningonport/datastore/pruning.go @@ -1,15 +1,6 @@ package datastore const ( - // Explain Analyze indicated that 2 statements for PLOP is faster than one. - deleteOrphanedPLOPDeploymentsAndPI = `DELETE FROM listening_endpoints WHERE processindicatorid in (SELECT id from process_indicators pi WHERE NOT EXISTS - (SELECT 1 FROM deployments WHERE pi.deploymentid = deployments.Id) AND - (signal_time < now() at time zone 'utc' - INTERVAL '%d MINUTES' OR signal_time is NULL))` - - deleteOrphanedPLOPPods = `DELETE FROM listening_endpoints WHERE processindicatorid in (SELECT id from process_indicators pi WHERE NOT EXISTS - (SELECT 1 FROM pods WHERE pi.poduid = pods.Id) AND - (signal_time < now() at time zone 'utc' - INTERVAL '%d MINUTES' OR signal_time is NULL))` - // Unfortunately if a listening endpoint is marked as being open there is no indication of how old it is. // This leads to a possible race condition where a listening endpoint reaches the database before the deployment, // and the pruning job happens to run before the deployment information arrives in the database. @@ -31,17 +22,4 @@ const ( // if the serialized data has process information. PLOPs without process information are then deleted. getPotentiallyOrphanedPLOPs = `SELECT plop.serialized FROM listening_endpoints plop where NOT EXISTS (select 1 FROM process_indicators proc where plop.processindicatorid = proc.id)` - - // This is used to make pagination more efficient compared to using offset. The ids obtained using this query are used for deleting - // PLOPs without poduids in batches. See the query below this query. - getLastIdFromPage = `WITH tmp as ( - SELECT id FROM listening_endpoints WHERE id > '%s' ORDER BY id LIMIT %d - ) - SELECT id FROM tmp ORDER BY id DESC LIMIT 1` - - // Deletes PLOPs without poduids in batches according to id, which is more efficient than using offset. - // It is possible that new rows may be inserted between the starting and ending ids, after the two ids - // are obtained. Meaning that the number of rows in the page may be different than what is expected for - // the page. This should not cause a problem. - deletePLOPsWithoutPoduidInPage = "DELETE FROM listening_endpoints WHERE poduid is null AND id >= '%s' AND id <= '%s'" ) diff --git a/central/pruning/pruning.go b/central/pruning/pruning.go index dbae515c7df27..a1b82548091de 100644 --- a/central/pruning/pruning.go +++ b/central/pruning/pruning.go @@ -61,8 +61,7 @@ const ( logImbueGCFreq = 24 * time.Hour logImbueWindow = 24 * 7 * time.Hour - alertQueryTimeout = 10 * time.Minute - alertDeleteBatchSize = 5000 + alertQueryTimeout = 10 * time.Minute flowsSemaphoreWeight = 5 @@ -74,14 +73,13 @@ const ( ) var ( - log = logging.LoggerForModule() - pruningCtx = sac.WithAllAccess(context.Background()) - lastClusterPruneTime time.Time - lastLogImbuePruneTime time.Time - lastV1ImagePruneTime time.Time - lastPrunedV1ImageID string - pruningTimeout = env.PostgresDefaultPruningStatementTimeout.DurationSetting() - prunedPLOPsWithoutPodUIDs = false + log = logging.LoggerForModule() + pruningCtx = sac.WithAllAccess(context.Background()) + lastClusterPruneTime time.Time + lastLogImbuePruneTime time.Time + lastV1ImagePruneTime time.Time + lastPrunedV1ImageID string + pruningTimeout = env.PostgresDefaultPruningStatementTimeout.DurationSetting() pruneInterval = env.PruneInterval.DurationSetting() orphanWindow = env.PruneOrphanedWindow.DurationSetting() @@ -671,9 +669,6 @@ func clusterIDsToNegationQuery(clusterIDSet set.FrozenStringSet) *v1.Query { func (g *garbageCollectorImpl) removeOrphanedProcesses() { defer metrics.SetPruningDuration(time.Now(), "Processes") - g.plops.PruneOrphanedPLOPsByProcessIndicators(pruningCtx, orphanWindow) - - log.Info("[PLOP pruning by processes] Pruning of orphaned PLOPs by processes complete") // Prune processes in chunks. First get the ones orphaned by deployments and then go back and // do the same for those orphaned by pod @@ -790,15 +785,6 @@ func (g *garbageCollectorImpl) removeOrphanedPLOPs() { log.Errorf("error removing PLOPs with no matching process indicator or process information: %v", err) } log.Infof("[PLOP pruning] Pruning of %d orphaned PLOPs with no matching process indicator or process information complete", prunedCount) - - // Only run once since we don't expect any new PLOPs without poduids. - if !prunedPLOPsWithoutPodUIDs { - prunedCount, err = g.plops.RemovePLOPsWithoutPodUID(pruningCtx) - if err != nil { - log.Errorf("error removing PLOPs without poduid: %v", err) - } - log.Infof("[PLOP pruning] Prunned %d orphaned PLOPs with no poduid", prunedCount) - } } func (g *garbageCollectorImpl) removeExpiredAdministrationEvents(config *storage.PrivateConfig) { diff --git a/central/pruning/pruning_test.go b/central/pruning/pruning_test.go index a61060837fb1b..7810806dee076 100644 --- a/central/pruning/pruning_test.go +++ b/central/pruning/pruning_test.go @@ -1683,30 +1683,6 @@ func (s *PruningTestSuite) TestRemoveOrphanedPLOPs() { pods: set.NewFrozenStringSet(fixtureconsts.PodUID1), expectedDeletions: []string{}, }, - { - name: "Plop does not have a poduid so it is removed", - initialPlops: []*storage.ProcessListeningOnPortStorage{ - { - Id: plopID1, - Port: 1234, - Protocol: storage.L4Protocol_L4_PROTOCOL_TCP, - CloseTimestamp: nil, - ProcessIndicatorId: fixtureconsts.ProcessIndicatorID1, - Closed: false, - Process: &storage.ProcessIndicatorUniqueKey{ - PodId: fixtureconsts.PodUID1, - ContainerName: "test_container1", - ProcessName: "test_process1", - ProcessArgs: "test_arguments1", - ProcessExecFilePath: "test_path1", - }, - DeploymentId: fixtureconsts.Deployment1, - }, - }, - deployments: set.NewFrozenStringSet(fixtureconsts.Deployment1), - pods: set.NewFrozenStringSet(fixtureconsts.PodUID1), - expectedDeletions: []string{plopID1}, - }, } for _, c := range cases { @@ -1718,7 +1694,6 @@ func (s *PruningTestSuite) TestRemoveOrphanedPLOPs() { postgres: db, plops: plopDS, } - prunedPLOPsWithoutPodUIDs = false // Populate some actual data so the query returns what needs deleted deploymentDS, err := deploymentDatastore.GetTestPostgresDataStore(t, db.DB) diff --git a/central/risk/multipliers/image/vulnerabilities.go b/central/risk/multipliers/image/vulnerabilities.go index f99dd7a3abb90..b37950fd5bfee 100644 --- a/central/risk/multipliers/image/vulnerabilities.go +++ b/central/risk/multipliers/image/vulnerabilities.go @@ -8,6 +8,7 @@ import ( "github.com/stackrox/rox/central/risk/scorer/vulns" "github.com/stackrox/rox/generated/storage" "github.com/stackrox/rox/pkg/scancomponent" + "github.com/stackrox/rox/pkg/set" ) const ( @@ -21,7 +22,7 @@ const ( // vulnerabilitiesMultiplier is a scorer for the vulnerabilities in an image type vulnerabilitiesMultiplier struct{} -// NewVulnerabilities provides a multiplier that scores the data based on the CVSS scores and number of CVEs +// NewVulnerabilities provides a multiplier that scores known-severity vulnerability findings across image components. func NewVulnerabilities() Multiplier { return &vulnerabilitiesMultiplier{} } @@ -43,8 +44,8 @@ func (c *vulnerabilitiesMultiplier) Score(_ context.Context, image *storage.Imag Name: VulnerabilitiesHeading, Factors: []*storage.Risk_Result_Factor{ { - Message: fmt.Sprintf("Image %q contains %d CVEs with severities ranging between %s and %s", - image.GetName().GetFullName(), num, min.Severity, max.Severity), + Message: fmt.Sprintf("Image %q contains %d distinct CVEs with severities ranging between %s and %s", + image.GetName().GetFullName(), countDistinctCVEs(imgComponents), min.Severity, max.Severity), }, }, Score: score, @@ -68,10 +69,23 @@ func (c *vulnerabilitiesMultiplier) ScoreV2(_ context.Context, image *storage.Im Name: VulnerabilitiesHeading, Factors: []*storage.Risk_Result_Factor{ { - Message: fmt.Sprintf("Image %q contains %d CVEs with severities ranging between %s and %s", - image.GetName().GetFullName(), num, min.Severity, max.Severity), + Message: fmt.Sprintf("Image %q contains %d distinct CVEs with severities ranging between %s and %s", + image.GetName().GetFullName(), countDistinctCVEs(imgComponents), min.Severity, max.Severity), }, }, Score: score, } } + +// countDistinctCVEs includes unknown-severity findings, which do not affect the risk score. +func countDistinctCVEs(components []*storage.EmbeddedImageScanComponent) int { + cves := set.NewStringSet() + for _, component := range components { + for _, vuln := range component.GetVulns() { + if cve := vuln.GetCve(); cve != "" { + cves.Add(cve) + } + } + } + return cves.Cardinality() +} diff --git a/central/risk/multipliers/image/vulnerabilities_test.go b/central/risk/multipliers/image/vulnerabilities_test.go index 959ac3af49969..5a7460316dda3 100644 --- a/central/risk/multipliers/image/vulnerabilities_test.go +++ b/central/risk/multipliers/image/vulnerabilities_test.go @@ -18,6 +18,7 @@ func TestVulnerabilitiesScore(t *testing.T) { images := multipliers.GetMockImages() result := mult.Score(ctx, images[0]) assert.Equal(t, float32(1.59025), result.GetScore()) + assert.Equal(t, "Image \"docker.io/library/nginx:1.10\" contains 2 distinct CVEs with severities ranging between Low and Critical", result.GetFactors()[0].GetMessage()) // Changing CVSS score should not affect result. images[0].GetScan().GetComponents()[0].GetVulns()[0].Cvss = 0 @@ -27,7 +28,14 @@ func TestVulnerabilitiesScore(t *testing.T) { result = mult.Score(ctx, images[0]) assert.Equal(t, float32(1.59025), result.GetScore()) - // Set both severity to unknown and then there should be a nil RiskResult + // Unknown-severity CVEs are displayed, but do not contribute to the score. + images[0].GetScan().GetComponents()[0].Vulns = append(images[0].GetScan().GetComponents()[0].GetVulns(), + &storage.EmbeddedVulnerability{Cve: "CVE-2019-0003", Severity: storage.VulnerabilitySeverity_UNKNOWN_VULNERABILITY_SEVERITY}) + result = mult.Score(ctx, images[0]) + assert.Equal(t, float32(1.59025), result.GetScore()) + assert.Equal(t, "Image \"docker.io/library/nginx:1.10\" contains 3 distinct CVEs with severities ranging between Low and Critical", result.GetFactors()[0].GetMessage()) + + // Only unknown-severity vulnerabilities remain, so there is no risk result. images[0].GetScan().GetComponents()[0].GetVulns()[0].Severity = storage.VulnerabilitySeverity_UNKNOWN_VULNERABILITY_SEVERITY images[0].GetScan().GetComponents()[0].GetVulns()[1].Severity = storage.VulnerabilitySeverity_UNKNOWN_VULNERABILITY_SEVERITY images[0].GetScan().GetComponents()[1].GetVulns()[0].Severity = storage.VulnerabilitySeverity_UNKNOWN_VULNERABILITY_SEVERITY @@ -44,6 +52,7 @@ func TestVulnerabilitiesScoreV2(t *testing.T) { images := multipliers.GetMockImagesV2() result := mult.ScoreV2(ctx, images[0]) assert.Equal(t, float32(1.59025), result.GetScore()) + assert.Equal(t, "Image \"docker.io/library/nginx:1.10\" contains 2 distinct CVEs with severities ranging between Low and Critical", result.GetFactors()[0].GetMessage()) // Changing CVSS score should not affect result. images[0].GetScan().GetComponents()[0].GetVulns()[0].Cvss = 0 @@ -53,7 +62,14 @@ func TestVulnerabilitiesScoreV2(t *testing.T) { result = mult.ScoreV2(ctx, images[0]) assert.Equal(t, float32(1.59025), result.GetScore()) - // Set both severity to unknown and then there should be a nil RiskResult + // Unknown-severity CVEs are displayed, but do not contribute to the score. + images[0].GetScan().GetComponents()[0].Vulns = append(images[0].GetScan().GetComponents()[0].GetVulns(), + &storage.EmbeddedVulnerability{Cve: "CVE-2019-0003", Severity: storage.VulnerabilitySeverity_UNKNOWN_VULNERABILITY_SEVERITY}) + result = mult.ScoreV2(ctx, images[0]) + assert.Equal(t, float32(1.59025), result.GetScore()) + assert.Equal(t, "Image \"docker.io/library/nginx:1.10\" contains 3 distinct CVEs with severities ranging between Low and Critical", result.GetFactors()[0].GetMessage()) + + // Only unknown-severity vulnerabilities remain, so there is no risk result. images[0].GetScan().GetComponents()[0].GetVulns()[0].Severity = storage.VulnerabilitySeverity_UNKNOWN_VULNERABILITY_SEVERITY images[0].GetScan().GetComponents()[0].GetVulns()[1].Severity = storage.VulnerabilitySeverity_UNKNOWN_VULNERABILITY_SEVERITY images[0].GetScan().GetComponents()[1].GetVulns()[0].Severity = storage.VulnerabilitySeverity_UNKNOWN_VULNERABILITY_SEVERITY diff --git a/central/risk/scorer/deployment/scorer_test.go b/central/risk/scorer/deployment/scorer_test.go index 59a5838e41802..faf919428da8e 100644 --- a/central/risk/scorer/deployment/scorer_test.go +++ b/central/risk/scorer/deployment/scorer_test.go @@ -62,7 +62,7 @@ func TestScore(t *testing.T) { { Name: imageMultiplier.VulnerabilitiesHeading, Factors: []*storage.Risk_Result_Factor{ - {Message: "Image \"docker.io/library/nginx:1.10\" contains 3 CVEs with severities ranging between Moderate and Critical"}, + {Message: "Image \"docker.io/library/nginx:1.10\" contains 3 distinct CVEs with severities ranging between Moderate and Critical"}, }, Score: 1.5535, }, diff --git a/central/risk/scorer/image/scorer_test.go b/central/risk/scorer/image/scorer_test.go index c89e474ce1f2c..902014711f510 100644 --- a/central/risk/scorer/image/scorer_test.go +++ b/central/risk/scorer/image/scorer_test.go @@ -28,7 +28,7 @@ func TestScore(t *testing.T) { { Name: imageMultiplier.VulnerabilitiesHeading, Factors: []*storage.Risk_Result_Factor{ - {Message: "Image \"docker.io/library/nginx:1.10\" contains 3 CVEs with severities ranging between Moderate and Critical"}, + {Message: "Image \"docker.io/library/nginx:1.10\" contains 3 distinct CVEs with severities ranging between Moderate and Critical"}, }, Score: 1.5535, }, @@ -67,7 +67,7 @@ func TestScoreV2(t *testing.T) { { Name: imageMultiplier.VulnerabilitiesHeading, Factors: []*storage.Risk_Result_Factor{ - {Message: "Image \"docker.io/library/nginx:1.10\" contains 3 CVEs with severities ranging between Moderate and Critical"}, + {Message: "Image \"docker.io/library/nginx:1.10\" contains 3 distinct CVEs with severities ranging between Moderate and Critical"}, }, Score: 1.5535, }, diff --git a/central/sensor/service/connection/connection_impl.go b/central/sensor/service/connection/connection_impl.go index 5be9321a48f0f..b59fdec096b88 100644 --- a/central/sensor/service/connection/connection_impl.go +++ b/central/sensor/service/connection/connection_impl.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "math" + "slices" "github.com/pkg/errors" compScanSetting "github.com/stackrox/rox/central/complianceoperator/v2/scanconfigurations/datastore" @@ -544,6 +545,13 @@ func (c *sensorConnection) getPolicySyncMsgFromPolicies(policies []*storage.Poli // Otherwise, sensor supports the same version or newer as central } + // Remove exclude_by_kind exclusions before syncing to a Sensor that + // lacks WorkloadKindExclusionCap. Sensor forwards this same list to + // the admission controller, so AC evaluates the same copy. + if !c.HasCapability(centralsensor.WorkloadKindExclusionCap) { + policies = stripExcludeByKindExclusions(policies) + } + return ¢ral.MsgToSensor{ Msg: ¢ral.MsgToSensor_PolicySync{ PolicySync: ¢ral.PolicySync{ @@ -553,6 +561,32 @@ func (c *sensorConnection) getPolicySyncMsgFromPolicies(policies []*storage.Poli }, nil } +// stripExcludeByKindExclusions returns a copy of policies with every exclusion +// that sets exclude_by_kind removed. Stored policies are not mutated. An old +// Sensor compiles an exclude_by_kind item as an unconstrained exclusion (nil +// name and nil scope match every workload). Sensor does not evaluate image +// exclusions, so an image+kind item with only the kind matcher removed is that +// same unconstrained exclusion and is removed entirely. An exclusion with no +// kind matcher is kept, including an image combined with a deployment name or +// scope. +func stripExcludeByKindExclusions(policies []*storage.Policy) []*storage.Policy { + out := make([]*storage.Policy, 0, len(policies)) + for _, p := range policies { + if !slices.ContainsFunc(p.GetExclusions(), exclusionSetsExcludeByKind) { + out = append(out, p) + continue + } + cloned := p.CloneVT() + cloned.Exclusions = slices.DeleteFunc(cloned.GetExclusions(), exclusionSetsExcludeByKind) + out = append(out, cloned) + } + return out +} + +func exclusionSetsExcludeByKind(e *storage.Exclusion) bool { + return e.GetExcludeByKind() != nil +} + func (c *sensorConnection) getNetworkBaselineSyncMsg(ctx context.Context) (*central.MsgToSensor, error) { var networkBaselines []*storage.NetworkBaseline walkFn := func() error { diff --git a/central/sensor/service/connection/connection_test.go b/central/sensor/service/connection/connection_test.go index a8fd0d52fd2fc..eaf8523107feb 100644 --- a/central/sensor/service/connection/connection_test.go +++ b/central/sensor/service/connection/connection_test.go @@ -365,6 +365,102 @@ func (s *testSuite) TestGetPolicySyncMsgFromPoliciesDoesntDowngradeInvalidVersio s.Equal(policyversion.CurrentVersion().String(), policySync.GetPolicies()[0].GetPolicyVersion()) } +func (s *testSuite) TestGetPolicySyncMsgFromPoliciesStripsExcludeByKind() { + kindExclusion := &storage.Exclusion{ + Name: "skip-cronjobs", + Matcher: &storage.Exclusion_ExcludeByKind_{ + ExcludeByKind: &storage.Exclusion_ExcludeByKind{ + Kinds: []storage.Exclusion_WorkloadKind{storage.Exclusion_CRON_JOB}, + }, + }, + } + nameExclusion := &storage.Exclusion{ + Name: "skip-web", + Matcher: &storage.Exclusion_Deployment_{ + Deployment: &storage.Exclusion_Deployment{Name: "web"}, + }, + } + imageExclusion := &storage.Exclusion{ + Name: "skip-image", + Image: &storage.Exclusion_Image{Name: "stackrox.io"}, + } + imageAndKindExclusion := &storage.Exclusion{ + Name: "skip-image-and-jobs", + Image: &storage.Exclusion_Image{Name: "example.io"}, + Matcher: &storage.Exclusion_ExcludeByKind_{ + ExcludeByKind: &storage.Exclusion_ExcludeByKind{ + Kinds: []storage.Exclusion_WorkloadKind{storage.Exclusion_JOB}, + }, + }, + } + imageAndDeploymentExclusion := &storage.Exclusion{ + Name: "skip-image-and-web", + Image: &storage.Exclusion_Image{Name: "nginx"}, + Matcher: &storage.Exclusion_Deployment_{ + Deployment: &storage.Exclusion_Deployment{Name: "web-image"}, + }, + } + policy := &storage.Policy{ + Name: "test-policy", + Exclusions: []*storage.Exclusion{ + kindExclusion, + nameExclusion, + imageExclusion, + imageAndKindExclusion, + imageAndDeploymentExclusion, + }, + } + + s.Run("without capability strips kind items and keeps others", func() { + original := policy.CloneVT() + msg, err := (&sensorConnection{}).getPolicySyncMsgFromPolicies([]*storage.Policy{policy}) + s.Require().NoError(err) + + synced := msg.GetPolicySync().GetPolicies() + s.Require().Len(synced, 1) + got := synced[0].GetExclusions() + s.Require().Len(got, 3) + s.Equal("skip-web", got[0].GetName()) + s.Equal("web", got[0].GetDeployment().GetName()) + s.Equal("skip-image", got[1].GetName()) + s.Equal("stackrox.io", got[1].GetImage().GetName()) + s.Equal("skip-image-and-web", got[2].GetName()) + s.Equal("nginx", got[2].GetImage().GetName()) + s.Equal("web-image", got[2].GetDeployment().GetName()) + protoassert.Equal(s.T(), original, policy) + }) + + s.Run("with capability keeps kind items", func() { + conn := &sensorConnection{ + capabilities: set.NewSet(centralsensor.WorkloadKindExclusionCap), + } + msg, err := conn.getPolicySyncMsgFromPolicies([]*storage.Policy{policy}) + s.Require().NoError(err) + + got := msg.GetPolicySync().GetPolicies()[0].GetExclusions() + s.Require().Len(got, 5) + s.NotNil(got[0].GetExcludeByKind()) + s.NotNil(got[3].GetExcludeByKind()) + s.Equal(storage.Exclusion_JOB, got[3].GetExcludeByKind().GetKinds()[0]) + s.Equal("example.io", got[3].GetImage().GetName()) + s.Equal("web-image", got[4].GetDeployment().GetName()) + }) + + s.Run("later capable sensor still sees original kind items", func() { + // PreparePoliciesAndBroadcast reuses the same slice across connections. + input := []*storage.Policy{policy} + _, err := (&sensorConnection{}).getPolicySyncMsgFromPolicies(input) + s.Require().NoError(err) + + capable := &sensorConnection{ + capabilities: set.NewSet(centralsensor.WorkloadKindExclusionCap), + } + msg, err := capable.getPolicySyncMsgFromPolicies(input) + s.Require().NoError(err) + s.NotNil(msg.GetPolicySync().GetPolicies()[0].GetExclusions()[0].GetExcludeByKind()) + }) +} + func (s *testSuite) TestSendsAuditLogSyncMessageIfEnabledOnRun() { ctx := context.Background() clusterID := "this-cluster" diff --git a/central/splunk/violations_test.go b/central/splunk/violations_test.go index a5c7baa7fd128..6a6397caddd3a 100644 --- a/central/splunk/violations_test.go +++ b/central/splunk/violations_test.go @@ -53,19 +53,17 @@ var ( }, LifecycleStages: []storage.LifecycleStage{storage.LifecycleStage_BUILD, storage.LifecycleStage_DEPLOY}, Exclusions: []*storage.Exclusion{{ - Name: "Don't alert on kube-system namespace", - Deployment: &storage.Exclusion_Deployment{ + Name: "Don't alert on kube-system namespace", Matcher: &storage.Exclusion_Deployment_{Deployment: &storage.Exclusion_Deployment{ Scope: &storage.Scope{ Namespace: "kube-system", }, - }, + }}, }, { - Name: "Don't alert on istio-system namespace", - Deployment: &storage.Exclusion_Deployment{ + Name: "Don't alert on istio-system namespace", Matcher: &storage.Exclusion_Deployment_{Deployment: &storage.Exclusion_Deployment{ Scope: &storage.Scope{ Namespace: "istio-system", }, - }, + }}, }}, Severity: storage.Severity_LOW_SEVERITY, SORTName: "90-Day Image Age", diff --git a/central/tlsconfig/openshift_tls.go b/central/tlsconfig/openshift_tls.go index 1fbb703e21039..8e35aa9bfe869 100644 --- a/central/tlsconfig/openshift_tls.go +++ b/central/tlsconfig/openshift_tls.go @@ -16,6 +16,9 @@ func OpenShiftTLSConfigured() bool { return false } exists, err := fileutils.Exists(env.OpenShiftTLSCertDir.Setting()) + if err != nil { + log.Warnf("Cannot check OpenShift TLS certificate directory %s: %v", env.OpenShiftTLSCertDir.Setting(), err) + } return exists && err == nil } diff --git a/central/virtualmachine/v2/datastore/datastore.go b/central/virtualmachine/v2/datastore/datastore.go index 940a03a9dddbd..296e00c821dfd 100644 --- a/central/virtualmachine/v2/datastore/datastore.go +++ b/central/virtualmachine/v2/datastore/datastore.go @@ -18,6 +18,8 @@ type DataStore interface { // GetVirtualMachine returns the VM with the given ID. GetVirtualMachine(ctx context.Context, id string) (*storage.VirtualMachineV2, bool, error) + // GetVirtualMachineWithLatestScan returns the VM and its latest scan together. + GetVirtualMachineWithLatestScan(ctx context.Context, id string) (*common.VMWithLatestScan, bool, error) // GetManyVirtualMachines returns the VMs with the given IDs. GetManyVirtualMachines(ctx context.Context, ids []string) ([]*storage.VirtualMachineV2, []int, error) diff --git a/central/virtualmachine/v2/datastore/datastore_impl.go b/central/virtualmachine/v2/datastore/datastore_impl.go index b0f091070620b..d3ef7fdff5509 100644 --- a/central/virtualmachine/v2/datastore/datastore_impl.go +++ b/central/virtualmachine/v2/datastore/datastore_impl.go @@ -47,6 +47,11 @@ func (ds *datastoreImpl) GetManyVirtualMachines(ctx context.Context, ids []strin return ds.store.GetMany(ctx, ids) } +func (ds *datastoreImpl) GetVirtualMachineWithLatestScan(ctx context.Context, id string) (*common.VMWithLatestScan, bool, error) { + defer metrics.SetDatastoreFunctionDuration(time.Now(), "VirtualMachineV2", "GetVirtualMachineWithLatestScan") + return ds.store.GetWithLatestScan(ctx, id) +} + func (ds *datastoreImpl) EnsureVirtualMachineExists(ctx context.Context, vmID string, clusterID string) error { defer metrics.SetDatastoreFunctionDuration(time.Now(), "VirtualMachineV2", "EnsureVirtualMachineExists") diff --git a/central/virtualmachine/v2/datastore/mocks/datastore.go b/central/virtualmachine/v2/datastore/mocks/datastore.go index 57be91ac410ed..d2bddff113c49 100644 --- a/central/virtualmachine/v2/datastore/mocks/datastore.go +++ b/central/virtualmachine/v2/datastore/mocks/datastore.go @@ -124,6 +124,22 @@ func (mr *MockDataStoreMockRecorder) GetVirtualMachine(ctx, id any) *gomock.Call return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetVirtualMachine", reflect.TypeOf((*MockDataStore)(nil).GetVirtualMachine), ctx, id) } +// GetVirtualMachineWithLatestScan mocks base method. +func (m *MockDataStore) GetVirtualMachineWithLatestScan(ctx context.Context, id string) (*common.VMWithLatestScan, bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetVirtualMachineWithLatestScan", ctx, id) + ret0, _ := ret[0].(*common.VMWithLatestScan) + ret1, _ := ret[1].(bool) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 +} + +// GetVirtualMachineWithLatestScan indicates an expected call of GetVirtualMachineWithLatestScan. +func (mr *MockDataStoreMockRecorder) GetVirtualMachineWithLatestScan(ctx, id any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetVirtualMachineWithLatestScan", reflect.TypeOf((*MockDataStore)(nil).GetVirtualMachineWithLatestScan), ctx, id) +} + // Search mocks base method. func (m *MockDataStore) Search(ctx context.Context, query *v1.Query) ([]search.Result, error) { m.ctrl.T.Helper() diff --git a/central/virtualmachine/v2/datastore/store/common/parts.go b/central/virtualmachine/v2/datastore/store/common/parts.go index 75ca8e978a2a7..f36d2761b65fd 100644 --- a/central/virtualmachine/v2/datastore/store/common/parts.go +++ b/central/virtualmachine/v2/datastore/store/common/parts.go @@ -2,6 +2,13 @@ package common import "github.com/stackrox/rox/generated/storage" +// VMWithLatestScan contains VM metadata and its latest scan from one database snapshot. +// Scan is nil when the VM has not been scanned. +type VMWithLatestScan struct { + VM *storage.VirtualMachineV2 + Scan *storage.VirtualMachineScanV2 +} + // VMScanParts groups the scan, components, and CVEs that together represent // a single VM scan result. UpsertScan consumes this structure. type VMScanParts struct { diff --git a/central/virtualmachine/v2/datastore/store/mocks/store.go b/central/virtualmachine/v2/datastore/store/mocks/store.go index 2c2e21dd692cc..8230b9540a9de 100644 --- a/central/virtualmachine/v2/datastore/store/mocks/store.go +++ b/central/virtualmachine/v2/datastore/store/mocks/store.go @@ -133,6 +133,22 @@ func (mr *MockStoreMockRecorder) GetMany(ctx, ids any) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMany", reflect.TypeOf((*MockStore)(nil).GetMany), ctx, ids) } +// GetWithLatestScan mocks base method. +func (m *MockStore) GetWithLatestScan(ctx context.Context, id string) (*common.VMWithLatestScan, bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetWithLatestScan", ctx, id) + ret0, _ := ret[0].(*common.VMWithLatestScan) + ret1, _ := ret[1].(bool) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 +} + +// GetWithLatestScan indicates an expected call of GetWithLatestScan. +func (mr *MockStoreMockRecorder) GetWithLatestScan(ctx, id any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetWithLatestScan", reflect.TypeOf((*MockStore)(nil).GetWithLatestScan), ctx, id) +} + // Search mocks base method. func (m *MockStore) Search(ctx context.Context, q *v1.Query) ([]search.Result, error) { m.ctrl.T.Helper() diff --git a/central/virtualmachine/v2/datastore/store/postgres/get_vm_bench_test.go b/central/virtualmachine/v2/datastore/store/postgres/get_vm_bench_test.go new file mode 100644 index 0000000000000..af78f48867913 --- /dev/null +++ b/central/virtualmachine/v2/datastore/store/postgres/get_vm_bench_test.go @@ -0,0 +1,64 @@ +//go:build sql_integration + +package postgres + +import ( + "context" + "fmt" + "testing" + + "github.com/stackrox/rox/generated/storage" + "github.com/stackrox/rox/pkg/concurrency" + "github.com/stackrox/rox/pkg/features" + "github.com/stackrox/rox/pkg/postgres/pgtest" + "github.com/stackrox/rox/pkg/sac" + "github.com/stackrox/rox/pkg/uuid" + "github.com/stretchr/testify/require" + "google.golang.org/protobuf/types/known/timestamppb" +) + +// BenchmarkGetVM measures the joined VM and latest-scan lookup. +// Only VM metadata and scans are seeded; the lookup does not read components or CVEs. +func BenchmarkGetVM(b *testing.B) { + b.Setenv(features.VirtualMachinesEnhancedDataModel.EnvVar(), "true") + ctx := sac.WithAllAccess(context.Background()) + testDB := pgtest.ForT(b) + vmStore := New(testDB.DB, concurrency.NewKeyFence()).(*storeImpl) + for _, vmCount := range []int{1000, 3000} { + b.Run(fmt.Sprintf("VMs%d", vmCount), func(b *testing.B) { + _, err := testDB.Exec(ctx, "TRUNCATE virtual_machine_v2 CASCADE") + require.NoError(b, err) + ids := make([]string, vmCount) + clusterID := uuid.NewV4().String() + tx, err := testDB.Begin(ctx) + require.NoError(b, err) + defer func() { _ = tx.Rollback(ctx) }() + for i := range ids { + ids[i] = uuid.NewV4().String() + require.NoError(b, vmStore.insertVM(ctx, tx, &storage.VirtualMachineV2{ + Id: ids[i], Name: fmt.Sprintf("vm-%d", i), Namespace: "default", + ClusterId: clusterID, ClusterName: "test-cluster", GuestOs: "rhel:9", + State: storage.VirtualMachineV2_RUNNING, LastUpdated: timestamppb.Now(), + })) + require.NoError(b, vmStore.insertScan(ctx, tx, &storage.VirtualMachineScanV2{ + Id: uuid.NewV7().String(), VmV2Id: ids[i], ScanOs: "rhel:9", + ScanTime: timestamppb.Now(), TopCvss: 9.8, + })) + } + require.NoError(b, tx.Commit(ctx)) + _, err = testDB.Exec(ctx, "ANALYZE virtual_machine_v2, virtual_machine_scan_v2") + require.NoError(b, err) + b.Run("JoinedQuery", func(b *testing.B) { + b.ReportAllocs() + i := 0 + for b.Loop() { + result, found, err := vmStore.GetWithLatestScan(ctx, ids[i%len(ids)]) + i++ + require.NoError(b, err) + require.True(b, found) + require.NotNil(b, result.Scan) + } + }) + }) + } +} diff --git a/central/virtualmachine/v2/datastore/store/postgres/store.go b/central/virtualmachine/v2/datastore/store/postgres/store.go index f54c1f0990c29..b42d8871aebbc 100644 --- a/central/virtualmachine/v2/datastore/store/postgres/store.go +++ b/central/virtualmachine/v2/datastore/store/postgres/store.go @@ -13,12 +13,16 @@ import ( v1 "github.com/stackrox/rox/generated/api/v1" "github.com/stackrox/rox/generated/storage" "github.com/stackrox/rox/pkg/concurrency" + "github.com/stackrox/rox/pkg/contextutil" + "github.com/stackrox/rox/pkg/env" "github.com/stackrox/rox/pkg/errox" ops "github.com/stackrox/rox/pkg/metrics" "github.com/stackrox/rox/pkg/postgres" "github.com/stackrox/rox/pkg/postgres/pgutils" pkgSchema "github.com/stackrox/rox/pkg/postgres/schema" "github.com/stackrox/rox/pkg/protocompat" + "github.com/stackrox/rox/pkg/sac" + "github.com/stackrox/rox/pkg/sac/resources" "github.com/stackrox/rox/pkg/search" pgSearch "github.com/stackrox/rox/pkg/search/postgres" "google.golang.org/protobuf/types/known/timestamppb" @@ -32,10 +36,17 @@ const ( getVMStmt = "SELECT serialized FROM " + vmTable + " WHERE Id = $1" getScanStmt = "SELECT serialized FROM " + scanTable + " WHERE VmV2Id = $1" + + // VM facts, notes, and VSOCK CID, plus scan notes, require the serialized blobs. + // LATERAL selects the newest UUIDv7 scan ID; LEFT JOIN retains VMs without scans. + getVMWithLatestScanStmt = "SELECT vm.serialized, scan.serialized FROM " + vmTable + " AS vm" + + " LEFT JOIN LATERAL (SELECT serialized FROM " + scanTable + + " WHERE VmV2Id = vm.Id ORDER BY Id DESC LIMIT 1) AS scan ON true WHERE vm.Id = $1" ) var ( schema = pkgSchema.VirtualMachineV2Schema + vmSAC = sac.ForResource(resources.VirtualMachine) ) // New returns a new Store instance using the provided sql instance. @@ -699,6 +710,44 @@ func (s *storeImpl) Get(ctx context.Context, id string) (*storage.VirtualMachine }) } +// GetWithLatestScan reads the VM and its latest scan in one snapshot. +func (s *storeImpl) GetWithLatestScan(ctx context.Context, id string) (*common.VMWithLatestScan, bool, error) { + defer metrics.SetPostgresOperationDurationTime(time.Now(), ops.Get, "VirtualMachineV2WithLatestScan") + queryCtx, cancel := contextutil.ContextWithTimeoutIfNotExists(ctx, env.PostgresVMStatementTimeout.DurationSetting()) + defer cancel() + return pgutils.Retry3(queryCtx, func() (*common.VMWithLatestScan, bool, error) { + var db postgres.Queryable = s.db + if tx, exists := postgres.TxFromContext(queryCtx); exists { + db = tx + } + var vmBytes, scanBytes []byte + err := db.QueryRow(queryCtx, getVMWithLatestScanStmt, pgutils.NilOrUUID(id)).Scan(&vmBytes, &scanBytes) + if errors.Is(err, pgx.ErrNoRows) { + return nil, false, nil + } + if err != nil { + return nil, false, errors.Wrap(err, "reading VM and latest scan") + } + vm := &storage.VirtualMachineV2{} + if err := vm.UnmarshalVT(vmBytes); err != nil { + return nil, false, errors.Wrap(err, "decoding VM") + } + // Match the scope filtering of Get: inaccessible VMs are not found, and + // neither their metadata nor their scan is returned to the caller. + if allowed, err := vmSAC.ReadAllowed(queryCtx, sac.ClusterScopeKey(vm.GetClusterId()), sac.NamespaceScopeKey(vm.GetNamespace())); err != nil || !allowed { + return nil, false, err + } + result := &common.VMWithLatestScan{VM: vm} + if scanBytes != nil { + result.Scan = &storage.VirtualMachineScanV2{} + if err := result.Scan.UnmarshalVT(scanBytes); err != nil { + return nil, false, errors.Wrap(err, "decoding latest VM scan") + } + } + return result, true, nil + }) +} + func (s *storeImpl) GetMany(ctx context.Context, ids []string) ([]*storage.VirtualMachineV2, []int, error) { defer metrics.SetPostgresOperationDurationTime(time.Now(), ops.GetMany, "VirtualMachineV2") diff --git a/central/virtualmachine/v2/datastore/store/postgres/store_test.go b/central/virtualmachine/v2/datastore/store/postgres/store_test.go index 8f9faf9624030..a62422a05fe48 100644 --- a/central/virtualmachine/v2/datastore/store/postgres/store_test.go +++ b/central/virtualmachine/v2/datastore/store/postgres/store_test.go @@ -4,6 +4,7 @@ package postgres import ( "context" + "slices" "testing" "time" @@ -16,6 +17,7 @@ import ( "github.com/stackrox/rox/pkg/postgres/pgutils" "github.com/stackrox/rox/pkg/protoassert" "github.com/stackrox/rox/pkg/sac" + "github.com/stackrox/rox/pkg/sac/resources" "github.com/stackrox/rox/pkg/search" "github.com/stackrox/rox/pkg/uuid" "github.com/stretchr/testify/suite" @@ -48,6 +50,117 @@ func (s *VMStoreTestSuite) SetupTest() { s.store = New(s.testDB.DB, concurrency.NewKeyFence()).(*storeImpl) } +func (s *VMStoreTestSuite) TestGetWithLatestScan_MissingVM() { + result, found, err := s.store.GetWithLatestScan(s.ctx, uuid.NewV4().String()) + s.Require().NoError(err) + s.False(found) + s.Nil(result) +} + +func (s *VMStoreTestSuite) TestGetWithLatestScan_NoScan() { + vm := s.newVM() + vm.Facts = map[string]string{"agentVersion": "1.0"} + s.Require().NoError(s.store.UpsertVM(s.ctx, vm)) + expected, found, err := s.store.Get(s.ctx, vm.GetId()) + s.Require().NoError(err) + s.Require().True(found) + result, found, err := s.store.GetWithLatestScan(s.ctx, vm.GetId()) + s.Require().NoError(err) + s.Require().True(found) + protoassert.Equal(s.T(), expected, result.VM) + s.Nil(result.Scan) +} + +func (s *VMStoreTestSuite) TestGetWithLatestScan_UsesScanID() { + vm := s.newVM() + s.Require().NoError(s.store.UpsertVM(s.ctx, vm)) + expected, found, err := s.store.Get(s.ctx, vm.GetId()) + s.Require().NoError(err) + s.Require().True(found) + ids := []string{uuid.NewV7().String(), uuid.NewV7().String()} + slices.Sort(ids) + old := &storage.VirtualMachineScanV2{ + Id: ids[0], VmV2Id: vm.GetId(), ScanTime: timestamppb.Now(), + } + latest := &storage.VirtualMachineScanV2{ + Id: ids[1], VmV2Id: vm.GetId(), ScanOs: "rhel:9", TopCvss: 9.8, + ScanTime: timestamppb.New(time.Now().Add(-time.Hour)), + Notes: []storage.VirtualMachineScanV2_Note{storage.VirtualMachineScanV2_OS_UNKNOWN}, + } + tx, err := s.testDB.Begin(s.ctx) + s.Require().NoError(err) + defer func() { _ = tx.Rollback(s.ctx) }() + s.Require().NoError(s.store.insertScan(s.ctx, tx, old)) + s.Require().NoError(s.store.insertScan(s.ctx, tx, latest)) + s.Require().NoError(tx.Commit(s.ctx)) + result, found, err := s.store.GetWithLatestScan(s.ctx, vm.GetId()) + s.Require().NoError(err) + s.Require().True(found) + protoassert.Equal(s.T(), expected, result.VM) + protoassert.Equal(s.T(), latest, result.Scan) +} + +func (s *VMStoreTestSuite) TestGetWithLatestScan_ScopedAccess() { + vm := s.newVM() + s.Require().NoError(s.store.UpsertVM(s.ctx, vm)) + s.Require().NoError(s.store.UpsertScan(s.ctx, vm.GetId(), s.newScanParts(vm.GetId()))) + allowedScope := func(cluster, namespace string) context.Context { + return sac.WithGlobalAccessScopeChecker(context.Background(), sac.AllowFixedScopes( + sac.AccessModeScopeKeys(storage.Access_READ_ACCESS), + sac.ResourceScopeKeys(resources.VirtualMachine), + sac.ClusterScopeKeys(cluster), + sac.NamespaceScopeKeys(namespace), + )) + } + tests := map[string]struct { + ctx context.Context + found bool + }{ + "all access": {ctx: s.ctx, found: true}, + "own namespace": {ctx: allowedScope(vm.GetClusterId(), vm.GetNamespace()), found: true}, + "different namespace": {ctx: allowedScope(vm.GetClusterId(), "other")}, + "different cluster": {ctx: allowedScope(uuid.NewV4().String(), vm.GetNamespace())}, + "no access": {ctx: sac.WithNoAccess(context.Background())}, + } + for name, tt := range tests { + s.Run(name, func() { + result, found, err := s.store.GetWithLatestScan(tt.ctx, vm.GetId()) + s.Require().NoError(err) + s.Equal(tt.found, found) + if tt.found { + s.NotNil(result.VM) + s.NotNil(result.Scan) + } else { + s.Nil(result, "VM metadata and scan must both be hidden") + } + _, oldFound, err := s.store.Get(tt.ctx, vm.GetId()) + s.Require().NoError(err) + s.Equal(oldFound, found, "joined read must match existing Get scope filtering") + }) + } +} + +func (s *VMStoreTestSuite) TestGetWithLatestScan_TransactionContext() { + vm := s.newVM() + scan := s.newScanParts(vm.GetId()).Scan + tx, txCtx, err := s.store.begin(s.ctx) + s.Require().NoError(err) + defer func() { _ = tx.Rollback(s.ctx) }() + s.Require().NoError(s.store.insertVM(txCtx, tx, vm)) + s.Require().NoError(s.store.insertScan(txCtx, tx, scan)) + + result, found, err := s.store.GetWithLatestScan(txCtx, vm.GetId()) + s.Require().NoError(err) + s.Require().True(found) + protoassert.Equal(s.T(), vm, result.VM) + protoassert.Equal(s.T(), scan, result.Scan) + + s.Require().NoError(tx.Rollback(s.ctx)) + _, found, err = s.store.GetWithLatestScan(s.ctx, vm.GetId()) + s.Require().NoError(err) + s.False(found, "uncommitted VM must only be visible through the transaction context") +} + func (s *VMStoreTestSuite) newVM() *storage.VirtualMachineV2 { return &storage.VirtualMachineV2{ Id: uuid.NewV4().String(), diff --git a/central/virtualmachine/v2/datastore/store/store.go b/central/virtualmachine/v2/datastore/store/store.go index c16f45b452d39..08b3ac8d05729 100644 --- a/central/virtualmachine/v2/datastore/store/store.go +++ b/central/virtualmachine/v2/datastore/store/store.go @@ -43,6 +43,8 @@ type Store interface { Search(ctx context.Context, q *v1.Query) ([]search.Result, error) // Get returns the VM with the given ID. Get(ctx context.Context, id string) (*storage.VirtualMachineV2, bool, error) + // GetWithLatestScan returns a VM and its latest scan in one query. + GetWithLatestScan(ctx context.Context, id string) (*common.VMWithLatestScan, bool, error) // GetMany returns the VMs with the given IDs. GetMany(ctx context.Context, ids []string) ([]*storage.VirtualMachineV2, []int, error) diff --git a/central/virtualmachine/v2/service/service_impl.go b/central/virtualmachine/v2/service/service_impl.go index 69775fe103509..855bdc9d4db8c 100644 --- a/central/virtualmachine/v2/service/service_impl.go +++ b/central/virtualmachine/v2/service/service_impl.go @@ -14,7 +14,6 @@ import ( cveDS "github.com/stackrox/rox/central/virtualmachine/cve/v2/datastore" scanDS "github.com/stackrox/rox/central/virtualmachine/scan/v2/datastore" vmDS "github.com/stackrox/rox/central/virtualmachine/v2/datastore" - v1 "github.com/stackrox/rox/generated/api/v1" v2 "github.com/stackrox/rox/generated/api/v2" "github.com/stackrox/rox/generated/storage" "github.com/stackrox/rox/pkg/auth/permissions" @@ -680,7 +679,7 @@ func (s *serviceImpl) GetVM(ctx context.Context, request *v2.GetVMRequest) (*v2. return nil, status.Errorf(codes.InvalidArgument, "id must be a valid UUID (got: %q)", request.GetId()) } - vm, exists, err := s.vmDS.GetVirtualMachine(ctx, request.GetId()) + vmWithScan, exists, err := s.vmDS.GetVirtualMachineWithLatestScan(ctx, request.GetId()) if err != nil { return nil, err } @@ -688,6 +687,7 @@ func (s *serviceImpl) GetVM(ctx context.Context, request *v2.GetVMRequest) (*v2. return nil, status.Errorf(codes.NotFound, "virtual machine %q not found", request.GetId()) } + vm := vmWithScan.VM detail := storagetov2.VirtualMachineV2ToDetail(vm) detail.AgentStatus = storagetov2.AgentStatusFromLastContact( vm.GetLastAgentContact(), @@ -695,22 +695,7 @@ func (s *serviceImpl) GetVM(ctx context.Context, request *v2.GetVMRequest) (*v2. env.VirtualMachinesAgentStaleAfter.DurationSetting(), ) - // Get the latest scan for this VM. Scan IDs are UUIDv7 (time-sortable), - // so sorting by the primary key is equivalent to sorting by time and avoids - // a separate index scan. - scanQuery := search.NewQueryBuilder().AddExactMatches(search.VirtualMachineID, request.GetId()).ProtoQuery() - scanQuery.Pagination = &v1.QueryPagination{ - Limit: 1, - SortOptions: []*v1.QuerySortOption{ - {Field: search.VirtualMachineScanID.String(), Reversed: true}, - }, - } - scans, err := s.scanDS.SearchRawVMScans(ctx, scanQuery) - if err != nil { - return nil, err - } - if len(scans) > 0 { - scan := scans[0] + if scan := vmWithScan.Scan; scan != nil { scanNotes := make([]v2.VMScanNote, 0, len(scan.GetNotes())) for _, n := range scan.GetNotes() { scanNotes = append(scanNotes, storagetov2.ConvertScanNote(n)) diff --git a/central/virtualmachine/v2/service/service_impl_test.go b/central/virtualmachine/v2/service/service_impl_test.go index f94c9b130c7ee..1dd1ae8eb09ea 100644 --- a/central/virtualmachine/v2/service/service_impl_test.go +++ b/central/virtualmachine/v2/service/service_impl_test.go @@ -14,6 +14,7 @@ import ( cveDSMocks "github.com/stackrox/rox/central/virtualmachine/cve/v2/datastore/mocks" scanDSMocks "github.com/stackrox/rox/central/virtualmachine/scan/v2/datastore/mocks" vmDSMocks "github.com/stackrox/rox/central/virtualmachine/v2/datastore/mocks" + "github.com/stackrox/rox/central/virtualmachine/v2/datastore/store/common" v2 "github.com/stackrox/rox/generated/api/v2" "github.com/stackrox/rox/generated/storage" "github.com/stackrox/rox/pkg/fixtures/fixtureconsts" @@ -722,7 +723,7 @@ func TestGetVM(t *testing.T) { Id: testVMID, }, setupMock: func(mockVM *vmDSMocks.MockDataStore, mockCVE *cveDSMocks.MockDataStore, mockComp *componentDSMocks.MockDataStore, mockScan *scanDSMocks.MockDataStore, mockView *cveViewMocks.MockCveView) { - mockVM.EXPECT().GetVirtualMachine(ctx, testVMID).Return(nil, false, nil) + mockVM.EXPECT().GetVirtualMachineWithLatestScan(ctx, testVMID).Return(nil, false, nil) }, expectedError: "not found", }, @@ -731,8 +732,7 @@ func TestGetVM(t *testing.T) { Id: testVMID, }, setupMock: func(mockVM *vmDSMocks.MockDataStore, mockCVE *cveDSMocks.MockDataStore, mockComp *componentDSMocks.MockDataStore, mockScan *scanDSMocks.MockDataStore, mockView *cveViewMocks.MockCveView) { - mockVM.EXPECT().GetVirtualMachine(ctx, testVMID).Return(vm1, true, nil) - mockScan.EXPECT().SearchRawVMScans(ctx, gomock.Any()).Return([]*storage.VirtualMachineScanV2{scan1}, nil) + mockVM.EXPECT().GetVirtualMachineWithLatestScan(ctx, testVMID).Return(&common.VMWithLatestScan{VM: vm1, Scan: scan1}, true, nil) }, expectedResult: func() *v2.VMDetail { detail := storagetov2.VirtualMachineV2ToDetail(vm1) @@ -750,8 +750,7 @@ func TestGetVM(t *testing.T) { Id: testVMID, }, setupMock: func(mockVM *vmDSMocks.MockDataStore, mockCVE *cveDSMocks.MockDataStore, mockComp *componentDSMocks.MockDataStore, mockScan *scanDSMocks.MockDataStore, mockView *cveViewMocks.MockCveView) { - mockVM.EXPECT().GetVirtualMachine(ctx, testVMID).Return(vm1, true, nil) - mockScan.EXPECT().SearchRawVMScans(ctx, gomock.Any()).Return(nil, nil) + mockVM.EXPECT().GetVirtualMachineWithLatestScan(ctx, testVMID).Return(&common.VMWithLatestScan{VM: vm1}, true, nil) }, expectedResult: storagetov2.VirtualMachineV2ToDetail(vm1), }, @@ -767,8 +766,7 @@ func TestGetVM(t *testing.T) { VsockCid: 42, Facts: map[string]string{"agentVersion": "5.0.x-174-g6b7ccc2192"}, } - mockVM.EXPECT().GetVirtualMachine(ctx, testVMID).Return(neverScraped, true, nil) - mockScan.EXPECT().SearchRawVMScans(ctx, gomock.Any()).Return(nil, nil) + mockVM.EXPECT().GetVirtualMachineWithLatestScan(ctx, testVMID).Return(&common.VMWithLatestScan{VM: neverScraped}, true, nil) }, expectedResult: func() *v2.VMDetail { detail := storagetov2.VirtualMachineV2ToDetail(&storage.VirtualMachineV2{ @@ -789,8 +787,7 @@ func TestGetVM(t *testing.T) { setupMock: func(mockVM *vmDSMocks.MockDataStore, mockCVE *cveDSMocks.MockDataStore, mockComp *componentDSMocks.MockDataStore, mockScan *scanDSMocks.MockDataStore, mockView *cveViewMocks.MockCveView) { fresh := vm1.CloneVT() fresh.LastAgentContact = timestamppb.New(time.Now().Add(-time.Hour)) - mockVM.EXPECT().GetVirtualMachine(ctx, testVMID).Return(fresh, true, nil) - mockScan.EXPECT().SearchRawVMScans(ctx, gomock.Any()).Return(nil, nil) + mockVM.EXPECT().GetVirtualMachineWithLatestScan(ctx, testVMID).Return(&common.VMWithLatestScan{VM: fresh}, true, nil) }, expectedResult: func() *v2.VMDetail { detail := storagetov2.VirtualMachineV2ToDetail(vm1) @@ -806,8 +803,7 @@ func TestGetVM(t *testing.T) { fresh := vm1.CloneVT() fresh.Facts = map[string]string{"activationStatus": "inactive"} fresh.LastAgentContact = timestamppb.New(time.Now().Add(-time.Hour)) - mockVM.EXPECT().GetVirtualMachine(ctx, testVMID).Return(fresh, true, nil) - mockScan.EXPECT().SearchRawVMScans(ctx, gomock.Any()).Return(nil, nil) + mockVM.EXPECT().GetVirtualMachineWithLatestScan(ctx, testVMID).Return(&common.VMWithLatestScan{VM: fresh}, true, nil) }, expectedResult: func() *v2.VMDetail { detail := storagetov2.VirtualMachineV2ToDetail(vm1) @@ -824,8 +820,7 @@ func TestGetVM(t *testing.T) { stale := vm1.CloneVT() stale.Facts = map[string]string{"agentVersion": "5.0.x-174-g6b7ccc2192"} stale.LastAgentContact = timestamppb.New(time.Now().Add(-13 * time.Hour)) - mockVM.EXPECT().GetVirtualMachine(ctx, testVMID).Return(stale, true, nil) - mockScan.EXPECT().SearchRawVMScans(ctx, gomock.Any()).Return(nil, nil) + mockVM.EXPECT().GetVirtualMachineWithLatestScan(ctx, testVMID).Return(&common.VMWithLatestScan{VM: stale}, true, nil) }, expectedResult: func() *v2.VMDetail { detail := storagetov2.VirtualMachineV2ToDetail(vm1) @@ -839,17 +834,16 @@ func TestGetVM(t *testing.T) { Id: testVMID, }, setupMock: func(mockVM *vmDSMocks.MockDataStore, mockCVE *cveDSMocks.MockDataStore, mockComp *componentDSMocks.MockDataStore, mockScan *scanDSMocks.MockDataStore, mockView *cveViewMocks.MockCveView) { - mockVM.EXPECT().GetVirtualMachine(ctx, testVMID).Return(nil, false, errors.New("db error")) + mockVM.EXPECT().GetVirtualMachineWithLatestScan(ctx, testVMID).Return(nil, false, errors.New("db error")) }, expectedError: "db error", }, - "scan search error": { + "scan decoding error": { request: &v2.GetVMRequest{ Id: testVMID, }, setupMock: func(mockVM *vmDSMocks.MockDataStore, mockCVE *cveDSMocks.MockDataStore, mockComp *componentDSMocks.MockDataStore, mockScan *scanDSMocks.MockDataStore, mockView *cveViewMocks.MockCveView) { - mockVM.EXPECT().GetVirtualMachine(ctx, testVMID).Return(vm1, true, nil) - mockScan.EXPECT().SearchRawVMScans(ctx, gomock.Any()).Return(nil, errors.New("scan error")) + mockVM.EXPECT().GetVirtualMachineWithLatestScan(ctx, testVMID).Return(nil, false, errors.New("scan error")) }, expectedError: "scan error", }, diff --git a/config-controller/api/v1alpha1/policy_types.go b/config-controller/api/v1alpha1/policy_types.go index c21b47a620ea6..3563ae52b0f4c 100644 --- a/config-controller/api/v1alpha1/policy_types.go +++ b/config-controller/api/v1alpha1/policy_types.go @@ -90,15 +90,29 @@ type SecurityPolicySpec struct { MitreVectorsLocked bool `json:"mitreVectorsLocked,omitempty"` } +// +kubebuilder:validation:AtMostOneOf=deployment;excludeByKind type Exclusion struct { - Name string `json:"name,omitempty"` - Deployment Deployment `json:"deployment,omitempty"` + Name string `json:"name,omitempty"` + // +optional + Deployment Deployment `json:"deployment,omitzero"` Image Image `json:"image,omitempty"` // +optional + // ExcludeByKind excludes all workloads of the selected Kubernetes kinds. + ExcludeByKind *ExcludeByKind `json:"excludeByKind,omitempty"` + // +optional // +kubebuilder:validation:Format="date-time" Expiration string `json:"expiration,omitempty"` } +// +kubebuilder:validation:Enum=CRON_JOB;JOB +type WorkloadKind string + +type ExcludeByKind struct { + // +kubebuilder:validation:MinItems=1 + // Kinds is the list of Kubernetes workload kinds to exclude. + Kinds []WorkloadKind `json:"kinds"` +} + type Deployment struct { Name string `json:"name,omitempty"` Scope Scope `json:"scope,omitempty"` @@ -152,10 +166,14 @@ type MitreAttackVectors struct { Techniques []string `json:"techniques,omitempty"` } +// ContainerType lists container kinds a policy may skip during evaluation. // +kubebuilder:validation:Enum=REGULAR;INIT type ContainerType string +// EvaluationFilter pre-filters which entities a policy evaluates. type EvaluationFilter struct { + // SkipContainerTypes lists container kinds to skip during evaluation. + // +optional SkipContainerTypes []ContainerType `json:"skipContainerTypes,omitempty"` } @@ -296,14 +314,18 @@ func (p SecurityPolicySpec) ToProtobuf(caches map[CacheType]map[string]string) ( protoExclusion.Expiration = protoTS } + if exclusion.Deployment != (Deployment{}) && exclusion.ExcludeByKind != nil { + return nil, errors.New("exclusion cannot set both 'deployment' and 'excludeByKind'") + } + if exclusion.Deployment != (Deployment{}) { - protoExclusion.Deployment = &storage.Exclusion_Deployment{ + dep := &storage.Exclusion_Deployment{ Name: exclusion.Deployment.Name, } scope := exclusion.Deployment.Scope if scope != (Scope{}) { - protoExclusion.Deployment.Scope = &storage.Scope{ + dep.Scope = &storage.Scope{ Namespace: scope.Namespace, } if scope.Cluster != "" { @@ -311,31 +333,53 @@ func (p SecurityPolicySpec) ToProtobuf(caches map[CacheType]map[string]string) ( if err != nil { return nil, errors.New(fmt.Sprintf("Cluster '%s' does not exist", scope.Cluster)) } - protoExclusion.Deployment.Scope.Cluster = clusterID + dep.Scope.Cluster = clusterID } } if scope.Label != (Label{}) { - protoExclusion.Deployment.Scope.Label = &storage.Scope_Label{ + dep.Scope.Label = &storage.Scope_Label{ Key: scope.Label.Key, Value: scope.Label.Value, } } if scope.ClusterLabel != (Label{}) { - protoExclusion.Deployment.Scope.ClusterLabel = &storage.Scope_Label{ + dep.Scope.ClusterLabel = &storage.Scope_Label{ Key: scope.ClusterLabel.Key, Value: scope.ClusterLabel.Value, } } if scope.NamespaceLabel != (Label{}) { - protoExclusion.Deployment.Scope.NamespaceLabel = &storage.Scope_Label{ + dep.Scope.NamespaceLabel = &storage.Scope_Label{ Key: scope.NamespaceLabel.Key, Value: scope.NamespaceLabel.Value, } } + protoExclusion.Matcher = &storage.Exclusion_Deployment_{ + Deployment: dep, + } + } + + if exclusion.ExcludeByKind != nil { + if len(exclusion.ExcludeByKind.Kinds) == 0 { + return nil, errors.New("excludeByKind must specify at least one workload kind") + } + kinds := make([]storage.Exclusion_WorkloadKind, 0, len(exclusion.ExcludeByKind.Kinds)) + for _, kind := range exclusion.ExcludeByKind.Kinds { + val, found := storage.Exclusion_WorkloadKind_value[string(kind)] + if !found || storage.Exclusion_WorkloadKind(val) == storage.Exclusion_WORKLOAD_KIND_UNSPECIFIED { + return nil, errors.Errorf("excludeByKind contains unknown workload kind %q", kind) + } + kinds = append(kinds, storage.Exclusion_WorkloadKind(val)) + } + protoExclusion.Matcher = &storage.Exclusion_ExcludeByKind_{ + ExcludeByKind: &storage.Exclusion_ExcludeByKind{ + Kinds: kinds, + }, + } } proto.Exclusions = append(proto.Exclusions, &protoExclusion) diff --git a/config-controller/api/v1alpha1/policy_types_test.go b/config-controller/api/v1alpha1/policy_types_test.go index 2c69dabf34afd..2e95d9bb1e5a2 100644 --- a/config-controller/api/v1alpha1/policy_types_test.go +++ b/config-controller/api/v1alpha1/policy_types_test.go @@ -1,6 +1,7 @@ package v1alpha1 import ( + "encoding/json" "testing" "github.com/stackrox/rox/generated/storage" @@ -106,22 +107,24 @@ func TestToProtobuf(t *testing.T) { Exclusions: []*storage.Exclusion{ { Name: "Don't alert on deployment collector in namespace stackrox", - Deployment: &storage.Exclusion_Deployment{ - Name: "collector", - Scope: &storage.Scope{ - Namespace: "stackrox", - Cluster: clusterID, - Label: &storage.Scope_Label{ - Key: "app", - Value: "collector", - }, - ClusterLabel: &storage.Scope_Label{ - Key: "env", - Value: "dev", - }, - NamespaceLabel: &storage.Scope_Label{ - Key: "team", - Value: "platform", + Matcher: &storage.Exclusion_Deployment_{ + Deployment: &storage.Exclusion_Deployment{ + Name: "collector", + Scope: &storage.Scope{ + Namespace: "stackrox", + Cluster: clusterID, + Label: &storage.Scope_Label{ + Key: "app", + Value: "collector", + }, + ClusterLabel: &storage.Scope_Label{ + Key: "env", + Value: "dev", + }, + NamespaceLabel: &storage.Scope_Label{ + Key: "team", + Value: "platform", + }, }, }, }, @@ -239,6 +242,14 @@ func TestToProtobufEvaluationFilter(t *testing.T) { filter: &EvaluationFilter{SkipContainerTypes: []ContainerType{"INIT"}}, expectedTypes: []storage.ContainerType{storage.ContainerType_INIT}, }, + "skip regular containers": { + filter: &EvaluationFilter{SkipContainerTypes: []ContainerType{"REGULAR"}}, + expectedTypes: []storage.ContainerType{storage.ContainerType_REGULAR}, + }, + "skip both container types": { + filter: &EvaluationFilter{SkipContainerTypes: []ContainerType{"REGULAR", "INIT"}}, + expectedTypes: []storage.ContainerType{storage.ContainerType_REGULAR, storage.ContainerType_INIT}, + }, "nil filter": { filter: nil, expectNilProto: true, @@ -279,3 +290,90 @@ func TestToProtobufEvaluationFilter(t *testing.T) { }) } } + +func TestToProtobufRejectsDeploymentAndExcludeByKind(t *testing.T) { + spec := SecurityPolicySpec{ + PolicyName: "both-matchers", + Categories: []string{"Security Best Practices"}, + LifecycleStages: []LifecycleStage{"DEPLOY"}, + Severity: "LOW_SEVERITY", + Exclusions: []Exclusion{ + { + Name: "invalid dual matcher", + Deployment: Deployment{ + Name: "collector", + }, + ExcludeByKind: &ExcludeByKind{ + Kinds: []WorkloadKind{"JOB"}, + }, + }, + }, + } + + _, err := spec.ToProtobuf(map[CacheType]map[string]string{ + Notifier: {}, + Cluster: {}, + }) + assert.EqualError(t, err, "exclusion cannot set both 'deployment' and 'excludeByKind'") +} + +func TestToProtobufExcludeByKind(t *testing.T) { + caches := map[CacheType]map[string]string{ + Notifier: {}, + Cluster: {}, + } + baseSpec := func(exclude *ExcludeByKind) SecurityPolicySpec { + return SecurityPolicySpec{ + PolicyName: "kind-exclusion", + Categories: []string{"Security Best Practices"}, + LifecycleStages: []LifecycleStage{"DEPLOY"}, + Severity: "LOW_SEVERITY", + Exclusions: []Exclusion{{ + Name: "by kind", + ExcludeByKind: exclude, + }}, + } + } + + t.Run("converts JOB and CRON_JOB", func(t *testing.T) { + proto, err := baseSpec(&ExcludeByKind{Kinds: []WorkloadKind{"JOB", "CRON_JOB"}}).ToProtobuf(caches) + assert.NoError(t, err) + assert.Equal(t, []storage.Exclusion_WorkloadKind{ + storage.Exclusion_JOB, + storage.Exclusion_CRON_JOB, + }, proto.GetExclusions()[0].GetExcludeByKind().GetKinds()) + }) + + t.Run("rejects empty kind list", func(t *testing.T) { + _, err := baseSpec(&ExcludeByKind{}).ToProtobuf(caches) + assert.EqualError(t, err, "excludeByKind must specify at least one workload kind") + }) + + t.Run("rejects unknown kind", func(t *testing.T) { + _, err := baseSpec(&ExcludeByKind{Kinds: []WorkloadKind{"CRONJOB"}}).ToProtobuf(caches) + assert.EqualError(t, err, `excludeByKind contains unknown workload kind "CRONJOB"`) + }) + + t.Run("rejects mixed valid and unknown kinds", func(t *testing.T) { + _, err := baseSpec(&ExcludeByKind{Kinds: []WorkloadKind{"JOB", "CRONJOB"}}).ToProtobuf(caches) + assert.EqualError(t, err, `excludeByKind contains unknown workload kind "CRONJOB"`) + }) +} + +func TestExclusionJSONOmitsZeroDeployment(t *testing.T) { + kindOnly, err := json.Marshal(Exclusion{ + Name: "skip jobs", + ExcludeByKind: &ExcludeByKind{Kinds: []WorkloadKind{"JOB"}}, + }) + assert.NoError(t, err) + assert.NotContains(t, string(kindOnly), `"deployment"`) + assert.Contains(t, string(kindOnly), `"excludeByKind"`) + + withDeployment, err := json.Marshal(Exclusion{ + Name: "skip web", + Deployment: Deployment{Name: "web"}, + }) + assert.NoError(t, err) + assert.Contains(t, string(withDeployment), `"deployment"`) + assert.Contains(t, string(withDeployment), `"web"`) +} diff --git a/config-controller/api/v1alpha1/zz_generated.deepcopy.go b/config-controller/api/v1alpha1/zz_generated.deepcopy.go index 89f899775ea59..93085d27ed0d5 100644 --- a/config-controller/api/v1alpha1/zz_generated.deepcopy.go +++ b/config-controller/api/v1alpha1/zz_generated.deepcopy.go @@ -60,11 +60,36 @@ func (in *EvaluationFilter) DeepCopy() *EvaluationFilter { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ExcludeByKind) DeepCopyInto(out *ExcludeByKind) { + *out = *in + if in.Kinds != nil { + in, out := &in.Kinds, &out.Kinds + *out = make([]WorkloadKind, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExcludeByKind. +func (in *ExcludeByKind) DeepCopy() *ExcludeByKind { + if in == nil { + return nil + } + out := new(ExcludeByKind) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Exclusion) DeepCopyInto(out *Exclusion) { *out = *in out.Deployment = in.Deployment out.Image = in.Image + if in.ExcludeByKind != nil { + in, out := &in.ExcludeByKind, &out.ExcludeByKind + *out = new(ExcludeByKind) + (*in).DeepCopyInto(*out) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Exclusion. @@ -314,7 +339,9 @@ func (in *SecurityPolicySpec) DeepCopyInto(out *SecurityPolicySpec) { if in.Exclusions != nil { in, out := &in.Exclusions, &out.Exclusions *out = make([]Exclusion, len(*in)) - copy(*out, *in) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } } if in.Scope != nil { in, out := &in.Scope, &out.Scope diff --git a/config-controller/config/crd/bases/config.stackrox.io_securitypolicies.yaml b/config-controller/config/crd/bases/config.stackrox.io_securitypolicies.yaml index 61a83b1baa76d..d3a1254eabdb2 100644 --- a/config-controller/config/crd/bases/config.stackrox.io_securitypolicies.yaml +++ b/config-controller/config/crd/bases/config.stackrox.io_securitypolicies.yaml @@ -82,7 +82,11 @@ spec: evaluates. properties: skipContainerTypes: + description: SkipContainerTypes lists container kinds to skip + during evaluation. items: + description: ContainerType lists container kinds a policy may + skip during evaluation. enum: - REGULAR - INIT @@ -138,6 +142,23 @@ spec: type: object type: object type: object + excludeByKind: + description: ExcludeByKind excludes all workloads of the selected + Kubernetes kinds. + properties: + kinds: + description: Kinds is the list of Kubernetes workload kinds + to exclude. + items: + enum: + - CRON_JOB + - JOB + type: string + minItems: 1 + type: array + required: + - kinds + type: object expiration: format: date-time type: string @@ -149,6 +170,11 @@ spec: name: type: string type: object + x-kubernetes-validations: + - message: at most one of the fields in [deployment excludeByKind] + may be set + rule: (has(self.deployment)?1:0)+(has(self.excludeByKind)?1:0) + <= 1 type: array isDefault: description: IsDefault is unused diff --git a/deploy/README.md b/deploy/README.md index 34cda08abfe6c..6f41a41b11f3a 100644 --- a/deploy/README.md +++ b/deploy/README.md @@ -24,8 +24,9 @@ Installation of roxie is straightforward: the hood. 1. there is also a containerized version of roxie living at quay.io/rhacs-eng/roxie, which can be used ad-hoc. -Furthermore, the `deploy/deploy.sh` shell script supports a roxie backend: set `USE_ROXIE_DEPLOY=true` in the -environment and it will automatically download and run the roxie version specified in the `ROXIE_VERSION` file. +Furthermore, the `deploy/deploy.sh` shell script uses a roxie backend by default. It will automatically +download and run the roxie version specified in the `ROXIE_VERSION` file. To fall back to the legacy +deploy flow, set `USE_ROXIE_DEPLOY=false` in the environment. Any extra arguments you pass to `deploy.sh` are forwarded directly to roxie, so you can use roxie CLI flags even with `deploy.sh`, e.g.: @@ -34,8 +35,8 @@ so you can use roxie CLI flags even with `deploy.sh`, e.g.: ./deploy/deploy.sh --tag 4.11.0 ``` -The roxie backend in `deploy.sh` is **opt-in** for now. Over time, we expect it to -become opt-out, and eventually the legacy deployment scripts will be removed entirely. +The roxie backend in `deploy.sh` is **opt-out**. Set `USE_ROXIE_DEPLOY=false` to fall +back to the legacy flow. The legacy deployment scripts will be removed entirely soon. If you would like to use roxie directly, but roxie is not installed on your system, all `roxie` commands below can also be run via `scripts/roxie.sh` instead: @@ -124,7 +125,6 @@ roxie deploy [ ... ] --tag
``` or alternatively ``` -export USE_ROXIE_DEPLOY=true MAIN_IMAGE_TAG=
./deploy/deploy.sh [ ... ] ``` @@ -294,4 +294,5 @@ Most environment variables can be found in [common/env.sh](common/env.sh). | `ROX_LOCAL_SOURCE_PATH` | `string` | When `ROX_HOTRELOAD` is enabled this variable sets the path to the local binary. This is useful when the `hostPath` mount links into a VM or container, e.g. when using KIND. | | `ROX_INIT_BUNDLE_PATH` | `string` | Sets a custom init-bundle file path for Sensor. | | `ROX_CENTRAL_EXTRA_HELM_VALUES_FILE` | `string` | Adds a custom value file path to the Central Helm chart. | +| `SCANNER_V4_DB_CLAIM_NAME` | `string` | Sets the Scanner V4 DB PVC claim name in both Central and bundled monitoring when using classic Helm deploy. | | `ROX_SENSOR_EXTRA_HELM_VALUES_FILE` | `string` | Adds a custom value file path to the Sensor Helm chart. | diff --git a/deploy/charts/monitoring/README.md b/deploy/charts/monitoring/README.md index 603f743ea3503..81ffd4f82d17a 100644 --- a/deploy/charts/monitoring/README.md +++ b/deploy/charts/monitoring/README.md @@ -21,6 +21,8 @@ On OpenShift, Helm may fail an upgrade with a ServiceAccount `imagePullSecrets` Roxie can install the same chart as add-on release `roxie-addon-monitoring` via `central.availableAddOns.monitoring`. +For a custom Scanner V4 DB PVC, set `scannerV4DbPvcClaimName` to the same name as Central's `scannerV4.db.persistence.persistentVolumeClaim.claimName` when installing monitoring. Classic deploy can set `SCANNER_V4_DB_CLAIM_NAME` to pass the same name to both charts; a custom claim specified only in `ROX_CENTRAL_EXTRA_HELM_VALUES_FILE` will not change the monitoring chart. + ## OpenShift When the cluster exposes `security.openshift.io/v1/SecurityContextConstraints`, the chart: diff --git a/deploy/charts/monitoring/templates/prometheus.yaml b/deploy/charts/monitoring/templates/prometheus.yaml index 0f81a5b2835c9..849f65facc268 100644 --- a/deploy/charts/monitoring/templates/prometheus.yaml +++ b/deploy/charts/monitoring/templates/prometheus.yaml @@ -58,6 +58,28 @@ data: {{ toYaml .Values.cadvisorMetricRelabelConfigs | indent 10 }} {{- end }} + # Kubelet /metrics exposes kubelet_volume_stats_* (PVC used/capacity), + # which cadvisor does not. Used for central-db and scanner-v4-db PVC space + # alerting. + - job_name: "kubernetes-kubelet" + scheme: https + metrics_path: /metrics + tls_config: + ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt + insecure_skip_verify: true + authorization: + credentials_file: /var/run/secrets/kubernetes.io/serviceaccount/token + kubernetes_sd_configs: + - role: node + relabel_configs: + - action: labelmap + regex: __meta_kubernetes_node_label_(.+) + metric_relabel_configs: + # Keep only the StackRox DB PVC volume stats needed for space alerting. + - source_labels: [__name__, persistentvolumeclaim] + action: keep + regex: kubelet_volume_stats_(used|capacity)_bytes;(central-db|{{ .Values.scannerV4DbPvcClaimName | regexQuoteMeta }}) + - job_name: stackrox tls_config: insecure_skip_verify: false @@ -109,3 +131,55 @@ data: annotations: summary: Kubernetes ReplicaSet mismatch (replicaset {{ "{{" }} $labels.replicaset {{ "}}" }}) description: "Replicas mismatch in ReplicaSet {{ "{{" }} $labels.exported_namespace {{ "}}" }}/{{ "{{" }} $labels.replicaset {{ "}}" }}" + + rules_alerts_stackrox.yml: |- + groups: + - name: StackRox + rules: + # Central DB PVC space utilization (used / filesystem capacity) from the + # kubelet volume stats. Exposed as a stable metric for dashboards and + # reused by the alerts below. + - record: stackrox:central_db_pvc_usage_ratio + expr: kubelet_volume_stats_used_bytes{persistentvolumeclaim="central-db"} / kubelet_volume_stats_capacity_bytes{persistentvolumeclaim="central-db"} + + - alert: StackRoxCentralDbPvcSpaceWarning + expr: stackrox:central_db_pvc_usage_ratio > {{ .Values.centralDbPvcUsageWarningRatio }} + for: 5m + labels: + severity: warning + annotations: + summary: Central DB PVC filling up (pvc {{ "{{" }} $labels.persistentvolumeclaim {{ "}}" }}) + description: "Central DB PVC {{ "{{" }} $labels.namespace {{ "}}" }}/{{ "{{" }} $labels.persistentvolumeclaim {{ "}}" }} is {{ "{{" }} $value | humanizePercentage {{ "}}" }} full." + + - alert: StackRoxCentralDbPvcSpaceCritical + expr: stackrox:central_db_pvc_usage_ratio > {{ .Values.centralDbPvcUsageCriticalRatio }} + for: 5m + labels: + severity: critical + annotations: + summary: Central DB PVC almost full (pvc {{ "{{" }} $labels.persistentvolumeclaim {{ "}}" }}) + description: "Central DB PVC {{ "{{" }} $labels.namespace {{ "}}" }}/{{ "{{" }} $labels.persistentvolumeclaim {{ "}}" }} is {{ "{{" }} $value | humanizePercentage {{ "}}" }} full." + + # Scanner V4 DB PVC space utilization (used / filesystem capacity) from the + # kubelet volume stats. Exposed as a stable metric for dashboards and + # reused by the alerts below. + - record: stackrox:scanner_v4_db_pvc_usage_ratio + expr: kubelet_volume_stats_used_bytes{persistentvolumeclaim={{ .Values.scannerV4DbPvcClaimName | quote }}} / kubelet_volume_stats_capacity_bytes{persistentvolumeclaim={{ .Values.scannerV4DbPvcClaimName | quote }}} + + - alert: StackRoxScannerV4DbPvcSpaceWarning + expr: stackrox:scanner_v4_db_pvc_usage_ratio > {{ .Values.scannerV4DbPvcUsageWarningRatio }} + for: 1h + labels: + severity: warning + annotations: + summary: Scanner V4 DB PVC filling up (pvc {{ "{{" }} $labels.persistentvolumeclaim {{ "}}" }}) + description: "Scanner V4 DB PVC {{ "{{" }} $labels.namespace {{ "}}" }}/{{ "{{" }} $labels.persistentvolumeclaim {{ "}}" }} is {{ "{{" }} $value | humanizePercentage {{ "}}" }} full." + + - alert: StackRoxScannerV4DbPvcSpaceCritical + expr: stackrox:scanner_v4_db_pvc_usage_ratio > {{ .Values.scannerV4DbPvcUsageCriticalRatio }} + for: 5m + labels: + severity: critical + annotations: + summary: Scanner V4 DB PVC almost full (pvc {{ "{{" }} $labels.persistentvolumeclaim {{ "}}" }}) + description: "Scanner V4 DB PVC {{ "{{" }} $labels.namespace {{ "}}" }}/{{ "{{" }} $labels.persistentvolumeclaim {{ "}}" }} is {{ "{{" }} $value | humanizePercentage {{ "}}" }} full." diff --git a/deploy/charts/monitoring/values.yaml b/deploy/charts/monitoring/values.yaml index 2b644d260b240..e84192fbcfc43 100644 --- a/deploy/charts/monitoring/values.yaml +++ b/deploy/charts/monitoring/values.yaml @@ -27,6 +27,19 @@ nodeSelector: {} # Metric relabel configs for kubernetes-cadvisor job to filter out metrics cadvisorMetricRelabelConfigs: [] +# Central DB PVC space-usage alerting. Utilization is used/capacity from the +# kubelet volume stats (scraped by the kubernetes-kubelet job). The warning and +# critical alerts fire when the central-db PVC exceeds these fill ratios. +centralDbPvcUsageWarningRatio: 0.8 +centralDbPvcUsageCriticalRatio: 0.9 + +# Scanner V4 DB PVC space-usage alerting. Same mechanism as central-db above; the +# warning and critical alerts fire when this PVC exceeds these fill ratios. +# Match scannerV4.db.persistence.persistentVolumeClaim.claimName in the Central chart. +scannerV4DbPvcClaimName: scanner-v4-db +scannerV4DbPvcUsageWarningRatio: 0.8 +scannerV4DbPvcUsageCriticalRatio: 0.9 + kube-state-metrics: releaseNamespace: true # Align with the parent monitoring Deployment UIDs. Upstream defaults use diff --git a/deploy/common/env.sh b/deploy/common/env.sh index 7d105df97857c..0b50d6c1d6ac6 100644 --- a/deploy/common/env.sh +++ b/deploy/common/env.sh @@ -88,10 +88,12 @@ echo "ROX_DECLARATIVE_CONFIGURATION is set to ${ROX_DECLARATIVE_CONFIGURATION}" export ROX_MANAGED_CENTRAL="${ROX_MANAGED_CENTRAL:-false}" echo "ROX_MANAGED_CENTRAL is set to ${ROX_MANAGED_CENTRAL}" -export ROX_CENTRAL_WORKER_ENABLED="${ROX_CENTRAL_WORKER_ENABLED:-false}" +# central-worker is enabled by default in the central-services chart. Disabling it is only +# possible through Helm values, which the kubectl output format does not support. +export ROX_CENTRAL_WORKER_ENABLED="${ROX_CENTRAL_WORKER_ENABLED:-true}" echo "ROX_CENTRAL_WORKER_ENABLED is set to ${ROX_CENTRAL_WORKER_ENABLED}" -if [[ "${ROX_CENTRAL_WORKER_ENABLED}" == "true" && "${OUTPUT_FORMAT}" != "helm" ]]; then - echo "ROX_CENTRAL_WORKER_ENABLED requires OUTPUT_FORMAT=helm, overriding" +if [[ "${ROX_CENTRAL_WORKER_ENABLED}" == "false" && "${OUTPUT_FORMAT}" != "helm" ]]; then + echo "ROX_CENTRAL_WORKER_ENABLED=false requires OUTPUT_FORMAT=helm, overriding" export OUTPUT_FORMAT="helm" fi diff --git a/deploy/common/k8sbased.sh b/deploy/common/k8sbased.sh index c8bd48861efb1..a205b6eb430f2 100644 --- a/deploy/common/k8sbased.sh +++ b/deploy/common/k8sbased.sh @@ -322,6 +322,9 @@ function launch_central { --set persistence.type="${STORAGE}" --set exposure.type="${MONITORING_LOAD_BALANCER}" ) + if [[ -n "${SCANNER_V4_DB_CLAIM_NAME:-}" ]]; then + helm_args+=(--set-string "scannerV4DbPvcClaimName=${SCANNER_V4_DB_CLAIM_NAME}") + fi if [[ "${is_local_dev}" == "true" ]]; then helm_args+=(-f "${COMMON_DIR}/monitoring-values-local.yaml") fi @@ -457,9 +460,9 @@ function launch_central { fi fi - if [[ "${ROX_CENTRAL_WORKER_ENABLED:-}" == "true" ]]; then + if [[ -n "${ROX_CENTRAL_WORKER_ENABLED:-}" ]]; then helm_args+=( - --set centralWorker.enabled=true + --set "centralWorker.enabled=${ROX_CENTRAL_WORKER_ENABLED}" ) fi @@ -515,6 +518,9 @@ function launch_central { if [[ -n "${SCANNER_V4_DB_STORAGE_CLASS}" ]]; then helm_args+=(--set "scannerV4.db.persistence.persistentVolumeClaim.storageClass=${SCANNER_V4_DB_STORAGE_CLASS}") fi + if [[ -n "${SCANNER_V4_DB_CLAIM_NAME:-}" ]]; then + helm_args+=(--set-string "scannerV4.db.persistence.persistentVolumeClaim.claimName=${SCANNER_V4_DB_CLAIM_NAME}") + fi # Add a custom values file to Helm if [[ -n "$ROX_CENTRAL_EXTRA_HELM_VALUES_FILE" ]]; then @@ -1001,7 +1007,7 @@ function launch_sensor { if [[ "${sensor_namespace}" != "stackrox" ]]; then kubectl create namespace "${sensor_namespace}" &>/dev/null || true - kubectl -n "${sensor_namespace}" get secret stackrox &>/dev/null || kubectl -n "${sensor_namespace}" create -f - < <("${common_dir}/pull-secret.sh" stackrox docker.io) + kubectl -n "${sensor_namespace}" get secret stackrox &>/dev/null || kubectl -n "${sensor_namespace}" create -f - < <("${common_dir}/../../scripts/pull-secret.sh" stackrox docker.io) fi if [[ -n "${ROX_PROCESS_INDICATORS_PER_NAMESPACE}" ]]; then diff --git a/deploy/deploy.sh b/deploy/deploy.sh index d431739141589..a9b091f08ccbd 100755 --- a/deploy/deploy.sh +++ b/deploy/deploy.sh @@ -3,13 +3,13 @@ set -e DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd)" -if [[ "${USE_ROXIE_DEPLOY:-false}" == "true" ]]; then +if [[ "${USE_ROXIE_DEPLOY:-true}" == "true" ]]; then ROOT="$( cd "$( dirname "${BASH_SOURCE[0]}" )/.." && pwd)" if [[ "${SKIP_ROXIE_DEPLOY_BANNER:-false}" != "true" ]]; then cat <\n" + + "expiration\x12?\n" + + "\n" + + "deployment\x18\x05 \x01(\v2\x1d.storage.Exclusion.DeploymentH\x00R\n" + + "deployment\x12J\n" + + "\x0fexclude_by_kind\x18\b \x01(\v2 .storage.Exclusion.ExcludeByKindH\x00R\rexcludeByKind\x1a>\n" + "\tContainer\x121\n" + "\n" + "image_name\x18\x03 \x01(\v2\x12.storage.ImageNameR\timageName\x1aF\n" + @@ -1448,7 +1585,14 @@ const file_storage_policy_proto_rawDesc = "" + "\x04name\x18\x03 \x01(\tR\x04name\x12$\n" + "\x05scope\x18\x04 \x01(\v2\x0e.storage.ScopeR\x05scope\x1a\x1b\n" + "\x05Image\x12\x12\n" + - "\x04name\x18\x01 \x01(\tR\x04nameJ\x04\b\x02\x10\x03\"E\n" + + "\x04name\x18\x01 \x01(\tR\x04name\x1aF\n" + + "\rExcludeByKind\x125\n" + + "\x05kinds\x18\x01 \x03(\x0e2\x1f.storage.Exclusion.WorkloadKindR\x05kinds\"D\n" + + "\fWorkloadKind\x12\x1d\n" + + "\x19WORKLOAD_KIND_UNSPECIFIED\x10\x00\x12\f\n" + + "\bCRON_JOB\x10\x01\x12\a\n" + + "\x03JOB\x10\x02B\t\n" + + "\amatcherJ\x04\b\x02\x10\x03\"E\n" + "\x16ExportPoliciesResponse\x12+\n" + "\bpolicies\x18\x01 \x03(\v2\x0f.storage.PolicyR\bpolicies*/\n" + "\fPolicySource\x12\x0e\n" + @@ -1506,8 +1650,8 @@ func file_storage_policy_proto_rawDescGZIP() []byte { return file_storage_policy_proto_rawDescData } -var file_storage_policy_proto_enumTypes = make([]protoimpl.EnumInfo, 7) -var file_storage_policy_proto_msgTypes = make([]protoimpl.MessageInfo, 13) +var file_storage_policy_proto_enumTypes = make([]protoimpl.EnumInfo, 8) +var file_storage_policy_proto_msgTypes = make([]protoimpl.MessageInfo, 14) var file_storage_policy_proto_goTypes = []any{ (PolicySource)(0), // 0: storage.PolicySource (EventSource)(0), // 1: storage.EventSource @@ -1516,58 +1660,62 @@ var file_storage_policy_proto_goTypes = []any{ (Severity)(0), // 4: storage.Severity (LifecycleStage)(0), // 5: storage.LifecycleStage (Comparator)(0), // 6: storage.Comparator - (*Policy)(nil), // 7: storage.Policy - (*PolicySection)(nil), // 8: storage.PolicySection - (*PolicyGroup)(nil), // 9: storage.PolicyGroup - (*PolicyValue)(nil), // 10: storage.PolicyValue - (*PolicyList)(nil), // 11: storage.PolicyList - (*ListPolicy)(nil), // 12: storage.ListPolicy - (*EvaluationFilter)(nil), // 13: storage.EvaluationFilter - (*Exclusion)(nil), // 14: storage.Exclusion - (*ExportPoliciesResponse)(nil), // 15: storage.ExportPoliciesResponse - (*Policy_MitreAttackVectors)(nil), // 16: storage.Policy.MitreAttackVectors - (*Exclusion_Container)(nil), // 17: storage.Exclusion.Container - (*Exclusion_Deployment)(nil), // 18: storage.Exclusion.Deployment - (*Exclusion_Image)(nil), // 19: storage.Exclusion.Image - (*Scope)(nil), // 20: storage.Scope - (*timestamppb.Timestamp)(nil), // 21: google.protobuf.Timestamp - (ContainerType)(0), // 22: storage.ContainerType - (*ImageName)(nil), // 23: storage.ImageName + (Exclusion_WorkloadKind)(0), // 7: storage.Exclusion.WorkloadKind + (*Policy)(nil), // 8: storage.Policy + (*PolicySection)(nil), // 9: storage.PolicySection + (*PolicyGroup)(nil), // 10: storage.PolicyGroup + (*PolicyValue)(nil), // 11: storage.PolicyValue + (*PolicyList)(nil), // 12: storage.PolicyList + (*ListPolicy)(nil), // 13: storage.ListPolicy + (*EvaluationFilter)(nil), // 14: storage.EvaluationFilter + (*Exclusion)(nil), // 15: storage.Exclusion + (*ExportPoliciesResponse)(nil), // 16: storage.ExportPoliciesResponse + (*Policy_MitreAttackVectors)(nil), // 17: storage.Policy.MitreAttackVectors + (*Exclusion_Container)(nil), // 18: storage.Exclusion.Container + (*Exclusion_Deployment)(nil), // 19: storage.Exclusion.Deployment + (*Exclusion_Image)(nil), // 20: storage.Exclusion.Image + (*Exclusion_ExcludeByKind)(nil), // 21: storage.Exclusion.ExcludeByKind + (*Scope)(nil), // 22: storage.Scope + (*timestamppb.Timestamp)(nil), // 23: google.protobuf.Timestamp + (ContainerType)(0), // 24: storage.ContainerType + (*ImageName)(nil), // 25: storage.ImageName } var file_storage_policy_proto_depIdxs = []int32{ 5, // 0: storage.Policy.lifecycle_stages:type_name -> storage.LifecycleStage 1, // 1: storage.Policy.event_source:type_name -> storage.EventSource - 14, // 2: storage.Policy.exclusions:type_name -> storage.Exclusion - 20, // 3: storage.Policy.scope:type_name -> storage.Scope + 15, // 2: storage.Policy.exclusions:type_name -> storage.Exclusion + 22, // 3: storage.Policy.scope:type_name -> storage.Scope 4, // 4: storage.Policy.severity:type_name -> storage.Severity 3, // 5: storage.Policy.enforcement_actions:type_name -> storage.EnforcementAction - 21, // 6: storage.Policy.last_updated:type_name -> google.protobuf.Timestamp - 8, // 7: storage.Policy.policy_sections:type_name -> storage.PolicySection - 16, // 8: storage.Policy.mitre_attack_vectors:type_name -> storage.Policy.MitreAttackVectors + 23, // 6: storage.Policy.last_updated:type_name -> google.protobuf.Timestamp + 9, // 7: storage.Policy.policy_sections:type_name -> storage.PolicySection + 17, // 8: storage.Policy.mitre_attack_vectors:type_name -> storage.Policy.MitreAttackVectors 0, // 9: storage.Policy.source:type_name -> storage.PolicySource - 13, // 10: storage.Policy.evaluation_filter:type_name -> storage.EvaluationFilter - 9, // 11: storage.PolicySection.policy_groups:type_name -> storage.PolicyGroup + 14, // 10: storage.Policy.evaluation_filter:type_name -> storage.EvaluationFilter + 10, // 11: storage.PolicySection.policy_groups:type_name -> storage.PolicyGroup 2, // 12: storage.PolicyGroup.boolean_operator:type_name -> storage.BooleanOperator - 10, // 13: storage.PolicyGroup.values:type_name -> storage.PolicyValue - 7, // 14: storage.PolicyList.policies:type_name -> storage.Policy + 11, // 13: storage.PolicyGroup.values:type_name -> storage.PolicyValue + 8, // 14: storage.PolicyList.policies:type_name -> storage.Policy 4, // 15: storage.ListPolicy.severity:type_name -> storage.Severity 5, // 16: storage.ListPolicy.lifecycle_stages:type_name -> storage.LifecycleStage - 21, // 17: storage.ListPolicy.last_updated:type_name -> google.protobuf.Timestamp + 23, // 17: storage.ListPolicy.last_updated:type_name -> google.protobuf.Timestamp 1, // 18: storage.ListPolicy.event_source:type_name -> storage.EventSource 0, // 19: storage.ListPolicy.source:type_name -> storage.PolicySource - 13, // 20: storage.ListPolicy.evaluation_filter:type_name -> storage.EvaluationFilter - 22, // 21: storage.EvaluationFilter.skip_container_types:type_name -> storage.ContainerType - 18, // 22: storage.Exclusion.deployment:type_name -> storage.Exclusion.Deployment - 19, // 23: storage.Exclusion.image:type_name -> storage.Exclusion.Image - 21, // 24: storage.Exclusion.expiration:type_name -> google.protobuf.Timestamp - 7, // 25: storage.ExportPoliciesResponse.policies:type_name -> storage.Policy - 23, // 26: storage.Exclusion.Container.image_name:type_name -> storage.ImageName - 20, // 27: storage.Exclusion.Deployment.scope:type_name -> storage.Scope - 28, // [28:28] is the sub-list for method output_type - 28, // [28:28] is the sub-list for method input_type - 28, // [28:28] is the sub-list for extension type_name - 28, // [28:28] is the sub-list for extension extendee - 0, // [0:28] is the sub-list for field type_name + 14, // 20: storage.ListPolicy.evaluation_filter:type_name -> storage.EvaluationFilter + 24, // 21: storage.EvaluationFilter.skip_container_types:type_name -> storage.ContainerType + 20, // 22: storage.Exclusion.image:type_name -> storage.Exclusion.Image + 23, // 23: storage.Exclusion.expiration:type_name -> google.protobuf.Timestamp + 19, // 24: storage.Exclusion.deployment:type_name -> storage.Exclusion.Deployment + 21, // 25: storage.Exclusion.exclude_by_kind:type_name -> storage.Exclusion.ExcludeByKind + 8, // 26: storage.ExportPoliciesResponse.policies:type_name -> storage.Policy + 25, // 27: storage.Exclusion.Container.image_name:type_name -> storage.ImageName + 22, // 28: storage.Exclusion.Deployment.scope:type_name -> storage.Scope + 7, // 29: storage.Exclusion.ExcludeByKind.kinds:type_name -> storage.Exclusion.WorkloadKind + 30, // [30:30] is the sub-list for method output_type + 30, // [30:30] is the sub-list for method input_type + 30, // [30:30] is the sub-list for extension type_name + 30, // [30:30] is the sub-list for extension extendee + 0, // [0:30] is the sub-list for field type_name } func init() { file_storage_policy_proto_init() } @@ -1578,13 +1726,17 @@ func file_storage_policy_proto_init() { file_storage_deployment_proto_init() file_storage_image_proto_init() file_storage_scope_proto_init() + file_storage_policy_proto_msgTypes[7].OneofWrappers = []any{ + (*Exclusion_Deployment_)(nil), + (*Exclusion_ExcludeByKind_)(nil), + } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_storage_policy_proto_rawDesc), len(file_storage_policy_proto_rawDesc)), - NumEnums: 7, - NumMessages: 13, + NumEnums: 8, + NumMessages: 14, NumExtensions: 0, NumServices: 0, }, diff --git a/generated/storage/policy_vtproto.pb.go b/generated/storage/policy_vtproto.pb.go index e24289130948a..4e4e98f768d0f 100644 --- a/generated/storage/policy_vtproto.pb.go +++ b/generated/storage/policy_vtproto.pb.go @@ -325,15 +325,38 @@ func (m *Exclusion_Image) CloneMessageVT() proto.Message { return m.CloneVT() } +func (m *Exclusion_ExcludeByKind) CloneVT() *Exclusion_ExcludeByKind { + if m == nil { + return (*Exclusion_ExcludeByKind)(nil) + } + r := new(Exclusion_ExcludeByKind) + if rhs := m.Kinds; rhs != nil { + tmpContainer := make([]Exclusion_WorkloadKind, len(rhs)) + copy(tmpContainer, rhs) + r.Kinds = tmpContainer + } + if len(m.unknownFields) > 0 { + r.unknownFields = make([]byte, len(m.unknownFields)) + copy(r.unknownFields, m.unknownFields) + } + return r +} + +func (m *Exclusion_ExcludeByKind) CloneMessageVT() proto.Message { + return m.CloneVT() +} + func (m *Exclusion) CloneVT() *Exclusion { if m == nil { return (*Exclusion)(nil) } r := new(Exclusion) r.Name = m.Name - r.Deployment = m.Deployment.CloneVT() r.Image = m.Image.CloneVT() r.Expiration = (*timestamppb.Timestamp)((*timestamppb1.Timestamp)(m.Expiration).CloneVT()) + if m.Matcher != nil { + r.Matcher = m.Matcher.(interface{ CloneVT() isExclusion_Matcher }).CloneVT() + } if len(m.unknownFields) > 0 { r.unknownFields = make([]byte, len(m.unknownFields)) copy(r.unknownFields, m.unknownFields) @@ -345,6 +368,24 @@ func (m *Exclusion) CloneMessageVT() proto.Message { return m.CloneVT() } +func (m *Exclusion_Deployment_) CloneVT() isExclusion_Matcher { + if m == nil { + return (*Exclusion_Deployment_)(nil) + } + r := new(Exclusion_Deployment_) + r.Deployment = m.Deployment.CloneVT() + return r +} + +func (m *Exclusion_ExcludeByKind_) CloneVT() isExclusion_Matcher { + if m == nil { + return (*Exclusion_ExcludeByKind_)(nil) + } + r := new(Exclusion_ExcludeByKind_) + r.ExcludeByKind = m.ExcludeByKind.CloneVT() + return r +} + func (m *ExportPoliciesResponse) CloneVT() *ExportPoliciesResponse { if m == nil { return (*ExportPoliciesResponse)(nil) @@ -849,16 +890,50 @@ func (this *Exclusion_Image) EqualMessageVT(thatMsg proto.Message) bool { } return this.EqualVT(that) } +func (this *Exclusion_ExcludeByKind) EqualVT(that *Exclusion_ExcludeByKind) bool { + if this == that { + return true + } else if this == nil || that == nil { + return false + } + if len(this.Kinds) != len(that.Kinds) { + return false + } + for i, vx := range this.Kinds { + vy := that.Kinds[i] + if vx != vy { + return false + } + } + return string(this.unknownFields) == string(that.unknownFields) +} + +func (this *Exclusion_ExcludeByKind) EqualMessageVT(thatMsg proto.Message) bool { + that, ok := thatMsg.(*Exclusion_ExcludeByKind) + if !ok { + return false + } + return this.EqualVT(that) +} func (this *Exclusion) EqualVT(that *Exclusion) bool { if this == that { return true } else if this == nil || that == nil { return false } - if this.Name != that.Name { + if this.Matcher == nil && that.Matcher != nil { return false + } else if this.Matcher != nil { + if that.Matcher == nil { + return false + } + if !this.Matcher.(interface { + EqualVT(isExclusion_Matcher) bool + }).EqualVT(that.Matcher) { + return false + } } - if !this.Deployment.EqualVT(that.Deployment) { + if this.Name != that.Name { return false } if !(*timestamppb1.Timestamp)(this.Expiration).EqualVT((*timestamppb1.Timestamp)(that.Expiration)) { @@ -877,6 +952,56 @@ func (this *Exclusion) EqualMessageVT(thatMsg proto.Message) bool { } return this.EqualVT(that) } +func (this *Exclusion_Deployment_) EqualVT(thatIface isExclusion_Matcher) bool { + that, ok := thatIface.(*Exclusion_Deployment_) + if !ok { + return false + } + if this == that { + return true + } + if this == nil && that != nil || this != nil && that == nil { + return false + } + if p, q := this.Deployment, that.Deployment; p != q { + if p == nil { + p = &Exclusion_Deployment{} + } + if q == nil { + q = &Exclusion_Deployment{} + } + if !p.EqualVT(q) { + return false + } + } + return true +} + +func (this *Exclusion_ExcludeByKind_) EqualVT(thatIface isExclusion_Matcher) bool { + that, ok := thatIface.(*Exclusion_ExcludeByKind_) + if !ok { + return false + } + if this == that { + return true + } + if this == nil && that != nil || this != nil && that == nil { + return false + } + if p, q := this.ExcludeByKind, that.ExcludeByKind; p != q { + if p == nil { + p = &Exclusion_ExcludeByKind{} + } + if q == nil { + q = &Exclusion_ExcludeByKind{} + } + if !p.EqualVT(q) { + return false + } + } + return true +} + func (this *ExportPoliciesResponse) EqualVT(that *ExportPoliciesResponse) bool { if this == that { return true @@ -1797,6 +1922,60 @@ func (m *Exclusion_Image) MarshalToSizedBufferVT(dAtA []byte) (int, error) { return len(dAtA) - i, nil } +func (m *Exclusion_ExcludeByKind) MarshalVT() (dAtA []byte, err error) { + if m == nil { + return nil, nil + } + size := m.SizeVT() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBufferVT(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Exclusion_ExcludeByKind) MarshalToVT(dAtA []byte) (int, error) { + size := m.SizeVT() + return m.MarshalToSizedBufferVT(dAtA[:size]) +} + +func (m *Exclusion_ExcludeByKind) MarshalToSizedBufferVT(dAtA []byte) (int, error) { + if m == nil { + return 0, nil + } + i := len(dAtA) + _ = i + var l int + _ = l + if m.unknownFields != nil { + i -= len(m.unknownFields) + copy(dAtA[i:], m.unknownFields) + } + if len(m.Kinds) > 0 { + var pksize2 int + for _, num := range m.Kinds { + pksize2 += protohelpers.SizeOfVarint(uint64(num)) + } + i -= pksize2 + j1 := i + for _, num1 := range m.Kinds { + num := uint64(num1) + for num >= 1<<7 { + dAtA[j1] = uint8(uint64(num)&0x7f | 0x80) + num >>= 7 + j1++ + } + dAtA[j1] = uint8(num) + j1++ + } + i = protohelpers.EncodeVarint(dAtA, i, uint64(pksize2)) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + func (m *Exclusion) MarshalVT() (dAtA []byte, err error) { if m == nil { return nil, nil @@ -1827,6 +2006,15 @@ func (m *Exclusion) MarshalToSizedBufferVT(dAtA []byte) (int, error) { i -= len(m.unknownFields) copy(dAtA[i:], m.unknownFields) } + if vtmsg, ok := m.Matcher.(interface { + MarshalToSizedBufferVT([]byte) (int, error) + }); ok { + size, err := vtmsg.MarshalToSizedBufferVT(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + } if m.Image != nil { size, err := m.Image.MarshalToSizedBufferVT(dAtA[:i]) if err != nil { @@ -1847,6 +2035,23 @@ func (m *Exclusion) MarshalToSizedBufferVT(dAtA []byte) (int, error) { i-- dAtA[i] = 0x32 } + if len(m.Name) > 0 { + i -= len(m.Name) + copy(dAtA[i:], m.Name) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.Name))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *Exclusion_Deployment_) MarshalToVT(dAtA []byte) (int, error) { + size := m.SizeVT() + return m.MarshalToSizedBufferVT(dAtA[:size]) +} + +func (m *Exclusion_Deployment_) MarshalToSizedBufferVT(dAtA []byte) (int, error) { + i := len(dAtA) if m.Deployment != nil { size, err := m.Deployment.MarshalToSizedBufferVT(dAtA[:i]) if err != nil { @@ -1856,17 +2061,36 @@ func (m *Exclusion) MarshalToSizedBufferVT(dAtA []byte) (int, error) { i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) i-- dAtA[i] = 0x2a - } - if len(m.Name) > 0 { - i -= len(m.Name) - copy(dAtA[i:], m.Name) - i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.Name))) + } else { + i = protohelpers.EncodeVarint(dAtA, i, 0) i-- - dAtA[i] = 0xa + dAtA[i] = 0x2a } return len(dAtA) - i, nil } +func (m *Exclusion_ExcludeByKind_) MarshalToVT(dAtA []byte) (int, error) { + size := m.SizeVT() + return m.MarshalToSizedBufferVT(dAtA[:size]) +} +func (m *Exclusion_ExcludeByKind_) MarshalToSizedBufferVT(dAtA []byte) (int, error) { + i := len(dAtA) + if m.ExcludeByKind != nil { + size, err := m.ExcludeByKind.MarshalToSizedBufferVT(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0x42 + } else { + i = protohelpers.EncodeVarint(dAtA, i, 0) + i-- + dAtA[i] = 0x42 + } + return len(dAtA) - i, nil +} func (m *ExportPoliciesResponse) MarshalVT() (dAtA []byte, err error) { if m == nil { return nil, nil @@ -2253,6 +2477,23 @@ func (m *Exclusion_Image) SizeVT() (n int) { return n } +func (m *Exclusion_ExcludeByKind) SizeVT() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.Kinds) > 0 { + l = 0 + for _, e := range m.Kinds { + l += protohelpers.SizeOfVarint(uint64(e)) + } + n += 1 + protohelpers.SizeOfVarint(uint64(l)) + l + } + n += len(m.unknownFields) + return n +} + func (m *Exclusion) SizeVT() (n int) { if m == nil { return 0 @@ -2263,9 +2504,8 @@ func (m *Exclusion) SizeVT() (n int) { if l > 0 { n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) } - if m.Deployment != nil { - l = m.Deployment.SizeVT() - n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + if vtmsg, ok := m.Matcher.(interface{ SizeVT() int }); ok { + n += vtmsg.SizeVT() } if m.Expiration != nil { l = (*timestamppb1.Timestamp)(m.Expiration).SizeVT() @@ -2279,6 +2519,34 @@ func (m *Exclusion) SizeVT() (n int) { return n } +func (m *Exclusion_Deployment_) SizeVT() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Deployment != nil { + l = m.Deployment.SizeVT() + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } else { + n += 2 + } + return n +} +func (m *Exclusion_ExcludeByKind_) SizeVT() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.ExcludeByKind != nil { + l = m.ExcludeByKind.SizeVT() + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } else { + n += 2 + } + return n +} func (m *ExportPoliciesResponse) SizeVT() (n int) { if m == nil { return 0 @@ -4551,6 +4819,126 @@ func (m *Exclusion_Image) UnmarshalVT(dAtA []byte) error { } return nil } +func (m *Exclusion_ExcludeByKind) UnmarshalVT(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Exclusion_ExcludeByKind: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Exclusion_ExcludeByKind: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType == 0 { + var v Exclusion_WorkloadKind + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= Exclusion_WorkloadKind(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.Kinds = append(m.Kinds, v) + } else if wireType == 2 { + var packedLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + packedLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if packedLen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + packedLen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + var elementCount int + if elementCount != 0 && len(m.Kinds) == 0 { + m.Kinds = make([]Exclusion_WorkloadKind, 0, elementCount) + } + for iNdEx < postIndex { + var v Exclusion_WorkloadKind + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= Exclusion_WorkloadKind(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.Kinds = append(m.Kinds, v) + } + } else { + return fmt.Errorf("proto: wrong wireType = %d for field Kinds", wireType) + } + default: + iNdEx = preIndex + skippy, err := protohelpers.Skip(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return protohelpers.ErrInvalidLength + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func (m *Exclusion) UnmarshalVT(dAtA []byte) error { l := len(dAtA) iNdEx := 0 @@ -4641,11 +5029,16 @@ func (m *Exclusion) UnmarshalVT(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - if m.Deployment == nil { - m.Deployment = &Exclusion_Deployment{} - } - if err := m.Deployment.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { - return err + if oneof, ok := m.Matcher.(*Exclusion_Deployment_); ok { + if err := oneof.Deployment.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } + } else { + v := &Exclusion_Deployment{} + if err := v.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } + m.Matcher = &Exclusion_Deployment_{Deployment: v} } iNdEx = postIndex case 6: @@ -4720,6 +5113,47 @@ func (m *Exclusion) UnmarshalVT(dAtA []byte) error { return err } iNdEx = postIndex + case 8: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ExcludeByKind", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if oneof, ok := m.Matcher.(*Exclusion_ExcludeByKind_); ok { + if err := oneof.ExcludeByKind.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } + } else { + v := &Exclusion_ExcludeByKind{} + if err := v.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } + m.Matcher = &Exclusion_ExcludeByKind_{ExcludeByKind: v} + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := protohelpers.Skip(dAtA[iNdEx:]) @@ -7167,6 +7601,126 @@ func (m *Exclusion_Image) UnmarshalVTUnsafe(dAtA []byte) error { } return nil } +func (m *Exclusion_ExcludeByKind) UnmarshalVTUnsafe(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Exclusion_ExcludeByKind: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Exclusion_ExcludeByKind: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType == 0 { + var v Exclusion_WorkloadKind + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= Exclusion_WorkloadKind(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.Kinds = append(m.Kinds, v) + } else if wireType == 2 { + var packedLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + packedLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if packedLen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + packedLen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + var elementCount int + if elementCount != 0 && len(m.Kinds) == 0 { + m.Kinds = make([]Exclusion_WorkloadKind, 0, elementCount) + } + for iNdEx < postIndex { + var v Exclusion_WorkloadKind + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= Exclusion_WorkloadKind(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.Kinds = append(m.Kinds, v) + } + } else { + return fmt.Errorf("proto: wrong wireType = %d for field Kinds", wireType) + } + default: + iNdEx = preIndex + skippy, err := protohelpers.Skip(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return protohelpers.ErrInvalidLength + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func (m *Exclusion) UnmarshalVTUnsafe(dAtA []byte) error { l := len(dAtA) iNdEx := 0 @@ -7261,11 +7815,16 @@ func (m *Exclusion) UnmarshalVTUnsafe(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - if m.Deployment == nil { - m.Deployment = &Exclusion_Deployment{} - } - if err := m.Deployment.UnmarshalVTUnsafe(dAtA[iNdEx:postIndex]); err != nil { - return err + if oneof, ok := m.Matcher.(*Exclusion_Deployment_); ok { + if err := oneof.Deployment.UnmarshalVTUnsafe(dAtA[iNdEx:postIndex]); err != nil { + return err + } + } else { + v := &Exclusion_Deployment{} + if err := v.UnmarshalVTUnsafe(dAtA[iNdEx:postIndex]); err != nil { + return err + } + m.Matcher = &Exclusion_Deployment_{Deployment: v} } iNdEx = postIndex case 6: @@ -7340,6 +7899,47 @@ func (m *Exclusion) UnmarshalVTUnsafe(dAtA []byte) error { return err } iNdEx = postIndex + case 8: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ExcludeByKind", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if oneof, ok := m.Matcher.(*Exclusion_ExcludeByKind_); ok { + if err := oneof.ExcludeByKind.UnmarshalVTUnsafe(dAtA[iNdEx:postIndex]); err != nil { + return err + } + } else { + v := &Exclusion_ExcludeByKind{} + if err := v.UnmarshalVTUnsafe(dAtA[iNdEx:postIndex]); err != nil { + return err + } + m.Matcher = &Exclusion_ExcludeByKind_{ExcludeByKind: v} + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := protohelpers.Skip(dAtA[iNdEx:]) diff --git a/go.mod b/go.mod index 1d8f786ac5d42..c853c31da7fff 100644 --- a/go.mod +++ b/go.mod @@ -4,14 +4,14 @@ go 1.26.7 require ( cloud.google.com/go/artifactregistry v1.27.0 - cloud.google.com/go/compute/metadata v0.9.1 + cloud.google.com/go/compute/metadata v0.10.0 cloud.google.com/go/containeranalysis v0.20.0 - cloud.google.com/go/securitycenter v1.46.0 - cloud.google.com/go/storage v1.64.0 + cloud.google.com/go/securitycenter v1.47.0 + cloud.google.com/go/storage v1.69.0 dario.cat/mergo v1.0.2 github.com/Azure/azure-sdk-for-go-extensions v0.7.0 - github.com/Azure/azure-sdk-for-go/sdk/azcore v1.23.1 - github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.14.0 + github.com/Azure/azure-sdk-for-go/sdk/azcore v1.23.3 + github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.14.1 github.com/Azure/azure-sdk-for-go/sdk/containers/azcontainerregistry v0.2.3 github.com/Azure/azure-sdk-for-go/sdk/monitor/ingestion/azlogs v1.1.0 github.com/BurntSushi/toml v1.6.0 @@ -23,25 +23,25 @@ require ( github.com/RoaringBitmap/roaring/v2 v2.29.0 github.com/Shopify/toxiproxy/v2 v2.12.0 github.com/VividCortex/ewma v1.2.0 - github.com/adhocore/gronx v1.20.4 + github.com/adhocore/gronx v1.20.5 github.com/andygrunwald/go-jira v1.17.0 - github.com/aws/aws-sdk-go-v2 v1.47.1 - github.com/aws/aws-sdk-go-v2/config v1.33.6 - github.com/aws/aws-sdk-go-v2/credentials v1.20.6 - github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.20.1 - github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.23.10 - github.com/aws/aws-sdk-go-v2/service/ecr v1.66.1 - github.com/aws/aws-sdk-go-v2/service/s3 v1.113.4 - github.com/aws/aws-sdk-go-v2/service/securityhub v1.82.1 - github.com/aws/aws-sdk-go-v2/service/sts v1.51.1 - github.com/aws/smithy-go v1.28.2 - github.com/bmatcuk/doublestar/v4 v4.10.0 + github.com/aws/aws-sdk-go-v2 v1.47.3 + github.com/aws/aws-sdk-go-v2/config v1.33.9 + github.com/aws/aws-sdk-go-v2/credentials v1.20.9 + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.20.3 + github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.23.14 + github.com/aws/aws-sdk-go-v2/service/ecr v1.66.4 + github.com/aws/aws-sdk-go-v2/service/s3 v1.114.3 + github.com/aws/aws-sdk-go-v2/service/securityhub v1.84.1 + github.com/aws/aws-sdk-go-v2/service/sts v1.51.4 + github.com/aws/smithy-go v1.28.5 + github.com/bmatcuk/doublestar/v4 v4.10.2 github.com/cenkalti/backoff/v4 v4.3.0 github.com/cespare/xxhash/v2 v2.3.0 github.com/cloudflare/cfssl v1.6.5 - github.com/cockroachdb/pebble/v2 v2.1.6 + github.com/cockroachdb/pebble/v2 v2.1.7 github.com/containers/image/v5 v5.36.2 - github.com/coreos/go-oidc/v3 v3.20.0 + github.com/coreos/go-oidc/v3 v3.21.0 github.com/coreos/go-systemd/v22 v22.7.0 github.com/dave/jennifer v1.7.1 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc @@ -54,23 +54,23 @@ require ( github.com/go-logr/logr v1.4.4 github.com/go-logr/zapr v1.3.0 github.com/go-viper/mapstructure/v2 v2.5.0 - github.com/gobwas/glob v0.2.3 - github.com/godbus/dbus/v5 v5.2.2 + github.com/gobwas/glob v1.0.0 + github.com/godbus/dbus/v5 v5.2.3 github.com/golang-jwt/jwt/v5 v5.3.1 github.com/google/certificate-transparency-go v1.3.3 github.com/google/gnostic-models v0.7.1 github.com/google/go-cmp v0.7.0 - github.com/google/go-containerregistry v0.21.9 + github.com/google/go-containerregistry v0.22.1 github.com/google/go-github/v91 v91.0.0 github.com/google/uuid v1.6.0 - github.com/googleapis/gax-go/v2 v2.26.0 + github.com/googleapis/gax-go/v2 v2.26.2 github.com/gorilla/schema v1.4.1 github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 - github.com/grafana/pyroscope-go v1.4.2 - github.com/graph-gophers/graphql-go v1.10.3-0.20260702060009-60399cce0c05 - github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.3 + github.com/grafana/pyroscope-go v1.4.3 + github.com/graph-gophers/graphql-go v1.10.3 + github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.4 github.com/grpc-ecosystem/go-grpc-prometheus v1.2.1-0.20210315223345-82c243799c99 - github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0 + github.com/grpc-ecosystem/grpc-gateway/v2 v2.31.0 github.com/hashicorp/go-multierror v1.1.1 github.com/hashicorp/go-retryablehttp v0.7.8 github.com/hashicorp/go-version v1.9.0 @@ -78,10 +78,10 @@ require ( github.com/heimdalr/dag v1.5.1 github.com/helm/helm-mapkubeapis v0.6.1 github.com/heroku/docker-registry-client v0.0.0 - github.com/jackc/pgx/v5 v5.10.0 + github.com/jackc/pgx/v5 v5.11.0 github.com/jeremywohl/flatten v1.0.1 github.com/joshdk/go-junit v1.0.0 - github.com/klauspost/compress v1.20.0 + github.com/klauspost/compress v1.20.3 github.com/lib/pq v1.12.3 github.com/machinebox/graphql v0.2.2 github.com/mailru/easyjson v0.9.2 @@ -90,34 +90,34 @@ require ( github.com/np-guard/cluster-topology-analyzer/v2 v2.3.0 github.com/np-guard/netpol-analyzer v1.4.4 github.com/nxadm/tail v1.4.11 - github.com/olekukonko/tablewriter v1.1.4 - github.com/onsi/ginkgo/v2 v2.32.1 - github.com/onsi/gomega v1.43.1 + github.com/olekukonko/tablewriter v1.1.5 + github.com/onsi/ginkgo/v2 v2.33.1 + github.com/onsi/gomega v1.44.0 github.com/opencontainers/go-digest v1.0.0 github.com/opencontainers/image-spec v1.1.1 - github.com/openshift-online/ocm-sdk-go v0.1.509 + github.com/openshift-online/ocm-sdk-go v0.1.517 github.com/openshift/api v0.0.0-20260317165824-54a3998d81eb github.com/openshift/client-go v0.0.0-20260317180604-743f664b82d1 github.com/openshift/controller-runtime-common v0.0.0-20260318085703-1812aed6dbd2 github.com/openshift/library-go v0.0.0-20260318142011-72bf34f474bc github.com/openshift/runtime-utils v0.0.0-20230921210328-7bdb5b9c177b github.com/operator-framework/helm-operator-plugins v0.0.0-00010101000000-000000000000 - github.com/owenrumney/go-sarif/v2 v2.3.3 + github.com/owenrumney/go-sarif/v3 v3.3.1 github.com/package-url/packageurl-go v0.1.7 github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c github.com/pkg/errors v0.9.1 github.com/planetscale/vtprotobuf v0.6.1-0.20240409071808-615f978279ca github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 - github.com/prometheus/client_golang v1.24.1 + github.com/prometheus/client_golang v1.25.0 github.com/prometheus/client_model v0.6.3 - github.com/prometheus/common v0.71.0 + github.com/prometheus/common v0.72.1 github.com/quay/claircore v1.6.1-0.20260916195820-3902930ff815 github.com/quay/claircore/toolkit v1.7.0 github.com/quay/zlog/v2 v2.1.1 github.com/remind101/migrate v0.0.0-20170729031349-52c1edff7319 github.com/russellhaering/gosaml2 v0.12.0 github.com/russellhaering/goxmldsig v1.6.1 - github.com/segmentio/analytics-go/v3 v3.3.0 + github.com/segmentio/analytics-go/v3 v3.4.0 github.com/sergi/go-diff v1.4.0 github.com/sigstore/cosign/v3 v3.1.3 github.com/sigstore/rekor v1.5.4 @@ -135,29 +135,29 @@ require ( github.com/stackrox/scanner v0.0.0-20240830165150-d133ba942d59 github.com/stolostron/cluster-lifecycle-api v0.0.0-20260330032750-43755d6ceb09 github.com/stretchr/testify v1.12.1 - github.com/tidwall/gjson v1.19.0 + github.com/tidwall/gjson v1.20.0 github.com/tkuchiki/go-timezone v0.2.3 github.com/travelaudience/go-promhttp v1.0.1 github.com/vbauerster/mpb/v4 v4.12.2 - go.uber.org/atomic v1.11.0 + go.uber.org/atomic v1.12.0 go.uber.org/goleak v1.3.0 go.uber.org/mock v0.6.0 go.uber.org/zap v1.28.0 go.yaml.in/yaml/v3 v3.0.5 - golang.org/x/crypto v0.57.0 - golang.org/x/mod v0.41.0 - golang.org/x/net v0.59.0 + golang.org/x/crypto v0.58.0 + golang.org/x/mod v0.42.0 + golang.org/x/net v0.61.0 golang.org/x/oauth2 v0.37.0 - golang.org/x/sync v0.23.0 - golang.org/x/sys v0.48.0 - golang.org/x/term v0.46.0 - golang.org/x/text v0.42.0 + golang.org/x/sync v0.24.0 + golang.org/x/sys v0.49.0 + golang.org/x/term v0.47.0 + golang.org/x/text v0.43.0 golang.org/x/time v0.16.0 - golang.org/x/tools v0.50.0 + golang.org/x/tools v0.52.0 golang.stackrox.io/grpc-http1 v0.5.1 - google.golang.org/api v0.299.0 + google.golang.org/api v0.301.0 google.golang.org/genproto v0.0.0-20260715232425-e75dac1f907d - google.golang.org/genproto/googleapis/api v0.0.0-20260803160001-6ac0973c030d + google.golang.org/genproto/googleapis/api v0.0.0-20260921155816-b14227669459 google.golang.org/grpc v1.84.0 google.golang.org/grpc/examples v0.0.0-20250407062114-b368379ef8f6 google.golang.org/protobuf v1.36.12 @@ -165,22 +165,22 @@ require ( gopkg.in/natefinch/lumberjack.v2 v2.2.1 gopkg.in/robfig/cron.v2 v2.0.0-20150107220207-be2e0b0deed5 gopkg.in/yaml.v3 v3.0.1 - gorm.io/driver/postgres v1.6.2 + gorm.io/driver/postgres v1.6.3 gorm.io/gorm v1.31.2 - helm.sh/helm/v3 v3.21.3 - k8s.io/api v0.36.5 - k8s.io/apiextensions-apiserver v0.36.5 - k8s.io/apimachinery v0.36.5 - k8s.io/apiserver v0.36.5 - k8s.io/cli-runtime v0.36.5 - k8s.io/client-go v0.36.5 - k8s.io/kubectl v0.36.5 + helm.sh/helm/v3 v3.22.0 + k8s.io/api v0.37.1 + k8s.io/apiextensions-apiserver v0.37.1 + k8s.io/apimachinery v0.37.1 + k8s.io/apiserver v0.37.1 + k8s.io/cli-runtime v0.37.1 + k8s.io/client-go v0.37.1 + k8s.io/kubectl v0.37.1 k8s.io/kubelet v0.32.13 - k8s.io/utils v0.0.0-20260319190234-28399d86e0b5 - kubevirt.io/api v1.9.0 - kubevirt.io/client-go v1.10.0-alpha.0 - sigs.k8s.io/controller-runtime v0.24.1 - sigs.k8s.io/controller-tools v0.21.0 + k8s.io/utils v0.0.0-20260626114624-be93311217bd + kubevirt.io/api v1.9.1 + kubevirt.io/client-go v1.10.0-beta.0 + sigs.k8s.io/controller-runtime v0.25.2 + sigs.k8s.io/controller-tools v0.22.0 sigs.k8s.io/e2e-framework v0.7.0 sigs.k8s.io/yaml v1.6.0 ) @@ -189,18 +189,18 @@ require ( al.essio.dev/pkg/shellescape v1.6.0 // indirect cel.dev/expr v0.25.2 // indirect cloud.google.com/go v0.123.0 // indirect - cloud.google.com/go/auth v0.23.3 // indirect - cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect + cloud.google.com/go/auth v0.24.1-0.20261001053825-dbc26066f70a // indirect + cloud.google.com/go/auth/oauth2adapt v0.3.0 // indirect cloud.google.com/go/iam v1.13.0 // indirect cloud.google.com/go/longrunning v1.2.0 // indirect cloud.google.com/go/monitoring v1.30.0 // indirect - github.com/Azure/azure-sdk-for-go/sdk/internal v1.12.0 // indirect + github.com/Azure/azure-sdk-for-go/sdk/internal v1.13.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/containerservice/armcontainerservice/v9 v9.0.0 // indirect github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect - github.com/AzureAD/microsoft-authentication-library-for-go v1.7.2 // indirect + github.com/AzureAD/microsoft-authentication-library-for-go v1.8.0 // indirect github.com/ComplianceAsCode/compliance-sdk v0.0.0-20250925092238-0b432985772e // indirect github.com/DataDog/zstd v1.5.7 // indirect - github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.34.0 // indirect + github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.35.0 // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.57.0 // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.57.0 // indirect github.com/MakeNowJust/heredoc v1.0.0 // indirect @@ -216,23 +216,22 @@ require ( github.com/andybalholm/brotli v1.1.0 // indirect github.com/antlr4-go/antlr/v4 v4.13.1 // indirect github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect - github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.20 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.5.4 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.8.4 // indirect - github.com/aws/aws-sdk-go-v2/internal/v4a v1.5.4 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.19 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.11.5 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.14.4 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.20.4 // indirect - github.com/aws/aws-sdk-go-v2/service/signin v1.10.1 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.38.1 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.43.1 // indirect + github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.22 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.5.6 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.8.6 // indirect + github.com/aws/aws-sdk-go-v2/internal/v4a v1.5.6 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.21 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.11.7 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.14.6 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.20.6 // indirect + github.com/aws/aws-sdk-go-v2/service/signin v1.10.4 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.38.4 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.43.4 // indirect github.com/aymerick/douceur v0.2.0 // indirect github.com/beevik/etree v1.7.0 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bits-and-blooms/bitset v1.24.4 // indirect github.com/blang/semver/v4 v4.0.0 // indirect - github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 // indirect github.com/cenkalti/backoff/v5 v5.0.3 // indirect github.com/chai2010/gettext-go v1.0.2 // indirect github.com/clipperhouse/displaywidth v0.10.0 // indirect @@ -243,7 +242,7 @@ require ( github.com/cockroachdb/errors v1.12.0 // indirect github.com/cockroachdb/logtags v0.0.0-20241215232642-bb51bb14a506 // indirect github.com/cockroachdb/redact v1.1.6 // indirect - github.com/cockroachdb/swiss v0.0.0-20251224182025-b0f6560f979b // indirect + github.com/cockroachdb/swiss v0.0.0-20260820225851-333444432258 // indirect github.com/cockroachdb/tokenbucket v0.0.0-20250429170803-42689b6311bb // indirect github.com/coder/websocket v1.8.14 // indirect github.com/containers/storage v1.59.1 // indirect @@ -252,7 +251,7 @@ require ( github.com/cyphar/filepath-securejoin v0.7.0 // indirect github.com/digitorus/pkcs7 v0.0.0-20230818184609-3a137a874352 // indirect github.com/digitorus/timestamp v0.0.0-20231217203849-220c5c2851b7 // indirect - github.com/docker/cli v29.6.2+incompatible // indirect + github.com/docker/cli v29.7.2+incompatible // indirect github.com/docker/docker-credential-helpers v0.9.5 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 // indirect @@ -283,29 +282,28 @@ require ( github.com/go-kit/log v0.2.1 // indirect github.com/go-logfmt/logfmt v0.6.0 // indirect github.com/go-logr/stdr v1.2.2 // indirect - github.com/go-openapi/analysis v0.26.0 // indirect - github.com/go-openapi/errors v0.22.8 // indirect - github.com/go-openapi/jsonpointer v1.0.0 // indirect - github.com/go-openapi/jsonreference v1.0.0 // indirect - github.com/go-openapi/loads v0.25.1 // indirect - github.com/go-openapi/runtime v0.33.0 // indirect - github.com/go-openapi/runtime/server-middleware v0.30.0 // indirect - github.com/go-openapi/spec v0.22.9 // indirect - github.com/go-openapi/strfmt v0.27.0 // indirect - github.com/go-openapi/swag v0.26.1 // indirect - github.com/go-openapi/swag/cmdutils v0.27.0 // indirect - github.com/go-openapi/swag/conv v0.28.0 // indirect - github.com/go-openapi/swag/fileutils v0.28.0 // indirect - github.com/go-openapi/swag/jsonname v0.26.1 // indirect - github.com/go-openapi/swag/jsonutils v0.28.0 // indirect - github.com/go-openapi/swag/loading v0.28.0 // indirect - github.com/go-openapi/swag/mangling v0.28.0 // indirect - github.com/go-openapi/swag/netutils v0.27.0 // indirect - github.com/go-openapi/swag/pools v0.28.0 // indirect - github.com/go-openapi/swag/stringutils v0.28.0 // indirect - github.com/go-openapi/swag/typeutils v0.28.0 // indirect - github.com/go-openapi/swag/yamlutils v0.28.0 // indirect - github.com/go-openapi/validate v0.26.3 // indirect + github.com/go-openapi/analysis v1.0.0 // indirect + github.com/go-openapi/errors v0.22.9 // indirect + github.com/go-openapi/jsonpointer v1.0.1 // indirect + github.com/go-openapi/jsonreference v1.0.1 // indirect + github.com/go-openapi/loads v0.25.2 // indirect + github.com/go-openapi/runtime v0.33.2 // indirect + github.com/go-openapi/runtime/server-middleware v0.33.2 // indirect + github.com/go-openapi/spec v1.0.0 // indirect + github.com/go-openapi/strfmt v0.27.2 // indirect + github.com/go-openapi/swag v0.29.2 // indirect + github.com/go-openapi/swag/cmdutils v0.29.2 // indirect + github.com/go-openapi/swag/conv v0.29.2 // indirect + github.com/go-openapi/swag/fileutils v0.29.2 // indirect + github.com/go-openapi/swag/jsonutils v0.29.2 // indirect + github.com/go-openapi/swag/loading v0.29.2 // indirect + github.com/go-openapi/swag/mangling v0.29.2 // indirect + github.com/go-openapi/swag/netutils v0.29.2 // indirect + github.com/go-openapi/swag/pools v0.29.2 // indirect + github.com/go-openapi/swag/stringutils v0.29.2 // indirect + github.com/go-openapi/swag/typeutils v0.29.2 // indirect + github.com/go-openapi/swag/yamlutils v0.29.2 // indirect + github.com/go-openapi/validate v1.0.0 // indirect github.com/go-task/slim-sprig/v3 v3.0.0 // indirect github.com/gobuffalo/flect v1.0.3 // indirect github.com/gogo/protobuf v1.3.2 // indirect @@ -317,7 +315,7 @@ require ( github.com/google/cel-go v0.31.0 // indirect github.com/google/go-querystring v1.2.0 // indirect github.com/google/pprof v0.0.0-20260802141513-ef3492d7dac3 // indirect - github.com/google/s2a-go v0.1.10 // indirect + github.com/google/s2a-go v0.1.11 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.22 // indirect github.com/gorilla/css v1.0.0 // indirect github.com/gosuri/uitable v0.0.4 // indirect @@ -368,7 +366,7 @@ require ( github.com/moby/spdystream v0.5.1 // indirect github.com/moby/sys/capability v0.4.0 // indirect github.com/moby/sys/mountinfo v0.7.2 // indirect - github.com/moby/sys/user v0.4.0 // indirect + github.com/moby/sys/user v0.4.1 // indirect github.com/moby/term v0.5.2 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect @@ -379,13 +377,13 @@ require ( github.com/nozzle/throttler v0.0.0-20180817012639-2ea982251481 // indirect github.com/np-guard/models v0.5.7 // indirect github.com/nwaples/rardecode v1.1.3 // indirect - github.com/oklog/ulid/v2 v2.1.1 // indirect + github.com/oklog/ulid/v2 v2.1.2 // indirect github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6 // indirect github.com/olekukonko/errors v1.2.0 // indirect github.com/olekukonko/ll v0.1.6 // indirect github.com/opencontainers/runtime-spec v1.2.1 // indirect - github.com/openshift-online/ocm-api-model/clientapi v0.0.464 // indirect - github.com/openshift-online/ocm-api-model/model v0.0.464 // indirect + github.com/openshift-online/ocm-api-model/clientapi v0.0.470 // indirect + github.com/openshift-online/ocm-api-model/model v0.0.470 // indirect github.com/openshift/custom-resource-status v1.1.2 // indirect github.com/operator-framework/operator-lib v0.17.0 // indirect github.com/pelletier/go-toml v1.9.5 // indirect @@ -393,7 +391,7 @@ require ( github.com/peterbourgon/diskv v2.0.1+incompatible // indirect github.com/pierrec/lz4/v4 v4.1.21 // indirect github.com/pjbgf/sha1cd v0.6.0 // indirect - github.com/prometheus/procfs v0.21.1 // indirect + github.com/prometheus/procfs v0.22.0 // indirect github.com/quay/claircore/updater/driver v1.0.0 // indirect github.com/quay/goval-parser v0.8.8 // indirect github.com/redis/go-redis/extra/redisotel/v9 v9.5.3 // indirect @@ -402,10 +400,9 @@ require ( github.com/rubenv/sql-migrate v1.8.1 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/sagikazarmark/locafero v0.11.0 // indirect - github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 // indirect + github.com/santhosh-tekuri/jsonschema/v6 v6.0.3 // indirect github.com/sassoftware/relic/v8 v8.2.0 // indirect github.com/secure-systems-lab/go-securesystemslib v0.11.0 // indirect - github.com/segmentio/backo-go v1.0.1 // indirect github.com/shibumi/go-pathspec v1.3.0 // indirect github.com/shopspring/decimal v1.4.0 // indirect github.com/sigstore/protobuf-specs v0.5.1 // indirect @@ -436,41 +433,44 @@ require ( github.com/weppos/publicsuffix-go v0.50.2 // indirect github.com/x448/float16 v0.8.4 // indirect github.com/xanzy/ssh-agent v0.3.3 // indirect + github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect + github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect + github.com/xeipuuv/gojsonschema v1.2.0 // indirect github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect github.com/xlab/treeprint v1.2.0 // indirect github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect github.com/zmap/zcrypto v0.0.0-20250129210703-03c45d0bae98 // indirect github.com/zmap/zlint/v3 v3.6.6 // indirect - go.etcd.io/bbolt v1.4.3 // indirect + go.etcd.io/bbolt v1.5.0 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect - go.opentelemetry.io/contrib/detectors/gcp v1.44.0 // indirect - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.68.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0 // indirect - go.opentelemetry.io/otel v1.45.0 // indirect + go.opentelemetry.io/contrib/detectors/gcp v1.45.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.71.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.71.0 // indirect + go.opentelemetry.io/otel v1.46.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.45.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.45.0 // indirect - go.opentelemetry.io/otel/metric v1.45.0 // indirect - go.opentelemetry.io/otel/sdk v1.45.0 // indirect - go.opentelemetry.io/otel/sdk/metric v1.45.0 // indirect - go.opentelemetry.io/otel/trace v1.45.0 // indirect + go.opentelemetry.io/otel/metric v1.46.0 // indirect + go.opentelemetry.io/otel/sdk v1.46.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.46.0 // indirect + go.opentelemetry.io/otel/trace v1.46.0 // indirect go.opentelemetry.io/proto/otlp v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.yaml.in/yaml/v2 v2.4.4 // indirect - golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f // indirect + golang.org/x/exp v0.0.0-20260908205506-85c1c2202aba // indirect gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20260921155816-b14227669459 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260928230214-8a89bd6388cc // indirect gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect - k8s.io/code-generator v0.36.5 // indirect - k8s.io/component-base v0.36.5 // indirect - k8s.io/component-helpers v0.36.5 // indirect - k8s.io/gengo/v2 v2.0.0-20250922181213-ec3ebc5fd46b // indirect + k8s.io/code-generator v0.37.1 // indirect + k8s.io/component-base v0.37.1 // indirect + k8s.io/component-helpers v0.37.1 // indirect + k8s.io/gengo/v2 v2.0.0-20260408192533-25e2208e0dc3 // indirect k8s.io/klog/v2 v2.140.0 // indirect k8s.io/kube-openapi v0.31.0 // indirect - k8s.io/streaming v0.36.5 // indirect + k8s.io/streaming v0.37.1 // indirect kubevirt.io/containerized-data-importer-api v1.64.0 // indirect kubevirt.io/controller-lifecycle-operator-sdk/api v0.2.4 // indirect modernc.org/libc v1.75.6 // indirect @@ -478,13 +478,13 @@ require ( modernc.org/memory v1.12.1 // indirect modernc.org/sqlite v1.58.0 // indirect oras.land/oras-go/v2 v2.6.2 // indirect - sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.34.0 // indirect + sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.36.0 // indirect sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect sigs.k8s.io/kustomize/api v0.21.1 // indirect sigs.k8s.io/kustomize/kyaml v0.21.1 // indirect sigs.k8s.io/network-policy-api v0.1.5 // indirect sigs.k8s.io/randfill v1.0.0 // indirect - sigs.k8s.io/structured-merge-diff/v6 v6.4.0 // indirect + sigs.k8s.io/structured-merge-diff/v6 v6.4.2 // indirect ) // HOW TO BUMP diff --git a/go.sum b/go.sum index afacb4c1f634e..738b265515d1e 100644 --- a/go.sum +++ b/go.sum @@ -34,10 +34,10 @@ 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/artifactregistry v1.27.0 h1:EBgdk9sXNAVyWJ3hnTmWRdlmKhGDKCjkJbLoF3EsyWE= cloud.google.com/go/artifactregistry v1.27.0/go.mod h1:r0rZgbNmn5ppdqwUMF182yc7EFH6vabJMbXM+PlfW9A= -cloud.google.com/go/auth v0.23.3 h1:UMK+oBtuNGMCR/6i6mmySUItqjOazpJrbmZyhGbGBWo= -cloud.google.com/go/auth v0.23.3/go.mod h1:fClbry28fo7XkxhSeT6AQtAVAp6Jy0fW9N99PoPNPFM= -cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc= -cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c= +cloud.google.com/go/auth v0.24.1-0.20261001053825-dbc26066f70a h1:jW1ZLFy9oa5ai8LOI40z6s+DL1cO/L1KSm9hVGnibn8= +cloud.google.com/go/auth v0.24.1-0.20261001053825-dbc26066f70a/go.mod h1:1dmBXa9xfBvBr39ZnDUDkaaA91ccq9CcqENDgJA4d7c= +cloud.google.com/go/auth/oauth2adapt v0.3.0 h1:FY8oSZpCYoUNv6QxVODuMjQz4IlSOVeiQtZ08vLPz88= +cloud.google.com/go/auth/oauth2adapt v0.3.0/go.mod h1:7+2uCm7++XFO+/lN06c2HXpDXb/NMNn2/UwyBPbTnkk= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= @@ -45,8 +45,8 @@ cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUM cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= -cloud.google.com/go/compute/metadata v0.9.1 h1:CTE1OWBQ0vnF5uHwdFAQJvMQ0Fi/KRcqqKTo9V0F8Ik= -cloud.google.com/go/compute/metadata v0.9.1/go.mod h1:NtnlvB6X3t4R6xSWyVX/ZWk493PCxGQlhI/iqxh4M8I= +cloud.google.com/go/compute/metadata v0.10.0 h1:pyKMUQSwchgkIBBJGdILqQbs/BNJXqwSA7Ej6LAvvtY= +cloud.google.com/go/compute/metadata v0.10.0/go.mod h1:rGFHRrIif570kSibjFTMbt6/4/tzgJWFGI/HVol4GIk= cloud.google.com/go/containeranalysis v0.20.0 h1:5tko84zYirVH+FEU6JPuT/xGBvSagsllYxH7m8ytjyI= cloud.google.com/go/containeranalysis v0.20.0/go.mod h1:cJzgbuNqFTWswsVcT0eemqdqtOEq4dJEFvAH3cgzkyc= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= @@ -66,15 +66,15 @@ cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2k cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= -cloud.google.com/go/securitycenter v1.46.0 h1:MfzR4Bj5W51MAIyJlA4molK/vj0xO64te4yCGAbwXL0= -cloud.google.com/go/securitycenter v1.46.0/go.mod h1:7mAlzsCsKlEVmciAFORl431laDGpoKGFkSQndAzFs30= +cloud.google.com/go/securitycenter v1.47.0 h1:cKjSLURYE5tewWgH6VW6Jo6YaZWG+FUUFh/SmrqrcHU= +cloud.google.com/go/securitycenter v1.47.0/go.mod h1:OWKAGzP36rJ4pFSjU441g+vi7WTRdMFRVw1rykvMwPE= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= -cloud.google.com/go/storage v1.64.0 h1:KLpxI/oX9LxeRsNqn877d2WyeT3ryiEwnGt8pwcSPZg= -cloud.google.com/go/storage v1.64.0/go.mod h1:lWyAtwvDZHdL3k68WVKbESP6bmWaV23ZJJ/JEVw/ZaQ= +cloud.google.com/go/storage v1.69.0 h1:jAAMC1411HEh78nKsU0Zns+eFj3TnhjAWIhg5Ud/XBM= +cloud.google.com/go/storage v1.69.0/go.mod h1:PELYsxTYm2peE4mwLEC1+mS1dA/kUSRUxNv56rOy44g= cloud.google.com/go/trace v1.16.0 h1:GmQovzFc5F0CNfl0VLgL64aoTtu7xsM0YajW2GlG9+E= cloud.google.com/go/trace v1.16.0/go.mod h1:r+bdAn16dKLSV1G2D5v3e58IlQlizfxWrUfjx7kM7X0= dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8= @@ -92,16 +92,16 @@ github.com/AdamKorcz/go-fuzz-headers-1 v0.0.0-20230919221257-8b5d3ce2d11d/go.mod github.com/Azure/azure-sdk-for-go v68.0.0+incompatible h1:fcYLmCpyNYRnvJbPerq7U0hS+6+I79yEDJBqVNcqUzU= github.com/Azure/azure-sdk-for-go-extensions v0.7.0 h1:ko7jxarxFmfm3OAtIkKcnC2yK1H7a7wB7As38huaa4Y= github.com/Azure/azure-sdk-for-go-extensions v0.7.0/go.mod h1:f/wRrqvvh197V5r4jGADV7528UdO/zfL+/Ud92BMSag= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.23.1 h1:zvXfGJCWvywnCA814d8ZiVyt+fm9nnTE8xSb99zRyfo= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.23.1/go.mod h1:iptorS+VYKFL2N6PnebpS91dubG35eAOEERnT4PJbQU= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.14.0 h1:CU4+EJeJi3TKYWEcYuSdWsjzw0nVsK/H0MSQOiPcymU= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.14.0/go.mod h1:q0+UTSRvShwUCrR/s5HtyInYphN7Wvxb7snFM3u+SLA= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.23.3 h1:qgnWTiWs1G7AqcKZegFvkJD1bmXJcaDU3uC0ELSgZ5U= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.23.3/go.mod h1:XtxD48QPInqoj+GMlcRlXHEYsoOxFnP6w2JgIZ9rDS4= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.14.1 h1:u93s+zU2JD62im61Bm5CZIc1ZrOJaIAWEg0WOrMVkEo= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.14.1/go.mod h1:oXtinPO4OLj9d1DOTrqrL1oRwGhcqadvAmrl6wTeGlk= github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.4.0 h1:xFaZZ+IubdftrDHnGGwZ6QvQ3KHTtWl2MCK+GMt2vxs= github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.4.0/go.mod h1:mCBhUhlMjLLJKr5aqw2TNS/VqJOie8MzWq3DAMJeKso= github.com/Azure/azure-sdk-for-go/sdk/containers/azcontainerregistry v0.2.3 h1:ldKsKtEIblsgsr6mPwrd9yRntoX6uLz/K89wsldwx/k= github.com/Azure/azure-sdk-for-go/sdk/containers/azcontainerregistry v0.2.3/go.mod h1:MAm7bk0oDLmD8yIkvfbxPW04fxzphPyL+7GzwHxOp6Y= -github.com/Azure/azure-sdk-for-go/sdk/internal v1.12.0 h1:fhqpLE3UEXi9lPaBRpQ6XuRW0nU7hgg4zlmZZa+a9q4= -github.com/Azure/azure-sdk-for-go/sdk/internal v1.12.0/go.mod h1:7dCRMLwisfRH3dBupKeNCioWYUZ4SS09Z14H+7i8ZoY= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.13.0 h1:Terj1CMW4+NY5CpWjSRkqLoxh2DkYDdv/FH2z0897Uc= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.13.0/go.mod h1:7Ut2Qw9vcwpLW5Q4MBy43uSxXS7tK2yjs94OJ6j5thE= github.com/Azure/azure-sdk-for-go/sdk/monitor/ingestion/azlogs v1.1.0 h1:Q+tp/BW0x11uAm5i9f2xEu3RZ3wy89KNYfDVCWFHUJQ= github.com/Azure/azure-sdk-for-go/sdk/monitor/ingestion/azlogs v1.1.0/go.mod h1:et3yi6OrdxM8YK0pfOwpHSLf4gWypxQVWh4T9wRzg3k= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/containerservice/armcontainerservice/v9 v9.0.0 h1:z5qKE92WxOYrrU2yWL7fL0XW7vVBjMKyaXOITK+Z3bI= @@ -118,8 +118,8 @@ github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEK 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.7.2 h1:RHK7bS+HQMslb1sZpAokUt+zTVmue0hKSs2C791hhzU= -github.com/AzureAD/microsoft-authentication-library-for-go v1.7.2/go.mod h1:HKpQxkWaGLJ+D/5H8QRpyQXA1eKjxkFlOMwck5+33Jk= +github.com/AzureAD/microsoft-authentication-library-for-go v1.8.0 h1:Nljr4q1GRA/5vCrMONS+g4u4LRHNgOXVSh3O43J2CnI= +github.com/AzureAD/microsoft-authentication-library-for-go v1.8.0/go.mod h1:Y33QHnf0FfdVewFFISOGe20mkZbxX4H839o955/PoeI= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk= @@ -135,8 +135,8 @@ github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/DataDog/zstd v1.5.7 h1:ybO8RBeh29qrxIhCA9E8gKY6xfONU9T6G6aP9DTKfLE= github.com/DataDog/zstd v1.5.7/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.34.0 h1:yzIYdwuro811Z27D3T80Wkd3rqZzb0K43nner7Eh1yE= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.34.0/go.mod h1:pJTkW8hEUIIi3Pf65lPZOnn4Y81yCllX6IWk2jNXdkM= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.35.0 h1:bN1gA3of5bXtbnLsRPrwfmbbe7A5UWFlcTHseujLnpc= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.35.0/go.mod h1:Yj5vHEz/aAepZGliRJsA6uvHAVAQyEwajq9ORCHPxzM= github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.57.0 h1:jLdiS1vO+XJFyDSWRHBx56r4s/NNtcl5J6KyCcWUX/w= github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.57.0/go.mod h1:8lmpHY+1VRoteiOwyrQMDt1YGXOrFKCz+1wJW7n3ODY= github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.57.0 h1:cSjUzZ7KU8hicTgzaSv9NmSyM9fTVK3y5lsBUl3wOis= @@ -182,8 +182,8 @@ github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpH github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo= github.com/aclements/go-perfevent v0.0.0-20240301234650-f7843625020f h1:JjxwchlOepwsUWcQwD2mLUAGE9aCp0/ehy6yCHFBOvo= github.com/aclements/go-perfevent v0.0.0-20240301234650-f7843625020f/go.mod h1:tMDTce/yLLN/SK8gMOxQfnyeMeCg8KGzp0D1cbECEeo= -github.com/adhocore/gronx v1.20.4 h1:vE2UKHC3mRH4DF+tKiq/kOnENTuKaEois2aD0McyHiU= -github.com/adhocore/gronx v1.20.4/go.mod h1:7oUY1WAU8rEJWmAxXR2DN0JaO4gi9khSgKjiRypqteg= +github.com/adhocore/gronx v1.20.5 h1:UgXvkD97Hovm7yA1TTRTd309o9F1h7C8gX04rktaxnE= +github.com/adhocore/gronx v1.20.5/go.mod h1:7oUY1WAU8rEJWmAxXR2DN0JaO4gi9khSgKjiRypqteg= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= @@ -203,7 +203,6 @@ github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuW github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= -github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-metrics v0.3.10/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc= @@ -213,50 +212,50 @@ github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPd github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= -github.com/aws/aws-sdk-go-v2 v1.47.1 h1:uOIZnp4PK3ZhKI0dNrJrhTEsLxbpXHTAJlwoS1pvAtw= -github.com/aws/aws-sdk-go-v2 v1.47.1/go.mod h1:bttEH6JqnUL8LepvDVfdrds/fZ5bCIxzpe3abyUrhDU= -github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.20 h1:GPRlPwz40I2B2VrBEASOA3Bi77NyeqejNLkifosX0rs= -github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.20/go.mod h1:g7PNzKcsOKWb4fkSRBA7BZVAS6Y8IcxzN+nRohhQ1Q8= -github.com/aws/aws-sdk-go-v2/config v1.33.6 h1:MBjkSTLczek/UgiK+EYPIoRTqE7gP8vtW3OFbFo7Nug= -github.com/aws/aws-sdk-go-v2/config v1.33.6/go.mod h1:grRAFzdAZJrwcbasJRg2MPvIrVjtlfXllHssN6+E1JE= -github.com/aws/aws-sdk-go-v2/credentials v1.20.6 h1:NpAFXCU7NzXNkdGK3zQTtsRJ+3v9tZQV0xcdRw8uBdw= -github.com/aws/aws-sdk-go-v2/credentials v1.20.6/go.mod h1:mcZCoiPnyMvP8VMNbygNX5lLqSlkYJIMPODylQMurOk= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.20.1 h1:8gALAAmacnIXh+z6VkdDanv4/IkG5APdg4DZLDTmLog= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.20.1/go.mod h1:Z7IJhJU+poOdJjUR2wpyY21ossQ1XS/R3Lk9Msq5kM4= -github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.23.10 h1:OYuXRtpSLUZA6TrtqfU42xi1zTS8uCpQlTode7VhDjE= -github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.23.10/go.mod h1:rWXRqN139C+pJzsA88pZRee5NBB1FqcDIo7dG9NlX48= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.5.4 h1:CLq4+8UHCI+ZZYl/EuJxXovaIVN2xeeT8JV+dsApQ5E= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.5.4/go.mod h1:Wv4q5sAM04xAMkoOedxLx2inVf6K5FdxYp+A61L+q/0= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.8.4 h1:dD4MR81I7YkpEBRk6UP9rocC2QnT3qVuXwzlYTtfGEs= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.8.4/go.mod h1:EcXV1kAFd5XwSkDHlj94gnF3q5CkJyYiIJfH8N0VmrE= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.5.4 h1:7Wo47d/xn/7KttCSBd8EGYeZ7ULRFRkUHr6vkZPBzVQ= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.5.4/go.mod h1:tDB2IVC1xC3vX8o+6uRlzhTxP3g1b77CZXFX/oD2FnQ= -github.com/aws/aws-sdk-go-v2/service/ecr v1.66.1 h1:H63vyEXid/tHpv/UlvQUyM1c2QK5WgQRB3MK5gnAo8A= -github.com/aws/aws-sdk-go-v2/service/ecr v1.66.1/go.mod h1:WglfLchOYcHrYOwNV7jERuy0Xc+7jArLkEnQay93auY= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.19 h1:bAdDl/HkGCcGPoe25ToSHEw23VIxt6CT5fLcg111BKg= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.19/go.mod h1:KaUzbLxv4CeSxh6ZCl9B4m7CuFenS8kUEaDs+f/DQr4= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.11.5 h1:/TYsZXdA8UTa+WCtCYSAJIr1vwl0+eho6TUgJGwFFO8= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.11.5/go.mod h1:qPqp1Uwd/BqdhPufv6oem9j5J7HNsgc2V22dUiDPn+s= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.14.4 h1:29SvnfGhXjTl8ONxFwbj2rs6lbhiFXD2CgFQmbT/bXY= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.14.4/go.mod h1:wm04I5DMuNVvZHFe/dHnUxincvNbbK7AiNBbYsQivek= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.20.4 h1:pPiWfgeNxqluKEph7hvU88kuGKBPOWzO+Dk9t2zqqNs= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.20.4/go.mod h1:YlwGoIUDG/3kBQbdNOVs/xKZ9J01G8e/6D1mRBj9uTk= +github.com/aws/aws-sdk-go-v2 v1.47.3 h1:HRoT/MkNi3bjO5xF4dCzLzvVmkvRGHj3/cLINDnJejk= +github.com/aws/aws-sdk-go-v2 v1.47.3/go.mod h1:xSl+OSI5ZUw6bSMupWc218zE5Pl+65MCCu3ySWFY99A= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.22 h1:oiEmPz/YMMeh/JvAE8xSxNCIsvWE6lpFe4ZPlyFykQI= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.22/go.mod h1:QWUDuRi9PFjBHIU2iH1Pkldbe7zqLfXJC4wWLbF8nuQ= +github.com/aws/aws-sdk-go-v2/config v1.33.9 h1:Hv6bcQUJVnmuWaKjqD6Ot8sE75NrWsWV20oGT5aWle0= +github.com/aws/aws-sdk-go-v2/config v1.33.9/go.mod h1:ivowmrZNfBQLcg4QkaItgi5wae4Bzot9RzIdbY1hfTg= +github.com/aws/aws-sdk-go-v2/credentials v1.20.9 h1:SNO55PnWhaDafsOcv16lCA0kdoFMl/YlegWoVsU02s0= +github.com/aws/aws-sdk-go-v2/credentials v1.20.9/go.mod h1:+2reOTRtdD3VSBqpRDNwrZIYy8yMCYa7oK3WKTJ/I1I= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.20.3 h1:gr3b87OLB29jymWPzp/CAyXwQRJVE+sC4Wm0qBIKHsk= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.20.3/go.mod h1:BRjf4L/Yx2hY54o+ZibPbLJyMMeSwqYQXwcJfqW66a8= +github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.23.14 h1:nX4dV32e7e2GSg60GojffG61w4tvYxVpvEGWfSlcImo= +github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.23.14/go.mod h1:dNw78YbA0ylhEVJ1h84fPdtIiWkVTaszT7oHkE/cq5E= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.5.6 h1:CqL8NN3p4I0JTwwV5MEoatIl4Y2amZ2X9dRD+0hbRL0= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.5.6/go.mod h1:exdJ32hMqXl0Wyo6NwqEC5yIhtua3+PDaKE8P29m4qE= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.8.6 h1:FpBq24TG6WVSObTNhQOv7Q0088rounUUlwn3zlNItkM= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.8.6/go.mod h1:ONzdPVYC95oBOu0VWNEsKEntcoemP3xVKjT+qWml1s4= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.5.6 h1:Qmn60RWuk9+bd2ddvJJqwf3Cpac3VvGMOdbGemtUhX4= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.5.6/go.mod h1:h/YJhe4AVyQxEUMluph10iqV7mjajH7A9L+BHF5ppis= +github.com/aws/aws-sdk-go-v2/service/ecr v1.66.4 h1:qnnwXADWCoti36pKognFfw3L4bPYW1wIlsm4JqYmjDY= +github.com/aws/aws-sdk-go-v2/service/ecr v1.66.4/go.mod h1:uDza1Eipo/Olii3CCgLglHmAni24PScSAhpfIKbUl0c= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.21 h1:e6YW5uME30TkrElR+4/xcY7NB6CCj2dcv7vtmGXhfhQ= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.21/go.mod h1:obpHAMP693+f3/A8PkLhIqfK1C+NWWn8zXzqD3OH520= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.11.7 h1:CwvFlKw0uUScfgOMflXSRZa+ayXgOf6wk7lhyMemQYU= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.11.7/go.mod h1:4SbD8BB+p+QEnG0aX6DoWwrtk9SufyYXRsUv0nve8LE= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.14.6 h1:LHHdobjo5le1sjJdolitV7uVvYg3wkaeM1Ju2fDgeZQ= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.14.6/go.mod h1:CBGB0evAiaMD3Lk4wPO7LQCcCZLuhzL5Qf/wfEUHQ5w= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.20.6 h1:mYViDr2azcgxOpRrObZOl631bvavqILoE/L+Kxgbwys= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.20.6/go.mod h1:OXVdAo9jNhggB+hJRGOyy7WolGTKB6Rg7IIulDeSzPM= github.com/aws/aws-sdk-go-v2/service/kms v1.55.0 h1:uB8ymkVosyourmGXCZHyWhJ4wuKA4xq3ii2dVMPtBZY= github.com/aws/aws-sdk-go-v2/service/kms v1.55.0/go.mod h1:rK4RITSY/qJw3qVJ7p19fceOWuvrisqqOChFkX05n5I= -github.com/aws/aws-sdk-go-v2/service/s3 v1.113.4 h1:n6kO3OlBvnDEksQpvBLbAldjHwGlu8kErvhHJkhlaRY= -github.com/aws/aws-sdk-go-v2/service/s3 v1.113.4/go.mod h1:9APRWGLFITKD+xzWSIyT9V7QV4bNlEuIieWlzXgGFlI= -github.com/aws/aws-sdk-go-v2/service/securityhub v1.82.1 h1:WwNVhW53/6HoJ+VoTOQ+DvO0fnGUa9k32FgKxEHyWhM= -github.com/aws/aws-sdk-go-v2/service/securityhub v1.82.1/go.mod h1:FpfFeYyrCUwWWbEz5BDgFz3H5QsdaIn45RwvcIyZhrc= -github.com/aws/aws-sdk-go-v2/service/signin v1.10.1 h1:DzCCWLzcIRQ77F3DEUljud7bEjTgFOIKXP52NmVRyhU= -github.com/aws/aws-sdk-go-v2/service/signin v1.10.1/go.mod h1:xpo/geVldu8payT375WekctUzopG/hBU7miiqItMUlw= -github.com/aws/aws-sdk-go-v2/service/sso v1.38.1 h1:Umtl/0YZhng4xndfW3lKJrYYP7NLEjI6bGXVomwLcs0= -github.com/aws/aws-sdk-go-v2/service/sso v1.38.1/go.mod h1:rRD/dnm7q0HYE/I5TMaPgkWyyUGLcwuxHLABsLnQ3e0= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.43.1 h1:orIWdNiLgzrhu/11RcPPKO/SBzUUymbUQuZbSPImghg= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.43.1/go.mod h1:skwM/xsbR/1ReUTesv9BhpJp1VjajR7DWQnuVLwiXsQ= -github.com/aws/aws-sdk-go-v2/service/sts v1.51.1 h1:0HOqZXRvMytH6bFHVIc0oJX07sZjfhz0zXtjs6gdE8s= -github.com/aws/aws-sdk-go-v2/service/sts v1.51.1/go.mod h1:26zA0GhDrLo+yiLI2yXWxqB1PdsShfLikoI7GOEgugM= -github.com/aws/smithy-go v1.28.2 h1:myhcykQcatTul2B/zITjDk203G7t0awUAs1hVry5Bvg= -github.com/aws/smithy-go v1.28.2/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc= +github.com/aws/aws-sdk-go-v2/service/s3 v1.114.3 h1:NyeuyXkZL476uoAowNActm4XJO1QPKf7aassgXHpiqA= +github.com/aws/aws-sdk-go-v2/service/s3 v1.114.3/go.mod h1:GfoBeKMDJecgRUsGf/0RGdx0NJAh81ObTd922h8pQys= +github.com/aws/aws-sdk-go-v2/service/securityhub v1.84.1 h1:LgsiaI6xfyBrEwso2XsZFZpqnmtjmqIJUN8p/mIkkH4= +github.com/aws/aws-sdk-go-v2/service/securityhub v1.84.1/go.mod h1:/had+BOeuoD/E3LQR4PxHLZase9U7VOdXkC/+4425FA= +github.com/aws/aws-sdk-go-v2/service/signin v1.10.4 h1:tvpDjuUvgKURf3Yk4hOaGbZ4QdY+070OWcA5d6LUv2A= +github.com/aws/aws-sdk-go-v2/service/signin v1.10.4/go.mod h1:3ibVKg5Ng82Zby8++jELTD2nHxiPk2DJRLEeW/LKp8M= +github.com/aws/aws-sdk-go-v2/service/sso v1.38.4 h1:ChCtagixSFOxltv0o6E3NT8F7ksvZtZGPwUBynO5Nmk= +github.com/aws/aws-sdk-go-v2/service/sso v1.38.4/go.mod h1:+FEO9jrVMpc9oN3CdAxJbtlQEu3z8UddDPSedpeM3PY= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.43.4 h1:j6c+HA4AoS0EBEjArPjavFEBrucJEryfrQU0TffmACk= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.43.4/go.mod h1:RhghaXUk9q66VtOQUX1FdPddQuHnqQZzbB0fEYXmPRA= +github.com/aws/aws-sdk-go-v2/service/sts v1.51.4 h1:OKEfbf2tcfsF2pBk/S9QqNpo6alaAtiBs4hysCsBjPw= +github.com/aws/aws-sdk-go-v2/service/sts v1.51.4/go.mod h1:mCd6qGo9b8hTtfjgsmNkFlzwjyce4owd7HDVFnCLXhM= +github.com/aws/smithy-go v1.28.5 h1:OqbqKtzY9NUnr2yAe9+hiTdFb20tNaK/lI0PMOGXX9k= +github.com/aws/smithy-go v1.28.5/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc= github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= github.com/beevik/etree v1.7.0 h1:xjBk9O4p4x7D1YajePjfLzdaFC4/uYUENA7P0pv6gXA= @@ -273,10 +272,8 @@ github.com/bits-and-blooms/bitset v1.24.4 h1:95H15Og1clikBrKr/DuzMXkQzECs1M6hhoG github.com/bits-and-blooms/bitset v1.24.4/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= -github.com/bmatcuk/doublestar/v4 v4.10.0 h1:zU9WiOla1YA122oLM6i4EXvGW62DvKZVxIe6TYWexEs= -github.com/bmatcuk/doublestar/v4 v4.10.0/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= -github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY= -github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= +github.com/bmatcuk/doublestar/v4 v4.10.2 h1:eF7W7HWKg3z9NrWV9pTLnNeoXaqq3Tq9DNKXVMfoCnw= +github.com/bmatcuk/doublestar/v4 v4.10.2/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= github.com/bshuster-repo/logrus-logstash-hook v1.1.0 h1:o2FzZifLg+z/DN1OFmzTWzZZx/roaqt8IPZCIVco8r4= github.com/bshuster-repo/logrus-logstash-hook v1.1.0/go.mod h1:Q2aXOe7rNuPgbBtPCOzYyWDvKX7+FpxE5sRdvcPoui0= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= @@ -330,12 +327,12 @@ github.com/cockroachdb/logtags v0.0.0-20241215232642-bb51bb14a506 h1:ASDL+UJcILM github.com/cockroachdb/logtags v0.0.0-20241215232642-bb51bb14a506/go.mod h1:Mw7HqKr2kdtu6aYGn3tPmAftiP3QPX63LdK/zcariIo= github.com/cockroachdb/metamorphic v0.0.0-20231108215700-4ba948b56895 h1:XANOgPYtvELQ/h4IrmPAohXqe2pWA8Bwhejr3VQoZsA= github.com/cockroachdb/metamorphic v0.0.0-20231108215700-4ba948b56895/go.mod h1:aPd7gM9ov9M8v32Yy5NJrDyOcD8z642dqs+F0CeNXfA= -github.com/cockroachdb/pebble/v2 v2.1.6 h1:GDo7Z2+LgFZ7LJLdLmBXhDeTVIwgSPGxIT15hE7vGqM= -github.com/cockroachdb/pebble/v2 v2.1.6/go.mod h1:Reo1RTniv1UjVTAu/Fv74y5i3kJ5gmVrPhO9UtFiKn8= +github.com/cockroachdb/pebble/v2 v2.1.7 h1:hFQnbsniSWg9BVcNKMuaUufYPiVXY6uJvaY9grbQ9+U= +github.com/cockroachdb/pebble/v2 v2.1.7/go.mod h1:JhU5cqqYkr2BdsBHbZhRZOryAtfhcV3eNI/oBcbrxWc= github.com/cockroachdb/redact v1.1.6 h1:zXJBwDZ84xJNlHl1rMyCojqyIxv+7YUpQiJLQ7n4314= github.com/cockroachdb/redact v1.1.6/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg= -github.com/cockroachdb/swiss v0.0.0-20251224182025-b0f6560f979b h1:VXvSNzmr8hMj8XTuY0PT9Ane9qZGul/p67vGYwl9BFI= -github.com/cockroachdb/swiss v0.0.0-20251224182025-b0f6560f979b/go.mod h1:yBRu/cnL4ks9bgy4vAASdjIW+/xMlFwuHKqtmh3GZQg= +github.com/cockroachdb/swiss v0.0.0-20260820225851-333444432258 h1:IJ+uNItEm0qx9FE2AgIc1PMsCUtk8nbSIzhQE1t5GWw= +github.com/cockroachdb/swiss v0.0.0-20260820225851-333444432258/go.mod h1:yBRu/cnL4ks9bgy4vAASdjIW+/xMlFwuHKqtmh3GZQg= github.com/cockroachdb/tokenbucket v0.0.0-20250429170803-42689b6311bb h1:3bCgBvB8PbJVMX1ouCcSIxvsqKPYM7gs72o0zC76n9g= github.com/cockroachdb/tokenbucket v0.0.0-20250429170803-42689b6311bb/go.mod h1:7nc4anLGjupUW/PeY5qiNYsdNXj7zopG+eqsS7To5IQ= github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb h1:EDmT6Q9Zs+SbUoc7Ik9EfrFqcylYqgPZ9ANSbTAntnE= @@ -346,8 +343,8 @@ github.com/containers/image/v5 v5.36.2 h1:GcxYQyAHRF/pLqR4p4RpvKllnNL8mOBn0eZnqJ github.com/containers/image/v5 v5.36.2/go.mod h1:b4GMKH2z/5t6/09utbse2ZiLK/c72GuGLFdp7K69eA4= github.com/containers/storage v1.59.1 h1:11Zu68MXsEQGBBd+GadPrHPpWeqjKS8hJDGiAHgIqDs= github.com/containers/storage v1.59.1/go.mod h1:KoAYHnAjP3/cTsRS+mmWZGkufSY2GACiKQ4V3ZLQnR0= -github.com/coreos/go-oidc/v3 v3.20.0 h1:EtE0WIBHk03N+DqGkY4+UONzzZHk7amKt6IyNd7OsZE= -github.com/coreos/go-oidc/v3 v3.20.0/go.mod h1:DYCf24+ncYi+XkIH97GY1+dqoRlbaSI26KVTCI9SrY4= +github.com/coreos/go-oidc/v3 v3.21.0 h1:wZo4Q9Pum8dYEj0eMUPrqR+kvuGkeUplbLpNCkBqoWM= +github.com/coreos/go-oidc/v3 v3.21.0/go.mod h1:DYCf24+ncYi+XkIH97GY1+dqoRlbaSI26KVTCI9SrY4= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/coreos/go-systemd/v22 v22.7.0 h1:LAEzFkke61DFROc7zNLX/WA2i5J8gYqe0rSj9KI28KA= @@ -383,8 +380,8 @@ github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5Qvfr github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI= github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= -github.com/docker/cli v29.6.2+incompatible h1:/bjePvcbbFTnRrMfWJBY7AjfICdsiLVgHn6LwTVOcqw= -github.com/docker/cli v29.6.2+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/cli v29.7.2+incompatible h1:dlkwallR8XqfeVnA2ELEhdwvb4lsSwuB4IgsG8Q9cLY= +github.com/docker/cli v29.7.2+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk= github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/docker-credential-helpers v0.9.5 h1:EFNN8DHvaiK8zVqFA2DT6BjXE0GzfLOZ38ggPTKePkY= @@ -523,69 +520,67 @@ 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-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg= -github.com/go-openapi/analysis v0.26.0 h1:1xECln1iMMmQnTjgcknC1vi1hA4KISt6IHpSwnqcuwI= -github.com/go-openapi/analysis v0.26.0/go.mod h1:40gERFi/2dyXA1FaqRRLxkv1IlC6X+GPDNd1xrYAjZE= -github.com/go-openapi/errors v0.22.8 h1:oP7sW7TWc3wFFjrzzj0nI83H2qMBkNjNfSd+XRejk/I= -github.com/go-openapi/errors v0.22.8/go.mod h1:BuUoHcYrU6E7V9gfj1I5wLQqgtIHnup/alXZ8KdgQ0w= +github.com/go-openapi/analysis v1.0.0 h1:sNvbAGCJqUTqIAodr9IVqJMmuZas3YS9ms1dGK9yiJ4= +github.com/go-openapi/analysis v1.0.0/go.mod h1:NhYjJ57fnE+bcE7UwrJyMkhWA3Dfz7TdiBfTVAnos4Y= +github.com/go-openapi/errors v0.22.9 h1:HI9+SyVYiRzyeBQGv0CbWbQa+u2S0nny7G7AhY+UMiM= +github.com/go-openapi/errors v0.22.9/go.mod h1:R73q66sXP2smzAGgFOI4nqtusRK2xN5pYCjsij+KX58= github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= -github.com/go-openapi/jsonpointer v1.0.0 h1:kR9tHqY0CtZaOPVFm622dPVNhrvYpwr4uCxgL3h1H8s= -github.com/go-openapi/jsonpointer v1.0.0/go.mod h1:Z3rw7dWu1p9IgitXCFamSlA5lmDiklEB6vkaxcNZW5Y= +github.com/go-openapi/jsonpointer v1.0.1 h1:2KxywRmNwJkT/FMBa3iRNHEaAxSJvjqoufQZy3au1Mg= +github.com/go-openapi/jsonpointer v1.0.1/go.mod h1:wI7ZYsFmbIi9nBXOZqgDaS/bqOchRGZjqxFli7FBYxY= github.com/go-openapi/jsonreference v0.19.5/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg= github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns= github.com/go-openapi/jsonreference v0.20.1/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= -github.com/go-openapi/jsonreference v1.0.0 h1:jlmTr6torcd1YgDQvSfNmRtKzYDO4FGBkrAdlAVWnpY= -github.com/go-openapi/jsonreference v1.0.0/go.mod h1:jtwdyGbJk0Xhe5Y+rwtglQP6Sb1WZST4rT32LWB+sv0= -github.com/go-openapi/loads v0.25.1 h1:toKQdIDLxlqfKLLGUUmUsiTd5/X0Chzvde9EGYQP/Ac= -github.com/go-openapi/loads v0.25.1/go.mod h1:33Hen4tsKXHL45TyYojvfD5fZUFN4O1y4r/XhsRW2zc= -github.com/go-openapi/runtime v0.33.0 h1:Dd3Oj2ig+WH8ckK95l0Wn2V8a4bH/UqWPRZVT0vc8yU= -github.com/go-openapi/runtime v0.33.0/go.mod h1:+rsupH3+TFKqmFysqkmgBOTxpVJV8eV+j9myvvea2Xw= -github.com/go-openapi/runtime/server-middleware v0.30.0 h1:8rPoJ/xv7JL8BsovaqboKETlpWBArVh8n+0L/GyePog= -github.com/go-openapi/runtime/server-middleware v0.30.0/go.mod h1:OYNT/TxNvB/VK5oe4htM2jDTwlEXuejVJmu0DVZfAMs= -github.com/go-openapi/spec v0.22.9 h1:/vKIFDcGKp0ktZWGbym/tJEWbk6/XOEmAVU0kqKMH+w= -github.com/go-openapi/spec v0.22.9/go.mod h1:b/mNUYIOQOyIiUzUzXEE8xzyZqf93KvM9hQGP91yfl0= -github.com/go-openapi/strfmt v0.27.0 h1:kbcTeaD9TXuXD0hhMXzuYa1sdTo6+dWGvwjW93E80IM= -github.com/go-openapi/strfmt v0.27.0/go.mod h1:s/qhDqfY72irigXUGJmtgid2Rm+3tnz3k8hZaRmvWYc= +github.com/go-openapi/jsonreference v1.0.1 h1:4zJ7AmYDKNmD3aSpfPnFNCFA5E80/xMHUNKgydaLh38= +github.com/go-openapi/jsonreference v1.0.1/go.mod h1:dYplQXa6p5lXprLcJ8LE2iU7vNpXsAHDQ5ZAgL+Qx3A= +github.com/go-openapi/loads v0.25.2 h1:+uNsDlRQfYtZTrh+3pdwampcAqZVPuBJW0IA82aZHII= +github.com/go-openapi/loads v0.25.2/go.mod h1:RXsfJEQGGNv4uw8u/KdmF8Vh8OR4fTfs9ZO4QrzNohA= +github.com/go-openapi/runtime v0.33.2 h1:HSxskMs0WmpCdQvBWVxHt2t2mXMwn8DDav3VtVidwig= +github.com/go-openapi/runtime v0.33.2/go.mod h1:NQpSLiIsEAIpZDEs6xUrhjm6ZiQh85X8bHKlqLPgNP8= +github.com/go-openapi/runtime/server-middleware v0.33.2 h1:BVFjAaW4Jh/kZ4QSsgDrGzLuSuaKzSiyvappUFKxgUk= +github.com/go-openapi/runtime/server-middleware v0.33.2/go.mod h1:E3mWY61/UgBJ5EUmKh+h6A1oQQdD1U4raowVMmPmQug= +github.com/go-openapi/spec v1.0.0 h1:JtB/GHOj+eetjse6YvxqLze88oEekl/4uPBethvzRrA= +github.com/go-openapi/spec v1.0.0/go.mod h1:boj1PRhqS0x5jylgcNp9BRWxenphrh7vVNYZ90IRCoo= +github.com/go-openapi/strfmt v0.27.2 h1:SG32SlbwNy92s0KJiVxt2joJeFdqIYHvwrA0OU6HqzQ= +github.com/go-openapi/strfmt v0.27.2/go.mod h1:M4CKsMO0Fb8qR10+1Ra75wCKNNquy+Vj+4LWZrhTo2E= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= github.com/go-openapi/swag v0.21.1/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= -github.com/go-openapi/swag v0.26.1 h1:l5sVEyVpwj+DDYeZyo7wQI/Ebn/mKYIyGB/pFwAfGoQ= -github.com/go-openapi/swag v0.26.1/go.mod h1:yNY38BbIVthxbkDtq1UHBCGasBqjakW3lCR6ANzdBEw= -github.com/go-openapi/swag/cmdutils v0.27.0 h1:aIKiqhB29AaP+7xm8/CPg3uOpeHx2SUp6TvMpu/a31Y= -github.com/go-openapi/swag/cmdutils v0.27.0/go.mod h1:Sm1MVFMkF6guJJ+pQqHnQA3N0j9qALV3NxzDSv6bETM= -github.com/go-openapi/swag/conv v0.28.0 h1:GtqqbyFe7vR5Y7ehxG9W6/OvrSFdf1OLeTGp40TqxH8= -github.com/go-openapi/swag/conv v0.28.0/go.mod h1:mbUE+mzctnhxi864m0Q07SpN8OowD9JhxmxuYvZZD/k= -github.com/go-openapi/swag/fileutils v0.28.0 h1:Z04XWQD7R8Eq+7GnOrjovBxPPmZzsS4gt2H2GPGIViU= -github.com/go-openapi/swag/fileutils v0.28.0/go.mod h1:VvJFZLTZS0AI854gEQz5tk7dBESdLjiNUMSZ/th2ry8= -github.com/go-openapi/swag/jsonname v0.26.1 h1:VReupaV6WxlAsCn0e4DUfgV6bPmINnPpyJDLqSfNPcE= -github.com/go-openapi/swag/jsonname v0.26.1/go.mod h1:OvdW6BoWoj33pTfi7x9vFrgmT+fk7aw0BRwvCE0YOuc= -github.com/go-openapi/swag/jsonutils v0.28.0 h1:YIch6FwO7RXzeAnbO8Tu7dWBZeUEH+4nA0HXltVTnv4= -github.com/go-openapi/swag/jsonutils v0.28.0/go.mod h1:CYM3WlTUcagR2ZoHdz54di/cbBqt82tuxuXgAjxw+mg= -github.com/go-openapi/swag/jsonutils/fixtures_test v0.28.0 h1:qV+VVUAx5Oro8WjVWpZeql7YReTKhT4smR4zhcOQZr0= -github.com/go-openapi/swag/jsonutils/fixtures_test v0.28.0/go.mod h1:mofwUWx70wvskwESqRJ//k/9kURmCgyJl5m5Ppoh5kY= -github.com/go-openapi/swag/loading v0.28.0 h1:td8QZdZC9MIYGGSnSPKShKiK22I2tU5UQvuUhIBPRLU= -github.com/go-openapi/swag/loading v0.28.0/go.mod h1:rXB0QiQX5mMveXEA7ouM4KiiM9jVJe4K6BVbwhD1M4k= -github.com/go-openapi/swag/mangling v0.28.0 h1:pH8eyeNO9SLYsTMWJrurnNfKmDa28XrlA+HePVD53VM= -github.com/go-openapi/swag/mangling v0.28.0/go.mod h1:jtBE2+V+3pILxOR7Vgce+Cwp6A2PgZbvVqfNntbVs0w= -github.com/go-openapi/swag/netutils v0.27.0 h1:lEUG+hHvPvLggB3A8snFk0IRKNf9uC0YKc+7WYqvAF8= -github.com/go-openapi/swag/netutils v0.27.0/go.mod h1:J+WYyFMLtvtCGqa6jLv+YNUmIKI3ZRQRrvfNDMoQoEQ= -github.com/go-openapi/swag/pools v0.28.0 h1:HPMZWSAfce3rdVTFcjFiCIBtDg9h4x2QlRrHipwhxeU= -github.com/go-openapi/swag/pools v0.28.0/go.mod h1:kVQefhSK5RWuRe7BXsL8htgBPAMpN7HDGpGEknqugeE= -github.com/go-openapi/swag/stringutils v0.28.0 h1:ixsc9iYgDPubHL/8nSkbnryEHpD2VRlBMLKpQyPXcDU= -github.com/go-openapi/swag/stringutils v0.28.0/go.mod h1:lzRN95CxXmA03XcDWHLOb6nOMcxCqR5rGY0lOgsfRoM= -github.com/go-openapi/swag/typeutils v0.28.0 h1:nRBKSBXjDgf01VDPB3fWeD9nQuhCOVeIYAkUx2tbkyY= -github.com/go-openapi/swag/typeutils v0.28.0/go.mod h1:Srm0xFNRZ1Y+vCxJclo5qzx8aj+1pAKda/YfFPrG0dQ= -github.com/go-openapi/swag/yamlutils v0.28.0 h1:TV3JXH6DS46KUroDtMLAYHGkdWf5VDq3wVWFirmzROY= -github.com/go-openapi/swag/yamlutils v0.28.0/go.mod h1:x0q/yndZHEgk9Rx3DyDqzFUmHy55KTvIZldvF2dTJXs= -github.com/go-openapi/testify/enable/yaml/v2 v2.6.0 h1:gGHwAJ0R/5jU8BEGDbfRNR3hL68dAVi84WuOApp29B0= -github.com/go-openapi/testify/enable/yaml/v2 v2.6.0/go.mod h1:tY+St1SGq4NFl0QIqdTY4aEdbChAHxhyB77XQi9iJCo= -github.com/go-openapi/testify/v2 v2.6.0 h1:5PKH2HE7YJ/LuRPQGvSxBRlFXNQhSetBLlGAgUEu3ug= -github.com/go-openapi/testify/v2 v2.6.0/go.mod h1:SgsVHtfooshd0tublTtJ50FPKhujf47YRqauXXOUxfw= -github.com/go-openapi/validate v0.26.3 h1:OkfZgLvLDnGP2hrRGD+42WBiPWWkoHomTJ+IVI+KaDc= -github.com/go-openapi/validate v0.26.3/go.mod h1:7DOOa4raU6NRe7A8VQSKbm3VcuUIioREYHFt+er9Sk8= +github.com/go-openapi/swag v0.29.2 h1:9n8frkcsuQRA0INU31VbxNxt1zBaHxrHYGEf5BUjFi4= +github.com/go-openapi/swag v0.29.2/go.mod h1:RkCiX1gCVXWgLOavDXE3ALJrQ/VO2ziiIzOUs3z10xo= +github.com/go-openapi/swag/cmdutils v0.29.2 h1:cXEzX/nWCODon251f+1HgL75/R4XXML5IZxgLCWMoro= +github.com/go-openapi/swag/cmdutils v0.29.2/go.mod h1:XziaSVzYqkDvpWXzASfWajw2YvJrLscq610O2XiyqJw= +github.com/go-openapi/swag/conv v0.29.2 h1:8c9shoB8l0QRSR6ymq1llHdBWDqpIgJfjTGHx3Vuhm0= +github.com/go-openapi/swag/conv v0.29.2/go.mod h1:AZS0YigTNf8qNtD6WqJz/N4RUNmpr76SyjFGkq4+p6Q= +github.com/go-openapi/swag/fileutils v0.29.2 h1:mdUL+Vw5ah1fO1AvFCoHeIyv7YZsCz2MN3KNqd/lLVI= +github.com/go-openapi/swag/fileutils v0.29.2/go.mod h1:7QKmmodjAebaq61lGVJa8ldLdGeMF4DSgYv8tXOGGo0= +github.com/go-openapi/swag/jsonutils v0.29.2 h1:uZNSD2/rJDYAfvsLYkykTzXuyxM3QpDKV9jhRHrMu6k= +github.com/go-openapi/swag/jsonutils v0.29.2/go.mod h1:ONTdNvj3Y+IXRzfa17E+Rgt2ns/qiSOYjIo2K7v2yDs= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.29.2 h1:w+Fd6EBOMGlC74GYGHqCLGyb3Bpams8TtNrgM/SG4jo= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.29.2/go.mod h1:HC2egPORFzbj/gf05Zrfgz8Mgplx3oW7hgGi7pjZVhM= +github.com/go-openapi/swag/loading v0.29.2 h1:QU1ry24e6r6Shoxe380Nx0X5iKkpRO+hJffsyJ2dVlY= +github.com/go-openapi/swag/loading v0.29.2/go.mod h1:DqilDjuiJKETecsS4TRopWG1acUDsfqN39ZmQhPu6uI= +github.com/go-openapi/swag/mangling v0.29.2 h1:ltdo3K0tTP4P6zWhVVWDU9D37/YYBuMJRdZxiS2Dtog= +github.com/go-openapi/swag/mangling v0.29.2/go.mod h1:RjDi4TnItAenS3cgCXSpIl1kKbjG7il6pnmQVl/8bp8= +github.com/go-openapi/swag/netutils v0.29.2 h1:dBli6jyUa93sDS/XYWShJzf4+nnVfFKfvY9SLUaUzB8= +github.com/go-openapi/swag/netutils v0.29.2/go.mod h1:1pM6Xg/Um8E1eps3umWl8Hme3ByWXOtHsvFC0CULHUE= +github.com/go-openapi/swag/pools v0.29.2 h1:PlGoDRF8WtSyXkedZZkpW3f9wDhFf3u8KtaBcmgmh8c= +github.com/go-openapi/swag/pools v0.29.2/go.mod h1:V3lhxVT4qDYRqc2MRSSbLsTYIYV/2HlHafhyEkmzLIc= +github.com/go-openapi/swag/stringutils v0.29.2 h1:lcnBxwAaysT3bMUVBfn9V/PkfVqkurpqufKU6rTUMGk= +github.com/go-openapi/swag/stringutils v0.29.2/go.mod h1:i9cdh7sGaa0nm3CqIvH5IM5h2QClk1wns2ktKeILvRI= +github.com/go-openapi/swag/typeutils v0.29.2 h1:O7aVvkTs3pXwikgtrPLenigEMdQFgONKRooQA9pgf2I= +github.com/go-openapi/swag/typeutils v0.29.2/go.mod h1:7+GDG+uz9Ke+eVt4rClZg7wklSDp8hT/mKNh/pC26TE= +github.com/go-openapi/swag/yamlutils v0.29.2 h1:IFKFFeDnuIwzfsuWRQU+rI8iL3g4XyAYjV/RlnlxPLM= +github.com/go-openapi/swag/yamlutils v0.29.2/go.mod h1:7MGqtcrK73sxQ4ceiyIf8qiXS/s09JLMdR5esK5euYQ= +github.com/go-openapi/testify/enable/yaml/v2 v2.7.0 h1:wPW6YRgx3+SID1yUy/Xwa17L8kFEaEKod2VRbJDZNUs= +github.com/go-openapi/testify/enable/yaml/v2 v2.7.0/go.mod h1:mI1M88etYbc3PhgHsWQK2kwvNwW5aGFqMPbmib+SGIs= +github.com/go-openapi/testify/v2 v2.8.0 h1:19QDx5b57p8KjO2E4sgs/woL4Akp+FGLRlVm2xn0Rho= +github.com/go-openapi/testify/v2 v2.8.0/go.mod h1:4gUN8jC+RqOE2qjW/vDmfyTvl5BCht4qL7oca0odrak= +github.com/go-openapi/validate v1.0.0 h1:dFsYCLVUQUL6Vi2lQSexgwmCXDuHe7eWRDhQxkE+xYA= +github.com/go-openapi/validate v1.0.0/go.mod h1:wwXGRqMQzOZ7PCqBcgNk+DD9+Cacnxv7we5T0M/eA3Y= github.com/go-rod/rod v0.116.2 h1:A5t2Ky2A+5eD/ZJQr1EfsQSe5rms5Xof/qj296e+ZqA= github.com/go-rod/rod v0.116.2/go.mod h1:H+CMO9SCNc2TJ2WfrG+pKhITz57uGNYU43qYHh438Mg= github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= @@ -604,13 +599,13 @@ github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPE github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/gobuffalo/flect v1.0.3 h1:xeWBM2nui+qnVvNM4S3foBhCAL2XgPU+a7FdpelbTq4= github.com/gobuffalo/flect v1.0.3/go.mod h1:A5msMlrHtLqh9umBSnvabjsMrCcCpAyzglnDvkbYKHs= -github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= -github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= +github.com/gobwas/glob v1.0.0 h1:p+FKbLEIsK1yZ39/OINwFvqNb5oyPY4H8xcy6uYu8dg= +github.com/gobwas/glob v1.0.0/go.mod h1:oWCdo522i2P1n/hMXGNWs7yoV4wy/ciZuUIbvKj5rkc= github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw= github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/godbus/dbus/v5 v5.2.2 h1:TUR3TgtSVDmjiXOgAAyaZbYmIeP3DPkld3jgKGV8mXQ= -github.com/godbus/dbus/v5 v5.2.2/go.mod h1:3AAv2+hPq5rdnr5txxxRwiGjPXamgoIHgz9FPBfOp3c= +github.com/godbus/dbus/v5 v5.2.3 h1:IQNSXULh6Nc4ZH8ETJ5Mdsj8loUFCWSBnsjmJkyjUB8= +github.com/godbus/dbus/v5 v5.2.3/go.mod h1:3AAv2+hPq5rdnr5txxxRwiGjPXamgoIHgz9FPBfOp3c= github.com/gofrs/flock v0.13.0 h1:95JolYOvGMqeH31+FC7D2+uULf6mG61mEZ/A8dRYMzw= github.com/gofrs/flock v0.13.0/go.mod h1:jxeyy9R1auM5S6JYDBhDt+E2TCo7DkratH4Pgi8P+Z0= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= @@ -690,8 +685,8 @@ github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= -github.com/google/go-containerregistry v0.21.9 h1:F+D4uZ3iA3DLMJLfhaqMdHJbzeqm/216WGQq2dokuLs= -github.com/google/go-containerregistry v0.21.9/go.mod h1:dP5XNKcL7kMFF/TB3LfvWmVhAcv7iqkHb3oDK8aauTo= +github.com/google/go-containerregistry v0.22.1 h1:RZuuSYhTvlDvtsK+NkutoCZ//C0X2ebLK8X8l3ULs84= +github.com/google/go-containerregistry v0.22.1/go.mod h1:bJR35SK8XgisYmhg/FMQ/5RK0S/XrOAqLBV5/LR2XE0= github.com/google/go-github/v32 v32.1.0/go.mod h1:rIEpZD9CTDQwDK9GDrtMTycQNA4JU3qBsCizh3q2WCI= github.com/google/go-github/v91 v91.0.0 h1:fpulREh37uBvOj4kF0vLuzjwJ1HHmFcAfquNjJfmpYs= github.com/google/go-github/v91 v91.0.0/go.mod h1:rHtn7haKvmkTLhUK/aCQypiIj/suWSB3rnS+a1FdH6M= @@ -727,8 +722,8 @@ github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20260802141513-ef3492d7dac3 h1:LMLX+LgTNWpfvCBdFebv6EsYotImrt/Ppc5cXIriCSo= github.com/google/pprof v0.0.0-20260802141513-ef3492d7dac3/go.mod h1:jl5iWTm0/hd5PjEYEOuwAJ57L/CibdZfrqZ5XA5GrCk= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/s2a-go v0.1.10 h1:EMp+aOuXN6l8cE/gjF5Bt+vyZxsUuyCWe9chDWR/+uU= -github.com/google/s2a-go v0.1.10/go.mod h1:pz4tyvwXvJLLbyrkh6FW1eS2zPUXMaTmyNhYtyP2tNw= +github.com/google/s2a-go v0.1.11 h1:7v1V1TraNGP63WptsAkNqr2w8jgm4vM6A6qx6Lsw9iM= +github.com/google/s2a-go v0.1.11/go.mod h1:pz4tyvwXvJLLbyrkh6FW1eS2zPUXMaTmyNhYtyP2tNw= github.com/google/trillian v1.7.3 h1:hziW+vo4czis48tzx2GK5xRBl/ZxBA9B0/UR5avXOro= github.com/google/trillian v1.7.3/go.mod h1:qh8iy4x/GvnVXUBd5pK4oncuT1Y9vVYfibQVsR/WpKg= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -741,8 +736,8 @@ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+ github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM= -github.com/googleapis/gax-go/v2 v2.26.0 h1:EQd25QY0qQk+S8OZA5wRflUckH5jD2Uw0zj2l23f/lc= -github.com/googleapis/gax-go/v2 v2.26.0/go.mod h1:sMKqnMesnKH+3wiRJROcttA+cJoZoGbZl1vDQ8XYtGk= +github.com/googleapis/gax-go/v2 v2.26.2 h1:ydkmNXxj7bEmmeK5AihkKnWxyOyBR9TDebvp5L5izk8= +github.com/googleapis/gax-go/v2 v2.26.2/go.mod h1:sMKqnMesnKH+3wiRJROcttA+cJoZoGbZl1vDQ8XYtGk= github.com/googleapis/gnostic v0.5.5/go.mod h1:7+EbHbldMins07ALC74bsA81Ovc97DwqyJO1AENw9kA= github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY= github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c= @@ -757,19 +752,19 @@ github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5T github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA= github.com/gosuri/uitable v0.0.4 h1:IG2xLKRvErL3uhY6e1BylFzG+aJiwQviDDTfOKeKTpY= github.com/gosuri/uitable v0.0.4/go.mod h1:tKR86bXuXPZazfOTG1FIzvjIdXzd0mo4Vtn16vt0PJo= -github.com/grafana/pyroscope-go v1.4.2 h1:0LW5HrUJXgGr9zF5gITP/HaFXN9/LsMiwlgVJAK75l0= -github.com/grafana/pyroscope-go v1.4.2/go.mod h1:Ej13Jr05rRJrjWvrrFhfh6gGYXtfibuukOs3Tl3Y7QQ= +github.com/grafana/pyroscope-go v1.4.3 h1:XAfYZe5ie8eRTsKAMHQG60ESJjHBV5XUijk8vDvqalw= +github.com/grafana/pyroscope-go v1.4.3/go.mod h1:enNhwzbML7+hMzJHTvKAIqTGqIaOOF1rgF+2au+NOwg= github.com/grafana/pyroscope-go/godeltaprof v0.1.11 h1:el5LYpXissAiCKZ5/6yjlr6mhYVV6Cp5lahTocxraXM= github.com/grafana/pyroscope-go/godeltaprof v0.1.11/go.mod h1:jl1V8M4cWsXciROCPIDDG7CtjSjT/ECbp6eLVuMxYRI= -github.com/graph-gophers/graphql-go v1.10.3-0.20260702060009-60399cce0c05 h1:nz9OX9q1iUJZwrPA2Ge8xNlB3inmq+pafyf6qmBLBew= -github.com/graph-gophers/graphql-go v1.10.3-0.20260702060009-60399cce0c05/go.mod h1:AsADheC4CCFwd8n1/QbkduTlHgYYMsRgtPihYVAlEsk= -github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.3 h1:B+8ClL/kCQkRiU82d9xajRPKYMrB7E0MbtzWVi1K4ns= -github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.3/go.mod h1:NbCUVmiS4foBGBHOYlCT25+YmGpJ32dZPi75pGEUpj4= +github.com/graph-gophers/graphql-go v1.10.3 h1:H6bqOfbuyolAQsbLapHnkIFdJ59vrXuAvDmc4uFvjbY= +github.com/graph-gophers/graphql-go v1.10.3/go.mod h1:AsADheC4CCFwd8n1/QbkduTlHgYYMsRgtPihYVAlEsk= +github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.4 h1:9ZJYjPEJpcleIKcqysXOo14NLQYwu23fzmKRFpIjPjc= +github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.4/go.mod h1:0xydIeg2omQ9DH6zYMP24Kk57793rnLUvP8fwdbFvz8= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.1-0.20210315223345-82c243799c99 h1:JYghRBlGCZyCF2wNUJ8W0cwaQdtpcssJ4CgC406g+WU= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.1-0.20210315223345-82c243799c99/go.mod h1:3bDW6wMZJB7tiONtC/1Xpicra6Wp5GgbTbQWCbI5fkc= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0 h1:5VipnvEpbqr2gA2VbM+nYVbkIF28c5ZQfqCBQ5g2xfk= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0/go.mod h1:Hyl3n6Twe1hvtd9XUXDec4pTvgMSEixRuQKPTMH2bNs= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.31.0 h1:Bd7KaOxzULLxtZ/K5s1aLbWhR0+5RToO65TXHsf3bqQ= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.31.0/go.mod h1:nN7ts3dFXKtCZWc//yfkpcQNKJABg16/uDVAZpLDalo= github.com/hashicorp/consul/api v1.11.0/go.mod h1:XjsvQN+RJGWI2TWy1/kqaE16HrR2J/FWgkYjdZQsX9M= github.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOjagTIwIR1vPms= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -854,8 +849,8 @@ github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsI 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.10.0 h1:VhSvgU2jSli8o3AqIEOTJr7rZwAEUVo4E4XhR94Zfr0= -github.com/jackc/pgx/v5 v5.10.0/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4= +github.com/jackc/pgx/v5 v5.11.0 h1:IzBBtyK9AHqf98cctWFifYSci2hgQR/cd56wB4p+ogg= +github.com/jackc/pgx/v5 v5.11.0/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/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= @@ -896,8 +891,8 @@ github.com/kisielk/sqlstruct v0.0.0-20201105191214-5f3e10d3ab46 h1:veS9QfglfvqAw github.com/kisielk/sqlstruct v0.0.0-20201105191214-5f3e10d3ab46/go.mod h1:yyMNCyc/Ib3bDTKd379tNMpB/7/H5TjM2Y9QJ5THLbE= github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/klauspost/compress v1.11.4/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= -github.com/klauspost/compress v1.20.0 h1:a3C1ke2ohxFymNlb2HWAHjDeKCI90scRskErZkR0ezA= -github.com/klauspost/compress v1.20.0/go.mod h1:LUdAzn7YLVvxLpc7y3V1m40wESHTgc1422pwwBSKYuI= +github.com/klauspost/compress v1.20.3 h1:zWNoFqq/lSuYQRim3dUXlXeA0IqwuZGicdG5ZTfY5j4= +github.com/klauspost/compress v1.20.3/go.mod h1:LUdAzn7YLVvxLpc7y3V1m40wESHTgc1422pwwBSKYuI= github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= @@ -1011,8 +1006,8 @@ github.com/moby/sys/capability v0.4.0 h1:4D4mI6KlNtWMCM1Z/K0i7RV1FkX+DBDHKVJpCnd github.com/moby/sys/capability v0.4.0/go.mod h1:4g9IK291rVkms3LKCDOoYlnV8xKwoDTpIrNEE35Wq0I= github.com/moby/sys/mountinfo v0.7.2 h1:1shs6aH5s4o5H2zQLn796ADW1wMrIwHsyJ2v9KouLrg= github.com/moby/sys/mountinfo v0.7.2/go.mod h1:1YOa8w8Ih7uW0wALDUgT1dTTSBrZ+HiBLGws92L2RU4= -github.com/moby/sys/user v0.4.0 h1:jhcMKit7SA80hivmFJcbB1vqmw//wU61Zdui2eQXuMs= -github.com/moby/sys/user v0.4.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs= +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/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ= github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -1049,16 +1044,16 @@ github.com/np-guard/netpol-analyzer v1.4.4/go.mod h1:T31vBeTIBl1ksQr9NTW5GJBMHpm github.com/nwaples/rardecode v1.1.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0= github.com/nwaples/rardecode v1.1.3 h1:cWCaZwfM5H7nAD6PyEdcVnczzV8i/JtotnyW/dD9lEc= github.com/nwaples/rardecode v1.1.3/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0= -github.com/oklog/ulid/v2 v2.1.1 h1:suPZ4ARWLOJLegGFiZZ1dFAkqzhMjL3J1TzI+5wHz8s= -github.com/oklog/ulid/v2 v2.1.1/go.mod h1:rcEKHmBBKfef9DhnvX7y1HZBYxjXb0cP5ExxNsTT1QQ= +github.com/oklog/ulid/v2 v2.1.2 h1:IEclFb9JNvzYA6MW2SCxbLzcHTVsfqm3PrqGQJH5zec= +github.com/oklog/ulid/v2 v2.1.2/go.mod h1:rcEKHmBBKfef9DhnvX7y1HZBYxjXb0cP5ExxNsTT1QQ= github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6 h1:zrbMGy9YXpIeTnGj4EljqMiZsIcE09mmF8XsD5AYOJc= github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6/go.mod h1:rEKTHC9roVVicUIfZK7DYrdIoM0EOr8mK1Hj5s3JjH0= github.com/olekukonko/errors v1.2.0 h1:10Zcn4GeV59t/EGqJc8fUjtFT/FuUh5bTMzZ1XwmCRo= github.com/olekukonko/errors v1.2.0/go.mod h1:ppzxA5jBKcO1vIpCXQ9ZqgDh8iwODz6OXIGKU8r5m4Y= github.com/olekukonko/ll v0.1.6 h1:lGVTHO+Qc4Qm+fce/2h2m5y9LvqaW+DCN7xW9hsU3uA= github.com/olekukonko/ll v0.1.6/go.mod h1:NVUmjBb/aCtUpjKk75BhWrOlARz3dqsM+OtszpY4o88= -github.com/olekukonko/tablewriter v1.1.4 h1:ORUMI3dXbMnRlRggJX3+q7OzQFDdvgbN9nVWj1drm6I= -github.com/olekukonko/tablewriter v1.1.4/go.mod h1:+kedxuyTtgoZLwif3P1Em4hARJs+mVnzKxmsCL/C5RY= +github.com/olekukonko/tablewriter v1.1.5 h1:4LoZSfMySpMQY3PT8RWJsJeuEuMIoo9xGRgvmqjg6IQ= +github.com/olekukonko/tablewriter v1.1.5/go.mod h1:+kedxuyTtgoZLwif3P1Em4hARJs+mVnzKxmsCL/C5RY= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= @@ -1067,15 +1062,15 @@ github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= -github.com/onsi/ginkgo/v2 v2.32.1 h1:6tlvcDm/3sE8lGJbZ4+d4mO3RLy24/tQWOFzVSQNIfw= -github.com/onsi/ginkgo/v2 v2.32.1/go.mod h1:+aXOY+vzZ5mu2iI2HpTZUPmM//oQfsNFX6gU9kNcA44= +github.com/onsi/ginkgo/v2 v2.33.1 h1:62XfWJbEIjPSCSDYrsCPP/SHcmH7O0M8ne8zfqQ2iz4= +github.com/onsi/ginkgo/v2 v2.33.1/go.mod h1:+aXOY+vzZ5mu2iI2HpTZUPmM//oQfsNFX6gU9kNcA44= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs= github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= -github.com/onsi/gomega v1.43.1 h1:vGIPFuYrIO6/0Z09s0I0QQQgFchiX4+tb1re3MScJYo= -github.com/onsi/gomega v1.43.1/go.mod h1:e/C2HwaZ1DhvjzXXuFhcR7hY7Sh9pl7MmoWKEjzwcdA= +github.com/onsi/gomega v1.44.0 h1:eAiGl3Pw5jz5GQdDff0BcxYpAX1JxW8xD7mFUuwNfZQ= +github.com/onsi/gomega v1.44.0/go.mod h1:e/C2HwaZ1DhvjzXXuFhcR7hY7Sh9pl7MmoWKEjzwcdA= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= @@ -1083,12 +1078,12 @@ github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJw github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M= github.com/opencontainers/runtime-spec v1.2.1 h1:S4k4ryNgEpxW1dzyqffOmhI1BHYcjzU8lpJfSlR0xww= github.com/opencontainers/runtime-spec v1.2.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= -github.com/openshift-online/ocm-api-model/clientapi v0.0.464 h1:MrAdeU7E0ElWu4Ztv7hMRqlzY6cdcQeqWiNaO00n2n4= -github.com/openshift-online/ocm-api-model/clientapi v0.0.464/go.mod h1:fZwy5HY2URG9nrExvQeXrDU/08TGqZ16f8oymVEN5lo= -github.com/openshift-online/ocm-api-model/model v0.0.464 h1:aZCIx0+EPZxVSbct4QQ9cSCaI1deoeAhfgwx5CfNl54= -github.com/openshift-online/ocm-api-model/model v0.0.464/go.mod h1:PQIoq6P8Vlb7goOdRMLK8nJY+B7HH0RTqYAa4kyidTE= -github.com/openshift-online/ocm-sdk-go v0.1.509 h1:JcmyaaJQslBqe4vIFugV0rQElGLojJSP/lO11moI/Ho= -github.com/openshift-online/ocm-sdk-go v0.1.509/go.mod h1:h1owsjOKlqLFRgzos5WWa0K0lSr8xRU6P1lF9EG94do= +github.com/openshift-online/ocm-api-model/clientapi v0.0.470 h1:9oXPeJPc6/L3OaulJySDXzkTEJs+YEndI+HIZvQRLO4= +github.com/openshift-online/ocm-api-model/clientapi v0.0.470/go.mod h1:fZwy5HY2URG9nrExvQeXrDU/08TGqZ16f8oymVEN5lo= +github.com/openshift-online/ocm-api-model/model v0.0.470 h1:gnubZARa3aSclaWpVdd4aisOVYN4G2V255SRpyExOf8= +github.com/openshift-online/ocm-api-model/model v0.0.470/go.mod h1:PQIoq6P8Vlb7goOdRMLK8nJY+B7HH0RTqYAa4kyidTE= +github.com/openshift-online/ocm-sdk-go v0.1.517 h1:79KiFTqWHds1FanFByIEqo/485VqXB7LHQiN8XOTdJw= +github.com/openshift-online/ocm-sdk-go v0.1.517/go.mod h1:3rNVzZ86jRbpFtdQUqbdlxmpR+MhKs87szY45bLg3EI= github.com/openshift/api v0.0.0-20260317165824-54a3998d81eb h1:iwBR3mzmyE3EMFx7R3CQ9lOccTS0dNht8TW82aGITg0= github.com/openshift/api v0.0.0-20260317165824-54a3998d81eb/go.mod h1:pyVjK0nZ4sRs4fuQVQ4rubsJdahI1PB94LnQ8sGdvxo= github.com/openshift/client-go v0.0.0-20260317180604-743f664b82d1 h1:Hr/R38eg5ZJXfbiaHumjJIN1buDZwhsm4ys4npVCXH0= @@ -1103,9 +1098,8 @@ github.com/openshift/runtime-utils v0.0.0-20230921210328-7bdb5b9c177b h1:oXzC1N6 github.com/openshift/runtime-utils v0.0.0-20230921210328-7bdb5b9c177b/go.mod h1:l9/qeKZuAmYUMl0yicJlbkPGDsIycGhwxOvOAWyaP0E= github.com/operator-framework/operator-lib v0.17.0 h1:cbz51wZ9+GpWR1ZYP4CSKSSBxDlWxmmnseaHVZZjZt4= github.com/operator-framework/operator-lib v0.17.0/go.mod h1:TGopBxIE8L6E/Cojzo26R3NFp1eNlqhQNmzqhOblaLw= -github.com/owenrumney/go-sarif v1.1.1/go.mod h1:dNDiPlF04ESR/6fHlPyq7gHKmrM0sHUvAGjsoh8ZH0U= -github.com/owenrumney/go-sarif/v2 v2.3.3 h1:ubWDJcF5i3L/EIOER+ZyQ03IfplbSU1BLOE26uKQIIU= -github.com/owenrumney/go-sarif/v2 v2.3.3/go.mod h1:MSqMMx9WqlBSY7pXoOZWgEsVB4FDNfhcaXDA1j6Sr+w= +github.com/owenrumney/go-sarif/v3 v3.3.1 h1:bKY8YnWcY8zYTiGVObC5wZLlfEvMkCxK9XMGXBbzEtQ= +github.com/owenrumney/go-sarif/v3 v3.3.1/go.mod h1:72MaugkExDexbSauRuPq6BvUAAqAX0TwoNYMIQyZCMw= github.com/package-url/packageurl-go v0.1.7 h1:iFWg6tzAjLA6F/qX3M5nZaiMHJgc+p2zxVyr/fY+sZY= github.com/package-url/packageurl-go v0.1.7/go.mod h1:nKAWB8E6uk1MHqiS/lQb9pYBGH2+mdJ2PJc2s50dQY0= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= @@ -1118,8 +1112,6 @@ github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0 github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= -github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5 h1:Ii+DKncOVM8Cu1Hc+ETb5K+23HdAMvESYE3ZJ5b5cMI= -github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE= github.com/pierrec/lz4/v4 v4.1.2/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ= github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= @@ -1148,8 +1140,8 @@ github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXP github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= -github.com/prometheus/client_golang v1.24.1 h1:JnJkREXzWxUdCuPFpIWZiPispT9xVV59uiuyR2bPlnU= -github.com/prometheus/client_golang v1.24.1/go.mod h1:F+oSRECHg4sse5ucfYpYDeIv/hu68Zo0uoHKetWnzcE= +github.com/prometheus/client_golang v1.25.0 h1:cDtW1TcFsMi1RJc8khwCKB4VhzwV04aKqbYtNOXuiK0= +github.com/prometheus/client_golang v1.25.0/go.mod h1:j2X/4/kaQHLPnmyEU8zjmkndo1T317VpCIQEml8LTKk= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= @@ -1160,8 +1152,8 @@ github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7q github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= -github.com/prometheus/common v0.71.0 h1:9KDAKb7Mj3HEVKyFCK6Dc/HIwlBzZIN2l7/lrHl3KK8= -github.com/prometheus/common v0.71.0/go.mod h1:CLJ5H8TEsGX8bl31BdMkfhIZ+QmZ9tBPPotUxUbfcmk= +github.com/prometheus/common v0.72.1 h1:ZAiQed/nk8IIcsdzb2TlJu23NEUDyDLQ9TXTN2WRK6Y= +github.com/prometheus/common v0.72.1/go.mod h1:1IoBY4IRaJnhs2ESjNzjilP1cJ/P8pimGoomq7zRXrA= github.com/prometheus/otlptranslator v1.0.0 h1:s0LJW/iN9dkIH+EnhiD3BlkkP5QVIUVEoIwkU+A6qos= github.com/prometheus/otlptranslator v1.0.0/go.mod h1:vRYWnXvI6aWGpsdY/mOT/cbeVRBlPWtBNDb7kGR3uKM= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= @@ -1169,8 +1161,8 @@ github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= -github.com/prometheus/procfs v0.21.1 h1:GljZCt+zSTS+NZq88cyQ1LjZ+RCHp3uVuabBWA5+OJI= -github.com/prometheus/procfs v0.21.1/go.mod h1:aB55Cww9pdSJVHk0hUf0inxWyyjPogFIjmHKYgMKmtY= +github.com/prometheus/procfs v0.22.0 h1:6q9+/JL9IKAPbCmBrv9n5O5Ty3NKnciV5X7YGw0oics= +github.com/prometheus/procfs v0.22.0/go.mod h1:CvmFr/GVhIjIvWJZW3tgkODBQMRIf0EyWMQLHCHab58= github.com/quay/claircore v1.6.1-0.20260916195820-3902930ff815 h1:jCU2qfY0HKDxhgnsn5tuZ3qaAE/C3r2neX+TYhcJwEo= github.com/quay/claircore v1.6.1-0.20260916195820-3902930ff815/go.mod h1:fdao6wPVC6bYZk3DOuUQDGjxhYVL9codeSnmx/nqlo0= github.com/quay/claircore/toolkit v1.0.0/go.mod h1:3ELtgf92x7o1JCTSKVOAqhcnCTXc4s5qiGaEDx62i20= @@ -1212,17 +1204,15 @@ github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIH github.com/sagikazarmark/crypt v0.3.0/go.mod h1:uD/D+6UF4SrIR1uGEv7bBNkNqLGqUr43MRiaGWX1Nig= github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc= github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik= -github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 h1:KRzFb2m7YtdldCEkzs6KqmJw4nqEVZGK7IN2kJkjTuQ= -github.com/santhosh-tekuri/jsonschema/v6 v6.0.2/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU= +github.com/santhosh-tekuri/jsonschema/v6 v6.0.3 h1:1EYB5IzjZawrrnELUi78f9fPu57HuXjmddZPjrls/28= +github.com/santhosh-tekuri/jsonschema/v6 v6.0.3/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU= github.com/sassoftware/relic/v8 v8.2.0 h1:9/L4S4I6an/JsPNhmcTpqfiOIsLb/iJaePZD1xR+ulE= github.com/sassoftware/relic/v8 v8.2.0/go.mod h1:pZy7hLT9WCOKPonV8G/fplvtBLOZd6/kWtKqeHR6nKc= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/secure-systems-lab/go-securesystemslib v0.11.0 h1:iuCR9kcMFD4QurdKrGvPLoKZLv9YvwPYVr0473BdtFs= github.com/secure-systems-lab/go-securesystemslib v0.11.0/go.mod h1:+PMOTjUGwHj2vcZ+TFKlb1tXRbrdWE1LYDT5i9JC80Q= -github.com/segmentio/analytics-go/v3 v3.3.0 h1:8VOMaVGBW03pdBrj1CMFfY9o/rnjJC+1wyQHlVxjw5o= -github.com/segmentio/analytics-go/v3 v3.3.0/go.mod h1:p8owAF8X+5o27jmvUognuXxdtqvSGtD0ZrfY2kcS9bE= -github.com/segmentio/backo-go v1.0.1 h1:68RQccglxZeyURy93ASB/2kc9QudzgIDexJ927N++y4= -github.com/segmentio/backo-go v1.0.1/go.mod h1:9/Rh6yILuLysoQnZ2oNooD2g7aBnvM7r/fNVxRNWfBc= +github.com/segmentio/analytics-go/v3 v3.4.0 h1:8ACTF5ggK3W70b+kHId5SyxyWGYZ1sdgd/e8tb8jMRM= +github.com/segmentio/analytics-go/v3 v3.4.0/go.mod h1:l0qTADX69i/7AuXvN3tg0QMfcW4Oe6YN5VgsgqGaAPI= github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw= github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= @@ -1357,8 +1347,8 @@ github.com/theupdateframework/go-tuf v0.7.0 h1:CqbQFrWo1ae3/I0UCblSbczevCCbS31Qv github.com/theupdateframework/go-tuf v0.7.0/go.mod h1:uEB7WSY+7ZIugK6R1hiBMBjQftaFzn7ZCDJcp1tCUug= github.com/theupdateframework/go-tuf/v2 v2.4.2 h1:w7976/W8uTwlsegP5nRymlpjPgrwSh+AXUf85is6nJk= github.com/theupdateframework/go-tuf/v2 v2.4.2/go.mod h1:JqBrIUnNLAaNq/8GmBcEMFWfAFBbqp/MkJEJseXKbks= -github.com/tidwall/gjson v1.19.0 h1:xwxm7n691Uf3u5OFjzngavjGTh55KX5q/9w9xHW88JU= -github.com/tidwall/gjson v1.19.0/go.mod h1:V37/opeE/JbLUOfH0QTXiNez2l0RUjYUhpT4szFQAfc= +github.com/tidwall/gjson v1.20.0 h1:+agJ3rEzKcCXKDKo0ml26UROcpcAlnZyjq+TjbY5Fto= +github.com/tidwall/gjson v1.20.0/go.mod h1:V37/opeE/JbLUOfH0QTXiNez2l0RUjYUhpT4szFQAfc= github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= @@ -1392,8 +1382,6 @@ github.com/vbauerster/mpb/v4 v4.12.2 h1:TsBs1nWRYF0m8cUH13pxNhOUqY6yKcOr2PeSYxp2 github.com/vbauerster/mpb/v4 v4.12.2/go.mod h1:LVRGvMch8T4HQO3eg2pFPsACH9kO/O6fT/7vhGje3QE= github.com/vladimirvivien/gexe v0.5.0 h1:AWBVaYnrTsGYBktXvcO0DfWPeSiZxn6mnQ5nvL+A1/A= github.com/vladimirvivien/gexe v0.5.0/go.mod h1:3gjgTqE2c0VyHnU5UOIwk7gyNzZDGulPb/DJPgcw64E= -github.com/vmihailenco/msgpack/v4 v4.3.12/go.mod h1:gborTTJjAo/GWTqqRjrLCn9pgNN+NXzzngzBKDPIqw4= -github.com/vmihailenco/tagparser v0.1.1/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI= github.com/weppos/publicsuffix-go v0.13.0/go.mod h1:z3LCPQ38eedDQSwmsSRW4Y7t2L8Ln16JPQ02lHAdn5k= github.com/weppos/publicsuffix-go v0.40.3-0.20250127173806-e489a31678ca/go.mod h1:43Dfyxu2dpmLg56at26Q4k9gwf3yWSUiwk8kGnwzULk= github.com/weppos/publicsuffix-go v0.50.2 h1:KsJFc8IEKTJovM46SRCnGNsM+rFShxcs6VEHjOJcXzE= @@ -1402,6 +1390,13 @@ github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo= +github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= +github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= +github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo= github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos= github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ= @@ -1428,7 +1423,6 @@ github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1 github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/zalando/go-keyring v0.2.6 h1:r7Yc3+H+Ux0+M72zacZoItR3UDxeWfKTcabvkI8ua9s= github.com/zalando/go-keyring v0.2.6/go.mod h1:2TCrxYrbUNYfNS/Kgy/LSrkSQzZ5UPVH85RwfczwvcI= -github.com/zclconf/go-cty v1.10.0/go.mod h1:vVKLxnk3puL4qRAv72AO+W99LUD4da90g3uUAzyuvAk= github.com/zmap/rc2 v0.0.0-20131011165748-24b9757f5521/go.mod h1:3YZ9o3WnatTIZhuOtot4IcUfzoKVjUHqu6WALIyI0nE= github.com/zmap/rc2 v0.0.0-20190804163417-abaa70531248/go.mod h1:3YZ9o3WnatTIZhuOtot4IcUfzoKVjUHqu6WALIyI0nE= github.com/zmap/zcertificate v0.0.0-20180516150559-0e3d58b1bac4/go.mod h1:5iU54tB79AMBcySS0R2XIyZBAVmeHranShAFELYx7is= @@ -1440,8 +1434,8 @@ github.com/zmap/zcrypto v0.0.0-20250129210703-03c45d0bae98/go.mod h1:YTUyN/U1oJ7 github.com/zmap/zlint/v3 v3.0.0/go.mod h1:paGwFySdHIBEMJ61YjoqT4h7Ge+fdYG4sUQhnTb1lJ8= github.com/zmap/zlint/v3 v3.6.6 h1:tH7RJM9bDmh7IonlLEkFIkIn8XDYDYjehhUPgpLVqYA= github.com/zmap/zlint/v3 v3.6.6/go.mod h1:6yXG+CBOQBRpMCOnpIVPUUL296m5HYksZC9bj5LZkwE= -go.etcd.io/bbolt v1.4.3 h1:dEadXpI6G79deX5prL3QRNP6JB8UxVkqo4UPnHaNXJo= -go.etcd.io/bbolt v1.4.3/go.mod h1:tKQlpPaYCVFctUIgFKFnAlvbmB3tpy1vkTnDWohtc0E= +go.etcd.io/bbolt v1.5.0 h1:S7GAl7Fxv12yohbwFfIbQCGDWbQbtDGPET4P/bD4lxU= +go.etcd.io/bbolt v1.5.0/go.mod h1:mkltfYE5aUHQxUct9N9V+Kp7aSjFqjgrhcXIS70Lrdk= go.etcd.io/etcd/api/v3 v3.5.1/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= go.etcd.io/etcd/client/pkg/v3 v3.5.1/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= go.etcd.io/etcd/client/v2 v2.305.1/go.mod h1:pMEacxZW7o8pg4CrFE7pquyCJJzZvkvdD2RibOCCCGs= @@ -1456,16 +1450,16 @@ go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/contrib/bridges/prometheus v0.67.0 h1:dkBzNEAIKADEaFnuESzcXvpd09vxvDZsOjx11gjUqLk= go.opentelemetry.io/contrib/bridges/prometheus v0.67.0/go.mod h1:Z5RIwRkZgauOIfnG5IpidvLpERjhTninpP1dTG2jTl4= -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/detectors/gcp v1.45.0 h1:9jR0ZPRok9ryaOQ2Wx8rg5F7Aon59mxrqbVI60/vlBk= +go.opentelemetry.io/contrib/detectors/gcp v1.45.0/go.mod h1:VSme3o2fvSg5bVg0dRzyHaj4Z5EVhG+g2Fde6LKzmQA= go.opentelemetry.io/contrib/exporters/autoexport v0.67.0 h1:4fnRcNpc6YFtG3zsFw9achKn3XgmxPxuMuqIL5rE8e8= go.opentelemetry.io/contrib/exporters/autoexport v0.67.0/go.mod h1:qTvIHMFKoxW7HXg02gm6/Wofhq5p3Ib/A/NNt1EoBSQ= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.68.0 h1:0Qx7VGBacMm9ZENQ7TnNObTYI4ShC+lHI16seduaxZo= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.68.0/go.mod h1:Sje3i3MjSPKTSPvVWCaL8ugBzJwik3u4smCjUeuupqg= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0 h1:8tvICD4vSTOOsNrsI4Ljf6C+6UKvpTEH5XY3JMoyPoo= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0/go.mod h1:z9+yiacE0IHRqM4qFfkbt/JYlmYXgss8GY/jXoNuPJI= -go.opentelemetry.io/otel v1.45.0 h1:pdrWmLHofpubmArBv1LgFSv1Z0Ie/ppdZzu+kUN5EeU= -go.opentelemetry.io/otel v1.45.0/go.mod h1:XZxIqPapzEYnhNSScF5DIqXhm/rYi0FzCe2XddAwZfQ= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.71.0 h1:B2h3uqicet1CT2N5TOFhS+Gq++9i0/CLmaxvhmhtP5s= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.71.0/go.mod h1:dylvB+ZiiwMvsDij9O84Uy7SijLgHMX4mbkncds+4Sw= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.71.0 h1:3g7B90UzBltIDKq1/5mrTGxTnOFDV0ICOhLoxiZ8jlg= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.71.0/go.mod h1:Ef8SuTh59BT7+ofpDxN9z+yOlc4t2GjLmKDgYNJL/NU= +go.opentelemetry.io/otel v1.46.0 h1:FHt5/CDyVxi/8IM1CH7VE/rRgq3kLHa2mSTVMO8AWyc= +go.opentelemetry.io/otel v1.46.0/go.mod h1:Gj3SEScelsNC45tp4nSxRYlS+f5iez7W8XPMCt905kE= go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.19.0 h1:Dn8rkudDzY6KV9dr/D/bTUuWgqDf9xe0rr4G2elrn0Y= go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.19.0/go.mod h1:gMk9F0xDgyN9M/3Ed5Y1wKcx/9mlU91NXY2SNq7RQuU= go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.19.0 h1:HIBTQ3VO5aupLKjC90JgMqpezVXwFuq6Ryjn0/izoag= @@ -1484,32 +1478,32 @@ go.opentelemetry.io/otel/exporters/prometheus v0.66.0 h1:vkrK8PAznv2NKt2r+kdu252 go.opentelemetry.io/otel/exporters/prometheus v0.66.0/go.mod h1:V/UB6D3vMF/UBOL5igAsAYnk1nG/bzYYTzvsB16cy7o= go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.19.0 h1:GJkybS+crDMdExT/BUNCEgfrmfboztcS6PhvSo88HKM= go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.19.0/go.mod h1:NuAyxRYIG2lKX3YQkB+83StTxM7s52PUUkRRiC0wnYI= -go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.44.0 h1:hqxVTu/GtBF+vJ8d1fzW7fRxZFvgoDjWcxwwCaFDYpU= -go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.44.0/go.mod h1:z5fVEF4X5v0ESvlJqBrrFlBVoj5EQuefZpzsu7R+x5Q= -go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.43.0 h1:mS47AX77OtFfKG4vtp+84kuGSFZHTyxtXIN269vChY0= -go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.43.0/go.mod h1:PJnsC41lAGncJlPUniSwM81gc80GkgWJWr3cu2nKEtU= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.45.0 h1:dm9iyzn6tioYZtwqaiBSU0TSI8Yu/8dTIbfG0+B49DY= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.45.0/go.mod h1:xAvxYjYK28qvt+yu4BYZ/zMmAjwMXINXD6JiMyeB8iI= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.46.0 h1:KdRxPiAoMptR3vfWzvjjvutTsSiwbC2uG0496rzZNfo= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.46.0/go.mod h1:K/qSA+3G7Eovxi4K09wzrAgkWRnosS0DAOZeEpve7sM= go.opentelemetry.io/otel/log v0.19.0 h1:KUZs/GOsw79TBBMfDWsXS+KZ4g2Ckzksd1ymzsIEbo4= go.opentelemetry.io/otel/log v0.19.0/go.mod h1:5DQYeGmxVIr4n0/BcJvF4upsraHjg6vudJJpnkL6Ipk= -go.opentelemetry.io/otel/metric v1.45.0 h1:7Eg1uH7CJ5cXv9is6tnBe1FI6rj1nwUdbFypRm3br/M= -go.opentelemetry.io/otel/metric v1.45.0/go.mod h1:HAPbm1nd3p1PmFH7v2dR+6BjXxw+Lq4a2+pndMAm08s= -go.opentelemetry.io/otel/metric/x v0.67.0 h1:PcicCNZFkZ4bXfSooXdo3WN7RBOVOtjVdo1wD358Uns= -go.opentelemetry.io/otel/metric/x v0.67.0/go.mod h1:FBjCWZe6wgcqxcMtjdGiClDKXb2YxxXii0CXftE4QtI= -go.opentelemetry.io/otel/sdk v1.45.0 h1:4VVSMgQ83dUgW2aoX5f6JgLvHwIvzcuLnF9lUdCSpCw= -go.opentelemetry.io/otel/sdk v1.45.0/go.mod h1:Sr40LgXV7DsKMMJMKOhUWOgMWTfAaqvm2kF0g7ilwuA= +go.opentelemetry.io/otel/metric v1.46.0 h1:yBnkXvgV7AXFILZc5K6IZe/CBFF3OS7BJ8ov6/lj0K8= +go.opentelemetry.io/otel/metric v1.46.0/go.mod h1:iPmdWqifKUdzziPkvvzIJXITl56fQx2mGM/DHLB3/2o= +go.opentelemetry.io/otel/metric/x v0.68.0 h1:TA/cBT23D3MnxYPwHL7YFOdYGdx0A0v+s7Mzotpd1dU= +go.opentelemetry.io/otel/metric/x v0.68.0/go.mod h1:agudOmvWhwUTjgibWDzxD2PoWYnpw5Ht5jISYOD2Hd4= +go.opentelemetry.io/otel/sdk v1.46.0 h1:h5CNQQjEbuQXY/JfZtgt3i7HVFV3aHPO2OAwO2eTYPI= +go.opentelemetry.io/otel/sdk v1.46.0/go.mod h1:GAERFXFt5SYCEB+YiKUbMBeza6UaDH7GmGOZEfh2gSM= go.opentelemetry.io/otel/sdk/log v0.19.0 h1:scYVLqT22D2gqXItnWiocLUKGH9yvkkeql5dBDiXyko= go.opentelemetry.io/otel/sdk/log v0.19.0/go.mod h1:vFBowwXGLlW9AvpuF7bMgnNI95LiW10szrOdvzBHlAg= -go.opentelemetry.io/otel/sdk/metric v1.45.0 h1:oVFszMfyj1Am6s24Vtc7wBb8BKLcwepJjNEYILuiE3o= -go.opentelemetry.io/otel/sdk/metric v1.45.0/go.mod h1:vUWUxDZvu1WVRj8JA8S0AdhsPrZoDpA2DdZauIh4mDA= -go.opentelemetry.io/otel/trace v1.45.0 h1:l/mP6Uv7oNO7/TblbhpbgMidxhq1uO/rPsikOyVhxag= -go.opentelemetry.io/otel/trace v1.45.0/go.mod h1:qoJJA2xNMnxRrdISU/kLtfUH2wNeQbiv+jhs/CxI8bc= +go.opentelemetry.io/otel/sdk/metric v1.46.0 h1:0piZ26EG4RBfebb2jhDH6ERCYHoVWduc3kLgPCwSnSE= +go.opentelemetry.io/otel/sdk/metric v1.46.0/go.mod h1:I1PbKrdVc8Qu8HYVDNtqVIwLwjNrhsV/uFuxfwg8mO4= +go.opentelemetry.io/otel/trace v1.46.0 h1:OULy7ccdJnZtJ0UDYFOIGaCmiWzJ8Vi2G/Rsu60qs1c= +go.opentelemetry.io/otel/trace v1.46.0/go.mod h1:J7GAXweO77XSFkB/rmAqk9D6ihszhFjLU+d9WuUxDLI= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.opentelemetry.io/proto/otlp v1.11.0 h1:5rrYs0Ykyj50sdU/JU0x8etU+LubXWb+gED6TbEdMIk= go.opentelemetry.io/proto/otlp v1.11.0/go.mod h1:SmVizdCOAm3XBtG1g1NnOdhW6jtddT72hLMhv8VwA8E= go.step.sm/crypto v0.87.0 h1:+u0pDz1OV9M/SswA6KtcQ3GSrYMMelbL//0W/ceJVYI= go.step.sm/crypto v0.87.0/go.mod h1:gBr1mpMiKs5804/Yt03uAoOBiw3DFxARZnGUKEUO82M= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= -go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= +go.uber.org/atomic v1.12.0 h1:BvcXdFKuviU4fTL/f+SxdQ5qJX/Jix8pAkgdUcb3XOE= +go.uber.org/atomic v1.12.0/go.mod h1:I6c4cg+6HCxRjfjSsYtApoFILnpc0CGUdGkXVqbYVNk= go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= @@ -1550,8 +1544,8 @@ golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqj golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U= golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY= golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc= -golang.org/x/crypto v0.57.0 h1:3ZVCjf8Ggz7zneR/EHRVx68Ctf+2pmIMP2UFhh9cC6M= -golang.org/x/crypto v0.57.0/go.mod h1:Fdz0i5U6CoizGwLda9DttjSk6qlZo25zYNtR+ycvuZA= +golang.org/x/crypto v0.58.0 h1:COkYLr4k7nDI4r6QtMML2AfzeIswWHAJthLS6K/M/54= +golang.org/x/crypto v0.58.0/go.mod h1:Xh+kl5A+M0gMiWDytH2Hhr5Z7WbNCpv/eB/EFgfSk+w= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -1562,8 +1556,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f h1:W3F4c+6OLc6H2lb//N1q4WpJkhzJCK5J6kUi1NTVXfM= -golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f/go.mod h1:J1xhfL/vlindoeF/aINzNzt2Bket5bjo9sdOYzOsU80= +golang.org/x/exp v0.0.0-20260908205506-85c1c2202aba h1:Ck8QetSgk912qxWLMCKxd0in+aiyBQyDSMae6e/xmpU= +golang.org/x/exp v0.0.0-20260908205506-85c1c2202aba/go.mod h1:50RgIsmK7OwqzTTeqcSXQW8SswW0o8fRcDxmqGluJ8E= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -1602,8 +1596,8 @@ golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= golang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ= golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc= -golang.org/x/mod v0.41.0 h1:qJmnOUb4YB+FsEuM3HcWucdZASCPGhsX6uljO6pog0c= -golang.org/x/mod v0.41.0/go.mod h1:Ek9pY8RKWXwsWvd3rQiHYtMqkjSUV+s1Rj7j4H5Ur6o= +golang.org/x/mod v0.42.0 h1:ICeL3t2L30ljSPHHxDnR4fqVcBt/zR2zJAb9juavmQg= +golang.org/x/mod v0.42.0/go.mod h1:u5fzIrRInkL8MaO1cbRkUHT64v303YIT5KEqpNr7jRs= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1670,8 +1664,8 @@ golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA= golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= -golang.org/x/net v0.59.0 h1:5zfYln+w5XCxwrnMMJPufRgNoXEaGxl0wo5GqPXyues= -golang.org/x/net v0.59.0/go.mod h1:2DA/G1UfVbCpQPeWTmMPGY7Cs2PkBkwu743bVX5PIVg= +golang.org/x/net v0.61.0 h1:RR+6j/BTBCrggOLugxa+66R8FvoN6MHlzDlZIbN9YFw= +golang.org/x/net v0.61.0/go.mod h1:WgDCOTH8iwtB66T4/GfI94AHo/hhdb7Hah9NDseQfiI= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -1693,8 +1687,8 @@ golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= -golang.org/x/sync v0.23.0 h1:KameEIfc1IkluZyXWLn39Wd4tURc6GbCiISGiZm2bQk= -golang.org/x/sync v0.23.0/go.mod h1:sUUOizhqBxiL6pEWpqNLUiaJn1ShEbZ6BBqskPbjZm0= +golang.org/x/sync v0.24.0 h1:iqiDrGoDgyuvAKrhLQthUEzU/R378N78gXbBjxed7oA= +golang.org/x/sync v0.24.0/go.mod h1:sUUOizhqBxiL6pEWpqNLUiaJn1ShEbZ6BBqskPbjZm0= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1792,8 +1786,8 @@ golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/sys v0.48.0 h1:bbX/i/6MgT9BVLM9RT1thmxL04yeTAhbEz4SyadbXoo= -golang.org/x/sys v0.48.0/go.mod h1:hNLxWAXmnKAxqDtdwIYC4bM9oQPEecfsnNMuSxOs3og= +golang.org/x/sys v0.49.0 h1:XbzkgYJHdqh/8m2Uu0W/dQv8nktxx4BFHp1M0gROTXA= +golang.org/x/sys v0.49.0/go.mod h1:hNLxWAXmnKAxqDtdwIYC4bM9oQPEecfsnNMuSxOs3og= golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= golang.org/x/telemetry v0.0.0-20240521205824-bda55230c457/go.mod h1:pRgIJT+bRLFKnoM1ldnzKoxTIn14Yxz928LQRYYgIN0= golang.org/x/telemetry v0.0.0-20250710130107-8d8967aff50b/go.mod h1:4ZwOYna0/zsOKwuR5X/m0QFOJpSZvAxFfkQT+Erd9D4= @@ -1812,8 +1806,8 @@ golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek= golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ= golang.org/x/term v0.33.0/go.mod h1:s18+ql9tYWp1IfpV9DmCtQDDSRBUjKaw9M1eAv5UeF0= golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw= -golang.org/x/term v0.46.0 h1:3+OXuTbaKDgwk8jTi3aSLHRlmWqHEUDUtxnbFigO4YE= -golang.org/x/term v0.46.0/go.mod h1:+K02xbkittuwc0Am4abfA3Fc+XRGXkvBXNO88NCXPoc= +golang.org/x/term v0.47.0 h1:cpyOwQkr2Nzxe2p6KnrTG7Ei2wtzjsuSjhpXXepZ/BM= +golang.org/x/term v0.47.0/go.mod h1:efAfAuG6RKABvg1ArGebRkvX6LXul14SM67o2e4mUCY= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1833,8 +1827,8 @@ golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA= golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU= golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= -golang.org/x/text v0.42.0 h1:JbOZXgfeCPU9gacVtYliJqOhD+zhrEqK4LfdpmlUZqI= -golang.org/x/text v0.42.0/go.mod h1:ojzP1Z+2QtioaF8DTtO8K5q7JWVVYwZKenzujK0Zd0E= +golang.org/x/text v0.43.0 h1:1QivrlhwAsnMOcqgOdl9my7s9OyZl8O5/UGUTJVXoWc= +golang.org/x/text v0.43.0/go.mod h1:hbSIYA/amXcRXTFSZqh8tWHEoqRvvbKPJ2uz/KU+a+s= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -1911,8 +1905,8 @@ golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg golang.org/x/tools v0.34.1-0.20250613162507-3f93fece84c7/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg= golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw= golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s= -golang.org/x/tools v0.50.0 h1:c2ifzfcuY7L90lZ2aKd8S4K2NpASF08SZx9ZuJkHmSU= -golang.org/x/tools v0.50.0/go.mod h1:7ulVMw3831Mwi5EZD6RomGyffr4VFjuNYXf2BbCEAV0= +golang.org/x/tools v0.52.0 h1:ryxg4U63oaUYk7uOLTF37n43f4qgtGvqV4Ioit1BaEs= +golang.org/x/tools v0.52.0/go.mod h1:ItcP5UNaVGG+Yc4eIYmaFLNJ4soEopkx5Y2d4lcEurs= golang.org/x/tools/go/expect v0.1.0-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY= golang.org/x/tools/go/expect v0.1.1-deprecated h1:jpBZDwmgPhXsKZC6WhL20P4b/wmnpsEAGHaNy0n/rJM= golang.org/x/tools/go/expect v0.1.1-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY= @@ -1961,8 +1955,8 @@ google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdr google.golang.org/api v0.59.0/go.mod h1:sT2boj7M9YJxZzgeZqXogmhfmRWDtPzT31xkieUbuZU= google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I= google.golang.org/api v0.62.0/go.mod h1:dKmwPCydfsad4qCH08MSdgWjfHOyfpd4VtDGgRFdavw= -google.golang.org/api v0.299.0 h1:b3K+ydSMd0kh6TQI6bJyApRQfqQX2MfSOaVkpM59mJw= -google.golang.org/api v0.299.0/go.mod h1:zlR3GVA8b2R5nv5Ij9UWe37StVB3cxDD7DBFi4ZFsHw= +google.golang.org/api v0.301.0 h1:rTvI8szs17R7b09ggb34Bv4XrmFaFl9uCBncgxskqtw= +google.golang.org/api v0.301.0/go.mod h1:gGUTaOjgsaG/KNyyW1tFO+DSFm3EJw4BwyehlcmDCXs= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -2036,10 +2030,10 @@ google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ6 google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20260715232425-e75dac1f907d h1:C9v1o0/4quuhOAfmRXA2j+we0PqZIp8traLdeogF3Ms= google.golang.org/genproto v0.0.0-20260715232425-e75dac1f907d/go.mod h1:Wz2wFJntZFmLGo7pLDXZ3wYk5hyc0Mb+SkHhDDXT+lU= -google.golang.org/genproto/googleapis/api v0.0.0-20260803160001-6ac0973c030d h1:FarXi840EJWSHYTN3ERkADbPWjl307+FGrA22KAVjjc= -google.golang.org/genproto/googleapis/api v0.0.0-20260803160001-6ac0973c030d/go.mod h1:K/+WGbmBY7aNW1HDw1fJnKYo10i0DkAX6pows00dLig= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260921155816-b14227669459 h1:b0xCahf3FK2m2Cv0p4vTozGPWncCvLfwV86UNg8xWU8= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260921155816-b14227669459/go.mod h1:OaIUM3+LpYcK2GXM4FTmhWoIq371Owdr+Cc7/BsYHHc= +google.golang.org/genproto/googleapis/api v0.0.0-20260921155816-b14227669459 h1:GS9OIt/j7c8bvBjYNgnKQysVfmV7e4jM0H8ZK95G4t8= +google.golang.org/genproto/googleapis/api v0.0.0-20260921155816-b14227669459/go.mod h1:PX5/4vemwVoXtwEcRDWwcR1/r0qrosfx3qoVADMwnVE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260928230214-8a89bd6388cc h1:4bNTbnb44EqGVy9HaQxSY2jnafSUdgV3qHtedvNpDKg= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260928230214-8a89bd6388cc/go.mod h1:OaIUM3+LpYcK2GXM4FTmhWoIq371Owdr+Cc7/BsYHHc= google.golang.org/grpc v1.18.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= @@ -2113,16 +2107,16 @@ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkep gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= -gorm.io/driver/postgres v1.6.2 h1:BvXQ/cNUg63q5TFNg672DmDcowZSFrNLkkA3Xe6GXq4= -gorm.io/driver/postgres v1.6.2/go.mod h1:0c4fQA44XhOklXDkgtuKqysHCycTa5i9e3EIpDGCwXk= +gorm.io/driver/postgres v1.6.3 h1:bAn6O2pUa8LtpWEvL5NFU4+52Tfx8Ut7IVaIacCLcI0= +gorm.io/driver/postgres v1.6.3/go.mod h1:0c4fQA44XhOklXDkgtuKqysHCycTa5i9e3EIpDGCwXk= gorm.io/driver/sqlite v1.6.0 h1:WHRRrIiulaPiPFmDcod6prc4l2VGVWHz80KspNsxSfQ= gorm.io/driver/sqlite v1.6.0/go.mod h1:AO9V1qIQddBESngQUKWL9yoH93HIeA1X6V633rBwyT8= gorm.io/gorm v1.31.2 h1:3o8FXNo9v9S858gil+3LlZA1LkCOzgb4g5BL64FgaCo= gorm.io/gorm v1.31.2/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs= gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q= gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA= -helm.sh/helm/v3 v3.21.3 h1:wkamdwI3liEkW6wI1l9aGqQZGxcTKyt8kx0qJLPcmCg= -helm.sh/helm/v3 v3.21.3/go.mod h1:iaJ0iNsPoTZl++7h6vzQFyT0VEVtLYJiyRBDkPOOBTs= +helm.sh/helm/v3 v3.22.0 h1:n1if5iQgHYsujPAcO5oonH/KiSNUmqmUF0iGVjSF3AU= +helm.sh/helm/v3 v3.22.0/go.mod h1:EVl/147Ai2z13JOykbO1Jn9HRaXyU7uHlbAJmODq/Fg= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= @@ -2132,31 +2126,31 @@ honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= k8s.io/api v0.23.3/go.mod h1:w258XdGyvCmnBj/vGzQMj6kzdufJZVUwEM1U2fRJwSQ= -k8s.io/api v0.36.5 h1:vtL/ByHmw7suLt+SGVMPZnuj8QdfZ0kN1vvMvHRCY9c= -k8s.io/api v0.36.5/go.mod h1:erfc3/3d6z30KO2Kf+BmOewE6CajqZTz1Hcd+FQqg60= -k8s.io/apiextensions-apiserver v0.36.5 h1:pZcOcB7ur1ou+VeQSGChj3cvhrL2nA3gERMUaCHVJNQ= -k8s.io/apiextensions-apiserver v0.36.5/go.mod h1:l1uFNtfzcEBqgrYEiOIYwu+6R1RCkuZIiEvfPE6RpNs= +k8s.io/api v0.37.1 h1:l6N77U7tjwB5L056bgrBTJIEdevac/naBZ3iSvDNfpM= +k8s.io/api v0.37.1/go.mod h1:zSlbB1YpJ1YQlFVQy20UYll81UJSJJUMLhkhvg6Z78M= +k8s.io/apiextensions-apiserver v0.37.1 h1:7fIQG8eThDSTVYBWg/DOpI8v5wYfKCo0N8/TDMjj+zY= +k8s.io/apiextensions-apiserver v0.37.1/go.mod h1:1Q3ujWwHgnhBoGsdmGORflwK1sDFmuaqH1OsMDWk2Gs= k8s.io/apimachinery v0.23.3/go.mod h1:BEuFMMBaIbcOqVIJqNZJXGFTP4W6AycEpb5+m/97hrM= -k8s.io/apimachinery v0.36.5 h1:X5Xbg6G4om0ma6LOcGbmUmo+B5Nq7mmIpAWhk+tgCh8= -k8s.io/apimachinery v0.36.5/go.mod h1:oPTSicSaDHDdoGwOs5DFPo6YS2wdTnzFHIYMEglfJ7I= -k8s.io/apiserver v0.36.5 h1:KX599gn2a5fQXeoCrDNdpND+AJ9ymq3u4XXvAslwnlo= -k8s.io/apiserver v0.36.5/go.mod h1:wxPt+sgzaPkk+WOgsrwsGB5eyRFXlRuPd2EoZSvh5k8= -k8s.io/cli-runtime v0.36.5 h1:TwL178KVy8bRTe6h2nfvSwkcn/B2nDZpOqYqE8e+Eg0= -k8s.io/cli-runtime v0.36.5/go.mod h1:cPBZUIJIH8ElAFPy+5v2ypmhQOlzyNS36Sm+K3fr+/I= -k8s.io/client-go v0.36.5 h1:D79FgevPon0NIBcjUHMy+QnALtihLGTBqOvIGCgoZmw= -k8s.io/client-go v0.36.5/go.mod h1:JAGQ3N4Z8E3H0W0Nm6NBg1EvB9+/wtEoBPDe8XGkHsI= +k8s.io/apimachinery v0.37.1 h1:hGCYyvKHCwtwMitj2vU4vYx0Z16N9GyZk9BBnz0wDAE= +k8s.io/apimachinery v0.37.1/go.mod h1:jF84AyUi/IRIXRot5f+lm6MpxoWI+F1XgjaMmwCdTFw= +k8s.io/apiserver v0.37.1 h1:dUsNwjsvItyVKNpC1N0AhQ0OBSiUJKfWz004vPifOe0= +k8s.io/apiserver v0.37.1/go.mod h1:EWXW6PntGfOC2uD9GGVobnOExgFoX1z0hZo7f7Rs7Ng= +k8s.io/cli-runtime v0.37.1 h1:3mir5bM4XjMJHW3SMRk++3Ylf4YQne0XFFsW+IGT85U= +k8s.io/cli-runtime v0.37.1/go.mod h1:g3VQOm71f//aNbp79g0az9jBRTrvPjAHz7WxVysbZ4M= +k8s.io/client-go v0.37.1 h1:QTv/5ha4jAHtW9qxxVBkQVFBRDb4jHfFopQqqMdc+wM= +k8s.io/client-go v0.37.1/go.mod h1:dnAPtTnCNY38Ho04D2KdY1F4IKausa9UbqaAZKl60SY= k8s.io/code-generator v0.23.3/go.mod h1:S0Q1JVA+kSzTI1oUvbKAxZY/DYbA/ZUb4Uknog12ETk= -k8s.io/code-generator v0.36.5 h1:fpPI4R6mXMx9kWZnSq7MAlaeKS+T4dga9NYSqVJi3Pg= -k8s.io/code-generator v0.36.5/go.mod h1:hcPwUWz7zAAEfME+HgcvQePfBZmY/B/NMQUV7Rw3yIk= -k8s.io/component-base v0.36.5 h1:4lvIJ/QZDOchuIRqIna6JqjqO2MoDsH7JdaQ8FJtLVw= -k8s.io/component-base v0.36.5/go.mod h1:ZCmi+txXm31F2uYY5p9FwQsww0g5BM0pI/gNsH9uAMY= -k8s.io/component-helpers v0.36.5 h1:ZRChXMmBeKd0U34CgYHgQiQLzV0O971DTR3JSMUpgRU= -k8s.io/component-helpers v0.36.5/go.mod h1:cSPYaE5E3h0f8kHMCNYX7h9+XBWUNZ7Qt0VPzepw8z0= +k8s.io/code-generator v0.37.1 h1:tvxxAiGc2kMxoGx8HtUacs4cBdl0X5R+auBD2He974U= +k8s.io/code-generator v0.37.1/go.mod h1:RJepz1uW2WnHEwPDoCGzI3p4V23C7WuUdtqjueDl1uU= +k8s.io/component-base v0.37.1 h1:93DMmlENnK7gNLkL4pMqLO5M/HVf3p3sM4q/GasLveY= +k8s.io/component-base v0.37.1/go.mod h1:bBrdziT4dreQG5lzTaPVxBwsZe1oxphG+ZVdD56dQWQ= +k8s.io/component-helpers v0.37.1 h1:V6ueiH2SBqpZil1X9+uMK8GAwsGYPVfoRN9eA5vY2G8= +k8s.io/component-helpers v0.37.1/go.mod h1:Ib5+txJA7lG9/xCdRrPshX8GnRRTXPElxtxo+bFQ0Y8= k8s.io/gengo v0.0.0-20210813121822-485abfe95c7c/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= k8s.io/gengo v0.0.0-20211129171323-c02415ce4185/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= k8s.io/gengo/v2 v2.0.0-20250604051438-85fd79dbfd9f/go.mod h1:EJykeLsmFC60UQbYJezXkEsG2FLrt0GPNkU5iK5GWxU= -k8s.io/gengo/v2 v2.0.0-20250922181213-ec3ebc5fd46b h1:gMplByicHV/TJBizHd9aVEsTYoJBnnUAT5MHlTkbjhQ= -k8s.io/gengo/v2 v2.0.0-20250922181213-ec3ebc5fd46b/go.mod h1:CgujABENc3KuTrcsdpGmrrASjtQsWCT7R99mEV4U/fM= +k8s.io/gengo/v2 v2.0.0-20260408192533-25e2208e0dc3 h1:3L6PNkMLXkU/pz3jWzaaIUz0Rs2V9h+5O51AeRC7poc= +k8s.io/gengo/v2 v2.0.0-20260408192533-25e2208e0dc3/go.mod h1:yvyl3l9E+UxlqOMUULdKTAYB0rEhsmjr7+2Vb/1pCSo= k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= k8s.io/klog/v2 v2.30.0/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= @@ -2167,20 +2161,20 @@ k8s.io/klog/v2 v2.140.0 h1:Tf+J3AH7xnUzZyVVXhTgGhEKnFqye14aadWv7bzXdzc= k8s.io/klog/v2 v2.140.0/go.mod h1:o+/RWfJ6PwpnFn7OyAG3QnO47BFsymfEfrz6XyYSSp0= k8s.io/kube-openapi v0.0.0-20260427204847-8949caaa1199 h1:sWu4Td5mgJlwunsUydnhKEAfNUHM7hm1wfKEQmD7G5c= k8s.io/kube-openapi v0.0.0-20260427204847-8949caaa1199/go.mod h1:uGBT7iTA6c6MvqUvSXIaYZo9ukscABYi2btjhvgKGZ0= -k8s.io/kubectl v0.36.5 h1:466PfDKAiTUbgQSmnjYlashGcMn1zZ0IsOVo7oK1CIk= -k8s.io/kubectl v0.36.5/go.mod h1:nBQ7q6lEydoSESFe9DdQD9r6FS/k0/78C8hp1sC44zw= +k8s.io/kubectl v0.37.1 h1:n1VCIntOJiX943uByXCThSZQmE+4v9jGS8S9u94/aHU= +k8s.io/kubectl v0.37.1/go.mod h1:66cc4Bz8PxBTxlrHzBuzXEzGIytAnNasKQPeEcpuaV8= k8s.io/kubelet v0.32.13 h1:pGSrLTytcmuIlq4yvuRFnF4RdQjQh1FfsmPeRZXDKTo= k8s.io/kubelet v0.32.13/go.mod h1:XrwKgyKhVUE8TO/w9Lqq93UTM5W6t7ePItIgjvSmV/4= -k8s.io/streaming v0.36.5 h1:gcY9VwgT4J/idqwS9ZrzJtH8ouHvZuFEyDWofcaL+F4= -k8s.io/streaming v0.36.5/go.mod h1:tJ6S2bZa2HxIBauguBbCWSCYyd93Grfz1+z3tcOvlDE= +k8s.io/streaming v0.37.1 h1:TpzVfQeFuVndn2g9mFqxy1UcUYPwDzqjUmwR/IzJCWc= +k8s.io/streaming v0.37.1/go.mod h1:APlJR26ZWRcVy5bIEj0QRrKUXROtBHPcxl2NT7EAzPU= k8s.io/utils v0.0.0-20211116205334-6203023598ed/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20240711033017-18e509b52bc8/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -k8s.io/utils v0.0.0-20260319190234-28399d86e0b5 h1:kBawHLSnx/mYHmRnNUf9d4CpjREbeZuxoSGOX/J+aYM= -k8s.io/utils v0.0.0-20260319190234-28399d86e0b5/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk= -kubevirt.io/api v1.9.0 h1:FWqx9yGk/GHUP5o4IthGXZ8FeAlgVNv2It48wD86UUo= -kubevirt.io/api v1.9.0/go.mod h1:tFKAVaJMm7bXCMLxOoUdAqBprKAXoCa0sp0gBmnfBgY= -kubevirt.io/client-go v1.10.0-alpha.0 h1:4fEp/8Aa6OUDRo5RZT5Uehw3K6Bp/tQPA2hIJhFCQfw= -kubevirt.io/client-go v1.10.0-alpha.0/go.mod h1:bXmbwFVCJsUnFGg1lgVSB2Jc180N7IzlQRHXJxlrG6U= +k8s.io/utils v0.0.0-20260626114624-be93311217bd h1:Ea7fgQ5we8Y9T0OX5o0dAHzQOBRI07D/dEYRaB9ZZEs= +k8s.io/utils v0.0.0-20260626114624-be93311217bd/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk= +kubevirt.io/api v1.9.1 h1:kV+n8NPBS0SJ8b6Xm+tPxPgFn3JeQUHmvSQ2SNtu1IU= +kubevirt.io/api v1.9.1/go.mod h1:tFKAVaJMm7bXCMLxOoUdAqBprKAXoCa0sp0gBmnfBgY= +kubevirt.io/client-go v1.10.0-beta.0 h1:W0b7BBY4uKCbi+QP22Wpv/MRAmTch9R+3FumXtSlCUA= +kubevirt.io/client-go v1.10.0-beta.0/go.mod h1:lxFei8lrDf6zbLTzv2jE+O7lQxx32E8LHGmD8Tw4XbE= kubevirt.io/containerized-data-importer-api v1.64.0 h1:yBLY6qEogUp8F+XSJvabIkx9uEEDeToYDfRr0mbUxJc= kubevirt.io/containerized-data-importer-api v1.64.0/go.mod h1:VGp35wxpLXU18b7cnEpmcThI3AjcZUSfg/Zfql44U4o= kubevirt.io/controller-lifecycle-operator-sdk/api v0.2.4 h1:fZYvD3/Vnitfkx6IJxjLAk8ugnZQ7CXVYcRfkSKmuZY= @@ -2218,12 +2212,12 @@ oras.land/oras-go/v2 v2.6.2/go.mod h1:PlTtg4JTDJkDe8yVHpM2wz7/YDc00GVas+i4jAW2TZ rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= -sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.34.0 h1:hSfpvjjTQXQY2Fol2CS0QHMNs/WI1MOSGzCm1KhM5ec= -sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.34.0/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw= -sigs.k8s.io/controller-runtime v0.24.1 h1:miPEwrmirImAvgME1L9qebGHrOnGJoVmVdtOU9fRfo4= -sigs.k8s.io/controller-runtime v0.24.1/go.mod h1:vFkfY5fGt5xAC/sKb8IBFKgWPNKG9OUG29dR8Y2wImw= -sigs.k8s.io/controller-tools v0.21.0 h1:KXDQza3bgjlPY6xLR63tI/40gzjhyUAvkCrwzd2/6cs= -sigs.k8s.io/controller-tools v0.21.0/go.mod h1:DLIypi3Q2+azVAP8jr/mHXJgveYYHFjhnNOUuBJ10JE= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.36.0 h1:/YpDJ4vReG7ZmzSpBGxduXgywWkJU9zHubgJG03MT+Y= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.36.0/go.mod h1:tJo1aepTXyR+8Xs3sUsGBDk4Ub2AM5dPAPKJx0mpm5c= +sigs.k8s.io/controller-runtime v0.25.2 h1:bEkK3PVOIVK9X8QWLGVhJgmFc++47vfT6wakSzAOLsQ= +sigs.k8s.io/controller-runtime v0.25.2/go.mod h1:4QqLdT6z/L6Olj8JJCtvztid4/fnIiYsfaTFScegctc= +sigs.k8s.io/controller-tools v0.22.0 h1:eG3FAVja/KnlXKIWg95udIFz1cMyAtMjP11cqBh3t+k= +sigs.k8s.io/controller-tools v0.22.0/go.mod h1:VizwUStoZK7rReCj704czGGrB7mLxXTiJSJt7wN5ilI= sigs.k8s.io/e2e-framework v0.7.0 h1:AHkySTC6MvnnMbVSxaO4z1m2MhQKNFP+2Ihs5pRNLlM= sigs.k8s.io/e2e-framework v0.7.0/go.mod h1:1ZgXkUSjmnf18/JgHZNEATWjv48O5lJm9aI1QIsRdbw= sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6/go.mod h1:p4QtZmO4uMYipTQNzagwnNoseA6OxSUutVw05NhYDRs= @@ -2241,8 +2235,8 @@ sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= sigs.k8s.io/structured-merge-diff/v4 v4.2.1/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4= sigs.k8s.io/structured-merge-diff/v6 v6.2.0/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= -sigs.k8s.io/structured-merge-diff/v6 v6.4.0 h1:qmp2e3ZfFi1/jJbDGpD4mt3wyp6PE1NfKHCYLqgNQJo= -sigs.k8s.io/structured-merge-diff/v6 v6.4.0/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= +sigs.k8s.io/structured-merge-diff/v6 v6.4.2 h1:qdOxHwrl2Kaag1aQEarlYcOA9vSyGCp3CIki3aW8c4Q= +sigs.k8s.io/structured-merge-diff/v6 v6.4.2/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= diff --git a/image/postgres/Dockerfile b/image/postgres/Dockerfile index edb3503720933..dd775fda126b7 100644 --- a/image/postgres/Dockerfile +++ b/image/postgres/Dockerfile @@ -1,4 +1,4 @@ -ARG PG_VERSION=15 +ARG PG_VERSION=16 FROM quay.io/sclorg/postgresql-${PG_VERSION}-c9s:latest AS final USER root diff --git a/image/postgres/download.sh b/image/postgres/download.sh index 624b992ee4867..977dfde78cf4f 100755 --- a/image/postgres/download.sh +++ b/image/postgres/download.sh @@ -2,7 +2,7 @@ set -euo pipefail -postgres_major=15 +postgres_major=16 pg_rhel_major=9 arch="$(uname -m)" @@ -16,6 +16,7 @@ output_dir="/rpms" mkdir $output_dir if [[ "$arch" == "s390x" ]]; then + dnf module enable -y postgresql:16 dnf install -y --downloadonly --downloaddir=/tmp postgresql postgresql-private-libs postgresql-server postgresql-contrib mv /tmp/postgresql-contrib-*.rpm "${output_dir}/postgres-contrib.rpm" mv /tmp/postgresql-server-*.rpm "${output_dir}/postgres-server.rpm" diff --git a/image/postgres/konflux.Dockerfile b/image/postgres/konflux.Dockerfile index e68bb68a7eb91..044adaba6addd 100644 --- a/image/postgres/konflux.Dockerfile +++ b/image/postgres/konflux.Dockerfile @@ -1,5 +1,5 @@ -ARG PG_VERSION=15 -FROM registry.redhat.io/rhel9/postgresql-${PG_VERSION}:latest@sha256:a3a5c31afcd91ef5d96f20bcf7aa7b47b10c034f86b549b04985ebed6d14567e AS final +ARG PG_VERSION=16 +FROM registry.redhat.io/rhel9/postgresql-${PG_VERSION}:latest@sha256:791a6f14563178d1bbcd3515bc7dc80c1ae0788a829ff26551cdedbf8f50f0d2 AS final USER root diff --git a/image/postgres/scripts/docker-entrypoint.sh b/image/postgres/scripts/docker-entrypoint.sh index 371b5dbd60021..6eafa1e5ac6bb 100755 --- a/image/postgres/scripts/docker-entrypoint.sh +++ b/image/postgres/scripts/docker-entrypoint.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -# This file is from https://github.com/docker-library/postgres/tree/master/15/bookworm +# This file is from https://github.com/docker-library/postgres/tree/master/16/bookworm set -Eeo pipefail # TODO swap to -Eeuo pipefail above (after handling all potentially-unset variables) @@ -362,7 +362,7 @@ _main() { ### ROX_TLS_MIN_VERSION: passed to ssl_min_protocol_version (e.g. "TLSv1.2", "TLSv1.3"). ### ROX_TLS_OPENSSL_CIPHERS: colon-separated OpenSSL cipher names ### (e.g. "ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384"). - ### Only affects TLS 1.2; TLS 1.3 ciphers are not configurable in PostgreSQL 15. + ### Only affects TLS 1.2; TLS 1.3 ciphers are not configurable in PostgreSQL 16. if [ -n "${ROX_TLS_MIN_VERSION:-}" ]; then set -- "$@" -c "ssl_min_protocol_version=${ROX_TLS_MIN_VERSION}" fi diff --git a/image/postgres/scripts/init-entrypoint.sh b/image/postgres/scripts/init-entrypoint.sh index cb4ce80fc7c12..34344f78ab674 100755 --- a/image/postgres/scripts/init-entrypoint.sh +++ b/image/postgres/scripts/init-entrypoint.sh @@ -232,7 +232,7 @@ else --new-bindir="${NEW_BINARIES}" \ --old-datadir="${PGDATA}" \ --new-datadir="${PGDATA_NEW}" \ - --clone -j 4 -k --check + -j 4 -k --check RESULT=$? if [ $RESULT -ne 0 ]; then @@ -247,7 +247,7 @@ else --new-bindir="${NEW_BINARIES}" \ --old-datadir="${PGDATA}" \ --new-datadir="${PGDATA_NEW}" \ - --clone -j 4 -k + -j 4 -k RESULT=$? if [ $RESULT -ne 0 ]; then diff --git a/image/rhel/Dockerfile b/image/rhel/Dockerfile index 73ebed2028f8d..770bf1dc49ad4 100644 --- a/image/rhel/Dockerfile +++ b/image/rhel/Dockerfile @@ -1,12 +1,16 @@ +ARG PG_VERSION=16 + FROM registry.access.redhat.com/ubi9:latest AS ubi-base FROM registry.access.redhat.com/ubi9/ubi-micro:latest AS ubi-micro-base FROM ubi-base AS downloads ARG DEBUG_BUILD=no +ARG PG_VERSION WORKDIR / COPY download.sh /download.sh +COPY signatures/RPM-GPG-KEY-CentOS-Official /tmp/ RUN /download.sh FROM ubi-base AS stackrox_data @@ -39,6 +43,23 @@ RUN rpm --import /tmp/RPM-GPG-KEY-CentOS-Official && \ dnf clean all --installroot=/out/ && \ rm -rf /out/var/cache/dnf /out/var/cache/yum +FROM ubi-micro-base AS runtime-base + +ARG PG_VERSION + +COPY --from=package_installer /out/ / + +# Execute the clients in the final runtime to check their version and libraries. +RUN set -eu; \ + for client in pg_dump pg_restore; do \ + version="$("$client" --version)"; \ + echo "$version"; \ + case "$version" in \ + "$client (PostgreSQL) ${PG_VERSION}."*) ;; \ + *) echo "Expected PostgreSQL ${PG_VERSION} client: $client" >&2; exit 1 ;; \ + esac; \ + done + FROM ubi-base AS cli-tarballs RUN dnf install -y pigz @@ -50,7 +71,7 @@ RUN cd /tmp/roxctl && \ tar -I pigz -cvf "${f%.exe}.tar.gz" "$f"; \ done -FROM ubi-micro-base +FROM runtime-base ARG LABEL_VERSION ARG LABEL_RELEASE @@ -73,8 +94,6 @@ ENV PATH="/stackrox:$PATH" \ ROX_IMAGE_FLAVOR=${ROX_IMAGE_FLAVOR} \ ROX_PRODUCT_BRANDING=${ROX_PRODUCT_BRANDING} -COPY --from=package_installer /out/ / - COPY static-bin /stackrox/ COPY --from=downloads /output/go/ /go/ diff --git a/image/rhel/README.md b/image/rhel/README.md index c3283e8a714f3..818126d99469d 100644 --- a/image/rhel/README.md +++ b/image/rhel/README.md @@ -3,3 +3,15 @@ The RedHat based main image is currently used for the RedHat marketplace as well as for DoD customers. This image is built in opinionated way based on the DoD Centralized Artifacts Repository (DCAR) requirements outlined [here](https://dccscr.dsop.io/dsop/dccscr/tree/master/contributor-onboarding) + +## PostgreSQL clients + +`Dockerfile` downloads PostgreSQL client RPMs from PGDG for amd64, arm64, and +ppc64le. For s390x, `download.sh` adds the CentOS Stream 9 AppStream repository +to its download commands because PGDG does not publish s390x packages and UBI's +repositories do not provide the PostgreSQL 16 module. The download and runtime +stages use UBI on every architecture. + +`PG_VERSION` selects the client major version. The `runtime-base` stage runs +`pg_dump` and `pg_restore` and checks their versions in the runtime filesystem, +so missing libraries or an older client fail the image build. diff --git a/image/rhel/download.sh b/image/rhel/download.sh index 82570540b1b49..cbddcdaabef82 100755 --- a/image/rhel/download.sh +++ b/image/rhel/download.sh @@ -3,6 +3,7 @@ set -euo pipefail arch=$(uname -m) +postgres_major="${PG_VERSION:-16}" goarch="$arch" dnf_list_args=() if [[ "$arch" == "x86_64" ]]; then @@ -35,15 +36,17 @@ fi mkdir -p "$output_dir/rpms" if [[ "$arch" == "s390x" ]]; then - dnf install -y --downloadonly --downloaddir=/tmp postgresql postgresql-private-libs + rpm --import /tmp/RPM-GPG-KEY-CentOS-Official + centos_repo="--repofrompath=centos-appstream,https://mirror.stream.centos.org/9-stream/AppStream/${arch}/os/" + dnf "$centos_repo" module enable -y "postgresql:${postgres_major}" + dnf "$centos_repo" install -y --downloadonly --downloaddir=/tmp postgresql postgresql-private-libs mv /tmp/postgresql-private-libs-*.rpm "${output_dir}/rpms/postgres-libs.rpm" mv /tmp/postgresql-*.rpm "${output_dir}/rpms/postgres.rpm" else - postgres_major=15 pg_rhel_major=9 postgres_repo_url="https://download.postgresql.org/pub/repos/yum/reporpms/EL-${pg_rhel_major}-${arch}/pgdg-redhat-repo-latest.noarch.rpm" dnf install --disablerepo='*' -y "${postgres_repo_url}" - postgres_minor=$(dnf list ${dnf_list_args[@]+"${dnf_list_args[@]}"} --disablerepo='*' ""--enablerepo=pgdg${postgres_major} -y postgresql${postgres_major}-server."$arch" | tail -n 1 | awk '{print $2}') + postgres_minor=$(dnf list ${dnf_list_args[@]+"${dnf_list_args[@]}"} --disablerepo='*' "--enablerepo=pgdg${postgres_major}" -y "postgresql${postgres_major}-server.${arch}" | tail -n 1 | awk '{print $2}') postgres_minor="$postgres_minor.$arch" postgres_url="https://download.postgresql.org/pub/repos/yum/${postgres_major}/redhat/rhel-${pg_rhel_major}-${arch}" diff --git a/image/rhel/konflux.Dockerfile b/image/rhel/konflux.Dockerfile index 7361053a6a0f8..c06e1e6500b4d 100644 --- a/image/rhel/konflux.Dockerfile +++ b/image/rhel/konflux.Dockerfile @@ -1,4 +1,4 @@ -ARG PG_VERSION=15 +ARG PG_VERSION=16 FROM brew.registry.redhat.io/rh-osbs/openshift-golang-builder:rhel_9_golang_1.26@sha256:e74b03198cccc24c24a27071ad4d6c2edcfe0bb10c6de99c609ce6e3c06d6d04 AS go-builder @@ -49,7 +49,7 @@ RUN arch=$(uname -m) ; \ /go/src/github.com/stackrox/rox/app/image/rhel/bin/roxctl -FROM registry.access.redhat.com/ubi9/nodejs-22@sha256:ef4f18a7e736696b1314bdc21bcf57bfd50ba3ea4f80c13833f777316b25c035 as ui-builder +FROM registry.access.redhat.com/ubi9/nodejs-22@sha256:ec6d5fae468462129f094c6c463207bd950c9e2b0023f6c0fd1ae1cbdc5abd3e as ui-builder WORKDIR /go/src/github.com/stackrox/rox/app @@ -71,9 +71,9 @@ ENV UI_PKG_INSTALL_EXTRA_ARGS="--ignore-scripts" RUN make -C ui build -FROM registry.access.redhat.com/ubi9/ubi-micro:latest@sha256:7a0454cbd9bd847e8f6a63b6f0254a6efbeb6e0ed71a5d824a4f6cccbe626650 AS ubi-micro-base +FROM registry.access.redhat.com/ubi9/ubi-micro:latest@sha256:5be877a2a280ae36eb9ab80ca4efb28bbf77c57abbba38137cdc37a03b8465e6 AS ubi-micro-base -FROM registry.access.redhat.com/ubi9/ubi:latest@sha256:094ea2ecfd3225af8f93807b99daa9ff33710fc705ebdf6e8466f46ed605585c AS package_installer +FROM registry.access.redhat.com/ubi9/ubi:latest@sha256:16e9e43d99e181463513cc84a514859b1abb9460f1c4d9e208da4b4e51cd5cc3 AS package_installer ARG PG_VERSION diff --git a/image/roxctl/konflux.Dockerfile b/image/roxctl/konflux.Dockerfile index 948c16d3964d7..c52eb5d654c53 100644 --- a/image/roxctl/konflux.Dockerfile +++ b/image/roxctl/konflux.Dockerfile @@ -25,9 +25,9 @@ ENV GOEXPERIMENT=strictfipsruntime RUN RACE=0 CGO_ENABLED=1 GOOS=linux GOARCH=$(go env GOARCH) scripts/go-build.sh ./roxctl && \ cp bin/linux_$(go env GOARCH)/roxctl image/bin/roxctl -FROM registry.access.redhat.com/ubi9/ubi-micro:latest@sha256:7a0454cbd9bd847e8f6a63b6f0254a6efbeb6e0ed71a5d824a4f6cccbe626650 AS ubi-micro-base +FROM registry.access.redhat.com/ubi9/ubi-micro:latest@sha256:5be877a2a280ae36eb9ab80ca4efb28bbf77c57abbba38137cdc37a03b8465e6 AS ubi-micro-base -FROM registry.access.redhat.com/ubi9/ubi:latest@sha256:094ea2ecfd3225af8f93807b99daa9ff33710fc705ebdf6e8466f46ed605585c AS package_installer +FROM registry.access.redhat.com/ubi9/ubi:latest@sha256:16e9e43d99e181463513cc84a514859b1abb9460f1c4d9e208da4b4e51cd5cc3 AS package_installer # Copy ubi-micro base to /out/ to preserve its rpmdb COPY --from=ubi-micro-base / /out/ @@ -37,7 +37,7 @@ COPY --from=ubi-micro-base / /out/ # prefetched by Hermeto/Cachi2, instead of installroot's default UBI repos. RUN dnf install -y \ --installroot=/out/ \ - --releasever=8 \ + --releasever=9 \ --setopt=install_weak_deps=False \ --setopt=reposdir=/etc/yum.repos.d \ --nodocs \ diff --git a/image/templates/helm/shared/templates/99-scanner-v4-openshift-monitoring.yaml b/image/templates/helm/shared/templates/99-scanner-v4-openshift-monitoring.yaml index e45dbdb4eb0cd..8a68b67dd9759 100644 --- a/image/templates/helm/shared/templates/99-scanner-v4-openshift-monitoring.yaml +++ b/image/templates/helm/shared/templates/99-scanner-v4-openshift-monitoring.yaml @@ -62,4 +62,56 @@ subjects: name: scanner-v4 namespace: "{{ ._rox._namespace }}" {{- end }} +{{- /* +Scanner V4 DB PVC space alerting is evaluated by the platform Prometheus, which +scrapes the relevant metrics in every namespace. +The rule is emitted only when the DB is PVC-backed (not for hostPath or +emptyDir persistence). +*/}} +{{- if ._rox.scannerV4._dbEnabled }} +{{- $scannerV4DBClaimName := "" -}} +{{- with ._rox.scannerV4.db.persistence._volumeCfg -}} +{{- with .persistentVolumeClaim -}} +{{- $scannerV4DBClaimName = .claimName -}} +{{- end -}} +{{- end -}} +{{- if $scannerV4DBClaimName }} +--- +apiVersion: monitoring.coreos.com/v1 +kind: PrometheusRule +metadata: + name: "scanner-v4-db-{{ .Release.Namespace }}" + namespace: openshift-monitoring + labels: + {{- include "srox.labels" (list . "prometheusrule" (print "scanner-v4-db-" .Release.Namespace)) | nindent 4 }} + annotations: + {{- include "srox.annotations" (list . "prometheusrule" (print "scanner-v4-db-" .Release.Namespace)) | nindent 4 }} +spec: + groups: + - name: rhacs.scanner-v4-db + rules: + - alert: ScannerV4DBPersistentVolumeFillingUp + expr: | + kubelet_volume_stats_used_bytes{namespace="{{ ._rox._namespace }}", persistentvolumeclaim="{{ $scannerV4DBClaimName }}"} + / kubelet_volume_stats_capacity_bytes{namespace="{{ ._rox._namespace }}", persistentvolumeclaim="{{ $scannerV4DBClaimName }}"} + > 0.8 + for: 1h + labels: + severity: warning + annotations: + summary: "Scanner V4 DB persistent volume is filling up" + description: "The Scanner V4 DB PVC {{ $scannerV4DBClaimName }} in namespace {{ ._rox._namespace }} is {{ "{{" }} $value | humanizePercentage {{ "}}" }} full (warning threshold 80%)." + - alert: ScannerV4DBPersistentVolumeFillingUp + expr: | + kubelet_volume_stats_used_bytes{namespace="{{ ._rox._namespace }}", persistentvolumeclaim="{{ $scannerV4DBClaimName }}"} + / kubelet_volume_stats_capacity_bytes{namespace="{{ ._rox._namespace }}", persistentvolumeclaim="{{ $scannerV4DBClaimName }}"} + > 0.9 + for: 5m + labels: + severity: critical + annotations: + summary: "Scanner V4 DB persistent volume is almost full" + description: "The Scanner V4 DB PVC {{ $scannerV4DBClaimName }} in namespace {{ ._rox._namespace }} is {{ "{{" }} $value | humanizePercentage {{ "}}" }} full (critical threshold 90%)." +{{- end }} +{{- end }} {{- end }} diff --git a/image/templates/helm/shared/templates/_metadata.tpl b/image/templates/helm/shared/templates/_metadata.tpl index dc74c28239993..f77112634b9a2 100644 --- a/image/templates/helm/shared/templates/_metadata.tpl +++ b/image/templates/helm/shared/templates/_metadata.tpl @@ -229,23 +229,66 @@ {{ end }} {{ end }} -{{/* Add namespace specific prefixes for global resources to avoid resource name clashes for multi-namespace deployments. */}} +{{/* + Add namespace specific prefixes for global resources to avoid resource name clashes for + multi-namespace deployments. + + The separator between the "stackrox" base and the resource-specific suffix is + inferred from $name ("-" for "stackrox-foo", ":" for "stackrox:foo"). A name that is + exactly "stackrox" (i.e. just the base name, no suffix) is completely replaced with globalPrefix. + */}} {{- define "srox.globalResourceName" -}} {{- $ := index . 0 -}} {{- $name := index . 1 -}} +{{- $separator := "" -}} {{- if eq $.Release.Namespace "stackrox" -}} {{- /* Standard namespace, use resource name as is. */ -}} {{- $name -}} {{- else -}} - {{- /* Add global prefix to resource name. */ -}} + {{- /* Infer the separator from $name. */ -}} {{- if hasPrefix "stackrox-" $name -}} - {{- printf "%s-%s" $._rox.globalPrefix (trimPrefix "stackrox-" $name) -}} + {{- $separator = "-" -}} {{- else if hasPrefix "stackrox:" $name -}} - {{- printf "%s:%s" $._rox.globalPrefix (trimPrefix "stackrox:" $name) -}} + {{- $separator = ":" -}} + {{- else if eq "stackrox" $name -}} + {{/* Keep $separator empty. */}} {{- else -}} {{- include "srox.fail" (printf "Unknown naming convention for global resource %q." $name) -}} {{- end -}} + {{- /* Add global prefix to resource name. */ -}} + {{- $suffix := trimPrefix (printf "stackrox%s" $separator) $name -}} + {{- if not $suffix -}} + {{- /* Bare "stackrox" base name: the global prefix already carries the namespace. */ -}} + {{- printf "%s" $._rox.globalPrefix -}} + {{- else -}} + {{- printf "%s%s%s" $._rox.globalPrefix $separator $suffix -}} + {{- end -}} +{{- end -}} +{{- end -}} + +{{/* + srox.optionalGlobalResourceName $ $name + + Opt-in wrapper around srox.globalResourceName: it applies namespace-prefixing to a + global (cluster-scoped) resource name only when ._rox.namespacePrefixGlobalResources + is set; otherwise the static base $name is returned verbatim. The arguments are + forwarded unchanged to srox.globalResourceName. + + This keeps namespace-prefixing of global resources opt-in: names default to stable, + static values (production safety) and prefixing can be enabled -- e.g. for tests that + deploy multiple secured clusters into different namespaces of the same cluster -- to + avoid cluster-scoped resource name clashes. Individual global resources opt in by + calling this helper instead of hard-coding a static name. This is deliberately generic + so further global resources can adopt it as the need arises. + */}} +{{- define "srox.optionalGlobalResourceName" -}} +{{- $ := index . 0 -}} +{{- $name := index . 1 -}} +{{- if $._rox.namespacePrefixGlobalResources -}} + {{- include "srox.globalResourceName" . -}} +{{- else -}} + {{- $name -}} {{- end -}} {{- end -}} diff --git a/image/templates/helm/stackrox-central/internal/config-shape.yaml b/image/templates/helm/stackrox-central/internal/config-shape.yaml index d0d96cd6ccda9..96dc0fc23ae7c 100644 --- a/image/templates/helm/stackrox-central/internal/config-shape.yaml +++ b/image/templates/helm/stackrox-central/internal/config-shape.yaml @@ -125,7 +125,7 @@ central: none: null # bool extraMounts: null # [dict] centralWorker: - enabled: false # bool + enabled: true # bool nodeSelector: null # string | dict tolerations: null # [dict] hostAliases: null # [dict] diff --git a/image/templates/helm/stackrox-central/internal/config.stackrox.io_securitypolicies.yaml b/image/templates/helm/stackrox-central/internal/config.stackrox.io_securitypolicies.yaml index 61a83b1baa76d..d3a1254eabdb2 100644 --- a/image/templates/helm/stackrox-central/internal/config.stackrox.io_securitypolicies.yaml +++ b/image/templates/helm/stackrox-central/internal/config.stackrox.io_securitypolicies.yaml @@ -82,7 +82,11 @@ spec: evaluates. properties: skipContainerTypes: + description: SkipContainerTypes lists container kinds to skip + during evaluation. items: + description: ContainerType lists container kinds a policy may + skip during evaluation. enum: - REGULAR - INIT @@ -138,6 +142,23 @@ spec: type: object type: object type: object + excludeByKind: + description: ExcludeByKind excludes all workloads of the selected + Kubernetes kinds. + properties: + kinds: + description: Kinds is the list of Kubernetes workload kinds + to exclude. + items: + enum: + - CRON_JOB + - JOB + type: string + minItems: 1 + type: array + required: + - kinds + type: object expiration: format: date-time type: string @@ -149,6 +170,11 @@ spec: name: type: string type: object + x-kubernetes-validations: + - message: at most one of the fields in [deployment excludeByKind] + may be set + rule: (has(self.deployment)?1:0)+(has(self.excludeByKind)?1:0) + <= 1 type: array isDefault: description: IsDefault is unused diff --git a/image/templates/helm/stackrox-central/internal/defaults.yaml.htpl b/image/templates/helm/stackrox-central/internal/defaults.yaml.htpl index b008adbca4019..79b9ced9d35f6 100644 --- a/image/templates/helm/stackrox-central/internal/defaults.yaml.htpl +++ b/image/templates/helm/stackrox-central/internal/defaults.yaml.htpl @@ -92,6 +92,7 @@ defaults: memory: "16Gi" cpu: "8" centralWorker: + enabled: true affinity: nodeAffinity: preferredDuringSchedulingIgnoredDuringExecution: diff --git a/image/templates/helm/stackrox-central/templates/99-openshift-monitoring.yaml b/image/templates/helm/stackrox-central/templates/99-openshift-monitoring.yaml index a2a6f28ff73aa..de5e7d80d88d8 100644 --- a/image/templates/helm/stackrox-central/templates/99-openshift-monitoring.yaml +++ b/image/templates/helm/stackrox-central/templates/99-openshift-monitoring.yaml @@ -77,6 +77,42 @@ spec: matchNames: - "{{ .Release.Namespace }}" +{{- if and ._rox.monitoring.openshift.custom ._rox.monitoring.openshift.custom.enabled (not ._rox.env.managedServices) }} +--- + +{{- /* +The ServiceMonitor is placed into the openshift-monitoring namespace to comply with the +OpenShift monitoring namespace label requirements. This is an exception to the rule +that leaking resources into system namespaces should be avoided. +*/}} +apiVersion: monitoring.coreos.com/v1 +kind: ServiceMonitor +metadata: + name: "central-ocp-api-monitor-{{ .Release.Namespace }}" + namespace: openshift-monitoring + labels: + {{- include "srox.labels" (list . "servicemonitor" (print "central-ocp-api-monitor-" .Release.Namespace)) | nindent 4 }} + annotations: + {{- include "srox.annotations" (list . "servicemonitor" (print "central-ocp-api-monitor-" .Release.Namespace)) | nindent 4 }} +spec: + endpoints: + - interval: 30s + path: /metrics + port: https + scheme: https + tlsConfig: + caFile: /etc/prometheus/configmaps/serving-certs-ca-bundle/service-ca.crt + certFile: /etc/prometheus/secrets/metrics-client-certs/tls.crt + keyFile: /etc/prometheus/secrets/metrics-client-certs/tls.key + serverName: "central-ocp.{{ .Release.Namespace }}.svc" + selector: + matchLabels: + stackrox.io/ocp-api: "true" + namespaceSelector: + matchNames: + - "{{ .Release.Namespace }}" + +{{- end }} --- {{- /* @@ -145,5 +181,43 @@ spec: rox_central_secured_vcpus{branding="RHACS"} ) record: rhacs:telemetry:rox_central_secured_vcpus +{{- /* +Central DB PVC space alerting is evaluated by the platform Prometheus in +openshift-monitoring, which scrapes the relevant metrics in every namespace. +The rule is emitted only when central-db is PVC-backed (not for external, +hostPath, or emptyDir persistence). +*/}} +{{- $centralDBClaimName := "" -}} +{{- with ._rox.central.db.persistence._volumeCfg -}} +{{- with .persistentVolumeClaim -}} +{{- $centralDBClaimName = .claimName -}} +{{- end -}} +{{- end -}} +{{- if $centralDBClaimName }} + - name: rhacs.central-db + rules: + - alert: CentralDBPersistentVolumeFillingUp + expr: | + kubelet_volume_stats_used_bytes{namespace="{{ .Release.Namespace }}", persistentvolumeclaim="{{ $centralDBClaimName }}"} + / kubelet_volume_stats_capacity_bytes{namespace="{{ .Release.Namespace }}", persistentvolumeclaim="{{ $centralDBClaimName }}"} + > 0.8 + for: 1h + labels: + severity: warning + annotations: + summary: "Central DB persistent volume is filling up" + description: "The Central DB PVC {{ $centralDBClaimName }} in namespace {{ .Release.Namespace }} is {{ "{{" }} $value | humanizePercentage {{ "}}" }} full (warning threshold 80%)." + - alert: CentralDBPersistentVolumeFillingUp + expr: | + kubelet_volume_stats_used_bytes{namespace="{{ .Release.Namespace }}", persistentvolumeclaim="{{ $centralDBClaimName }}"} + / kubelet_volume_stats_capacity_bytes{namespace="{{ .Release.Namespace }}", persistentvolumeclaim="{{ $centralDBClaimName }}"} + > 0.9 + for: 5m + labels: + severity: critical + annotations: + summary: "Central DB persistent volume is almost full" + description: "The Central DB PVC {{ $centralDBClaimName }} in namespace {{ .Release.Namespace }} is {{ "{{" }} $value | humanizePercentage {{ "}}" }} full (critical threshold 90%)." +{{- end }} {{- end -}} diff --git a/image/templates/helm/stackrox-central/values-public.yaml.example.htpl b/image/templates/helm/stackrox-central/values-public.yaml.example.htpl index c4be9e07fe0f4..2fe94b736f17c 100644 --- a/image/templates/helm/stackrox-central/values-public.yaml.example.htpl +++ b/image/templates/helm/stackrox-central/values-public.yaml.example.htpl @@ -614,3 +614,6 @@ # # Enables integration with OpenShift platform monitoring. # openshift: # enabled: true +# # Enables custom metrics integration. +# custom: +# enabled: false diff --git a/image/templates/helm/stackrox-central/values.yaml.htpl b/image/templates/helm/stackrox-central/values.yaml.htpl index 10325b1af358a..5e67c78b7f884 100644 --- a/image/templates/helm/stackrox-central/values.yaml.htpl +++ b/image/templates/helm/stackrox-central/values.yaml.htpl @@ -495,3 +495,6 @@ central: # # Enables integration with OpenShift platform monitoring. # openshift: # enabled: true +# # Enables custom metrics integration. +# custom: +# enabled: false diff --git a/image/templates/helm/stackrox-secured-cluster/internal/config-shape.yaml b/image/templates/helm/stackrox-secured-cluster/internal/config-shape.yaml index c0c84066a2003..c19237777a1ce 100644 --- a/image/templates/helm/stackrox-secured-cluster/internal/config-shape.yaml +++ b/image/templates/helm/stackrox-secured-cluster/internal/config-shape.yaml @@ -182,6 +182,11 @@ customize: other: {} # dict allowNonstandardNamespace: null # bool allowNonstandardReleaseName: null # bool +# Undocumented, test-only: when true, global (cluster-scoped) resources that opt in via +# srox.optionalGlobalResourceName get namespace-prefixed names to avoid clashes when +# multiple secured clusters are deployed into different namespaces of one cluster. +# Defaults to false so production keeps stable, static names. +namespacePrefixGlobalResources: null # bool enableOpenShiftMonitoring: null # bool monitoring: openshift: diff --git a/image/templates/helm/stackrox-secured-cluster/internal/defaults/30-base-config.yaml.htpl b/image/templates/helm/stackrox-secured-cluster/internal/defaults/30-base-config.yaml.htpl index 02280536c4b4f..81335de9dd8cf 100644 --- a/image/templates/helm/stackrox-secured-cluster/internal/defaults/30-base-config.yaml.htpl +++ b/image/templates/helm/stackrox-secured-cluster/internal/defaults/30-base-config.yaml.htpl @@ -9,6 +9,10 @@ createSecrets: true exposeMonitoring: false +# Undocumented, test-only. Keep global resource names static by default for production +# safety; see config-shape.yaml and srox.optionalGlobalResourceName. +namespacePrefixGlobalResources: false + helmManaged: [< not .KubectlOutput >] [< if .Operator >] diff --git a/image/templates/helm/stackrox-secured-cluster/templates/admission-controller.yaml b/image/templates/helm/stackrox-secured-cluster/templates/admission-controller.yaml index 0f9fba8ea14ad..5863e9403d24f 100644 --- a/image/templates/helm/stackrox-secured-cluster/templates/admission-controller.yaml +++ b/image/templates/helm/stackrox-secured-cluster/templates/admission-controller.yaml @@ -218,7 +218,7 @@ spec: apiVersion: admissionregistration.k8s.io/v1 kind: ValidatingWebhookConfiguration metadata: - name: stackrox + name: {{ include "srox.optionalGlobalResourceName" (list . "stackrox") }} labels: {{- include "srox.labels" (list . "validatingwebhookconfiguration" "stackrox") | nindent 4 }} annotations: diff --git a/migrator/migrations/m_211_to_m_212_admission_control_config/migration_test.go b/migrator/migrations/m_211_to_m_212_admission_control_config/migration_test.go index 1f3d1e59d47de..7bfbd94cdfe51 100644 --- a/migrator/migrations/m_211_to_m_212_admission_control_config/migration_test.go +++ b/migrator/migrations/m_211_to_m_212_admission_control_config/migration_test.go @@ -63,17 +63,17 @@ func (s *migrationTestSuite) TestMigration() { search.NewQueryBuilder().AddExactMatches(search.Cluster, clusters[0].GetName()).ProtoQuery()) assert.NoError(s.T(), err) - assert.EqualValues(s.T(), result[0].HelmConfig.GetDynamicConfig().GetAdmissionControllerConfig().GetScanInline(), false) - assert.EqualValues(s.T(), result[0].HelmConfig.GetDynamicConfig().GetAdmissionControllerConfig().GetEnabled(), true) - assert.EqualValues(s.T(), result[0].HelmConfig.GetDynamicConfig().GetAdmissionControllerConfig().GetEnforceOnUpdates(), false) + assert.EqualValues(s.T(), result[0].GetHelmConfig().GetDynamicConfig().GetAdmissionControllerConfig().GetScanInline(), false) + assert.EqualValues(s.T(), result[0].GetHelmConfig().GetDynamicConfig().GetAdmissionControllerConfig().GetEnabled(), true) + assert.EqualValues(s.T(), result[0].GetHelmConfig().GetDynamicConfig().GetAdmissionControllerConfig().GetEnforceOnUpdates(), false) result, err = store.GetByQuery(s.ctx, search.NewQueryBuilder().AddExactMatches(search.Cluster, clusters[1].GetName()).ProtoQuery()) assert.NoError(s.T(), err) - assert.EqualValues(s.T(), result[0].DynamicConfig.GetAdmissionControllerConfig().GetScanInline(), true) - assert.EqualValues(s.T(), result[0].DynamicConfig.GetAdmissionControllerConfig().GetEnabled(), true) - assert.EqualValues(s.T(), result[0].DynamicConfig.GetAdmissionControllerConfig().GetEnforceOnUpdates(), true) + assert.EqualValues(s.T(), result[0].GetDynamicConfig().GetAdmissionControllerConfig().GetScanInline(), true) + assert.EqualValues(s.T(), result[0].GetDynamicConfig().GetAdmissionControllerConfig().GetEnabled(), true) + assert.EqualValues(s.T(), result[0].GetDynamicConfig().GetAdmissionControllerConfig().GetEnforceOnUpdates(), true) } diff --git a/migrator/migrations/policymigrationhelper/postgres_policy_migrator_test.go b/migrator/migrations/policymigrationhelper/postgres_policy_migrator_test.go index c6b0cf1c6bd6f..32f545dfaf0e7 100644 --- a/migrator/migrations/policymigrationhelper/postgres_policy_migrator_test.go +++ b/migrator/migrations/policymigrationhelper/postgres_policy_migrator_test.go @@ -177,12 +177,12 @@ func (s *postgresPolicyMigratorTestSuite) TestExclusionAreAddedAndRemovedAsNeces // Add a bunch of exclusions into the DB policy.Exclusions = []*storage.Exclusion{ - {Name: "exclusion0", Deployment: &storage.Exclusion_Deployment{Scope: &storage.Scope{Namespace: "namespace-0"}}}, - {Name: "exclusion1", Deployment: &storage.Exclusion_Deployment{Scope: &storage.Scope{Namespace: "namespace 1"}}}, - {Name: "exclusion0", Deployment: &storage.Exclusion_Deployment{Scope: &storage.Scope{Namespace: "namespace-0"}}}, - {Name: "exclusion2", Deployment: &storage.Exclusion_Deployment{Scope: &storage.Scope{Namespace: "namespace-2"}}}, - {Name: "exclusion3", Deployment: &storage.Exclusion_Deployment{Scope: &storage.Scope{Namespace: "namespace-3"}}}, - {Name: "exclusion4", Deployment: &storage.Exclusion_Deployment{Scope: &storage.Scope{Namespace: "namespace-4"}}}, + {Name: "exclusion0", Matcher: &storage.Exclusion_Deployment_{Deployment: &storage.Exclusion_Deployment{Scope: &storage.Scope{Namespace: "namespace-0"}}}}, + {Name: "exclusion1", Matcher: &storage.Exclusion_Deployment_{Deployment: &storage.Exclusion_Deployment{Scope: &storage.Scope{Namespace: "namespace 1"}}}}, + {Name: "exclusion0", Matcher: &storage.Exclusion_Deployment_{Deployment: &storage.Exclusion_Deployment{Scope: &storage.Scope{Namespace: "namespace-0"}}}}, + {Name: "exclusion2", Matcher: &storage.Exclusion_Deployment_{Deployment: &storage.Exclusion_Deployment{Scope: &storage.Scope{Namespace: "namespace-2"}}}}, + {Name: "exclusion3", Matcher: &storage.Exclusion_Deployment_{Deployment: &storage.Exclusion_Deployment{Scope: &storage.Scope{Namespace: "namespace-3"}}}}, + {Name: "exclusion4", Matcher: &storage.Exclusion_Deployment_{Deployment: &storage.Exclusion_Deployment{Scope: &storage.Scope{Namespace: "namespace-4"}}}}, } s.NoError(s.store.Upsert(s.ctx, policy)) @@ -196,14 +196,14 @@ func (s *postgresPolicyMigratorTestSuite) TestExclusionAreAddedAndRemovedAsNeces FieldsToCompare: []FieldComparator{ExclusionComparator}, ToChange: PolicyUpdates{ ExclusionsToAdd: []*storage.Exclusion{ - {Name: "exclusion1-changed", Deployment: &storage.Exclusion_Deployment{Scope: &storage.Scope{Namespace: "namespace-1"}}}, - {Name: "NEW exclusion", Deployment: &storage.Exclusion_Deployment{Scope: &storage.Scope{Namespace: "namespace-NEW"}}}, - {Name: "NEW exclusion2", Deployment: &storage.Exclusion_Deployment{Scope: &storage.Scope{Namespace: "namespace-NEW2"}}}, + {Name: "exclusion1-changed", Matcher: &storage.Exclusion_Deployment_{Deployment: &storage.Exclusion_Deployment{Scope: &storage.Scope{Namespace: "namespace-1"}}}}, + {Name: "NEW exclusion", Matcher: &storage.Exclusion_Deployment_{Deployment: &storage.Exclusion_Deployment{Scope: &storage.Scope{Namespace: "namespace-NEW"}}}}, + {Name: "NEW exclusion2", Matcher: &storage.Exclusion_Deployment_{Deployment: &storage.Exclusion_Deployment{Scope: &storage.Scope{Namespace: "namespace-NEW2"}}}}, }, ExclusionsToRemove: []*storage.Exclusion{ - {Name: "exclusion1", Deployment: &storage.Exclusion_Deployment{Scope: &storage.Scope{Namespace: "namespace 1"}}}, - {Name: "exclusion4", Deployment: &storage.Exclusion_Deployment{Scope: &storage.Scope{Namespace: "namespace-4"}}}, - {Name: "exclusion-NaN", Deployment: &storage.Exclusion_Deployment{Scope: &storage.Scope{Namespace: "namespace-NaN"}}}, // this exclusion doesn't exist so it shouldn't get removed + {Name: "exclusion1", Matcher: &storage.Exclusion_Deployment_{Deployment: &storage.Exclusion_Deployment{Scope: &storage.Scope{Namespace: "namespace 1"}}}}, + {Name: "exclusion4", Matcher: &storage.Exclusion_Deployment_{Deployment: &storage.Exclusion_Deployment{Scope: &storage.Scope{Namespace: "namespace-4"}}}}, + {Name: "exclusion-NaN", Matcher: &storage.Exclusion_Deployment_{Deployment: &storage.Exclusion_Deployment{Scope: &storage.Scope{Namespace: "namespace-NaN"}}}}, // this exclusion doesn't exist so it shouldn't get removed }, }, }, @@ -221,13 +221,13 @@ func (s *postgresPolicyMigratorTestSuite) TestExclusionAreAddedAndRemovedAsNeces // Policy exclusions should be updated policy.Exclusions = []*storage.Exclusion{ - {Name: "exclusion0", Deployment: &storage.Exclusion_Deployment{Scope: &storage.Scope{Namespace: "namespace-0"}}}, - {Name: "exclusion0", Deployment: &storage.Exclusion_Deployment{Scope: &storage.Scope{Namespace: "namespace-0"}}}, - {Name: "exclusion2", Deployment: &storage.Exclusion_Deployment{Scope: &storage.Scope{Namespace: "namespace-2"}}}, - {Name: "exclusion3", Deployment: &storage.Exclusion_Deployment{Scope: &storage.Scope{Namespace: "namespace-3"}}}, - {Name: "exclusion1-changed", Deployment: &storage.Exclusion_Deployment{Scope: &storage.Scope{Namespace: "namespace-1"}}}, - {Name: "NEW exclusion", Deployment: &storage.Exclusion_Deployment{Scope: &storage.Scope{Namespace: "namespace-NEW"}}}, - {Name: "NEW exclusion2", Deployment: &storage.Exclusion_Deployment{Scope: &storage.Scope{Namespace: "namespace-NEW2"}}}, + {Name: "exclusion0", Matcher: &storage.Exclusion_Deployment_{Deployment: &storage.Exclusion_Deployment{Scope: &storage.Scope{Namespace: "namespace-0"}}}}, + {Name: "exclusion0", Matcher: &storage.Exclusion_Deployment_{Deployment: &storage.Exclusion_Deployment{Scope: &storage.Scope{Namespace: "namespace-0"}}}}, + {Name: "exclusion2", Matcher: &storage.Exclusion_Deployment_{Deployment: &storage.Exclusion_Deployment{Scope: &storage.Scope{Namespace: "namespace-2"}}}}, + {Name: "exclusion3", Matcher: &storage.Exclusion_Deployment_{Deployment: &storage.Exclusion_Deployment{Scope: &storage.Scope{Namespace: "namespace-3"}}}}, + {Name: "exclusion1-changed", Matcher: &storage.Exclusion_Deployment_{Deployment: &storage.Exclusion_Deployment{Scope: &storage.Scope{Namespace: "namespace-1"}}}}, + {Name: "NEW exclusion", Matcher: &storage.Exclusion_Deployment_{Deployment: &storage.Exclusion_Deployment{Scope: &storage.Scope{Namespace: "namespace-NEW"}}}}, + {Name: "NEW exclusion2", Matcher: &storage.Exclusion_Deployment_{Deployment: &storage.Exclusion_Deployment{Scope: &storage.Scope{Namespace: "namespace-NEW2"}}}}, } s.comparePolicyWithDB(policyID, policy) @@ -248,7 +248,7 @@ func testPolicy(id string) *storage.Policy { }, }, Exclusions: []*storage.Exclusion{ - {Name: "exclusion name", Deployment: &storage.Exclusion_Deployment{Scope: &storage.Scope{Namespace: "namespace name"}}}, + {Name: "exclusion name", Matcher: &storage.Exclusion_Deployment_{Deployment: &storage.Exclusion_Deployment{Scope: &storage.Scope{Namespace: "namespace name"}}}}, }, } } diff --git a/operator/Makefile b/operator/Makefile index 496b3271ef53d..1e13f7b7cc243 100755 --- a/operator/Makefile +++ b/operator/Makefile @@ -303,7 +303,7 @@ stackrox-image-pull-secret: ## Create default image pull secret for StackRox ima # Create stackrox namespace if not exists. echo '{ "apiVersion": "v1", "kind": "Namespace", "metadata": { "name": "$(NAMESPACE)" } }' | $(PROJECT_DIR)/hack/retry-kubectl.sh apply -f - # Create stackrox image pull secret in stackrox namespace. - $(PROJECT_DIR)/../deploy/common/pull-secret.sh stackrox quay.io | $(PROJECT_DIR)/hack/retry-kubectl.sh -n "$(NAMESPACE)" apply -f - + $(PROJECT_DIR)/../scripts/pull-secret.sh stackrox quay.io | $(PROJECT_DIR)/hack/retry-kubectl.sh -n "$(NAMESPACE)" apply -f - .PHONY: check-ci-setup check-ci-setup: ## Make sure this target is not started in CI environment. diff --git a/operator/api/v1alpha1/central_types.go b/operator/api/v1alpha1/central_types.go index a33c3769d82f3..610b453f0bb94 100644 --- a/operator/api/v1alpha1/central_types.go +++ b/operator/api/v1alpha1/central_types.go @@ -38,7 +38,7 @@ type CentralSpec struct { //+operator-sdk:csv:customresourcedefinitions:type=spec,xDescriptors={"urn:alm:descriptor:com.tectonic.ui:hidden"} Scanner *ScannerComponentSpec `json:"scanner,omitempty"` - // Settings for the Scanner V4 component, which can run in addition to the previously existing Scanner components + // Settings for the Scanner V4 component // TODO(ROX-36705): renumber order annotations after legacy Scanner field removal //+operator-sdk:csv:customresourcedefinitions:type=spec,order=3,displayName="Scanner V4 Component Settings" ScannerV4 *ScannerV4Spec `json:"scannerV4,omitempty"` @@ -643,7 +643,7 @@ const ( // CentralWorkerSpec defines the central-worker configuration. type CentralWorkerSpec struct { // If set to true, pruning and reporting jobs run in a separate Deployment. - // The default is: false. + // The default is: true. //+operator-sdk:csv:customresourcedefinitions:type=spec,order=1,displayName="Enabled" Enabled *bool `json:"enabled,omitempty"` diff --git a/operator/api/v1alpha1/securedcluster_types.go b/operator/api/v1alpha1/securedcluster_types.go index 922d58f0dadda..5dd4fce7fc6b4 100644 --- a/operator/api/v1alpha1/securedcluster_types.go +++ b/operator/api/v1alpha1/securedcluster_types.go @@ -74,7 +74,7 @@ type SecuredClusterSpec struct { //+operator-sdk:csv:customresourcedefinitions:type=spec,xDescriptors={"urn:alm:descriptor:com.tectonic.ui:hidden"} Scanner *LocalScannerComponentSpec `json:"scanner,omitempty"` - // Settings for the Scanner V4 components, which can run in addition to the previously existing Scanner components + // Settings for the Scanner V4 components // TODO(ROX-36705): renumber order annotations after legacy Scanner field removal //+operator-sdk:csv:customresourcedefinitions:type=spec,order=9,displayName="Scanner V4 Component Settings" ScannerV4 *LocalScannerV4ComponentSpec `json:"scannerV4,omitempty"` diff --git a/operator/bundle/manifests/config.stackrox.io_securitypolicies.yaml b/operator/bundle/manifests/config.stackrox.io_securitypolicies.yaml index 3b94d5f785b32..dadcf9abe6338 100644 --- a/operator/bundle/manifests/config.stackrox.io_securitypolicies.yaml +++ b/operator/bundle/manifests/config.stackrox.io_securitypolicies.yaml @@ -83,7 +83,11 @@ spec: evaluates. properties: skipContainerTypes: + description: SkipContainerTypes lists container kinds to skip + during evaluation. items: + description: ContainerType lists container kinds a policy may + skip during evaluation. enum: - REGULAR - INIT @@ -139,6 +143,23 @@ spec: type: object type: object type: object + excludeByKind: + description: ExcludeByKind excludes all workloads of the selected + Kubernetes kinds. + properties: + kinds: + description: Kinds is the list of Kubernetes workload kinds + to exclude. + items: + enum: + - CRON_JOB + - JOB + type: string + minItems: 1 + type: array + required: + - kinds + type: object expiration: format: date-time type: string @@ -150,6 +171,11 @@ spec: name: type: string type: object + x-kubernetes-validations: + - message: at most one of the fields in [deployment excludeByKind] + may be set + rule: (has(self.deployment)?1:0)+(has(self.excludeByKind)?1:0) + <= 1 type: array isDefault: description: IsDefault is unused diff --git a/operator/bundle/manifests/platform.stackrox.io_centrals.yaml b/operator/bundle/manifests/platform.stackrox.io_centrals.yaml index 5789d721f9c43..3474ec526ae09 100644 --- a/operator/bundle/manifests/platform.stackrox.io_centrals.yaml +++ b/operator/bundle/manifests/platform.stackrox.io_centrals.yaml @@ -1608,7 +1608,7 @@ spec: enabled: description: |- If set to true, pruning and reporting jobs run in a separate Deployment. - The default is: false. + The default is: true. type: boolean hostAliases: description: HostAliases allows configuring additional hostnames @@ -1998,7 +1998,9 @@ spec: description: Selects a key of a ConfigMap. properties: key: - description: The key to select. + description: |- + The key to select from the ConfigMap's Data field. + Keys in the BinaryData field are not currently propagated to container env vars. type: string name: default: "" @@ -2602,8 +2604,7 @@ spec: type: string type: object scannerV4: - description: Settings for the Scanner V4 component, which can run - in addition to the previously existing Scanner components + description: Settings for the Scanner V4 component properties: db: description: Settings pertaining to the DB deployment. diff --git a/operator/bundle/manifests/platform.stackrox.io_securedclusters.yaml b/operator/bundle/manifests/platform.stackrox.io_securedclusters.yaml index 91434ef91868e..9c168a70ee06e 100644 --- a/operator/bundle/manifests/platform.stackrox.io_securedclusters.yaml +++ b/operator/bundle/manifests/platform.stackrox.io_securedclusters.yaml @@ -401,7 +401,9 @@ spec: description: Selects a key of a ConfigMap. properties: key: - description: The key to select. + description: |- + The key to select from the ConfigMap's Data field. + Keys in the BinaryData field are not currently propagated to container env vars. type: string name: default: "" @@ -1347,8 +1349,7 @@ spec: type: string type: object scannerV4: - description: Settings for the Scanner V4 components, which can run - in addition to the previously existing Scanner components + description: Settings for the Scanner V4 components properties: db: description: Settings pertaining to the DB deployment. diff --git a/operator/bundle/manifests/rhacs-operator.clusterserviceversion.yaml b/operator/bundle/manifests/rhacs-operator.clusterserviceversion.yaml index fd2bb21690add..daada4042163d 100644 --- a/operator/bundle/manifests/rhacs-operator.clusterserviceversion.yaml +++ b/operator/bundle/manifests/rhacs-operator.clusterserviceversion.yaml @@ -84,7 +84,7 @@ spec: - description: Settings for the Central component, which is responsible for all user interaction. displayName: Central Component Settings path: central - - description: Settings for the Scanner V4 component, which can run in addition to the previously existing Scanner components + - description: Settings for the Scanner V4 component displayName: Scanner V4 Component Settings path: scannerV4 - description: Settings related to outgoing network traffic. @@ -516,7 +516,7 @@ spec: path: central.telemetry.storage.key - description: |- If set to true, pruning and reporting jobs run in a separate Deployment. - The default is: false. + The default is: true. displayName: Enabled path: centralWorker.enabled - description: |- @@ -1034,7 +1034,7 @@ spec: - description: Settings relating to process baselines. displayName: Process Baselines Settings path: processBaselines - - description: Settings for the Scanner V4 components, which can run in addition to the previously existing Scanner components + - description: Settings for the Scanner V4 components displayName: Scanner V4 Component Settings path: scannerV4 - description: Settings related to Transport Layer Security, such as Certificate Authorities. @@ -1692,7 +1692,7 @@ spec: 2. **Secured Cluster Services** - Secured cluster services are placed on each cluster you manage and report back to Central. These services allow users to enforce policies and monitor your OpenShift and Kubernetes clusters. Secured Cluster Services come as two Deployments (Sensor and Admission Controller) and one DaemonSet (Collector). - **Console Plugin (Tech Preview):** RHACS provides a dynamic plugin that displays vulnerability management information in the OpenShift web console. Install a SecuredCluster to deploy the plugin. Enable the plugin by selecting *Operators* > *Installed Operators* or by modifying the console Operator configuration. + **Console Plugin:** RHACS provides a dynamic plugin that displays vulnerability management information in the OpenShift web console. Install a SecuredCluster to deploy the plugin. Enable the plugin by selecting *Operators* > *Installed Operators* or by modifying the console Operator configuration. **Important:** The Console Plugin requires OpenShift 4.19 or later. ### Central Services Explained @@ -1701,8 +1701,10 @@ spec: | :------------------------------- | :-------------- | :-------------- | | Central | Deployment | Users interact with Red Hat Advanced Cluster Security through the user interface or APIs on Central. Central also sends notifications for violations and interacts with integrations. | | Central DB | Deployment | Central DB is a PostgreSQL-based persistent storage for the data collected and managed by Central. | - | Scanner | Deployment | Scanner is a Red Hat developed and certified image scanner. Scanner analyzes and reports vulnerabilities for images. Scanner uses HPA to scale the number of replicas based on workload. | - | Scanner DB | Deployment | Scanner DB is a cache for vulnerability definitions to serve vulnerability scanning use cases throughout the software development life cycle. | + | Scanner V4 Indexer | Deployment | Scanner V4 Indexer analyzes container image layers and creates an index of packages and files. Scanner V4 Indexer uses Horizontal Pod Autoscaler (HPA) to scale the number of replicas based on workload. | + | Scanner V4 Matcher | Deployment | Scanner V4 Matcher matches indexed image content in vulnerability databases to identify security issues. Scanner V4 Matcher uses HPA to scale the number of replicas based on workload. | + | Scanner V4 DB | Deployment | Scanner V4 DB is a PostgreSQL-based persistent storage for indexed image data and vulnerability definitions. | + | Config Controller | Deployment | Config Controller watches SecurityPolicy custom resources and reconciles them with Central to manage security policies declaratively. | ### Secured Cluster Services Explained @@ -1711,6 +1713,8 @@ spec: | Sensor | Deployment | Sensor analyzes and monitors Kubernetes in secured clusters. | | Collector | DaemonSet | Analyzes and monitors container activity on Kubernetes nodes.| | Admission Controller | Deployment | ValidatingWebhookConfiguration for enforcing policies in the deploy lifecycle. | + | Scanner V4 Indexer | Deployment | Scanner V4 Indexer analyzes container image layers and creates an index of packages and files. Scanner V4 Indexer uses HPA to scale the number of replicas based on workload. | + | Scanner V4 DB | Deployment | Scanner V4 DB is a PostgreSQL-based persistent storage for indexed image data. | ### Central Custom Resource @@ -1927,7 +1931,7 @@ spec: - risk links: - name: Red Hat Advanced Cluster Security Documentation - url: https://docs.openshift.com/acs/welcome/ + url: https://docs.redhat.com/en/documentation/red_hat_advanced_cluster_security_for_kubernetes - name: DataSheet url: https://www.redhat.com/en/resources/advanced-cluster-security-for-kubernetes-datasheet - name: Support Policy diff --git a/operator/config/crd/bases/platform.stackrox.io_centrals.yaml b/operator/config/crd/bases/platform.stackrox.io_centrals.yaml index 6134c58800ff5..57482b2ae2c29 100644 --- a/operator/config/crd/bases/platform.stackrox.io_centrals.yaml +++ b/operator/config/crd/bases/platform.stackrox.io_centrals.yaml @@ -1607,7 +1607,7 @@ spec: enabled: description: |- If set to true, pruning and reporting jobs run in a separate Deployment. - The default is: false. + The default is: true. type: boolean hostAliases: description: HostAliases allows configuring additional hostnames @@ -1997,7 +1997,9 @@ spec: description: Selects a key of a ConfigMap. properties: key: - description: The key to select. + description: |- + The key to select from the ConfigMap's Data field. + Keys in the BinaryData field are not currently propagated to container env vars. type: string name: default: "" @@ -2601,8 +2603,7 @@ spec: type: string type: object scannerV4: - description: Settings for the Scanner V4 component, which can run - in addition to the previously existing Scanner components + description: Settings for the Scanner V4 component properties: db: description: Settings pertaining to the DB deployment. diff --git a/operator/config/crd/bases/platform.stackrox.io_securedclusters.yaml b/operator/config/crd/bases/platform.stackrox.io_securedclusters.yaml index 30e63d97f9a99..6b2b3c02d80d3 100644 --- a/operator/config/crd/bases/platform.stackrox.io_securedclusters.yaml +++ b/operator/config/crd/bases/platform.stackrox.io_securedclusters.yaml @@ -400,7 +400,9 @@ spec: description: Selects a key of a ConfigMap. properties: key: - description: The key to select. + description: |- + The key to select from the ConfigMap's Data field. + Keys in the BinaryData field are not currently propagated to container env vars. type: string name: default: "" @@ -1346,8 +1348,7 @@ spec: type: string type: object scannerV4: - description: Settings for the Scanner V4 components, which can run - in addition to the previously existing Scanner components + description: Settings for the Scanner V4 components properties: db: description: Settings pertaining to the DB deployment. diff --git a/operator/config/manifests/bases/rhacs-operator.clusterserviceversion.yaml b/operator/config/manifests/bases/rhacs-operator.clusterserviceversion.yaml index ec980c0ddc385..54cbf3652418e 100644 --- a/operator/config/manifests/bases/rhacs-operator.clusterserviceversion.yaml +++ b/operator/config/manifests/bases/rhacs-operator.clusterserviceversion.yaml @@ -126,7 +126,7 @@ spec: path: central.telemetry.storage.endpoint - description: |- If set to true, pruning and reporting jobs run in a separate Deployment. - The default is: false. + The default is: true. displayName: Enabled path: centralWorker.enabled - description: |- @@ -438,8 +438,7 @@ spec: path: scanner.db x-descriptors: - urn:alm:descriptor:com.tectonic.ui:fieldDependency:.scannerComponent:Enabled - - description: Settings for the Scanner V4 component, which can run in addition - to the previously existing Scanner components + - description: Settings for the Scanner V4 component displayName: Scanner V4 Component Settings path: scannerV4 - description: |- @@ -1377,8 +1376,7 @@ spec: - description: Settings relating to process baselines. displayName: Process Baselines Settings path: processBaselines - - description: Settings for the Scanner V4 components, which can run in addition - to the previously existing Scanner components + - description: Settings for the Scanner V4 components displayName: Scanner V4 Component Settings path: scannerV4 - description: Settings related to Transport Layer Security, such as Certificate @@ -1698,7 +1696,7 @@ spec: 2. **Secured Cluster Services** - Secured cluster services are placed on each cluster you manage and report back to Central. These services allow users to enforce policies and monitor your OpenShift and Kubernetes clusters. Secured Cluster Services come as two Deployments (Sensor and Admission Controller) and one DaemonSet (Collector). - **Console Plugin (Tech Preview):** RHACS provides a dynamic plugin that displays vulnerability management information in the OpenShift web console. Install a SecuredCluster to deploy the plugin. Enable the plugin by selecting *Operators* > *Installed Operators* or by modifying the console Operator configuration. + **Console Plugin:** RHACS provides a dynamic plugin that displays vulnerability management information in the OpenShift web console. Install a SecuredCluster to deploy the plugin. Enable the plugin by selecting *Operators* > *Installed Operators* or by modifying the console Operator configuration. **Important:** The Console Plugin requires OpenShift 4.19 or later. ### Central Services Explained @@ -1707,8 +1705,10 @@ spec: | :------------------------------- | :-------------- | :-------------- | | Central | Deployment | Users interact with Red Hat Advanced Cluster Security through the user interface or APIs on Central. Central also sends notifications for violations and interacts with integrations. | | Central DB | Deployment | Central DB is a PostgreSQL-based persistent storage for the data collected and managed by Central. | - | Scanner | Deployment | Scanner is a Red Hat developed and certified image scanner. Scanner analyzes and reports vulnerabilities for images. Scanner uses HPA to scale the number of replicas based on workload. | - | Scanner DB | Deployment | Scanner DB is a cache for vulnerability definitions to serve vulnerability scanning use cases throughout the software development life cycle. | + | Scanner V4 Indexer | Deployment | Scanner V4 Indexer analyzes container image layers and creates an index of packages and files. Scanner V4 Indexer uses Horizontal Pod Autoscaler (HPA) to scale the number of replicas based on workload. | + | Scanner V4 Matcher | Deployment | Scanner V4 Matcher matches indexed image content in vulnerability databases to identify security issues. Scanner V4 Matcher uses HPA to scale the number of replicas based on workload. | + | Scanner V4 DB | Deployment | Scanner V4 DB is a PostgreSQL-based persistent storage for indexed image data and vulnerability definitions. | + | Config Controller | Deployment | Config Controller watches SecurityPolicy custom resources and reconciles them with Central to manage security policies declaratively. | ### Secured Cluster Services Explained @@ -1717,6 +1717,8 @@ spec: | Sensor | Deployment | Sensor analyzes and monitors Kubernetes in secured clusters. | | Collector | DaemonSet | Analyzes and monitors container activity on Kubernetes nodes.| | Admission Controller | Deployment | ValidatingWebhookConfiguration for enforcing policies in the deploy lifecycle. | + | Scanner V4 Indexer | Deployment | Scanner V4 Indexer analyzes container image layers and creates an index of packages and files. Scanner V4 Indexer uses HPA to scale the number of replicas based on workload. | + | Scanner V4 DB | Deployment | Scanner V4 DB is a PostgreSQL-based persistent storage for indexed image data. | ### Central Custom Resource @@ -1776,7 +1778,7 @@ spec: - risk links: - name: Red Hat Advanced Cluster Security Documentation - url: https://docs.openshift.com/acs/welcome/ + url: https://docs.redhat.com/en/documentation/red_hat_advanced_cluster_security_for_kubernetes - name: DataSheet url: https://www.redhat.com/en/resources/advanced-cluster-security-for-kubernetes-datasheet - name: Support Policy diff --git a/operator/config/ui-metadata/bases/rhacs-operator.clusterserviceversion.yaml b/operator/config/ui-metadata/bases/rhacs-operator.clusterserviceversion.yaml index e14968528e1ee..cdd078d494257 100644 --- a/operator/config/ui-metadata/bases/rhacs-operator.clusterserviceversion.yaml +++ b/operator/config/ui-metadata/bases/rhacs-operator.clusterserviceversion.yaml @@ -65,7 +65,7 @@ spec: 2. **Secured Cluster Services** - Secured cluster services are placed on each cluster you manage and report back to Central. These services allow users to enforce policies and monitor your OpenShift and Kubernetes clusters. Secured Cluster Services come as two Deployments (Sensor and Admission Controller) and one DaemonSet (Collector). - **Console Plugin (Tech Preview):** RHACS provides a dynamic plugin that displays vulnerability management information in the OpenShift web console. Install a SecuredCluster to deploy the plugin. Enable the plugin by selecting *Operators* > *Installed Operators* or by modifying the console Operator configuration. + **Console Plugin:** RHACS provides a dynamic plugin that displays vulnerability management information in the OpenShift web console. Install a SecuredCluster to deploy the plugin. Enable the plugin by selecting *Operators* > *Installed Operators* or by modifying the console Operator configuration. **Important:** The Console Plugin requires OpenShift 4.19 or later. ### Central Services Explained @@ -74,8 +74,10 @@ spec: | :------------------------------- | :-------------- | :-------------- | | Central | Deployment | Users interact with Red Hat Advanced Cluster Security through the user interface or APIs on Central. Central also sends notifications for violations and interacts with integrations. | | Central DB | Deployment | Central DB is a PostgreSQL-based persistent storage for the data collected and managed by Central. | - | Scanner | Deployment | Scanner is a Red Hat developed and certified image scanner. Scanner analyzes and reports vulnerabilities for images. Scanner uses HPA to scale the number of replicas based on workload. | - | Scanner DB | Deployment | Scanner DB is a cache for vulnerability definitions to serve vulnerability scanning use cases throughout the software development life cycle. | + | Scanner V4 Indexer | Deployment | Scanner V4 Indexer analyzes container image layers and creates an index of packages and files. Scanner V4 Indexer uses Horizontal Pod Autoscaler (HPA) to scale the number of replicas based on workload. | + | Scanner V4 Matcher | Deployment | Scanner V4 Matcher matches indexed image content in vulnerability databases to identify security issues. Scanner V4 Matcher uses HPA to scale the number of replicas based on workload. | + | Scanner V4 DB | Deployment | Scanner V4 DB is a PostgreSQL-based persistent storage for indexed image data and vulnerability definitions. | + | Config Controller | Deployment | Config Controller watches SecurityPolicy custom resources and reconciles them with Central to manage security policies declaratively. | ### Secured Cluster Services Explained @@ -84,6 +86,8 @@ spec: | Sensor | Deployment | Sensor analyzes and monitors Kubernetes in secured clusters. | | Collector | DaemonSet | Analyzes and monitors container activity on Kubernetes nodes.| | Admission Controller | Deployment | ValidatingWebhookConfiguration for enforcing policies in the deploy lifecycle. | + | Scanner V4 Indexer | Deployment | Scanner V4 Indexer analyzes container image layers and creates an index of packages and files. Scanner V4 Indexer uses HPA to scale the number of replicas based on workload. | + | Scanner V4 DB | Deployment | Scanner V4 DB is a PostgreSQL-based persistent storage for indexed image data. | ### Central Custom Resource @@ -139,7 +143,7 @@ spec: - risk links: - name: Red Hat Advanced Cluster Security Documentation - url: https://docs.openshift.com/acs/welcome/ + url: https://docs.redhat.com/en/documentation/red_hat_advanced_cluster_security_for_kubernetes - name: DataSheet url: https://www.redhat.com/en/resources/advanced-cluster-security-for-kubernetes-datasheet - name: Support Policy diff --git a/operator/internal/central/defaults/static.go b/operator/internal/central/defaults/static.go index 2617c971296b4..3bb381ca7e2a9 100644 --- a/operator/internal/central/defaults/static.go +++ b/operator/internal/central/defaults/static.go @@ -79,7 +79,7 @@ var staticDefaults = platform.CentralSpec{ Policies: new(platform.NetworkPoliciesEnabled), }, CentralWorker: &platform.CentralWorkerSpec{ - Enabled: new(false), + Enabled: new(true), }, ConfigAsCode: &platform.ConfigAsCodeSpec{ ComponentPolicy: new(platform.ConfigAsCodeComponentEnabled), diff --git a/operator/internal/securedcluster/values/translation/translation.go b/operator/internal/securedcluster/values/translation/translation.go index 75a9cd11b3c53..f5a99086a15a4 100644 --- a/operator/internal/securedcluster/values/translation/translation.go +++ b/operator/internal/securedcluster/values/translation/translation.go @@ -40,6 +40,14 @@ const ( legacyCollectionKernelModule = "KernelModule" legacyCollectionEBPF = "EBPF" + + // namespacePrefixGlobalResourcesAnnotation is an undocumented, test-only annotation. + // When set to "true" it enables namespace-prefixing of global (cluster-scoped) + // resource names in the secured-cluster chart (see srox.optionalGlobalResourceName), + // so that multiple secured clusters can be deployed into different namespaces of the + // same cluster without cluster-scoped resource name clashes. It defaults to off, so + // production keeps stable, static names. Deliberately kept out of the CRD schema. + namespacePrefixGlobalResourcesAnnotation = "platform.stackrox.io/namespace-prefix-global-resources" ) var ( @@ -107,6 +115,17 @@ func (t Translator) translate(ctx context.Context, sc platform.SecuredCluster) ( } v.SetStringMap("clusterLabels", sc.Spec.ClusterLabels) + if annotationValue, ok := sc.GetAnnotations()[namespacePrefixGlobalResourcesAnnotation]; ok { + namespacePrefix, err := strconv.ParseBool(annotationValue) + if err != nil { + return nil, fmt.Errorf("invalid annotation value %q for annotation %s", + annotationValue, namespacePrefixGlobalResourcesAnnotation) + } + if namespacePrefix { + v.SetBoolValue("namespacePrefixGlobalResources", true) + } + } + if sc.Spec.CentralEndpoint != nil && *sc.Spec.CentralEndpoint != "" { v.SetStringValue("centralEndpoint", *sc.Spec.CentralEndpoint) } diff --git a/operator/internal/securedcluster/values/translation/translation_test.go b/operator/internal/securedcluster/values/translation/translation_test.go index f047f65085cbf..b7e8246fc1b60 100644 --- a/operator/internal/securedcluster/values/translation/translation_test.go +++ b/operator/internal/securedcluster/values/translation/translation_test.go @@ -257,6 +257,31 @@ func (s *TranslationTestSuite) TestTranslate() { }, }, }, + "namespace-prefix-global-resources annotation enabled": { + args: args{ + client: newDefaultFakeClient(t), + sc: platform.SecuredCluster{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "stackrox", + Annotations: map[string]string{namespacePrefixGlobalResourcesAnnotation: "true"}, + }, + Spec: platform.SecuredClusterSpec{ + ClusterName: new("test-cluster"), + }, + }, + }, + want: chartutil.Values{ + "clusterName": "test-cluster", + "ca": map[string]string{"cert": "ca central content"}, + "createSecrets": false, + "namespacePrefixGlobalResources": true, + "monitoring": map[string]interface{}{ + "openshift": map[string]interface{}{ + "enabled": true, + }, + }, + }, + }, "local scanner autosense suppression": { args: args{ client: newDefaultFakeClientWithCentral(t), diff --git a/operator/konflux.Dockerfile b/operator/konflux.Dockerfile index 835b924e191d7..a0e2da48c3cb4 100644 --- a/operator/konflux.Dockerfile +++ b/operator/konflux.Dockerfile @@ -17,10 +17,10 @@ ENV CI=1 GOFLAGS="" CGO_ENABLED=1 RUN GOOS=linux GOARCH=$(go env GOARCH) scripts/go-build-file.sh operator/cmd/main.go image/bin/operator -FROM registry.access.redhat.com/ubi9/ubi-micro:latest@sha256:7a0454cbd9bd847e8f6a63b6f0254a6efbeb6e0ed71a5d824a4f6cccbe626650 AS ubi-micro-base +FROM registry.access.redhat.com/ubi9/ubi-micro:latest@sha256:5be877a2a280ae36eb9ab80ca4efb28bbf77c57abbba38137cdc37a03b8465e6 AS ubi-micro-base -FROM registry.access.redhat.com/ubi9/ubi:latest@sha256:094ea2ecfd3225af8f93807b99daa9ff33710fc705ebdf6e8466f46ed605585c AS package_installer +FROM registry.access.redhat.com/ubi9/ubi:latest@sha256:16e9e43d99e181463513cc84a514859b1abb9460f1c4d9e208da4b4e51cd5cc3 AS package_installer # Copy ubi-micro base to /out/ to preserve its rpmdb COPY --from=ubi-micro-base / /out/ @@ -30,7 +30,7 @@ COPY --from=ubi-micro-base / /out/ # prefetched by Hermeto/Cachi2, instead of installroot's default UBI repos. RUN dnf install -y \ --installroot=/out/ \ - --releasever=8 \ + --releasever=9 \ --setopt=install_weak_deps=False \ --setopt=reposdir=/etc/yum.repos.d \ --nodocs \ diff --git a/operator/tests/central/central-misc/080-assert-non-openshift.yaml b/operator/tests/central/central-misc/080-assert-non-openshift.yaml index c14a8f2a726c9..2f0a7e5508697 100644 --- a/operator/tests/central/central-misc/080-assert-non-openshift.yaml +++ b/operator/tests/central/central-misc/080-assert-non-openshift.yaml @@ -51,6 +51,8 @@ spec: value: "true" - name: ROX_LEGACY_SCANNER value: "false" + - name: ROX_CENTRAL_WORKER_ENABLED + value: "true" - name: NO_PROXY valueFrom: secretKeyRef: diff --git a/operator/tests/central/central-misc/080-assert-openshift.yaml b/operator/tests/central/central-misc/080-assert-openshift.yaml index cb70e23734560..ae04ed59c1774 100644 --- a/operator/tests/central/central-misc/080-assert-openshift.yaml +++ b/operator/tests/central/central-misc/080-assert-openshift.yaml @@ -55,6 +55,8 @@ spec: value: "true" - name: ROX_LEGACY_SCANNER value: "false" + - name: ROX_CENTRAL_WORKER_ENABLED + value: "true" - name: NO_PROXY valueFrom: secretKeyRef: diff --git a/operator/tests/central/central-misc/081-assert-non-openshift.yaml b/operator/tests/central/central-misc/081-assert-non-openshift.yaml index 3e1dd62db2461..c65e401e52019 100644 --- a/operator/tests/central/central-misc/081-assert-non-openshift.yaml +++ b/operator/tests/central/central-misc/081-assert-non-openshift.yaml @@ -47,6 +47,8 @@ spec: value: "true" - name: ROX_LEGACY_SCANNER value: "false" + - name: ROX_CENTRAL_WORKER_ENABLED + value: "true" - name: NO_PROXY valueFrom: secretKeyRef: diff --git a/operator/tests/central/central-misc/081-assert-openshift.yaml b/operator/tests/central/central-misc/081-assert-openshift.yaml index 462ed4f144083..1e5773a8454ed 100644 --- a/operator/tests/central/central-misc/081-assert-openshift.yaml +++ b/operator/tests/central/central-misc/081-assert-openshift.yaml @@ -51,6 +51,8 @@ spec: value: "true" - name: ROX_LEGACY_SCANNER value: "false" + - name: ROX_CENTRAL_WORKER_ENABLED + value: "true" - name: NO_PROXY valueFrom: secretKeyRef: diff --git a/operator/tests/common/image-pull-secrets.yaml b/operator/tests/common/image-pull-secrets.yaml index 0a0a21fd5ebf4..98145fd926f3b 100644 --- a/operator/tests/common/image-pull-secrets.yaml +++ b/operator/tests/common/image-pull-secrets.yaml @@ -6,7 +6,7 @@ commands: set -eu # shell in CI does not grok -o pipefail secret=$(mktemp) registry_hostname=quay.io - ../../../../deploy/common/pull-secret.sh e2e-test-pull-secret ${registry_hostname} > $secret + ../../../../scripts/pull-secret.sh e2e-test-pull-secret ${registry_hostname} > $secret retry-kubectl.sh -n $NAMESPACE apply -f $secret echo "Created pull secret for ${registry_hostname} in $NAMESPACE" rm $secret diff --git a/operator/tools/kustomize/go.mod b/operator/tools/kustomize/go.mod index d3f33a6e89ea5..2ab7c7f278b5c 100644 --- a/operator/tools/kustomize/go.mod +++ b/operator/tools/kustomize/go.mod @@ -1,36 +1,31 @@ module github.com/stackrox/rox/operator/tools/kustomize -go 1.26.3 +go 1.27.0 -require sigs.k8s.io/kustomize/kustomize/v5 v5.8.1 +require sigs.k8s.io/kustomize/kustomize/v5 v5.8.3 require ( github.com/blang/semver/v4 v4.0.0 // indirect - github.com/davecgh/go-spew v1.1.1 // indirect - github.com/go-errors/errors v1.4.2 // indirect - github.com/go-openapi/jsonpointer v0.21.0 // indirect - github.com/go-openapi/jsonreference v0.20.2 // indirect - github.com/go-openapi/swag v0.23.0 // indirect - github.com/google/gnostic-models v0.6.9 // indirect + github.com/go-errors/errors v1.5.1 // indirect + github.com/go-openapi/jsonpointer v1.0.0 // indirect + github.com/go-openapi/jsonreference v1.0.0 // indirect + github.com/google/gnostic-models v0.7.1 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/josharian/intern v1.0.0 // indirect - github.com/mailru/easyjson v0.7.7 // indirect github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect github.com/sergi/go-diff v1.4.0 // indirect - github.com/spf13/cobra v1.9.1 // indirect - github.com/spf13/pflag v1.0.6 // indirect + github.com/spf13/cobra v1.10.2 // indirect + github.com/spf13/pflag v1.0.10 // indirect github.com/xlab/treeprint v1.2.0 // indirect - go.yaml.in/yaml/v2 v2.4.2 // indirect - go.yaml.in/yaml/v3 v3.0.3 // indirect - golang.org/x/sys v0.35.0 // indirect - golang.org/x/text v0.28.0 // indirect - google.golang.org/protobuf v1.36.5 // indirect + go.yaml.in/yaml/v2 v2.4.4 // indirect + go.yaml.in/yaml/v3 v3.0.5 // indirect + golang.org/x/sys v0.48.0 // indirect + golang.org/x/text v0.42.0 // indirect + google.golang.org/protobuf v1.36.12 // indirect gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/kube-openapi v0.0.0-20241212222426-2c72e554b1e7 // indirect - sigs.k8s.io/kustomize/api v0.21.1 // indirect - sigs.k8s.io/kustomize/cmd/config v0.21.1 // indirect - sigs.k8s.io/kustomize/kyaml v0.21.1 // indirect - sigs.k8s.io/yaml v1.5.0 // indirect + k8s.io/kube-openapi v0.0.0-20261001230523-97fa35140926 // indirect + sigs.k8s.io/kustomize/api v0.21.3 // indirect + sigs.k8s.io/kustomize/cmd/config v0.21.3 // indirect + sigs.k8s.io/kustomize/kyaml v0.21.3 // indirect + sigs.k8s.io/yaml v1.6.0 // indirect ) diff --git a/operator/tools/kustomize/go.sum b/operator/tools/kustomize/go.sum index ce3be4ca9c359..12247c596d1ac 100644 --- a/operator/tools/kustomize/go.sum +++ b/operator/tools/kustomize/go.sum @@ -1,83 +1,60 @@ github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= -github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= -github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= -github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= -github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= -github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= -github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= -github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= -github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= -github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= -github.com/google/gnostic-models v0.6.9 h1:MU/8wDLif2qCXZmzncUQ/BOfxWfthHi63KqpoNbWqVw= -github.com/google/gnostic-models v0.6.9/go.mod h1:CiWsm0s6BSQd1hRn8/QmxqB6BesYcbSZxsz9b0KuDBw= +github.com/go-errors/errors v1.5.1 h1:ZwEMSLRCapFLflTpT7NKaAc7ukJ8ZPEjzlxt8rPN8bk= +github.com/go-errors/errors v1.5.1/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= +github.com/go-openapi/jsonpointer v1.0.0 h1:kR9tHqY0CtZaOPVFm622dPVNhrvYpwr4uCxgL3h1H8s= +github.com/go-openapi/jsonpointer v1.0.0/go.mod h1:Z3rw7dWu1p9IgitXCFamSlA5lmDiklEB6vkaxcNZW5Y= +github.com/go-openapi/jsonreference v1.0.0 h1:jlmTr6torcd1YgDQvSfNmRtKzYDO4FGBkrAdlAVWnpY= +github.com/go-openapi/jsonreference v1.0.0/go.mod h1:jtwdyGbJk0Xhe5Y+rwtglQP6Sb1WZST4rT32LWB+sv0= +github.com/go-openapi/testify/v2 v2.6.0 h1:5PKH2HE7YJ/LuRPQGvSxBRlFXNQhSetBLlGAgUEu3ug= +github.com/go-openapi/testify/v2 v2.6.0/go.mod h1:SgsVHtfooshd0tublTtJ50FPKhujf47YRqauXXOUxfw= +github.com/google/gnostic-models v0.7.1 h1:SisTfuFKJSKM5CPZkffwi6coztzzeYUhc3v4yxLWH8c= +github.com/google/gnostic-models v0.7.1/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= 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/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= -github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= -github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= -github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= -github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 h1:n6/2gBQ3RWajuToeY6ZtZTIKv2v7ThUy5KKusIT0yc0= github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00/go.mod h1:Pm3mSP3c5uWn86xMLZ5Sa7JB9GsEZySvHYXCTK4E9q4= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -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/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw= github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= -github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= -github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= -github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= -github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= +github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4= +github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= +github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= -github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/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.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= +github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg= github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ= github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= -go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= -go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= -go.yaml.in/yaml/v3 v3.0.3 h1:bXOww4E/J3f66rav3pX3m8w6jDE4knZjGOw8b5Y6iNE= -go.yaml.in/yaml/v3 v3.0.3/go.mod h1:tBHosrYAkRZjRAOREWbDnBXUf08JOwYq++0QNwQiWzI= -golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= -golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= -golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= -google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= -google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ= +go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ= +go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= +go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= +go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= +golang.org/x/sys v0.48.0 h1:bbX/i/6MgT9BVLM9RT1thmxL04yeTAhbEz4SyadbXoo= +golang.org/x/sys v0.48.0/go.mod h1:hNLxWAXmnKAxqDtdwIYC4bM9oQPEecfsnNMuSxOs3og= +golang.org/x/text v0.42.0 h1:JbOZXgfeCPU9gacVtYliJqOhD+zhrEqK4LfdpmlUZqI= +golang.org/x/text v0.42.0/go.mod h1:ojzP1Z+2QtioaF8DTtO8K5q7JWVVYwZKenzujK0Zd0E= +google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc= +google.golang.org/protobuf v1.36.12/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-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo= gopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= @@ -85,19 +62,19 @@ gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 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= -k8s.io/kube-openapi v0.0.0-20241212222426-2c72e554b1e7 h1:hcha5B1kVACrLujCKLbr8XWMxCxzQx42DY8QKYJrDLg= -k8s.io/kube-openapi v0.0.0-20241212222426-2c72e554b1e7/go.mod h1:GewRfANuJ70iYzvn+i4lezLDAFzvjxZYK1gn1lWcfas= -sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= -sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= -sigs.k8s.io/kustomize/api v0.21.1 h1:lzqbzvz2CSvsjIUZUBNFKtIMsEw7hVLJp0JeSIVmuJs= -sigs.k8s.io/kustomize/api v0.21.1/go.mod h1:f3wkKByTrgpgltLgySCntrYoq5d3q7aaxveSagwTlwI= -sigs.k8s.io/kustomize/cmd/config v0.21.1 h1:/gxf3J1rQD9nfuL8fHlrTLeUL+JHWbK44eOnXJDYx0M= -sigs.k8s.io/kustomize/cmd/config v0.21.1/go.mod h1:7yEFYBJyBJlpZQ50VaRGQRtFMn3Vzn9Fb2wts4TCok4= -sigs.k8s.io/kustomize/kustomize/v5 v5.8.1 h1:Pgsg5psubpVEy7Nf6S89PARg5VmmWUC1l9dC6Dl4PG0= -sigs.k8s.io/kustomize/kustomize/v5 v5.8.1/go.mod h1:0vFa5pQ/elNEQMyiAJuGku9rhAMzz7u9+61hRqFKiwY= -sigs.k8s.io/kustomize/kyaml v0.21.1 h1:IVlbmhC076nf6foyL6Taw4BkrLuEsXUXNpsE+ScX7fI= -sigs.k8s.io/kustomize/kyaml v0.21.1/go.mod h1:hmxADesM3yUN2vbA5z1/YTBnzLJ1dajdqpQonwBL1FQ= -sigs.k8s.io/yaml v1.5.0 h1:M10b2U7aEUY6hRtU870n2VTPgR5RZiL/I6Lcc2F4NUQ= -sigs.k8s.io/yaml v1.5.0/go.mod h1:wZs27Rbxoai4C0f8/9urLZtZtF3avA3gKvGyPdDqTO4= +k8s.io/kube-openapi v0.0.0-20261001230523-97fa35140926 h1:Fe0G8t8FaMDn2lpat0IHu3X9lHrVyGzDxhhCwKXKp28= +k8s.io/kube-openapi v0.0.0-20261001230523-97fa35140926/go.mod h1:3e2VxsK2svhjvkNik92kYU1a5kgau5tZeAuEWwoN4q0= +sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= +sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= +sigs.k8s.io/kustomize/api v0.21.3 h1:DXGrbifGrAd+7AQ75Kx/tVhr+RLMCZFfAYl61HOR3T0= +sigs.k8s.io/kustomize/api v0.21.3/go.mod h1:JKlHjHXGh54cdoWVdKNqDscFzPb9z+sBk9upy9PBh6k= +sigs.k8s.io/kustomize/cmd/config v0.21.3 h1:/G7n3/SM3o5IxVxHY3+3/qPBliMtE5UcMAzbabkBuM4= +sigs.k8s.io/kustomize/cmd/config v0.21.3/go.mod h1:BFUnlKdjh9rfFkgb5HWi6xmhsW6ybqW/JsV6Y1cZmK0= +sigs.k8s.io/kustomize/kustomize/v5 v5.8.3 h1:SJXKgCEb4Ru+8PrhwQ+ORAASc2rPWBneHjh4fKTXPWY= +sigs.k8s.io/kustomize/kustomize/v5 v5.8.3/go.mod h1:goZZjpeux35r4u4E3BPtetYBYcj6Vi5xQtpABCPF9Nk= +sigs.k8s.io/kustomize/kyaml v0.21.3 h1:4ygzLsc8B1ep9dFuEivrJ2lFIAglL19dhPrWp1ioceY= +sigs.k8s.io/kustomize/kyaml v0.21.3/go.mod h1:HrAVNlYyEphOlRCcaSIQziGR+0ehwx6Xp1ucUrpC6hs= +sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= +sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= +sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= +sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4= diff --git a/operator/tools/kuttl/go.mod b/operator/tools/kuttl/go.mod index 78782800d13cc..efd998592d696 100644 --- a/operator/tools/kuttl/go.mod +++ b/operator/tools/kuttl/go.mod @@ -2,7 +2,7 @@ module github.com/stackrox/rox/operator/tools/kuttl go 1.26.3 -require github.com/kudobuilder/kuttl v0.26.0 +require github.com/kudobuilder/kuttl v0.27.0 require ( al.essio.dev/pkg/shellescape v1.5.1 // indirect @@ -22,31 +22,31 @@ require ( github.com/distribution/reference v0.6.0 // indirect github.com/docker/go-connections v0.7.0 // indirect github.com/docker/go-units v0.5.0 // indirect - github.com/dustin/go-humanize v1.0.1 // indirect + github.com/dustin/go-humanize v1.1.0 // indirect github.com/dustinkirkland/golang-petname v0.0.0-20191129215211-8e5a1ed0cff0 // indirect github.com/emicklei/go-restful/v3 v3.13.0 // indirect github.com/evanphx/json-patch/v5 v5.9.11 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fsnotify/fsnotify v1.10.0 // indirect github.com/fxamacker/cbor/v2 v2.9.1 // indirect - github.com/go-logr/logr v1.4.3 // indirect + github.com/go-logr/logr v1.4.4 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-logr/zapr v1.3.0 // indirect - github.com/go-openapi/jsonpointer v0.23.1 // indirect - github.com/go-openapi/jsonreference v0.21.5 // indirect - github.com/go-openapi/swag v0.26.0 // indirect - github.com/go-openapi/swag/cmdutils v0.26.0 // indirect - github.com/go-openapi/swag/conv v0.26.0 // indirect - github.com/go-openapi/swag/fileutils v0.26.0 // indirect - github.com/go-openapi/swag/jsonname v0.26.0 // indirect - github.com/go-openapi/swag/jsonutils v0.26.0 // indirect - github.com/go-openapi/swag/loading v0.26.0 // indirect - github.com/go-openapi/swag/mangling v0.26.0 // indirect - github.com/go-openapi/swag/netutils v0.26.0 // indirect - github.com/go-openapi/swag/stringutils v0.26.0 // indirect - github.com/go-openapi/swag/typeutils v0.26.0 // indirect - github.com/go-openapi/swag/yamlutils v0.26.0 // indirect - github.com/google/cel-go v0.29.0 // indirect + github.com/go-openapi/jsonpointer v1.0.0 // indirect + github.com/go-openapi/jsonreference v1.0.0 // indirect + github.com/go-openapi/swag v0.27.1 // indirect + github.com/go-openapi/swag/cmdutils v0.27.1 // indirect + github.com/go-openapi/swag/conv v0.27.1 // indirect + github.com/go-openapi/swag/fileutils v0.27.1 // indirect + github.com/go-openapi/swag/jsonutils v0.27.1 // indirect + github.com/go-openapi/swag/loading v0.27.1 // indirect + github.com/go-openapi/swag/mangling v0.27.1 // indirect + github.com/go-openapi/swag/netutils v0.27.1 // indirect + github.com/go-openapi/swag/pools v0.27.1 // indirect + github.com/go-openapi/swag/stringutils v0.27.1 // indirect + github.com/go-openapi/swag/typeutils v0.27.1 // indirect + github.com/go-openapi/swag/yamlutils v0.27.1 // indirect + github.com/google/cel-go v0.31.0 // indirect github.com/google/gnostic-models v0.7.1 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/google/uuid v1.6.0 // indirect @@ -57,21 +57,22 @@ require ( github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect - github.com/moby/moby/api v1.54.2 // indirect - github.com/moby/moby/client v0.4.1 // indirect + github.com/moby/moby/api v1.56.0 // indirect + github.com/moby/moby/client v0.6.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect - github.com/onsi/gomega v1.40.0 // indirect + github.com/onsi/ginkgo/v2 v2.32.1 // indirect + github.com/onsi/gomega v1.43.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.1 // indirect github.com/pelletier/go-toml v1.9.5 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - github.com/prometheus/client_golang v1.23.2 // indirect + github.com/prometheus/client_golang v1.24.0 // indirect github.com/prometheus/client_model v0.6.2 // indirect - github.com/prometheus/common v0.67.5 // indirect - github.com/prometheus/procfs v0.20.1 // indirect + github.com/prometheus/common v0.70.0 // indirect + github.com/prometheus/procfs v0.21.1 // indirect github.com/shopspring/decimal v1.4.0 // indirect github.com/spf13/cast v1.7.0 // indirect github.com/spf13/cobra v1.10.2 // indirect @@ -79,42 +80,42 @@ require ( github.com/thoas/go-funk v0.9.3 // indirect github.com/x448/float16 v0.8.4 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0 // indirect - go.opentelemetry.io/otel v1.43.0 // indirect - go.opentelemetry.io/otel/metric v1.43.0 // indirect - go.opentelemetry.io/otel/trace v1.43.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0 // indirect + 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 go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.1 // indirect go.yaml.in/yaml/v2 v2.4.4 // indirect - go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/crypto v0.53.0 // indirect - golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93 // indirect - golang.org/x/net v0.55.0 // indirect + go.yaml.in/yaml/v3 v3.0.5 // indirect + golang.org/x/crypto v0.55.0 // indirect + golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f // indirect + golang.org/x/net v0.58.0 // indirect golang.org/x/oauth2 v0.36.0 // indirect - golang.org/x/sync v0.21.0 // indirect - golang.org/x/sys v0.46.0 // indirect - golang.org/x/term v0.44.0 // indirect - golang.org/x/text v0.38.0 // indirect + golang.org/x/sync v0.22.0 // indirect + golang.org/x/sys v0.47.0 // indirect + golang.org/x/term v0.45.0 // indirect + golang.org/x/text v0.41.0 // indirect golang.org/x/time v0.15.0 // indirect gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9 // indirect + 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/protobuf v1.36.12-0.20260120151049-f2248ac996af // indirect gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/api v0.36.0 // indirect - k8s.io/apiextensions-apiserver v0.36.0 // indirect - k8s.io/apimachinery v0.36.0 // indirect - k8s.io/client-go v0.36.0 // indirect + k8s.io/api v0.37.0 // indirect + k8s.io/apiextensions-apiserver v0.37.0 // indirect + k8s.io/apimachinery v0.37.0 // indirect + k8s.io/client-go v0.37.0 // indirect k8s.io/klog/v2 v2.140.0 // indirect - k8s.io/kube-openapi v0.0.0-20260427204847-8949caaa1199 // indirect - k8s.io/utils v0.0.0-20260319190234-28399d86e0b5 // indirect - sigs.k8s.io/controller-runtime v0.24.0 // indirect + k8s.io/kube-openapi v0.0.0-20260721132016-d427ff9ee9ad // indirect + k8s.io/utils v0.0.0-20260626114624-be93311217bd // indirect + sigs.k8s.io/controller-runtime v0.25.1 // indirect sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect - sigs.k8s.io/kind v0.31.0 // indirect + sigs.k8s.io/kind v0.33.0 // indirect sigs.k8s.io/randfill v1.0.0 // indirect - sigs.k8s.io/structured-merge-diff/v6 v6.4.0 // indirect + sigs.k8s.io/structured-merge-diff/v6 v6.4.2 // indirect sigs.k8s.io/yaml v1.6.0 // indirect ) diff --git a/operator/tools/kuttl/go.sum b/operator/tools/kuttl/go.sum index 57a9d9a3222f9..6d90ca396d9c2 100644 --- a/operator/tools/kuttl/go.sum +++ b/operator/tools/kuttl/go.sum @@ -35,8 +35,8 @@ github.com/docker/go-connections v0.7.0 h1:6SsRfJddP22WMrCkj19x9WKjEDTB+ahsdiGYf github.com/docker/go-connections v0.7.0/go.mod h1:no1qkHdjq7kLMGUXYAduOhYPSJxxvgWBh7ogVvptn3Q= 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/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= -github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/dustin/go-humanize v1.1.0 h1:dbKTrvD0klcbBV/h4AWJdMuZogJACoMlvWIWZ5b2xWg= +github.com/dustin/go-humanize v1.1.0/go.mod h1:hc1CvRkJMsgxqjmjMQF3QNRAZBwY8AXBAzKYoSX9sFI= github.com/dustinkirkland/golang-petname v0.0.0-20191129215211-8e5a1ed0cff0 h1:90Ly+6UfUypEF6vvvW5rQIv9opIL8CbmW9FT20LDQoY= github.com/dustinkirkland/golang-petname v0.0.0-20191129215211-8e5a1ed0cff0/go.mod h1:V+Qd57rJe8gd4eiGzZyg4h54VLHmYVVw54iMnlAMrF8= github.com/emicklei/go-restful/v3 v3.13.0 h1:C4Bl2xDndpU6nJ4bc1jXd+uTmYPVUwkD6bFY/oTyCes= @@ -54,50 +54,50 @@ github.com/fsnotify/fsnotify v1.10.0/go.mod h1:TLheqan6HD6GBK6PrDWyDPBaEV8LspOxv github.com/fxamacker/cbor/v2 v2.9.1 h1:2rWm8B193Ll4VdjsJY28jxs70IdDsHRWgQYAI80+rMQ= github.com/fxamacker/cbor/v2 v2.9.1/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= -github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.4 h1:tG4xh9yMsRCAiodLVTxyrkzSZ9+o0L1Kg/+cPVcbP/8= +github.com/go-logr/logr v1.4.4/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-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg= -github.com/go-openapi/jsonpointer v0.23.1 h1:1HBACs7XIwR2RcmItfdSFlALhGbe6S92p0ry4d1GWg4= -github.com/go-openapi/jsonpointer v0.23.1/go.mod h1:iWRmZTrGn7XwYhtPt/fvdSFj1OfNBngqRT2UG3BxSqY= -github.com/go-openapi/jsonreference v0.21.5 h1:6uCGVXU/aNF13AQNggxfysJ+5ZcU4nEAe+pJyVWRdiE= -github.com/go-openapi/jsonreference v0.21.5/go.mod h1:u25Bw85sX4E2jzFodh1FOKMTZLcfifd1Q+iKKOUxExw= -github.com/go-openapi/swag v0.26.0 h1:GVDXCmfvhfu1BxiHo8/FA+BbKmhecHnG3varjON5/RI= -github.com/go-openapi/swag v0.26.0/go.mod h1:82g3193sZJRbocs7bNCqGfIgq8pkuwVwCfhKIRlEQF0= -github.com/go-openapi/swag/cmdutils v0.26.0 h1:iowihOcvq7y4egO8cOq0dmfohz6wfeQ63U1EnuhO2TU= -github.com/go-openapi/swag/cmdutils v0.26.0/go.mod h1:Sm1MVFMkF6guJJ+pQqHnQA3N0j9qALV3NxzDSv6bETM= -github.com/go-openapi/swag/conv v0.26.0 h1:5yGGsPYI1ZCva93U0AoKi/iZrNhaJEjr324YVsiD89I= -github.com/go-openapi/swag/conv v0.26.0/go.mod h1:tpAmIL7X58VPnHHiSO4uE3jBeRamGsFsfdDeDtb5ECE= -github.com/go-openapi/swag/fileutils v0.26.0 h1:WJoPRvsA7QRiiWluowkLJa9jaYR7FCuxmDvnCgaRRxU= -github.com/go-openapi/swag/fileutils v0.26.0/go.mod h1:0WDJ7lp67eNjPMO50wAWYlKvhOb6CQ37rzR7wrgI8Tc= -github.com/go-openapi/swag/jsonname v0.26.0 h1:gV1NFX9M8avo0YSpmWogqfQISigCmpaiNci8cGECU5w= -github.com/go-openapi/swag/jsonname v0.26.0/go.mod h1:urBBR8bZNoDYGr653ynhIx+gTeIz0ARZxHkAPktJK2M= -github.com/go-openapi/swag/jsonutils v0.26.0 h1:FawFML2iAXsPqmERscuMPIHmFsoP1tOqWkxBaKNMsnA= -github.com/go-openapi/swag/jsonutils v0.26.0/go.mod h1:2VmA0CJlyFqgawOaPI9psnjFDqzyivIqLYN34t9p91E= -github.com/go-openapi/swag/jsonutils/fixtures_test v0.26.0 h1:apqeINu/ICHouqiRZbyFvuDge5jCmmLTqGQ9V95EaOM= -github.com/go-openapi/swag/jsonutils/fixtures_test v0.26.0/go.mod h1:AyM6QT8uz5IdKxk5akv0y6u4QvcL9GWERt0Jx/F/R8Y= -github.com/go-openapi/swag/loading v0.26.0 h1:Apg6zaKhCJurpJer0DCxq99qwmhFddBhaMX7kilDcko= -github.com/go-openapi/swag/loading v0.26.0/go.mod h1:dBxQ/6V2uBaAQdevN18VELE6xSpJWZxLX4txe12JwDg= -github.com/go-openapi/swag/mangling v0.26.0 h1:Du2YC4YLA/Y5m/YKQd7AnY5qq0wRKSFZTTt8ktFaXcQ= -github.com/go-openapi/swag/mangling v0.26.0/go.mod h1:jifS7W9vbg+pw63bT+GI53otluMQL3CeemuyCHKwVx0= -github.com/go-openapi/swag/netutils v0.26.0 h1:CmZp+ZT7HrmFwrC3GdGsXBq2+42T1bjKBapcqVpIs3c= -github.com/go-openapi/swag/netutils v0.26.0/go.mod h1:5iK+Ok3ZohWWex1C50BFTPexi03UaPwjW4Oj8kgrpwo= -github.com/go-openapi/swag/stringutils v0.26.0 h1:qZQngLxs5s7SLijc3N2ZO+fUq2o8LjuWAASSrJuh+xg= -github.com/go-openapi/swag/stringutils v0.26.0/go.mod h1:sWn5uY+QIIspwPhvgnqJsH8xqFT2ZbYcvbcFanRyhFE= -github.com/go-openapi/swag/typeutils v0.26.0 h1:2kdEwdiNWy+JJdOvu5MA2IIg2SylWAFuuyQIKYybfq4= -github.com/go-openapi/swag/typeutils v0.26.0/go.mod h1:oovDuIUvTrEHVMqWilQzKzV4YlSKgyZmFh7AlfABNVE= -github.com/go-openapi/swag/yamlutils v0.26.0 h1:H7O8l/8NJJQ/oiReEN+oMpnGMyt8G0hl460nRZxhLMQ= -github.com/go-openapi/swag/yamlutils v0.26.0/go.mod h1:1evKEGAtP37Pkwcc7EWMF0hedX0/x3Rkvei2wtG/TbU= -github.com/go-openapi/testify/enable/yaml/v2 v2.4.2 h1:5zRca5jw7lzVREKCZVNBpysDNBjj74rBh0N2BGQbSR0= -github.com/go-openapi/testify/enable/yaml/v2 v2.4.2/go.mod h1:XVevPw5hUXuV+5AkI1u1PeAm27EQVrhXTTCPAF85LmE= -github.com/go-openapi/testify/v2 v2.4.2 h1:tiByHpvE9uHrrKjOszax7ZvKB7QOgizBWGBLuq0ePx4= -github.com/go-openapi/testify/v2 v2.4.2/go.mod h1:SgsVHtfooshd0tublTtJ50FPKhujf47YRqauXXOUxfw= +github.com/go-openapi/jsonpointer v1.0.0 h1:kR9tHqY0CtZaOPVFm622dPVNhrvYpwr4uCxgL3h1H8s= +github.com/go-openapi/jsonpointer v1.0.0/go.mod h1:Z3rw7dWu1p9IgitXCFamSlA5lmDiklEB6vkaxcNZW5Y= +github.com/go-openapi/jsonreference v1.0.0 h1:jlmTr6torcd1YgDQvSfNmRtKzYDO4FGBkrAdlAVWnpY= +github.com/go-openapi/jsonreference v1.0.0/go.mod h1:jtwdyGbJk0Xhe5Y+rwtglQP6Sb1WZST4rT32LWB+sv0= +github.com/go-openapi/swag v0.27.1 h1:VotvOLWW8q/EAxB0YdsBBGC8XYyeL1YwBj2ungAGPNg= +github.com/go-openapi/swag v0.27.1/go.mod h1:GTkJPwHfhJp6MWr4/rCh64HVI3Ofu+tcsbfjfHmTxpE= +github.com/go-openapi/swag/cmdutils v0.27.1 h1:I7sYqaWVl5mq0NEmNQkAmFDyNin9ufvMX/p2zwtQaOE= +github.com/go-openapi/swag/cmdutils v0.27.1/go.mod h1:Sm1MVFMkF6guJJ+pQqHnQA3N0j9qALV3NxzDSv6bETM= +github.com/go-openapi/swag/conv v0.27.1 h1:8wi9ZG+olmY1wXphl93EWniPtbSPkXM/feH7FgjsvrU= +github.com/go-openapi/swag/conv v0.27.1/go.mod h1:QbqMivkpKhC3g1B1GGGOJ6ANewI3S62dbzYu3Duowqs= +github.com/go-openapi/swag/fileutils v0.27.1 h1:QQqBSoi5mW4XpU85nS0mLcA+zAE6vLzrb0QkmLKf9oM= +github.com/go-openapi/swag/fileutils v0.27.1/go.mod h1:VvJFZLTZS0AI854gEQz5tk7dBESdLjiNUMSZ/th2ry8= +github.com/go-openapi/swag/jsonutils v0.27.1 h1:SVgK3i4USzCU5mibOOS/l4ea2h9UQXy7J7RNLTjuXjU= +github.com/go-openapi/swag/jsonutils v0.27.1/go.mod h1:tdlEpZqdcQ17uj6J4YdK9vd8It5qWMwjWXOs0tjpRlk= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.27.1 h1:mJu3COL9WEaZVp/Kf2PRMi7tPszPEJfSr/OO75ynCs8= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.27.1/go.mod h1:mofwUWx70wvskwESqRJ//k/9kURmCgyJl5m5Ppoh5kY= +github.com/go-openapi/swag/loading v0.27.1 h1:/DxUgDXKbBX4bcn7r9uEXfJyzN5XpiJmZplzQTjrRCY= +github.com/go-openapi/swag/loading v0.27.1/go.mod h1:jvGh3iA2+zyUUycB5fgJWzeHnhrpvGnJJM0RVE9ZShE= +github.com/go-openapi/swag/mangling v0.27.1 h1:yC9D0HyUE8gbP+BfmGx9+AA89ikwZTMjESK3OnnoaqA= +github.com/go-openapi/swag/mangling v0.27.1/go.mod h1:jtBE2+V+3pILxOR7Vgce+Cwp6A2PgZbvVqfNntbVs0w= +github.com/go-openapi/swag/netutils v0.27.1 h1:mICMFoS82F5TZ4Zy3cqmcQk+BFeCp3Uyq3Np7GI0/qU= +github.com/go-openapi/swag/netutils v0.27.1/go.mod h1:J+WYyFMLtvtCGqa6jLv+YNUmIKI3ZRQRrvfNDMoQoEQ= +github.com/go-openapi/swag/pools v0.27.1 h1:9LeadcMyb2GJCbXX5hVQDbZ2Lq9TL4dCs/nx1j5DO0E= +github.com/go-openapi/swag/pools v0.27.1/go.mod h1:kVQefhSK5RWuRe7BXsL8htgBPAMpN7HDGpGEknqugeE= +github.com/go-openapi/swag/stringutils v0.27.1 h1:ZXePZ0r2p1qSjo8tD3Un4vFj8+FqlCkczxDrJIhYUp8= +github.com/go-openapi/swag/stringutils v0.27.1/go.mod h1:lzRN95CxXmA03XcDWHLOb6nOMcxCqR5rGY0lOgsfRoM= +github.com/go-openapi/swag/typeutils v0.27.1 h1:KSTdFlfnse4r6dP9IrEnwMldjE+zs71UeEB3//PtVXc= +github.com/go-openapi/swag/typeutils v0.27.1/go.mod h1:Srm0xFNRZ1Y+vCxJclo5qzx8aj+1pAKda/YfFPrG0dQ= +github.com/go-openapi/swag/yamlutils v0.27.1 h1:ftxv6xvXb1E3zohUc+okZ9nSqNb9StQX/FXnKZ98sQA= +github.com/go-openapi/swag/yamlutils v0.27.1/go.mod h1:bnxFIB1qewGRiZHypXGZ3fNgf13/0HfRgnS/iZBDrOo= +github.com/go-openapi/testify/enable/yaml/v2 v2.6.0 h1:gGHwAJ0R/5jU8BEGDbfRNR3hL68dAVi84WuOApp29B0= +github.com/go-openapi/testify/enable/yaml/v2 v2.6.0/go.mod h1:tY+St1SGq4NFl0QIqdTY4aEdbChAHxhyB77XQi9iJCo= +github.com/go-openapi/testify/v2 v2.6.0 h1:5PKH2HE7YJ/LuRPQGvSxBRlFXNQhSetBLlGAgUEu3ug= +github.com/go-openapi/testify/v2 v2.6.0/go.mod h1:SgsVHtfooshd0tublTtJ50FPKhujf47YRqauXXOUxfw= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= -github.com/google/cel-go v0.29.0 h1:fEG+Ja3YRwNOqnQxTyJwoByAUAvTuxUGiro/jhrm4F4= -github.com/google/cel-go v0.29.0/go.mod h1:X0bD6iVNR8pkROSOoHVdgTkzmRcosof7WQqCD6wcMc8= +github.com/google/cel-go v0.31.0 h1:H0bhpFTqOvmHrBGrWKp7ZlhBm5Hh8PYUEXnwxT1LL7A= +github.com/google/cel-go v0.31.0/go.mod h1:X0bD6iVNR8pkROSOoHVdgTkzmRcosof7WQqCD6wcMc8= github.com/google/gnostic-models v0.7.1 h1:SisTfuFKJSKM5CPZkffwi6coztzzeYUhc3v4yxLWH8c= github.com/google/gnostic-models v0.7.1/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= @@ -105,8 +105,8 @@ github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8= -github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= +github.com/google/pprof v0.0.0-20260402051712-545e8a4df936 h1:EwtI+Al+DeppwYX2oXJCETMO23COyaKGP6fHVpkpWpg= +github.com/google/pprof v0.0.0-20260402051712-545e8a4df936/go.mod h1:MxpfABSjhmINe3F1It9d+8exIHFvUqtLIRCdOGNXqiI= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= @@ -117,14 +117,14 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2 github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= -github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= -github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= +github.com/klauspost/compress v1.19.0 h1:sXLILfc9jV2QYWkzFOPWStmcUVH2RHEB1JCdY2oVvCQ= +github.com/klauspost/compress v1.19.0/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/kudobuilder/kuttl v0.26.0 h1:8PXqnuismPqmxk5yslqfCezXLfkCbR+PIqz8KDzuo7M= -github.com/kudobuilder/kuttl v0.26.0/go.mod h1:jsTOHjoskFIcrkIWnLjAztV5n86o+YqUOjgTK/5lKsc= +github.com/kudobuilder/kuttl v0.27.0 h1:Dc+h0OaoLSaNdSt0caMtHFr0mvdhz7flDuZzq1k1wuU= +github.com/kudobuilder/kuttl v0.27.0/go.mod h1:mpoA6IwOP7pe3hxGO5ojVcpbE6KphWTn7QNN9qpZaWw= 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/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= @@ -135,10 +135,10 @@ github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zx github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= 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/moby/api v1.54.2 h1:wiat9QAhnDQjA7wk1kh/TqHz2I1uUA7M7t9SAl/JNXg= -github.com/moby/moby/api v1.54.2/go.mod h1:+RQ6wluLwtYaTd1WnPLykIDPekkuyD/ROWQClE83pzs= -github.com/moby/moby/client v0.4.1 h1:DMQgisVoMkmMs7fp3ROSdiBnoAu8+vo3GggFl06M/wY= -github.com/moby/moby/client v0.4.1/go.mod h1:z52C9O2POPOsnxZAy//WtKcQ32P+jT/NGeXu/7nfjGQ= +github.com/moby/moby/api v1.56.0 h1:GQzua3NA599ASSIICx0iFgiJeO9YkdDARvQsm23ZZuQ= +github.com/moby/moby/api v1.56.0/go.mod h1:sZ+THbVWkjOmBPPfbnzdD/G1LuIexWhqlSHHPTDQ1Uk= +github.com/moby/moby/client v0.6.0 h1:AJjEB21QPbXSXjDsZorFBoDZPhMrfbpaPLgSMAW9Bgs= +github.com/moby/moby/client v0.6.0/go.mod h1:OCo00wNRyA3m4lmJ228W3JbyCN4ZNNYjpOXiJydBdcQ= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -147,10 +147,10 @@ github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFd github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/onsi/ginkgo/v2 v2.27.4 h1:fcEcQW/A++6aZAZQNUmNjvA9PSOzefMJBerHJ4t8v8Y= -github.com/onsi/ginkgo/v2 v2.27.4/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo= -github.com/onsi/gomega v1.40.0 h1:Vtol0e1MghCD2ZVIilPDIg44XSL9l2QAn8ZNaljWcJc= -github.com/onsi/gomega v1.40.0/go.mod h1:M/Uqpu/8qTjtzCLUA2zJHX9Iilrau25x1PdoSRbWh5A= +github.com/onsi/ginkgo/v2 v2.32.1 h1:6tlvcDm/3sE8lGJbZ4+d4mO3RLy24/tQWOFzVSQNIfw= +github.com/onsi/ginkgo/v2 v2.32.1/go.mod h1:+aXOY+vzZ5mu2iI2HpTZUPmM//oQfsNFX6gU9kNcA44= +github.com/onsi/gomega v1.43.0 h1:VlG/1FxqNxhSO+lq/OHBNaaqwiBK/mO8JbVkX9Y+FeU= +github.com/onsi/gomega v1.43.0/go.mod h1:REff/hsDsodHoKlWsP2mAPhu1+5/6hVYNf9rIEBpeSg= 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= @@ -162,14 +162,14 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE 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/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= -github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= +github.com/prometheus/client_golang v1.24.0 h1:5XStIklKuAtJSNpdD3s8XJj/Yv78IQmE1kbNk87JrAI= +github.com/prometheus/client_golang v1.24.0/go.mod h1:QcsNdotprC2nS4BTM2ucbcqxd2CeXTEa9jW7zHO9iDE= github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= -github.com/prometheus/common v0.67.5 h1:pIgK94WWlQt1WLwAC5j2ynLaBRDiinoAb86HZHTUGI4= -github.com/prometheus/common v0.67.5/go.mod h1:SjE/0MzDEEAyrdr5Gqc6G+sXI67maCxzaT3A2+HqjUw= -github.com/prometheus/procfs v0.20.1 h1:XwbrGOIplXW/AU3YhIhLODXMJYyC1isLFfYCsTEycfc= -github.com/prometheus/procfs v0.20.1/go.mod h1:o9EMBZGRyvDrSPH1RqdxhojkuXstoe4UlK79eF5TGGo= +github.com/prometheus/common v0.70.0 h1:bcpru3tWPVnxGnETLgOV5jbp/JRXgYEyv65CuBLAMMI= +github.com/prometheus/common v0.70.0/go.mod h1:S/SFasQmgGiYH6C81LKCtYa8QACgthGg5zxL2udV7SY= +github.com/prometheus/procfs v0.21.1 h1:GljZCt+zSTS+NZq88cyQ1LjZ+RCHp3uVuabBWA5+OJI= +github.com/prometheus/procfs v0.21.1/go.mod h1:aB55Cww9pdSJVHk0hUf0inxWyyjPogFIjmHKYgMKmtY= 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= @@ -183,30 +183,30 @@ github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= -github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/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.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -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/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= +github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg= github.com/thoas/go-funk v0.9.3 h1:7+nAEx3kn5ZJcnDm2Bh23N2yOtweO14bi//dvRtgLpw= github.com/thoas/go-funk v0.9.3/go.mod h1:+IWnUfUmFO1+WVYQWQtIJHeRRdaIyyYglZN7xzUPe4Q= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0 h1:7iP2uCb7sGddAr30RRS6xjKy7AZ2JtTOPA3oolgVSw8= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0/go.mod h1:c7hN3ddxs/z6q9xwvfLPk+UHlWRQyaeR1LdgfL/66l0= -go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I= -go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0= -go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM= -go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY= -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/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A= -go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0 h1:8tvICD4vSTOOsNrsI4Ljf6C+6UKvpTEH5XY3JMoyPoo= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0/go.mod h1:z9+yiacE0IHRqM4qFfkbt/JYlmYXgss8GY/jXoNuPJI= +go.opentelemetry.io/otel v1.44.0 h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU= +go.opentelemetry.io/otel v1.44.0/go.mod h1:BMgjTHL9WPRlRjL2oZCBTL4whCGtXch2H4BhOPIAyYc= +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.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= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= @@ -215,37 +215,38 @@ go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc= go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ= go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ= -go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= -golang.org/x/crypto v0.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto= -golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsio= -golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93 h1:fQsdNF2N+/YewlRZiricy4P1iimyPKZ/xwniHj8Q2a0= -golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93/go.mod h1:EPRbTFwzwjXj9NpYyyrvenVh9Y+GFeEvMNh7Xuz7xgU= -golang.org/x/mod v0.36.0 h1:JJjpVx6myfUsUdAzZuOSTTmRE0PfZeNWzzvKrP7amb4= -golang.org/x/mod v0.36.0/go.mod h1:moc6ELqsWcOw5Ef3xVprK5ul/MvtVvkIXLziUOICjUQ= -golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8= -golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww= +go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= +go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= +golang.org/x/crypto v0.55.0 h1:+KWHjbgOaAQ66dh/YlkZKHlz9ZUlq61AFirAR9ntP8M= +golang.org/x/crypto v0.55.0/go.mod h1:uq0V9dE/fzQuJtbnL+2EhWOE63vo164FY8xqEnV9xis= +golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f h1:W3F4c+6OLc6H2lb//N1q4WpJkhzJCK5J6kUi1NTVXfM= +golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f/go.mod h1:J1xhfL/vlindoeF/aINzNzt2Bket5bjo9sdOYzOsU80= +golang.org/x/mod v0.39.0 h1:UF5zwQdCRRUpHfyPwr7d4UrGiVeldIsogtzWVnczL74= +golang.org/x/mod v0.39.0/go.mod h1:bvIbwjQ0HUFFf5AKukeeYQG4ZBUG9yxQbR9aEweIwYY= +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.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM= -golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +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.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw= -golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= -golang.org/x/term v0.44.0 h1:0rLvDRCtNj0gZkyIXhCyOb2OAzEhLVqc4B+hrsBhrmc= -golang.org/x/term v0.44.0/go.mod h1:7ze4MdzUzLXpSAoFP1H0bOI9aXDqveSvatT5vKcFh2Y= -golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE= -golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4= +golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs= +golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/term v0.45.0 h1:NwWyBmoJCbfTHpxrWoZ9C6/VxOf7ic219I8xZZFdrf0= +golang.org/x/term v0.45.0/go.mod h1:9aqxs0blBcrm/n0L9QW0aRVD+ktan8ssZromtqJC43w= +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.45.0 h1:18qN3FAooORvApf5XjCXgsuayZOEtXf6JK18I3+ONa8= -golang.org/x/tools v0.45.0/go.mod h1:LuUGqqaXcXMEFEruIVJVm5mgDD8vww/z/SR1gQ4uE/0= +golang.org/x/tools v0.49.0 h1:3NI7VXzL9+1WZD52Dx2ttoPwD5DWrFGpl9mFZDlmisI= +golang.org/x/tools v0.49.0/go.mod h1:SJNXV9DBKT0UbdttsQjbfJlAE/q+y36++zo3uL3N0Oo= gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw= gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= -google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 h1:VPWxll4HlMw1Vs/qXtN7BvhZqsS9cdAittCNvVENElA= -google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9/go.mod h1:7QBABkRtR8z+TEnmXTqIqwJLlzrZKVfAUm7tY3yGv0M= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9 h1:m8qni9SQFH0tJc1X0vmnpw/0t+AImlSvp30sEupozUg= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= +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/protobuf v1.36.12-0.20260120151049-f2248ac996af h1:+5/Sw3GsDNlEmu7TfklWKPdQ0Ykja5VEmq2i817+jbI= google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -262,31 +263,31 @@ 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= -k8s.io/api v0.36.0 h1:SgqDhZzHdOtMk40xVSvCXkP9ME0H05hPM3p9AB1kL80= -k8s.io/api v0.36.0/go.mod h1:m1LVrGPNYax5NBHdO+QuAedXyuzTt4RryI/qnmNvs34= -k8s.io/apiextensions-apiserver v0.36.0 h1:Wt7E8J+VBCbj4FjiBfDTK/neXDDjyJVJc7xfuOHImZ0= -k8s.io/apiextensions-apiserver v0.36.0/go.mod h1:kGDjH0msuiIB3tgsYRV0kS9GqpMYMUsQ3GHv7TApyug= -k8s.io/apimachinery v0.36.0 h1:jZyPzhd5Z+3h9vJLt0z9XdzW9VzNzWAUw+P1xZ9PXtQ= -k8s.io/apimachinery v0.36.0/go.mod h1:FklypaRJt6n5wUIwWXIP6GJlIpUizTgfo1T/As+Tyxc= -k8s.io/client-go v0.36.0 h1:pOYi7C4RHChYjMiHpZSpSbIM6ZxVbRXBy7CuiIwqA3c= -k8s.io/client-go v0.36.0/go.mod h1:ZKKcpwF0aLYfkHFCjillCKaTK/yBkEDHTDXCFY6AS9Y= +k8s.io/api v0.37.0 h1:Z//Vj9N7RA/yS2sDmxyeo7h+RR4zbUrd2vrd3Z0TbB4= +k8s.io/api v0.37.0/go.mod h1:LKXgcJWMc+f4OLbP5SFR8rulEg07zZhpi/zMULiBImk= +k8s.io/apiextensions-apiserver v0.37.0 h1:zRMQ3+/LIE5oZ0tVvXwYHC+dIkSP5cjNWju7AZU1LOI= +k8s.io/apiextensions-apiserver v0.37.0/go.mod h1:HU0PfSBwchHL5iDau6jjt9zU6ryWkDDlaVUiq91NK80= +k8s.io/apimachinery v0.37.0 h1:Np2AbDtf8x6RDHiD8T9LbKJ9gaegeVNa8yNm5FuGKm0= +k8s.io/apimachinery v0.37.0/go.mod h1:RN3nhprFSCxOi5Selxd7oMTXOe/c+ZbcE7Im+TS2zkE= +k8s.io/client-go v0.37.0 h1:nsN31fy8wBySuZ+QRnKmrjRSQLOG2rvoGN0tKd12zhQ= +k8s.io/client-go v0.37.0/go.mod h1:FcGqw+Ll/gNQiq+nPGY1Oyt9y7SgDh1d3MW3RFDEbn0= k8s.io/klog/v2 v2.140.0 h1:Tf+J3AH7xnUzZyVVXhTgGhEKnFqye14aadWv7bzXdzc= k8s.io/klog/v2 v2.140.0/go.mod h1:o+/RWfJ6PwpnFn7OyAG3QnO47BFsymfEfrz6XyYSSp0= -k8s.io/kube-openapi v0.0.0-20260427204847-8949caaa1199 h1:sWu4Td5mgJlwunsUydnhKEAfNUHM7hm1wfKEQmD7G5c= -k8s.io/kube-openapi v0.0.0-20260427204847-8949caaa1199/go.mod h1:uGBT7iTA6c6MvqUvSXIaYZo9ukscABYi2btjhvgKGZ0= -k8s.io/utils v0.0.0-20260319190234-28399d86e0b5 h1:kBawHLSnx/mYHmRnNUf9d4CpjREbeZuxoSGOX/J+aYM= -k8s.io/utils v0.0.0-20260319190234-28399d86e0b5/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk= +k8s.io/kube-openapi v0.0.0-20260721132016-d427ff9ee9ad h1:oXImqH8mQNk7PmvzKhmN3ddJoY6OnyM225MXwGHPm0A= +k8s.io/kube-openapi v0.0.0-20260721132016-d427ff9ee9ad/go.mod h1:0/mqHCVhlumdJ3BhCfnjSZQE037nAhNodh1/hK0T8/I= +k8s.io/utils v0.0.0-20260626114624-be93311217bd h1:Ea7fgQ5we8Y9T0OX5o0dAHzQOBRI07D/dEYRaB9ZZEs= +k8s.io/utils v0.0.0-20260626114624-be93311217bd/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk= pgregory.net/rapid v1.2.0 h1:keKAYRcjm+e1F0oAuU5F5+YPAWcyxNNRK2wud503Gnk= pgregory.net/rapid v1.2.0/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04= -sigs.k8s.io/controller-runtime v0.24.0 h1:Ck6N2LdS8Lovy1o25BB4r1xjvLEKUl1s2o9kU+KWDE4= -sigs.k8s.io/controller-runtime v0.24.0/go.mod h1:vFkfY5fGt5xAC/sKb8IBFKgWPNKG9OUG29dR8Y2wImw= +sigs.k8s.io/controller-runtime v0.25.1 h1:BKgU9OeE8xv8EbbM8cY0NVzTQs35rokkdq1jh12fMb4= +sigs.k8s.io/controller-runtime v0.25.1/go.mod h1:4QqLdT6z/L6Olj8JJCtvztid4/fnIiYsfaTFScegctc= sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= -sigs.k8s.io/kind v0.31.0 h1:UcT4nzm+YM7YEbqiAKECk+b6dsvc/HRZZu9U0FolL1g= -sigs.k8s.io/kind v0.31.0/go.mod h1:FSqriGaoTPruiXWfRnUXNykF8r2t+fHtK0P0m1AbGF8= +sigs.k8s.io/kind v0.33.0 h1:AjvDv3vOygb/VKLVQW87lfktIBzkxR8Ump9DjxC8+Lk= +sigs.k8s.io/kind v0.33.0/go.mod h1:FSqriGaoTPruiXWfRnUXNykF8r2t+fHtK0P0m1AbGF8= sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= -sigs.k8s.io/structured-merge-diff/v6 v6.4.0 h1:qmp2e3ZfFi1/jJbDGpD4mt3wyp6PE1NfKHCYLqgNQJo= -sigs.k8s.io/structured-merge-diff/v6 v6.4.0/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= +sigs.k8s.io/structured-merge-diff/v6 v6.4.2 h1:qdOxHwrl2Kaag1aQEarlYcOA9vSyGCp3CIki3aW8c4Q= +sigs.k8s.io/structured-merge-diff/v6 v6.4.2/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4= diff --git a/operator/tools/operator-sdk/go.mod b/operator/tools/operator-sdk/go.mod index 017715960a436..6c414de8ca28a 100644 --- a/operator/tools/operator-sdk/go.mod +++ b/operator/tools/operator-sdk/go.mod @@ -28,7 +28,7 @@ require ( github.com/chai2010/gettext-go v1.0.2 // indirect github.com/clipperhouse/uax29/v2 v2.6.0 // indirect github.com/containerd/cgroups/v3 v3.1.2 // indirect - github.com/containerd/containerd v1.7.35 // indirect + github.com/containerd/containerd v1.7.36 // indirect github.com/containerd/containerd/api v1.10.0 // indirect github.com/containerd/continuity v0.4.5 // indirect github.com/containerd/errdefs v1.0.0 // indirect @@ -62,10 +62,10 @@ require ( github.com/go-errors/errors v1.4.2 // indirect github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect github.com/go-git/go-billy/v5 v5.9.0 // indirect - github.com/go-git/go-git/v5 v5.19.1 // indirect + github.com/go-git/go-git/v5 v5.19.2 // indirect github.com/go-gorp/gorp/v3 v3.1.0 // indirect github.com/go-jose/go-jose/v4 v4.1.4 // indirect - github.com/go-logr/logr v1.4.3 // indirect + github.com/go-logr/logr v1.4.4 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-openapi/jsonpointer v0.22.4 // indirect github.com/go-openapi/jsonreference v0.21.4 // indirect @@ -91,7 +91,7 @@ require ( github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/google/btree v1.1.3 // indirect - github.com/google/cel-go v0.29.0 // indirect + github.com/google/cel-go v0.31.0 // indirect github.com/google/gnostic-models v0.7.1 // indirect github.com/google/go-cmp v0.7.0 // indirect github.com/google/go-containerregistry v0.21.5 // indirect @@ -135,7 +135,7 @@ require ( github.com/moby/sys/capability v0.4.0 // indirect github.com/moby/sys/mountinfo v0.7.2 // indirect github.com/moby/sys/sequential v0.6.0 // indirect - github.com/moby/sys/user v0.4.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/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect @@ -194,13 +194,13 @@ require ( go.opencensus.io v0.24.0 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 // indirect - go.opentelemetry.io/otel v1.44.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.42.0 // indirect - go.opentelemetry.io/otel/metric v1.44.0 // indirect - go.opentelemetry.io/otel/sdk v1.44.0 // indirect - go.opentelemetry.io/otel/trace v1.44.0 // indirect - go.opentelemetry.io/proto/otlp v1.10.0 // indirect + go.opentelemetry.io/otel v1.45.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.45.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.45.0 // indirect + go.opentelemetry.io/otel/metric v1.45.0 // indirect + go.opentelemetry.io/otel/sdk v1.45.0 // indirect + go.opentelemetry.io/otel/trace v1.45.0 // indirect + go.opentelemetry.io/proto/otlp v1.11.0 // indirect go.podman.io/common v0.66.1 // indirect go.podman.io/image/v5 v5.38.0 // indirect go.podman.io/storage v1.61.0 // indirect @@ -218,8 +218,8 @@ require ( golang.org/x/time v0.15.0 // indirect golang.org/x/tools v0.48.0 // indirect google.golang.org/genproto v0.0.0-20260319201613-d00831a3d3e7 // indirect - 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/genproto/googleapis/api v0.0.0-20260803160001-6ac0973c030d // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260803160001-6ac0973c030d // indirect google.golang.org/grpc v1.83.2 // indirect google.golang.org/protobuf v1.36.11 // indirect gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect @@ -239,7 +239,7 @@ require ( k8s.io/kube-openapi v0.0.0-20260127142750-a19766b6e2d4 // indirect k8s.io/kubectl v0.35.1 // indirect k8s.io/utils v0.0.0-20260108192941-914a6e750570 // indirect - oras.land/oras-go/v2 v2.6.0 // indirect + oras.land/oras-go/v2 v2.6.2 // indirect sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.34.0 // indirect sigs.k8s.io/controller-runtime v0.23.1 // indirect sigs.k8s.io/controller-tools v0.20.0 // indirect diff --git a/operator/tools/operator-sdk/go.sum b/operator/tools/operator-sdk/go.sum index c16a85d690c9e..790d026981d54 100644 --- a/operator/tools/operator-sdk/go.sum +++ b/operator/tools/operator-sdk/go.sum @@ -58,8 +58,8 @@ github.com/clipperhouse/uax29/v2 v2.6.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsV github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/containerd/cgroups/v3 v3.1.2 h1:OSosXMtkhI6Qove637tg1XgK4q+DhR0mX8Wi8EhrHa4= github.com/containerd/cgroups/v3 v3.1.2/go.mod h1:PKZ2AcWmSBsY/tJUVhtS/rluX0b1uq1GmPO1ElCmbOw= -github.com/containerd/containerd v1.7.35 h1:7AU2T1qI2OdNBmKkreWZ7kASHUNFItN5Hgejd9sY938= -github.com/containerd/containerd v1.7.35/go.mod h1:ozI//0TomTCLPhQREnx0IXDIQMg+Fk7yTtg9fNvU8EQ= +github.com/containerd/containerd v1.7.36 h1:HyMsOG5kmp1LQsGzqwI6Ts06T4VpYZbszfDZYB0bW5w= +github.com/containerd/containerd v1.7.36/go.mod h1:ozI//0TomTCLPhQREnx0IXDIQMg+Fk7yTtg9fNvU8EQ= github.com/containerd/containerd/api v1.10.0 h1:5n0oHYVBwN4VhoX9fFykCV9dF1/BvAXeg2F8W6UYq1o= github.com/containerd/containerd/api v1.10.0/go.mod h1:NBm1OAk8ZL+LG8R0ceObGxT5hbUYj7CzTmR3xh0DlMM= github.com/containerd/continuity v0.4.5 h1:ZRoN1sXq9u7V6QoHMcVWGhOwDFqZ4B9i5H6un1Wh0x4= @@ -154,15 +154,15 @@ github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66D github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= github.com/go-git/go-billy/v5 v5.9.0 h1:jItGXszUDRtR/AlferWPTMN4j38BQ88XnXKbilmmBPA= github.com/go-git/go-billy/v5 v5.9.0/go.mod h1:jCnQMLj9eUgGU7+ludSTYoZL/GGmii14RxKFj7ROgHw= -github.com/go-git/go-git/v5 v5.19.1 h1:nX27AnaU43/K5bKktKwgBmR9lawoYVe1Ckg0rgzzN00= -github.com/go-git/go-git/v5 v5.19.1/go.mod h1:Pb1v0c7/g8aGQJwx9Us09W85yGoyvSwuhEGMH7zjDKQ= +github.com/go-git/go-git/v5 v5.19.2 h1:wkfn7vOlUBu8ivAWKBWisTiwJK4jYHzTF8Ndv1LyGqY= +github.com/go-git/go-git/v5 v5.19.2/go.mod h1:QqCBE1EFN5ddFmrliLQ3/ntRCUjZU3EJuwuB/jWEHjk= github.com/go-gorp/gorp/v3 v3.1.0 h1:ItKF/Vbuj31dmV4jxA1qblpSwkl9g1typ24xoe70IGs= github.com/go-gorp/gorp/v3 v3.1.0/go.mod h1:dLEjIyyRNiXvNZ8PSmzpt1GsWAUK8kjVhEpjH8TixEw= github.com/go-jose/go-jose/v4 v4.1.4 h1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA= github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= -github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.4 h1:tG4xh9yMsRCAiodLVTxyrkzSZ9+o0L1Kg/+cPVcbP/8= +github.com/go-logr/logr v1.4.4/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-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= @@ -239,8 +239,8 @@ github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= -github.com/google/cel-go v0.29.0 h1:fEG+Ja3YRwNOqnQxTyJwoByAUAvTuxUGiro/jhrm4F4= -github.com/google/cel-go v0.29.0/go.mod h1:X0bD6iVNR8pkROSOoHVdgTkzmRcosof7WQqCD6wcMc8= +github.com/google/cel-go v0.31.0 h1:H0bhpFTqOvmHrBGrWKp7ZlhBm5Hh8PYUEXnwxT1LL7A= +github.com/google/cel-go v0.31.0/go.mod h1:X0bD6iVNR8pkROSOoHVdgTkzmRcosof7WQqCD6wcMc8= github.com/google/gnostic-models v0.7.1 h1:SisTfuFKJSKM5CPZkffwi6coztzzeYUhc3v4yxLWH8c= github.com/google/gnostic-models v0.7.1/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= @@ -356,8 +356,8 @@ github.com/moby/sys/mountinfo v0.7.2 h1:1shs6aH5s4o5H2zQLn796ADW1wMrIwHsyJ2v9Kou github.com/moby/sys/mountinfo v0.7.2/go.mod h1:1YOa8w8Ih7uW0wALDUgT1dTTSBrZ+HiBLGws92L2RU4= github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU= github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko= -github.com/moby/sys/user v0.4.0 h1:jhcMKit7SA80hivmFJcbB1vqmw//wU61Zdui2eQXuMs= -github.com/moby/sys/user v0.4.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs= +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= @@ -527,8 +527,8 @@ go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.6 go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0/go.mod h1:NoUCKYWK+3ecatC4HjkRktREheMeEtrXoQxrqYFeHSc= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 h1:OyrsyzuttWTSur2qN/Lm0m2a8yqyIjUVBZcxFPuXq2o= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0/go.mod h1:C2NGBr+kAB4bk3xtMXfZ94gqFDtg/GkI7e9zqGh5Beg= -go.opentelemetry.io/otel v1.44.0 h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU= -go.opentelemetry.io/otel v1.44.0/go.mod h1:BMgjTHL9WPRlRjL2oZCBTL4whCGtXch2H4BhOPIAyYc= +go.opentelemetry.io/otel v1.45.0 h1:pdrWmLHofpubmArBv1LgFSv1Z0Ie/ppdZzu+kUN5EeU= +go.opentelemetry.io/otel v1.45.0/go.mod h1:XZxIqPapzEYnhNSScF5DIqXhm/rYi0FzCe2XddAwZfQ= go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.18.0 h1:deI9UQMoGFgrg5iLPgzueqFPHevDl+28YKfSpPTI6rY= go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.18.0/go.mod h1:PFx9NgpNUKXdf7J4Q3agRxMs3Y07QhTCVipKmLsMKnU= go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.19.0 h1:HIBTQ3VO5aupLKjC90JgMqpezVXwFuq6Ryjn0/izoag= @@ -537,10 +537,10 @@ go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.42.0 h1:MdK go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.42.0/go.mod h1:RolT8tWtfHcjajEH5wFIZ4Dgh5jpPdFXYV9pTAk/qjc= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.43.0 h1:w1K+pCJoPpQifuVpsKamUdn9U0zM3xUziVOqsGksUrY= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.43.0/go.mod h1:HBy4BjzgVE8139ieRI75oXm3EcDN+6GhD88JT1Kjvxg= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0 h1:88Y4s2C8oTui1LGM6bTWkw0ICGcOLCAI5l6zsD1j20k= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0/go.mod h1:Vl1/iaggsuRlrHf/hfPJPvVag77kKyvrLeD10kpMl+A= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.42.0 h1:zWWrB1U6nqhS/k6zYB74CjRpuiitRtLLi68VcgmOEto= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.42.0/go.mod h1:2qXPNBX1OVRC0IwOnfo1ljoid+RD0QK3443EaqVlsOU= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.45.0 h1:QRefszxJmfPdjXUUm3j6iDzY03mTPXMjqErFqQ67vUg= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.45.0/go.mod h1:Tiz03lTBVBrm7eWZBOidzEaYaJa8tjwGUGv6d8mlTyk= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.45.0 h1:fG5MCxGz8+2VtrN/WgqSpJFctVz24gpxj8CxkKmc8Ww= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.45.0/go.mod h1:BmAYTn+3ysbRe+IU2msxmf5Rx3g6DHvex+tWI3LdhYI= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0 h1:3iZJKlCZufyRzPzlQhUIWVmfltrXuGyfjREgGP3UUjc= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0/go.mod h1:/G+nUPfhq2e+qiXMGxMwumDrP5jtzU+mWN7/sjT2rak= go.opentelemetry.io/otel/exporters/prometheus v0.64.0 h1:g0LRDXMX/G1SEZtK8zl8Chm4K6GBwRkjPKE36LxiTYs= @@ -553,18 +553,18 @@ go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.42.0 h1:s/1iRkCKDfhlh1J go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.42.0/go.mod h1:UI3wi0FXg1Pofb8ZBiBLhtMzgoTm1TYkMvn71fAqDzs= go.opentelemetry.io/otel/log v0.19.0 h1:KUZs/GOsw79TBBMfDWsXS+KZ4g2Ckzksd1ymzsIEbo4= go.opentelemetry.io/otel/log v0.19.0/go.mod h1:5DQYeGmxVIr4n0/BcJvF4upsraHjg6vudJJpnkL6Ipk= -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.44.0 h1:nHYwb9lK+fJPU/dnT6s7W7Z8itMWyqrnVfbheVYrZ58= -go.opentelemetry.io/otel/sdk v1.44.0/go.mod h1:Osuydd3Se74nqjAKxid74N5eC+jfEqfTegHRnq58oK0= +go.opentelemetry.io/otel/metric v1.45.0 h1:7Eg1uH7CJ5cXv9is6tnBe1FI6rj1nwUdbFypRm3br/M= +go.opentelemetry.io/otel/metric v1.45.0/go.mod h1:HAPbm1nd3p1PmFH7v2dR+6BjXxw+Lq4a2+pndMAm08s= +go.opentelemetry.io/otel/sdk v1.45.0 h1:4VVSMgQ83dUgW2aoX5f6JgLvHwIvzcuLnF9lUdCSpCw= +go.opentelemetry.io/otel/sdk v1.45.0/go.mod h1:Sr40LgXV7DsKMMJMKOhUWOgMWTfAaqvm2kF0g7ilwuA= go.opentelemetry.io/otel/sdk/log v0.19.0 h1:scYVLqT22D2gqXItnWiocLUKGH9yvkkeql5dBDiXyko= go.opentelemetry.io/otel/sdk/log v0.19.0/go.mod h1:vFBowwXGLlW9AvpuF7bMgnNI95LiW10szrOdvzBHlAg= -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= -go.opentelemetry.io/proto/otlp v1.10.0 h1:IQRWgT5srOCYfiWnpqUYz9CVmbO8bFmKcwYxpuCSL2g= -go.opentelemetry.io/proto/otlp v1.10.0/go.mod h1:/CV4QoCR/S9yaPj8utp3lvQPoqMtxXdzn7ozvvozVqk= +go.opentelemetry.io/otel/sdk/metric v1.45.0 h1:oVFszMfyj1Am6s24Vtc7wBb8BKLcwepJjNEYILuiE3o= +go.opentelemetry.io/otel/sdk/metric v1.45.0/go.mod h1:vUWUxDZvu1WVRj8JA8S0AdhsPrZoDpA2DdZauIh4mDA= +go.opentelemetry.io/otel/trace v1.45.0 h1:l/mP6Uv7oNO7/TblbhpbgMidxhq1uO/rPsikOyVhxag= +go.opentelemetry.io/otel/trace v1.45.0/go.mod h1:qoJJA2xNMnxRrdISU/kLtfUH2wNeQbiv+jhs/CxI8bc= +go.opentelemetry.io/proto/otlp v1.11.0 h1:5rrYs0Ykyj50sdU/JU0x8etU+LubXWb+gED6TbEdMIk= +go.opentelemetry.io/proto/otlp v1.11.0/go.mod h1:SmVizdCOAm3XBtG1g1NnOdhW6jtddT72hLMhv8VwA8E= go.podman.io/common v0.66.1 h1:zDyd4HhVgQAN8LupBHCnhtM3FEOJ9DwmThjulXZq2qA= go.podman.io/common v0.66.1/go.mod h1:aNd2a0S7pY+fx1X5kpQYuF4hbwLU8ZOccuVrhu7h1Xc= go.podman.io/image/v5 v5.38.0 h1:aUKrCANkPvze1bnhLJsaubcfz0d9v/bSDLnwsXJm6G4= @@ -713,10 +713,10 @@ google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98 google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= 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-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/genproto/googleapis/api v0.0.0-20260803160001-6ac0973c030d h1:FarXi840EJWSHYTN3ERkADbPWjl307+FGrA22KAVjjc= +google.golang.org/genproto/googleapis/api v0.0.0-20260803160001-6ac0973c030d/go.mod h1:K/+WGbmBY7aNW1HDw1fJnKYo10i0DkAX6pows00dLig= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260803160001-6ac0973c030d h1:IL4hdHzcUv2l/gcg98/Rj3FbtE6axwqslOW8SW0C+S0= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260803160001-6ac0973c030d/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= @@ -780,8 +780,8 @@ k8s.io/kubectl v0.35.1 h1:zP3Er8C5i1dcAFUMh9Eva0kVvZHptXIn/+8NtRWMxwg= k8s.io/kubectl v0.35.1/go.mod h1:cQ2uAPs5IO/kx8R5s5J3Ihv3VCYwrx0obCXum0CvnXo= k8s.io/utils v0.0.0-20260108192941-914a6e750570 h1:JT4W8lsdrGENg9W+YwwdLJxklIuKWdRm+BC+xt33FOY= k8s.io/utils v0.0.0-20260108192941-914a6e750570/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk= -oras.land/oras-go/v2 v2.6.0 h1:X4ELRsiGkrbeox69+9tzTu492FMUu7zJQW6eJU+I2oc= -oras.land/oras-go/v2 v2.6.0/go.mod h1:magiQDfG6H1O9APp+rOsvCPcW1GD2MM7vgnKY0Y+u1o= +oras.land/oras-go/v2 v2.6.2 h1:N04RXngAp1LJKTG6ifz3xHPipasEkWr+hFmInja5YKo= +oras.land/oras-go/v2 v2.6.2/go.mod h1:PlTtg4JTDJkDe8yVHpM2wz7/YDc00GVas+i4jAW2TZ4= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.34.0 h1:hSfpvjjTQXQY2Fol2CS0QHMNs/WI1MOSGzCm1KhM5ec= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.34.0/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw= sigs.k8s.io/controller-runtime v0.23.1 h1:TjJSM80Nf43Mg21+RCy3J70aj/W6KyvDtOlpKf+PupE= diff --git a/operator/tools/yq/go.mod b/operator/tools/yq/go.mod index 73b47acec8e29..ae946785ad2b6 100644 --- a/operator/tools/yq/go.mod +++ b/operator/tools/yq/go.mod @@ -2,7 +2,7 @@ module github.com/stackrox/rox/operator/tools/yq go 1.26.3 -require github.com/mikefarah/yq/v4 v4.53.6 +require github.com/mikefarah/yq/v4 v4.54.1 require ( github.com/a8m/envsubst v1.4.3 // indirect @@ -17,12 +17,12 @@ require ( github.com/goccy/go-json v0.10.6 // indirect github.com/goccy/go-yaml v1.19.2 // indirect github.com/google/go-cmp v0.6.0 // indirect - github.com/hashicorp/hcl/v2 v2.24.0 // indirect + github.com/hashicorp/hcl/v2 v2.25.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jinzhu/copier v0.4.0 // indirect - github.com/magiconair/properties v1.18.11 // indirect + github.com/magiconair/properties v1.18.12 // indirect github.com/mattn/go-colorable v0.1.14 // indirect - github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-isatty v0.0.24 // indirect github.com/mitchellh/go-wordwrap v1.0.1 // indirect github.com/pelletier/go-toml/v2 v2.4.3 // indirect github.com/spf13/cobra v1.10.2 // indirect @@ -30,10 +30,10 @@ require ( github.com/yuin/gopher-lua v1.1.2 // indirect github.com/zclconf/go-cty v1.19.0 // indirect go.yaml.in/yaml/v4 v4.0.0-rc.6 // indirect - golang.org/x/mod v0.40.0 // indirect - golang.org/x/net v0.58.0 // indirect - 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/mod v0.41.0 // indirect + golang.org/x/net v0.59.0 // indirect + golang.org/x/sync v0.23.0 // indirect + golang.org/x/sys v0.48.0 // indirect + golang.org/x/text v0.42.0 // indirect golang.org/x/tools v0.49.0 // indirect ) diff --git a/operator/tools/yq/go.sum b/operator/tools/yq/go.sum index 6b0189e7efba9..81dbe3c8b66ed 100644 --- a/operator/tools/yq/go.sum +++ b/operator/tools/yq/go.sum @@ -32,22 +32,22 @@ github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM= github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/hashicorp/hcl/v2 v2.24.0 h1:2QJdZ454DSsYGoaE6QheQZjtKZSUs9Nh2izTWiwQxvE= -github.com/hashicorp/hcl/v2 v2.24.0/go.mod h1:oGoO1FIQYfn/AgyOhlg9qLC6/nOJPX3qGbkZpYAcqfM= +github.com/hashicorp/hcl/v2 v2.25.0 h1:HmmQVYRny4MaBo4b20TjmL46wyuUxpnMWkPZ4+NTbWk= +github.com/hashicorp/hcl/v2 v2.25.0/go.mod h1:vR+FKETxoZAmRlHgFfKmuqivj+C4Izm/c66XkmZ3r7M= github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8= github.com/jinzhu/copier v0.4.0/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg= -github.com/magiconair/properties v1.18.11 h1:j5ozYZl0zCjG7ahMDH0GWIobOvvUzT0BdAguG0ViKy0= -github.com/magiconair/properties v1.18.11/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/magiconair/properties v1.18.12 h1:sT9zQpvTB3B4gzrX0tmZNTEaGyg8Zw55MFYRE32Mr9I= +github.com/magiconair/properties v1.18.12/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= -github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= -github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mikefarah/yq/v4 v4.53.6 h1:5vB15LTZRvrF6z/metQF31bmF63SjDzCKKX8o8UemQQ= -github.com/mikefarah/yq/v4 v4.53.6/go.mod h1:3tmDcCSTOTUFa5Z1X8R4j0H1kauIwNo2wjKq9sLyAh4= +github.com/mattn/go-isatty v0.0.24 h1:tGZZoVgT/KiqK1c8ocVLeDS8BSWMRd47J3Lbz7vsReI= +github.com/mattn/go-isatty v0.0.24/go.mod h1:nMCL3Zebbrt45jsMDgnfIwz6ydEQApk5oEI3HqDio6A= +github.com/mikefarah/yq/v4 v4.54.1 h1:wFk+RhKFcx6fU6tkjC9z069vLTL4uDoXi9R4SytFB8s= +github.com/mikefarah/yq/v4 v4.54.1/go.mod h1:2s/axTs7Quuo86zZrkN/2QjflxEtFay8aynJsuNT6Mw= github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= github.com/pelletier/go-toml/v2 v2.4.3 h1:GTRvJQutkOSftxIFD5xw9aepkYNuPWmVJpffdDPYVpY= @@ -74,17 +74,16 @@ github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940/go.mod h1:CmB go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= go.yaml.in/yaml/v4 v4.0.0-rc.6 h1:1h7H1ohdUh93/FyE4YaDa1Zh64K6VVbjF4K6WUxMtH4= go.yaml.in/yaml/v4 v4.0.0-rc.6/go.mod h1:aZqd9kCMsGL7AuUv/m/PvWLdg5sjJsZ4oHDEnfPPfY0= -golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs= -golang.org/x/mod v0.40.0/go.mod h1:0/weTWkPWGBikyTWAX3dkjVztMmBA5hM0DH6BElSupE= -golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To= -golang.org/x/net v0.58.0/go.mod h1:YwCddHnFlT7eLQqVprV19OnhLGtc5xOKgE0RyqgfWAU= -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.6.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= -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/mod v0.41.0 h1:qJmnOUb4YB+FsEuM3HcWucdZASCPGhsX6uljO6pog0c= +golang.org/x/mod v0.41.0/go.mod h1:Ek9pY8RKWXwsWvd3rQiHYtMqkjSUV+s1Rj7j4H5Ur6o= +golang.org/x/net v0.59.0 h1:5zfYln+w5XCxwrnMMJPufRgNoXEaGxl0wo5GqPXyues= +golang.org/x/net v0.59.0/go.mod h1:2DA/G1UfVbCpQPeWTmMPGY7Cs2PkBkwu743bVX5PIVg= +golang.org/x/sync v0.23.0 h1:KameEIfc1IkluZyXWLn39Wd4tURc6GbCiISGiZm2bQk= +golang.org/x/sync v0.23.0/go.mod h1:sUUOizhqBxiL6pEWpqNLUiaJn1ShEbZ6BBqskPbjZm0= +golang.org/x/sys v0.48.0 h1:bbX/i/6MgT9BVLM9RT1thmxL04yeTAhbEz4SyadbXoo= +golang.org/x/sys v0.48.0/go.mod h1:hNLxWAXmnKAxqDtdwIYC4bM9oQPEecfsnNMuSxOs3og= +golang.org/x/text v0.42.0 h1:JbOZXgfeCPU9gacVtYliJqOhD+zhrEqK4LfdpmlUZqI= +golang.org/x/text v0.42.0/go.mod h1:ojzP1Z+2QtioaF8DTtO8K5q7JWVVYwZKenzujK0Zd0E= golang.org/x/tools v0.49.0 h1:3NI7VXzL9+1WZD52Dx2ttoPwD5DWrFGpl9mFZDlmisI= golang.org/x/tools v0.49.0/go.mod h1:SJNXV9DBKT0UbdttsQjbfJlAE/q+y36++zo3uL3N0Oo= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/pkg/auth/authproviders/provider_options.go b/pkg/auth/authproviders/provider_options.go index e7896739add1b..5409952ada564 100644 --- a/pkg/auth/authproviders/provider_options.go +++ b/pkg/auth/authproviders/provider_options.go @@ -166,3 +166,18 @@ func WithVisibility(visibility storage.Traits_Visibility) ProviderOption { return nil } } + +// WithOrigin sets the origin for the auth provider. +func WithOrigin(origin storage.Traits_Origin) ProviderOption { + return func(pr *providerImpl) error { + if pr.storedInfo == nil { + return errox.InvariantViolation.CausedBy("no storage data for auth provider") + } + if pr.storedInfo.GetTraits() != nil { + pr.storedInfo.Traits.Origin = origin + } else { + pr.storedInfo.Traits = &storage.Traits{Origin: origin} + } + return nil + } +} diff --git a/pkg/booleanpolicy/evaluator/base_evaluators.go b/pkg/booleanpolicy/evaluator/base_evaluators.go index 78fec49431158..d187c64eb5a64 100644 --- a/pkg/booleanpolicy/evaluator/base_evaluators.go +++ b/pkg/booleanpolicy/evaluator/base_evaluators.go @@ -252,7 +252,7 @@ func generateTimestampMatcher(value string, matchAll bool) (baseMatcherAndExtrac } } return func(instance reflect.Value) []valueMatchedPair { - ts, ok := instance.Interface().(*protocompat.Timestamp) + ts, ok := reflect.TypeAssert[*protocompat.Timestamp](instance) if !ok { return nil } @@ -335,7 +335,7 @@ func generateBoolMatcher(value string, _ reflect.Type, matchAll bool) (baseMatch } func generateIntMatcher(value string, fieldType reflect.Type, matchAll bool) (baseMatcherAndExtractor, error) { - if enum, ok := reflect.Zero(fieldType).Interface().(protoreflect.ProtoEnum); ok { + if enum, ok := reflect.TypeAssert[protoreflect.ProtoEnum](reflect.Zero(fieldType)); ok { return generateEnumMatcher(value, enum, matchAll) } if matchAll && value != "" { diff --git a/pkg/centralsensor/caps_list.go b/pkg/centralsensor/caps_list.go index 2cbfbdf727d5a..e594c6fd237bf 100644 --- a/pkg/centralsensor/caps_list.go +++ b/pkg/centralsensor/caps_list.go @@ -110,8 +110,8 @@ const ( // a new Sensor" from "old Sensor that predates VM telemetry fields." VirtualMachineTelemetryCap SensorCapability = "VirtualMachineTelemetry" - // WorkloadTypeExclusionCap identifies the capability of Sensor to honor - // Exclusion.exclude_by_type rules. Central strips those rules when syncing + // WorkloadKindExclusionCap identifies the capability of Sensor to honor + // Exclusion.exclude_by_kind rules. Central strips those rules when syncing // policies to Sensors that lack this capability. - WorkloadTypeExclusionCap SensorCapability = "WorkloadTypeExclusion" + WorkloadKindExclusionCap SensorCapability = "WorkloadKindExclusion" ) diff --git a/pkg/concurrency/value_stream.go b/pkg/concurrency/value_stream.go index 354be592cb9d3..c106a7d9faafb 100644 --- a/pkg/concurrency/value_stream.go +++ b/pkg/concurrency/value_stream.go @@ -101,7 +101,7 @@ func (i *valueStreamIterBase[T]) Done() <-chan struct{} { return i.nextC } -func (*valueStreamIterBase[T]) isValueStreamIter(T) {} //nolint:unused // This is required for generic magic +func (*valueStreamIterBase[T]) isValueStreamIter(T) {} type valueStreamStrictIter[T any] struct { valueStreamIterBase[T] diff --git a/pkg/declarativeconfig/context.go b/pkg/declarativeconfig/context.go index b507bbc34d561..658247c115a93 100644 --- a/pkg/declarativeconfig/context.go +++ b/pkg/declarativeconfig/context.go @@ -10,6 +10,7 @@ type originCheckerKey struct{} const allowOnlyDeclarativeOperations = 0 const allowModifyDeclarativeOrImperative = 1 +const allowDefaultOperations = 2 // WithModifyDeclarativeResource returns a context that is a child of the given context and allows to modify // proto messages with the traits origin == DECLARATIVE. @@ -23,6 +24,13 @@ func WithModifyDeclarativeOrImperative(ctx context.Context) context.Context { return context.WithValue(ctx, originCheckerKey{}, allowModifyDeclarativeOrImperative) } +// WithModifyDefaultResource returns a context that allows creating and modifying +// proto messages with the traits origin == DEFAULT. This is intended for internal +// seeding of system-managed objects that should not be user-modifiable. +func WithModifyDefaultResource(ctx context.Context) context.Context { + return context.WithValue(ctx, originCheckerKey{}, allowDefaultOperations) +} + // ResourceWithTraits is a common interface for proto messages containing storage.Traits. type ResourceWithTraits interface { GetTraits() *storage.Traits @@ -37,11 +45,14 @@ func HasModifyDeclarativeResourceKey(ctx context.Context) bool { // CanModifyResource returns whether context holder is allowed to modify resource. func CanModifyResource(ctx context.Context, resource ResourceWithTraits) bool { - if ctx.Value(originCheckerKey{}) == allowOnlyDeclarativeOperations { + switch ctx.Value(originCheckerKey{}) { + case allowOnlyDeclarativeOperations: return IsDeclarativeOrigin(resource) - } - if ctx.Value(originCheckerKey{}) == allowModifyDeclarativeOrImperative { + case allowModifyDeclarativeOrImperative: return IsDeclarativeOrigin(resource) || IsImperativeOrigin(resource) || IsEphemeralOrigin(resource) + case allowDefaultOperations: + return IsDefaultOrigin(resource) + default: + return IsImperativeOrigin(resource) || IsEphemeralOrigin(resource) } - return IsImperativeOrigin(resource) || IsEphemeralOrigin(resource) } diff --git a/pkg/declarativeconfig/context_test.go b/pkg/declarativeconfig/context_test.go index fd850da97f5b4..8444aedc0de60 100644 --- a/pkg/declarativeconfig/context_test.go +++ b/pkg/declarativeconfig/context_test.go @@ -24,6 +24,7 @@ func TestContext(t *testing.T) { ctx := context.Background() declarativeCtx := WithModifyDeclarativeResource(ctx) declarativeOrImperativeCtx := WithModifyDeclarativeOrImperative(ctx) + defaultCtx := WithModifyDefaultResource(ctx) // 1.1. empty context can modify imperative origin assert.True(t, CanModifyResource(ctx, imperativeResource)) // 1.2. empty context can modify ephemeral origin @@ -48,4 +49,10 @@ func TestContext(t *testing.T) { assert.True(t, CanModifyResource(declarativeOrImperativeCtx, ephemeralResource)) // 9. context.WithModifyDeclarativeOrImperative can't modify default origin assert.False(t, CanModifyResource(declarativeOrImperativeCtx, defaultResource)) + // 10. context.WithModifyDefaultResource can modify default origin + assert.True(t, CanModifyResource(defaultCtx, defaultResource)) + // 11.1. context.WithModifyDefaultResource can't modify imperative origin + assert.False(t, CanModifyResource(defaultCtx, imperativeResource)) + // 11.2. context.WithModifyDefaultResource can't modify declarative origin + assert.False(t, CanModifyResource(defaultCtx, declarativeResource)) } diff --git a/pkg/declarativeconfig/origin.go b/pkg/declarativeconfig/origin.go index f23b14a6a7cd0..7b406e0a7bd37 100644 --- a/pkg/declarativeconfig/origin.go +++ b/pkg/declarativeconfig/origin.go @@ -28,3 +28,8 @@ func IsEphemeralOrigin(resource ResourceWithTraits) bool { func IsImperativeOrigin(resource ResourceWithTraits) bool { return resource.GetTraits().GetOrigin() == storage.Traits_IMPERATIVE } + +// IsDefaultOrigin returns whether origin of resource is default or not. +func IsDefaultOrigin(resource ResourceWithTraits) bool { + return resource.GetTraits().GetOrigin() == storage.Traits_DEFAULT +} diff --git a/pkg/delegatedregistry/delegator.go b/pkg/delegatedregistry/delegator.go index a22466ebb3f50..0b22df893ad40 100644 --- a/pkg/delegatedregistry/delegator.go +++ b/pkg/delegatedregistry/delegator.go @@ -28,5 +28,5 @@ type Delegator interface { // ValidateCluster returns nil if a cluster is a valid target for delegation, returns an // error otherwise. - ValidateCluster(clusterID string) error + ValidateCluster(ctx context.Context, clusterID string) error } diff --git a/pkg/delegatedregistry/mocks/delegator.go b/pkg/delegatedregistry/mocks/delegator.go index b9e01fe6b7022..6caf1c8279489 100644 --- a/pkg/delegatedregistry/mocks/delegator.go +++ b/pkg/delegatedregistry/mocks/delegator.go @@ -88,15 +88,15 @@ func (mr *MockDelegatorMockRecorder) GetDelegateClusterID(ctx, imgName any) *gom } // ValidateCluster mocks base method. -func (m *MockDelegator) ValidateCluster(clusterID string) error { +func (m *MockDelegator) ValidateCluster(ctx context.Context, clusterID string) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ValidateCluster", clusterID) + ret := m.ctrl.Call(m, "ValidateCluster", ctx, clusterID) ret0, _ := ret[0].(error) return ret0 } // ValidateCluster indicates an expected call of ValidateCluster. -func (mr *MockDelegatorMockRecorder) ValidateCluster(clusterID any) *gomock.Call { +func (mr *MockDelegatorMockRecorder) ValidateCluster(ctx, clusterID any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ValidateCluster", reflect.TypeOf((*MockDelegator)(nil).ValidateCluster), clusterID) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ValidateCluster", reflect.TypeOf((*MockDelegator)(nil).ValidateCluster), ctx, clusterID) } diff --git a/pkg/detection/compiled_policy.go b/pkg/detection/compiled_policy.go index 9fedc810495f5..351f03c45caf6 100644 --- a/pkg/detection/compiled_policy.go +++ b/pkg/detection/compiled_policy.go @@ -9,9 +9,12 @@ import ( "github.com/stackrox/rox/pkg/booleanpolicy/augmentedobjs" "github.com/stackrox/rox/pkg/booleanpolicy/filter" "github.com/stackrox/rox/pkg/booleanpolicy/policyfields" + "github.com/stackrox/rox/pkg/features" + "github.com/stackrox/rox/pkg/kubernetes" "github.com/stackrox/rox/pkg/policies" "github.com/stackrox/rox/pkg/regexutils" "github.com/stackrox/rox/pkg/scopecomp" + "github.com/stackrox/rox/pkg/set" ) // CompiledPolicy is a compiled policy, which means it can match a policy, as well as check whether a policy is applicable. @@ -46,11 +49,14 @@ func newCompiledPolicy(policy *storage.Policy, clusterLabelProvider scopecomp.Cl exclusions := make([]*compiledExclusion, 0, len(policy.GetExclusions())) for _, w := range policy.GetExclusions() { - w, err := newCompiledExclusion(w) + if w.GetExcludeByKind() != nil && !features.PolicyWorkloadKindExclusion.Enabled() { + continue + } + cx, err := newCompiledExclusion(w) if err != nil { return nil, errors.Wrapf(err, "compiling exclusion list %+v for policy %s", w, policy.GetName()) } - exclusions = append(exclusions, w) + exclusions = append(exclusions, cx) } scopes := make([]*scopecomp.CompiledScope, 0, len(policy.GetScope())) @@ -477,6 +483,7 @@ type compiledExclusion struct { exclusion *storage.Exclusion deploymentNameMatcher regexutils.StringMatcher cs *scopecomp.CompiledScope + excludedKinds set.Set[string] } type alwaysFalseMatcher struct{} @@ -489,6 +496,18 @@ func newCompiledExclusion(exclusion *storage.Exclusion) (*compiledExclusion, err cx := &compiledExclusion{ exclusion: exclusion, } + if exclusion.GetExcludeByKind() != nil { + if features.PolicyWorkloadKindExclusion.Enabled() { + excludedKinds, err := kubernetesKindsForExcludeByKind(exclusion.GetExcludeByKind()) + if err != nil { + return nil, err + } + cx.excludedKinds = excludedKinds + } else { + cx.excludedKinds = set.NewSet[string]() + } + return cx, nil + } if name := exclusion.GetDeployment().GetName(); name != "" { deploymentNameMatcher, err := regexutils.CompileWholeStringMatcher(name, regexutils.Flags{CaseInsensitive: true}) if err != nil { @@ -514,11 +533,23 @@ func newCompiledExclusion(exclusion *storage.Exclusion) (*compiledExclusion, err return cx, nil } +// appliesToDeployments reports whether the exclusion can match a deployment. +// Image-only exclusions are applied to images, so they must not match deployments or audit events. +// Without this check, the nil deployment scope matches everything and the exclusion turns the policy off for every deployment. +// Kind exclusions have no deployment matcher, but they still apply to deployments of the selected kinds. +func (cw *compiledExclusion) appliesToDeployments() bool { + return cw.exclusion.GetDeployment() != nil || cw.excludedKinds != nil +} + func (cw *compiledExclusion) MatchesDeployment(ctx context.Context, deployment *storage.Deployment) bool { - if exclusionIsExpired(cw.exclusion) { + if !cw.appliesToDeployments() || exclusionIsExpired(cw.exclusion) { return false } + if cw.excludedKinds != nil { + return cw.excludedKinds.Contains(deployment.GetType()) + } + if cw.deploymentNameMatcher != nil && !cw.deploymentNameMatcher.MatchString(deployment.GetName()) { return false } @@ -527,7 +558,11 @@ func (cw *compiledExclusion) MatchesDeployment(ctx context.Context, deployment * } func (cw *compiledExclusion) MatchesAuditEvent(ctx context.Context, auditEvent *storage.KubernetesEvent) bool { - if exclusionIsExpired(cw.exclusion) { + if !cw.appliesToDeployments() || exclusionIsExpired(cw.exclusion) { + return false + } + // Kind exclusions apply to workload detections, not audit-log events. + if cw.excludedKinds != nil { return false } if !cw.cs.MatchesAuditEvent(ctx, auditEvent) { @@ -536,6 +571,25 @@ func (cw *compiledExclusion) MatchesAuditEvent(ctx context.Context, auditEvent * return true } +func kubernetesKindsForExcludeByKind(excludeByKind *storage.Exclusion_ExcludeByKind) (set.Set[string], error) { + kinds := excludeByKind.GetKinds() + if len(kinds) == 0 { + return nil, errors.New("exclude_by_kind must specify at least one workload kind") + } + kubernetesKinds := set.NewSet[string]() + for _, kind := range kinds { + switch kind { + case storage.Exclusion_CRON_JOB: + kubernetesKinds.Add(kubernetes.CronJob) + case storage.Exclusion_JOB: + kubernetesKinds.Add(kubernetes.Job) + default: + return nil, errors.Errorf("exclude_by_kind contains unknown workload kind %s", kind) + } + } + return kubernetesKinds, nil +} + // Predicate for deployments. type deploymentPredicate struct { exclusions []*compiledExclusion diff --git a/pkg/detection/compiled_policy_test.go b/pkg/detection/compiled_policy_test.go index f50720961ee0a..4ea21b0d5e949 100644 --- a/pkg/detection/compiled_policy_test.go +++ b/pkg/detection/compiled_policy_test.go @@ -6,10 +6,13 @@ import ( "testing" "github.com/stackrox/rox/generated/storage" + "github.com/stackrox/rox/pkg/booleanpolicy" "github.com/stackrox/rox/pkg/booleanpolicy/fieldnames" "github.com/stackrox/rox/pkg/booleanpolicy/policyversion" "github.com/stackrox/rox/pkg/features" "github.com/stackrox/rox/pkg/fixtures" + "github.com/stackrox/rox/pkg/kubernetes" + "github.com/stackrox/rox/pkg/testutils" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -64,9 +67,20 @@ func TestCompiledPolicyScopesAndExclusions(t *testing.T) { { desc: "only stackrox ns, but app=stackrox excluded", scopes: []*storage.Scope{stackRoxNSScope}, - exclusions: []*storage.Exclusion{{Deployment: &storage.Exclusion_Deployment{Scope: appStackRoxScope}}}, + exclusions: []*storage.Exclusion{{Matcher: &storage.Exclusion_Deployment_{Deployment: &storage.Exclusion_Deployment{Scope: appStackRoxScope}}}}, shouldApplyTo: []*storage.Deployment{stackRoxNSDep}, }, + { + desc: "image-only exclusion does not exclude any deployment", + exclusions: []*storage.Exclusion{{Image: &storage.Exclusion_Image{Name: "docker.io/library/unrelated"}}}, + shouldApplyTo: []*storage.Deployment{stackRoxNSDep, defaultNSDep, appStackRoxDep}, + }, + { + desc: "only stackrox ns, with an image-only exclusion", + scopes: []*storage.Scope{stackRoxNSScope}, + exclusions: []*storage.Exclusion{{Image: &storage.Exclusion_Image{Name: "docker.io/library/unrelated"}}}, + shouldApplyTo: []*storage.Deployment{stackRoxNSDep, appStackRoxDep}, + }, { desc: "only default ns", scopes: []*storage.Scope{defaultNSScope}, @@ -94,6 +108,60 @@ func TestCompiledPolicyScopesAndExclusions(t *testing.T) { } } +func TestCompiledPolicyWorkloadKindExclusion(t *testing.T) { + testutils.MustUpdateFeature(t, features.PolicyWorkloadKindExclusion, true) + + cronJob := newDeployment("CRONJOB") + cronJob.Name = "batch-cron" + cronJob.Type = kubernetes.CronJob + regular := newDeployment("DEPLOYMENT") + regular.Name = "web" + regular.Type = kubernetes.Deployment + + policy := constructPolicy(nil, []*storage.Exclusion{ + { + Matcher: &storage.Exclusion_ExcludeByKind_{ + ExcludeByKind: &storage.Exclusion_ExcludeByKind{ + Kinds: []storage.Exclusion_WorkloadKind{storage.Exclusion_CRON_JOB}, + }, + }, + }, + { + Matcher: &storage.Exclusion_Deployment_{ + Deployment: &storage.Exclusion_Deployment{Name: regular.GetName()}, + }, + }, + }) + compiled, err := CompilePolicy(policy, nil, nil) + require.NoError(t, err) + assert.False(t, compiled.AppliesTo(context.Background(), cronJob)) + assert.False(t, compiled.AppliesTo(context.Background(), regular)) + + other := newDeployment("OTHER") + other.Name = "other" + other.Type = kubernetes.DaemonSet + assert.True(t, compiled.AppliesTo(context.Background(), other)) +} + +func TestCompiledPolicyWorkloadKindExclusionFlagOff(t *testing.T) { + testutils.MustUpdateFeature(t, features.PolicyWorkloadKindExclusion, false) + + cronJob := newDeployment("CRONJOB") + cronJob.Type = kubernetes.CronJob + policy := constructPolicy(nil, []*storage.Exclusion{ + { + Matcher: &storage.Exclusion_ExcludeByKind_{ + ExcludeByKind: &storage.Exclusion_ExcludeByKind{ + Kinds: []storage.Exclusion_WorkloadKind{storage.Exclusion_CRON_JOB}, + }, + }, + }, + }) + compiled, err := CompilePolicy(policy, nil, nil) + require.NoError(t, err) + assert.True(t, compiled.AppliesTo(context.Background(), cronJob)) +} + // TestProcessAndFileAccessMatchers verifies that when a policy contains both Process and FileAccess fields, // only the file access matcher is created, not both matchers. func TestProcessAndFileAccessMatchers(t *testing.T) { @@ -276,3 +344,115 @@ func TestProcessAndFileAccessMatchers(t *testing.T) { }) } } + +func initAndRegularDeployment() (*storage.Deployment, []*storage.Image) { + dep := &storage.Deployment{ + Id: "dep", + Name: "dep", + Namespace: "ns", + Containers: []*storage.Container{ + { + Name: "init-setup", + Type: storage.ContainerType_INIT, + SecurityContext: &storage.SecurityContext{Privileged: true}, + Image: &storage.ContainerImage{Id: "init-img", Name: &storage.ImageName{FullName: "busybox:latest"}}, + }, + { + Name: "app", + Type: storage.ContainerType_REGULAR, + SecurityContext: &storage.SecurityContext{}, + Image: &storage.ContainerImage{Id: "app-img", Name: &storage.ImageName{FullName: "nginx:1.25"}}, + }, + }, + } + images := []*storage.Image{ + {Id: "init-img", Name: &storage.ImageName{FullName: "busybox:latest"}}, + {Id: "app-img", Name: &storage.ImageName{FullName: "nginx:1.25"}}, + } + return dep, images +} + +func TestEvaluationFilterSkipsContainers(t *testing.T) { + t.Setenv(features.EvaluationFilter.EnvVar(), "true") + + dep, images := initAndRegularDeployment() + ed := booleanpolicy.EnhancedDeployment{Deployment: dep, Images: images} + + skipInit := &storage.EvaluationFilter{ + SkipContainerTypes: []storage.ContainerType{storage.ContainerType_INIT}, + } + privilegedPolicy := &storage.Policy{ + PolicyVersion: policyversion.CurrentVersion().String(), + Name: "privileged", + LifecycleStages: []storage.LifecycleStage{storage.LifecycleStage_DEPLOY}, + PolicySections: []*storage.PolicySection{{ + PolicyGroups: []*storage.PolicyGroup{{ + FieldName: fieldnames.PrivilegedContainer, + Values: []*storage.PolicyValue{{Value: "true"}}, + }}, + }}, + EvaluationFilter: skipInit, + } + + compiled, err := CompilePolicy(privilegedPolicy, nil, nil) + require.NoError(t, err) + + violations, err := compiled.MatchAgainstDeployment(nil, ed) + require.NoError(t, err) + assert.Empty(t, violations.AlertViolations, "privileged init container is skipped") + + dep.Containers[0].SecurityContext.Privileged = false + dep.Containers[1].SecurityContext.Privileged = true + violations, err = compiled.MatchAgainstDeployment(nil, ed) + require.NoError(t, err) + require.Len(t, violations.AlertViolations, 1) + assert.Contains(t, violations.AlertViolations[0].GetMessage(), "app") + + skipRegular := privilegedPolicy.CloneVT() + skipRegular.EvaluationFilter.SkipContainerTypes = []storage.ContainerType{storage.ContainerType_REGULAR} + compiled, err = CompilePolicy(skipRegular, nil, nil) + require.NoError(t, err) + violations, err = compiled.MatchAgainstDeployment(nil, ed) + require.NoError(t, err) + assert.Empty(t, violations.AlertViolations, "privileged regular container is skipped") + + dep.Containers[0].SecurityContext.Privileged = true + dep.Containers[1].SecurityContext.Privileged = false + violations, err = compiled.MatchAgainstDeployment(nil, ed) + require.NoError(t, err) + require.Len(t, violations.AlertViolations, 1) + assert.Contains(t, violations.AlertViolations[0].GetMessage(), "init-setup") + + unfiltered := privilegedPolicy.CloneVT() + unfiltered.EvaluationFilter = nil + compiled, err = CompilePolicy(unfiltered, nil, nil) + require.NoError(t, err) + violations, err = compiled.MatchAgainstDeployment(nil, ed) + require.NoError(t, err) + require.NotEmpty(t, violations.AlertViolations) + assert.Contains(t, violations.AlertViolations[0].GetMessage(), "init-setup") + + processPolicy := &storage.Policy{ + PolicyVersion: policyversion.CurrentVersion().String(), + Name: "process", + LifecycleStages: []storage.LifecycleStage{storage.LifecycleStage_RUNTIME}, + EventSource: storage.EventSource_DEPLOYMENT_EVENT, + PolicySections: []*storage.PolicySection{{ + PolicyGroups: []*storage.PolicyGroup{{ + FieldName: fieldnames.ProcessName, + Values: []*storage.PolicyValue{{Value: "bash"}}, + }}, + }}, + EvaluationFilter: skipInit, + } + compiled, err = CompilePolicy(processPolicy, nil, nil) + require.NoError(t, err) + + appProcess := &storage.ProcessIndicator{ + ContainerName: "app", + Signal: &storage.ProcessSignal{Name: "bash", ExecFilePath: "/bin/bash"}, + } + violations, err = compiled.MatchAgainstDeploymentAndProcess(nil, ed, appProcess, true) + require.NoError(t, err) + assert.NotNil(t, violations.ProcessViolation) +} diff --git a/pkg/detection/deploytime/detector_impl_test.go b/pkg/detection/deploytime/detector_impl_test.go new file mode 100644 index 0000000000000..377ebbc6d353e --- /dev/null +++ b/pkg/detection/deploytime/detector_impl_test.go @@ -0,0 +1,138 @@ +package deploytime + +import ( + "context" + "testing" + "time" + + "github.com/stackrox/rox/generated/storage" + "github.com/stackrox/rox/pkg/booleanpolicy" + "github.com/stackrox/rox/pkg/booleanpolicy/fieldnames" + "github.com/stackrox/rox/pkg/booleanpolicy/policyversion" + "github.com/stackrox/rox/pkg/detection" + "github.com/stackrox/rox/pkg/protocompat" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +const ( + excludedImage = "registry.example.com/team/legacy-app:1.0" + vulnerableImage = "registry.example.com/team/payments:2.3" +) + +// oldCriticalCVEPolicy mirrors a "Block Critical CVE older than 180 days" policy that runs at +// both the build and deploy stages and excludes a single image. +func oldCriticalCVEPolicy(exclusions ...*storage.Exclusion) *storage.Policy { + return &storage.Policy{ + Id: "old-critical-cve", + Name: "Block Critical CVE older than 180 days", + PolicyVersion: policyversion.CurrentVersion().String(), + Severity: storage.Severity_CRITICAL_SEVERITY, + LifecycleStages: []storage.LifecycleStage{storage.LifecycleStage_BUILD, storage.LifecycleStage_DEPLOY}, + Exclusions: exclusions, + PolicySections: []*storage.PolicySection{{ + PolicyGroups: []*storage.PolicyGroup{ + {FieldName: fieldnames.Severity, Values: []*storage.PolicyValue{{Value: "CRITICAL"}}}, + {FieldName: fieldnames.DaysSinceImageFirstDiscovered, Values: []*storage.PolicyValue{{Value: "180"}}}, + }, + }}, + } +} + +func imageExclusion(fullName string) *storage.Exclusion { + return &storage.Exclusion{Image: &storage.Exclusion_Image{Name: fullName}} +} + +func imageWithOldCriticalCVE(t *testing.T, fullName string) *storage.Image { + firstSeen, err := protocompat.ConvertTimeToTimestampOrError(time.Now().AddDate(0, 0, -365)) + require.NoError(t, err) + return &storage.Image{ + Id: fullName + "-sha", + Name: &storage.ImageName{FullName: fullName}, + Scan: &storage.ImageScan{ + Components: []*storage.EmbeddedImageScanComponent{{ + Name: "openssl", + Version: "1.0.1", + Vulns: []*storage.EmbeddedVulnerability{{ + Cve: "CVE-2014-0160", + Severity: storage.VulnerabilitySeverity_CRITICAL_VULNERABILITY_SEVERITY, + FirstImageOccurrence: firstSeen, + }}, + }}, + }, + } +} + +func deploymentRunning(name string, image *storage.Image) booleanpolicy.EnhancedDeployment { + return booleanpolicy.EnhancedDeployment{ + Deployment: &storage.Deployment{ + Id: name + "-id", + Name: name, + Namespace: "shop", + ClusterId: "cluster-1", + Containers: []*storage.Container{{ + Name: name, + Image: &storage.ContainerImage{Id: image.GetId(), Name: image.GetName()}, + }}, + }, + Images: []*storage.Image{image}, + } +} + +func detectorFor(t *testing.T, policy *storage.Policy) Detector { + policySet := detection.NewPolicySet(nil, nil) + require.NoError(t, policySet.UpsertPolicy(policy)) + return NewDetector(policySet) +} + +// TestImageExclusionDoesNotDisableDeployTimeDetection reproduces the customer report: adding an +// image exclusion to a build and deploy policy stopped the policy from alerting on every deployment, +// including deployments that do not run the excluded image. +func TestImageExclusionDoesNotDisableDeployTimeDetection(t *testing.T) { + ctx := context.Background() + payments := deploymentRunning("payments", imageWithOldCriticalCVE(t, vulnerableImage)) + + t.Run("without an exclusion, the vulnerable deployment alerts", func(t *testing.T) { + alerts, err := detectorFor(t, oldCriticalCVEPolicy()).Detect(ctx, payments) + require.NoError(t, err) + assert.Len(t, alerts, 1) + }) + + t.Run("with an image exclusion, a deployment that does not run the excluded image still alerts", func(t *testing.T) { + policy := oldCriticalCVEPolicy(imageExclusion(excludedImage)) + alerts, err := detectorFor(t, policy).Detect(ctx, payments) + require.NoError(t, err) + require.Len(t, alerts, 1) + assert.Equal(t, "payments", alerts[0].GetDeployment().GetName()) + }) + + t.Run("with an image exclusion, a deployment running the excluded image still alerts", func(t *testing.T) { + // Image exclusions only apply at build time, so a deployment running the excluded image is + // still evaluated at deploy time. + legacy := deploymentRunning("legacy-app", imageWithOldCriticalCVE(t, excludedImage)) + policy := oldCriticalCVEPolicy(imageExclusion(excludedImage)) + alerts, err := detectorFor(t, policy).Detect(ctx, legacy) + require.NoError(t, err) + require.Len(t, alerts, 1) + assert.Equal(t, "legacy-app", alerts[0].GetDeployment().GetName()) + }) + + t.Run("a deployment exclusion still skips the named deployment", func(t *testing.T) { + policy := oldCriticalCVEPolicy(&storage.Exclusion{Matcher: &storage.Exclusion_Deployment_{Deployment: &storage.Exclusion_Deployment{Name: "payments"}}}) + alerts, err := detectorFor(t, policy).Detect(ctx, payments) + require.NoError(t, err) + assert.Empty(t, alerts) + }) +} + +// TestImageExclusionStillAppliesAtBuildTime checks that the fix does not change build-time +// behavior: the excluded image is skipped, other images are still checked. +func TestImageExclusionStillAppliesAtBuildTime(t *testing.T) { + ctx := context.Background() + policy := oldCriticalCVEPolicy(imageExclusion(excludedImage)) + compiled, err := detection.CompilePolicy(policy, nil, nil) + require.NoError(t, err) + + assert.False(t, compiled.AppliesTo(ctx, imageWithOldCriticalCVE(t, excludedImage))) + assert.True(t, compiled.AppliesTo(ctx, imageWithOldCriticalCVE(t, vulnerableImage))) +} diff --git a/pkg/detection/exclusion_test.go b/pkg/detection/exclusion_test.go index f075901acf537..c96474e6189bc 100644 --- a/pkg/detection/exclusion_test.go +++ b/pkg/detection/exclusion_test.go @@ -6,8 +6,11 @@ import ( "time" "github.com/stackrox/rox/generated/storage" + "github.com/stackrox/rox/pkg/features" "github.com/stackrox/rox/pkg/fixtures" + "github.com/stackrox/rox/pkg/kubernetes" "github.com/stackrox/rox/pkg/protoconv" + "github.com/stackrox/rox/pkg/testutils" "github.com/stackrox/rox/pkg/uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -31,9 +34,7 @@ func TestMatchesDeploymentExclusion(t *testing.T) { deployment: fixtures.GetDeployment(), policy: &storage.Policy{ Exclusions: []*storage.Exclusion{ - { - Deployment: &storage.Exclusion_Deployment{Name: fixtures.GetDeployment().GetName()}, - }, + {Matcher: &storage.Exclusion_Deployment_{Deployment: &storage.Exclusion_Deployment{Name: fixtures.GetDeployment().GetName()}}}, }, }, shouldMatch: true, @@ -43,9 +44,7 @@ func TestMatchesDeploymentExclusion(t *testing.T) { deployment: fixtures.GetDeployment(), policy: &storage.Policy{ Exclusions: []*storage.Exclusion{ - { - Deployment: &storage.Exclusion_Deployment{Name: "nginx.*"}, - }, + {Matcher: &storage.Exclusion_Deployment_{Deployment: &storage.Exclusion_Deployment{Name: "nginx.*"}}}, }, }, shouldMatch: true, @@ -55,9 +54,7 @@ func TestMatchesDeploymentExclusion(t *testing.T) { deployment: fixtures.GetDeployment(), policy: &storage.Policy{ Exclusions: []*storage.Exclusion{ - { - Deployment: &storage.Exclusion_Deployment{Name: "nginy.*"}, - }, + {Matcher: &storage.Exclusion_Deployment_{Deployment: &storage.Exclusion_Deployment{Name: "nginy.*"}}}, }, }, shouldMatch: false, @@ -67,9 +64,7 @@ func TestMatchesDeploymentExclusion(t *testing.T) { deployment: fixtures.GetDeployment(), policy: &storage.Policy{ Exclusions: []*storage.Exclusion{ - { - Deployment: &storage.Exclusion_Deployment{Name: "ngin\\K"}, - }, + {Matcher: &storage.Exclusion_Deployment_{Deployment: &storage.Exclusion_Deployment{Name: "ngin\\K"}}}, }, }, shouldMatch: false, @@ -79,12 +74,8 @@ func TestMatchesDeploymentExclusion(t *testing.T) { deployment: fixtures.GetDeployment(), policy: &storage.Policy{ Exclusions: []*storage.Exclusion{ - { - Deployment: &storage.Exclusion_Deployment{Name: fixtures.GetDeployment().GetName()}, - }, - { - Deployment: &storage.Exclusion_Deployment{Name: uuid.NewV4().String()}, - }, + {Matcher: &storage.Exclusion_Deployment_{Deployment: &storage.Exclusion_Deployment{Name: fixtures.GetDeployment().GetName()}}}, + {Matcher: &storage.Exclusion_Deployment_{Deployment: &storage.Exclusion_Deployment{Name: uuid.NewV4().String()}}}, }, }, shouldMatch: true, @@ -94,9 +85,7 @@ func TestMatchesDeploymentExclusion(t *testing.T) { deployment: fixtures.GetDeployment(), policy: &storage.Policy{ Exclusions: []*storage.Exclusion{ - { - Deployment: &storage.Exclusion_Deployment{Name: uuid.NewV4().String()}, - }, + {Matcher: &storage.Exclusion_Deployment_{Deployment: &storage.Exclusion_Deployment{Name: uuid.NewV4().String()}}}, }, }, shouldMatch: false, @@ -106,9 +95,7 @@ func TestMatchesDeploymentExclusion(t *testing.T) { deployment: fixtures.GetDeployment(), policy: &storage.Policy{ Exclusions: []*storage.Exclusion{ - { - Deployment: &storage.Exclusion_Deployment{Scope: &storage.Scope{Namespace: fixtures.GetDeployment().GetNamespace()}}, - }, + {Matcher: &storage.Exclusion_Deployment_{Deployment: &storage.Exclusion_Deployment{Scope: &storage.Scope{Namespace: fixtures.GetDeployment().GetNamespace()}}}}, }, }, shouldMatch: true, @@ -116,10 +103,20 @@ func TestMatchesDeploymentExclusion(t *testing.T) { { name: "Scoped excluded scope with wrong name", deployment: fixtures.GetDeployment(), + policy: &storage.Policy{ + Exclusions: []*storage.Exclusion{ + {Matcher: &storage.Exclusion_Deployment_{Deployment: &storage.Exclusion_Deployment{Scope: &storage.Scope{Namespace: uuid.NewV4().String()}}}}, + }, + }, + shouldMatch: false, + }, + { + name: "Image-only exclusion does not match deployments", + deployment: fixtures.GetDeployment(), policy: &storage.Policy{ Exclusions: []*storage.Exclusion{ { - Deployment: &storage.Exclusion_Deployment{Scope: &storage.Scope{Namespace: uuid.NewV4().String()}}, + Image: &storage.Exclusion_Image{Name: "docker.io/library/nginx"}, }, }, }, @@ -130,9 +127,7 @@ func TestMatchesDeploymentExclusion(t *testing.T) { deployment: fixtures.GetDeployment(), policy: &storage.Policy{ Exclusions: []*storage.Exclusion{ - { - Deployment: &storage.Exclusion_Deployment{Name: uuid.NewV4().String(), Scope: &storage.Scope{Namespace: fixtures.GetDeployment().GetNamespace()}}, - }, + {Matcher: &storage.Exclusion_Deployment_{Deployment: &storage.Exclusion_Deployment{Name: uuid.NewV4().String(), Scope: &storage.Scope{Namespace: fixtures.GetDeployment().GetNamespace()}}}}, }, }, shouldMatch: false, @@ -168,6 +163,126 @@ func TestMatchesDeploymentExclusion(t *testing.T) { } } +func TestMatchesWorkloadKindExclusion(t *testing.T) { + cronJob := fixtures.GetDeployment() + cronJob.Type = kubernetes.CronJob + job := fixtures.GetDeployment() + job.Type = kubernetes.Job + deployment := fixtures.GetDeployment() + deployment.Type = kubernetes.Deployment + + kindExclusion := func(kinds ...storage.Exclusion_WorkloadKind) *storage.Exclusion { + return &storage.Exclusion{ + Matcher: &storage.Exclusion_ExcludeByKind_{ + ExcludeByKind: &storage.Exclusion_ExcludeByKind{Kinds: kinds}, + }, + } + } + nameExclusion := func(name string) *storage.Exclusion { + return &storage.Exclusion{ + Matcher: &storage.Exclusion_Deployment_{ + Deployment: &storage.Exclusion_Deployment{Name: name}, + }, + } + } + + compile := func(t *testing.T, exclusions ...*storage.Exclusion) []*compiledExclusion { + t.Helper() + compiled := make([]*compiledExclusion, 0, len(exclusions)) + for _, exclusion := range exclusions { + cw, err := newCompiledExclusion(exclusion) + require.NoError(t, err) + compiled = append(compiled, cw) + } + return compiled + } + + t.Run("flag off does not exclude by kind", func(t *testing.T) { + testutils.MustUpdateFeature(t, features.PolicyWorkloadKindExclusion, false) + compiled := compile(t, kindExclusion(storage.Exclusion_CRON_JOB, storage.Exclusion_JOB)) + assert.False(t, deploymentMatchesExclusions(context.Background(), cronJob, compiled)) + assert.False(t, deploymentMatchesExclusions(context.Background(), job, compiled)) + assert.False(t, deploymentMatchesExclusions(context.Background(), deployment, compiled)) + }) + + testutils.MustUpdateFeature(t, features.PolicyWorkloadKindExclusion, true) + + cases := map[string]struct { + exclusions []*storage.Exclusion + workload *storage.Deployment + shouldMatch bool + }{ + "cronjob matches cronjob kind": { + exclusions: []*storage.Exclusion{kindExclusion(storage.Exclusion_CRON_JOB)}, + workload: cronJob, + shouldMatch: true, + }, + "deployment does not match cronjob kind": { + exclusions: []*storage.Exclusion{kindExclusion(storage.Exclusion_CRON_JOB)}, + workload: deployment, + shouldMatch: false, + }, + "job matches job and cronjob kinds": { + exclusions: []*storage.Exclusion{kindExclusion(storage.Exclusion_CRON_JOB, storage.Exclusion_JOB)}, + workload: job, + shouldMatch: true, + }, + "OR with name/scope exclusion": { + exclusions: []*storage.Exclusion{ + kindExclusion(storage.Exclusion_JOB), + nameExclusion(deployment.GetName()), + }, + workload: deployment, + shouldMatch: true, + }, + "kind exclusion does not match unrelated name": { + exclusions: []*storage.Exclusion{kindExclusion(storage.Exclusion_JOB)}, + workload: deployment, + shouldMatch: false, + }, + } + + for name, c := range cases { + t.Run(name, func(t *testing.T) { + compiled := compile(t, c.exclusions...) + got := deploymentMatchesExclusions(context.Background(), c.workload, compiled) + assert.Equal(t, c.shouldMatch, got) + if c.shouldMatch { + for _, exclusion := range c.exclusions { + exclusion.Expiration = protoconv.MustConvertTimeToTimestamp(time.Now().Add(-1 * time.Hour)) + } + assert.False(t, deploymentMatchesExclusions(context.Background(), c.workload, compiled)) + } + }) + } + + for name, tc := range map[string]struct { + kinds []storage.Exclusion_WorkloadKind + errContains string + }{ + "empty kind list is rejected": { + errContains: "at least one workload kind", + }, + "unknown kind is rejected": { + kinds: []storage.Exclusion_WorkloadKind{storage.Exclusion_WorkloadKind(99)}, + errContains: "unknown workload kind", + }, + "mixed valid and unknown kinds are rejected": { + kinds: []storage.Exclusion_WorkloadKind{storage.Exclusion_JOB, storage.Exclusion_WorkloadKind(99)}, + errContains: "unknown workload kind", + }, + "UNSPECIFIED is rejected": { + kinds: []storage.Exclusion_WorkloadKind{storage.Exclusion_WORKLOAD_KIND_UNSPECIFIED}, + errContains: "unknown workload kind", + }, + } { + t.Run(name, func(t *testing.T) { + _, err := newCompiledExclusion(kindExclusion(tc.kinds...)) + require.ErrorContains(t, err, tc.errContains) + }) + } +} + func TestMatchesImageExclusion(t *testing.T) { cases := []struct { name string @@ -243,8 +358,18 @@ func TestMatchesImageExclusion(t *testing.T) { } assert.True(t, matchesImageExclusion(c.image, c.policy)) } - c.policy.Exclusions = append(c.policy.Exclusions, &storage.Exclusion{Deployment: &storage.Exclusion_Deployment{Name: "BLAH"}}) + c.policy.Exclusions = append(c.policy.Exclusions, &storage.Exclusion{Matcher: &storage.Exclusion_Deployment_{Deployment: &storage.Exclusion_Deployment{Name: "BLAH"}}}) assert.Equal(t, c.shouldMatch, got) }) } } + +func TestImageOnlyExclusionDoesNotMatchAuditEvent(t *testing.T) { + cx, err := newCompiledExclusion(&storage.Exclusion{Image: &storage.Exclusion_Image{Name: "docker.io/library/nginx"}}) + require.NoError(t, err) + + auditEvent := &storage.KubernetesEvent{ + Object: &storage.KubernetesEvent_Object{Name: "my-secret", Namespace: "default", ClusterId: "cluster-1"}, + } + assert.False(t, auditEventMatchesExclusions(context.Background(), auditEvent, []*compiledExclusion{cx})) +} diff --git a/pkg/features/list.go b/pkg/features/list.go index f650124fef654..c2d19ebbf9269 100644 --- a/pkg/features/list.go +++ b/pkg/features/list.go @@ -15,9 +15,6 @@ var ( // ClusterAwareDeploymentCheck enables roxctl deployment check to check deployments on the cluster level. ClusterAwareDeploymentCheck = registerFeature("Enables cluster level check for the 'roxctl deployment check' command.", "ROX_CLUSTER_AWARE_DEPLOYMENT_CHECK", enabled) - // PolicyCriteriaModal enables a modal for selecting policy criteria when editing a policy - PolicyCriteriaModal = registerFeature("Enable modal to select policy criteria when editing a policy", "ROX_POLICY_CRITERIA_MODAL") - // SensorDeploymentBuildOptimization enables a performance improvement by skipping deployments processing when no dependency or spec changed SensorDeploymentBuildOptimization = registerFeature("Enables a performance improvement by skipping deployments processing when no dependency or spec changed", "ROX_DEPLOYMENT_BUILD_OPTIMIZATION", enabled) @@ -36,6 +33,12 @@ var ( // ComplianceRemediationV2 enables the remediation feature of the compliance v2 integration. ComplianceRemediationV2 = registerFeature("Enable Compliance Remediation feature", "ROX_COMPLIANCE_REMEDIATION", enabled) + // ComplianceSurfaceStaleData gates surfacing stale compliance data in Coverage views (ROX-35712). Default off. + ComplianceSurfaceStaleData = registerFeature("Surface stale compliance data in Coverage views", "ROX_COMPLIANCE_SURFACE_STALE_DATA") + + // ComplianceCustomNodeRoles gates configurable node roles in compliance scan configurations (ROX-34167). Default off. + ComplianceCustomNodeRoles = registerFeature("Enable configurable node roles in compliance scan configurations", "ROX_COMPLIANCE_CUSTOM_NODE_ROLES") + // SensorAggregateDeploymentReferenceOptimization enables a performance improvement by aggregating deployment references when the same reference is queued for processing SensorAggregateDeploymentReferenceOptimization = registerFeature("Enables a performance improvement by aggregating deployment references when the same reference is queued for processing", "ROX_AGGREGATE_DEPLOYMENT_REFERENCE_OPTIMIZATION", enabled) @@ -101,9 +104,6 @@ var ( // This must be enabled in Central and Scanner V4 Matcher to have any effect. KnownExploitedVulnerabilities = registerFeature("Display CISA Known Exploited Vulnerabilities (KEV) data", "ROX_CISA_KEV", enabled) - // Display Compliance Dashboard (Deprecated) in user interface (not displayed by default starting in 4.11 release). - DeprecatedComplianceDashboard = registerFeature("Display Compliance Dashboard (Deprecated) in user interface", "ROX_DEPRECATED_COMPLIANCE_DASHBOARD") - // SensitiveFileActivity enables monitoring of sensitive files. SensitiveFileActivity = registerFeature("Enable sensitive file monitoring", "ROX_SENSITIVE_FILE_ACTIVITY", enabled) @@ -146,8 +146,8 @@ var ( // EvaluationFilter enables evaluation filtering in policy evaluation. EvaluationFilter = registerFeature("Enable evaluation filtering in policy evaluation", "ROX_EVALUATION_FILTER", enabled) - // PolicyWorkloadTypeExclusion enables excluding all workloads of selected Kubernetes kinds (Job, CronJob) from a policy. - PolicyWorkloadTypeExclusion = registerFeature("Enable excluding all workloads of a given type from a policy", "ROX_POLICY_WORKLOAD_TYPE_EXCLUSION") + // PolicyWorkloadKindExclusion enables excluding all workloads of selected Kubernetes kinds (Job, CronJob) from a policy. + PolicyWorkloadKindExclusion = registerFeature("Enable excluding all workloads of a given kind from a policy", "ROX_POLICY_WORKLOAD_KIND_EXCLUSION") // UISecretsPageMigration enables the secrets list page under the Risk section UISecretsPageMigration = registerFeature("Display secrets list page under Risk section", "ROX_UI_SECRETS_PAGE_MIGRATION") diff --git a/pkg/glob/pattern.go b/pkg/glob/pattern.go index 3f4009ddd3134..a89e0eeb7fe0b 100644 --- a/pkg/glob/pattern.go +++ b/pkg/glob/pattern.go @@ -19,7 +19,7 @@ func (p *Pattern) Compile() error { return err } -func (p *Pattern) compile() (glob.Glob, error) { +func (p *Pattern) compile() (*glob.Pattern, error) { g, err := glob.Compile(string(*p)) if err != nil { return nil, errors.WithMessagef(err, "failed to compile %q", string(*p)) @@ -36,7 +36,7 @@ func (p *Pattern) Match(s string) bool { return false } } - return v.(glob.Glob).Match(s) + return v.(*glob.Pattern).Match(s) } func (p Pattern) Ptr() *Pattern { diff --git a/pkg/helm/charts/tests/centralservices/testdata/helmtest/central-worker.test.yaml b/pkg/helm/charts/tests/centralservices/testdata/helmtest/central-worker.test.yaml new file mode 100644 index 0000000000000..ce2ae05e8ad72 --- /dev/null +++ b/pkg/helm/charts/tests/centralservices/testdata/helmtest/central-worker.test.yaml @@ -0,0 +1,29 @@ +values: + imagePullSecrets: + allowNone: true +tests: +- name: "central-worker is enabled by default" + expect: | + .deployments["central-worker"] | assertThat(. != null) + .serviceaccounts["central-worker"] | assertThat(. != null) + container(.deployments["central-worker"]; "central-worker") | .command | assertThat(. == ["/stackrox/central-worker"]) + container(.deployments["central-worker"]; "central-worker") | .env[] | select(.name == "ROX_CENTRAL_WORKER_ENABLED") | assertThat(.value == "true") + container(.deployments.central; "central") | .env[] | select(.name == "ROX_CENTRAL_WORKER_ENABLED") | assertThat(.value == "true") + tests: + - name: "with network policies" + values: + network: + enableNetworkPolicies: true + expect: | + .networkpolicys["central-worker"] | assertThat(. != null) + .networkpolicys["central-db"].spec.ingress[].from[].podSelector.matchLabels.app | select(. == "central-worker") | assertThat(. == "central-worker") + +- name: "central-worker can be disabled" + values: + centralWorker: + enabled: false + expect: | + .deployments["central-worker"] | assertThat(. == null) + .serviceaccounts["central-worker"] | assertThat(. == null) + .networkpolicys["central-worker"] | assertThat(. == null) + [container(.deployments.central; "central") | .env[] | select(.name == "ROX_CENTRAL_WORKER_ENABLED")] | assertThat(length == 0) diff --git a/pkg/helm/charts/tests/centralservices/testdata/helmtest/openshift-monitoring.test.yaml b/pkg/helm/charts/tests/centralservices/testdata/helmtest/openshift-monitoring.test.yaml index a9b75d76642e6..fb1f3ab3d3756 100644 --- a/pkg/helm/charts/tests/centralservices/testdata/helmtest/openshift-monitoring.test.yaml +++ b/pkg/helm/charts/tests/centralservices/testdata/helmtest/openshift-monitoring.test.yaml @@ -54,6 +54,120 @@ tests: env.openshift: 4 tests: *enabled-test +- name: "Central DB PVC space alerts" + set: + monitoring.openshift.enabled: true + env.openshift: 4 + tests: + - name: "warning and critical alerts are created" + expect: | + .prometheusrules["central-telemeter-stackrox"].spec.groups | + assertThat(any(.[]; .name == "rhacs.central-db")) + .prometheusrules["central-telemeter-stackrox"].spec.groups[] | + select(.name == "rhacs.central-db") | .rules | [ + assertThat(length == 2), + assertThat(all(.[]; .alert == "CentralDBPersistentVolumeFillingUp")), + assertThat(any(.[]; .labels.severity == "warning")), + assertThat(any(.[]; .labels.severity == "critical")) + ] + .prometheusrules["central-telemeter-stackrox"].spec.groups[] | + select(.name == "rhacs.central-db") | .rules[] | + select(.labels.severity == "warning") | + assertThat(.expr | contains("persistentvolumeclaim=\"central-db\"") and contains("> 0.8")) + + - name: "custom claim name is honoured" + set: + central.db.persistence.persistentVolumeClaim.claimName: my-central-db + expect: | + .prometheusrules["central-telemeter-stackrox"].spec.groups[] | + select(.name == "rhacs.central-db") | .rules[0] | + assertThat(.expr | contains("persistentvolumeclaim=\"my-central-db\"")) + + - name: "omitted when central-db is not PVC-backed" + set: + central.db.persistence.hostPath: /var/lib/central-db-test + expect: | + .prometheusrules["central-telemeter-stackrox"].spec.groups | + assertThat(all(.[]; .name != "rhacs.central-db")) + +- name: "Scanner V4 DB PVC space alerts" + set: + monitoring.openshift.enabled: true + env.openshift: 4 + scannerV4: + disable: false + tests: + - name: "warning and critical alerts are created" + expect: | + .prometheusrules["scanner-v4-db-stackrox"].spec.groups | + assertThat(any(.[]; .name == "rhacs.scanner-v4-db")) + .prometheusrules["scanner-v4-db-stackrox"].spec.groups[] | + select(.name == "rhacs.scanner-v4-db") | .rules | [ + assertThat(length == 2), + assertThat(all(.[]; .alert == "ScannerV4DBPersistentVolumeFillingUp")), + assertThat(any(.[]; .labels.severity == "warning")), + assertThat(any(.[]; .labels.severity == "critical")) + ] + .prometheusrules["scanner-v4-db-stackrox"].spec.groups[] | + select(.name == "rhacs.scanner-v4-db") | .rules[] | + select(.labels.severity == "warning") | + assertThat(.expr | contains("persistentvolumeclaim=\"scanner-v4-db\"") and contains("> 0.8")) + + - name: "custom claim name is honoured" + set: + scannerV4.db.persistence.persistentVolumeClaim.claimName: my-scanner-v4-db + expect: | + .prometheusrules["scanner-v4-db-stackrox"].spec.groups[] | + select(.name == "rhacs.scanner-v4-db") | .rules[0] | + assertThat(.expr | contains("persistentvolumeclaim=\"my-scanner-v4-db\"")) + + - name: "omitted when scanner-v4-db is not PVC-backed" + set: + scannerV4.db.persistence.none: true + expect: | + .prometheusrules["scanner-v4-db-stackrox"] | assertThat(. == null) + + - name: "omitted when Scanner V4 is disabled" + set: + scannerV4.disable: true + expect: | + .prometheusrules["scanner-v4-db-stackrox"] | assertThat(. == null) + +- name: "Custom metrics" + set: + env.openshift: 4 + tests: + - name: "openshift metrics disabled" + expect: | + .servicemonitors["central-ocp-api-monitor-stackrox"] | assertThat(. == null) + + - name: "custom metrics disabled" + set: + monitoring.openshift.enabled: true + expect: | + .servicemonitors["central-ocp-api-monitor-stackrox"] | assertThat(. == null) + + - name: "custom metrics enabled" + set: + monitoring.openshift.enabled: true + monitoring.openshift.custom.enabled: true + expect: | + .servicemonitors["central-ocp-api-monitor-stackrox"] | [ + assertThat(.metadata.namespace == "openshift-monitoring"), + assertThat(.spec.endpoints[].port == "https"), + assertThat(.spec.endpoints[].path == "/metrics"), + assertThat(.spec.endpoints[].scheme == "https"), + assertThat(.spec.endpoints[].tlsConfig.caFile == "/etc/prometheus/configmaps/serving-certs-ca-bundle/service-ca.crt") + ] + + - name: "custom metrics disabled on managed services" + set: + monitoring.openshift.enabled: true + monitoring.openshift.custom.enabled: true + env.managedServices: true + expect: | + .servicemonitors["central-ocp-api-monitor-stackrox"] | assertThat(. == null) + - name: "When enabled with Scanner V4" set: monitoring.openshift.enabled: true @@ -146,6 +260,7 @@ tests: .rolebindings["central-prometheus-k8s"] | assertThat(. == null) .rolebindings["rhacs-central-auth-reader-stackrox"] | assertThat(. == null) .servicemonitors["central-monitor-stackrox"] | assertThat(. == null) + .servicemonitors["central-ocp-api-monitor-stackrox"] | assertThat(. == null) .services["central-ocp"] | assertThat(. == null) - name: "secure metrics endpoint is disabled" diff --git a/pkg/helm/charts/tests/securedclusterservices/testdata/helmtest/openshift-monitoring.test.yaml b/pkg/helm/charts/tests/securedclusterservices/testdata/helmtest/openshift-monitoring.test.yaml index f10712e50aaf9..2cea2a231037f 100644 --- a/pkg/helm/charts/tests/securedclusterservices/testdata/helmtest/openshift-monitoring.test.yaml +++ b/pkg/helm/charts/tests/securedclusterservices/testdata/helmtest/openshift-monitoring.test.yaml @@ -124,6 +124,72 @@ tests: ] .networkpolicys["scanner-v4-indexer-monitoring-tls"] | assertThat(. != null) +- name: "Scanner V4 DB PVC space alerts" + set: + monitoring.openshift.enabled: true + env.openshift: 4 + scannerV4.disable: false + tests: + # Scanner V4 DB in the secured cluster only uses a PVC when a default + # StorageClass is present; otherwise it falls back to emptyDir and no PVC + # alert should be emitted. + - name: "warning and critical alerts are created when PVC-backed" + server: + objects: + - apiVersion: storage.k8s.io/v1 + kind: StorageClass + metadata: + name: def-sc + annotations: + storageclass.kubernetes.io/is-default-class: true + expect: | + .prometheusrules["scanner-v4-db-stackrox"].spec.groups | + assertThat(any(.[]; .name == "rhacs.scanner-v4-db")) + .prometheusrules["scanner-v4-db-stackrox"].spec.groups[] | + select(.name == "rhacs.scanner-v4-db") | .rules | [ + assertThat(length == 2), + assertThat(all(.[]; .alert == "ScannerV4DBPersistentVolumeFillingUp")), + assertThat(any(.[]; .labels.severity == "warning")), + assertThat(any(.[]; .labels.severity == "critical")) + ] + .prometheusrules["scanner-v4-db-stackrox"].spec.groups[] | + select(.name == "rhacs.scanner-v4-db") | .rules[] | + select(.labels.severity == "warning") | + assertThat(.expr | contains("persistentvolumeclaim=\"scanner-v4-db\"") and contains("> 0.8")) + + - name: "alerts target the PVC namespace when overridden" + release: + namespace: default + set: + meta.namespaceOverride: my-stackrox + allowNonstandardNamespace: true + server: + objects: + - apiVersion: storage.k8s.io/v1 + kind: StorageClass + metadata: + name: def-sc + annotations: + storageclass.kubernetes.io/is-default-class: true + expect: | + .persistentvolumeclaims["scanner-v4-db"].metadata.namespace | assertThat(. == "my-stackrox") + .prometheusrules["scanner-v4-db-default"].spec.groups[] | + select(.name == "rhacs.scanner-v4-db") | .rules[] | [ + assertThat(.expr | contains("kubelet_volume_stats_used_bytes{namespace=\"my-stackrox\"")), + assertThat(.expr | contains("kubelet_volume_stats_capacity_bytes{namespace=\"my-stackrox\"")), + assertThat(.annotations.description | contains("in namespace my-stackrox")) + ] + + - name: "omitted when scanner-v4-db falls back to emptyDir (no default StorageClass)" + expect: | + .prometheusrules["scanner-v4-db-stackrox"] | assertThat(. == null) + + - name: "omitted when Scanner V4 is disabled" + set: + scannerV4.disable: true + expect: | + .prometheusrules["scanner-v4-db-stackrox"] | assertThat(. == null) + - name: "When enabled via default value" set: env.openshift: 4 diff --git a/pkg/helm/charts/tests/securedclusterservices/testdata/helmtest/vwc-global-name.test.yaml b/pkg/helm/charts/tests/securedclusterservices/testdata/helmtest/vwc-global-name.test.yaml new file mode 100644 index 0000000000000..fa547c87e89ca --- /dev/null +++ b/pkg/helm/charts/tests/securedclusterservices/testdata/helmtest/vwc-global-name.test.yaml @@ -0,0 +1,42 @@ +name: "Test ValidatingWebhookConfiguration global resource naming" +values: + imagePullSecrets: + allowNone: true + +defs: | + def vwcNames: + (.validatingwebhookconfigurations // {}) | keys; + +tests: +# By default namespacePrefixGlobalResources is false, so the base name is kept static +# ("stackrox") regardless of namespace, for production safety. +- name: base name is static in the standard namespace + expect: | + vwcNames | assertThat(. == ["stackrox"]) + +- name: base name stays static in a non-standard namespace when prefixing is disabled + release: + namespace: custom-ns + set: + allowNonstandardNamespace: true + expect: | + vwcNames | assertThat(. == ["stackrox"]) + +# When namespacePrefixGlobalResources is enabled the name is namespace-prefixed to avoid +# clashes across multiple secured clusters in different namespaces of the same cluster. +- name: base name is used verbatim in the standard namespace even when prefixing is enabled + set: + namespacePrefixGlobalResources: true + expect: | + vwcNames | assertThat(. == ["stackrox"]) + +- name: base name is namespace-prefixed in a non-standard namespace when prefixing is enabled + release: + namespace: custom-ns + set: + allowNonstandardNamespace: true + namespacePrefixGlobalResources: true + expect: | + # A bare "stackrox" base name (no suffix) must not trigger the "Unknown naming + # convention" failure; the global prefix already carries the namespace. + vwcNames | assertThat(. == ["stackrox-custom-ns"]) diff --git a/pkg/images/enricher/enricher_impl.go b/pkg/images/enricher/enricher_impl.go index 6f747496609c2..3e773a97b9992 100644 --- a/pkg/images/enricher/enricher_impl.go +++ b/pkg/images/enricher/enricher_impl.go @@ -158,7 +158,7 @@ func (e *enricherImpl) delegateEnrichImage(ctx context.Context, enrichCtx Enrich clusterID, shouldDelegate, err = e.scanDelegator.GetDelegateClusterID(ctx, image.GetName()) } else { // A cluster ID has been passed to the enricher, determine if it's valid for delegation. - err = e.scanDelegator.ValidateCluster(enrichCtx.ClusterID) + err = e.scanDelegator.ValidateCluster(ctx, enrichCtx.ClusterID) shouldDelegate = true } diff --git a/pkg/images/enricher/enricher_impl_test.go b/pkg/images/enricher/enricher_impl_test.go index 072e106e51fed..24658b4757270 100644 --- a/pkg/images/enricher/enricher_impl_test.go +++ b/pkg/images/enricher/enricher_impl_test.go @@ -1179,7 +1179,7 @@ func TestDelegateEnrichImage(t *testing.T) { t.Run("delegate enrich success with cluster id provided", func(t *testing.T) { setup(t) fakeImage := &storage.Image{} - dele.EXPECT().ValidateCluster("cluster-id").Return(nil) + dele.EXPECT().ValidateCluster(emptyCtx, "cluster-id").Return(nil) dele.EXPECT().DelegateScanImage(emptyCtx, gomock.Any(), "cluster-id", "", gomock.Any()).Return(fakeImage, nil) deleEnrichCtx := EnrichmentContext{Delegable: true, ClusterID: "cluster-id"} @@ -1192,7 +1192,7 @@ func TestDelegateEnrichImage(t *testing.T) { t.Run("delegate enrich error with cluster id provided", func(t *testing.T) { setup(t) fakeImage := &storage.Image{} - dele.EXPECT().ValidateCluster("cluster-id").Return(errBroken) + dele.EXPECT().ValidateCluster(emptyCtx, "cluster-id").Return(errBroken) deleEnrichCtx := EnrichmentContext{Delegable: true, ClusterID: "cluster-id"} should, err := e.delegateEnrichImage(emptyCtx, deleEnrichCtx, fakeImage) diff --git a/pkg/images/enricher/enricher_v2_impl.go b/pkg/images/enricher/enricher_v2_impl.go index 55f7034b45ccc..df8b3a70969bf 100644 --- a/pkg/images/enricher/enricher_v2_impl.go +++ b/pkg/images/enricher/enricher_v2_impl.go @@ -168,7 +168,7 @@ func (e *enricherV2Impl) delegateEnrichImage(ctx context.Context, enrichCtx Enri clusterID, shouldDelegate, err = e.scanDelegator.GetDelegateClusterID(ctx, imageV2.GetName()) } else { // A cluster ID has been passed to the enricher, determine if it's valid for delegation. - err = e.scanDelegator.ValidateCluster(enrichCtx.ClusterID) + err = e.scanDelegator.ValidateCluster(ctx, enrichCtx.ClusterID) shouldDelegate = true } diff --git a/pkg/images/enricher/enricher_v2_impl_test.go b/pkg/images/enricher/enricher_v2_impl_test.go index ec24990660c35..1976852d1bc3b 100644 --- a/pkg/images/enricher/enricher_v2_impl_test.go +++ b/pkg/images/enricher/enricher_v2_impl_test.go @@ -1209,7 +1209,7 @@ func TestDelegateEnrichImageV2(t *testing.T) { t.Run("delegate enrich success with cluster id provided", func(t *testing.T) { setup(t) fakeImage := &storage.ImageV2{} - dele.EXPECT().ValidateCluster("cluster-id").Return(nil) + dele.EXPECT().ValidateCluster(emptyCtx, "cluster-id").Return(nil) dele.EXPECT().DelegateScanImageV2(emptyCtx, gomock.Any(), "cluster-id", "", gomock.Any()).Return(fakeImage, nil) deleEnrichCtx := EnrichmentContext{Delegable: true, ClusterID: "cluster-id"} @@ -1222,7 +1222,7 @@ func TestDelegateEnrichImageV2(t *testing.T) { t.Run("delegate enrich error with cluster id provided", func(t *testing.T) { setup(t) fakeImage := &storage.ImageV2{} - dele.EXPECT().ValidateCluster("cluster-id").Return(errBroken) + dele.EXPECT().ValidateCluster(emptyCtx, "cluster-id").Return(errBroken) deleEnrichCtx := EnrichmentContext{Delegable: true, ClusterID: "cluster-id"} should, err := e.delegateEnrichImage(emptyCtx, deleEnrichCtx, fakeImage) diff --git a/pkg/migrations/release_version.go b/pkg/migrations/release_version.go new file mode 100644 index 0000000000000..31182bb6b3d40 --- /dev/null +++ b/pkg/migrations/release_version.go @@ -0,0 +1,9 @@ +package migrations + +//go:generate go run ../../tools/generate-helpers/release-versions + +// ReleaseVersion records a stream's initial-release database sequence (GA, or latest RC until GA). +type ReleaseVersion struct { + Version string + Sequence int +} diff --git a/pkg/migrations/release_versions.go b/pkg/migrations/release_versions.go new file mode 100644 index 0000000000000..f787dccd185fb --- /dev/null +++ b/pkg/migrations/release_versions.go @@ -0,0 +1,16 @@ +// Code generated by release-versions. DO NOT EDIT. + +package migrations + +var releaseVersions = []ReleaseVersion{ + {Version: "4.4", Sequence: 199}, + {Version: "4.5", Sequence: 205}, + {Version: "4.6", Sequence: 209}, + {Version: "4.7", Sequence: 209}, + {Version: "4.8", Sequence: 211}, + {Version: "4.9", Sequence: 213}, + {Version: "4.10", Sequence: 220}, + {Version: "4.11", Sequence: 225}, + {Version: "5.0", Sequence: 227}, + {Version: "5.1", Sequence: 227}, +} diff --git a/pkg/mocks/github.com/jackc/pgx/v5/mocks/rows.go b/pkg/mocks/github.com/jackc/pgx/v5/mocks/rows.go index 5deff357c4ee4..64eec106df3ec 100644 --- a/pkg/mocks/github.com/jackc/pgx/v5/mocks/rows.go +++ b/pkg/mocks/github.com/jackc/pgx/v5/mocks/rows.go @@ -8,6 +8,7 @@ import ( "github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5/pgconn" + "github.com/jackc/pgx/v5/pgtype" ) // This file was copied and fixed @@ -221,6 +222,10 @@ func (rs *rowSets) Conn() *pgx.Conn { return nil } +func (rs *rowSets) TypeMap() *pgtype.Map { + return nil +} + func convert(rows ...*Rows) pgx.Rows { defs := 0 sets := make([]*Rows, len(rows)) diff --git a/pkg/mtls/README.md b/pkg/mtls/README.md index f9045e81f8f0a..e601bbdedbf30 100644 --- a/pkg/mtls/README.md +++ b/pkg/mtls/README.md @@ -52,12 +52,12 @@ This document covers TLS certificate management in StackRox, primarily the inter - Sensor cert refresh (TLS challenge + CA bundle): `sensor/kubernetes/certrefresh/` - Postgres DB cert reload (Central DB, Scanner V4 DB): `image/postgres/scripts/cert-watcher.sh`, `image/templates/helm/shared/templates/_cert-watcher.tpl` -### Central has four independent cert-handling paths +### Central has three independent cert-handling paths 1. **TLS manager** (`TLSConfigHolder`) — incoming connections. Composes default cert (watched) + internal cert (watched) + OpenShift cert (watched, if configured) + sync.Once trust roots. + - **OpenShift service-serving TLS** (`central/tlsconfig/openshift_tls.go`) — on OpenShift, watches certs issued for the `central-ocp` Service. Loaded into the TLS manager and presented via SNI on the same `:8443` listener alongside the StackRox internal cert. 2. **Outbound client connections** (`clientconn.TLSConfig`) — reads leaf cert from disk on each TLS handshake, trust pool from `mtls.CACert()`. 3. **TLS challenge endpoint** (`central/metadata/service`) — reads primary leaf via certwatch, issues secondary leaf with short validity and auto-renewal, reads CA via `mtls.CACert()`. -4. **OpenShift service-serving TLS** (`central/tlsconfig/openshift_tls.go`) — on OpenShift, watches certs issued for the `central-ocp` Service. Presented via SNI on the same `:8443` listener alongside the StackRox internal cert. ## Certificate caching diff --git a/pkg/policyutils/exclusions_test.go b/pkg/policyutils/exclusions_test.go index c5af2b5742431..ff7c7a218f1dd 100644 --- a/pkg/policyutils/exclusions_test.go +++ b/pkg/policyutils/exclusions_test.go @@ -33,8 +33,7 @@ func TestDeploymentExclusionToQuery_NoDeploymentExclusions(t *testing.T) { func TestDeploymentExclusionToQuery_MalformedDeploymentExclusion(t *testing.T) { q := DeploymentExclusionToQuery([]*storage.Exclusion{ { - Name: "myExcludedScope", - Deployment: &storage.Exclusion_Deployment{}, + Name: "myExcludedScope", Matcher: &storage.Exclusion_Deployment_{Deployment: &storage.Exclusion_Deployment{}}, }, }) protoassert.Equal(t, q, search.MatchNoneQuery()) @@ -43,10 +42,9 @@ func TestDeploymentExclusionToQuery_MalformedDeploymentExclusion(t *testing.T) { func TestDeploymentExclusionToQuery_NamedDeploymentExclusion(t *testing.T) { q := DeploymentExclusionToQuery([]*storage.Exclusion{ { - Name: "myExcludedScope", - Deployment: &storage.Exclusion_Deployment{ + Name: "myExcludedScope", Matcher: &storage.Exclusion_Deployment_{Deployment: &storage.Exclusion_Deployment{ Name: "blessed-deployment", - }, + }}, }, }) protoassert.Equal(t, q, search.NewQueryBuilder().AddExactMatches(search.DeploymentName, "blessed-deployment").ProtoQuery()) @@ -55,12 +53,11 @@ func TestDeploymentExclusionToQuery_NamedDeploymentExclusion(t *testing.T) { func TestDeploymentExclusionToQuery_ScopedDeploymentExclusion(t *testing.T) { q := DeploymentExclusionToQuery([]*storage.Exclusion{ { - Name: "myExcludedScope", - Deployment: &storage.Exclusion_Deployment{ + Name: "myExcludedScope", Matcher: &storage.Exclusion_Deployment_{Deployment: &storage.Exclusion_Deployment{ Scope: &storage.Scope{ Cluster: "blessed-cluster-id", }, - }, + }}, }, }) protoassert.Equal(t, q, search.NewQueryBuilder().AddExactMatches(search.ClusterID, "blessed-cluster-id").ProtoQuery()) diff --git a/pkg/postgres/walker/walker.go b/pkg/postgres/walker/walker.go index fcfe5a6e82e1e..fb79783ca2119 100644 --- a/pkg/postgres/walker/walker.go +++ b/pkg/postgres/walker/walker.go @@ -357,7 +357,7 @@ func tableName(parent, child string) string { } func typeIsEnum(typ reflect.Type) bool { - enum, ok := reflect.Zero(typ).Interface().(protoreflect.ProtoEnum) + enum, ok := reflect.TypeAssert[protoreflect.ProtoEnum](reflect.Zero(typ)) if !ok { return false } diff --git a/pkg/printers/sarif.go b/pkg/printers/sarif.go index e1188b3dea0d2..d1de4b365a5a7 100644 --- a/pkg/printers/sarif.go +++ b/pkg/printers/sarif.go @@ -5,8 +5,8 @@ import ( "slices" "strings" - "github.com/owenrumney/go-sarif/v2/sarif" - "github.com/pkg/errors" + "github.com/owenrumney/go-sarif/v3/pkg/report" + "github.com/owenrumney/go-sarif/v3/pkg/report/v210/sarif" "github.com/stackrox/rox/generated/storage" "github.com/stackrox/rox/pkg/errox" "github.com/stackrox/rox/pkg/gjson" @@ -125,58 +125,54 @@ func (s *SarifPrinter) Print(object interface{}, out io.Writer) error { WithVersion(version.GetMainVersion()). WithFullName("roxctl command line utility") - report, err := sarif.New(sarif.Version210) - if err != nil { - return errors.Wrap(err, "creating sarif report") - } + sarifReport := report.NewV210Report() for _, entry := range sarifEntries { s.addEntry(run, entry) } - report.AddRun(run) - return report.PrettyWrite(out) + sarifReport.AddRun(run) + return sarifReport.PrettyWrite(out) } func (s *SarifPrinter) addEntry(run *sarif.Run, entry sarifEntry) { rule := run.AddRule(entry.ruleID) rule.WithName(s.reportType). // Setting the description is also setting the title displayed in GitHub. - WithShortDescription(sarif.NewMultiformatMessageString(entry.ruleID)). - WithFullDescription(sarif.NewMultiformatMessageString(entry.ruleID)). - WithHelp(sarif.NewMultiformatMessageString(entry.help)) + WithShortDescription(sarif.NewMultiformatMessageString().WithText(entry.ruleID)). + WithFullDescription(sarif.NewMultiformatMessageString().WithText(entry.ruleID)). + WithHelp(sarif.NewMultiformatMessageString().WithText(entry.help)) if entry.helpLink != "" { rule.WithHelpURI(entry.helpLink) } - properties := sarif.Properties{ + properties := sarif.NewPropertyBag(). // Precision very-high ensures this violation is shown first within GitHub. - "precision": "very-high", + Add("precision", "very-high"). // Tags allow filtering, which is desirable to have. - "tags": []string{ + WithTags([]string{ utils.IfThenElse(s.reportType == SarifVulnerabilityReport, "security", "policy-violation"), entry.severity, - }, - } + }) + // For vulnerability reports, generated the security severity based of the severity reported. if s.reportType == SarifVulnerabilityReport { - properties["security-severity"] = toSecuritySeverity(entry.severity) + properties.Add("security-severity", toSecuritySeverity(entry.severity)) } rule.WithProperties(properties) - run.AddResult(sarif.NewRuleResult(entry.ruleID). + run.CreateResultForRule(entry.ruleID). WithLevel(toSarifLevel(entry.severity)). // Reusing the help here, since the help includes remediation information. WithMessage(sarif.NewMessage().WithText(entry.help)). WithLocations([]*sarif.Location{ - { - PhysicalLocation: &sarif.PhysicalLocation{ - ArtifactLocation: sarif.NewArtifactLocation().WithUri(s.entity), - Region: sarif.NewSimpleRegion(1, 1), - }, - }, - })) + sarif.NewLocationWithPhysicalLocation( + sarif.NewPhysicalLocation(). + WithArtifactLocation(sarif.NewArtifactLocation().WithURI(s.entity)). + WithRegion(sarif.NewSimpleRegion(1, 1)), + ), + }) } func sarifEntriesFromJSONObject(jsonObject interface{}, pathExpressions map[string]string) ([]sarifEntry, error) { diff --git a/pkg/printers/sarif_test.go b/pkg/printers/sarif_test.go index 0fe19b6db5d3b..35d41a8e07802 100644 --- a/pkg/printers/sarif_test.go +++ b/pkg/printers/sarif_test.go @@ -8,6 +8,7 @@ import ( "strings" "testing" + "github.com/owenrumney/go-sarif/v3/pkg/report/v210/sarif" "github.com/stackrox/rox/pkg/errox" "github.com/stackrox/rox/pkg/version" "github.com/stretchr/testify/assert" @@ -75,11 +76,63 @@ func TestSarifPrinter_Print_Success(t *testing.T) { err = printer.Print(obj, &out) require.NoError(t, err) + // Strict parsing preserves null arrays so validation checks the emitted output. + report, err := sarif.FromString(out.String(), sarif.WithStrictValidation()) + require.NoError(t, err) + assert.NoError(t, report.Validate()) + // Since the report contains the version, replace it specifically here. exp, err := regexp.Compile(fmt.Sprintf(`"version": "%s"`, version.GetMainVersion())) require.NoError(t, err) output := exp.ReplaceAllString(out.String(), `"version": ""`) - assert.Equal(t, string(expectedOutput), output) + assert.JSONEq(t, string(expectedOutput), output) +} + +func TestSarifPrinter_Print_RuleIndexes(t *testing.T) { + for name, tc := range map[string]struct { + ruleIDs []string + indexes []int + }{ + "distinct rules": { + ruleIDs: []string{"A", "B", "C"}, + indexes: []int{0, 1, 2}, + }, + "repeated rule": { + ruleIDs: []string{"A", "B", "A", "C"}, + indexes: []int{0, 1, 0, 2}, + }, + } { + t.Run(name, func(t *testing.T) { + obj := &testObject{} + for _, ruleID := range tc.ruleIDs { + obj.Violations = append(obj.Violations, violation{ + ID: ruleID, + Reason: "remediation", + Severity: "IMPORTANT", + }) + } + printer := NewSarifPrinter(map[string]string{ + SarifRuleJSONPathExpressionKey: "violations.#.id", + SarifHelpJSONPathExpressionKey: "violations.#.reason", + SarifSeverityJSONPathExpressionKey: "violations.#.severity", + }, "docker.io/nginx:1.19", SarifPolicyReport) + var out strings.Builder + require.NoError(t, printer.Print(obj, &out)) + report, err := sarif.FromString(out.String(), sarif.WithStrictValidation()) + require.NoError(t, err) + assert.NoError(t, report.Validate()) + require.Len(t, report.Runs, 1) + run := report.Runs[0] + require.Len(t, run.Tool.Driver.Rules, 3) + require.Len(t, run.Results, len(tc.ruleIDs)) + for i, result := range run.Results { + assert.Equal(t, tc.indexes[i], result.RuleIndex) + require.GreaterOrEqual(t, result.RuleIndex, 0) + require.Less(t, result.RuleIndex, len(run.Tool.Driver.Rules)) + assert.Equal(t, result.RuleID, run.Tool.Driver.Rules[result.RuleIndex].ID) + } + }) + } } func TestSarifPrinter_Print_EmptyViolations(t *testing.T) { @@ -98,9 +151,14 @@ func TestSarifPrinter_Print_EmptyViolations(t *testing.T) { err = printer.Print(obj, &out) require.NoError(t, err) + // Strict parsing preserves null arrays so validation checks the emitted output. + report, err := sarif.FromString(out.String(), sarif.WithStrictValidation()) + require.NoError(t, err) + assert.NoError(t, report.Validate()) + // Since the report contains the version, replace it specifically here. exp, err := regexp.Compile(fmt.Sprintf(`"version": "%s"`, version.GetMainVersion())) require.NoError(t, err) output := exp.ReplaceAllString(out.String(), `"version": ""`) - assert.Equal(t, string(expectedOutput), output) + assert.JSONEq(t, string(expectedOutput), output) } diff --git a/pkg/printers/testdata/empty_sarif_report.json b/pkg/printers/testdata/empty_sarif_report.json index 12a809e7d2a13..3d7731ab0b0a6 100644 --- a/pkg/printers/testdata/empty_sarif_report.json +++ b/pkg/printers/testdata/empty_sarif_report.json @@ -1,10 +1,40 @@ { "version": "2.1.0", "$schema": "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/main/sarif-2.1/schema/sarif-schema-2.1.0.json", + "properties": {}, "runs": [ { + "addresses": [], + "graphs": [], + "invocations": [], + "language": "en-US", + "logicalLocations": [], + "newlineSequences": [ + "\r\n", + "\n" + ], + "policies": [], + "redactionTokens": [], + "runAggregates": [], + "taxonomies": [], + "threadFlowLocations": [], + "translations": [], + "versionControlProvenance": [], + "webRequests": [], + "webResponses": [], "tool": { + "extensions": [], "driver": { + "contents": [ + "localizedData", + "nonLocalizedData" + ], + "isComprehensive": false, + "language": "en-US", + "locations": [], + "notifications": [], + "supportedTaxonomies": [], + "taxa": [], "fullName": "roxctl command line utility", "informationUri": "https://github.com/stackrox/stackrox", "name": "roxctl", diff --git a/pkg/printers/testdata/sarif_report.json b/pkg/printers/testdata/sarif_report.json index f397ad866c0d6..3b6c3aad03213 100644 --- a/pkg/printers/testdata/sarif_report.json +++ b/pkg/printers/testdata/sarif_report.json @@ -1,15 +1,46 @@ { + "properties": {}, "version": "2.1.0", "$schema": "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/main/sarif-2.1/schema/sarif-schema-2.1.0.json", "runs": [ { + "addresses": [], + "graphs": [], + "invocations": [], + "language": "en-US", + "logicalLocations": [], + "newlineSequences": [ + "\r\n", + "\n" + ], + "policies": [], + "redactionTokens": [], + "runAggregates": [], + "taxonomies": [], + "threadFlowLocations": [], + "translations": [], + "versionControlProvenance": [], + "webRequests": [], + "webResponses": [], "tool": { + "extensions": [], "driver": { + "contents": [ + "localizedData", + "nonLocalizedData" + ], + "isComprehensive": false, + "language": "en-US", + "locations": [], + "notifications": [], + "supportedTaxonomies": [], + "taxa": [], "fullName": "roxctl command line utility", "informationUri": "https://github.com/stackrox/stackrox", "name": "roxctl", "rules": [ { + "relationships": [], "id": "first-violation", "name": "PolicyViolations", "shortDescription": { @@ -30,6 +61,7 @@ } }, { + "relationships": [], "id": "second-violation", "name": "PolicyViolations", "shortDescription": { @@ -50,6 +82,7 @@ } }, { + "relationships": [], "id": "third-violation", "name": "PolicyViolations", "shortDescription": { @@ -75,19 +108,37 @@ }, "results": [ { + "attachments": [], + "codeFlows": [], + "fixes": [], + "graphTraversals": [], + "graphs": [], + "kind": "fail", + "rank": -1, + "relatedLocations": [], + "stacks": [], + "taxa": [], "ruleId": "first-violation", "ruleIndex": 0, "level": "error", "message": { + "arguments": [], "text": "something about misconfiguration" }, "locations": [ { + "annotations": [], + "id": -1, + "logicalLocations": [], + "relationships": [], "physicalLocation": { "artifactLocation": { + "index": -1, "uri": "docker.io/nginx:1.19" }, "region": { + "byteOffset": -1, + "charOffset": -1, "startLine": 1, "endLine": 1 } @@ -96,19 +147,37 @@ ] }, { + "attachments": [], + "codeFlows": [], + "fixes": [], + "graphTraversals": [], + "graphs": [], + "kind": "fail", + "rank": -1, + "relatedLocations": [], + "stacks": [], + "taxa": [], "ruleId": "second-violation", "ruleIndex": 1, "level": "note", "message": { + "arguments": [], "text": "something about vulnerabilities" }, "locations": [ { + "annotations": [], + "id": -1, + "logicalLocations": [], + "relationships": [], "physicalLocation": { "artifactLocation": { + "index": -1, "uri": "docker.io/nginx:1.19" }, "region": { + "byteOffset": -1, + "charOffset": -1, "startLine": 1, "endLine": 1 } @@ -117,19 +186,37 @@ ] }, { + "attachments": [], + "codeFlows": [], + "fixes": [], + "graphTraversals": [], + "graphs": [], + "kind": "fail", + "rank": -1, + "relatedLocations": [], + "stacks": [], + "taxa": [], "ruleId": "third-violation", "ruleIndex": 2, "level": "error", "message": { + "arguments": [], "text": "something about secrets" }, "locations": [ { + "annotations": [], + "id": -1, + "logicalLocations": [], + "relationships": [], "physicalLocation": { "artifactLocation": { + "index": -1, "uri": "docker.io/nginx:1.19" }, "region": { + "byteOffset": -1, + "charOffset": -1, "startLine": 1, "endLine": 1 } diff --git a/pkg/protocompat/oneof.go b/pkg/protocompat/oneof.go index 96d043e7249c5..aa148cd07926d 100644 --- a/pkg/protocompat/oneof.go +++ b/pkg/protocompat/oneof.go @@ -25,7 +25,7 @@ func GetOneOfTypesByInterface(msgType reflect.Type, oneOfInterfaceType reflect.T oneOfFieldTypes := make([]reflect.Type, 0) // Get proto message from Go reflect type. - msg, ok := reflect.New(msgType).Interface().(proto.Message) + msg, ok := reflect.TypeAssert[proto.Message](reflect.New(msgType)) if !ok { return oneOfFieldTypes } diff --git a/pkg/protoconv/resources/resources.go b/pkg/protoconv/resources/resources.go index e95a3a12736b7..e03afc7e1a050 100644 --- a/pkg/protoconv/resources/resources.go +++ b/pkg/protoconv/resources/resources.go @@ -106,7 +106,7 @@ func SpecToPodTemplateSpec(spec reflect.Value) (v1.PodTemplateSpec, error) { if templateInterface.Type().Kind() == reflect.Pointer && !templateInterface.IsNil() { templateInterface = templateInterface.Elem() } - podTemplate, ok := templateInterface.Interface().(v1.PodTemplateSpec) + podTemplate, ok := reflect.TypeAssert[v1.PodTemplateSpec](templateInterface) if !ok { return v1.PodTemplateSpec{}, errors.New("not a valid PodTemplateSpec") } @@ -356,12 +356,12 @@ func (w *DeploymentWrap) populateReplicas(spec reflect.Value, obj interface{}) { return } - replicasPointer, ok := replicaField.Interface().(*int32) + replicasPointer, ok := reflect.TypeAssert[*int32](replicaField) if ok && replicasPointer != nil { w.Deployment.Replicas = int64(*replicasPointer) } - replicas, ok := replicaField.Interface().(int32) + replicas, ok := reflect.TypeAssert[int32](replicaField) if ok { w.Deployment.Replicas = int64(replicas) } diff --git a/pkg/protoreflect/field.go b/pkg/protoreflect/field.go index c7a97b95069f1..f06ae1d1ed51e 100644 --- a/pkg/protoreflect/field.go +++ b/pkg/protoreflect/field.go @@ -7,7 +7,7 @@ import ( ) func IsProtoMessage(msgType reflect.Type) bool { - _, ok := reflect.New(msgType).Interface().(protocompat.Message) + _, ok := reflect.TypeAssert[protocompat.Message](reflect.New(msgType)) return ok } diff --git a/pkg/registrymirror/store_test.go b/pkg/registrymirror/store_test.go index 1d31e682108ff..705603c959772 100644 --- a/pkg/registrymirror/store_test.go +++ b/pkg/registrymirror/store_test.go @@ -172,6 +172,14 @@ func TestDataRaceAtCleanup(t *testing.T) { s.Cleanup() doneSignal.Signal() wg.Wait() + + // The stopped writers may still have a delayed updateConfigNow scheduled. + // One more update cancels those pending writes and lets us wait for the pipeline to flush, + // so no background write recreates the file after Cleanup(). + source := icspA.Spec.RepositoryDigestMirrors[0].Source + require.NoError(t, s.UpsertImageContentSourcePolicy(icspA)) + require.Eventually(t, func() bool { return fileContains(path, source) }, 5*time.Second, 10*time.Millisecond) + s.Cleanup() } func TestCleanup(t *testing.T) { diff --git a/pkg/renderer/render_test.go b/pkg/renderer/render_test.go index 56e6947578a74..c23521ec0cfe7 100644 --- a/pkg/renderer/render_test.go +++ b/pkg/renderer/render_test.go @@ -1,10 +1,12 @@ package renderer import ( + "fmt" "strings" "testing" v1 "github.com/stackrox/rox/generated/api/v1" + "github.com/stackrox/rox/pkg/helm/charts" "github.com/stackrox/rox/pkg/images/defaults" "github.com/stackrox/rox/pkg/images/defaults/testutils" "github.com/stackrox/rox/pkg/k8sutil" @@ -16,6 +18,56 @@ import ( "k8s.io/apimachinery/pkg/runtime" ) +func TestRenderCentralWorkerEnv(t *testing.T) { + t.Setenv("TEST_VERSIONS", "true") + flavor := testutils.MakeImageFlavorForTest(t) + config := getBaseConfig() + config.Environment = map[string]string{ + "ROX_CENTRAL_WORKER_ENABLED": "true", + "CUSTOM_ENV": "preserved", + } + require.NoError(t, postProcessConfig(&config, renderAll, flavor)) + valuesFiles, err := renderNewHelmValues(config) + require.NoError(t, err) + chartTemplate, err := config.HelmImage.GetCentralServicesChartTemplate() + require.NoError(t, err) + metaValues := charts.GetMetaValuesForFlavor(flavor) + metaValues.KubectlOutput = true + chartFiles, err := chartTemplate.InstantiateRaw(metaValues) + require.NoError(t, err) + + cases := map[string]struct { + enabled bool + expectedEnv []corev1.EnvVar + }{ + "enabled": { + enabled: true, + expectedEnv: []corev1.EnvVar{{Name: "ROX_CENTRAL_WORKER_ENABLED", Value: "true"}}, + }, + "disabled": {}, + } + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + workerValues := zip.NewFile("worker-values.yaml", []byte(fmt.Sprintf("centralWorker:\n enabled: %t\n", tc.enabled)), 0) + files, err := renderHelmChart(chartFiles, renderAll, append(valuesFiles, workerValues)) + require.NoError(t, err) + centralFile := filterCentralFile(files) + require.NotNil(t, centralFile) + deployment := getCentralDeployment(t, centralFile) + require.Len(t, deployment.Spec.Template.Spec.Containers, 1) + envVars := deployment.Spec.Template.Spec.Containers[0].Env + var workerEnv []corev1.EnvVar + for _, envVar := range envVars { + if envVar.Name == "ROX_CENTRAL_WORKER_ENABLED" { + workerEnv = append(workerEnv, envVar) + } + } + assert.Equal(t, tc.expectedEnv, workerEnv) + assert.Contains(t, envVars, corev1.EnvVar{Name: "CUSTOM_ENV", Value: "preserved"}) + }) + } +} + func TestRenderTLSSecretsOnly(t *testing.T) { config := Config{ SecretsByteMap: map[string][]byte{ diff --git a/pkg/renderer/templates/public_values.yaml.tpl b/pkg/renderer/templates/public_values.yaml.tpl index e3b754ae2cb7b..30a40e9942d31 100644 --- a/pkg/renderer/templates/public_values.yaml.tpl +++ b/pkg/renderer/templates/public_values.yaml.tpl @@ -150,6 +150,7 @@ scannerV4: {{- $_ := unset $envVars "ROX_TELEMETRY_ENDPOINT" -}} {{- $_ := unset $envVars "ROX_TELEMETRY_STORAGE_KEY_V1" -}} {{- $_ := unset $envVars "ROX_SCANNER_V4" -}} +{{- $_ := unset $envVars "ROX_CENTRAL_WORKER_ENABLED" -}} {{- if $envVars }} customize: diff --git a/pkg/scanners/scannerv4/convert.go b/pkg/scanners/scannerv4/convert.go index e511b1a605c66..a4869148a29dd 100644 --- a/pkg/scanners/scannerv4/convert.go +++ b/pkg/scanners/scannerv4/convert.go @@ -8,6 +8,8 @@ import ( "strconv" "strings" + "github.com/quay/claircore/rhel/rhcc" + v4 "github.com/stackrox/rox/generated/internalapi/scanner/v4" "github.com/stackrox/rox/generated/storage" "github.com/stackrox/rox/pkg/clair" @@ -21,16 +23,23 @@ import ( "github.com/stackrox/rox/pkg/utils" ) -// vulnDataSourceDelimiter separates the parts of a vuln's datasource. -// IMPORTANT: This delimiter was chosen because it does not appear in any known -// Claircore or StackRox updater names. -const vulnDataSourceDelimiter = "::" +const ( + // vulnDataSourceDelimiter separates the parts of a vuln's datasource. + // IMPORTANT: This delimiter was chosen because it does not appear in any known + // Claircore or StackRox updater names. + vulnDataSourceDelimiter = "::" + // redHatVEXUpdaterName identifies vulnerabilities from Claircore's Red Hat VEX updater. + redHatVEXUpdaterName = "rhel-vex" +) // digitSegment matches contiguous runs of digits for numeric segment comparisons. var digitSegment = regexp.MustCompile(`\d+`) func imageScan(metadata *storage.ImageMetadata, report *v4.VulnerabilityReport, scannerVersion string) *storage.ImageScan { layerSHAToIndex := clair.BuildSHAToIndexMap(metadata) + if features.ScannerV4RedHatLayers.Enabled() { + filterRedHatLayerVulnerabilities(report, layerSHAToIndex) + } if features.ScannerV4RedHatVEXNotAffected.Enabled() { filterNotAffectedVulnerabilities(report, layerSHAToIndex) } @@ -50,14 +59,7 @@ func imageScan(metadata *storage.ImageMetadata, report *v4.VulnerabilityReport, } func componentsWithLayerMap(metadata *storage.ImageMetadata, report *v4.VulnerabilityReport, layerSHAToIndex map[string]int32) []*storage.EmbeddedImageScanComponent { - pkgs := report.GetContents().GetPackages() - if len(pkgs) == 0 { - pkgs = make(map[string]*v4.Package, len(report.GetContents().GetPackagesDEPRECATED())) - // Fallback to the deprecated slice, if needed. - for _, pkg := range report.GetContents().GetPackagesDEPRECATED() { - pkgs[pkg.GetId()] = pkg - } - } + pkgs := packagesByID(report.GetContents()) // Filter out packages that should not become user-facing components. // Unreferenced source packages are kept defensively. dedupe := features.ScannerV4Dedupe.Enabled() @@ -149,6 +151,21 @@ func envOS(env *v4.Environment, report *v4.VulnerabilityReport) string { return dist.GetDid() + ":" + dist.GetVersionId() } +// packagesByID returns the current package map, falling back to the deprecated +// package list when the map is empty. +func packagesByID(contents *v4.Contents) map[string]*v4.Package { + packages := contents.GetPackages() + if len(packages) > 0 { + return packages + } + + packages = make(map[string]*v4.Package, len(contents.GetPackagesDEPRECATED())) + for _, pkg := range contents.GetPackagesDEPRECATED() { + packages[pkg.GetId()] = pkg + } + return packages +} + // environmentList returns the *v4.Environment_List associated with the given // package ID, falling back to the deprecated environments map if the // current one is unset. @@ -577,6 +594,51 @@ func getPackageLayerIndex(report *v4.VulnerabilityReport, layerSHAToIndex map[st return layerIdx, ok } +// filterRedHatLayerVulnerabilities removes non-Red Hat VEX findings at or below the +// highest RHCC-marked ancestry layer. Findings above it are unchanged. +func filterRedHatLayerVulnerabilities(report *v4.VulnerabilityReport, layerSHAToIndex map[string]int32) { + contents := report.GetContents() + if len(layerSHAToIndex) == 0 || contents == nil { + return + } + + packages := packagesByID(contents) + maxLayerIdx := int32(-1) + for pkgID, pkg := range packages { + if pkg == nil || pkg.GetKind() != "ancestry" || !packageHasRepositoryKey(report, pkgID, rhcc.RepositoryKey) { + continue + } + layerIdx, ok := getPackageLayerIndex(report, layerSHAToIndex, pkgID) + if !ok { + continue + } + if layerIdx > maxLayerIdx { + maxLayerIdx = layerIdx + } + } + if maxLayerIdx < 0 { + return + } + + for pkgID, vulnIDs := range report.GetPackageVulnerabilities() { + pkgLayerIdx, ok := getPackageLayerIndex(report, layerSHAToIndex, pkgID) + if !ok || pkgLayerIdx > maxLayerIdx { + continue + } + filtered := make([]string, 0, len(vulnIDs.GetValues())) + for _, vulnID := range vulnIDs.GetValues() { + if report.GetVulnerabilities()[vulnID].GetUpdater() == redHatVEXUpdaterName { + filtered = append(filtered, vulnID) + } + } + if len(filtered) == 0 { + delete(report.PackageVulnerabilities, pkgID) //nolint:protogetter // mutation requires direct field access + } else { + report.PackageVulnerabilities[pkgID] = &v4.StringList{Values: filtered} + } + } +} + // filterNotAffectedVulnerabilities removes vulnerabilities from PackageVulnerabilities // when they are covered by VEX not-affected assertions via AncestryPackage entries. // A package is covered if it was introduced at or below the AncestryPackage's layer @@ -661,14 +723,7 @@ func filterNotAffectedVulnerabilities(report *v4.VulnerabilityReport, layerSHATo // PackageVulnerabilities when a rhel-vex updater vulnerability shares a // CVE alias at or above the OSV package's layer. func filterOSVSupersededByRedHatVEX(report *v4.VulnerabilityReport, layerSHAToIndex map[string]int32) { - const ( - osvUpdaterPrefix = "osv/" - redHatVEXUpdaterName = "rhel-vex" - // redHatContainerRepositoryKey matches - // github.com/quay/claircore/rhel/rhcc.RepositoryKey: it is set on - // every Repository indexed by claircore's RHCC (container) ecosystem. - redHatContainerRepositoryKey = "rhcc-container-repository" - ) + const osvUpdaterPrefix = "osv/" if len(report.GetPackageVulnerabilities()) == 0 { return @@ -679,7 +734,7 @@ func filterOSVSupersededByRedHatVEX(report *v4.VulnerabilityReport, layerSHAToIn aliasKeyToLayerIndex := make(map[string]int32) for pkgID, vulnIDs := range report.GetPackageVulnerabilities() { // Only consider OCI (RHCC) packages. - if !packageHasRepositoryKey(report, pkgID, redHatContainerRepositoryKey) { + if !packageHasRepositoryKey(report, pkgID, rhcc.RepositoryKey) { continue } diff --git a/pkg/scanners/scannerv4/convert_test.go b/pkg/scanners/scannerv4/convert_test.go index 9ad37ca82d9b2..98c6964be66b9 100644 --- a/pkg/scanners/scannerv4/convert_test.go +++ b/pkg/scanners/scannerv4/convert_test.go @@ -1847,6 +1847,177 @@ func TestNotes(t *testing.T) { } } +func TestFilterRedHatLayerVulnerabilities(t *testing.T) { + layers := map[string]int32{ + "base": 0, "rh-base": 1, "middle": 2, "rh-top": 3, "user": 4, + } + newReport := func() *v4.VulnerabilityReport { + packages := map[string]*v4.Package{} + environments := map[string]*v4.Environment_List{} + pkgVulns := map[string]*v4.StringList{} + for layer := range layers { + packages[layer] = &v4.Package{Id: layer, Kind: "binary"} + environments[layer] = &v4.Environment_List{Environments: []*v4.Environment{{IntroducedIn: layer}}} + pkgVulns[layer] = &v4.StringList{Values: []string{"osv", "redhat"}} + } + // These layers have only OSV findings. + pkgVulns["base"] = &v4.StringList{Values: []string{"osv"}} + pkgVulns["middle"] = &v4.StringList{Values: []string{"osv"}} + for _, layer := range []string{"rh-base", "rh-top"} { + id := layer + "-ancestry" + packages[id] = &v4.Package{Id: id, Kind: "ancestry"} + environments[id] = &v4.Environment_List{Environments: []*v4.Environment{{IntroducedIn: layer, RepositoryIds: []string{"rhcc"}}}} + } + return &v4.VulnerabilityReport{ + Contents: &v4.Contents{ + Packages: packages, + Repositories: map[string]*v4.Repository{"rhcc": {Key: "rhcc-container-repository"}}, + Environments: environments, + }, + Vulnerabilities: map[string]*v4.VulnerabilityReport_Vulnerability{ + "osv": {Updater: "osv/go"}, "redhat": {Updater: "rhel-vex"}, + }, + PackageVulnerabilities: pkgVulns, + } + } + + tests := map[string]struct { + prepare func(*v4.VulnerabilityReport) + indices map[string]int32 + filtered []string + }{ + "unmarked layer between Red Hat markers needs no VEX assertion": { + indices: layers, filtered: []string{"base", "rh-base", "middle", "rh-top"}, + }, + "single marker includes every lower layer": { + prepare: func(r *v4.VulnerabilityReport) { delete(r.GetContents().GetEnvironments(), "rh-top-ancestry") }, + indices: layers, filtered: []string{"base", "rh-base"}, + }, + "RHCC-marked binary above ancestry does not extend boundary": { + prepare: func(r *v4.VulnerabilityReport) { r.Contents.Packages["rh-top-ancestry"].Kind = "binary" }, + indices: layers, filtered: []string{"base", "rh-base"}, + }, + "RHCC-marked binaries without ancestry leave findings alone": { + prepare: func(r *v4.VulnerabilityReport) { + r.Contents.Packages["rh-base-ancestry"].Kind = "binary" + r.Contents.Packages["rh-top-ancestry"].Kind = "binary" + }, + indices: layers, + }, + "top marker includes unmarked lower layers": { + prepare: func(r *v4.VulnerabilityReport) { delete(r.GetContents().GetEnvironments(), "rh-base-ancestry") }, + indices: layers, filtered: []string{"base", "rh-base", "middle", "rh-top"}, + }, + "marker in layer zero": { + prepare: func(r *v4.VulnerabilityReport) { + delete(r.GetContents().GetEnvironments(), "rh-top-ancestry") + r.Contents.Environments["rh-base-ancestry"].Environments[0].IntroducedIn = "base" + }, + indices: layers, filtered: []string{"base"}, + }, + "legacy Gold Repository marker uses RHCC key": { + prepare: func(r *v4.VulnerabilityReport) { + r.Contents.Repositories["rhcc"] = &v4.Repository{ + Name: "Red Hat Container Catalog", + Uri: "https://catalog.redhat.com/software/containers/explore", + Key: "rhcc-container-repository", + } + }, + indices: layers, filtered: []string{"base", "rh-base", "middle", "rh-top"}, + }, + "no Red Hat marker leaves findings alone": { + prepare: func(r *v4.VulnerabilityReport) { + delete(r.GetContents().GetEnvironments(), "rh-base-ancestry") + delete(r.GetContents().GetEnvironments(), "rh-top-ancestry") + }, + indices: layers, + }, + "orphaned environments do not mark Red Hat layers": { + prepare: func(r *v4.VulnerabilityReport) { + delete(r.GetContents().GetPackages(), "rh-base-ancestry") + delete(r.GetContents().GetPackages(), "rh-top-ancestry") + }, + indices: layers, + }, + "deprecated environments retain the boundary": { + prepare: func(r *v4.VulnerabilityReport) { + for _, pkg := range r.GetContents().GetPackages() { + r.Contents.PackagesDEPRECATED = append(r.Contents.PackagesDEPRECATED, pkg) + } + r.Contents.Packages = nil + r.Contents.EnvironmentsDEPRECATED = r.GetContents().GetEnvironments() + r.Contents.Environments = nil + }, + indices: layers, filtered: []string{"base", "rh-base", "middle", "rh-top"}, + }, + "SBOM without image layer order leaves findings alone": { + indices: nil, + }, + } + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + report := newReport() + if tc.prepare != nil { + tc.prepare(report) + } + filterRedHatLayerVulnerabilities(report, tc.indices) + filtered := make(map[string]bool) + for _, layer := range tc.filtered { + filtered[layer] = true + } + for layer := range layers { + want := []string{"osv", "redhat"} + if layer == "base" || layer == "middle" { + want = []string{"osv"} + } + if filtered[layer] { + want = []string{"redhat"} + if layer == "base" || layer == "middle" { + want = nil + } + } + assert.Equal(t, want, report.GetPackageVulnerabilities()[layer].GetValues(), "layer %s", layer) + if want == nil { + assert.NotContains(t, report.GetPackageVulnerabilities(), layer) + } + } + }) + } +} + +func TestImageScanRedHatLayerFlag(t *testing.T) { + newReport := func() *v4.VulnerabilityReport { + return &v4.VulnerabilityReport{ + Contents: &v4.Contents{ + Packages: map[string]*v4.Package{ + "marker": {Kind: "ancestry"}, + "package": {Kind: "binary"}, + }, + Repositories: map[string]*v4.Repository{"rhcc": {Key: "rhcc-container-repository"}}, + Environments: map[string]*v4.Environment_List{ + "marker": {Environments: []*v4.Environment{{IntroducedIn: "layer", RepositoryIds: []string{"rhcc"}}}}, + "package": {Environments: []*v4.Environment{{IntroducedIn: "layer"}}}, + }, + }, + Vulnerabilities: map[string]*v4.VulnerabilityReport_Vulnerability{"osv": {Updater: "osv/go"}}, + PackageVulnerabilities: map[string]*v4.StringList{"package": {Values: []string{"osv"}}}, + } + } + metadata := &storage.ImageMetadata{ + V1: &storage.V1Metadata{Layers: []*storage.ImageLayer{{}}}, + LayerShas: []string{"layer"}, + } + + testutils.MustUpdateFeature(t, features.ScannerV4RedHatLayers, true) + imageReport := newReport() + imageScan(metadata, imageReport, scannerVersion) + assert.Empty(t, imageReport.GetPackageVulnerabilities()) + + sbomReport := newReport() + imageScan(nil, sbomReport, scannerVersion) + assert.Equal(t, []string{"osv"}, sbomReport.GetPackageVulnerabilities()["package"].GetValues()) +} + func TestFilterNotAffectedVulnerabilities(t *testing.T) { // Layer SHAs for test scenarios: // layer0 (index 0) - base layer diff --git a/pkg/scanners/scannerv4/nodescan_convert.go b/pkg/scanners/scannerv4/nodescan_convert.go index ade2eb0b28650..307e7f5ae4d41 100644 --- a/pkg/scanners/scannerv4/nodescan_convert.go +++ b/pkg/scanners/scannerv4/nodescan_convert.go @@ -13,7 +13,8 @@ import ( ) var ( - rhcosOSImageRegexp = regexp.MustCompile(`(Red Hat Enterprise Linux) (CoreOS) ([\d])([\d]+)`) + rhcosOSImageRegexp = regexp.MustCompile(`Red Hat Enterprise Linux CoreOS (4)(\d+)`) + rhcosDottedOSImageRegexp = regexp.MustCompile(`Red Hat Enterprise Linux CoreOS (\d+)\.(\d+)\b`) ) const ( @@ -40,21 +41,18 @@ func toNodeScan(r *v4.VulnerabilityReport, osImageRef string) *storage.NodeScan func toOperatingSystem(ref string) string { r := rhcosOSImageRegexp.FindStringSubmatch(ref) - if len(r) != 5 { - return "" + if len(r) == 3 { + return fmt.Sprintf("rhcos:%s.%s", r[1], r[2]) } - return fmt.Sprintf("rhcos:%s.%s", r[3], r[4]) + r = rhcosDottedOSImageRegexp.FindStringSubmatch(ref) + if len(r) == 3 { + return fmt.Sprintf("rhcos:%s.%s", r[1], r[2]) + } + return "" } func toStorageComponents(r *v4.VulnerabilityReport) []*storage.EmbeddedNodeScanComponent { - packages := r.GetContents().GetPackages() - if len(packages) == 0 { - packages = make(map[string]*v4.Package, len(r.GetContents().GetPackagesDEPRECATED())) - // Fallback to the deprecated slice, if needed. - for _, pkg := range r.GetContents().GetPackagesDEPRECATED() { - packages[pkg.GetId()] = pkg - } - } + packages := packagesByID(r.GetContents()) result := make([]*storage.EmbeddedNodeScanComponent, 0, len(packages)) for id, pkg := range packages { diff --git a/pkg/scanners/scannerv4/nodescan_convert_test.go b/pkg/scanners/scannerv4/nodescan_convert_test.go index f7d5417209736..220503b4efe7b 100644 --- a/pkg/scanners/scannerv4/nodescan_convert_test.go +++ b/pkg/scanners/scannerv4/nodescan_convert_test.go @@ -251,6 +251,26 @@ func (s *indexReportConvertSuite) TestToOperatingSystem() { in: "Red Hat Enterprise Linux CoreOS 41712345.94.2024", expected: "rhcos:4.1712345", }, + "legacy single-digit minor version": { + in: "Red Hat Enterprise Linux CoreOS 41.84.201907030753-0", + expected: "rhcos:4.1", + }, + "RHCOS 9.6 (OCP 4.19)": { + in: "Red Hat Enterprise Linux CoreOS 9.6.20250701-0 (Plow)", + expected: "rhcos:9.6", + }, + "RHCOS 9.8": { + in: "Red Hat Enterprise Linux CoreOS 9.8.20260908-0 (Plow)", + expected: "rhcos:9.8", + }, + "RHCOS 10.1": { + in: "Red Hat Enterprise Linux CoreOS 10.1.20260908-0 (Plow)", + expected: "rhcos:10.1", + }, + "RHCOS dotted version without build suffix": { + in: "Red Hat Enterprise Linux CoreOS 9.8 (Plow)", + expected: "rhcos:9.8", + }, "non-RHCOS": { in: "Oracle Linux Server release 6.8", expected: "", diff --git a/pkg/scanners/scannerv4/vm_convert.go b/pkg/scanners/scannerv4/vm_convert.go index 7c8a553bd72fc..93042ef3713b3 100644 --- a/pkg/scanners/scannerv4/vm_convert.go +++ b/pkg/scanners/scannerv4/vm_convert.go @@ -53,13 +53,7 @@ func hasValidCPE(repositories map[string]*v4.Repository, environments map[string func toVirtualMachineComponents(r *v4.VulnerabilityReport) []*storage.EmbeddedVirtualMachineScanComponent { repositories := r.GetContents().GetRepositories() environments := r.GetContents().GetEnvironments() - packages := r.GetContents().GetPackages() - if len(packages) == 0 { - packages = make(map[string]*v4.Package, len(r.GetContents().GetPackagesDEPRECATED())) - for _, pkg := range r.GetContents().GetPackagesDEPRECATED() { - packages[pkg.GetId()] = pkg - } - } + packages := packagesByID(r.GetContents()) result := make([]*storage.EmbeddedVirtualMachineScanComponent, 0, len(packages)) for id, pkg := range packages { vulnerabilityIDs := r.GetPackageVulnerabilities()[id].GetValues() diff --git a/pkg/scannerv4/mappers/mappers.go b/pkg/scannerv4/mappers/mappers.go index 1b3edc59b8005..78df19c2148ca 100644 --- a/pkg/scannerv4/mappers/mappers.go +++ b/pkg/scannerv4/mappers/mappers.go @@ -19,7 +19,6 @@ import ( "github.com/quay/claircore" "github.com/quay/claircore/enricher/epss" "github.com/quay/claircore/enricher/kev" - "github.com/quay/claircore/rhel/rhcc" "github.com/quay/claircore/rhel/vex" "github.com/quay/claircore/toolkit/types" "github.com/quay/claircore/toolkit/types/cpe" @@ -91,13 +90,6 @@ var ( // Catchall regexp.MustCompile(`[A-Z]+-\d{4}[-:]\d+`), } - - // vexUpdater is the name of the Red Hat VEX updater used by Claircore. - vexUpdater = (*vex.Updater)(nil).Name() - // rhccRepoName is the name of the "Gold Repository". - rhccRepoName = rhcc.GoldRepo.Name - // rhccRepoURI is the URI of the "Gold Repository". - rhccRepoURI = rhcc.GoldRepo.URI ) // ToProtoV4IndexReport maps claircore.IndexReport to v4.IndexReport. @@ -123,7 +115,6 @@ func ToProtoV4VulnerabilityReport(ctx context.Context, r *claircore.Vulnerabilit return nil, nil } filterPackages(r) - filterVulnerabilities(r) nvdVulns, err := nvdVulnerabilities(r.Enrichments) if err != nil { return nil, fmt.Errorf("internal error: parsing nvd vulns: %w", err) @@ -1177,106 +1168,6 @@ func filterPackages(report *claircore.VulnerabilityReport) { } } -// filterVulnerabilities filters out vulnerabilities from the given vulnerability report. -// Note: This function only modifies the report's PackageVulnerabilities map. -// It is non-trivial to remove all unreferenced vulnerabilities from the report's -// Vulnerabilities map, so we leave it untouched and accept we may send Scanner V4 clients -// extra vulnerabilities. It is up to the client to handle this. -func filterVulnerabilities(report *claircore.VulnerabilityReport) { - // We only filter out non-Red Hat vulnerabilities found in Red Hat layers (if configured to do so) at this time. - if !features.ScannerV4RedHatLayers.Enabled() { - return - } - - redhatLayers := rhccLayers(report) - if redhatLayers.IsEmpty() { - // This image is neither an official Red Hat image nor based on one, so nothing to do here. - return - } - for pkgID, pkg := range report.Packages { - // Sanity check. - if pkg == nil { - continue - } - - envs, found := report.Environments[pkgID] - if !found { - // Did not find a related environment, so we cannot determine the layer. - continue - } - - // Just use the first environment. - // It is possible there are multiple environments associated with this package; - // however, for our purposes, we only need the first one, - // as the layer index will always be the same between different environments. - // Quay does this, too: https://github.com/quay/quay/blob/v3.10.3/data/secscan_model/secscan_v4_model.py#L583. - // We also do this in our own client code. - env := envs[0] - // If this package was introduced in a non-Red Hat layer, skip it. - if !redhatLayers.Contains(env.IntroducedIn.String()) { - continue - } - - var n int - for _, vulnID := range report.PackageVulnerabilities[pkgID] { - vuln := report.Vulnerabilities[vulnID] - // Sanity check. - if vuln == nil { - continue - } - - // If the vulnerability did not come from Red Hat's VEX data, then skip it. - if vuln.Updater != vexUpdater { - continue - } - - report.PackageVulnerabilities[pkgID][n] = vulnID - n++ - } - - // Hint to the GC the filtered vulnerabilities are no longer needed. - clear(report.PackageVulnerabilities[pkgID][n:]) - report.PackageVulnerabilities[pkgID] = report.PackageVulnerabilities[pkgID][:n:n] - - // If there aren't any vulnerabilities from Red Hat's VEX data, - // then delete the whole entry. - if n == 0 { - delete(report.PackageVulnerabilities, pkgID) - } - } -} - -// rhccLayers returns a set of SHAs for the layers in official Red Hat images. -// TODO(ROX-29158): account for images built via Konflux, as they do not contain the same identifiers -// as the old build system. -func rhccLayers(report *claircore.VulnerabilityReport) set.FrozenStringSet { - layers := set.NewStringSet() - - var rhccID string - for id, repo := range report.Repositories { - if repo.Name == rhccRepoName && repo.URI == rhccRepoURI { - rhccID = id - break - } - } - if rhccID == "" { - // Not an official Red Hat image nor based on one. - return layers.Freeze() - } - - for _, envs := range report.Environments { - for _, env := range envs { - for _, repoID := range env.RepositoryIDs { - if repoID == rhccID { - layers.Add(env.IntroducedIn.String()) - } - } - } - } - - return layers.Freeze() -} - // pkgFixedBy unmarshals and returns the package-fixed-by enrichment, if it exists. func pkgFixedBy(enrichments map[string][]json.RawMessage) (map[string]string, error) { enrichmentsList := enrichments[fixedby.Type] diff --git a/pkg/scannerv4/mappers/mappers_test.go b/pkg/scannerv4/mappers/mappers_test.go index c4d734fe57111..e46084b043fb4 100644 --- a/pkg/scannerv4/mappers/mappers_test.go +++ b/pkg/scannerv4/mappers/mappers_test.go @@ -2,9 +2,7 @@ package mappers import ( "encoding/json" - "slices" "strconv" - "strings" "testing" "time" @@ -471,361 +469,6 @@ func Test_ToProtoV4VulnerabilityReport_FilterNodeJS(t *testing.T) { } } -func TestToProtoV4VulnerabilityReport_FilterRHCCLayers(t *testing.T) { - testutils.MustUpdateFeature(t, features.ScannerV4RedHatLayers, true) - - layerA := claircore.MustParseDigest("sha256:" + strings.Repeat("a", 64)) - layerB := claircore.MustParseDigest("sha256:" + strings.Repeat("b", 64)) - - tests := map[string]struct { - arg *claircore.VulnerabilityReport - want *v4.VulnerabilityReport - wantErr string - }{ - "filter non-RPM packages in Red Hat layers": { - arg: &claircore.VulnerabilityReport{ - Hash: claircore.MustParseDigest("sha256:9124cd5256c6d674f6b11a4d01fea8148259be1f66ca2cf9dfbaafc83c31874e"), - Vulnerabilities: map[string]*claircore.Vulnerability{ - "0": { - ID: "0", - Name: "0", - Updater: "rhel-vex", - }, - "1": { - ID: "1", - Name: "1", - Updater: "rhel-vex", - }, - "2": { - ID: "2", - Name: "2", - Updater: "something else", - }, - "3": { - ID: "3", - Name: "3", - Updater: "something different", - }, - }, - Packages: map[string]*claircore.Package{ - "0": { - ID: "0", - Name: "my go binary", - Version: "0", - }, - "1": { - ID: "1", - Name: "my java jar", - Version: "1", - }, - "2": { - ID: "2", - Name: "my python egg", - Version: "2", - }, - "3": { - ID: "3", - Name: "my ruby gem", - Version: "3", - }, - }, - Repositories: map[string]*claircore.Repository{ - "0": { - ID: "0", - Name: "Red Hat Container Catalog", - URI: `https://catalog.redhat.com/software/containers/explore`, - }, - "something else": { - ID: "1", - Name: "something else", - Key: "rhel-cpe-repository", - URI: "somethingelse.com", - }, - }, - Environments: map[string][]*claircore.Environment{ - "0": { - { - RepositoryIDs: []string{"0", "something else"}, - IntroducedIn: layerA, - }, - }, - "1": { - { - RepositoryIDs: []string{"something else"}, - IntroducedIn: layerB, - }, - }, - "2": { - { - RepositoryIDs: []string{"0"}, - IntroducedIn: layerA, - }, - }, - "3": { - { - RepositoryIDs: []string{"something else"}, - IntroducedIn: layerB, - }, - }, - }, - PackageVulnerabilities: map[string][]string{ - "0": {"2", "0", "3", "1"}, - "1": {"1", "2"}, - "2": {"2", "3"}, - "3": {"0", "1", "2", "3"}, - }, - }, - want: &v4.VulnerabilityReport{ - // Converter doesn't set HashId to empty. - HashId: "", - Vulnerabilities: map[string]*v4.VulnerabilityReport_Vulnerability{ - "0": { - Id: "0", - Name: "0", - Updater: "rhel-vex", - }, - "1": { - Id: "1", - Name: "1", - Updater: "rhel-vex", - }, - "2": { - Id: "2", - Name: "2", - Updater: "something else", - }, - "3": { - Id: "3", - Name: "3", - Updater: "something different", - }, - }, - PackageVulnerabilities: map[string]*v4.StringList{ - "0": { - Values: []string{"0", "1"}, - }, - "1": { - Values: []string{"1", "2"}, - }, - "3": { - Values: []string{"0", "1", "2", "3"}, - }, - }, - Contents: &v4.Contents{ - Packages: map[string]*v4.Package{ - "0": { - Id: "0", - Name: "my go binary", - Version: "0", - NormalizedVersion: &v4.NormalizedVersion{ - V: []int32{0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - }, - Kind: "unknown", - Cpe: emptyCPE, - }, - "1": { - Id: "1", - Name: "my java jar", - Version: "1", - NormalizedVersion: &v4.NormalizedVersion{ - V: []int32{0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - }, - Kind: "unknown", - Cpe: emptyCPE, - }, - "2": { - Id: "2", - Name: "my python egg", - Version: "2", - NormalizedVersion: &v4.NormalizedVersion{ - V: []int32{0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - }, - Kind: "unknown", - Cpe: emptyCPE, - }, - "3": { - Id: "3", - Name: "my ruby gem", - Version: "3", - NormalizedVersion: &v4.NormalizedVersion{ - V: []int32{0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - }, - Kind: "unknown", - Cpe: emptyCPE, - }, - }, - PackagesDEPRECATED: []*v4.Package{ - { - Id: "0", - Name: "my go binary", - Version: "0", - NormalizedVersion: &v4.NormalizedVersion{ - V: []int32{0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - }, - Kind: "unknown", - Cpe: emptyCPE, - }, - { - Id: "1", - Name: "my java jar", - Version: "1", - NormalizedVersion: &v4.NormalizedVersion{ - V: []int32{0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - }, - Kind: "unknown", - Cpe: emptyCPE, - }, - { - Id: "2", - Name: "my python egg", - Version: "2", - NormalizedVersion: &v4.NormalizedVersion{ - V: []int32{0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - }, - Kind: "unknown", - Cpe: emptyCPE, - }, - { - Id: "3", - Name: "my ruby gem", - Version: "3", - NormalizedVersion: &v4.NormalizedVersion{ - V: []int32{0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - }, - Kind: "unknown", - Cpe: emptyCPE, - }, - }, - Repositories: map[string]*v4.Repository{ - "0": { - Id: "0", - Name: "Red Hat Container Catalog", - Uri: `https://catalog.redhat.com/software/containers/explore`, - Cpe: emptyCPE, - }, - "something else": { - Id: "1", - Name: "something else", - Key: "rhel-cpe-repository", - Uri: "somethingelse.com", - Cpe: emptyCPE, - }, - }, - RepositoriesDEPRECATED: []*v4.Repository{ - { - Id: "0", - Name: "Red Hat Container Catalog", - Uri: `https://catalog.redhat.com/software/containers/explore`, - Cpe: emptyCPE, - }, - { - Id: "1", - Name: "something else", - Key: "rhel-cpe-repository", - Uri: "somethingelse.com", - Cpe: emptyCPE, - }, - }, - Environments: map[string]*v4.Environment_List{ - "0": { - Environments: []*v4.Environment{ - { - RepositoryIds: []string{"0", "something else"}, - IntroducedIn: layerA.String(), - }, - }, - }, - "1": { - Environments: []*v4.Environment{ - { - RepositoryIds: []string{"something else"}, - IntroducedIn: layerB.String(), - }, - }, - }, - "2": { - Environments: []*v4.Environment{ - { - RepositoryIds: []string{"0"}, - IntroducedIn: layerA.String(), - }, - }, - }, - "3": { - Environments: []*v4.Environment{ - { - RepositoryIds: []string{"something else"}, - IntroducedIn: layerB.String(), - }, - }, - }, - }, - EnvironmentsDEPRECATED: map[string]*v4.Environment_List{ - "0": { - Environments: []*v4.Environment{ - { - RepositoryIds: []string{"0", "1"}, - IntroducedIn: layerA.String(), - }, - }, - }, - "1": { - Environments: []*v4.Environment{ - { - RepositoryIds: []string{"1"}, - IntroducedIn: layerB.String(), - }, - }, - }, - "2": { - Environments: []*v4.Environment{ - { - RepositoryIds: []string{"0"}, - IntroducedIn: layerA.String(), - }, - }, - }, - "3": { - Environments: []*v4.Environment{ - { - RepositoryIds: []string{"1"}, - IntroducedIn: layerB.String(), - }, - }, - }, - }, - }, - }, - wantErr: "", - }, - } - for name, tt := range tests { - t.Run(name, func(t *testing.T) { - ctx := test.Logging(t) - got, err := ToProtoV4VulnerabilityReport(ctx, tt.arg) - if tt.wantErr != "" { - assert.ErrorContains(t, err, tt.wantErr) - return - } - assert.NoError(t, err) - - // The assert library cannot compare elements in slices like the ones below - // while ignoring order. So, sort each slice. - for _, pkgVulns := range got.GetPackageVulnerabilities() { - slices.Sort(pkgVulns.GetValues()) - } - slices.SortFunc(got.GetContents().GetPackagesDEPRECATED(), func(a, b *v4.Package) int { - return strings.Compare(a.GetId(), b.GetId()) - }) - slices.SortFunc(got.GetContents().GetRepositoriesDEPRECATED(), func(a, b *v4.Repository) int { - return strings.Compare(a.GetId(), b.GetId()) - }) - - protoassert.Equal(t, tt.want, got) - }) - } -} - func Test_ToClairCoreIndexReport(t *testing.T) { tests := map[string]struct { arg *v4.Contents @@ -1063,6 +706,28 @@ func Test_ToClairCoreIndexReport(t *testing.T) { } } +func TestDeprecatedIndexContentsBecomeCurrentVulnerabilityContents(t *testing.T) { + const layerSHA = "sha256:9124cd5256c6d674f6b11a4d01fea8148259be1f66ca2cf9dfbaafc83c31874e" + index, err := ToClairCoreIndexReport(&v4.Contents{ + PackagesDEPRECATED: []*v4.Package{{Id: "marker", Name: "image", Kind: "ancestry"}}, + RepositoriesDEPRECATED: []*v4.Repository{{Id: "rhcc", Key: "rhcc-container-repository"}}, + EnvironmentsDEPRECATED: map[string]*v4.Environment_List{ + "marker": {Environments: []*v4.Environment{{IntroducedIn: layerSHA, RepositoryIds: []string{"rhcc"}}}}, + }, + }) + require.NoError(t, err) + + report, err := ToProtoV4VulnerabilityReport(test.Logging(t), &claircore.VulnerabilityReport{ + Packages: index.Packages, + Repositories: index.Repositories, + Environments: index.Environments, + }) + require.NoError(t, err) + require.Contains(t, report.GetContents().GetPackages(), "marker") + require.Equal(t, "rhcc-container-repository", report.GetContents().GetRepositories()["rhcc"].GetKey()) + require.Equal(t, layerSHA, report.GetContents().GetEnvironments()["marker"].GetEnvironments()[0].GetIntroducedIn()) +} + func Test_toProtoV4Package(t *testing.T) { tests := []struct { name string diff --git a/pkg/search/predicate/base_predicates.go b/pkg/search/predicate/base_predicates.go index 1497c46ed3935..556695794f25e 100644 --- a/pkg/search/predicate/base_predicates.go +++ b/pkg/search/predicate/base_predicates.go @@ -38,7 +38,7 @@ func createBasePredicate(fullPath string, fieldType reflect.Type, value string) case reflect.Bool: return createBoolPredicate(fullPath, value) case reflect.Int64, reflect.Int32, reflect.Int16, reflect.Int8, reflect.Int: - if enum, ok := reflect.Zero(fieldType).Interface().(protoreflect.ProtoEnum); ok { + if enum, ok := reflect.TypeAssert[protoreflect.ProtoEnum](reflect.Zero(fieldType)); ok { return createEnumPredicate(fullPath, value, enum) } return createIntPredicate(fullPath, value) diff --git a/pkg/search/predicate/timestamp_predicate.go b/pkg/search/predicate/timestamp_predicate.go index 2dd98eb589f75..18aea5f5e070e 100644 --- a/pkg/search/predicate/timestamp_predicate.go +++ b/pkg/search/predicate/timestamp_predicate.go @@ -14,7 +14,7 @@ func createTimestampPredicate(fullPath, value string) (internalPredicate, error) return nil, err } return internalPredicateFunc(func(instance reflect.Value) (*search.Result, bool) { - instanceTS, ok := instance.Interface().(*protocompat.Timestamp) + instanceTS, ok := reflect.TypeAssert[*protocompat.Timestamp](instance) if ok && baseMatcher(instanceTS) { return &search.Result{ diff --git a/pkg/search/walker.go b/pkg/search/walker.go index 2a4a3df0e3571..0e6262a8617a5 100644 --- a/pkg/search/walker.go +++ b/pkg/search/walker.go @@ -137,7 +137,7 @@ func (s *searchWalker) walkRecursive(prefix string, original reflect.Type) v1.Se case reflect.Bool: return v1.SearchDataType_SEARCH_BOOL case reflect.Uint32, reflect.Uint64, reflect.Int32, reflect.Int64, reflect.Float32, reflect.Float64: - enum, ok := reflect.Zero(original).Interface().(protoreflect.ProtoEnum) + enum, ok := reflect.TypeAssert[protoreflect.ProtoEnum](reflect.Zero(original)) if !ok { return v1.SearchDataType_SEARCH_NUMERIC } diff --git a/pkg/signatures/cosign_sig_fetcher.go b/pkg/signatures/cosign_sig_fetcher.go index ae3ac21bffd84..468214b06e37b 100644 --- a/pkg/signatures/cosign_sig_fetcher.go +++ b/pkg/signatures/cosign_sig_fetcher.go @@ -121,12 +121,12 @@ func (c *cosignSignatureFetcher) FetchSignatures(ctx context.Context, image *sto if tagErr == nil { allPayloads = append(allPayloads, tagPayloads...) } else { - log.Warnf("Tag-based discovery failed for %q: %v", fullImageName, tagErr) + log.Debugf("Tag-based discovery failed for %q: %v", fullImageName, tagErr) } if referrerErr == nil { allPayloads = append(allPayloads, referrerPayloads...) } else { - log.Warnf("Referrer-based discovery failed for %q: %v", fullImageName, referrerErr) + log.Debugf("Referrer-based discovery failed for %q: %v", fullImageName, referrerErr) } // Return an error only when both discovery paths failed. diff --git a/pkg/telemetry/data/data_test.go b/pkg/telemetry/data/data_test.go index 1f9b94d47d909..2f8850001fe5b 100644 --- a/pkg/telemetry/data/data_test.go +++ b/pkg/telemetry/data/data_test.go @@ -26,7 +26,7 @@ func validateJSONFieldNames(t *testing.T, val reflect.Value) { validateJSONFieldNames(t, val.Index(i)) } case reflect.Map: - m, ok := val.Interface().(map[string]interface{}) + m, ok := reflect.TypeAssert[map[string]interface{}](val) assert.True(t, ok) for mKey, mVal := range m { validateFieldName(t, mKey) diff --git a/pkg/telemetry/phonehome/campaign_test.go b/pkg/telemetry/phonehome/campaign_test.go index a56b5b8ce9aa2..b9a440febf3d1 100644 --- a/pkg/telemetry/phonehome/campaign_test.go +++ b/pkg/telemetry/phonehome/campaign_test.go @@ -408,7 +408,7 @@ func TestCompile(t *testing.T) { }, { criterion: *PathPattern("[b-a]"), - errorMessage: `error parsing path pattern: failed to compile "[b-a]": hi character 'a' should be greater than lo 'b'`, + errorMessage: `error parsing path pattern: failed to compile "[b-a]": glob: syntax error at 5: range hi character is less than lo`, }, } diff --git a/proto/storage/policy.proto b/proto/storage/policy.proto index c07c52d6152e2..922f9fba17715 100644 --- a/proto/storage/policy.proto +++ b/proto/storage/policy.proto @@ -36,7 +36,7 @@ message Policy { EventSource event_source = 22; // @gotags: crYaml:"eventSource,stringer" reserved 10; //was repeated Exclusion whitelists - // Define deployments or images that should be excluded from this policy. + // Define deployments, images, or workload kinds that should be excluded from this policy. repeated Exclusion exclusions = 21; // @gotags: crYaml:",omitempty" // Defines clusters, namespaces, and deployments that should be included in this policy. No scopes defined includes everything. repeated Scope scope = 11; // @gotags: crYaml:",omitempty" @@ -180,7 +180,7 @@ enum Comparator { // EvaluationFilter pre-filters which entities a policy evaluates. message EvaluationFilter { - repeated ContainerType skip_container_types = 1; + repeated ContainerType skip_container_types = 1; // @gotags: crYaml:"skipContainerTypes,omitempty,stringer" } message Exclusion { @@ -195,7 +195,6 @@ message Exclusion { string name = 3; // @gotags: crYaml:",omitempty" Scope scope = 4; // @gotags: crYaml:",omitempty" } - Deployment deployment = 5; // @gotags: crYaml:",omitempty" message Image { string name = 1; @@ -203,6 +202,25 @@ message Exclusion { Image image = 7; // @gotags: crYaml:",omitempty" google.protobuf.Timestamp expiration = 6; // @gotags: crYaml:",timestamp,omitempty" + + // WorkloadKind is a Kubernetes workload kind that can be excluded from a policy in bulk. + enum WorkloadKind { + WORKLOAD_KIND_UNSPECIFIED = 0; + CRON_JOB = 1; + JOB = 2; + } + + // ExcludeByKind excludes all workloads of the selected Kubernetes kinds. + message ExcludeByKind { + repeated WorkloadKind kinds = 1; // @gotags: crYaml:",omitempty,stringer" + } + + // matcher is either a name/scope exclusion or an exclusion by Kubernetes kind. + // Image stays outside the oneof because existing payloads may set both image and deployment. + oneof matcher { + Deployment deployment = 5; // @gotags: crYaml:",omitempty" + ExcludeByKind exclude_by_kind = 8; // @gotags: crYaml:"excludeByKind,omitempty" + } } // ExportPoliciesResponse is used by the API but it is defined in storage because we expect customers to store them. diff --git a/proto/storage/proto.lock b/proto/storage/proto.lock index ada9094986dcf..d2c619b50b714 100644 --- a/proto/storage/proto.lock +++ b/proto/storage/proto.lock @@ -14054,6 +14054,22 @@ "integer": 4 } ] + }, + { + "name": "Exclusion.WorkloadKind", + "enum_fields": [ + { + "name": "WORKLOAD_KIND_UNSPECIFIED" + }, + { + "name": "CRON_JOB", + "integer": 1 + }, + { + "name": "JOB", + "integer": 2 + } + ] } ], "messages": [ @@ -14371,11 +14387,6 @@ "name": "name", "type": "string" }, - { - "id": 5, - "name": "deployment", - "type": "Deployment" - }, { "id": 7, "name": "image", @@ -14385,6 +14396,18 @@ "id": 6, "name": "expiration", "type": "google.protobuf.Timestamp" + }, + { + "id": 5, + "name": "deployment", + "type": "Deployment", + "oneof_parent": "matcher" + }, + { + "id": 8, + "name": "exclude_by_kind", + "type": "ExcludeByKind", + "oneof_parent": "matcher" } ], "reserved_ids": [ @@ -14425,6 +14448,17 @@ "type": "string" } ] + }, + { + "name": "ExcludeByKind", + "fields": [ + { + "id": 1, + "name": "kinds", + "type": "WorkloadKind", + "is_repeated": true + } + ] } ] }, diff --git a/qa-tests-backend/scripts/run-part-1.sh b/qa-tests-backend/scripts/run-part-1.sh index ef446d76f608a..819caaefda26f 100755 --- a/qa-tests-backend/scripts/run-part-1.sh +++ b/qa-tests-backend/scripts/run-part-1.sh @@ -45,6 +45,11 @@ config_part_1() { setup_gcp setup_deployment_env false false if [[ "$use_roxie_deploy" == "true" ]]; then + # On GHA we already installed roxie in a previous workflow step. On Prow we have roxie available as part of the apollo-ci image, + # but it might not be up to date, hence this invocation to make sure that we have a recent roxie version also on Prow. + if [[ "${GITHUB_ACTIONS:-}" != "true" ]]; then + ensure_roxie_on_path + fi info "Using roxie-based pre-test teardown" roxie teardown all --single-namespace else @@ -58,6 +63,8 @@ config_part_1() { if [[ "$use_roxie_deploy" == "true" ]]; then info "Using roxie-based config_part_1 for qa-tests-backend" + ensure_roxie_on_path + local config_file config_file="$(mktemp)" @@ -71,13 +78,6 @@ securedCluster: resourceProfile: ci EOF - if [[ "${USE_KONFLUX_IMAGES:-false}" == "true" ]]; then - info "Environment contains USE_KONFLUX_IMAGES=true, will be using Konflux-built images for deploying StackRox" - patch_yaml "$config_file" ".roxie.konfluxImages = true" - elif pr_has_label test-konflux-images; then - info "PR label 'test-konflux-images' detected, will be using Konflux-built images for deploying StackRox" - patch_yaml "$config_file" ".roxie.konfluxImages = true" - fi deploy_stackrox_with_roxie_compat "$config_file" setup_client_TLS_certs "$ROOT/$DEPLOY_DIR/client_TLS_certs" # Note: The traditional deployment path still references PodSecurityPolicies, diff --git a/qa-tests-backend/src/main/groovy/orchestratormanager/Kubernetes.groovy b/qa-tests-backend/src/main/groovy/orchestratormanager/Kubernetes.groovy index 14a7f0f7fac89..7d44e2751784b 100644 --- a/qa-tests-backend/src/main/groovy/orchestratormanager/Kubernetes.groovy +++ b/qa-tests-backend/src/main/groovy/orchestratormanager/Kubernetes.groovy @@ -2681,10 +2681,90 @@ class Kubernetes { client.namespaces().withName(ns).delete() } if (waitForDeletion && !waitForNamespaceDeletion(ns)) { - // Fail loudly: a namespace stuck Terminating leaks into later tests - // (e.g. NamespaceTest's ACS/orchestrator comparison) instead of being - // silently ignored. - throw new OrchestratorManagerException("Timed out waiting for namespace ${ns} to be deleted") + // A namespace that is still listed is a failed cleanup, not a soft warning. + throw new OrchestratorManagerException( + "Timed out waiting for namespace ${ns} to be deleted${namespaceDeletionDiagnostics(ns)}") + } + } + + // deleteManagedWorkloads removes deployments, replica sets, and pods so + // namespace deletion is not held by a pod's termination. A second delete + // with grace period 0 shortens a termination already in progress. + @CompileDynamic + void deleteManagedWorkloads(String ns) { + Timer t = new Timer(20, 1) + boolean sawNoPods = false + while (t.IsValid()) { + if (namespaceGone(ns)) { + return + } + deleteCollectionQuietly { client.apps().deployments().inNamespace(ns).delete() } + deleteCollectionQuietly { client.apps().replicaSets().inNamespace(ns).delete() } + deleteCollectionQuietly { client.pods().inNamespace(ns).withGracePeriod(0L).delete() } + if (listPodsQuietly(ns).isEmpty()) { + // A second empty read covers a replica set that creates one + // more pod after it is told to delete. + if (sawNoPods) { + return + } + sawNoPods = true + continue + } + sawNoPods = false + } + def stuck = listPodsQuietly(ns).collect { + "${it.metadata?.name} phase=${it.status?.phase} finalizers=${it.metadata?.finalizers}" + } + log.info "Workloads still present in ${ns} before namespace deletion: ${stuck}" + } + + @CompileDynamic + private boolean namespaceGone(String ns) { + try { + return client.namespaces().withName(ns).get() == null + } catch (KubernetesClientException e) { + return e.code == 404 + } + } + + @CompileDynamic + private void deleteCollectionQuietly(Closure deletion) { + try { + deletion.call() + } catch (KubernetesClientException e) { + if (e.code != 404) { + log.warn "Resource delete during namespace cleanup failed: ${e.message}" + } + } + } + + @CompileDynamic + private List listPodsQuietly(String ns) { + try { + return client.pods().inNamespace(ns).list()?.items ?: [] + } catch (KubernetesClientException e) { + if (e.code == 404) { + return [] + } + throw e + } + } + + @CompileDynamic + private String namespaceDeletionDiagnostics(String ns) { + try { + def remaining = client.namespaces().withName(ns).get() + if (remaining == null) { + return "" + } + def pods = listPodsQuietly(ns).collect { + "${it.metadata?.name}(phase=${it.status?.phase}, finalizers=${it.metadata?.finalizers})" + } + return " (phase=${remaining.status?.phase}," + + " deletionTimestamp=${remaining.metadata?.deletionTimestamp}," + + " finalizers=${remaining.metadata?.finalizers}, pods=${pods})" + } catch (Exception e) { + return " (failed to collect namespace diagnostics: ${e.message})" } } diff --git a/qa-tests-backend/src/test/groovy/AdmissionControllerTest.groovy b/qa-tests-backend/src/test/groovy/AdmissionControllerTest.groovy index c031ef3011efd..5c674a7b3b03b 100644 --- a/qa-tests-backend/src/test/groovy/AdmissionControllerTest.groovy +++ b/qa-tests-backend/src/test/groovy/AdmissionControllerTest.groovy @@ -1,5 +1,7 @@ import static util.Helpers.withRetry +import io.grpc.Status +import io.grpc.StatusRuntimeException import io.stackrox.proto.storage.Cve.VulnerabilitySeverity import io.stackrox.proto.storage.ImageOuterClass import io.stackrox.proto.storage.PolicyOuterClass @@ -8,6 +10,7 @@ import io.stackrox.proto.storage.ScopeOuterClass import objects.Deployment import services.ClusterService import services.ImageService +import services.NamespaceService import services.PolicyService import util.Timer @@ -130,10 +133,17 @@ class AdmissionControllerTest extends BaseSpecification { } def cleanupSpec() { + // A pod can still be listed after its deployment object is gone, and + // that holds this shared namespace in Terminating. + orchestrator.deleteManagedWorkloads(TEST_NAMESPACE) orchestrator.deleteNamespace(TEST_NAMESPACE) - for (policyID in createdPolicyIds) { - PolicyService.deletePolicy(policyID) + try { + for (policyID in createdPolicyIds) { + PolicyService.deletePolicy(policyID) + } + } catch (Exception e) { + log.warn "Failed to delete one or more policies during cleanup: ${e.message}" } } @@ -238,6 +248,20 @@ class AdmissionControllerTest extends BaseSpecification { true | true } + // cleanupLabelTestNamespace removes workloads before the namespace. A pod + // from an allowed deployment keeps the namespace past the delete wait, and + // Central can still list it after the API object is gone (ROX-36941). + private void cleanupLabelTestNamespace(String testNs) { + if (!testNs) { + return + } + orchestrator.deleteManagedWorkloads(testNs) + orchestrator.deleteNamespace(testNs) + withRetry(30, 1) { + assert NamespaceService.getNamespaces().every { it.metadata.name != testNs } + } + } + def deleteDeploymentWithCaution(Deployment deployment) { orchestrator.deleteDeployment(deployment) def timer = new Timer(30, 1) @@ -249,6 +273,32 @@ class AdmissionControllerTest extends BaseSpecification { log.warn "Failed to confirm deletion of deployment ${deployment.name}. Subsequent tests may be affected ..." } + private Exception deletePolicyWithCaution(String policyId) { + try { + if (policyId) { + PolicyService.deletePolicy(policyId) + // Wait for policy deletion to propagate to admission controller + withRetry(10, 1) { + def policyExists = true + try { + Services.getPolicy(policyId) + } catch (StatusRuntimeException e) { + if (e.status.code == Status.Code.NOT_FOUND) { + policyExists = false + } else { + throw e + } + } + assert !policyExists : "Policy ${policyId} still exists after deletion" + } + } + } catch (Exception e) { + log.warn "Failed to delete policy ${policyId}: ${e.message}" + return e + } + return null + } + // Retry to allow time for the admission controller to fetch scan data from // Central. Policies that require image enrichment (e.g. severity) may not // evaluate on the first attempt if the AC pod handling this request hasn't @@ -313,19 +363,15 @@ class AdmissionControllerTest extends BaseSpecification { assert created == !blocked cleanup: - if (created) { - deleteDeploymentWithCaution(deployment) - } + def policyDeletionFailure = deletePolicyWithCaution(policyId) try { - // Wait for full namespace deletion; a namespace left in Terminating state - // leaks into NamespaceTest and breaks its ACS/orchestrator count check (ROX-36941). - orchestrator.deleteNamespace(testNs) - } finally { - // Delete the per-test policy even if the namespace deletion above times out - // and throws, otherwise the leftover policy affects later tests. - if (policyId) { - PolicyService.deletePolicy(policyId) - } + cleanupLabelTestNamespace(testNs) + } catch (Exception e) { + log.warn "Namespace ${testNs} deletion failed or timed out: ${e.message}" + throw e + } + if (policyDeletionFailure) { + throw policyDeletionFailure } where: @@ -417,19 +463,15 @@ class AdmissionControllerTest extends BaseSpecification { assert created2 cleanup: - if (created2) { - deleteDeploymentWithCaution(deployment2) - } + def policyDeletionFailure = deletePolicyWithCaution(policyId) try { - // Wait for full namespace deletion; a namespace left in Terminating state - // leaks into NamespaceTest and breaks its ACS/orchestrator count check (ROX-36941). - orchestrator.deleteNamespace(testNs) - } finally { - // Delete the per-test policy even if the namespace deletion above times out - // and throws, otherwise the leftover policy affects later tests. - if (policyId) { - PolicyService.deletePolicy(policyId) - } + cleanupLabelTestNamespace(testNs) + } catch (Exception e) { + log.warn "Namespace ${testNs} deletion failed or timed out: ${e.message}" + throw e + } + if (policyDeletionFailure) { + throw policyDeletionFailure } where: diff --git a/qa-tests-backend/src/test/groovy/DefaultPoliciesTest.groovy b/qa-tests-backend/src/test/groovy/DefaultPoliciesTest.groovy index 7b261936537d0..f2bb5227ac1b6 100644 --- a/qa-tests-backend/src/test/groovy/DefaultPoliciesTest.groovy +++ b/qa-tests-backend/src/test/groovy/DefaultPoliciesTest.groovy @@ -463,11 +463,10 @@ class DefaultPoliciesTest extends BaseSpecification { "Port 80 is exposed in the cluster" | null | [] "Image Vulnerabilities" | 4.0f | null | - // StackRox Scanner reports 100+ CVEs between Low and Critical. - // Scanner V4 reports fewer CVEs (~78) and a different minimum - // severity (Moderate in this case). Using \\d{2,} and \\w+ to accept both outcomes. + // Scanner versions report different counts of distinct CVEs and minimum + // severities. Use a regex to accept both outcomes. "Image \"" + STRUTS_IMAGE + "\\\"" + - " contains \\d{2,} CVEs with severities ranging between " + + " contains \\d{2,} distinct CVEs with severities ranging between " + "\\w+ and \\w+" | [] "Service Configuration" | 2.0f | diff --git a/roxctl/central/generate/interactive.go b/roxctl/central/generate/interactive.go index fe544dca69b66..e5df581fb6f4a 100644 --- a/roxctl/central/generate/interactive.go +++ b/roxctl/central/generate/interactive.go @@ -352,11 +352,9 @@ func processFlagWraps(argSlice *argSlice, fws []flagWrap) { depUnmet := false for _, dep := range fw.Annotations[flags.DependenciesKey] { flag := flagsByName[dep] - //nolint:staticcheck // SA5011 flag is definitely not nil because utils.Must panics. if flag == nil { utils.CrashOnError(errors.Errorf("invalid flag dependency %q", dep)) } - //nolint:staticcheck // SA5011 flag is definitely not nil because utils.Must panics. if !argSlice.flagNameIsSetExplicitly(flag.Name) { depUnmet = true break diff --git a/roxctl/common/client.go b/roxctl/common/client.go index d1f8cb4ef4dcb..3cc94fb51002a 100644 --- a/roxctl/common/client.go +++ b/roxctl/common/client.go @@ -2,6 +2,7 @@ package common import ( "context" + "crypto/tls" "fmt" "io" "net/http" @@ -59,16 +60,16 @@ func getURL(path string) (string, error) { return fmt.Sprintf("%s://%s/%s", scheme, endpoint, strings.TrimLeft(path, "/")), nil } -// GetRoxctlHTTPClient returns a new instance of RoxctlHTTPClient with the given configuration -func GetRoxctlHTTPClient(config *HttpClientConfig) (RoxctlHTTPClient, error) { - tlsConf, err := tlsConfigForCentral() - if err != nil { - return nil, errors.Wrap(err, "instantiating TLS configuration for central") - } +// newHTTPTransport builds the HTTP transport used by roxctl to talk to Central. +// Proxy is set to http.ProxyFromEnvironment so that HTTP(S)_PROXY/NO_PROXY are honored; +// a hand-built http.Transport otherwise defaults to no proxy (unlike http.DefaultTransport), +// which made roxctl dial Central directly and time out behind egress proxies (ROX-37149). +func newHTTPTransport(tlsConf *tls.Config, forceHTTP1 bool) *http.Transport { transport := &http.Transport{ + Proxy: http.ProxyFromEnvironment, TLSClientConfig: tlsConf, } - if config.ForceHTTP1 { + if forceHTTP1 { transport.TLSClientConfig.NextProtos = http1NextProtos } else { // There's no reason to not use HTTP/2, but we don't go out of our way to do so. @@ -76,6 +77,16 @@ func GetRoxctlHTTPClient(config *HttpClientConfig) (RoxctlHTTPClient, error) { transport.TLSClientConfig.NextProtos = http1NextProtos } } + return transport +} + +// GetRoxctlHTTPClient returns a new instance of RoxctlHTTPClient with the given configuration +func GetRoxctlHTTPClient(config *HttpClientConfig) (RoxctlHTTPClient, error) { + tlsConf, err := tlsConfigForCentral() + if err != nil { + return nil, errors.Wrap(err, "instantiating TLS configuration for central") + } + transport := newHTTPTransport(tlsConf, config.ForceHTTP1) retryClient := retryablehttp.NewClient() retryClient.CheckRetry = func(ctx context.Context, resp *http.Response, err error) (bool, error) { diff --git a/roxctl/common/client_test.go b/roxctl/common/client_test.go index 9804d62c26050..be569f3b6e40a 100644 --- a/roxctl/common/client_test.go +++ b/roxctl/common/client_test.go @@ -1,13 +1,16 @@ package common import ( + "crypto/tls" "net/http" + "reflect" "testing" "github.com/stackrox/rox/pkg/clientconn" "github.com/stackrox/rox/pkg/env" "github.com/stackrox/rox/pkg/telemetry/phonehome" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestSanitizeHeaderValue(t *testing.T) { @@ -22,6 +25,25 @@ func TestSanitizeHeaderValue(t *testing.T) { } } +func Test_newHTTPTransport_honorsProxyEnv(t *testing.T) { + // Regression test for ROX-37149: roxctl ignored HTTP(S)_PROXY because the + // hand-built transport left Proxy unset. Verify the transport delegates to + // http.ProxyFromEnvironment for both HTTP/2 and forced HTTP/1 modes. + for name, forceHTTP1 := range map[string]bool{ + "http2": false, + "forceHTTP1": true, + } { + t.Run(name, func(t *testing.T) { + transport := newHTTPTransport(&tls.Config{}, forceHTTP1) + require.NotNil(t, transport.Proxy, "transport must resolve the proxy from the environment") + assert.Equal(t, + reflect.ValueOf(http.ProxyFromEnvironment).Pointer(), + reflect.ValueOf(transport.Proxy).Pointer(), + "transport.Proxy must be http.ProxyFromEnvironment") + }) + } +} + func Test_setCustomHeaders(t *testing.T) { headers := http.Header{} setCustomHeaders(phonehome.Headers(headers).Set) diff --git a/rpms.in.yaml b/rpms.in.yaml index 8df6cca8fdb6a..ee1616dcd4c38 100644 --- a/rpms.in.yaml +++ b/rpms.in.yaml @@ -19,7 +19,7 @@ packages: - tar moduleEnable: # final stage in image/rhel/konflux.Dockerfile -- postgresql:15 +- postgresql:16 contentOrigin: repofiles: [ "rpms.rhel.repo" ] context: diff --git a/rpms.lock.yaml b/rpms.lock.yaml index eaaf8f5d53047..3263e010852b7 100644 --- a/rpms.lock.yaml +++ b/rpms.lock.yaml @@ -18,13 +18,13 @@ arches: name: delve evr: 1.26.1-1.el9_8 sourcerpm: delve-1.26.1-1.el9_8.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/appstream/os/Packages/g/gawk-all-langpacks-5.1.0-6.el9.aarch64.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/appstream/os/Packages/g/gawk-all-langpacks-5.1.0-6.el9_8.1.aarch64.rpm repoid: rhel-9-for-aarch64-appstream-rpms - size: 216292 - checksum: sha256:3fd73bd271e9c04dc297eb08c3829d276faee372f77666aad29f4b676ea220f4 + size: 218086 + checksum: sha256:d38712f7aa4e5b3821ba899d6b4f62821407f6f239e17bf240ca488fb1d35376 name: gawk-all-langpacks - evr: 5.1.0-6.el9 - sourcerpm: gawk-5.1.0-6.el9.src.rpm + evr: 5.1.0-6.el9_8.1 + sourcerpm: gawk-5.1.0-6.el9_8.1.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/appstream/os/Packages/g/gcc-11.5.0-14.el9.aarch64.rpm repoid: rhel-9-for-aarch64-appstream-rpms size: 31291354 @@ -32,13 +32,13 @@ arches: name: gcc evr: 11.5.0-14.el9 sourcerpm: gcc-11.5.0-14.el9.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/appstream/os/Packages/g/glibc-devel-2.34-275.el9_8.aarch64.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/appstream/os/Packages/g/glibc-devel-2.34-283.el9_8.aarch64.rpm repoid: rhel-9-for-aarch64-appstream-rpms - size: 576947 - checksum: sha256:aef79b53955c2ec67efdc39cb91148b377f8719a8b68cf76c7ddcffbcb6b8bf0 + size: 577250 + checksum: sha256:b363f894683abf7076596ea8d67269eb878d2724d9447e6b3f4c6409650cefd2 name: glibc-devel - evr: 2.34-275.el9_8 - sourcerpm: glibc-2.34-275.el9_8.src.rpm + evr: 2.34-283.el9_8 + sourcerpm: glibc-2.34-283.el9_8.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/appstream/os/Packages/g/go-toolset-1.26.7-2.el9_8.aarch64.rpm repoid: rhel-9-for-aarch64-appstream-rpms size: 12453 @@ -74,13 +74,13 @@ arches: name: golang-src evr: 1.26.7-2.el9_8 sourcerpm: golang-1.26.7-2.el9_8.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/appstream/os/Packages/k/kernel-headers-5.14.0-687.53.1.el9_8.aarch64.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/appstream/os/Packages/k/kernel-headers-5.14.0-687.57.1.el9_8.aarch64.rpm repoid: rhel-9-for-aarch64-appstream-rpms - size: 2925601 - checksum: sha256:7e82c856a00206976aede9e7b3865d0a2363b1262c0f9e1949ba29c7b9c47281 + size: 2939977 + checksum: sha256:31860419149438987e706dd2929fbc707552cd2e51dbc86547d294f9fed5f204 name: kernel-headers - evr: 5.14.0-687.53.1.el9_8 - sourcerpm: kernel-5.14.0-687.53.1.el9_8.src.rpm + evr: 5.14.0-687.57.1.el9_8 + sourcerpm: kernel-5.14.0-687.57.1.el9_8.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/appstream/os/Packages/l/libasan-11.5.0-14.el9.aarch64.rpm repoid: rhel-9-for-aarch64-appstream-rpms size: 409047 @@ -130,41 +130,41 @@ arches: name: mpdecimal evr: 2.5.1-3.el9 sourcerpm: mpdecimal-2.5.1-3.el9.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/appstream/os/Packages/o/openssl-devel-3.5.8-1.el9_8.aarch64.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/appstream/os/Packages/o/openssl-devel-3.5.8-2.el9_8.aarch64.rpm repoid: rhel-9-for-aarch64-appstream-rpms - size: 5039228 - checksum: sha256:49aec716fb44d8a07528f59a635d40a47a847b05e6cf57735994e9c2430efa12 + size: 5049780 + checksum: sha256:7ae4c2948a7e30ad50c399558b1af9be4055de267eb8de1d1757e12b2776e3ef name: openssl-devel - evr: 1:3.5.8-1.el9_8 - sourcerpm: openssl-3.5.8-1.el9_8.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/appstream/os/Packages/p/postgresql-15.19-1.module+el9.8.0+24800+a95bf172.aarch64.rpm + evr: 1:3.5.8-2.el9_8 + sourcerpm: openssl-3.5.8-2.el9_8.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/appstream/os/Packages/p/postgresql-16.15-1.module+el9.8.0+24803+9dca000b.aarch64.rpm repoid: rhel-9-for-aarch64-appstream-rpms - size: 1828909 - checksum: sha256:06bde44430fc94e09d7dac2c748f19b41161fdc2fa4b4b6cd861ca9a7a1532c0 + size: 2049624 + checksum: sha256:5295a9a347f24655946ca8870d76cc7acdaa96c1e631a6880193576e92061d27 name: postgresql - evr: 15.19-1.module+el9.8.0+24800+a95bf172 - sourcerpm: postgresql-15.19-1.module+el9.8.0+24800+a95bf172.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/appstream/os/Packages/p/postgresql-private-libs-15.19-1.module+el9.8.0+24800+a95bf172.aarch64.rpm + evr: 16.15-1.module+el9.8.0+24803+9dca000b + sourcerpm: postgresql-16.15-1.module+el9.8.0+24803+9dca000b.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/appstream/os/Packages/p/postgresql-private-libs-16.15-1.module+el9.8.0+24803+9dca000b.aarch64.rpm repoid: rhel-9-for-aarch64-appstream-rpms - size: 150016 - checksum: sha256:869f22709281040978c3130c972685c217b4be0c4f915c69c3036b886d75bcc2 + size: 149710 + checksum: sha256:e4b9f9f73ee8986c64148b838c9a32492d75e4c6352810542952a8126eaa31ef name: postgresql-private-libs - evr: 15.19-1.module+el9.8.0+24800+a95bf172 - sourcerpm: postgresql-15.19-1.module+el9.8.0+24800+a95bf172.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/appstream/os/Packages/p/python3.12-3.12.14-1.el9_8.aarch64.rpm + evr: 16.15-1.module+el9.8.0+24803+9dca000b + sourcerpm: postgresql-16.15-1.module+el9.8.0+24803+9dca000b.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/appstream/os/Packages/p/python3.12-3.12.15-1.el9_8.aarch64.rpm repoid: rhel-9-for-aarch64-appstream-rpms - size: 32440 - checksum: sha256:127b8467e15c3f233d87bdb85c57809332eefb2e7b483aca7a08790dd9b62b74 + size: 32621 + checksum: sha256:9097113ea007073e718b55da7ea5d33f5ee5ce0e5f7b0b04beb706872d5968a4 name: python3.12 - evr: 3.12.14-1.el9_8 - sourcerpm: python3.12-3.12.14-1.el9_8.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/appstream/os/Packages/p/python3.12-libs-3.12.14-1.el9_8.aarch64.rpm + evr: 3.12.15-1.el9_8 + sourcerpm: python3.12-3.12.15-1.el9_8.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/appstream/os/Packages/p/python3.12-libs-3.12.15-1.el9_8.aarch64.rpm repoid: rhel-9-for-aarch64-appstream-rpms - size: 10145691 - checksum: sha256:cca9ebdb4242036911ccc7271de7cb9e3d621c152b402f781ae22b700ab02231 + size: 10154341 + checksum: sha256:f0b73d057827f2380f50f3bc9459298cc103da8b333eefc5cacd3865087b4ee1 name: python3.12-libs - evr: 3.12.14-1.el9_8 - sourcerpm: python3.12-3.12.14-1.el9_8.src.rpm + evr: 3.12.15-1.el9_8 + sourcerpm: python3.12-3.12.15-1.el9_8.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/appstream/os/Packages/p/python3.12-pip-wheel-23.2.1-5.el9.noarch.rpm repoid: rhel-9-for-aarch64-appstream-rpms size: 1527128 @@ -361,13 +361,13 @@ arches: name: findutils evr: 1:4.8.0-7.el9 sourcerpm: findutils-4.8.0-7.el9.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/os/Packages/g/gawk-5.1.0-6.el9.aarch64.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/os/Packages/g/gawk-5.1.0-6.el9_8.1.aarch64.rpm repoid: rhel-9-for-aarch64-baseos-rpms - size: 1024204 - checksum: sha256:a4b7202ac90653a7d3e072c2444bde6a9270d6a818eb6f2ffcfcaa50774f1fad + size: 1026105 + checksum: sha256:54365d2a6150079c2dc5003eeb94ada32fc15801b5db05422361f02ed58e6772 name: gawk - evr: 5.1.0-6.el9 - sourcerpm: gawk-5.1.0-6.el9.src.rpm + evr: 5.1.0-6.el9_8.1 + sourcerpm: gawk-5.1.0-6.el9_8.1.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/os/Packages/g/gdbm-libs-1.23-1.el9.aarch64.rpm repoid: rhel-9-for-aarch64-baseos-rpms size: 60311 @@ -375,34 +375,34 @@ arches: name: gdbm-libs evr: 1:1.23-1.el9 sourcerpm: gdbm-1.23-1.el9.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/os/Packages/g/glibc-2.34-275.el9_8.aarch64.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/os/Packages/g/glibc-2.34-283.el9_8.aarch64.rpm repoid: rhel-9-for-aarch64-baseos-rpms - size: 1813928 - checksum: sha256:e40a78100f731b5f5a1b235880a0aaad5b08bb32e6521e90535b7f4a94c6e9e9 + size: 1814330 + checksum: sha256:76611b8d5d56730d15142d62b85e83731f5c199523b40a31da5250f81ec66d3c name: glibc - evr: 2.34-275.el9_8 - sourcerpm: glibc-2.34-275.el9_8.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/os/Packages/g/glibc-common-2.34-275.el9_8.aarch64.rpm + evr: 2.34-283.el9_8 + sourcerpm: glibc-2.34-283.el9_8.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/os/Packages/g/glibc-common-2.34-283.el9_8.aarch64.rpm repoid: rhel-9-for-aarch64-baseos-rpms - size: 312665 - checksum: sha256:24e1c7189d101531c526dd3cc1a42ab62d89f874824b54fb6b518ec24f190554 + size: 312943 + checksum: sha256:a3cbba6c367d316bb1738bab6ac3e3e1c9423fa642faf8365b39a23a7b067768 name: glibc-common - evr: 2.34-275.el9_8 - sourcerpm: glibc-2.34-275.el9_8.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/os/Packages/g/glibc-gconv-extra-2.34-275.el9_8.aarch64.rpm + evr: 2.34-283.el9_8 + sourcerpm: glibc-2.34-283.el9_8.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/os/Packages/g/glibc-gconv-extra-2.34-283.el9_8.aarch64.rpm repoid: rhel-9-for-aarch64-baseos-rpms - size: 1829737 - checksum: sha256:7f2f23c07b84b3cd3bacb905dffaca4f4474298e936f3b10b93390d4127591da + size: 1831695 + checksum: sha256:15321958afc2a83349b084df59bdf272ee447ea98b384fdfec57e5c589fa143c name: glibc-gconv-extra - evr: 2.34-275.el9_8 - sourcerpm: glibc-2.34-275.el9_8.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/os/Packages/g/glibc-minimal-langpack-2.34-275.el9_8.aarch64.rpm + evr: 2.34-283.el9_8 + sourcerpm: glibc-2.34-283.el9_8.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/os/Packages/g/glibc-minimal-langpack-2.34-283.el9_8.aarch64.rpm repoid: rhel-9-for-aarch64-baseos-rpms - size: 30881 - checksum: sha256:b1348c4c4fe3da979f342b0c27ff0d33a11688274a0b6708292d7750bbc8d853 + size: 31033 + checksum: sha256:422d151f31a9fea5dd99aa7bb218c2932d64386f5daa3a8af0827183dc1beb39 name: glibc-minimal-langpack - evr: 2.34-275.el9_8 - sourcerpm: glibc-2.34-275.el9_8.src.rpm + evr: 2.34-283.el9_8 + sourcerpm: glibc-2.34-283.el9_8.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/os/Packages/g/gmp-6.2.0-13.el9.aarch64.rpm repoid: rhel-9-for-aarch64-baseos-rpms size: 275679 @@ -494,13 +494,13 @@ arches: name: libblkid evr: 2.37.4-25.el9 sourcerpm: util-linux-2.37.4-25.el9.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/os/Packages/l/libbrotli-1.0.9-9.el9_7.aarch64.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/os/Packages/l/libbrotli-1.0.9-10.el9_8.aarch64.rpm repoid: rhel-9-for-aarch64-baseos-rpms - size: 325179 - checksum: sha256:f5237abc90191238333c1214da97b5202c8a15c2be3ab401ee10d95343cfdf17 + size: 325419 + checksum: sha256:04d157b138153dcd27c04d81ae985e8190ddc542d63620f3d1ae0a690a1041b7 name: libbrotli - evr: 1.0.9-9.el9_7 - sourcerpm: brotli-1.0.9-9.el9_7.src.rpm + evr: 1.0.9-10.el9_8 + sourcerpm: brotli-1.0.9-10.el9_8.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/os/Packages/l/libcap-2.48-10.el9_8.1.aarch64.rpm repoid: rhel-9-for-aarch64-baseos-rpms size: 78021 @@ -522,13 +522,13 @@ arches: name: libcom_err evr: 1.46.5-8.el9 sourcerpm: e2fsprogs-1.46.5-8.el9.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/os/Packages/l/libcurl-7.76.1-40.el9_8.7.aarch64.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/os/Packages/l/libcurl-7.76.1-40.el9_8.8.aarch64.rpm repoid: rhel-9-for-aarch64-baseos-rpms - size: 292483 - checksum: sha256:408a3da221a1d3856a4b9ab7a7c70901430f71521ef9d05f9b847c0deb27b160 + size: 293076 + checksum: sha256:a7ec72530e41371da4984e19c93168de6a911067d2bb597dec1e3bbe48e179a8 name: libcurl - evr: 7.76.1-40.el9_8.7 - sourcerpm: curl-7.76.1-40.el9_8.7.src.rpm + evr: 7.76.1-40.el9_8.8 + sourcerpm: curl-7.76.1-40.el9_8.8.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/os/Packages/l/libdb-5.3.28-57.el9_6.aarch64.rpm repoid: rhel-9-for-aarch64-baseos-rpms size: 727417 @@ -809,13 +809,13 @@ arches: name: openldap evr: 2.6.8-4.el9 sourcerpm: openldap-2.6.8-4.el9.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/os/Packages/o/openssl-3.5.8-1.el9_8.aarch64.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/os/Packages/o/openssl-3.5.8-2.el9_8.aarch64.rpm repoid: rhel-9-for-aarch64-baseos-rpms - size: 1546056 - checksum: sha256:1ef4001b9a9df4695c80e986d3c8ceb7900bb6925f86fde09d16aff9f8d733f5 + size: 1546093 + checksum: sha256:f625d004eb04013ac83037d7b6b77a100d54706de8d9a9c8a6d39d907169bbf8 name: openssl - evr: 1:3.5.8-1.el9_8 - sourcerpm: openssl-3.5.8-1.el9_8.src.rpm + evr: 1:3.5.8-2.el9_8 + sourcerpm: openssl-3.5.8-2.el9_8.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/os/Packages/o/openssl-fips-provider-3.0.7-11.el9_8.aarch64.rpm repoid: rhel-9-for-aarch64-baseos-rpms size: 14220 @@ -830,13 +830,13 @@ arches: name: openssl-fips-provider-so evr: 3.0.7-11.el9_8 sourcerpm: openssl-fips-provider-3.0.7-11.el9_8.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/os/Packages/o/openssl-libs-3.5.8-1.el9_8.aarch64.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/os/Packages/o/openssl-libs-3.5.8-2.el9_8.aarch64.rpm repoid: rhel-9-for-aarch64-baseos-rpms - size: 2294194 - checksum: sha256:23134af6ea097b94d8261367db01f91604ef3b508654caf4f7399e5c142abcc8 + size: 2294447 + checksum: sha256:89aa84e734616240e7541b8095c2f2c2cd9c114e98b345042f5276d03781180b name: openssl-libs - evr: 1:3.5.8-1.el9_8 - sourcerpm: openssl-3.5.8-1.el9_8.src.rpm + evr: 1:3.5.8-2.el9_8 + sourcerpm: openssl-3.5.8-2.el9_8.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/os/Packages/p/p11-kit-0.26.4-1.el9_8.aarch64.rpm repoid: rhel-9-for-aarch64-baseos-rpms size: 589112 @@ -865,20 +865,20 @@ arches: name: pcre evr: 8.44-4.el9 sourcerpm: pcre-8.44-4.el9.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/os/Packages/p/pcre2-10.40-6.el9.aarch64.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/os/Packages/p/pcre2-10.40-7.el9_8.aarch64.rpm repoid: rhel-9-for-aarch64-baseos-rpms - size: 224938 - checksum: sha256:29285f81cef68f73b4f8ff81ee8fdf4ceaa007933302119ed1615e4aa1091613 + size: 227991 + checksum: sha256:40fd0cd92f4cf824fb5186128d360bdc0a8a9f70c9f820b8c0efda4afec75cc8 name: pcre2 - evr: 10.40-6.el9 - sourcerpm: pcre2-10.40-6.el9.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/os/Packages/p/pcre2-syntax-10.40-6.el9.noarch.rpm + evr: 10.40-7.el9_8 + sourcerpm: pcre2-10.40-7.el9_8.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/os/Packages/p/pcre2-syntax-10.40-7.el9_8.noarch.rpm repoid: rhel-9-for-aarch64-baseos-rpms - size: 147926 - checksum: sha256:d386b5e9b3a4b077b2ba143882e605750855dd3354f13c55fa12ed26908cb442 + size: 150069 + checksum: sha256:e7466a43ef2bfda2b1dc07e855b0db743d48b11c0bdc1402e1d2ea6fb23d33a3 name: pcre2-syntax - evr: 10.40-6.el9 - sourcerpm: pcre2-10.40-6.el9.src.rpm + evr: 10.40-7.el9_8 + sourcerpm: pcre2-10.40-7.el9_8.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/os/Packages/p/pigz-2.8-1.el9.aarch64.rpm repoid: rhel-9-for-aarch64-baseos-rpms size: 93525 @@ -998,13 +998,13 @@ arches: name: tar evr: 2:1.34-13.el9_8 sourcerpm: tar-1.34-13.el9_8.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/os/Packages/t/tzdata-2026c-1.el9_8.noarch.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/os/Packages/t/tzdata-2026e-1.el9_8.noarch.rpm repoid: rhel-9-for-aarch64-baseos-rpms - size: 933286 - checksum: sha256:8d6196e02853c0900c3c2e2367665beffb62cb2d0ff5f465ea2d00848a9a35dc + size: 936099 + checksum: sha256:c105d92f6cacb9bb8337b77041cedd9c9c906a72c4b5233909b9b69b78f4369f name: tzdata - evr: 2026c-1.el9_8 - sourcerpm: tzdata-2026c-1.el9_8.src.rpm + evr: 2026e-1.el9_8 + sourcerpm: tzdata-2026e-1.el9_8.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/os/Packages/u/util-linux-2.37.4-25.el9.aarch64.rpm repoid: rhel-9-for-aarch64-baseos-rpms size: 2390152 @@ -1070,18 +1070,18 @@ arches: checksum: sha256:2f5ece89d6a8d892816f386462d8be291ad83f1500b4c4b3655dcc7ed5192c0d name: mpdecimal evr: 2.5.1-3.el9 - - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/appstream/source/SRPMS/Packages/p/postgresql-15.19-1.module+el9.8.0+24800+a95bf172.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/appstream/source/SRPMS/Packages/p/postgresql-16.15-1.module+el9.8.0+24803+9dca000b.src.rpm repoid: rhel-9-for-aarch64-appstream-source-rpms - size: 53848854 - checksum: sha256:9e2e4c6af57fe5ab47f3f2ca77b4cb8d16a828cfa03b1eba6704203fe926eb96 + size: 48516429 + checksum: sha256:fdb3a4e84c097bb87061fbf6b3f1f11a03b4aa6adf13b0b20cc1d439b5a2c7d7 name: postgresql - evr: 15.19-1.module+el9.8.0+24800+a95bf172 - - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/appstream/source/SRPMS/Packages/p/python3.12-3.12.14-1.el9_8.src.rpm + evr: 16.15-1.module+el9.8.0+24803+9dca000b + - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/appstream/source/SRPMS/Packages/p/python3.12-3.12.15-1.el9_8.src.rpm repoid: rhel-9-for-aarch64-appstream-source-rpms - size: 20894742 - checksum: sha256:d855a90132b827b528e26c617484c250fd02dbe2cc8fdb8ef1f7d0d0a9f3f4f7 + size: 20904561 + checksum: sha256:8a3db1154cbfe206ac68b31268348da6039522d09be22520dd94207417146ebd name: python3.12 - evr: 3.12.14-1.el9_8 + evr: 3.12.15-1.el9_8 - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/appstream/source/SRPMS/Packages/p/python3.12-pip-23.2.1-5.el9.src.rpm repoid: rhel-9-for-aarch64-appstream-source-rpms size: 9384965 @@ -1130,12 +1130,12 @@ arches: checksum: sha256:82174416ee39df7795da7fab8d37ea5dbbe25f1dbc2df39f5fb3f8a168bf5120 name: binutils evr: 2.35.2-72.el9 - - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/source/SRPMS/Packages/b/brotli-1.0.9-9.el9_7.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/source/SRPMS/Packages/b/brotli-1.0.9-10.el9_8.src.rpm repoid: rhel-9-for-aarch64-baseos-source-rpms - size: 517498 - checksum: sha256:814868e0bec831c79d3e12ff76d31e06e5e62c462a1a4b6607b1f3cab7014438 + size: 519221 + checksum: sha256:c833ffbd6c6555543472ef4034a958cf4b8b8dee35b8661b2144ca6fc38908c7 name: brotli - evr: 1.0.9-9.el9_7 + evr: 1.0.9-10.el9_8 - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/source/SRPMS/Packages/b/bzip2-1.0.8-11.el9.src.rpm repoid: rhel-9-for-aarch64-baseos-source-rpms size: 828199 @@ -1172,12 +1172,12 @@ arches: checksum: sha256:775d926429179caeade6af8dc7dea15a0bea49102c2592d5b712f996a329ec38 name: crypto-policies evr: 20260224-1.gitea0f072.el9_8 - - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/source/SRPMS/Packages/c/curl-7.76.1-40.el9_8.7.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/source/SRPMS/Packages/c/curl-7.76.1-40.el9_8.8.src.rpm repoid: rhel-9-for-aarch64-baseos-source-rpms - size: 2579601 - checksum: sha256:2cb2bc752c2e87f1abf88ac67c2dd19a740df993d57e88f3920dedbc37f0019d + size: 2581541 + checksum: sha256:303a97cbdfaaac1d709ef7621b2b349dc403da6a553ecfe5e3114f76d757d554 name: curl - evr: 7.76.1-40.el9_8.7 + evr: 7.76.1-40.el9_8.8 - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/source/SRPMS/Packages/c/cyrus-sasl-2.1.27-22.el9.src.rpm repoid: rhel-9-for-aarch64-baseos-source-rpms size: 4025677 @@ -1232,12 +1232,12 @@ arches: checksum: sha256:48bd4d4dd081120bcc6ab772b930a150f30b2fc89a4a14a24220383d24a30b3f name: findutils evr: 1:4.8.0-7.el9 - - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/source/SRPMS/Packages/g/gawk-5.1.0-6.el9.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/source/SRPMS/Packages/g/gawk-5.1.0-6.el9_8.1.src.rpm repoid: rhel-9-for-aarch64-baseos-source-rpms - size: 3190934 - checksum: sha256:37571947707e835d36ceb5641b187aba13ef8f5f605a6762ce72aeea3e745b8d + size: 3197459 + checksum: sha256:8f156f11c96b1720ba2e9e287f2ddf5e0588a9c9ad17753f779ebbdf0c4e99df name: gawk - evr: 5.1.0-6.el9 + evr: 5.1.0-6.el9_8.1 - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/source/SRPMS/Packages/g/gcc-11.5.0-14.el9.src.rpm repoid: rhel-9-for-aarch64-baseos-source-rpms size: 81978017 @@ -1250,12 +1250,12 @@ arches: checksum: sha256:ad42264274c2a792220395a4dbe736a1de6100c4e14611707ec1dd447583271f name: gdbm evr: 1:1.23-1.el9 - - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/source/SRPMS/Packages/g/glibc-2.34-275.el9_8.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/source/SRPMS/Packages/g/glibc-2.34-283.el9_8.src.rpm repoid: rhel-9-for-aarch64-baseos-source-rpms - size: 20442764 - checksum: sha256:bd7a453f66e13da24d073f53070c2d209fcd88ca2aaa24ab3b2f81cae198b46c + size: 20462553 + checksum: sha256:9007b0a97151f43f812049a925cb418753f9fd643fe1283cc7eddc7e6785e2f3 name: glibc - evr: 2.34-275.el9_8 + evr: 2.34-283.el9_8 - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/source/SRPMS/Packages/g/gmp-6.2.0-13.el9.src.rpm repoid: rhel-9-for-aarch64-baseos-source-rpms size: 2503825 @@ -1286,12 +1286,12 @@ arches: checksum: sha256:c4c76ebfd66ba6d00edf672797d7f077b29d279b7866a04e05258a257a5bc306 name: json-c evr: 0.14-11.el9 - - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/source/SRPMS/Packages/k/kernel-5.14.0-687.53.1.el9_8.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/source/SRPMS/Packages/k/kernel-5.14.0-687.57.1.el9_8.src.rpm repoid: rhel-9-for-aarch64-baseos-source-rpms - size: 152530420 - checksum: sha256:28a39e99b31b7b924672b14e70b1a0608755a7898c20563f52f2c0b22c67a91a + size: 152563149 + checksum: sha256:a2660af0b87ac6a4d161ccc76c7d8a8338a44b4fbaad6da9e5529e3027f25aa2 name: kernel - evr: 5.14.0-687.53.1.el9_8 + evr: 5.14.0-687.57.1.el9_8 - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/source/SRPMS/Packages/k/keyutils-1.6.3-1.el9.src.rpm repoid: rhel-9-for-aarch64-baseos-source-rpms size: 150790 @@ -1502,12 +1502,12 @@ arches: checksum: sha256:0d21c12c55d40d1fc2f006c8ec187b5fcc799b794cfd31ed2d98434189c62800 name: openldap evr: 2.6.8-4.el9 - - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/source/SRPMS/Packages/o/openssl-3.5.8-1.el9_8.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/source/SRPMS/Packages/o/openssl-3.5.8-2.el9_8.src.rpm repoid: rhel-9-for-aarch64-baseos-source-rpms - size: 53429735 - checksum: sha256:393e8694b8161826f67f149cd7dde6b63da96f02efc050fda651d265907f2c20 + size: 53435636 + checksum: sha256:1e67b59efe228f804c0544a31ab3de0bb4a0c8250d3d5f5fc53a3c7794104795 name: openssl - evr: 1:3.5.8-1.el9_8 + evr: 1:3.5.8-2.el9_8 - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/source/SRPMS/Packages/o/openssl-fips-provider-3.0.7-11.el9_8.src.rpm repoid: rhel-9-for-aarch64-baseos-source-rpms size: 28686020 @@ -1532,12 +1532,12 @@ arches: checksum: sha256:7edbd87b866a3f6e3df1426d660b902e063193d6186027bf99f6d77626a43817 name: pcre evr: 8.44-4.el9 - - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/source/SRPMS/Packages/p/pcre2-10.40-6.el9.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/source/SRPMS/Packages/p/pcre2-10.40-7.el9_8.src.rpm repoid: rhel-9-for-aarch64-baseos-source-rpms - size: 1789790 - checksum: sha256:a570f7192be555222aa3704882b9199fb013a84ad4d7dcf40a93d8de2ecf6e0a + size: 1805958 + checksum: sha256:43c40093332890a77f7d7c573a1b540867319bbcaaec5a6dfd73101765dc6ea0 name: pcre2 - evr: 10.40-6.el9 + evr: 10.40-7.el9_8 - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/source/SRPMS/Packages/p/pigz-2.8-1.el9.src.rpm repoid: rhel-9-for-aarch64-baseos-source-rpms size: 131701 @@ -1604,12 +1604,12 @@ arches: checksum: sha256:15eeffee75eaef17e056cef4d5207825782a755db6dfd509a759e672ffd03b6c name: tar evr: 2:1.34-13.el9_8 - - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/source/SRPMS/Packages/t/tzdata-2026c-1.el9_8.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/source/SRPMS/Packages/t/tzdata-2026e-1.el9_8.src.rpm repoid: rhel-9-for-aarch64-baseos-source-rpms - size: 933774 - checksum: sha256:4cc10832b19807c03a30bd9317fb37dc811d2f13c11b1ed35af8d2efa481622f + size: 943814 + checksum: sha256:016ab67407e6f67418d8f36c5944396fe82c85896b9e945441f93d3cf1b823b4 name: tzdata - evr: 2026c-1.el9_8 + evr: 2026e-1.el9_8 - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/baseos/source/SRPMS/Packages/u/util-linux-2.37.4-25.el9.src.rpm repoid: rhel-9-for-aarch64-baseos-source-rpms size: 6291867 @@ -1635,10 +1635,10 @@ arches: name: zstd evr: 1.5.5-1.el9 module_metadata: - - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/appstream/os/repodata/80a77d18ba9e517b5d62a2859474edc8ab833383bc13d0fdbf1027a05f45a1bc-modules.yaml.gz + - url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/appstream/os/repodata/e4bd35a63d3720d47f8770f923c2f4f4a21dc06f0ff1cb37c98ba520477a2b5f-modules.yaml.gz repoid: rhel-9-for-aarch64-appstream-rpms - size: 68829 - checksum: sha256:80a77d18ba9e517b5d62a2859474edc8ab833383bc13d0fdbf1027a05f45a1bc + size: 69384 + checksum: sha256:e4bd35a63d3720d47f8770f923c2f4f4a21dc06f0ff1cb37c98ba520477a2b5f - arch: ppc64le packages: - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/os/Packages/c/cpp-11.5.0-14.el9.ppc64le.rpm @@ -1655,13 +1655,13 @@ arches: name: delve evr: 1.26.1-1.el9_8 sourcerpm: delve-1.26.1-1.el9_8.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/os/Packages/g/gawk-all-langpacks-5.1.0-6.el9.ppc64le.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/os/Packages/g/gawk-all-langpacks-5.1.0-6.el9_8.1.ppc64le.rpm repoid: rhel-9-for-ppc64le-appstream-rpms - size: 216324 - checksum: sha256:f44a17730803f53266874678031b8121541b5b1a8047a3205c2576630216f468 + size: 218118 + checksum: sha256:c12a101cc4a7fd08f4fa1a430629fe45fa7fd56e59d5f4094aaa159c9a0aabea name: gawk-all-langpacks - evr: 5.1.0-6.el9 - sourcerpm: gawk-5.1.0-6.el9.src.rpm + evr: 5.1.0-6.el9_8.1 + sourcerpm: gawk-5.1.0-6.el9_8.1.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/os/Packages/g/gcc-11.5.0-14.el9.ppc64le.rpm repoid: rhel-9-for-ppc64le-appstream-rpms size: 29072214 @@ -1669,13 +1669,13 @@ arches: name: gcc evr: 11.5.0-14.el9 sourcerpm: gcc-11.5.0-14.el9.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/os/Packages/g/glibc-devel-2.34-275.el9_8.ppc64le.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/os/Packages/g/glibc-devel-2.34-283.el9_8.ppc64le.rpm repoid: rhel-9-for-ppc64le-appstream-rpms - size: 587995 - checksum: sha256:8a7198be7897a154c0882d7302fe37ebead33e09d41ca2643b1677360dbb06ee + size: 588179 + checksum: sha256:bbf07477a92658eb7667624537b47fc925a61c8aa379b912a5d701ff22cd924b name: glibc-devel - evr: 2.34-275.el9_8 - sourcerpm: glibc-2.34-275.el9_8.src.rpm + evr: 2.34-283.el9_8 + sourcerpm: glibc-2.34-283.el9_8.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/os/Packages/g/go-toolset-1.26.7-2.el9_8.ppc64le.rpm repoid: rhel-9-for-ppc64le-appstream-rpms size: 12469 @@ -1711,13 +1711,13 @@ arches: name: golang-src evr: 1.26.7-2.el9_8 sourcerpm: golang-1.26.7-2.el9_8.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/os/Packages/k/kernel-headers-5.14.0-687.53.1.el9_8.ppc64le.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/os/Packages/k/kernel-headers-5.14.0-687.57.1.el9_8.ppc64le.rpm repoid: rhel-9-for-ppc64le-appstream-rpms - size: 2949645 - checksum: sha256:7318493a0814cea968050892155214dc61c91549fbea2f66aa4008be389ace7c + size: 2963757 + checksum: sha256:92f45cbda331f1f1a398d83ef564d02085e9e8e5274127f949129446a1e62668 name: kernel-headers - evr: 5.14.0-687.53.1.el9_8 - sourcerpm: kernel-5.14.0-687.53.1.el9_8.src.rpm + evr: 5.14.0-687.57.1.el9_8 + sourcerpm: kernel-5.14.0-687.57.1.el9_8.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/os/Packages/l/libasan-11.5.0-14.el9.ppc64le.rpm repoid: rhel-9-for-ppc64le-appstream-rpms size: 440756 @@ -1767,41 +1767,41 @@ arches: name: mpdecimal evr: 2.5.1-3.el9 sourcerpm: mpdecimal-2.5.1-3.el9.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/os/Packages/o/openssl-devel-3.5.8-1.el9_8.ppc64le.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/os/Packages/o/openssl-devel-3.5.8-2.el9_8.ppc64le.rpm repoid: rhel-9-for-ppc64le-appstream-rpms - size: 5038938 - checksum: sha256:ee649a8cc13b05fbf552e367c502e0c1a3f272b029be90f368e4588f65407e58 + size: 5049303 + checksum: sha256:43e79a52675424d735be395869a49f30355257639571d46b7a15699364547463 name: openssl-devel - evr: 1:3.5.8-1.el9_8 - sourcerpm: openssl-3.5.8-1.el9_8.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/os/Packages/p/postgresql-15.19-1.module+el9.8.0+24800+a95bf172.ppc64le.rpm + evr: 1:3.5.8-2.el9_8 + sourcerpm: openssl-3.5.8-2.el9_8.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/os/Packages/p/postgresql-16.15-1.module+el9.8.0+24803+9dca000b.ppc64le.rpm repoid: rhel-9-for-ppc64le-appstream-rpms - size: 1893060 - checksum: sha256:cb037d48821efe06c06c45369e4a4fd9ca9f048022bd62bf7373028b9c8c74d9 + size: 2115888 + checksum: sha256:a5f63dd209db8b709ebda515c4a71c746360e2438ed54a448920a84b431ab3d3 name: postgresql - evr: 15.19-1.module+el9.8.0+24800+a95bf172 - sourcerpm: postgresql-15.19-1.module+el9.8.0+24800+a95bf172.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/os/Packages/p/postgresql-private-libs-15.19-1.module+el9.8.0+24800+a95bf172.ppc64le.rpm + evr: 16.15-1.module+el9.8.0+24803+9dca000b + sourcerpm: postgresql-16.15-1.module+el9.8.0+24803+9dca000b.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/os/Packages/p/postgresql-private-libs-16.15-1.module+el9.8.0+24803+9dca000b.ppc64le.rpm repoid: rhel-9-for-ppc64le-appstream-rpms - size: 170264 - checksum: sha256:64a378b9cb22b6228f0b1a5a1b2b3b50b959e7d8d093953f5c09058c8e2167f3 + size: 171976 + checksum: sha256:96366eb8e165d019f05d42c0b5fb801e658377f65b1d92bae29a8e9d73722725 name: postgresql-private-libs - evr: 15.19-1.module+el9.8.0+24800+a95bf172 - sourcerpm: postgresql-15.19-1.module+el9.8.0+24800+a95bf172.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/os/Packages/p/python3.12-3.12.14-1.el9_8.ppc64le.rpm + evr: 16.15-1.module+el9.8.0+24803+9dca000b + sourcerpm: postgresql-16.15-1.module+el9.8.0+24803+9dca000b.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/os/Packages/p/python3.12-3.12.15-1.el9_8.ppc64le.rpm repoid: rhel-9-for-ppc64le-appstream-rpms - size: 32494 - checksum: sha256:8e3fcabf92bf5be325dbb114ab2921b92c3a3007c9bab8b24f93d3c2a5bb44c8 + size: 32671 + checksum: sha256:8487fe96fe76e5dce0a7725ac8d7f0567f7b6efd79bfa425994e5e58c11fc4e7 name: python3.12 - evr: 3.12.14-1.el9_8 - sourcerpm: python3.12-3.12.14-1.el9_8.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/os/Packages/p/python3.12-libs-3.12.14-1.el9_8.ppc64le.rpm + evr: 3.12.15-1.el9_8 + sourcerpm: python3.12-3.12.15-1.el9_8.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/os/Packages/p/python3.12-libs-3.12.15-1.el9_8.ppc64le.rpm repoid: rhel-9-for-ppc64le-appstream-rpms - size: 10093244 - checksum: sha256:32e4a045a4ce07f8db54811f223def1404fbabe4b7b521d48a56c7480f6b1057 + size: 10098201 + checksum: sha256:da7dceeaf521afad56701795db246750b261a5759053fe37c1ebbfe623034c21 name: python3.12-libs - evr: 3.12.14-1.el9_8 - sourcerpm: python3.12-3.12.14-1.el9_8.src.rpm + evr: 3.12.15-1.el9_8 + sourcerpm: python3.12-3.12.15-1.el9_8.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/os/Packages/p/python3.12-pip-wheel-23.2.1-5.el9.noarch.rpm repoid: rhel-9-for-ppc64le-appstream-rpms size: 1527128 @@ -1998,13 +1998,13 @@ arches: name: findutils evr: 1:4.8.0-7.el9 sourcerpm: findutils-4.8.0-7.el9.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/os/Packages/g/gawk-5.1.0-6.el9.ppc64le.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/os/Packages/g/gawk-5.1.0-6.el9_8.1.ppc64le.rpm repoid: rhel-9-for-ppc64le-baseos-rpms - size: 1064251 - checksum: sha256:b0cc389ad0855900c79f2cfa525df8cbc663093056b0b5d29ec3e36a189b325e + size: 1062624 + checksum: sha256:4c2d81c6874201646d5c5eee719ecdb95f610b277b3782bb1956a19f71d17d59 name: gawk - evr: 5.1.0-6.el9 - sourcerpm: gawk-5.1.0-6.el9.src.rpm + evr: 5.1.0-6.el9_8.1 + sourcerpm: gawk-5.1.0-6.el9_8.1.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/os/Packages/g/gdbm-libs-1.23-1.el9.ppc64le.rpm repoid: rhel-9-for-ppc64le-baseos-rpms size: 65838 @@ -2012,34 +2012,34 @@ arches: name: gdbm-libs evr: 1:1.23-1.el9 sourcerpm: gdbm-1.23-1.el9.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/os/Packages/g/glibc-2.34-275.el9_8.ppc64le.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/os/Packages/g/glibc-2.34-283.el9_8.ppc64le.rpm repoid: rhel-9-for-ppc64le-baseos-rpms - size: 2910729 - checksum: sha256:283894cf70b0f263673a2c312c221fdde95c57ee42919ca55f2a8ddffe9ac35b + size: 2912446 + checksum: sha256:58cb522948a569844774333002f3f46bef3605995a12ae1d604c6b9346d2c3e2 name: glibc - evr: 2.34-275.el9_8 - sourcerpm: glibc-2.34-275.el9_8.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/os/Packages/g/glibc-common-2.34-275.el9_8.ppc64le.rpm + evr: 2.34-283.el9_8 + sourcerpm: glibc-2.34-283.el9_8.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/os/Packages/g/glibc-common-2.34-283.el9_8.ppc64le.rpm repoid: rhel-9-for-ppc64le-baseos-rpms - size: 339553 - checksum: sha256:385baf7f6dff31df34fd649b90b3352f6435bc18aa1bb6b75b4cae1f706148d2 + size: 339738 + checksum: sha256:425ee42d476c84313217e727d19fa9e5621536de9154be01a3a1de2e24732445 name: glibc-common - evr: 2.34-275.el9_8 - sourcerpm: glibc-2.34-275.el9_8.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/os/Packages/g/glibc-gconv-extra-2.34-275.el9_8.ppc64le.rpm + evr: 2.34-283.el9_8 + sourcerpm: glibc-2.34-283.el9_8.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/os/Packages/g/glibc-gconv-extra-2.34-283.el9_8.ppc64le.rpm repoid: rhel-9-for-ppc64le-baseos-rpms - size: 1828793 - checksum: sha256:6c70cd8679823b0834ddc4e31d0d878a7b65b0a2299cfd860705e7fc51328ee1 + size: 1829644 + checksum: sha256:8818013abf534cecafd88c6c8f0e47229745a9aac310d322e4eef3cf31c5ceaf name: glibc-gconv-extra - evr: 2.34-275.el9_8 - sourcerpm: glibc-2.34-275.el9_8.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/os/Packages/g/glibc-minimal-langpack-2.34-275.el9_8.ppc64le.rpm + evr: 2.34-283.el9_8 + sourcerpm: glibc-2.34-283.el9_8.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/os/Packages/g/glibc-minimal-langpack-2.34-283.el9_8.ppc64le.rpm repoid: rhel-9-for-ppc64le-baseos-rpms - size: 30901 - checksum: sha256:207421071834e057ca6e25ee11c273be2b4f230cd9fd6f16760be5be3276b39f + size: 31057 + checksum: sha256:809935a22c981342d7ce446e8085753351a2cbc29db8baa2006a67091e7331d4 name: glibc-minimal-langpack - evr: 2.34-275.el9_8 - sourcerpm: glibc-2.34-275.el9_8.src.rpm + evr: 2.34-283.el9_8 + sourcerpm: glibc-2.34-283.el9_8.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/os/Packages/g/gmp-6.2.0-13.el9.ppc64le.rpm repoid: rhel-9-for-ppc64le-baseos-rpms size: 312805 @@ -2131,13 +2131,13 @@ arches: name: libblkid evr: 2.37.4-25.el9 sourcerpm: util-linux-2.37.4-25.el9.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/os/Packages/l/libbrotli-1.0.9-9.el9_7.ppc64le.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/os/Packages/l/libbrotli-1.0.9-10.el9_8.ppc64le.rpm repoid: rhel-9-for-ppc64le-baseos-rpms - size: 349508 - checksum: sha256:ff72df4a441c2f8ee8e1bcde8dcbd5bbd89250db9caf8792ff253b7af3e1c51c + size: 349850 + checksum: sha256:42e896518cf1eafcacb3de33fc0bd827d574413543166a218ee69b22653ccea9 name: libbrotli - evr: 1.0.9-9.el9_7 - sourcerpm: brotli-1.0.9-9.el9_7.src.rpm + evr: 1.0.9-10.el9_8 + sourcerpm: brotli-1.0.9-10.el9_8.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/os/Packages/l/libcap-2.48-10.el9_8.1.ppc64le.rpm repoid: rhel-9-for-ppc64le-baseos-rpms size: 82771 @@ -2159,13 +2159,13 @@ arches: name: libcom_err evr: 1.46.5-8.el9 sourcerpm: e2fsprogs-1.46.5-8.el9.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/os/Packages/l/libcurl-7.76.1-40.el9_8.7.ppc64le.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/os/Packages/l/libcurl-7.76.1-40.el9_8.8.ppc64le.rpm repoid: rhel-9-for-ppc64le-baseos-rpms - size: 329526 - checksum: sha256:4bf40a5958683446b5757d3f8be92961ffa568ec64a794500f959533bce1bb1b + size: 329921 + checksum: sha256:3398c7352676781afaccc81aeb80925dc9b213c4fdbe6784e0662194931ad948 name: libcurl - evr: 7.76.1-40.el9_8.7 - sourcerpm: curl-7.76.1-40.el9_8.7.src.rpm + evr: 7.76.1-40.el9_8.8 + sourcerpm: curl-7.76.1-40.el9_8.8.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/os/Packages/l/libdb-5.3.28-57.el9_6.ppc64le.rpm repoid: rhel-9-for-ppc64le-baseos-rpms size: 837087 @@ -2453,13 +2453,13 @@ arches: name: openldap evr: 2.6.8-4.el9 sourcerpm: openldap-2.6.8-4.el9.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/os/Packages/o/openssl-3.5.8-1.el9_8.ppc64le.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/os/Packages/o/openssl-3.5.8-2.el9_8.ppc64le.rpm repoid: rhel-9-for-ppc64le-baseos-rpms - size: 1571362 - checksum: sha256:5ebd2f786ee6f05e2d38848451c33e3e8288766e151dfa0e73bbceab7847837c + size: 1571604 + checksum: sha256:1821093057fa1deba0217258135142da201100664028ce15b36471a36c4a20d9 name: openssl - evr: 1:3.5.8-1.el9_8 - sourcerpm: openssl-3.5.8-1.el9_8.src.rpm + evr: 1:3.5.8-2.el9_8 + sourcerpm: openssl-3.5.8-2.el9_8.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/os/Packages/o/openssl-fips-provider-3.0.7-11.el9_8.ppc64le.rpm repoid: rhel-9-for-ppc64le-baseos-rpms size: 14245 @@ -2474,13 +2474,13 @@ arches: name: openssl-fips-provider-so evr: 3.0.7-11.el9_8 sourcerpm: openssl-fips-provider-3.0.7-11.el9_8.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/os/Packages/o/openssl-libs-3.5.8-1.el9_8.ppc64le.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/os/Packages/o/openssl-libs-3.5.8-2.el9_8.ppc64le.rpm repoid: rhel-9-for-ppc64le-baseos-rpms - size: 2558899 - checksum: sha256:3883db997377c770e6ff6c87bbcaecb42470d03745639b86c66fc835f672fc1f + size: 2558230 + checksum: sha256:a3cc786155a4fe2be6d16c15b12bdb628f3f5141f21851dfd061bc550dfe34d8 name: openssl-libs - evr: 1:3.5.8-1.el9_8 - sourcerpm: openssl-3.5.8-1.el9_8.src.rpm + evr: 1:3.5.8-2.el9_8 + sourcerpm: openssl-3.5.8-2.el9_8.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/os/Packages/p/p11-kit-0.26.4-1.el9_8.ppc64le.rpm repoid: rhel-9-for-ppc64le-baseos-rpms size: 621129 @@ -2509,20 +2509,20 @@ arches: name: pcre evr: 8.44-4.el9 sourcerpm: pcre-8.44-4.el9.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/os/Packages/p/pcre2-10.40-6.el9.ppc64le.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/os/Packages/p/pcre2-10.40-7.el9_8.ppc64le.rpm repoid: rhel-9-for-ppc64le-baseos-rpms - size: 243057 - checksum: sha256:fcccf3d6981333ac0ac747ee143dae975381e02025ad2bc53e6aa7e0dc5fafb7 + size: 245697 + checksum: sha256:58ff04ee8a1bca90fbabc3310043eac023d09440579f9140fa74306425dd3de4 name: pcre2 - evr: 10.40-6.el9 - sourcerpm: pcre2-10.40-6.el9.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/os/Packages/p/pcre2-syntax-10.40-6.el9.noarch.rpm + evr: 10.40-7.el9_8 + sourcerpm: pcre2-10.40-7.el9_8.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/os/Packages/p/pcre2-syntax-10.40-7.el9_8.noarch.rpm repoid: rhel-9-for-ppc64le-baseos-rpms - size: 147926 - checksum: sha256:d386b5e9b3a4b077b2ba143882e605750855dd3354f13c55fa12ed26908cb442 + size: 150069 + checksum: sha256:e7466a43ef2bfda2b1dc07e855b0db743d48b11c0bdc1402e1d2ea6fb23d33a3 name: pcre2-syntax - evr: 10.40-6.el9 - sourcerpm: pcre2-10.40-6.el9.src.rpm + evr: 10.40-7.el9_8 + sourcerpm: pcre2-10.40-7.el9_8.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/os/Packages/p/pigz-2.8-1.el9.ppc64le.rpm repoid: rhel-9-for-ppc64le-baseos-rpms size: 101362 @@ -2642,13 +2642,13 @@ arches: name: tar evr: 2:1.34-13.el9_8 sourcerpm: tar-1.34-13.el9_8.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/os/Packages/t/tzdata-2026c-1.el9_8.noarch.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/os/Packages/t/tzdata-2026e-1.el9_8.noarch.rpm repoid: rhel-9-for-ppc64le-baseos-rpms - size: 933286 - checksum: sha256:8d6196e02853c0900c3c2e2367665beffb62cb2d0ff5f465ea2d00848a9a35dc + size: 936099 + checksum: sha256:c105d92f6cacb9bb8337b77041cedd9c9c906a72c4b5233909b9b69b78f4369f name: tzdata - evr: 2026c-1.el9_8 - sourcerpm: tzdata-2026c-1.el9_8.src.rpm + evr: 2026e-1.el9_8 + sourcerpm: tzdata-2026e-1.el9_8.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/os/Packages/u/util-linux-2.37.4-25.el9.ppc64le.rpm repoid: rhel-9-for-ppc64le-baseos-rpms size: 2426763 @@ -2714,18 +2714,18 @@ arches: checksum: sha256:2f5ece89d6a8d892816f386462d8be291ad83f1500b4c4b3655dcc7ed5192c0d name: mpdecimal evr: 2.5.1-3.el9 - - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/source/SRPMS/Packages/p/postgresql-15.19-1.module+el9.8.0+24800+a95bf172.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/source/SRPMS/Packages/p/postgresql-16.15-1.module+el9.8.0+24803+9dca000b.src.rpm repoid: rhel-9-for-ppc64le-appstream-source-rpms - size: 53848854 - checksum: sha256:9e2e4c6af57fe5ab47f3f2ca77b4cb8d16a828cfa03b1eba6704203fe926eb96 + size: 48516429 + checksum: sha256:fdb3a4e84c097bb87061fbf6b3f1f11a03b4aa6adf13b0b20cc1d439b5a2c7d7 name: postgresql - evr: 15.19-1.module+el9.8.0+24800+a95bf172 - - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/source/SRPMS/Packages/p/python3.12-3.12.14-1.el9_8.src.rpm + evr: 16.15-1.module+el9.8.0+24803+9dca000b + - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/source/SRPMS/Packages/p/python3.12-3.12.15-1.el9_8.src.rpm repoid: rhel-9-for-ppc64le-appstream-source-rpms - size: 20894742 - checksum: sha256:d855a90132b827b528e26c617484c250fd02dbe2cc8fdb8ef1f7d0d0a9f3f4f7 + size: 20904561 + checksum: sha256:8a3db1154cbfe206ac68b31268348da6039522d09be22520dd94207417146ebd name: python3.12 - evr: 3.12.14-1.el9_8 + evr: 3.12.15-1.el9_8 - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/source/SRPMS/Packages/p/python3.12-pip-23.2.1-5.el9.src.rpm repoid: rhel-9-for-ppc64le-appstream-source-rpms size: 9384965 @@ -2774,12 +2774,12 @@ arches: checksum: sha256:82174416ee39df7795da7fab8d37ea5dbbe25f1dbc2df39f5fb3f8a168bf5120 name: binutils evr: 2.35.2-72.el9 - - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/source/SRPMS/Packages/b/brotli-1.0.9-9.el9_7.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/source/SRPMS/Packages/b/brotli-1.0.9-10.el9_8.src.rpm repoid: rhel-9-for-ppc64le-baseos-source-rpms - size: 517498 - checksum: sha256:814868e0bec831c79d3e12ff76d31e06e5e62c462a1a4b6607b1f3cab7014438 + size: 519221 + checksum: sha256:c833ffbd6c6555543472ef4034a958cf4b8b8dee35b8661b2144ca6fc38908c7 name: brotli - evr: 1.0.9-9.el9_7 + evr: 1.0.9-10.el9_8 - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/source/SRPMS/Packages/b/bzip2-1.0.8-11.el9.src.rpm repoid: rhel-9-for-ppc64le-baseos-source-rpms size: 828199 @@ -2816,12 +2816,12 @@ arches: checksum: sha256:775d926429179caeade6af8dc7dea15a0bea49102c2592d5b712f996a329ec38 name: crypto-policies evr: 20260224-1.gitea0f072.el9_8 - - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/source/SRPMS/Packages/c/curl-7.76.1-40.el9_8.7.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/source/SRPMS/Packages/c/curl-7.76.1-40.el9_8.8.src.rpm repoid: rhel-9-for-ppc64le-baseos-source-rpms - size: 2579601 - checksum: sha256:2cb2bc752c2e87f1abf88ac67c2dd19a740df993d57e88f3920dedbc37f0019d + size: 2581541 + checksum: sha256:303a97cbdfaaac1d709ef7621b2b349dc403da6a553ecfe5e3114f76d757d554 name: curl - evr: 7.76.1-40.el9_8.7 + evr: 7.76.1-40.el9_8.8 - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/source/SRPMS/Packages/c/cyrus-sasl-2.1.27-22.el9.src.rpm repoid: rhel-9-for-ppc64le-baseos-source-rpms size: 4025677 @@ -2876,12 +2876,12 @@ arches: checksum: sha256:48bd4d4dd081120bcc6ab772b930a150f30b2fc89a4a14a24220383d24a30b3f name: findutils evr: 1:4.8.0-7.el9 - - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/source/SRPMS/Packages/g/gawk-5.1.0-6.el9.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/source/SRPMS/Packages/g/gawk-5.1.0-6.el9_8.1.src.rpm repoid: rhel-9-for-ppc64le-baseos-source-rpms - size: 3190934 - checksum: sha256:37571947707e835d36ceb5641b187aba13ef8f5f605a6762ce72aeea3e745b8d + size: 3197459 + checksum: sha256:8f156f11c96b1720ba2e9e287f2ddf5e0588a9c9ad17753f779ebbdf0c4e99df name: gawk - evr: 5.1.0-6.el9 + evr: 5.1.0-6.el9_8.1 - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/source/SRPMS/Packages/g/gcc-11.5.0-14.el9.src.rpm repoid: rhel-9-for-ppc64le-baseos-source-rpms size: 81978017 @@ -2894,12 +2894,12 @@ arches: checksum: sha256:ad42264274c2a792220395a4dbe736a1de6100c4e14611707ec1dd447583271f name: gdbm evr: 1:1.23-1.el9 - - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/source/SRPMS/Packages/g/glibc-2.34-275.el9_8.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/source/SRPMS/Packages/g/glibc-2.34-283.el9_8.src.rpm repoid: rhel-9-for-ppc64le-baseos-source-rpms - size: 20442764 - checksum: sha256:bd7a453f66e13da24d073f53070c2d209fcd88ca2aaa24ab3b2f81cae198b46c + size: 20462553 + checksum: sha256:9007b0a97151f43f812049a925cb418753f9fd643fe1283cc7eddc7e6785e2f3 name: glibc - evr: 2.34-275.el9_8 + evr: 2.34-283.el9_8 - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/source/SRPMS/Packages/g/gmp-6.2.0-13.el9.src.rpm repoid: rhel-9-for-ppc64le-baseos-source-rpms size: 2503825 @@ -2930,12 +2930,12 @@ arches: checksum: sha256:c4c76ebfd66ba6d00edf672797d7f077b29d279b7866a04e05258a257a5bc306 name: json-c evr: 0.14-11.el9 - - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/source/SRPMS/Packages/k/kernel-5.14.0-687.53.1.el9_8.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/source/SRPMS/Packages/k/kernel-5.14.0-687.57.1.el9_8.src.rpm repoid: rhel-9-for-ppc64le-baseos-source-rpms - size: 152530420 - checksum: sha256:28a39e99b31b7b924672b14e70b1a0608755a7898c20563f52f2c0b22c67a91a + size: 152563149 + checksum: sha256:a2660af0b87ac6a4d161ccc76c7d8a8338a44b4fbaad6da9e5529e3027f25aa2 name: kernel - evr: 5.14.0-687.53.1.el9_8 + evr: 5.14.0-687.57.1.el9_8 - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/source/SRPMS/Packages/k/keyutils-1.6.3-1.el9.src.rpm repoid: rhel-9-for-ppc64le-baseos-source-rpms size: 150790 @@ -3152,12 +3152,12 @@ arches: checksum: sha256:0d21c12c55d40d1fc2f006c8ec187b5fcc799b794cfd31ed2d98434189c62800 name: openldap evr: 2.6.8-4.el9 - - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/source/SRPMS/Packages/o/openssl-3.5.8-1.el9_8.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/source/SRPMS/Packages/o/openssl-3.5.8-2.el9_8.src.rpm repoid: rhel-9-for-ppc64le-baseos-source-rpms - size: 53429735 - checksum: sha256:393e8694b8161826f67f149cd7dde6b63da96f02efc050fda651d265907f2c20 + size: 53435636 + checksum: sha256:1e67b59efe228f804c0544a31ab3de0bb4a0c8250d3d5f5fc53a3c7794104795 name: openssl - evr: 1:3.5.8-1.el9_8 + evr: 1:3.5.8-2.el9_8 - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/source/SRPMS/Packages/o/openssl-fips-provider-3.0.7-11.el9_8.src.rpm repoid: rhel-9-for-ppc64le-baseos-source-rpms size: 28686020 @@ -3182,12 +3182,12 @@ arches: checksum: sha256:7edbd87b866a3f6e3df1426d660b902e063193d6186027bf99f6d77626a43817 name: pcre evr: 8.44-4.el9 - - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/source/SRPMS/Packages/p/pcre2-10.40-6.el9.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/source/SRPMS/Packages/p/pcre2-10.40-7.el9_8.src.rpm repoid: rhel-9-for-ppc64le-baseos-source-rpms - size: 1789790 - checksum: sha256:a570f7192be555222aa3704882b9199fb013a84ad4d7dcf40a93d8de2ecf6e0a + size: 1805958 + checksum: sha256:43c40093332890a77f7d7c573a1b540867319bbcaaec5a6dfd73101765dc6ea0 name: pcre2 - evr: 10.40-6.el9 + evr: 10.40-7.el9_8 - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/source/SRPMS/Packages/p/pigz-2.8-1.el9.src.rpm repoid: rhel-9-for-ppc64le-baseos-source-rpms size: 131701 @@ -3254,12 +3254,12 @@ arches: checksum: sha256:15eeffee75eaef17e056cef4d5207825782a755db6dfd509a759e672ffd03b6c name: tar evr: 2:1.34-13.el9_8 - - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/source/SRPMS/Packages/t/tzdata-2026c-1.el9_8.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/source/SRPMS/Packages/t/tzdata-2026e-1.el9_8.src.rpm repoid: rhel-9-for-ppc64le-baseos-source-rpms - size: 933774 - checksum: sha256:4cc10832b19807c03a30bd9317fb37dc811d2f13c11b1ed35af8d2efa481622f + size: 943814 + checksum: sha256:016ab67407e6f67418d8f36c5944396fe82c85896b9e945441f93d3cf1b823b4 name: tzdata - evr: 2026c-1.el9_8 + evr: 2026e-1.el9_8 - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/baseos/source/SRPMS/Packages/u/util-linux-2.37.4-25.el9.src.rpm repoid: rhel-9-for-ppc64le-baseos-source-rpms size: 6291867 @@ -3285,10 +3285,10 @@ arches: name: zstd evr: 1.5.5-1.el9 module_metadata: - - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/os/repodata/008320dd4d21a6fa411c839dcc3c6d1a773b019ad9bddbac6ddb3c3718c5d4a7-modules.yaml.gz + - url: https://cdn.redhat.com/content/dist/rhel9/9/ppc64le/appstream/os/repodata/2b46c0e65a7612308b8149db945f1a1710a6c4491c106262a500f9942b2fc429-modules.yaml.gz repoid: rhel-9-for-ppc64le-appstream-rpms - size: 70701 - checksum: sha256:008320dd4d21a6fa411c839dcc3c6d1a773b019ad9bddbac6ddb3c3718c5d4a7 + size: 70564 + checksum: sha256:2b46c0e65a7612308b8149db945f1a1710a6c4491c106262a500f9942b2fc429 - arch: s390x packages: - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/os/Packages/c/cpp-11.5.0-14.el9.s390x.rpm @@ -3298,13 +3298,13 @@ arches: name: cpp evr: 11.5.0-14.el9 sourcerpm: gcc-11.5.0-14.el9.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/os/Packages/g/gawk-all-langpacks-5.1.0-6.el9.s390x.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/os/Packages/g/gawk-all-langpacks-5.1.0-6.el9_8.1.s390x.rpm repoid: rhel-9-for-s390x-appstream-rpms - size: 216312 - checksum: sha256:fc71db041e9b749a3f8bcbc9f812509625cfaf5d4fa83d37324c4dbdeb00a36e + size: 218106 + checksum: sha256:4828be3b34d4f1e54ec4b0ff53c1291f18b01be78165e10ec4967b6b061e13d4 name: gawk-all-langpacks - evr: 5.1.0-6.el9 - sourcerpm: gawk-5.1.0-6.el9.src.rpm + evr: 5.1.0-6.el9_8.1 + sourcerpm: gawk-5.1.0-6.el9_8.1.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/os/Packages/g/gcc-11.5.0-14.el9.s390x.rpm repoid: rhel-9-for-s390x-appstream-rpms size: 26825105 @@ -3312,20 +3312,20 @@ arches: name: gcc evr: 11.5.0-14.el9 sourcerpm: gcc-11.5.0-14.el9.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/os/Packages/g/glibc-devel-2.34-275.el9_8.s390x.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/os/Packages/g/glibc-devel-2.34-283.el9_8.s390x.rpm repoid: rhel-9-for-s390x-appstream-rpms - size: 54280 - checksum: sha256:afc3171234ba01129468f8e08f175f17454f060961c364bc35a1242ea41b9111 + size: 54448 + checksum: sha256:5231ede78f3db9f2a58735cb755f8f930f00f0398c60b809c8d6ca0ed1b231c2 name: glibc-devel - evr: 2.34-275.el9_8 - sourcerpm: glibc-2.34-275.el9_8.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/os/Packages/g/glibc-headers-2.34-275.el9_8.s390x.rpm + evr: 2.34-283.el9_8 + sourcerpm: glibc-2.34-283.el9_8.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/os/Packages/g/glibc-headers-2.34-283.el9_8.s390x.rpm repoid: rhel-9-for-s390x-appstream-rpms - size: 557847 - checksum: sha256:4d87900e5abc5755b1bf57a4e127d6edddd8f59b1501b5005d0463c3ad3e457d + size: 558061 + checksum: sha256:72bb935c23190ee6b4162cff38de2e5289f55de71e454bcd198624424d3e3d8b name: glibc-headers - evr: 2.34-275.el9_8 - sourcerpm: glibc-2.34-275.el9_8.src.rpm + evr: 2.34-283.el9_8 + sourcerpm: glibc-2.34-283.el9_8.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/os/Packages/g/go-toolset-1.26.7-2.el9_8.s390x.rpm repoid: rhel-9-for-s390x-appstream-rpms size: 12445 @@ -3361,13 +3361,13 @@ arches: name: golang-src evr: 1.26.7-2.el9_8 sourcerpm: golang-1.26.7-2.el9_8.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/os/Packages/k/kernel-headers-5.14.0-687.53.1.el9_8.s390x.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/os/Packages/k/kernel-headers-5.14.0-687.57.1.el9_8.s390x.rpm repoid: rhel-9-for-s390x-appstream-rpms - size: 2955557 - checksum: sha256:9b1a97d19e52dfb5387cae5fd1a7888ce3e1f794b17117ef4cc1eeb1daca8aeb + size: 2969965 + checksum: sha256:33a6f1dfc82b4e4487e3a00df7f49d05a07644da11e9f3ccbf32aca3453837fa name: kernel-headers - evr: 5.14.0-687.53.1.el9_8 - sourcerpm: kernel-5.14.0-687.53.1.el9_8.src.rpm + evr: 5.14.0-687.57.1.el9_8 + sourcerpm: kernel-5.14.0-687.57.1.el9_8.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/os/Packages/l/libasan-11.5.0-14.el9.s390x.rpm repoid: rhel-9-for-s390x-appstream-rpms size: 412888 @@ -3417,41 +3417,41 @@ arches: name: mpdecimal evr: 2.5.1-3.el9 sourcerpm: mpdecimal-2.5.1-3.el9.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/os/Packages/o/openssl-devel-3.5.8-1.el9_8.s390x.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/os/Packages/o/openssl-devel-3.5.8-2.el9_8.s390x.rpm repoid: rhel-9-for-s390x-appstream-rpms - size: 5048884 - checksum: sha256:d8b8c7b922db5cdcb11f503591c39d5ab9031262193f6a5932c4b5d8b17520cc + size: 5050289 + checksum: sha256:fcd9416c3dfecbd9144d886d183fb0b9633fb05e142fb5cade8ae01874a82396 name: openssl-devel - evr: 1:3.5.8-1.el9_8 - sourcerpm: openssl-3.5.8-1.el9_8.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/os/Packages/p/postgresql-15.19-1.module+el9.8.0+24800+a95bf172.s390x.rpm + evr: 1:3.5.8-2.el9_8 + sourcerpm: openssl-3.5.8-2.el9_8.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/os/Packages/p/postgresql-16.15-1.module+el9.8.0+24803+9dca000b.s390x.rpm repoid: rhel-9-for-s390x-appstream-rpms - size: 1861033 - checksum: sha256:a3aa3b5a24a81f1cb8a36a0eecba4f0eb00a54cbb5840d101533cf4793d9c67e + size: 2085027 + checksum: sha256:7c047295d9ae473c18fbbeb9b0ce4a432ae6a75f1a8378d857809cbad2596214 name: postgresql - evr: 15.19-1.module+el9.8.0+24800+a95bf172 - sourcerpm: postgresql-15.19-1.module+el9.8.0+24800+a95bf172.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/os/Packages/p/postgresql-private-libs-15.19-1.module+el9.8.0+24800+a95bf172.s390x.rpm + evr: 16.15-1.module+el9.8.0+24803+9dca000b + sourcerpm: postgresql-16.15-1.module+el9.8.0+24803+9dca000b.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/os/Packages/p/postgresql-private-libs-16.15-1.module+el9.8.0+24803+9dca000b.s390x.rpm repoid: rhel-9-for-s390x-appstream-rpms - size: 147716 - checksum: sha256:738dfc0971a4ec61b9092d9598809c5cb082c6aff8db768507d9c0aaa5ae35ea + size: 150732 + checksum: sha256:fa72693328f1f6fa5958912ca1d048aec07563e4bc7f013ccb1f4050805a8703 name: postgresql-private-libs - evr: 15.19-1.module+el9.8.0+24800+a95bf172 - sourcerpm: postgresql-15.19-1.module+el9.8.0+24800+a95bf172.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/os/Packages/p/python3.12-3.12.14-1.el9_8.s390x.rpm + evr: 16.15-1.module+el9.8.0+24803+9dca000b + sourcerpm: postgresql-16.15-1.module+el9.8.0+24803+9dca000b.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/os/Packages/p/python3.12-3.12.15-1.el9_8.s390x.rpm repoid: rhel-9-for-s390x-appstream-rpms - size: 32298 - checksum: sha256:1d9c6400225666643a98052596e6a10a3e0f84c81daf0fef28867331749bf2f9 + size: 32494 + checksum: sha256:5056455a54f6c67bab0c0758930535a8364a18b71075ab6e34e37076111c87b5 name: python3.12 - evr: 3.12.14-1.el9_8 - sourcerpm: python3.12-3.12.14-1.el9_8.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/os/Packages/p/python3.12-libs-3.12.14-1.el9_8.s390x.rpm + evr: 3.12.15-1.el9_8 + sourcerpm: python3.12-3.12.15-1.el9_8.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/os/Packages/p/python3.12-libs-3.12.15-1.el9_8.s390x.rpm repoid: rhel-9-for-s390x-appstream-rpms - size: 9974169 - checksum: sha256:000bf9abc0a7627f453b50e1a94181a2f6729927d8d3324aa27ab8b251fb1ed1 + size: 9982480 + checksum: sha256:d83ff7b955bd7bc2aadbdd44a6f80bbf6004b0c08371db0c7b90945a587e5b78 name: python3.12-libs - evr: 3.12.14-1.el9_8 - sourcerpm: python3.12-3.12.14-1.el9_8.src.rpm + evr: 3.12.15-1.el9_8 + sourcerpm: python3.12-3.12.15-1.el9_8.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/os/Packages/p/python3.12-pip-wheel-23.2.1-5.el9.noarch.rpm repoid: rhel-9-for-s390x-appstream-rpms size: 1527128 @@ -3648,13 +3648,13 @@ arches: name: findutils evr: 1:4.8.0-7.el9 sourcerpm: findutils-4.8.0-7.el9.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/os/Packages/g/gawk-5.1.0-6.el9.s390x.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/os/Packages/g/gawk-5.1.0-6.el9_8.1.s390x.rpm repoid: rhel-9-for-s390x-baseos-rpms - size: 1029188 - checksum: sha256:d34fd3f586240f43f71bc74824ae513cba2e4a6812f0ebbd101122e7e99bafe8 + size: 1029963 + checksum: sha256:7382392d06b5e21161966cec0c187bba6afb9710047ab17d79e7b55ce43def40 name: gawk - evr: 5.1.0-6.el9 - sourcerpm: gawk-5.1.0-6.el9.src.rpm + evr: 5.1.0-6.el9_8.1 + sourcerpm: gawk-5.1.0-6.el9_8.1.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/os/Packages/g/gdbm-libs-1.23-1.el9.s390x.rpm repoid: rhel-9-for-s390x-baseos-rpms size: 60437 @@ -3662,34 +3662,34 @@ arches: name: gdbm-libs evr: 1:1.23-1.el9 sourcerpm: gdbm-1.23-1.el9.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/os/Packages/g/glibc-2.34-275.el9_8.s390x.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/os/Packages/g/glibc-2.34-283.el9_8.s390x.rpm repoid: rhel-9-for-s390x-baseos-rpms - size: 1790650 - checksum: sha256:f73a9a19f2139ac3aca555309f1f9d5909a549d1bda684eadbdc9ed95e536407 + size: 1790814 + checksum: sha256:7b757b36efa81a2a5ed6e3cd47d1c0df6c1a8b52e9c7975411d308e022bc09fd name: glibc - evr: 2.34-275.el9_8 - sourcerpm: glibc-2.34-275.el9_8.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/os/Packages/g/glibc-common-2.34-275.el9_8.s390x.rpm + evr: 2.34-283.el9_8 + sourcerpm: glibc-2.34-283.el9_8.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/os/Packages/g/glibc-common-2.34-283.el9_8.s390x.rpm repoid: rhel-9-for-s390x-baseos-rpms - size: 325700 - checksum: sha256:cb7edc5131da954868e8053a9203ec7d94375627c1c8b717a3e42e7768b037e1 + size: 325539 + checksum: sha256:a240480901672e62f1d3f9b46c4b8beed69fa912d9871ccc11080b0ef35c5fd4 name: glibc-common - evr: 2.34-275.el9_8 - sourcerpm: glibc-2.34-275.el9_8.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/os/Packages/g/glibc-gconv-extra-2.34-275.el9_8.s390x.rpm + evr: 2.34-283.el9_8 + sourcerpm: glibc-2.34-283.el9_8.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/os/Packages/g/glibc-gconv-extra-2.34-283.el9_8.s390x.rpm repoid: rhel-9-for-s390x-baseos-rpms - size: 1755391 - checksum: sha256:7959a606ce9cab72ff1f0221e4f1d43a4cdf905a9b49bf868401b9409cc324cc + size: 1757088 + checksum: sha256:e4d30d442cc59041e259199420b8be9e73f6526671983020526586fbbf50b940 name: glibc-gconv-extra - evr: 2.34-275.el9_8 - sourcerpm: glibc-2.34-275.el9_8.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/os/Packages/g/glibc-minimal-langpack-2.34-275.el9_8.s390x.rpm + evr: 2.34-283.el9_8 + sourcerpm: glibc-2.34-283.el9_8.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/os/Packages/g/glibc-minimal-langpack-2.34-283.el9_8.s390x.rpm repoid: rhel-9-for-s390x-baseos-rpms - size: 30885 - checksum: sha256:96398be130a7db017656673be8f39dbd063d0fce335c1eb20b300b6c503e8dbc + size: 31037 + checksum: sha256:86d08f88de137ba5dfef4aff54bbb29b628a9b774f0930423f44fc5a2e340000 name: glibc-minimal-langpack - evr: 2.34-275.el9_8 - sourcerpm: glibc-2.34-275.el9_8.src.rpm + evr: 2.34-283.el9_8 + sourcerpm: glibc-2.34-283.el9_8.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/os/Packages/g/gmp-6.2.0-13.el9.s390x.rpm repoid: rhel-9-for-s390x-baseos-rpms size: 296399 @@ -3781,13 +3781,13 @@ arches: name: libblkid evr: 2.37.4-25.el9 sourcerpm: util-linux-2.37.4-25.el9.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/os/Packages/l/libbrotli-1.0.9-9.el9_7.s390x.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/os/Packages/l/libbrotli-1.0.9-10.el9_8.s390x.rpm repoid: rhel-9-for-s390x-baseos-rpms - size: 329308 - checksum: sha256:ea47c24d8670923c31472fac1c2887ee8124b0a142ffb8a3c4953da8bf65c238 + size: 329468 + checksum: sha256:1d94d6401d4dd4c75b96b810046f1918c82460e0c3e9651e98d0e0c810ba0e22 name: libbrotli - evr: 1.0.9-9.el9_7 - sourcerpm: brotli-1.0.9-9.el9_7.src.rpm + evr: 1.0.9-10.el9_8 + sourcerpm: brotli-1.0.9-10.el9_8.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/os/Packages/l/libcap-2.48-10.el9_8.1.s390x.rpm repoid: rhel-9-for-s390x-baseos-rpms size: 77818 @@ -3809,13 +3809,13 @@ arches: name: libcom_err evr: 1.46.5-8.el9 sourcerpm: e2fsprogs-1.46.5-8.el9.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/os/Packages/l/libcurl-7.76.1-40.el9_8.7.s390x.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/os/Packages/l/libcurl-7.76.1-40.el9_8.8.s390x.rpm repoid: rhel-9-for-s390x-baseos-rpms - size: 291971 - checksum: sha256:18d6734aa46eb35076d0dc6a1d0cc1d8df42be99d2c964fe7125890df4144056 + size: 292806 + checksum: sha256:fba30ce3801d491bd946c7df555b671f8973fab988413c006abe5874d5547207 name: libcurl - evr: 7.76.1-40.el9_8.7 - sourcerpm: curl-7.76.1-40.el9_8.7.src.rpm + evr: 7.76.1-40.el9_8.8 + sourcerpm: curl-7.76.1-40.el9_8.8.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/os/Packages/l/libdb-5.3.28-57.el9_6.s390x.rpm repoid: rhel-9-for-s390x-baseos-rpms size: 721041 @@ -4096,13 +4096,13 @@ arches: name: openldap evr: 2.6.8-4.el9 sourcerpm: openldap-2.6.8-4.el9.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/os/Packages/o/openssl-3.5.8-1.el9_8.s390x.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/os/Packages/o/openssl-3.5.8-2.el9_8.s390x.rpm repoid: rhel-9-for-s390x-baseos-rpms - size: 1554467 - checksum: sha256:edd90361aa0492b198d636debc6a0ce01b7213ccaaeb199a7679a151bf15317c + size: 1554288 + checksum: sha256:45353765ef5b07378507001e3a16b9752fb25ce4c98fba108fe4272902661c43 name: openssl - evr: 1:3.5.8-1.el9_8 - sourcerpm: openssl-3.5.8-1.el9_8.src.rpm + evr: 1:3.5.8-2.el9_8 + sourcerpm: openssl-3.5.8-2.el9_8.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/os/Packages/o/openssl-fips-provider-3.0.7-11.el9_8.s390x.rpm repoid: rhel-9-for-s390x-baseos-rpms size: 14236 @@ -4117,13 +4117,13 @@ arches: name: openssl-fips-provider-so evr: 3.0.7-11.el9_8 sourcerpm: openssl-fips-provider-3.0.7-11.el9_8.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/os/Packages/o/openssl-libs-3.5.8-1.el9_8.s390x.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/os/Packages/o/openssl-libs-3.5.8-2.el9_8.s390x.rpm repoid: rhel-9-for-s390x-baseos-rpms - size: 2034590 - checksum: sha256:60c73dcda28a2bbc224c5a307521128fe58b5bc8e28d752f25d21822d974e3fc + size: 2034568 + checksum: sha256:0c2715769f47c21fdaf465e61ee350d939903cc06f50ee0f2fcb9448809c4f5c name: openssl-libs - evr: 1:3.5.8-1.el9_8 - sourcerpm: openssl-3.5.8-1.el9_8.src.rpm + evr: 1:3.5.8-2.el9_8 + sourcerpm: openssl-3.5.8-2.el9_8.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/os/Packages/p/p11-kit-0.26.4-1.el9_8.s390x.rpm repoid: rhel-9-for-s390x-baseos-rpms size: 628346 @@ -4152,20 +4152,20 @@ arches: name: pcre evr: 8.44-4.el9 sourcerpm: pcre-8.44-4.el9.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/os/Packages/p/pcre2-10.40-6.el9.s390x.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/os/Packages/p/pcre2-10.40-7.el9_8.s390x.rpm repoid: rhel-9-for-s390x-baseos-rpms - size: 224385 - checksum: sha256:b7166437e0179c46304403ddb126fd8a3d6c15d29b33a395b951e54b67697559 + size: 227201 + checksum: sha256:fee820347bd080be9dabf5dc7ce23a1d354240703f2665c5791607fcf2b67aec name: pcre2 - evr: 10.40-6.el9 - sourcerpm: pcre2-10.40-6.el9.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/os/Packages/p/pcre2-syntax-10.40-6.el9.noarch.rpm + evr: 10.40-7.el9_8 + sourcerpm: pcre2-10.40-7.el9_8.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/os/Packages/p/pcre2-syntax-10.40-7.el9_8.noarch.rpm repoid: rhel-9-for-s390x-baseos-rpms - size: 147926 - checksum: sha256:d386b5e9b3a4b077b2ba143882e605750855dd3354f13c55fa12ed26908cb442 + size: 150069 + checksum: sha256:e7466a43ef2bfda2b1dc07e855b0db743d48b11c0bdc1402e1d2ea6fb23d33a3 name: pcre2-syntax - evr: 10.40-6.el9 - sourcerpm: pcre2-10.40-6.el9.src.rpm + evr: 10.40-7.el9_8 + sourcerpm: pcre2-10.40-7.el9_8.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/os/Packages/p/pigz-2.8-1.el9.s390x.rpm repoid: rhel-9-for-s390x-baseos-rpms size: 96630 @@ -4285,13 +4285,13 @@ arches: name: tar evr: 2:1.34-13.el9_8 sourcerpm: tar-1.34-13.el9_8.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/os/Packages/t/tzdata-2026c-1.el9_8.noarch.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/os/Packages/t/tzdata-2026e-1.el9_8.noarch.rpm repoid: rhel-9-for-s390x-baseos-rpms - size: 933286 - checksum: sha256:8d6196e02853c0900c3c2e2367665beffb62cb2d0ff5f465ea2d00848a9a35dc + size: 936099 + checksum: sha256:c105d92f6cacb9bb8337b77041cedd9c9c906a72c4b5233909b9b69b78f4369f name: tzdata - evr: 2026c-1.el9_8 - sourcerpm: tzdata-2026c-1.el9_8.src.rpm + evr: 2026e-1.el9_8 + sourcerpm: tzdata-2026e-1.el9_8.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/os/Packages/u/util-linux-2.37.4-25.el9.s390x.rpm repoid: rhel-9-for-s390x-baseos-rpms size: 2327681 @@ -4351,18 +4351,18 @@ arches: checksum: sha256:2f5ece89d6a8d892816f386462d8be291ad83f1500b4c4b3655dcc7ed5192c0d name: mpdecimal evr: 2.5.1-3.el9 - - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/source/SRPMS/Packages/p/postgresql-15.19-1.module+el9.8.0+24800+a95bf172.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/source/SRPMS/Packages/p/postgresql-16.15-1.module+el9.8.0+24803+9dca000b.src.rpm repoid: rhel-9-for-s390x-appstream-source-rpms - size: 53848854 - checksum: sha256:9e2e4c6af57fe5ab47f3f2ca77b4cb8d16a828cfa03b1eba6704203fe926eb96 + size: 48516429 + checksum: sha256:fdb3a4e84c097bb87061fbf6b3f1f11a03b4aa6adf13b0b20cc1d439b5a2c7d7 name: postgresql - evr: 15.19-1.module+el9.8.0+24800+a95bf172 - - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/source/SRPMS/Packages/p/python3.12-3.12.14-1.el9_8.src.rpm + evr: 16.15-1.module+el9.8.0+24803+9dca000b + - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/source/SRPMS/Packages/p/python3.12-3.12.15-1.el9_8.src.rpm repoid: rhel-9-for-s390x-appstream-source-rpms - size: 20894742 - checksum: sha256:d855a90132b827b528e26c617484c250fd02dbe2cc8fdb8ef1f7d0d0a9f3f4f7 + size: 20904561 + checksum: sha256:8a3db1154cbfe206ac68b31268348da6039522d09be22520dd94207417146ebd name: python3.12 - evr: 3.12.14-1.el9_8 + evr: 3.12.15-1.el9_8 - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/source/SRPMS/Packages/p/python3.12-pip-23.2.1-5.el9.src.rpm repoid: rhel-9-for-s390x-appstream-source-rpms size: 9384965 @@ -4411,12 +4411,12 @@ arches: checksum: sha256:82174416ee39df7795da7fab8d37ea5dbbe25f1dbc2df39f5fb3f8a168bf5120 name: binutils evr: 2.35.2-72.el9 - - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/source/SRPMS/Packages/b/brotli-1.0.9-9.el9_7.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/source/SRPMS/Packages/b/brotli-1.0.9-10.el9_8.src.rpm repoid: rhel-9-for-s390x-baseos-source-rpms - size: 517498 - checksum: sha256:814868e0bec831c79d3e12ff76d31e06e5e62c462a1a4b6607b1f3cab7014438 + size: 519221 + checksum: sha256:c833ffbd6c6555543472ef4034a958cf4b8b8dee35b8661b2144ca6fc38908c7 name: brotli - evr: 1.0.9-9.el9_7 + evr: 1.0.9-10.el9_8 - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/source/SRPMS/Packages/b/bzip2-1.0.8-11.el9.src.rpm repoid: rhel-9-for-s390x-baseos-source-rpms size: 828199 @@ -4453,12 +4453,12 @@ arches: checksum: sha256:775d926429179caeade6af8dc7dea15a0bea49102c2592d5b712f996a329ec38 name: crypto-policies evr: 20260224-1.gitea0f072.el9_8 - - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/source/SRPMS/Packages/c/curl-7.76.1-40.el9_8.7.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/source/SRPMS/Packages/c/curl-7.76.1-40.el9_8.8.src.rpm repoid: rhel-9-for-s390x-baseos-source-rpms - size: 2579601 - checksum: sha256:2cb2bc752c2e87f1abf88ac67c2dd19a740df993d57e88f3920dedbc37f0019d + size: 2581541 + checksum: sha256:303a97cbdfaaac1d709ef7621b2b349dc403da6a553ecfe5e3114f76d757d554 name: curl - evr: 7.76.1-40.el9_8.7 + evr: 7.76.1-40.el9_8.8 - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/source/SRPMS/Packages/c/cyrus-sasl-2.1.27-22.el9.src.rpm repoid: rhel-9-for-s390x-baseos-source-rpms size: 4025677 @@ -4513,12 +4513,12 @@ arches: checksum: sha256:48bd4d4dd081120bcc6ab772b930a150f30b2fc89a4a14a24220383d24a30b3f name: findutils evr: 1:4.8.0-7.el9 - - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/source/SRPMS/Packages/g/gawk-5.1.0-6.el9.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/source/SRPMS/Packages/g/gawk-5.1.0-6.el9_8.1.src.rpm repoid: rhel-9-for-s390x-baseos-source-rpms - size: 3190934 - checksum: sha256:37571947707e835d36ceb5641b187aba13ef8f5f605a6762ce72aeea3e745b8d + size: 3197459 + checksum: sha256:8f156f11c96b1720ba2e9e287f2ddf5e0588a9c9ad17753f779ebbdf0c4e99df name: gawk - evr: 5.1.0-6.el9 + evr: 5.1.0-6.el9_8.1 - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/source/SRPMS/Packages/g/gcc-11.5.0-14.el9.src.rpm repoid: rhel-9-for-s390x-baseos-source-rpms size: 81978017 @@ -4531,12 +4531,12 @@ arches: checksum: sha256:ad42264274c2a792220395a4dbe736a1de6100c4e14611707ec1dd447583271f name: gdbm evr: 1:1.23-1.el9 - - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/source/SRPMS/Packages/g/glibc-2.34-275.el9_8.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/source/SRPMS/Packages/g/glibc-2.34-283.el9_8.src.rpm repoid: rhel-9-for-s390x-baseos-source-rpms - size: 20442764 - checksum: sha256:bd7a453f66e13da24d073f53070c2d209fcd88ca2aaa24ab3b2f81cae198b46c + size: 20462553 + checksum: sha256:9007b0a97151f43f812049a925cb418753f9fd643fe1283cc7eddc7e6785e2f3 name: glibc - evr: 2.34-275.el9_8 + evr: 2.34-283.el9_8 - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/source/SRPMS/Packages/g/gmp-6.2.0-13.el9.src.rpm repoid: rhel-9-for-s390x-baseos-source-rpms size: 2503825 @@ -4567,12 +4567,12 @@ arches: checksum: sha256:c4c76ebfd66ba6d00edf672797d7f077b29d279b7866a04e05258a257a5bc306 name: json-c evr: 0.14-11.el9 - - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/source/SRPMS/Packages/k/kernel-5.14.0-687.53.1.el9_8.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/source/SRPMS/Packages/k/kernel-5.14.0-687.57.1.el9_8.src.rpm repoid: rhel-9-for-s390x-baseos-source-rpms - size: 152530420 - checksum: sha256:28a39e99b31b7b924672b14e70b1a0608755a7898c20563f52f2c0b22c67a91a + size: 152563149 + checksum: sha256:a2660af0b87ac6a4d161ccc76c7d8a8338a44b4fbaad6da9e5529e3027f25aa2 name: kernel - evr: 5.14.0-687.53.1.el9_8 + evr: 5.14.0-687.57.1.el9_8 - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/source/SRPMS/Packages/k/keyutils-1.6.3-1.el9.src.rpm repoid: rhel-9-for-s390x-baseos-source-rpms size: 150790 @@ -4783,12 +4783,12 @@ arches: checksum: sha256:0d21c12c55d40d1fc2f006c8ec187b5fcc799b794cfd31ed2d98434189c62800 name: openldap evr: 2.6.8-4.el9 - - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/source/SRPMS/Packages/o/openssl-3.5.8-1.el9_8.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/source/SRPMS/Packages/o/openssl-3.5.8-2.el9_8.src.rpm repoid: rhel-9-for-s390x-baseos-source-rpms - size: 53429735 - checksum: sha256:393e8694b8161826f67f149cd7dde6b63da96f02efc050fda651d265907f2c20 + size: 53435636 + checksum: sha256:1e67b59efe228f804c0544a31ab3de0bb4a0c8250d3d5f5fc53a3c7794104795 name: openssl - evr: 1:3.5.8-1.el9_8 + evr: 1:3.5.8-2.el9_8 - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/source/SRPMS/Packages/o/openssl-fips-provider-3.0.7-11.el9_8.src.rpm repoid: rhel-9-for-s390x-baseos-source-rpms size: 28686020 @@ -4813,12 +4813,12 @@ arches: checksum: sha256:7edbd87b866a3f6e3df1426d660b902e063193d6186027bf99f6d77626a43817 name: pcre evr: 8.44-4.el9 - - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/source/SRPMS/Packages/p/pcre2-10.40-6.el9.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/source/SRPMS/Packages/p/pcre2-10.40-7.el9_8.src.rpm repoid: rhel-9-for-s390x-baseos-source-rpms - size: 1789790 - checksum: sha256:a570f7192be555222aa3704882b9199fb013a84ad4d7dcf40a93d8de2ecf6e0a + size: 1805958 + checksum: sha256:43c40093332890a77f7d7c573a1b540867319bbcaaec5a6dfd73101765dc6ea0 name: pcre2 - evr: 10.40-6.el9 + evr: 10.40-7.el9_8 - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/source/SRPMS/Packages/p/pigz-2.8-1.el9.src.rpm repoid: rhel-9-for-s390x-baseos-source-rpms size: 131701 @@ -4885,12 +4885,12 @@ arches: checksum: sha256:15eeffee75eaef17e056cef4d5207825782a755db6dfd509a759e672ffd03b6c name: tar evr: 2:1.34-13.el9_8 - - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/source/SRPMS/Packages/t/tzdata-2026c-1.el9_8.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/source/SRPMS/Packages/t/tzdata-2026e-1.el9_8.src.rpm repoid: rhel-9-for-s390x-baseos-source-rpms - size: 933774 - checksum: sha256:4cc10832b19807c03a30bd9317fb37dc811d2f13c11b1ed35af8d2efa481622f + size: 943814 + checksum: sha256:016ab67407e6f67418d8f36c5944396fe82c85896b9e945441f93d3cf1b823b4 name: tzdata - evr: 2026c-1.el9_8 + evr: 2026e-1.el9_8 - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/baseos/source/SRPMS/Packages/u/util-linux-2.37.4-25.el9.src.rpm repoid: rhel-9-for-s390x-baseos-source-rpms size: 6291867 @@ -4916,10 +4916,10 @@ arches: name: zstd evr: 1.5.5-1.el9 module_metadata: - - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/os/repodata/dcb8fd74f73344285f768b59afb2d490b9f2b3c9afcee99023e950a69df10ba7-modules.yaml.gz + - url: https://cdn.redhat.com/content/dist/rhel9/9/s390x/appstream/os/repodata/7dc4986dbd0c0869d9d96782d955fa25fd75b6262959b8b115c35b4a58ed5570-modules.yaml.gz repoid: rhel-9-for-s390x-appstream-rpms - size: 65924 - checksum: sha256:dcb8fd74f73344285f768b59afb2d490b9f2b3c9afcee99023e950a69df10ba7 + size: 65738 + checksum: sha256:7dc4986dbd0c0869d9d96782d955fa25fd75b6262959b8b115c35b4a58ed5570 - arch: x86_64 packages: - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/os/Packages/c/cpp-11.5.0-14.el9.x86_64.rpm @@ -4936,13 +4936,13 @@ arches: name: delve evr: 1.26.1-1.el9_8 sourcerpm: delve-1.26.1-1.el9_8.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/os/Packages/g/gawk-all-langpacks-5.1.0-6.el9.x86_64.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/os/Packages/g/gawk-all-langpacks-5.1.0-6.el9_8.1.x86_64.rpm repoid: rhel-9-for-x86_64-appstream-rpms - size: 216340 - checksum: sha256:c1fcc71c1cc1160d58ace4b60cc6733b68d6f6d406e5ec5ce24327787f452cd1 + size: 218134 + checksum: sha256:bedaac1d71ca9f41e3398dc413dea2008bfd20799de1b4db14e37ebb7a7ce74d name: gawk-all-langpacks - evr: 5.1.0-6.el9 - sourcerpm: gawk-5.1.0-6.el9.src.rpm + evr: 5.1.0-6.el9_8.1 + sourcerpm: gawk-5.1.0-6.el9_8.1.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/os/Packages/g/gcc-11.5.0-14.el9.x86_64.rpm repoid: rhel-9-for-x86_64-appstream-rpms size: 33982584 @@ -4950,20 +4950,20 @@ arches: name: gcc evr: 11.5.0-14.el9 sourcerpm: gcc-11.5.0-14.el9.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/os/Packages/g/glibc-devel-2.34-275.el9_8.x86_64.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/os/Packages/g/glibc-devel-2.34-283.el9_8.x86_64.rpm repoid: rhel-9-for-x86_64-appstream-rpms - size: 46499 - checksum: sha256:a3b6ed698d21192fa7c421094a5d6648411fba4252db28c96d629778c86e6cd5 + size: 46663 + checksum: sha256:5f47e3ae6aea8fde6cbb02357463b3e8c884971284267ced3806b16a93a5c2e5 name: glibc-devel - evr: 2.34-275.el9_8 - sourcerpm: glibc-2.34-275.el9_8.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/os/Packages/g/glibc-headers-2.34-275.el9_8.x86_64.rpm + evr: 2.34-283.el9_8 + sourcerpm: glibc-2.34-283.el9_8.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/os/Packages/g/glibc-headers-2.34-283.el9_8.x86_64.rpm repoid: rhel-9-for-x86_64-appstream-rpms - size: 567171 - checksum: sha256:2124192aba2e7931cdf00c2dcd4b70b71b313790c28dcf09b2fb09cc9831c86f + size: 567260 + checksum: sha256:796a7079041c288eec9e74437c2f9f85513c0dfade393877a9281828bb55e966 name: glibc-headers - evr: 2.34-275.el9_8 - sourcerpm: glibc-2.34-275.el9_8.src.rpm + evr: 2.34-283.el9_8 + sourcerpm: glibc-2.34-283.el9_8.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/os/Packages/g/go-toolset-1.26.7-2.el9_8.x86_64.rpm repoid: rhel-9-for-x86_64-appstream-rpms size: 12485 @@ -4999,13 +4999,13 @@ arches: name: golang-src evr: 1.26.7-2.el9_8 sourcerpm: golang-1.26.7-2.el9_8.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/os/Packages/k/kernel-headers-5.14.0-687.53.1.el9_8.x86_64.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/os/Packages/k/kernel-headers-5.14.0-687.57.1.el9_8.x86_64.rpm repoid: rhel-9-for-x86_64-appstream-rpms - size: 2965145 - checksum: sha256:2473df2cf5b65c762af7941fe87324e98dc0e8b42d1e5745051a0405e200b7f5 + size: 2979225 + checksum: sha256:c5766b2f8e4531f824575546028de7d8eb9fa95d7246dfafd4883c58312a2ef2 name: kernel-headers - evr: 5.14.0-687.53.1.el9_8 - sourcerpm: kernel-5.14.0-687.53.1.el9_8.src.rpm + evr: 5.14.0-687.57.1.el9_8 + sourcerpm: kernel-5.14.0-687.57.1.el9_8.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/os/Packages/l/libmpc-1.2.1-4.el9.x86_64.rpm repoid: rhel-9-for-x86_64-appstream-rpms size: 66075 @@ -5041,41 +5041,41 @@ arches: name: mpdecimal evr: 2.5.1-3.el9 sourcerpm: mpdecimal-2.5.1-3.el9.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/os/Packages/o/openssl-devel-3.5.8-1.el9_8.x86_64.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/os/Packages/o/openssl-devel-3.5.8-2.el9_8.x86_64.rpm repoid: rhel-9-for-x86_64-appstream-rpms - size: 5039851 - checksum: sha256:8042a4b1134610ef06c27b7314a5fcd4ce887d1065d4b9e861bc3f647d7fb792 + size: 5050455 + checksum: sha256:72a693633446cd95202dd926109bf25aeb8ace8d91a630583f6d25e0ce10c0f3 name: openssl-devel - evr: 1:3.5.8-1.el9_8 - sourcerpm: openssl-3.5.8-1.el9_8.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/os/Packages/p/postgresql-15.19-1.module+el9.8.0+24800+a95bf172.x86_64.rpm + evr: 1:3.5.8-2.el9_8 + sourcerpm: openssl-3.5.8-2.el9_8.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/os/Packages/p/postgresql-16.15-1.module+el9.8.0+24803+9dca000b.x86_64.rpm repoid: rhel-9-for-x86_64-appstream-rpms - size: 1842434 - checksum: sha256:18506bf4217ef40600adcb830fe651840f041423ef3bea67bd73d6f8c78e383c + size: 2067493 + checksum: sha256:f078cf1183f6bb750e39cdcab4d2ecb3f936d745b7360b741a6919e926855ad7 name: postgresql - evr: 15.19-1.module+el9.8.0+24800+a95bf172 - sourcerpm: postgresql-15.19-1.module+el9.8.0+24800+a95bf172.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/os/Packages/p/postgresql-private-libs-15.19-1.module+el9.8.0+24800+a95bf172.x86_64.rpm + evr: 16.15-1.module+el9.8.0+24803+9dca000b + sourcerpm: postgresql-16.15-1.module+el9.8.0+24803+9dca000b.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/os/Packages/p/postgresql-private-libs-16.15-1.module+el9.8.0+24803+9dca000b.x86_64.rpm repoid: rhel-9-for-x86_64-appstream-rpms - size: 154163 - checksum: sha256:18d4936ef18627f03facdf1b8f54e313d04f67883d0019abee9a599b7e3e621b + size: 153488 + checksum: sha256:f36cf284eb08753b6b2576f8eed63c8c2651ef82e618fe1641a6b0ff4cd2d4ca name: postgresql-private-libs - evr: 15.19-1.module+el9.8.0+24800+a95bf172 - sourcerpm: postgresql-15.19-1.module+el9.8.0+24800+a95bf172.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/os/Packages/p/python3.12-3.12.14-1.el9_8.x86_64.rpm + evr: 16.15-1.module+el9.8.0+24803+9dca000b + sourcerpm: postgresql-16.15-1.module+el9.8.0+24803+9dca000b.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/os/Packages/p/python3.12-3.12.15-1.el9_8.x86_64.rpm repoid: rhel-9-for-x86_64-appstream-rpms - size: 32484 - checksum: sha256:10640d8db1555fb7ae4e63c62be4b8f341032fb3ee52cc7176047438ab893b1f + size: 32669 + checksum: sha256:819f912b87f620c462ab6500eea6ac8acf1c41c395af39746c30db2ab84e5629 name: python3.12 - evr: 3.12.14-1.el9_8 - sourcerpm: python3.12-3.12.14-1.el9_8.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/os/Packages/p/python3.12-libs-3.12.14-1.el9_8.x86_64.rpm + evr: 3.12.15-1.el9_8 + sourcerpm: python3.12-3.12.15-1.el9_8.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/os/Packages/p/python3.12-libs-3.12.15-1.el9_8.x86_64.rpm repoid: rhel-9-for-x86_64-appstream-rpms - size: 10211157 - checksum: sha256:0eaade47a20924ba0fe3fb0a93898e053772248793069f40c1ba80c5115fddbb + size: 10213038 + checksum: sha256:d2791de143c4172a6a2c272521071f580a78d3527781e074d8d2405de66fda2c name: python3.12-libs - evr: 3.12.14-1.el9_8 - sourcerpm: python3.12-3.12.14-1.el9_8.src.rpm + evr: 3.12.15-1.el9_8 + sourcerpm: python3.12-3.12.15-1.el9_8.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/os/Packages/p/python3.12-pip-wheel-23.2.1-5.el9.noarch.rpm repoid: rhel-9-for-x86_64-appstream-rpms size: 1527128 @@ -5272,13 +5272,13 @@ arches: name: findutils evr: 1:4.8.0-7.el9 sourcerpm: findutils-4.8.0-7.el9.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/os/Packages/g/gawk-5.1.0-6.el9.x86_64.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/os/Packages/g/gawk-5.1.0-6.el9_8.1.x86_64.rpm repoid: rhel-9-for-x86_64-baseos-rpms - size: 1045534 - checksum: sha256:99fda6725a2c668bae29fbab74d1b347e074f4e8c8ed18d656cb928fb6fc92b7 + size: 1046649 + checksum: sha256:fcc5e724c32597cf781626728f0faff2f0e5ed6455ab95af4883eefca30a074e name: gawk - evr: 5.1.0-6.el9 - sourcerpm: gawk-5.1.0-6.el9.src.rpm + evr: 5.1.0-6.el9_8.1 + sourcerpm: gawk-5.1.0-6.el9_8.1.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/os/Packages/g/gdbm-libs-1.23-1.el9.x86_64.rpm repoid: rhel-9-for-x86_64-baseos-rpms size: 60152 @@ -5286,34 +5286,34 @@ arches: name: gdbm-libs evr: 1:1.23-1.el9 sourcerpm: gdbm-1.23-1.el9.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/os/Packages/g/glibc-2.34-275.el9_8.x86_64.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/os/Packages/g/glibc-2.34-283.el9_8.x86_64.rpm repoid: rhel-9-for-x86_64-baseos-rpms - size: 2083064 - checksum: sha256:7d2d420b97c05c09ee1e9bd881cb0725fe8d89688b933f411f278cb23210c65d + size: 2083811 + checksum: sha256:ac0a85530d147cc35d56422d2f6eab14800eec63c6d5318f2bef576252426939 name: glibc - evr: 2.34-275.el9_8 - sourcerpm: glibc-2.34-275.el9_8.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/os/Packages/g/glibc-common-2.34-275.el9_8.x86_64.rpm + evr: 2.34-283.el9_8 + sourcerpm: glibc-2.34-283.el9_8.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/os/Packages/g/glibc-common-2.34-283.el9_8.x86_64.rpm repoid: rhel-9-for-x86_64-baseos-rpms - size: 321585 - checksum: sha256:f23581b888783f576bd3a55503618a48f74f468fcfd4837bbd7a2d00fe7f3530 + size: 321942 + checksum: sha256:87a1528415c5a1680faf8a816bf4a79e423f1b13c9a7d1aac387a5080ea48738 name: glibc-common - evr: 2.34-275.el9_8 - sourcerpm: glibc-2.34-275.el9_8.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/os/Packages/g/glibc-gconv-extra-2.34-275.el9_8.x86_64.rpm + evr: 2.34-283.el9_8 + sourcerpm: glibc-2.34-283.el9_8.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/os/Packages/g/glibc-gconv-extra-2.34-283.el9_8.x86_64.rpm repoid: rhel-9-for-x86_64-baseos-rpms - size: 1763577 - checksum: sha256:91899acba57caeb8768bf2ce4631d7f56c6b8e052f2bdf20003382205eb8eee8 + size: 1767979 + checksum: sha256:d421d4318c25a5817f72f4547b0be36c6be99303138a738080cdbe161577d31a name: glibc-gconv-extra - evr: 2.34-275.el9_8 - sourcerpm: glibc-2.34-275.el9_8.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/os/Packages/g/glibc-minimal-langpack-2.34-275.el9_8.x86_64.rpm + evr: 2.34-283.el9_8 + sourcerpm: glibc-2.34-283.el9_8.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/os/Packages/g/glibc-minimal-langpack-2.34-283.el9_8.x86_64.rpm repoid: rhel-9-for-x86_64-baseos-rpms - size: 30913 - checksum: sha256:6cc48d78bf2ceacfa5b58633b648c564b66505f4677adea8eb663fe90acd76f2 + size: 31065 + checksum: sha256:4031293a63d3851b03969172779a9b0f6669e00a544c80425c9eae74e08c70b0 name: glibc-minimal-langpack - evr: 2.34-275.el9_8 - sourcerpm: glibc-2.34-275.el9_8.src.rpm + evr: 2.34-283.el9_8 + sourcerpm: glibc-2.34-283.el9_8.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/os/Packages/g/gmp-6.2.0-13.el9.x86_64.rpm repoid: rhel-9-for-x86_64-baseos-rpms size: 326840 @@ -5398,13 +5398,13 @@ arches: name: libblkid evr: 2.37.4-25.el9 sourcerpm: util-linux-2.37.4-25.el9.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/os/Packages/l/libbrotli-1.0.9-9.el9_7.x86_64.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/os/Packages/l/libbrotli-1.0.9-10.el9_8.x86_64.rpm repoid: rhel-9-for-x86_64-baseos-rpms - size: 326278 - checksum: sha256:81096e6aed022489306e2fe1d1496b2b689d8f0bf6c70a94b5bddb82356eeda1 + size: 326501 + checksum: sha256:ecf2cda794cb9f68a7f67742ce0aeef4c3480795cce0e9bc8f49634072fd2e07 name: libbrotli - evr: 1.0.9-9.el9_7 - sourcerpm: brotli-1.0.9-9.el9_7.src.rpm + evr: 1.0.9-10.el9_8 + sourcerpm: brotli-1.0.9-10.el9_8.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/os/Packages/l/libcap-2.48-10.el9_8.1.x86_64.rpm repoid: rhel-9-for-x86_64-baseos-rpms size: 78928 @@ -5426,13 +5426,13 @@ arches: name: libcom_err evr: 1.46.5-8.el9 sourcerpm: e2fsprogs-1.46.5-8.el9.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/os/Packages/l/libcurl-7.76.1-40.el9_8.7.x86_64.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/os/Packages/l/libcurl-7.76.1-40.el9_8.8.x86_64.rpm repoid: rhel-9-for-x86_64-baseos-rpms - size: 296930 - checksum: sha256:044d7a379f0f0f6ed25b9d41439dedfb55b390108ae5a09d4acc6b3565c39ae1 + size: 297409 + checksum: sha256:cc12ee8e4fb07d93ad55dc59406ff65a3acbb795b3eca30b32195e51b3a12965 name: libcurl - evr: 7.76.1-40.el9_8.7 - sourcerpm: curl-7.76.1-40.el9_8.7.src.rpm + evr: 7.76.1-40.el9_8.8 + sourcerpm: curl-7.76.1-40.el9_8.8.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/os/Packages/l/libdb-5.3.28-57.el9_6.x86_64.rpm repoid: rhel-9-for-x86_64-baseos-rpms size: 755192 @@ -5713,13 +5713,13 @@ arches: name: openldap evr: 2.6.8-4.el9 sourcerpm: openldap-2.6.8-4.el9.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/os/Packages/o/openssl-3.5.8-1.el9_8.x86_64.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/os/Packages/o/openssl-3.5.8-2.el9_8.x86_64.rpm repoid: rhel-9-for-x86_64-baseos-rpms - size: 1569041 - checksum: sha256:2e0d5017032a48c23d4efa43afbe98d45179365eaec16f38e93c271b7728a67a + size: 1570074 + checksum: sha256:4a560a738babd422ff2ba6077da7abfe1b9ce5ef89c056ec5f693334dbebf928 name: openssl - evr: 1:3.5.8-1.el9_8 - sourcerpm: openssl-3.5.8-1.el9_8.src.rpm + evr: 1:3.5.8-2.el9_8 + sourcerpm: openssl-3.5.8-2.el9_8.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/os/Packages/o/openssl-fips-provider-3.0.7-11.el9_8.x86_64.rpm repoid: rhel-9-for-x86_64-baseos-rpms size: 14256 @@ -5734,13 +5734,13 @@ arches: name: openssl-fips-provider-so evr: 3.0.7-11.el9_8 sourcerpm: openssl-fips-provider-3.0.7-11.el9_8.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/os/Packages/o/openssl-libs-3.5.8-1.el9_8.x86_64.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/os/Packages/o/openssl-libs-3.5.8-2.el9_8.x86_64.rpm repoid: rhel-9-for-x86_64-baseos-rpms - size: 2430624 - checksum: sha256:3abe1f190415d91d4dc51db74cf2ad7e06b1e5ca5f63dac978fe58d8fd14e493 + size: 2431317 + checksum: sha256:6448671d67b300025edac8382995707a0763ae0843e097cbe717d1cbc29817e2 name: openssl-libs - evr: 1:3.5.8-1.el9_8 - sourcerpm: openssl-3.5.8-1.el9_8.src.rpm + evr: 1:3.5.8-2.el9_8 + sourcerpm: openssl-3.5.8-2.el9_8.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/os/Packages/p/p11-kit-0.26.4-1.el9_8.x86_64.rpm repoid: rhel-9-for-x86_64-baseos-rpms size: 625862 @@ -5769,20 +5769,20 @@ arches: name: pcre evr: 8.44-4.el9 sourcerpm: pcre-8.44-4.el9.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/os/Packages/p/pcre2-10.40-6.el9.x86_64.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/os/Packages/p/pcre2-10.40-7.el9_8.x86_64.rpm repoid: rhel-9-for-x86_64-baseos-rpms - size: 241900 - checksum: sha256:75db1e5a50e7b1794d7ba18212d95cd2684559da9e7c52eee46490302c7f24dd + size: 244785 + checksum: sha256:ef2c65a463e1ef1bfbf3d9ee66986e14cca0fbae88343f099dd5b16619276f1f name: pcre2 - evr: 10.40-6.el9 - sourcerpm: pcre2-10.40-6.el9.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/os/Packages/p/pcre2-syntax-10.40-6.el9.noarch.rpm + evr: 10.40-7.el9_8 + sourcerpm: pcre2-10.40-7.el9_8.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/os/Packages/p/pcre2-syntax-10.40-7.el9_8.noarch.rpm repoid: rhel-9-for-x86_64-baseos-rpms - size: 147926 - checksum: sha256:d386b5e9b3a4b077b2ba143882e605750855dd3354f13c55fa12ed26908cb442 + size: 150069 + checksum: sha256:e7466a43ef2bfda2b1dc07e855b0db743d48b11c0bdc1402e1d2ea6fb23d33a3 name: pcre2-syntax - evr: 10.40-6.el9 - sourcerpm: pcre2-10.40-6.el9.src.rpm + evr: 10.40-7.el9_8 + sourcerpm: pcre2-10.40-7.el9_8.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/os/Packages/p/pigz-2.8-1.el9.x86_64.rpm repoid: rhel-9-for-x86_64-baseos-rpms size: 99148 @@ -5902,13 +5902,13 @@ arches: name: tar evr: 2:1.34-13.el9_8 sourcerpm: tar-1.34-13.el9_8.src.rpm - - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/os/Packages/t/tzdata-2026c-1.el9_8.noarch.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/os/Packages/t/tzdata-2026e-1.el9_8.noarch.rpm repoid: rhel-9-for-x86_64-baseos-rpms - size: 933286 - checksum: sha256:8d6196e02853c0900c3c2e2367665beffb62cb2d0ff5f465ea2d00848a9a35dc + size: 936099 + checksum: sha256:c105d92f6cacb9bb8337b77041cedd9c9c906a72c4b5233909b9b69b78f4369f name: tzdata - evr: 2026c-1.el9_8 - sourcerpm: tzdata-2026c-1.el9_8.src.rpm + evr: 2026e-1.el9_8 + sourcerpm: tzdata-2026e-1.el9_8.src.rpm - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/os/Packages/u/util-linux-2.37.4-25.el9.x86_64.rpm repoid: rhel-9-for-x86_64-baseos-rpms size: 2382511 @@ -5974,18 +5974,18 @@ arches: checksum: sha256:2f5ece89d6a8d892816f386462d8be291ad83f1500b4c4b3655dcc7ed5192c0d name: mpdecimal evr: 2.5.1-3.el9 - - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/source/SRPMS/Packages/p/postgresql-15.19-1.module+el9.8.0+24800+a95bf172.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/source/SRPMS/Packages/p/postgresql-16.15-1.module+el9.8.0+24803+9dca000b.src.rpm repoid: rhel-9-for-x86_64-appstream-source-rpms - size: 53848854 - checksum: sha256:9e2e4c6af57fe5ab47f3f2ca77b4cb8d16a828cfa03b1eba6704203fe926eb96 + size: 48516429 + checksum: sha256:fdb3a4e84c097bb87061fbf6b3f1f11a03b4aa6adf13b0b20cc1d439b5a2c7d7 name: postgresql - evr: 15.19-1.module+el9.8.0+24800+a95bf172 - - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/source/SRPMS/Packages/p/python3.12-3.12.14-1.el9_8.src.rpm + evr: 16.15-1.module+el9.8.0+24803+9dca000b + - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/source/SRPMS/Packages/p/python3.12-3.12.15-1.el9_8.src.rpm repoid: rhel-9-for-x86_64-appstream-source-rpms - size: 20894742 - checksum: sha256:d855a90132b827b528e26c617484c250fd02dbe2cc8fdb8ef1f7d0d0a9f3f4f7 + size: 20904561 + checksum: sha256:8a3db1154cbfe206ac68b31268348da6039522d09be22520dd94207417146ebd name: python3.12 - evr: 3.12.14-1.el9_8 + evr: 3.12.15-1.el9_8 - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/source/SRPMS/Packages/p/python3.12-pip-23.2.1-5.el9.src.rpm repoid: rhel-9-for-x86_64-appstream-source-rpms size: 9384965 @@ -6034,12 +6034,12 @@ arches: checksum: sha256:82174416ee39df7795da7fab8d37ea5dbbe25f1dbc2df39f5fb3f8a168bf5120 name: binutils evr: 2.35.2-72.el9 - - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/source/SRPMS/Packages/b/brotli-1.0.9-9.el9_7.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/source/SRPMS/Packages/b/brotli-1.0.9-10.el9_8.src.rpm repoid: rhel-9-for-x86_64-baseos-source-rpms - size: 517498 - checksum: sha256:814868e0bec831c79d3e12ff76d31e06e5e62c462a1a4b6607b1f3cab7014438 + size: 519221 + checksum: sha256:c833ffbd6c6555543472ef4034a958cf4b8b8dee35b8661b2144ca6fc38908c7 name: brotli - evr: 1.0.9-9.el9_7 + evr: 1.0.9-10.el9_8 - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/source/SRPMS/Packages/b/bzip2-1.0.8-11.el9.src.rpm repoid: rhel-9-for-x86_64-baseos-source-rpms size: 828199 @@ -6076,12 +6076,12 @@ arches: checksum: sha256:775d926429179caeade6af8dc7dea15a0bea49102c2592d5b712f996a329ec38 name: crypto-policies evr: 20260224-1.gitea0f072.el9_8 - - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/source/SRPMS/Packages/c/curl-7.76.1-40.el9_8.7.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/source/SRPMS/Packages/c/curl-7.76.1-40.el9_8.8.src.rpm repoid: rhel-9-for-x86_64-baseos-source-rpms - size: 2579601 - checksum: sha256:2cb2bc752c2e87f1abf88ac67c2dd19a740df993d57e88f3920dedbc37f0019d + size: 2581541 + checksum: sha256:303a97cbdfaaac1d709ef7621b2b349dc403da6a553ecfe5e3114f76d757d554 name: curl - evr: 7.76.1-40.el9_8.7 + evr: 7.76.1-40.el9_8.8 - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/source/SRPMS/Packages/c/cyrus-sasl-2.1.27-22.el9.src.rpm repoid: rhel-9-for-x86_64-baseos-source-rpms size: 4025677 @@ -6136,12 +6136,12 @@ arches: checksum: sha256:48bd4d4dd081120bcc6ab772b930a150f30b2fc89a4a14a24220383d24a30b3f name: findutils evr: 1:4.8.0-7.el9 - - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/source/SRPMS/Packages/g/gawk-5.1.0-6.el9.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/source/SRPMS/Packages/g/gawk-5.1.0-6.el9_8.1.src.rpm repoid: rhel-9-for-x86_64-baseos-source-rpms - size: 3190934 - checksum: sha256:37571947707e835d36ceb5641b187aba13ef8f5f605a6762ce72aeea3e745b8d + size: 3197459 + checksum: sha256:8f156f11c96b1720ba2e9e287f2ddf5e0588a9c9ad17753f779ebbdf0c4e99df name: gawk - evr: 5.1.0-6.el9 + evr: 5.1.0-6.el9_8.1 - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/source/SRPMS/Packages/g/gcc-11.5.0-14.el9.src.rpm repoid: rhel-9-for-x86_64-baseos-source-rpms size: 81978017 @@ -6154,12 +6154,12 @@ arches: checksum: sha256:ad42264274c2a792220395a4dbe736a1de6100c4e14611707ec1dd447583271f name: gdbm evr: 1:1.23-1.el9 - - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/source/SRPMS/Packages/g/glibc-2.34-275.el9_8.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/source/SRPMS/Packages/g/glibc-2.34-283.el9_8.src.rpm repoid: rhel-9-for-x86_64-baseos-source-rpms - size: 20442764 - checksum: sha256:bd7a453f66e13da24d073f53070c2d209fcd88ca2aaa24ab3b2f81cae198b46c + size: 20462553 + checksum: sha256:9007b0a97151f43f812049a925cb418753f9fd643fe1283cc7eddc7e6785e2f3 name: glibc - evr: 2.34-275.el9_8 + evr: 2.34-283.el9_8 - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/source/SRPMS/Packages/g/gmp-6.2.0-13.el9.src.rpm repoid: rhel-9-for-x86_64-baseos-source-rpms size: 2503825 @@ -6190,12 +6190,12 @@ arches: checksum: sha256:c4c76ebfd66ba6d00edf672797d7f077b29d279b7866a04e05258a257a5bc306 name: json-c evr: 0.14-11.el9 - - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/source/SRPMS/Packages/k/kernel-5.14.0-687.53.1.el9_8.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/source/SRPMS/Packages/k/kernel-5.14.0-687.57.1.el9_8.src.rpm repoid: rhel-9-for-x86_64-baseos-source-rpms - size: 152530420 - checksum: sha256:28a39e99b31b7b924672b14e70b1a0608755a7898c20563f52f2c0b22c67a91a + size: 152563149 + checksum: sha256:a2660af0b87ac6a4d161ccc76c7d8a8338a44b4fbaad6da9e5529e3027f25aa2 name: kernel - evr: 5.14.0-687.53.1.el9_8 + evr: 5.14.0-687.57.1.el9_8 - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/source/SRPMS/Packages/k/keyutils-1.6.3-1.el9.src.rpm repoid: rhel-9-for-x86_64-baseos-source-rpms size: 150790 @@ -6406,12 +6406,12 @@ arches: checksum: sha256:0d21c12c55d40d1fc2f006c8ec187b5fcc799b794cfd31ed2d98434189c62800 name: openldap evr: 2.6.8-4.el9 - - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/source/SRPMS/Packages/o/openssl-3.5.8-1.el9_8.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/source/SRPMS/Packages/o/openssl-3.5.8-2.el9_8.src.rpm repoid: rhel-9-for-x86_64-baseos-source-rpms - size: 53429735 - checksum: sha256:393e8694b8161826f67f149cd7dde6b63da96f02efc050fda651d265907f2c20 + size: 53435636 + checksum: sha256:1e67b59efe228f804c0544a31ab3de0bb4a0c8250d3d5f5fc53a3c7794104795 name: openssl - evr: 1:3.5.8-1.el9_8 + evr: 1:3.5.8-2.el9_8 - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/source/SRPMS/Packages/o/openssl-fips-provider-3.0.7-11.el9_8.src.rpm repoid: rhel-9-for-x86_64-baseos-source-rpms size: 28686020 @@ -6436,12 +6436,12 @@ arches: checksum: sha256:7edbd87b866a3f6e3df1426d660b902e063193d6186027bf99f6d77626a43817 name: pcre evr: 8.44-4.el9 - - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/source/SRPMS/Packages/p/pcre2-10.40-6.el9.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/source/SRPMS/Packages/p/pcre2-10.40-7.el9_8.src.rpm repoid: rhel-9-for-x86_64-baseos-source-rpms - size: 1789790 - checksum: sha256:a570f7192be555222aa3704882b9199fb013a84ad4d7dcf40a93d8de2ecf6e0a + size: 1805958 + checksum: sha256:43c40093332890a77f7d7c573a1b540867319bbcaaec5a6dfd73101765dc6ea0 name: pcre2 - evr: 10.40-6.el9 + evr: 10.40-7.el9_8 - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/source/SRPMS/Packages/p/pigz-2.8-1.el9.src.rpm repoid: rhel-9-for-x86_64-baseos-source-rpms size: 131701 @@ -6508,12 +6508,12 @@ arches: checksum: sha256:15eeffee75eaef17e056cef4d5207825782a755db6dfd509a759e672ffd03b6c name: tar evr: 2:1.34-13.el9_8 - - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/source/SRPMS/Packages/t/tzdata-2026c-1.el9_8.src.rpm + - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/source/SRPMS/Packages/t/tzdata-2026e-1.el9_8.src.rpm repoid: rhel-9-for-x86_64-baseos-source-rpms - size: 933774 - checksum: sha256:4cc10832b19807c03a30bd9317fb37dc811d2f13c11b1ed35af8d2efa481622f + size: 943814 + checksum: sha256:016ab67407e6f67418d8f36c5944396fe82c85896b9e945441f93d3cf1b823b4 name: tzdata - evr: 2026c-1.el9_8 + evr: 2026e-1.el9_8 - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/baseos/source/SRPMS/Packages/u/util-linux-2.37.4-25.el9.src.rpm repoid: rhel-9-for-x86_64-baseos-source-rpms size: 6291867 @@ -6539,7 +6539,7 @@ arches: name: zstd evr: 1.5.5-1.el9 module_metadata: - - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/os/repodata/c57f58f8a4b1141dde68ffc9572f4b61fa851fd2c6819db8bd600704f831b512-modules.yaml.gz + - url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/os/repodata/23e894ddbf1b6da3354d9713960e5b9ec453ba7417208b4441fcc9eb979cd456-modules.yaml.gz repoid: rhel-9-for-x86_64-appstream-rpms - size: 72089 - checksum: sha256:c57f58f8a4b1141dde68ffc9572f4b61fa851fd2c6819db8bd600704f831b512 + size: 72012 + checksum: sha256:23e894ddbf1b6da3354d9713960e5b9ec453ba7417208b4441fcc9eb979cd456 diff --git a/scale/dev/kill-sensors.sh b/scale/dev/kill-sensors.sh index 37fed2d19c707..94c4279c722d1 100755 --- a/scale/dev/kill-sensors.sh +++ b/scale/dev/kill-sensors.sh @@ -3,8 +3,17 @@ set -eoux pipefail echo "Killing sensors" -mapfile -t sensor_namespaces < <(kubectl get namespaces -o custom-columns=:metadata.name | grep -E 'stackrox[0-9]+') +if [[ -n ${1:-} ]]; then + mapfile -t sensor_namespaces < <(for i in $(seq 1 $1); do echo "stackrox${i}"; done) +else + mapfile -t sensor_namespaces < <(kubectl get namespaces -o custom-columns=:metadata.name | grep -E 'stackrox[0-9]+') +fi nnamespace="${#sensor_namespaces[@]}" +for ((i = 0; i < nnamespace; i = i + 1)); do + roxie teardown secured-cluster --set securedCluster.namespace="${sensor_namespaces[i]}" +done +# The pods need a while to shut down which blocks namespace deletion, +# so deleting the namespaces separately speeds things up esp. when there are many. for ((i = 0; i < nnamespace; i = i + 1)); do kubectl delete namespace "${sensor_namespaces[i]}" done diff --git a/scale/dev/launch_central.sh b/scale/dev/launch_central.sh index 2426f50e5c088..74fe4bf2eb18f 100755 --- a/scale/dev/launch_central.sh +++ b/scale/dev/launch_central.sh @@ -4,23 +4,56 @@ DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd)" local_port=${1:-8000} -export STORAGE=pvc -export STORAGE_SIZE=100 # This is GKE specific so we may need to be careful in the future if kubectl version -o json | jq .serverVersion.gitVersion | grep "gke" > /dev/null; then - echo "Setting storage class to faster" - export STORAGE_CLASS=faster + echo "Setting storage class to SSD-based" + extra_args=(--set central.spec.central.db.persistence.persistentVolumeClaim.storageClassName=premium-rwo) fi # Launch Central -$DIR/../../deploy/k8s/central.sh -kubectl -n stackrox set env deploy/central MUTEX_WATCHDOG_TIMEOUT_SECS=0 ROX_SCALE_TEST=true -if [[ $(kubectl get nodes -o json | jq '.items | length') == 1 ]]; then - exit 0 +roxie_config=$(mktemp) +trap 'rm -f "${roxie_config}"' EXIT +cat > "${roxie_config}" < /dev/null; then >&2 echo "Running the scale workload requires a PVC" exit 1 fi -kubectl -n "${namespace}" delete deploy/admission-control -kubectl -n "${namespace}" delete daemonset collector +roxie_config=$(mktemp) +trap 'rm -f "${roxie_config}"' EXIT + +CENTRAL_ENDPOINT=${CENTRAL_ENDPOINT:-central.stackrox.svc:443} + +cat > "${roxie_config}" < "${PGDATA}/PG_VERSION" +} + +# given_prev_binaries mimics the postgres executable under /usr/lib64/pgsql. +function given_prev_binaries() { + local major="$1" + mock_executable "${prev_dir}/postgresql-${major}/bin/postgres" \ + "echo 'PostgreSQL ${major} server'" +} + +function mock_executable() { + local target_path="$1" + local script_body="$2" + + mkdir -p "$(dirname "$target_path")" + printf '#!/usr/bin/env bash\n%s\n' "$script_body" > "$target_path" + chmod +x "$target_path" +} + +# Calls rox_use_matching_binaries and gathers results. +function use_matching_binaries() { + local status=0 + + rox_use_matching_binaries "$prev_dir" || status=$? + printf 'first-path=%s\n' "${PATH%%:*}" + printf 'path=%s\n' "$PATH" + return "$status" +} + +# == rox_use_matching_binaries == +# +# The function must always return 0: under `set -e` a non-zero return takes the +# container down. + +@test "rox_use_matching_binaries - fresh install, no PG_VERSION yet" { + run use_matching_binaries + assert_success + assert_line "first-path=${image_bin}" +} + +@test "rox_use_matching_binaries - normal boot" { + given_binary_version 16 + given_data_version 16 + run use_matching_binaries + assert_success + refute_output --partial "no executable" + assert_line "first-path=${image_bin}" +} + +@test "rox_use_matching_binaries - data are newer than the image" { + # Unsupported. The image has no newer binaries. + given_binary_version 16 + given_data_version 17 + run use_matching_binaries + assert_success + refute_output --partial "no executable" + assert_line "first-path=${image_bin}" +} + +@test "rox_use_matching_binaries - data match the previous binaries" { + local data_major=15 + local matching_bin_dir="${prev_dir}/postgresql-${data_major}/bin" + given_binary_version 17 + given_data_version "$data_major" + given_prev_binaries "$data_major" + local before="$PATH" + run use_matching_binaries + assert_success + assert_output --partial \ + "Data are still in PostgreSQL ${data_major}, starting the binaries in ${matching_bin_dir}" + assert_line "path=${matching_bin_dir}:${before}" +} + +@test "rox_use_matching_binaries - data do not match the previous binaries" { + local data_major=14 + local matching_bin_dir="${prev_dir}/postgresql-${data_major}/bin" + given_binary_version 16 + given_prev_binaries 15 + given_data_version "$data_major" + run use_matching_binaries + assert_success + assert_output --partial \ + "Data are in PostgreSQL ${data_major}, but there is no executable ${matching_bin_dir}/postgres" + assert_line "first-path=${image_bin}" +} + +@test "rox_use_matching_binaries - matching postgres is not executable" { + local data_major=15 + local matching_bin_dir="${prev_dir}/postgresql-${data_major}/bin" + given_binary_version 16 + given_data_version "$data_major" + given_prev_binaries "$data_major" + chmod -x "${matching_bin_dir}/postgres" + run use_matching_binaries + assert_success + assert_output --partial "no executable" + assert_line "first-path=${image_bin}" +} + +@test "rox_use_matching_binaries - corrupted PG_VERSION" { + given_data_version "not-a-version" + run use_matching_binaries + assert_success + assert_output --partial "Cannot read a valid major version from ${PGDATA}/PG_VERSION" + assert_line "first-path=${image_bin}" +} + +@test "rox_use_matching_binaries - unparsable binary version" { + mock_executable "${image_bin}/postgres" "echo 'something else entirely'" + given_data_version 15 + run use_matching_binaries + assert_success + assert_output --partial "Cannot read a valid major version from postgres -V" + assert_line "first-path=${image_bin}" +} + +@test "rox_use_matching_binaries - postgres -V failure does not crash the container" { + mock_executable "${image_bin}/postgres" "exit 127" + given_data_version 15 + # Called without `run`, which would swallow an abort. + rox_use_matching_binaries "$prev_dir" + assert_equal "${PATH%%:*}" "$image_bin" +} diff --git a/scanner/image/db/konflux.Dockerfile b/scanner/image/db/konflux.Dockerfile index 6888180df6b18..a4b3f6bfc6890 100644 --- a/scanner/image/db/konflux.Dockerfile +++ b/scanner/image/db/konflux.Dockerfile @@ -1,4 +1,4 @@ -FROM registry.redhat.io/rhel9/postgresql-15:latest@sha256:a3a5c31afcd91ef5d96f20bcf7aa7b47b10c034f86b549b04985ebed6d14567e +FROM registry.redhat.io/rhel9/postgresql-15:latest@sha256:04dc558560697443edac184d16684147eba4d6bb7a6dd646928ad433def33c95 ARG BUILD_TAG RUN if [[ "$BUILD_TAG" == "" ]]; then >&2 echo "error: required BUILD_TAG arg is unset"; exit 6; fi @@ -36,6 +36,8 @@ RUN localedef -f UTF-8 -i en_US en_US.UTF-8 && \ usermod -u 70 postgres -d /var/lib/postgresql && \ chown -R postgres:postgres /var/lib/postgresql && \ chown -R postgres:postgres /var/run/postgresql && \ + # Update ownership of directories that are owned by the original postgres uid. + chown -R postgres /var/lib/pgsql && chown -R postgres /opt/app-root && \ dnf clean all && \ rpm --verbose -e --nodeps $(rpm -qa curl '*rpm*' '*dnf*' '*libsolv*' '*hawkey*' 'yum*') && \ rm -rf /var/cache/dnf /var/cache/yum diff --git a/scanner/image/db/scripts/docker-entrypoint.sh b/scanner/image/db/scripts/docker-entrypoint.sh index 2848ddbe4af01..705762aa70ba9 100755 --- a/scanner/image/db/scripts/docker-entrypoint.sh +++ b/scanner/image/db/scripts/docker-entrypoint.sh @@ -311,6 +311,51 @@ _pg_want_help() { return 1 } +### STACKROX MODIFIED - Always run the server binaries matching the on-disk +### data directory. If init-entrypoint.sh declined or failed to upgrade, starting +### the newer server against it would make the pod crashloop. +### +### The base image ships the previous major under +### /usr/lib64/pgsql/postgresql- (see POSTGRESQL_PREV_VERSION). +### +### This function returns 0. Parameter $1 is the root of the previous binaries. +rox_use_matching_binaries() { + local prev_dir="$1" + local major_regex='^[0-9]+$' + local data_version binary_version matching_bin_dir + + # A fresh install has no usable PG_VERSION file yet. + [ -s "$PGDATA/PG_VERSION" ] || return 0 + + data_version="$(< "$PGDATA/PG_VERSION")" || true + if ! [[ "$data_version" =~ $major_regex ]]; then + printf >&2 'Cannot read a valid major version from %s/PG_VERSION: "%s"\n' \ + "$PGDATA" "$data_version" + return 0 + fi + + binary_version="$(postgres -V 2>/dev/null | sed 's/postgres (PostgreSQL) \([0-9]*\).*/\1/')" || true + if ! [[ "$binary_version" =~ $major_regex ]]; then + printf >&2 'Cannot read a valid major version from postgres -V: "%s"\n' \ + "$binary_version" + return 0 + fi + + [ "$data_version" -lt "$binary_version" ] || return 0 + + # Binaries for the data version, which is the previous major. + matching_bin_dir="${prev_dir}/postgresql-${data_version}/bin" + if [ ! -x "$matching_bin_dir/postgres" ]; then + printf >&2 'Data are in PostgreSQL %s, but there is no executable %s/postgres; startup will fail\n' \ + "$data_version" "$matching_bin_dir" + return 0 + fi + + printf >&2 'Data are still in PostgreSQL %s, starting the binaries in %s\n' \ + "$data_version" "$matching_bin_dir" + export PATH="$matching_bin_dir:$PATH" +} + _main() { # if first arg looks like a flag, assume we want to run postgres server if [ "${1:0:1}" = '-' ]; then @@ -318,6 +363,12 @@ _main() { fi if [ "$1" = 'postgres' ] && ! _pg_want_help "$@"; then + ### STACKROX MODIFIED - Must run before anything that resolves a + ### PostgreSQL binary through PATH. The server itself is launched as + ### `flock ... "$@" &` below, which resolves `postgres` at exec time, + ### so exporting PATH here is enough. + rox_use_matching_binaries /usr/lib64/pgsql + docker_setup_env # setup data directories and permissions (when run as root) docker_create_db_directories diff --git a/scanner/image/scanner/konflux.Dockerfile b/scanner/image/scanner/konflux.Dockerfile index b21eb84546a32..cfec85c4ce01c 100644 --- a/scanner/image/scanner/konflux.Dockerfile +++ b/scanner/image/scanner/konflux.Dockerfile @@ -16,9 +16,9 @@ WORKDIR /src RUN make -C scanner NODEPS=1 CGO_ENABLED=1 image/scanner/bin/scanner copy-scripts -FROM registry.access.redhat.com/ubi9/ubi-micro:latest@sha256:7a0454cbd9bd847e8f6a63b6f0254a6efbeb6e0ed71a5d824a4f6cccbe626650 AS ubi-micro-base +FROM registry.access.redhat.com/ubi9/ubi-micro:latest@sha256:5be877a2a280ae36eb9ab80ca4efb28bbf77c57abbba38137cdc37a03b8465e6 AS ubi-micro-base -FROM registry.access.redhat.com/ubi9/ubi:latest@sha256:094ea2ecfd3225af8f93807b99daa9ff33710fc705ebdf6e8466f46ed605585c AS package_installer +FROM registry.access.redhat.com/ubi9/ubi:latest@sha256:16e9e43d99e181463513cc84a514859b1abb9460f1c4d9e208da4b4e51cd5cc3 AS package_installer COPY --from=ubi-micro-base / /out/ diff --git a/scanner/updater/ci/source-config.yaml b/scanner/updater/ci/source-config.yaml index 26c66ad004d41..e39335eaf0fd3 100644 --- a/scanner/updater/ci/source-config.yaml +++ b/scanner/updater/ci/source-config.yaml @@ -10,6 +10,14 @@ # updaters: per-updater configuration. Each updater inherits from 'default' # unless it overrides a field. New configuration sections can be added here # as the pipeline evolves. +# +# interval: minimum age of the last successful upload before a refresh is due. +# +# attempt_cadence: optional spacing between eligible one-hour scheduling windows, +# aligned to UTC epoch hours. Must be a positive whole-hour duration. +# For example, 4h permits attempts during UTC hours 00, 04, 08, 12, 16, and 20. +# If omitted, due refreshes may be attempted on every scheduler run. +# Failed attempts do not change the windows or the last successful upload time. storage: bucket: definitions.stackrox.io @@ -76,3 +84,6 @@ bundles: updaters: default: interval: 4h + oracle: + interval: 8h + attempt_cadence: 4h diff --git a/scanner/updater/version/RELEASE_VERSION b/scanner/updater/version/RELEASE_VERSION index 9461ad2816d53..bd9facc9a0966 100644 --- a/scanner/updater/version/RELEASE_VERSION +++ b/scanner/updater/version/RELEASE_VERSION @@ -8,6 +8,8 @@ # offline bundle for that release. 4.10.0 4.10.1 +4.10.10 +4.10.11 4.10.2 4.10.3 4.10.4 @@ -22,6 +24,7 @@ 4.11.3 4.11.4 4.11.5 +4.11.6 4.4.0 4.4.1 4.4.2 diff --git a/scripts/ci/add-vms/action.yml b/scripts/ci/add-vms/action.yml index 3b300baf010ec..41eca4d2c9bda 100644 --- a/scripts/ci/add-vms/action.yml +++ b/scripts/ci/add-vms/action.yml @@ -1,4 +1,4 @@ -name: "Add VMs to ACS Cluster" +name: "Ensure VMs on ACS Cluster" description: "Install OpenShift Virtualization, deploy VMs, and install roxagent on an Infra-managed cluster" inputs: @@ -6,11 +6,11 @@ inputs: description: "Infra cluster name (from `infractl list`)" required: true num-vms: - description: "Number of VMs to deploy" + description: "Desired count of {os}-1..N. Existing VMs are reused." required: false default: "1" vm-os: - description: "VM OS: rhel9 or rhel10" + description: "VM OS: rhel8, rhel9, or rhel10" required: false default: "rhel9" ssh-public-key: diff --git a/scripts/ci/add-vms/add-vms.bats b/scripts/ci/add-vms/add-vms.bats index 4b37bc3dba926..1c6ce5090598f 100644 --- a/scripts/ci/add-vms/add-vms.bats +++ b/scripts/ci/add-vms/add-vms.bats @@ -21,6 +21,7 @@ load "../../test_helpers.bats" ' _ "${BATS_TEST_DIRNAME}/add-vms.sh" assert_success + assert_output --partial "::error title=Add VMs incomplete::Skipped: rhel10-3; agent failed: rhel10-3" run cat "$summary_file" assert_output --partial "## Add VMs to Cluster" diff --git a/scripts/ci/add-vms/add-vms.sh b/scripts/ci/add-vms/add-vms.sh index 517a0143a5625..fd4f89cc880d5 100755 --- a/scripts/ci/add-vms/add-vms.sh +++ b/scripts/ci/add-vms/add-vms.sh @@ -7,7 +7,7 @@ # Options: # --cluster NAME Infra cluster name (calls infractl for kubeconfig) # --num-vms N Number of VMs (default: 1) -# --os OS VM OS: rhel9|rhel10 (default: rhel9) +# --os OS VM OS: rhel8|rhel9|rhel10 (default: rhel9) # --ssh-key PATH Path to user SSH public key to add to target VMs # --namespace NS Target namespace (default: openshift-cnv) # --vm-prefix PREFIX VM name prefix (default: same as OS) @@ -64,7 +64,7 @@ parse_args() { if ! [[ "$NUM_VMS" =~ ^[0-9]+$ ]] || (( NUM_VMS < 1 )); then die "--num-vms must be a positive integer" fi - [[ "$VM_OS" =~ ^rhel(9|10)$ ]] || die "--os must be rhel9 or rhel10" + [[ "$VM_OS" =~ ^rhel(8|9|10)$ ]] || die "--os must be rhel8, rhel9, or rhel10" } # Sets KUBECONFIG: either fetches it from infractl for the given cluster @@ -218,7 +218,11 @@ write_github_summary() { echo "" } >> "$GITHUB_STEP_SUMMARY" - echo "::notice title=VM action summary::See the job summary for VM access, key download, and native service verification." + if [[ ${#SKIPPED_VMS[@]} -gt 0 || ${#NATIVE_AGENT_FAILED_VMS[@]} -gt 0 ]]; then + echo "::error title=Add VMs incomplete::Skipped: ${SKIPPED_VMS[*]}; agent failed: ${NATIVE_AGENT_FAILED_VMS[*]}. See the job summary." + else + echo "::notice title=VM action summary::See the job summary for VM access, key download, and native service verification." + fi } cleanup() { @@ -268,6 +272,8 @@ main() { accessible_vms+=("${MANAGED_VMS[@]}" "${ADOPTED_VMS[@]}") if [[ ${#accessible_vms[@]} -eq 0 ]]; then + # Summary first so the ::error annotation still names the skipped VMs. + print_summary die "No accessible VMs — cannot install agent. All VMs failed SSH readiness." else export AUTOMATION_SSH_PRIVKEY @@ -278,6 +284,9 @@ main() { # Step 4: Summary print_summary + if [[ ${#SKIPPED_VMS[@]} -gt 0 || ${#NATIVE_AGENT_FAILED_VMS[@]} -gt 0 ]]; then + die "Some VMs were skipped or the native agent failed to start" + fi } if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then diff --git a/scripts/ci/bats/scanner-updater-config.bats b/scripts/ci/bats/scanner-updater-config.bats new file mode 100644 index 0000000000000..d9680a53c5a86 --- /dev/null +++ b/scripts/ci/bats/scanner-updater-config.bats @@ -0,0 +1,67 @@ +#!/usr/bin/env bats +# shellcheck disable=SC1091 + +load "../../test_helpers.bats" + +function setup() { + repo_root="$(cd "${BATS_TEST_DIRNAME}/../../.." && pwd)" + config_script="${repo_root}/.github/workflows/scripts/scanner-updater-config.sh" + export SOURCE_CONFIG="${BATS_TEST_TMPDIR}/source-config.yaml" +} + +@test "Oracle uses eight-hour freshness and four-hour attempt cadence" { + run env SOURCE_CONFIG="${repo_root}/scanner/updater/ci/source-config.yaml" \ + "$config_script" interval-secs oracle + assert_success + assert_output "28800" + + run env SOURCE_CONFIG="${repo_root}/scanner/updater/ci/source-config.yaml" \ + "$config_script" attempt-cadence-secs oracle + assert_success + assert_output "14400" +} + +@test "sources without a cadence retain default freshness and unrestricted attempts" { + run env SOURCE_CONFIG="${repo_root}/scanner/updater/ci/source-config.yaml" \ + "$config_script" interval-secs debian + assert_success + assert_output "14400" + + run env SOURCE_CONFIG="${repo_root}/scanner/updater/ci/source-config.yaml" \ + "$config_script" attempt-cadence-secs debian + assert_success + assert_output "0" +} + +@test "attempt cadence inherits the default and supports per-source overrides" { + cat > "$SOURCE_CONFIG" <<'YAML' +updaters: + default: + attempt_cadence: 6h + oracle: + attempt_cadence: 240m +YAML + run "$config_script" attempt-cadence-secs debian + assert_success + assert_output "21600" + + run "$config_script" attempt-cadence-secs oracle + assert_success + assert_output "14400" +} + +@test "attempt cadence rejects zero and fractional-hour durations" { + for cadence in 0h 30m 1h30m; do + printf 'updaters:\n oracle:\n attempt_cadence: %s\n' "$cadence" > "$SOURCE_CONFIG" + run "$config_script" attempt-cadence-secs oracle + assert_failure + assert_output --partial "attempt_cadence must be a positive whole-hour duration" + done +} + +@test "attempt cadence rejects malformed durations" { + printf 'updaters:\n oracle:\n attempt_cadence: invalid\n' > "$SOURCE_CONFIG" + run "$config_script" attempt-cadence-secs oracle + assert_failure + assert_output --partial "cannot parse duration" +} diff --git a/scripts/ci/bats/scanner-updater-matrix.bats b/scripts/ci/bats/scanner-updater-matrix.bats new file mode 100644 index 0000000000000..62147b86af1e8 --- /dev/null +++ b/scripts/ci/bats/scanner-updater-matrix.bats @@ -0,0 +1,124 @@ +#!/usr/bin/env bats +# shellcheck disable=SC1091 + +load "../../test_helpers.bats" + +function setup() { + bats_require_minimum_version 1.5.0 + repo_root="$(cd "${BATS_TEST_DIRNAME}/../../.." && pwd)" + fixture="${BATS_TEST_TMPDIR}/repo" + scripts="${fixture}/.github/workflows/scripts" + mkdir -p "$scripts" + cp "${repo_root}/.github/workflows/scripts/scanner-updater-matrix.sh" "$scripts/" + cp "${repo_root}/.github/workflows/scripts/scanner-updater-config.sh" "$scripts/" + export SOURCE_CONFIG="${fixture}/source-config.yaml" + export FRESHNESS_CALLS="${BATS_TEST_TMPDIR}/freshness-calls" + export SOURCE_FRESH=false + export FRESHNESS_ERROR=false + export SCHEDULE_TIME="2026-10-14T08:27:00Z" + : > "$FRESHNESS_CALLS" + + cat > "$SOURCE_CONFIG" <<'YAML' +storage: + bucket: test-bucket + prefix: test-prefix +bundles: + dev: [oracle, debian] + v4: [oracle, debian] +updaters: + default: + interval: 4h + oracle: + interval: 8h + attempt_cadence: 4h +YAML + + cat > "${scripts}/scanner-get-released-tags.sh" <<'SH' +#!/usr/bin/env bash +echo '[{"version":"dev","ref":"master"},{"version":"v4","ref":"release"}]' +SH + cat > "${scripts}/scanner-updater-check-freshness.sh" <<'SH' +#!/usr/bin/env bash +printf '%s %s\n' "$GCS_OBJECT" "$*" >> "$FRESHNESS_CALLS" +if [[ "$FRESHNESS_ERROR" == true ]]; then + exit 1 +fi +echo "$SOURCE_FRESH" +SH + chmod +x "${scripts}/scanner-get-released-tags.sh" \ + "${scripts}/scanner-updater-check-freshness.sh" + cd "$fixture" || return 1 +} + +@test "due sources are scheduled in eligible hours across streams" { + run "${scripts}/scanner-updater-matrix.sh" + assert_success + run jq -e '.has_due == "true" and .has_errors == "false" and + ([.matrix.include[] | select(.source == "oracle")] | length == 2)' <<< "$output" + assert_success + run grep -c 'oracle.json.zst --max-age 28800' "$FRESHNESS_CALLS" + assert_success + assert_output "2" + run grep -c 'debian.json.zst --max-age 14400' "$FRESHNESS_CALLS" + assert_success + assert_output "2" +} + +@test "ineligible hours skip Oracle before freshness checks but retain other sources" { + run env SCHEDULE_TIME="2026-10-14T09:27:00Z" \ + "${scripts}/scanner-updater-matrix.sh" + assert_success + run jq -e '.matrix.include | length == 2 and all(.[]; .source == "debian")' <<< "$output" + assert_success + run grep oracle "$FRESHNESS_CALLS" + assert_failure +} + +@test "fresh sources are not scheduled even in eligible hours" { + run env SOURCE_FRESH=true "${scripts}/scanner-updater-matrix.sh" + assert_success + run jq -e '.has_due == "false" and .has_errors == "false" and + (.matrix.include | length == 0)' <<< "$output" + assert_success +} + +@test "eligible windows include the whole hour and cross midnight" { + for timestamp in 2026-10-14T23:59:59Z 2026-10-15T00:00:00Z 2026-10-15T00:59:59Z 2026-10-15T01:00:00Z; do + expected=0 + [[ "$timestamp" == *T00:* ]] && expected=2 + run env SCHEDULE_TIME="$timestamp" "${scripts}/scanner-updater-matrix.sh" + assert_success + run jq -e --argjson expected "$expected" \ + '[.matrix.include[] | select(.source == "oracle")] | length == $expected' <<< "$output" + assert_success + done +} + +@test "changing cadence changes eligible hours using UTC epoch alignment" { + yq eval -i '.updaters.oracle.attempt_cadence = "5h"' "$SOURCE_CONFIG" + run env SCHEDULE_TIME="1970-01-02T00:27:00Z" \ + "${scripts}/scanner-updater-matrix.sh" + assert_success + run jq -e '[.matrix.include[] | select(.source == "oracle")] | length == 0' <<< "$output" + assert_success + + run env SCHEDULE_TIME="1970-01-02T01:27:00Z" \ + "${scripts}/scanner-updater-matrix.sh" + assert_success + run jq -e '[.matrix.include[] | select(.source == "oracle")] | length == 2' <<< "$output" + assert_success +} + +@test "freshness lookup failures remain visible scheduling errors" { + run --separate-stderr env FRESHNESS_ERROR=true "${scripts}/scanner-updater-matrix.sh" + assert_success + [[ "$stderr" == *"::error::gs://test-bucket/test-prefix/dev/sources/oracle.json.zst: freshness check failed"* ]] + run jq -e '.has_errors == "true" and (.errors | length == 4) and + (.matrix.include | length == 0)' <<< "$output" + assert_success +} + +@test "invalid scheduling time fails rather than using the current time" { + run env SCHEDULE_TIME="invalid" "${scripts}/scanner-updater-matrix.sh" + assert_failure +} diff --git a/scripts/ci/collect-collector-metrics.sh b/scripts/ci/collect-collector-metrics.sh index 777e922197db9..6d9ef92fd4af7 100755 --- a/scripts/ci/collect-collector-metrics.sh +++ b/scripts/ci/collect-collector-metrics.sh @@ -53,6 +53,7 @@ main() { exit 0 fi + exit_code=0 for pod in ${pods}; do remote="${pod}:${pod_port}" metrics_file="${pod}.txt" @@ -73,11 +74,17 @@ main() { done echo echo "set up port-forwarding from $remote to $local" - curl --silent --fail -k "${local}/${metrics_path}" > "${metrics_dir}/${metrics_file}" - echo "finished download ${metrics_file}" + if curl --silent --fail -k "${local}/${metrics_path}" > "${metrics_dir}/${metrics_file}"; then + echo "finished download ${metrics_file}" + else + echo >&2 "failed to download metrics from $pod" + exit_code=1 + fi kill ${PID} echo "finished tear down of port-forwarding from $remote to $local" done + + return "${exit_code}" } main "$@" diff --git a/scripts/ci/collect-service-logs.sh b/scripts/ci/collect-service-logs.sh index 82f0753973a3c..6564f669f4aa9 100755 --- a/scripts/ci/collect-service-logs.sh +++ b/scripts/ci/collect-service-logs.sh @@ -69,7 +69,8 @@ main() { set +e for object in daemonsets deployments services pods secrets serviceaccounts validatingwebhookconfigurations \ - catalogsources subscriptions clusterserviceversions central securedclusters nodes persistentvolumeclaims; do + catalogsources subscriptions clusterserviceversions central securedclusters nodes persistentvolumeclaims \ + storageclasses; do # A feel good command before pulling logs info ">>> Collecting ${object} from namespace ${namespace} <<<" out="$(mktemp)" diff --git a/scripts/ci/collect-vm-guest-logs.sh b/scripts/ci/collect-vm-guest-logs.sh new file mode 100755 index 0000000000000..ed9fae05bd5fe --- /dev/null +++ b/scripts/ci/collect-vm-guest-logs.sh @@ -0,0 +1,287 @@ +#!/usr/bin/env bash + +set -euo pipefail + +# collect-vm-guest-logs.sh copies roxagent journals from VM-scanning e2e guests +# via virtctl ssh. Best-effort: always exits 0. +# +# Usage: +# collect-vm-guest-logs.sh +# +# Environment: +# VM_SCAN_NAMESPACE_PREFIX default: vm-scan-e2e +# VM_GUEST_USER default: cloud-user +# VM_SCAN_E2E_DIR host files shared with the test run (default: /tmp/vm-scan-e2e) +# VM_SSH_PRIVATE_KEY_PATH PEM identity for virtctl (default: $VM_SCAN_E2E_DIR/ssh-identity) +# VIRTCTL_PATH optional virtctl override +# VM_GUEST_JOURNAL_TIMEOUT per-VM virtctl ssh ceiling (default: 90s) +# VM_GUEST_COLLECT_DEADLINE total collection budget (default: 540s) + +SCRIPTS_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")"/../.. && pwd)" +# shellcheck source=../../scripts/ci/lib.sh +source "$SCRIPTS_ROOT/scripts/ci/lib.sh" + +usage() { + echo "./scripts/ci/collect-vm-guest-logs.sh " +} + +vm_scan_e2e_dir() { + printf '%s\n' "${VM_SCAN_E2E_DIR:-/tmp/vm-scan-e2e}" +} + +resolve_virtctl() { + if [[ -n "${VIRTCTL_PATH:-}" ]]; then + if [[ -x "$VIRTCTL_PATH" ]]; then + printf '%s\n' "$VIRTCTL_PATH" + return 0 + fi + info "VIRTCTL_PATH is not executable: ${VIRTCTL_PATH}" >&2 + return 1 + fi + local persisted + persisted="$(vm_scan_e2e_dir)/virtctl" + if [[ -x "$persisted" ]]; then + printf '%s\n' "$persisted" + return 0 + fi + local pointed + pointed="$(vm_scan_e2e_dir)/virtctl-path" + if [[ -f "$pointed" ]]; then + local src + src="$(<"$pointed")" + if [[ -n "$src" && -x "$src" ]]; then + printf '%s\n' "$src" + return 0 + fi + fi + command -v virtctl +} + +resolve_ssh_identity() { + if [[ -n "${VM_SSH_PRIVATE_KEY_PATH:-}" && -f "${VM_SSH_PRIVATE_KEY_PATH}" ]]; then + printf '%s\n' "${VM_SSH_PRIVATE_KEY_PATH}" + return 0 + fi + local persisted + persisted="$(vm_scan_e2e_dir)/ssh-identity" + if [[ -f "$persisted" ]]; then + printf '%s\n' "$persisted" + return 0 + fi + return 1 +} + +# list_vm_scan_vmis prints "namespace name" lines for VMIs in vm-scan-e2e namespaces. +# Returns 1 when kubectl cannot list VMIs (distinct from zero matches). +list_vm_scan_vmis() { + local prefix="${VM_SCAN_NAMESPACE_PREFIX:-vm-scan-e2e}" + local out err ns name + out="$(mktemp)" + err="$(mktemp)" + if ! kubectl --request-timeout=30s get vmi -A \ + -o jsonpath='{range .items[*]}{.metadata.namespace}{" "}{.metadata.name}{"\n"}{end}' \ + > "$out" 2>"$err"; then + info "kubectl get vmi failed: $(<"$err")" >&2 + rm -f "$out" "$err" + return 1 + fi + rm -f "$err" + while read -r ns name; do + [[ -z "$ns" || -z "$name" ]] && continue + if [[ "$ns" == "$prefix" || "$ns" == "${prefix}-"* ]]; then + printf '%s %s\n' "$ns" "$name" + fi + done < "$out" + rm -f "$out" + return 0 +} + +run_with_timeout() { + local seconds="$1" + shift + if command -v timeout >/dev/null; then + timeout -k 5s "${seconds}s" "$@" + else + "$@" + fi +} + +guest_ssh() { + local virtctl_bin="$1" + local identity="$2" + local guest_user="$3" + local ns="$4" + local vmi="$5" + local ssh_timeout="$6" + local remote_cmd="$7" + + # virtctl ssh reads stdin (OpenSSH without -n) and would consume the VMI + # list from the caller's while-read loop. + run_with_timeout "$ssh_timeout" \ + "${virtctl_bin}" ssh \ + --namespace "$ns" \ + --identity-file "$identity" \ + --local-ssh-opts="-n" \ + --local-ssh-opts="-o StrictHostKeyChecking=no" \ + --local-ssh-opts="-o IdentitiesOnly=yes" \ + --local-ssh-opts="-o UserKnownHostsFile=/dev/null" \ + --local-ssh-opts="-o BatchMode=yes" \ + --local-ssh-opts="-o ConnectTimeout=30" \ + --username "$guest_user" \ + "vmi/${vmi}" \ + --command "$remote_cmd" \ + < /dev/null +} + +# seconds_left is the timeout for the next virtctl ssh. Collection stops +# on its own before the 10 minute post-test kill, which fails the job. +seconds_left() { + local cap="${VM_GUEST_JOURNAL_TIMEOUT:-90}" + local left=$((collect_deadline_at - SECONDS)) + if ((left < 1)); then + return 1 + fi + if ((left < cap)); then + printf '%s\n' "$left" + return 0 + fi + printf '%s\n' "$cap" +} + +# collect_roxagent_container_journal writes container stdout that RHEL 8 +# Podman stores in its own log, not the journal. Quadlet names that +# container systemd-roxagent. +collect_roxagent_container_journal() { + local virtctl_bin="$1" + local identity="$2" + local guest_user="$3" + local ns="$4" + local vmi="$5" + local out_file="$6" + local ssh_timeout="$7" + local stderr_file + + stderr_file="$(mktemp)" + if ! guest_ssh "$virtctl_bin" "$identity" "$guest_user" "$ns" "$vmi" "$ssh_timeout" \ + "sudo podman logs --timestamps systemd-roxagent || true" \ + > "$out_file" 2>"$stderr_file"; then + { + echo "roxagent container journal collection failed for ${ns}/${vmi}" + echo "virtctl: ${virtctl_bin}" + echo "guest_user: ${guest_user}" + echo "--- stderr ---" + cat "$stderr_file" + } >> "$out_file" + elif [[ -s "$stderr_file" ]]; then + { + echo "--- stderr ---" + cat "$stderr_file" + } >> "$out_file" + fi + rm -f "$stderr_file" +} + +# collect_roxagent_journal is enough on RHEL 9 and 10, where Podman files +# container stdout on roxagent.service. On RHEL 8 that stdout is missing +# here, so the container journal is collected separately. +collect_roxagent_journal() { + local virtctl_bin="$1" + local identity="$2" + local guest_user="$3" + local ns="$4" + local vmi="$5" + local out_file="$6" + local container_file="$7" + local ssh_timeout + + ssh_timeout="$(seconds_left)" || return 1 + + local stderr_file + stderr_file="$(mktemp)" + + if ! guest_ssh "$virtctl_bin" "$identity" "$guest_user" "$ns" "$vmi" "$ssh_timeout" \ + "sudo journalctl -u roxagent.service -u roxagent-serve.service -b --no-pager -o short-iso" \ + > "$out_file" 2>"$stderr_file"; then + { + echo "roxagent journal collection failed for ${ns}/${vmi}" + echo "virtctl: ${virtctl_bin}" + echo "guest_user: ${guest_user}" + echo "--- stderr ---" + cat "$stderr_file" + } >> "$out_file" + fi + rm -f "$stderr_file" + ssh_timeout="$(seconds_left)" || return 1 + collect_roxagent_container_journal "$virtctl_bin" "$identity" "$guest_user" "$ns" "$vmi" "$container_file" "$ssh_timeout" || true +} + +collect_journals() { + local output_dir="$1" + local guest_user="${VM_GUEST_USER:-cloud-user}" + local virtctl_bin="" identity="" + virtctl_bin="$(resolve_virtctl)" || virtctl_bin="" + identity="$(resolve_ssh_identity)" || identity="" + + if [[ -n "$virtctl_bin" && -n "$identity" ]]; then + info ">>> Collecting roxagent journals into ${output_dir} <<<" + collect_deadline_at=$((SECONDS + ${VM_GUEST_COLLECT_DEADLINE:-540})) + local vmi_count=0 ns vmi out_file container_file vmi_list list_err + vmi_list="$(mktemp)" + list_err="$(mktemp)" + if ! list_vm_scan_vmis > "$vmi_list" 2>"$list_err"; then + info "Failed to list VMIs; writing collection-failed.txt" + { + echo "kubectl get vmi failed (not an empty match)" + cat "$list_err" + } > "${output_dir}/collection-failed.txt" + rm -f "$vmi_list" "$list_err" + return 0 + fi + rm -f "$list_err" + while read -r ns vmi; do + [[ -z "$ns" || -z "$vmi" ]] && continue + vmi_count=$((vmi_count + 1)) + out_file="${output_dir}/${ns}_${vmi}_roxagent.journal.log" + container_file="${output_dir}/${ns}_${vmi}_roxagent.container.journal.log" + info "Collecting roxagent journals for ${ns}/${vmi} -> ${out_file} ${container_file}" + if ! collect_roxagent_journal "$virtctl_bin" "$identity" "$guest_user" "$ns" "$vmi" "$out_file" "$container_file"; then + echo "stopped at ${ns}/${vmi}: collect deadline reached" \ + > "${output_dir}/collection-partial.txt" + break + fi + done < "$vmi_list" + rm -f "$vmi_list" + + if [[ "$vmi_count" -eq 0 ]]; then + info "No VMIs found in VM scan namespaces" + echo "no VMIs matched prefix ${VM_SCAN_NAMESPACE_PREFIX:-vm-scan-e2e}" \ + > "${output_dir}/collection-skipped.txt" + fi + return 0 + fi + + if [[ -z "$virtctl_bin" ]]; then + info "virtctl not found; skipping VM guest journal collection" + echo "virtctl not found" > "${output_dir}/collection-skipped.txt" + else + info "VM SSH identity not found; skipping VM guest journal collection" + echo "ssh identity not found at $(vm_scan_e2e_dir)/ssh-identity" \ + > "${output_dir}/collection-skipped.txt" + fi +} + +main() { + cd "$SCRIPTS_ROOT" + + if [[ $# -lt 1 ]]; then + usage + exit 1 + fi + + local output_dir="$1" + mkdir -p "$output_dir" + collect_journals "$output_dir" || true + exit 0 +} + +main "$@" diff --git a/scripts/ci/delete-vm-ns.sh b/scripts/ci/delete-vm-ns.sh new file mode 100755 index 0000000000000..a733d1b5e012e --- /dev/null +++ b/scripts/ci/delete-vm-ns.sh @@ -0,0 +1,61 @@ +#!/usr/bin/env bash + +set -euo pipefail + +SCRIPTS_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")"/../.. && pwd)" +# shellcheck source=../../scripts/ci/lib.sh +source "$SCRIPTS_ROOT/scripts/ci/lib.sh" + +usage() { + cat <>> Cleaning up VM scan e2e namespaces <<<" + ns_list="$(mktemp)" + if ! list_vm_scan_namespaces > "$ns_list"; then + info "Skipping namespace delete: kubectl get ns failed" + rm -f "$ns_list" + return 0 + fi + while IFS= read -r ns; do + [[ -z "$ns" ]] && continue + info "Deleting namespace ${ns}" + # 10s leaves room for the namespace list inside the 60s cleanup budget. + kubectl delete namespace "$ns" --wait=false --request-timeout=10s &1 || \ + info "Namespace delete for ${ns} failed or already removed" + done < "$ns_list" + rm -f "$ns_list" +} + +main() { + case "${1:-}" in + -h|--help) + usage + exit 0 + ;; + "") + ;; + *) + usage >&2 + exit 1 + ;; + esac + + cd "$SCRIPTS_ROOT" + delete_vm_scan_namespaces + exit 0 +} + +main "$@" diff --git a/scripts/ci/get-slack-user-id.sh b/scripts/ci/get-slack-user-id.sh index b8287adce5be9..4f246c5179d7b 100755 --- a/scripts/ci/get-slack-user-id.sh +++ b/scripts/ci/get-slack-user-id.sh @@ -57,6 +57,7 @@ case "$github_login" in 'parametalol') slack_user='U02MJ72K1B5' ;; 'pedrottimark') slack_user='U01RN8V8DEH' ;; 'porridge') slack_user='U020XCUG2LA' ;; +'rhmdnd') slack_user='U01C24L97D1' ;; 'rhybrillou') slack_user='U02GPRG4NHF' ;; 'robbycochran') slack_user='U03NAEPKDE1' ;; 'rukletsov') slack_user='U01G6P17RTK' ;; diff --git a/scripts/ci/jobs/aks_qa_e2e_tests.py b/scripts/ci/jobs/aks_qa_e2e_tests.py index cd5cd84e514c9..fb4e65c757c02 100755 --- a/scripts/ci/jobs/aks_qa_e2e_tests.py +++ b/scripts/ci/jobs/aks_qa_e2e_tests.py @@ -10,6 +10,7 @@ # set required test parameters os.environ["ORCHESTRATOR_FLAVOR"] = "k8s" os.environ["KUBERNETES_PROVIDER"] = "aks" +os.environ["USE_ROXIE_DEPLOY"] = "true" os.environ["ROX_RISK_REPROCESSING_INTERVAL"] = "15s" os.environ["ROX_SENSOR_CONNECTION_RETRY_MAX_INTERVAL"] = "30s" diff --git a/scripts/ci/jobs/eks_qa_e2e_tests.py b/scripts/ci/jobs/eks_qa_e2e_tests.py index b52672293ebae..4e3dc19bd1ea4 100755 --- a/scripts/ci/jobs/eks_qa_e2e_tests.py +++ b/scripts/ci/jobs/eks_qa_e2e_tests.py @@ -10,6 +10,7 @@ # set required test parameters os.environ["ORCHESTRATOR_FLAVOR"] = "k8s" os.environ["KUBERNETES_PROVIDER"] = "eks" +os.environ["USE_ROXIE_DEPLOY"] = "true" os.environ["ROX_RISK_REPROCESSING_INTERVAL"] = "15s" os.environ["ROX_SENSOR_CONNECTION_RETRY_MAX_INTERVAL"] = "30s" diff --git a/scripts/ci/jobs/gke_external_pg_17_qa_e2e_tests.py b/scripts/ci/jobs/gke_external_pg_17_qa_e2e_tests.py index 2c652d5e83a9a..bed50919fccbe 100755 --- a/scripts/ci/jobs/gke_external_pg_17_qa_e2e_tests.py +++ b/scripts/ci/jobs/gke_external_pg_17_qa_e2e_tests.py @@ -13,8 +13,10 @@ # set test parameters os.environ["ORCHESTRATOR_FLAVOR"] = "k8s" os.environ["KUBERNETES_PROVIDER"] = "gke" +os.environ["SCANNER_V4_DB_STORAGE_CLASS"] = "stackrox-gke-ssd" os.environ["POSTGRES_VERSION"] = "17" os.environ["BYODB_TEST"] = "true" +os.environ["USE_ROXIE_DEPLOY"] = "true" os.environ["ROX_RISK_REPROCESSING_INTERVAL"] = "15s" os.environ["ROX_SENSOR_CONNECTION_RETRY_MAX_INTERVAL"] = "30s" diff --git a/scripts/ci/jobs/gke_qa_e2e_tests.py b/scripts/ci/jobs/gke_qa_e2e_tests.py index cf8f78280e1cd..65a1dd9f2ac0b 100755 --- a/scripts/ci/jobs/gke_qa_e2e_tests.py +++ b/scripts/ci/jobs/gke_qa_e2e_tests.py @@ -12,6 +12,7 @@ os.environ["KUBERNETES_PROVIDER"] = "gke" os.environ["GCP_IMAGE_TYPE"] = "cos_containerd" os.environ["OUTPUT_FORMAT"] = "helm" +os.environ["USE_ROXIE_DEPLOY"] = "true" os.environ["SCANNER_V4_DB_STORAGE_CLASS"] = "stackrox-gke-ssd" diff --git a/scripts/ci/jobs/gke_ui_e2e_tests.py b/scripts/ci/jobs/gke_ui_e2e_tests.py index a09dc8276720c..99cba4e19242b 100755 --- a/scripts/ci/jobs/gke_ui_e2e_tests.py +++ b/scripts/ci/jobs/gke_ui_e2e_tests.py @@ -16,7 +16,6 @@ # Override test env defaults here: # (for defaults see: tests/e2e/lib.sh export_test_environment()) -os.environ["OUTPUT_FORMAT"] = "helm" os.environ["SCANNER_V4_DB_STORAGE_CLASS"] = "stackrox-gke-ssd" os.environ["SENSOR_SCANNER_SUPPORT"] = "true" # Optimize Scanner V4 startup time by loading only RHEL vulnerability bundles diff --git a/scripts/ci/jobs/ocp-perf-scale-tests-post.sh b/scripts/ci/jobs/ocp-perf-scale-tests-post.sh index ca83582ff7713..cb0d75324c3fe 100755 --- a/scripts/ci/jobs/ocp-perf-scale-tests-post.sh +++ b/scripts/ci/jobs/ocp-perf-scale-tests-post.sh @@ -89,12 +89,24 @@ else info "Warning: Failed to get central diagnostics" fi +# Collect collector metrics +COLLECTOR_METRICS_OUTPUT="collector-metrics" +info "Collecting collector metrics to ${COLLECTOR_METRICS_OUTPUT}" +if "${ROOT}/scripts/ci/collect-collector-metrics.sh" stackrox "${COLLECTOR_METRICS_OUTPUT}"; then + info "Collected collector metrics to ${COLLECTOR_METRICS_OUTPUT}" +else + info "Warning: Failed to collect collector metrics" +fi + # Store artifacts to OpenShift CI artifact directory if [[ -n "${ARTIFACT_DIR:-}" ]]; then info "Copying diagnostics to ${ARTIFACT_DIR}" if [[ -d "${DIAGNOSTIC_OUTPUT}" ]]; then cp -r "${DIAGNOSTIC_OUTPUT}" "${ARTIFACT_DIR}/" || info "Warning: Failed to copy ${DIAGNOSTIC_OUTPUT}" fi + if [[ -d "${COLLECTOR_METRICS_OUTPUT}" ]]; then + cp -r "${COLLECTOR_METRICS_OUTPUT}" "${ARTIFACT_DIR}/" || info "Warning: Failed to copy ${COLLECTOR_METRICS_OUTPUT}" + fi else info "Warning: ARTIFACT_DIR not set, diagnostics not copied to artifacts" fi diff --git a/scripts/ci/jobs/ocp_qa_e2e_tests.py b/scripts/ci/jobs/ocp_qa_e2e_tests.py index 27d29566d7c84..4e7d7049e18b0 100755 --- a/scripts/ci/jobs/ocp_qa_e2e_tests.py +++ b/scripts/ci/jobs/ocp_qa_e2e_tests.py @@ -12,6 +12,7 @@ os.environ["DEPLOY_STACKROX_VIA_OPERATOR"] = "true" os.environ["ORCHESTRATOR_FLAVOR"] = "openshift" os.environ["KUBERNETES_PROVIDER"] = "ocp" +os.environ["USE_ROXIE_DEPLOY"] = "true" # Workload identities are only set up for `openshift-4` infra clusters. if 'openshift-4' in os.environ.get('CLUSTER_FLAVOR_VARIANT', ''): os.environ["SETUP_WORKLOAD_IDENTITIES"] = "true" diff --git a/scripts/ci/jobs/ocp_ui_e2e_tests.py b/scripts/ci/jobs/ocp_ui_e2e_tests.py index f3e4dfe4eeb8b..b5f28a700bd22 100755 --- a/scripts/ci/jobs/ocp_ui_e2e_tests.py +++ b/scripts/ci/jobs/ocp_ui_e2e_tests.py @@ -11,7 +11,6 @@ from post_tests import PostClusterTest, FinalPost # set required test parameters -os.environ["DEPLOY_STACKROX_VIA_OPERATOR"] = "true" os.environ["INSTALL_COMPLIANCE_OPERATOR"] = "true" os.environ["ORCHESTRATOR_FLAVOR"] = "openshift" os.environ["KUBERNETES_PROVIDER"] = "ocp" diff --git a/scripts/ci/jobs/ocp_vm_scanning_e2e_tests.py b/scripts/ci/jobs/ocp_vm_scanning_e2e_tests.py index 695d9d77640fc..a77f458cb16b8 100755 --- a/scripts/ci/jobs/ocp_vm_scanning_e2e_tests.py +++ b/scripts/ci/jobs/ocp_vm_scanning_e2e_tests.py @@ -23,6 +23,9 @@ os.environ["SENSOR_HELM_MANAGED"] = "true" os.environ["INSTALL_CNV_OPERATOR"] = "true" os.environ["ROX_VIRTUAL_MACHINES"] = "true" +# Leave guests up so post-test can collect roxagent journals, then +# delete-vm-ns.sh deletes the test namespaces. +os.environ["VM_SCAN_SKIP_CLEANUP"] = "true" # Selectively enable vulnerability bundles to prevent timeouts of matcher not being ready in 40 minutes. # The rhel-vex bundle alone has ~3M records and can take >30 min to import. # Drops alpine, aws, debian, oracle, osv, photon, suse, ubuntu (unused for RHEL guests). @@ -36,10 +39,10 @@ class VMScanningPostTest(PostClusterTest): - """Extends standard post-test with CNV namespace logs.""" + """Extends standard post-test with CNV namespace logs and guest roxagent journals.""" def __init__(self, **kwargs): - super().__init__(**kwargs) + super().__init__(collect_vm_guest_logs=True, **kwargs) self.k8s_namespaces.extend([ "openshift-cnv", ]) diff --git a/scripts/ci/lib.sh b/scripts/ci/lib.sh index 5b6c0fd779515..c87e420a12158 100755 --- a/scripts/ci/lib.sh +++ b/scripts/ci/lib.sh @@ -682,7 +682,7 @@ _image_prefetcher_prebuilt_start() { image_prefetcher_start_set qa-e2e _set_quay_pull_policy ;; - *nongroovy-e2e-tests) + *nongroovy-e2e-tests*) image_prefetcher_start_set qa-nongroovy-e2e _set_quay_pull_policy ;; @@ -850,7 +850,7 @@ _image_prefetcher_prebuilt_await() { *qa-e2e-tests*) image_prefetcher_await_set qa-e2e ;; - *nongroovy-e2e-tests) + *nongroovy-e2e-tests*) image_prefetcher_await_set qa-nongroovy-e2e ;; *compatibility-tests) @@ -940,28 +940,15 @@ EOM rm -f "${query}" fi info "Now retrieving prefetcher metrics..." - local attempt=0 - local service="service/${name}-metrics" - while [[ -z $(kubectl -n "${ns}" get "${service}" -o jsonpath="{.status.loadBalancer.ingress}" 2>/dev/null) ]]; do - if [ "$attempt" -lt "60" ]; then - info "Waiting for ${service} to obtain endpoint ..." - ((attempt++)) - sleep 10 - else - info "Something is wrong with the ${service} service. See the following 'describe' output." - kubectl -n "${ns}" describe "${service}" || true - die "Timeout waiting for ${service} to obtain endpoint!" - fi - done - local endpoint - endpoint="$(kubectl -n "${ns}" get "${service}" -o json | service_get_endpoint)" + make image-prefetcher-deploy-bin + local image_prefetcher_deploy_bin + image_prefetcher_deploy_bin="$(make print-image-prefetcher-deploy-bin)" local fetcher_metrics fetcher_metrics="$(mktemp --suffix=.csv)" local fetcher_metrics_json fetcher_metrics_json="$(mktemp --suffix=.json)" - local metrics_url="http://${endpoint}:8080/metrics" - if ! curl --silent --show-error --fail --retry 3 --retry-connrefused "${metrics_url}" > "${fetcher_metrics_json}"; then - die "Failed to fetch prefetcher metrics from ${metrics_url}" + if ! "${image_prefetcher_deploy_bin}" --fetch-metrics --namespace="${ns}" "${name}" > "${fetcher_metrics_json}"; then + die "Failed to fetch prefetcher metrics for set ${name}" fi # See the stackrox_image_prefetches table definition in https://github.com/stackrox/automation-iac/blob/main/resources/testing/stackrox-ci/metrics.tf # for the order of columns. @@ -1080,7 +1067,7 @@ scanner-v4-db ${tag} roxctl ${tag} END ;; - *qa-e2e-tests*) + *nongroovy-e2e-tests*|*qa-e2e-tests*) if [[ "${USE_KONFLUX_IMAGES:-false}" == "true" ]]; then cat >> "${image_list}" << END release-operator ${operator_controller_tag} @@ -2757,6 +2744,26 @@ _record_cluster_info() { set_ci_shared_export "cut_container_runtime_version" "$containerRuntimeVersion" } +# list_vm_scan_namespaces prints VM-scanning e2e namespace names, one per line. +# Returns 1 when kubectl cannot list namespaces, distinct from zero matches. +list_vm_scan_namespaces() { + local prefix="${VM_SCAN_NAMESPACE_PREFIX:-vm-scan-e2e}" + local out err + out="$(mktemp)" + err="$(mktemp)" + if ! kubectl --request-timeout=30s get ns \ + -o jsonpath='{range .items[*]}{.metadata.name}{"\n"}{end}' \ + > "$out" 2>"$err"; then + info "kubectl get ns failed: $(<"$err")" >&2 + rm -f "$out" "$err" + return 1 + fi + rm -f "$err" + grep -E "^${prefix}(-|$)" "$out" || true + rm -f "$out" + return 0 +} + if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then if [[ "$#" -lt 1 ]]; then die "When invoked at the command line a method is required." diff --git a/scripts/ci/logcheck/restart-ok-patterns.json b/scripts/ci/logcheck/restart-ok-patterns.json index a560532741d71..464531694d915 100644 --- a/scripts/ci/logcheck/restart-ok-patterns.json +++ b/scripts/ci/logcheck/restart-ok-patterns.json @@ -54,15 +54,9 @@ "logline": "Socket closed" }, { - "comment": "Scanner V4 slow to start indexer with external DB", - "job": "(gke-external-pg-17-qa-e2e-tests|gke-byodb-qa-e2e-tests)", - "logfile": "indexer-previous", - "logline": "panic: migrate: failed to connect to" - }, - { - "comment": "Scanner V4 slow to start matcher with external DB", - "job": "(gke-external-pg-17-qa-e2e-tests|gke-byodb-qa-e2e-tests)", - "logfile": "matcher-previous", + "comment": "Scanner V4 components can start before scanner-v4-db is ready to accept connections", + "job": ".*", + "logfile": "(indexer|matcher)-previous", "logline": "panic: migrate: failed to connect to" }, { diff --git a/scripts/fixxxers/config-controller/10-generate.sh b/scripts/fixxxers/config-controller/10-generate.sh new file mode 100755 index 0000000000000..22fcfa5a44923 --- /dev/null +++ b/scripts/fixxxers/config-controller/10-generate.sh @@ -0,0 +1,4 @@ +#!/bin/bash +set -euo pipefail + +make config-controller-gen diff --git a/deploy/common/pull-secret.sh b/scripts/pull-secret.sh similarity index 80% rename from deploy/common/pull-secret.sh rename to scripts/pull-secret.sh index 869de8c44d6fc..900b8c093441e 100755 --- a/deploy/common/pull-secret.sh +++ b/scripts/pull-secret.sh @@ -2,7 +2,7 @@ set -euo pipefail -DIR="$(cd "$(dirname "$0")" && pwd)" +ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)" die() { echo >&2 "$@" @@ -16,7 +16,7 @@ name="$1" registry="$2" [[ -n "$registry" ]] || die "No registry specified" -registry_auth="$("${DIR}/docker-auth.sh" -m k8s "$registry")" +registry_auth="$("${ROOT_DIR}/image/assets/docker-auth.sh" -m k8s "$registry")" [[ -n "$registry_auth" ]] || die "Unable to get registry auth info." cat <&2 'Note: $GITHUB_TOKEN not found, querying GitHub without an Authorization header may result in throttling.' + fi + + asset_id="$(curl -fsS --retry 3 --retry-delay 5 --retry-connrefused "${auth_headers[@]}" \ -H "Accept: application/vnd.github+json" \ -H "X-GitHub-Api-Version: 2026-03-10" \ "https://api.github.com/repos/${repo}/releases/${version}" \ @@ -117,7 +125,7 @@ gh_download_release() { echo >&2 "Error: No asset ID found for asset name ${asset_name} for release tag ${version} in repo ${repo}" exit 1 fi - curl -fsSL --retry 3 --retry-delay 5 --retry-connrefused \ + curl -fsSL --retry 3 --retry-delay 5 --retry-connrefused "${auth_headers[@]}" \ -H 'Accept: application/octet-stream' \ -o "$target" \ "https://api.github.com/repos/${repo}/releases/assets/${asset_id}" diff --git a/sensor/common/detector/detector.go b/sensor/common/detector/detector.go index 91a329abf3c20..5eae96f0afdc9 100644 --- a/sensor/common/detector/detector.go +++ b/sensor/common/detector/detector.go @@ -428,8 +428,8 @@ func (d *detectorImpl) Notify(e common.SensorComponentEvent) { func (d *detectorImpl) Capabilities() []centralsensor.SensorCapability { caps := []centralsensor.SensorCapability{centralsensor.SensorDetectionCap} - if features.PolicyWorkloadTypeExclusion.Enabled() { - caps = append(caps, centralsensor.WorkloadTypeExclusionCap) + if features.PolicyWorkloadKindExclusion.Enabled() { + caps = append(caps, centralsensor.WorkloadKindExclusionCap) } return caps } diff --git a/sensor/kubernetes/fake/virtualmachines.go b/sensor/kubernetes/fake/virtualmachines.go index d5ceb5cd40f5d..07139406828dc 100644 --- a/sensor/kubernetes/fake/virtualmachines.go +++ b/sensor/kubernetes/fake/virtualmachines.go @@ -167,15 +167,18 @@ func toUnstructuredVM(vm *kubeVirtV1.VirtualMachine) *unstructured.Unstructured return &unstructured.Unstructured{Object: unstructuredObj} } -// toUnstructuredVMI converts a VirtualMachineInstance to unstructured.Unstructured +// toUnstructuredVMI converts a VirtualMachineInstance to unstructured.Unstructured. func toUnstructuredVMI(vmi *kubeVirtV1.VirtualMachineInstance) *unstructured.Unstructured { - unstructuredObj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(vmi) + // A uint64 above MaxInt64 makes unstructured conversion fail the whole object. + sanitized := *vmi + sanitized.Status.RuntimeUser = uint64(jsonSafeUint64(sanitized.Status.RuntimeUser)) + unstructuredObj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&sanitized) if err != nil { - log.Warnf("failed to convert VMI %s to unstructured object: %v", vmi.GetName(), err) + log.Warnf("failed to convert VMI %s to unstructured object: %v", sanitized.GetName(), err) return &unstructured.Unstructured{Object: map[string]interface{}{}} } obj := &unstructured.Unstructured{Object: unstructuredObj} - normalizeVMIUnsignedFields(obj, vmi.Status) + normalizeVMIUnsignedFields(obj, sanitized.Status) return obj } diff --git a/sensor/kubernetes/listener/resources/convert.go b/sensor/kubernetes/listener/resources/convert.go index 79650f98be89c..7329c81b51fb4 100644 --- a/sensor/kubernetes/listener/resources/convert.go +++ b/sensor/kubernetes/listener/resources/convert.go @@ -476,14 +476,14 @@ func (w *deploymentWrap) getLabelSelector(spec reflect.Value) (*metav1.LabelSele } // Selector is of map type for replication controller - if labelMap, ok := s.Interface().(map[string]string); ok { + if labelMap, ok := reflect.TypeAssert[map[string]string](s); ok { return &metav1.LabelSelector{ MatchLabels: labelMap, }, nil } // All other resources uses labelSelector. - if ls, ok := s.Interface().(*metav1.LabelSelector); ok { + if ls, ok := reflect.TypeAssert[*metav1.LabelSelector](s); ok { return ls, nil } diff --git a/sensor/kubernetes/listener/resources/route.go b/sensor/kubernetes/listener/resources/route.go index d9bf38affd1a7..54c6375d36085 100644 --- a/sensor/kubernetes/listener/resources/route.go +++ b/sensor/kubernetes/listener/resources/route.go @@ -30,7 +30,7 @@ func (r *routeDispatcher) ProcessEvent(obj, _ interface{}, action central.Resour return nil } - if action == central.ResourceAction_CREATE_RESOURCE || action == central.ResourceAction_UPDATE_RESOURCE { + if action == central.ResourceAction_CREATE_RESOURCE || action == central.ResourceAction_UPDATE_RESOURCE || action == central.ResourceAction_SYNC_RESOURCE { r.serviceStore.upsertRoute(route) } if action == central.ResourceAction_REMOVE_RESOURCE { diff --git a/tests/byodb/lib.sh b/tests/byodb/lib.sh index 55fe4e0d82f90..38ebe7f5e7ed9 100644 --- a/tests/byodb/lib.sh +++ b/tests/byodb/lib.sh @@ -19,7 +19,8 @@ deploy_external_postgres() { kubectl create namespace database envsubst < ./tests/byodb/simple-postgres.yaml | kubectl apply -f - - kubectl wait --for=condition=Ready pod -l app=postgres -n database --timeout=180s + # The StatefulSet exists, but its pod may not have been created yet. + kubectl rollout status statefulset/postgres -n database --timeout=180s } preamble() { diff --git a/tests/common.go b/tests/common.go index 54565e09ea603..945cc36266b0e 100644 --- a/tests/common.go +++ b/tests/common.go @@ -1443,6 +1443,11 @@ func isOpenshift() bool { return os.Getenv("ORCHESTRATOR_FLAVOR") == "openshift" } +// usingKonfluxImages returns true when StackRox was deployed from Konflux-built images. +func usingKonfluxImages() bool { + return os.Getenv("USE_KONFLUX_IMAGES") == "true" +} + // mustGetCluster returns the details of the one and only known secured cluster. func mustGetCluster(t *testing.T, ctx context.Context) *storage.Cluster { conn := centralgrpc.GRPCConnectionToCentral(t) diff --git a/tests/delegated_scanning_test.go b/tests/delegated_scanning_test.go index 8e014122ff79d..8b932e00991e2 100644 --- a/tests/delegated_scanning_test.go +++ b/tests/delegated_scanning_test.go @@ -124,8 +124,8 @@ var ( // delegation, the scan attempt should be retried. // // ex: - // - no connection to "a21b168a-280e-40d1-a175-e84d14ed8232" - "no connection to", + // - no connection to cluster "remote", verify the cluster is healthy and connected + "no connection to cluster", // A registry having gateway issues (Quay.io in particular) may return a 502 (Bad Gateway) // or 504 (Gateway Timeout) along with some HTML, retry when this happens. diff --git a/tests/e2e/lib-compat.sh b/tests/e2e/lib-compat.sh index 2b85aa4392e4a..396d78b901c77 100644 --- a/tests/e2e/lib-compat.sh +++ b/tests/e2e/lib-compat.sh @@ -85,6 +85,9 @@ roxie_config_from_environment_compat() { handle_pod_security_policies + info "Configuring Konflux images..." + handle_konflux_images "$config_file" + info "Configuring TRUSTED_CA_FILE..." handle_trusted_ca_file "$config_file" @@ -96,6 +99,11 @@ roxie_config_from_environment_compat() { handle_endpoints_for_test "$config_file" # Echoes info internally. + if [[ -n "${EXTERNAL_DB:-}" ]]; then + info "Configuring external database..." + handle_external_database_settings "$config_file" "$namespace" + fi + info "Configuring custom central environment..." while read -r var_val; do local name="${var_val%%=*}" @@ -182,10 +190,18 @@ roxie_config_from_environment_compat() { fi ) + info "Configuring storage classes..." + handle_storage_classes "$config_file" + info "Configuring scanner V4..." - handle_scanner_v4_setting "$config_file" ".central.spec.scannerV4.scannerComponent" "Enabled" - handle_scanner_v4_setting "$config_file" ".securedCluster.spec.scannerV4.scannerComponent" "AutoSense" - handle_scanner_v4_vuln_readiness "$config_file" + if [[ ${ROX_SCANNER_V4:-} == false ]]; then + handle_scanner_v4_setting "$config_file" ".central.spec.scannerV4.scannerComponent" "Disabled" + handle_scanner_v4_setting "$config_file" ".securedCluster.spec.scannerV4.scannerComponent" "Disabled" + else + handle_scanner_v4_setting "$config_file" ".central.spec.scannerV4.scannerComponent" "Enabled" + handle_scanner_v4_setting "$config_file" ".securedCluster.spec.scannerV4.scannerComponent" "AutoSense" + handle_scanner_v4_vuln_readiness "$config_file" + fi info "Configuring declarative configuration..." handle_declarative_configuration "$config_file" @@ -197,6 +213,32 @@ roxie_config_from_environment_compat() { handle_virtual_machines_configuration "$config_file" } +handle_external_database_settings() { + local config_file="$1" + local namespace="$2" + + merge_yaml "$config_file" < "${roxie_config}" +central: + pauseReconciliation: true + namespace: stackrox + resourceProfile: ci +securedCluster: + pauseReconciliation: true + namespace: stackrox + resourceProfile: ci +EOF + deploy_stackrox_with_roxie_compat "$roxie_config" + rm -f "$roxie_config" # Enable the console plugin for OpenShift if the ConsolePlugin resource exists if [[ "${ORCHESTRATOR_FLAVOR}" == "openshift" ]] && kubectl get consoleplugin advanced-cluster-security &>/dev/null; then diff --git a/tests/e2e/run-vm-scanning.sh b/tests/e2e/run-vm-scanning.sh index 2587aeb93f57f..e54198d332529 100755 --- a/tests/e2e/run-vm-scanning.sh +++ b/tests/e2e/run-vm-scanning.sh @@ -47,6 +47,8 @@ test_vm_scanning_e2e() { deploy_stackrox cd "$ROOT" + ensure_vm_scan_ssh_identity + persist_vm_scan_virtctl rm -f FAIL # Run the full VM scanning e2e suite. make -C tests TESTFLAGS="-race -p 1 -timeout 90m" vm-scanning-tests || touch FAIL diff --git a/tests/e2e/vm-scanning-lib.sh b/tests/e2e/vm-scanning-lib.sh index 58d771a00eb62..87d65f137746d 100755 --- a/tests/e2e/vm-scanning-lib.sh +++ b/tests/e2e/vm-scanning-lib.sh @@ -270,3 +270,79 @@ ensure_virtctl_binary_insecure() { is_CI || die "Secure virtctl download failed; refusing insecure curl -k fallback outside CI. Set VIRTCTL_PATH or fix cluster ingress trust material." _download_and_install_virtctl -k } + +# Host-side files shared between the Go suite (via exported env) and post-test +# guest journal collection. Kept out of test_outputs so the private key is not +# uploaded as a CI artifact. +vm_scan_e2e_dir() { + printf '%s\n' "${VM_SCAN_E2E_DIR:-/tmp/vm-scan-e2e}" +} + +# export_vm_scan_public_key fills VM_SSH_PUBLIC_KEY from identity when unset. +# LoadVMScanConfig requires both halves of the keypair. +export_vm_scan_public_key() { + local identity="$1" + if [[ -n "${VM_SSH_PUBLIC_KEY:-}" ]]; then + return 0 + fi + if [[ -f "${identity}.pub" ]]; then + VM_SSH_PUBLIC_KEY="$(<"${identity}.pub")" + export VM_SSH_PUBLIC_KEY + return 0 + fi + VM_SSH_PUBLIC_KEY="$(ssh-keygen -y -f "$identity")" + printf '%s\n' "$VM_SSH_PUBLIC_KEY" > "${identity}.pub" + export VM_SSH_PUBLIC_KEY +} + +# ensure_vm_scan_ssh_identity writes or reuses an SSH key under vm_scan_e2e_dir +# so the Go suite and post-test guest log collection share the same identity. +ensure_vm_scan_ssh_identity() { + local dir identity + dir="$(vm_scan_e2e_dir)" + mkdir -p "$dir" + identity="${dir}/ssh-identity" + + if [[ -n "${VM_SSH_PRIVATE_KEY:-}" ]]; then + printf '%s\n' "${VM_SSH_PRIVATE_KEY}" > "$identity" + chmod 600 "$identity" + export VM_SSH_PRIVATE_KEY_PATH="$identity" + export_vm_scan_public_key "$identity" + return 0 + fi + + if [[ -f "$identity" ]]; then + export VM_SSH_PRIVATE_KEY_PATH="$identity" + VM_SSH_PRIVATE_KEY="$(<"$identity")" + export VM_SSH_PRIVATE_KEY + export_vm_scan_public_key "$identity" + return 0 + fi + + ssh-keygen -t ed25519 -f "$identity" -N "" -C "stackrox-vm-scan-e2e" >/dev/null + chmod 600 "$identity" + VM_SSH_PRIVATE_KEY="$(<"$identity")" + VM_SSH_PUBLIC_KEY="$(<"${identity}.pub")" + export VM_SSH_PRIVATE_KEY VM_SSH_PUBLIC_KEY VM_SSH_PRIVATE_KEY_PATH="$identity" + info "Ephemeral VM scan SSH identity written to ${identity}" +} + +# persist_vm_scan_virtctl copies virtctl next to the SSH identity and records +# the resolved path so post-test can find it after the test process's PATH +# goes away. +persist_vm_scan_virtctl() { + local dir src dest + dir="$(vm_scan_e2e_dir)" + mkdir -p "$dir" + src="$(command -v virtctl)" || { + info "virtctl not on PATH; post-test guest journal collection will skip" + return 0 + } + printf '%s\n' "$src" > "${dir}/virtctl-path" + dest="${dir}/virtctl" + if cp "$src" "$dest" && chmod +x "$dest"; then + info "Persisted virtctl to ${dest} for post-test guest journal collection" + else + info "Could not copy virtctl to ${dest}; post-test will use ${src}" + fi +} diff --git a/tests/excluded_scopes_test.go b/tests/excluded_scopes_test.go index e65841ee177e5..f1fd7e827fa97 100644 --- a/tests/excluded_scopes_test.go +++ b/tests/excluded_scopes_test.go @@ -48,10 +48,9 @@ func verifyNoAlertForExcludedScopes(t *testing.T, deploymentName string) { require.NoError(t, err) latestPolicy.Exclusions = []*storage.Exclusion{ - { - Deployment: &storage.Exclusion_Deployment{ - Name: deploymentName, - }, + {Matcher: &storage.Exclusion_Deployment_{Deployment: &storage.Exclusion_Deployment{ + Name: deploymentName, + }}, }, } ctx, cancel = context.WithTimeout(context.Background(), time.Minute) diff --git a/tests/init_container_test.go b/tests/init_container_test.go index ebf7a61e4b431..c7b108dbbf12a 100644 --- a/tests/init_container_test.go +++ b/tests/init_container_test.go @@ -180,12 +180,38 @@ func (s *InitContainerSuite) createPolicyWithCleanup(policy *storage.Policy) *st } func (s *InitContainerSuite) waitForViolationAlert(deploymentName, policyName string, expectedCount int) { + waitForAlert(s.T(), s.alertService, s.violationAlertRequest(deploymentName, policyName), expectedCount) +} + +// activeViolationText waits until one active alert exists and returns its violation messages. +func (s *InitContainerSuite) activeViolationText(deploymentName, policyName string) string { + s.waitForViolationAlert(deploymentName, policyName, 1) + + ctx, cancel := context.WithTimeout(context.Background(), time.Minute) + listResp, err := s.alertService.ListAlerts(ctx, s.violationAlertRequest(deploymentName, policyName)) + cancel() + s.Require().NoError(err) + s.Require().Len(listResp.GetAlerts(), 1) + + ctx, cancel = context.WithTimeout(context.Background(), time.Minute) + alert, err := s.alertService.GetAlert(ctx, &v1.ResourceByID{Id: listResp.GetAlerts()[0].GetId()}) + cancel() + s.Require().NoError(err) + s.Require().NotEmpty(alert.GetViolations()) + + messages := make([]string, 0, len(alert.GetViolations())) + for _, violation := range alert.GetViolations() { + messages = append(messages, violation.GetMessage()) + } + return strings.Join(messages, "\n") +} + +func (s *InitContainerSuite) violationAlertRequest(deploymentName, policyName string) *v1.ListAlertsRequest { query := search.NewQueryBuilder(). AddStrings(search.DeploymentName, deploymentName). AddStrings(search.PolicyName, policyName). AddStrings(search.ViolationState, storage.ViolationState_ACTIVE.String()) - - waitForAlert(s.T(), s.alertService, &v1.ListAlertsRequest{Query: query.Query()}, expectedCount) + return &v1.ListAlertsRequest{Query: query.Query()} } func (s *InitContainerSuite) TestInitContainerExtraction() { @@ -381,28 +407,85 @@ func (s *InitContainerSuite) TestPolicyEvaluatesBothContainerTypes() { t.Logf("Verified: both init and regular containers with :latest tag triggered policy violation") } -func (s *InitContainerSuite) TestEvaluationFilterSkipsInitContainers() { +func (s *InitContainerSuite) TestEvaluationFilterSkipsContainers() { t := s.T() ns := fmt.Sprintf("init-test-filter-%d", rand.IntN(10000)) createNamespaceWithLabels(t, ns, nil) defer deleteNamespace(t, ns) - policy := s.newLatestTagPolicy( - fmt.Sprintf("Test - Skip Init %d", rand.IntN(10000)), ns, - ) - policy.EvaluationFilter = &storage.EvaluationFilter{ - SkipContainerTypes: []storage.ContainerType{storage.ContainerType_INIT}, - } - createdPolicy := s.createPolicyWithCleanup(policy) + unfiltered := s.createPolicyWithCleanup(s.newLatestTagPolicy( + fmt.Sprintf("Test - Evaluate Init %d", rand.IntN(10000)), ns, + )) + + s.Require().Empty(unfiltered.GetEvaluationFilter().GetSkipContainerTypes()) - // Init uses :latest (would violate), regular uses tagged image (no violation). - // With skip-init filter, the only violating container is skipped — expect 0 alerts. + // Both containers use :latest. Require an alert naming both before checking filters. deployName := fmt.Sprintf("init-filter-skip-%d", rand.IntN(10000)) - s.createDeploymentWithInitContainers(deployName, ns, []string{busyboxLatest}, nginxTagged) + s.createDeploymentWithInitContainers(deployName, ns, []string{busyboxLatest}, nginxLatest) defer teardownDeploymentWithoutCheck(t, deployName, ns) - s.waitForDeploymentWithContainers(deployName, 2) + dep := s.waitForDeploymentWithContainers(deployName, 2) + var initContainer, regularContainer *storage.Container + for _, c := range dep.GetContainers() { + switch c.GetType() { + case storage.ContainerType_INIT: + initContainer = c + case storage.ContainerType_REGULAR: + regularContainer = c + } + } + s.Require().NotNil(initContainer, "expected an init container") + s.Require().NotNil(regularContainer, "expected a regular container") + s.Equal("init-0", initContainer.GetName()) + s.Equal(deployName, regularContainer.GetName()) + s.Contains(initContainer.GetImage().GetName().GetFullName(), "busybox") + s.Contains(regularContainer.GetImage().GetName().GetFullName(), "nginx") - s.waitForViolationAlert(deployName, createdPolicy.GetName(), 0) - t.Logf("Verified: policy with skip init filter produced no violations") + regularContainerRef := fmt.Sprintf("Container '%s'", deployName) + unfilteredText := s.activeViolationText(deployName, unfiltered.GetName()) + s.Require().Contains(unfilteredText, regularContainerRef) + s.Require().Contains(unfilteredText, "Container 'init-0'") + s.Require().Contains(unfilteredText, "tag 'latest'") + t.Logf("Verified unfiltered alert names both containers: %s", unfilteredText) + + tests := map[string]struct { + skipType storage.ContainerType + evaluatedContainer string + skippedContainer string + skippedImage string + }{ + "skip init": { + skipType: storage.ContainerType_INIT, + evaluatedContainer: deployName, + skippedContainer: "init-0", + skippedImage: "busybox", + }, + "skip regular": { + skipType: storage.ContainerType_REGULAR, + evaluatedContainer: "init-0", + skippedContainer: deployName, + skippedImage: "nginx", + }, + } + for name, tc := range tests { + s.Run(name, func() { + policy := s.newLatestTagPolicy(fmt.Sprintf("Test - %s %d", name, rand.IntN(10000)), ns) + policy.EvaluationFilter = &storage.EvaluationFilter{ + SkipContainerTypes: []storage.ContainerType{tc.skipType}, + } + filtered := s.createPolicyWithCleanup(policy) + + ctx, cancel := context.WithTimeout(context.Background(), time.Minute) + storedPolicy, err := s.policyService.GetPolicy(ctx, &v1.ResourceByID{Id: filtered.GetId()}) + cancel() + s.Require().NoError(err) + s.Require().Equal([]storage.ContainerType{tc.skipType}, storedPolicy.GetEvaluationFilter().GetSkipContainerTypes()) + + filteredText := s.activeViolationText(deployName, filtered.GetName()) + s.Require().Contains(filteredText, fmt.Sprintf("Container '%s'", tc.evaluatedContainer)) + s.Require().Contains(filteredText, "tag 'latest'") + s.Require().NotContains(filteredText, fmt.Sprintf("Container '%s'", tc.skippedContainer)) + s.Require().NotContains(filteredText, tc.skippedImage) + }) + } } diff --git a/tests/metadata_test.go b/tests/metadata_test.go index eb74145da8d70..e7af3d35aca39 100644 --- a/tests/metadata_test.go +++ b/tests/metadata_test.go @@ -5,6 +5,7 @@ package tests import ( "context" "os" + "strings" "testing" "time" @@ -69,7 +70,14 @@ func TestMetadataIsSetCorrectly(t *testing.T) { } metadataWithAuth := getMetadata(t, centralgrpc.GRPCConnectionToCentral(t)) - assert.Equal(t, buildinfo.BuildFlavor, metadataWithAuth.GetBuildFlavor()) - assert.Equal(t, buildinfo.ReleaseBuild, metadataWithAuth.GetReleaseBuild()) - assert.Equal(t, version.GetMainVersion(), metadataWithAuth.GetVersion()) + + if usingKonfluxImages() { + assert.Equal(t, "release", metadataWithAuth.GetBuildFlavor()) + assert.True(t, metadataWithAuth.GetReleaseBuild()) + assert.Equal(t, strings.TrimSuffix(version.GetMainVersion(), "-fast"), strings.TrimSuffix(metadataWithAuth.GetVersion(), "-fast")) + } else { + assert.Equal(t, buildinfo.BuildFlavor, metadataWithAuth.GetBuildFlavor()) + assert.Equal(t, buildinfo.ReleaseBuild, metadataWithAuth.GetReleaseBuild()) + assert.Equal(t, version.GetMainVersion(), metadataWithAuth.GetVersion()) + } } diff --git a/tests/performance/load/package-lock.json b/tests/performance/load/package-lock.json index 638304c522bd7..2dd22a073539b 100644 --- a/tests/performance/load/package-lock.json +++ b/tests/performance/load/package-lock.json @@ -258,10 +258,11 @@ "dev": true }, "node_modules/brace-expansion": { - "version": "1.1.16", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.16.tgz", - "integrity": "sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw==", + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz", + "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", "dev": true, + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -1697,10 +1698,11 @@ } }, "node_modules/moment": { - "version": "2.29.4", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", - "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==", + "version": "2.31.0", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.31.0.tgz", + "integrity": "sha512-0acOTfMiWOheYS4eoWb80yYMb/JLvVv9SHbs2PehaDzfUG0Bw855SKyk0IKTnPGa5+U2bmi3W68l1+sGLX/pvw==", "dev": true, + "license": "MIT", "engines": { "node": "*" } @@ -2715,9 +2717,9 @@ "dev": true }, "brace-expansion": { - "version": "1.1.16", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.16.tgz", - "integrity": "sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw==", + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz", + "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", "dev": true, "requires": { "balanced-match": "^1.0.0", @@ -3844,9 +3846,9 @@ } }, "moment": { - "version": "2.29.4", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", - "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==", + "version": "2.31.0", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.31.0.tgz", + "integrity": "sha512-0acOTfMiWOheYS4eoWb80yYMb/JLvVv9SHbs2PehaDzfUG0Bw855SKyk0IKTnPGa5+U2bmi3W68l1+sGLX/pvw==", "dev": true }, "ms": { diff --git a/tests/performance/scale/go.mod b/tests/performance/scale/go.mod index 00f91094ef10f..711815f73608c 100644 --- a/tests/performance/scale/go.mod +++ b/tests/performance/scale/go.mod @@ -4,7 +4,7 @@ go 1.26.0 require ( github.com/cloud-bulldozer/go-commons v1.0.19 - github.com/go-git/go-git/v5 v5.19.2 + github.com/go-git/go-git/v5 v5.19.3 github.com/hashicorp/go-multierror v1.1.1 github.com/spf13/cobra v1.10.2 go.yaml.in/yaml/v3 v3.0.5 @@ -22,7 +22,7 @@ require ( github.com/emirpasic/gods v1.18.1 // indirect github.com/fxamacker/cbor/v2 v2.9.1 // indirect github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect - github.com/go-git/go-billy/v5 v5.9.0 // indirect + github.com/go-git/go-billy/v5 v5.9.2 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-openapi/jsonpointer v1.0.0 // indirect github.com/go-openapi/jsonreference v1.0.0 // indirect @@ -46,23 +46,22 @@ require ( github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/kevinburke/ssh_config v1.2.0 // indirect - github.com/klauspost/cpuid/v2 v2.3.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect - github.com/pjbgf/sha1cd v0.6.0 // indirect + github.com/pjbgf/sha1cd v0.7.0 // indirect github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect github.com/skeema/knownhosts v1.3.1 // indirect github.com/spf13/pflag v1.0.10 // indirect github.com/x448/float16 v0.8.4 // indirect github.com/xanzy/ssh-agent v0.3.3 // indirect go.yaml.in/yaml/v2 v2.4.4 // indirect - golang.org/x/crypto v0.54.0 // indirect + golang.org/x/crypto v0.56.0 // indirect golang.org/x/net v0.57.0 // indirect golang.org/x/oauth2 v0.36.0 // indirect golang.org/x/sys v0.47.0 // indirect golang.org/x/term v0.45.0 // indirect - golang.org/x/text v0.40.0 // indirect + golang.org/x/text v0.41.0 // indirect golang.org/x/time v0.15.0 // indirect google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af // indirect gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect diff --git a/tests/performance/scale/go.sum b/tests/performance/scale/go.sum index a865a78a72587..7962276a38121 100644 --- a/tests/performance/scale/go.sum +++ b/tests/performance/scale/go.sum @@ -32,12 +32,12 @@ github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c= github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= -github.com/go-git/go-billy/v5 v5.9.0 h1:jItGXszUDRtR/AlferWPTMN4j38BQ88XnXKbilmmBPA= -github.com/go-git/go-billy/v5 v5.9.0/go.mod h1:jCnQMLj9eUgGU7+ludSTYoZL/GGmii14RxKFj7ROgHw= +github.com/go-git/go-billy/v5 v5.9.2 h1:OXFSRyz4g20upsGDJgQG9Bak1l/ZEv8GHVYB52O71sE= +github.com/go-git/go-billy/v5 v5.9.2/go.mod h1:ExsU+jcGwXTBOnyilvAnEM1wug1IxHr4yP2ZXsNRtV0= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= -github.com/go-git/go-git/v5 v5.19.2 h1:wkfn7vOlUBu8ivAWKBWisTiwJK4jYHzTF8Ndv1LyGqY= -github.com/go-git/go-git/v5 v5.19.2/go.mod h1:QqCBE1EFN5ddFmrliLQ3/ntRCUjZU3EJuwuB/jWEHjk= +github.com/go-git/go-git/v5 v5.19.3 h1:qHttbZ+Am7wEjdTpR1XMQ4rl42FK9SIXzZ4o9x7s6M4= +github.com/go-git/go-git/v5 v5.19.3/go.mod h1:Ye4C8dVigkqrv9UsB4NMdSLV4yAIkLiIhW61gcOyhl4= 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-openapi/jsonpointer v1.0.0 h1:kR9tHqY0CtZaOPVFm622dPVNhrvYpwr4uCxgL3h1H8s= @@ -95,8 +95,6 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= -github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= -github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= @@ -114,8 +112,8 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k= github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY= -github.com/pjbgf/sha1cd v0.6.0 h1:3WJ8Wz8gvDz29quX1OcEmkAlUg9diU4GxJHqs0/XiwU= -github.com/pjbgf/sha1cd v0.6.0/go.mod h1:lhpGlyHLpQZoxMv8HcgXvZEhcGs0PG/vsZnEJ7H0iCM= +github.com/pjbgf/sha1cd v0.7.0 h1:ZRNPKHj+gfkLBf0KJv/p1Hmz+7bqCT5o311YX0nq+DA= +github.com/pjbgf/sha1cd v0.7.0/go.mod h1:pKR5Li+qTCo+ebqITZfwPlJGoCFCvSO00qxjbNtTUFQ= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -152,8 +150,8 @@ go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.54.0 h1:YLIA59K4fiNzHzjnZt2tUJQjQtUWfWbeHBqKtk3eScw= -golang.org/x/crypto v0.54.0/go.mod h1:KWL8ny2AZdGR2cWmzeHrp2azQPGogOv+HeQaVEXC2dk= +golang.org/x/crypto v0.56.0 h1:GUh5Ii4J5jtcseSMiRqr1jXCNHoxjeV9Fmekc2oLy6Y= +golang.org/x/crypto v0.56.0/go.mod h1:OMW5y6CY9l38uPLmxU6l6pwcXp1obtLo3e6gT7gQR2I= golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f h1:W3F4c+6OLc6H2lb//N1q4WpJkhzJCK5J6kUi1NTVXfM= golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f/go.mod h1:J1xhfL/vlindoeF/aINzNzt2Bket5bjo9sdOYzOsU80= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= @@ -173,8 +171,8 @@ golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9sn golang.org/x/term v0.45.0 h1:NwWyBmoJCbfTHpxrWoZ9C6/VxOf7ic219I8xZZFdrf0= golang.org/x/term v0.45.0/go.mod h1:9aqxs0blBcrm/n0L9QW0aRVD+ktan8ssZromtqJC43w= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.40.0 h1:Ub2Z6/xjgF1WrYQz2nuITOEegKFtiIy+rieRJ5lHZKs= -golang.org/x/text v0.40.0/go.mod h1:hpnzDAfGV753zIKo+wk3u1bVKCGPbrnF7+7LBF/UHVY= +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.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/tests/pods_test.go b/tests/pods_test.go index a2e3b911163bd..b96ade2c69aed 100644 --- a/tests/pods_test.go +++ b/tests/pods_test.go @@ -10,7 +10,6 @@ import ( "time" "github.com/graph-gophers/graphql-go" - "github.com/stackrox/rox/central/graphql/resolvers/inputtypes" "github.com/stackrox/rox/generated/storage" "github.com/stackrox/rox/pkg/sliceutils" "github.com/stackrox/rox/pkg/testutils" @@ -194,18 +193,6 @@ func getPods(t testutils.T, deploymentID string) []Pod { Pods []Pod `json:"pods"` } - // Using this to ensure pagination does not fail. - offset := int32(0) - limit := int32(10) - field := "Pod Name" - pagination := inputtypes.Pagination{ - Offset: &offset, - Limit: &limit, - SortOption: &inputtypes.SortOption{ - Field: &field, - }, - } - makeGraphQLRequest(t, ` query getPods($podsQuery: String, $pagination: Pagination) { pods(query: $podsQuery, pagination: $pagination) { @@ -221,7 +208,7 @@ func getPods(t testutils.T, deploymentID string) []Pod { } `, map[string]interface{}{ "podsQuery": fmt.Sprintf("Deployment ID: %s", deploymentID), - "pagination": pagination, + "pagination": nil, }, &respData, timeout) t.Logf("%+v", respData) diff --git a/tests/policy_as_code_test.go b/tests/policy_as_code_test.go index 6f55908167678..2a023d61826b3 100644 --- a/tests/policy_as_code_test.go +++ b/tests/policy_as_code_test.go @@ -473,23 +473,34 @@ func (pc *PolicyAsCodeSuite) createCRAndObserveInCentral(policyCR *v1alpha1.Secu } func (pc *PolicyAsCodeSuite) TestCRWithEvaluationFilter() { - k8sPolicy := createBasePolicyStruct("test-eval-filter-cr") - k8sPolicy.Spec.EvaluationFilter = &v1alpha1.EvaluationFilter{ - SkipContainerTypes: []v1alpha1.ContainerType{"INIT"}, + tests := map[string]struct { + containerType v1alpha1.ContainerType + protoType storage.ContainerType + }{ + "init": {containerType: "INIT", protoType: storage.ContainerType_INIT}, + "regular": {containerType: "REGULAR", protoType: storage.ContainerType_REGULAR}, } + for name, tc := range tests { + pc.Run(name, func() { + k8sPolicy := createBasePolicyStruct("test-eval-filter-cr-" + name) + k8sPolicy.Spec.EvaluationFilter = &v1alpha1.EvaluationFilter{ + SkipContainerTypes: []v1alpha1.ContainerType{tc.containerType}, + } - id := pc.createCRAndObserveInCentral(k8sPolicy) - pc.Require().NotEmpty(id) + id := pc.createCRAndObserveInCentral(k8sPolicy) + pc.Require().NotEmpty(id) - policy, err := pc.policyClient.GetPolicy(pc.ctx, &v1.ResourceByID{Id: id}) - pc.Require().NoError(err) - pc.policies = append(pc.policies, policy) + policy, err := pc.policyClient.GetPolicy(pc.ctx, &v1.ResourceByID{Id: id}) + pc.Require().NoError(err) + pc.policies = append(pc.policies, policy) - pc.Require().NotNil(policy.GetEvaluationFilter()) - pc.Require().Equal( - []storage.ContainerType{storage.ContainerType_INIT}, - policy.GetEvaluationFilter().GetSkipContainerTypes(), - ) + pc.Require().NotNil(policy.GetEvaluationFilter()) + pc.Require().Equal( + []storage.ContainerType{tc.protoType}, + policy.GetEvaluationFilter().GetSkipContainerTypes(), + ) + }) + } } func (pc *PolicyAsCodeSuite) TearDownSuite() { diff --git a/tests/policy_test.go b/tests/policy_test.go index ab1ba5a7bb1ac..fd57f8515840e 100644 --- a/tests/policy_test.go +++ b/tests/policy_test.go @@ -519,12 +519,11 @@ func verifyExclusionsRemoved(t *testing.T) { policy.Id = "verifyExcludedScopesRemoved policy ID" policy.Name = "verifyExcludedScopesRemoved is a valid policy" policy.Exclusions = []*storage.Exclusion{ - { - Deployment: &storage.Exclusion_Deployment{ - Scope: &storage.Scope{ - Cluster: "This is not a cluster", - }, + {Matcher: &storage.Exclusion_Deployment_{Deployment: &storage.Exclusion_Deployment{ + Scope: &storage.Scope{ + Cluster: "This is not a cluster", }, + }}, }, } diff --git a/tests/roxctl/bats-tests/cluster/sensor-generate-image-overrides.bats b/tests/roxctl/bats-tests/cluster/sensor-generate-image-overrides.bats index 2ea36893fcd2b..e42e1558289bb 100755 --- a/tests/roxctl/bats-tests/cluster/sensor-generate-image-overrides.bats +++ b/tests/roxctl/bats-tests/cluster/sensor-generate-image-overrides.bats @@ -3,7 +3,7 @@ load "../helpers.bash" out_dir="" -cluster_name="override-test-cluster" +cluster_name="" central_flavor="" setup_file() { @@ -11,11 +11,20 @@ setup_file() { command -v yq || skip "Tests in this file require yq" [[ -n "$API_ENDPOINT" ]] || fail "API_ENDPOINT environment variable required" [[ -n "$ROX_ADMIN_PASSWORD" ]] || fail "ROX_ADMIN_PASSWORD environment variable required" - central_flavor="$(kubectl -n stackrox exec -it deployment/central -- env | grep -i ROX_IMAGE_FLAVOR | sed 's/ROX_IMAGE_FLAVOR=//')" } setup() { out_dir="$(mktemp -d -u)" + # Unique cluster name per test so that tests with different overrides don't affect + # each other due to '--continue-if-exists'. + cluster_name="override-test-cluster-${BATS_TEST_NUMBER}" + central_flavor="$(kubectl -n stackrox exec deployment/central -- env 2>&1 | grep -i '^ROX_IMAGE_FLAVOR=' | sed 's/ROX_IMAGE_FLAVOR=//' | tr -d '\r\n')" + if [[ -z "$central_flavor" ]]; then + echo "Failed to detect Central image flavor. Checking Central pod status:" >&3 + kubectl -n stackrox get pod -l app=central >&3 || true + fail "Could not detect Central image flavor from deployment/central" + fi + echo "Detected central flavor: '$central_flavor'" >&3 } teardown() { @@ -30,41 +39,58 @@ registry_from_flavor() { "opensource") echo "quay.io/stackrox-io" ;; + "rhacs") + echo "registry.redhat.io/advanced-cluster-security" + ;; + *) + fail "Unknown central flavor: '$central_flavor'" + ;; + esac +} + +image_name_from_flavor() { + local component="$1" + case "$central_flavor" in + "rhacs") + echo "rhacs-${component}-rhel9" + ;; + *) + echo "$component" + ;; esac } -any_version_latest="${any_version}[0-9]+\-latest" -any_version_slim="${any_version}[0-9]+\-slim" +any_version_regex="${any_version}(x|[0-9]+)(-.*)?$" @test "roxctl sensor generate: no overrides" { generate_bundle k8s --name "$cluster_name" assert_success - assert_bundle_registry "$out_dir" "sensor" "$(registry_from_flavor)/main:$any_version" - assert_bundle_registry "$out_dir" "collector" "$(registry_from_flavor)/collector:$any_version" + assert_bundle_registry "$out_dir" "sensor" "$(registry_from_flavor)/$(image_name_from_flavor main):$any_version_regex" + assert_bundle_registry "$out_dir" "collector" "$(registry_from_flavor)/$(image_name_from_flavor collector):$any_version_regex" delete_cluster "$cluster_name" } @test "roxctl sensor generate: with main image override. Collector should be derived from main override" { generate_bundle k8s "--main-image-repository=example.com/stackrox/main" --name "$cluster_name" assert_success - assert_bundle_registry "$out_dir" "sensor" "example\.com/stackrox/main:$any_version" - assert_bundle_registry "$out_dir" "collector" "example\.com/stackrox/collector:$any_version" + assert_bundle_registry "$out_dir" "sensor" "example\.com/stackrox/main:$any_version_regex" + assert_bundle_registry "$out_dir" "collector" "example\.com/stackrox/$(image_name_from_flavor collector):$any_version_regex" delete_cluster "$cluster_name" } @test "roxctl sensor generate: with collector override" { generate_bundle k8s "--collector-image-repository=example2.com/stackrox/collector" --name "$cluster_name" assert_success - assert_bundle_registry "$out_dir" "sensor" "$(registry_from_flavor)/main:$any_version" - assert_bundle_registry "$out_dir" "collector" "example2\.com/stackrox/collector:$any_version" + assert_bundle_registry "$out_dir" "sensor" "$(registry_from_flavor)/$(image_name_from_flavor main):$any_version_regex" + assert_bundle_registry "$out_dir" "collector" "example2\.com/stackrox/collector:$any_version_regex" delete_cluster "$cluster_name" } @test "roxctl sensor generate: with different overrides" { generate_bundle k8s "--main-image-repository=example.com/stackrox/main" "--collector-image-repository=example2.com/stackrox/collector" --name "$cluster_name" assert_success - assert_bundle_registry "$out_dir" "sensor" "example\.com/stackrox/main:$any_version" - assert_bundle_registry "$out_dir" "collector" "example2\.com/stackrox/collector:$any_version" + assert_bundle_registry "$out_dir" "sensor" "example\.com/stackrox/main:$any_version_regex" + assert_bundle_registry "$out_dir" "collector" "example2\.com/stackrox/collector:$any_version_regex" delete_cluster "$cluster_name" } diff --git a/tests/upgrade/lib.sh b/tests/upgrade/lib.sh index 5c0f21b0d70fb..7755bacf0b10b 100644 --- a/tests/upgrade/lib.sh +++ b/tests/upgrade/lib.sh @@ -105,162 +105,47 @@ get_target_bg_migration_seqnum() { "$TEST_ROOT/central/backgroundmigrations/seq_num.go" } -deploy_earlier_postgres_central() { +deploy_earlier_tag_central() { info "Deploying: $EARLIER_TAG..." - # Older checkouts force GOTOOLCHAIN=local during dependency checks. - # Put the toolchain selected for this checkout first on PATH for the build. - local build_goroot - build_goroot="$(go env GOROOT)" - PATH="${build_goroot}/bin:${PATH}" make cli - - PATH="bin/$TEST_HOST_PLATFORM:$PATH" command -v roxctl - PATH="bin/$TEST_HOST_PLATFORM:$PATH" roxctl version - - # Let's try helm - # BSD tr cannot filter /dev/urandom (NUL bytes). gen_admin_password - # produces a value shared with Helm --set and later restore/auth. - ROX_ADMIN_PASSWORD="$(gen_admin_password)" - export ROX_ADMIN_PASSWORD - PATH="bin/$TEST_HOST_PLATFORM:$PATH" roxctl helm output central-services --image-defaults opensource --output-dir /tmp/early-stackrox-central-services-chart --remove - - # Scanner V4 is installed by default in 4.10. Set its DB storage class now, - # because the later Helm upgrade cannot change an existing PVC's class. - local helm_extra_args=() - if [[ -n "${SCANNER_V4_DB_STORAGE_CLASS:-}" ]]; then - if [[ "${SCANNER_V4_DB_STORAGE_CLASS}" == "faster" ]]; then - kubectl apply -f "${TEST_ROOT}/deploy/common/ssd-storageclass.yaml" - fi - helm_extra_args+=(--set "scannerV4.db.persistence.persistentVolumeClaim.storageClass=${SCANNER_V4_DB_STORAGE_CLASS}") - fi - - # The generated chart can inherit the PR tag, which has no Scanner V2 images. - # Use the released Scanner V2 images for the initial deployment. - helm install -n stackrox --create-namespace stackrox-central-services /tmp/early-stackrox-central-services-chart \ - "${helm_extra_args[@]}" \ - --set central.adminPassword.value="${ROX_ADMIN_PASSWORD}" \ - --set central.db.enabled=true \ - --set central.db.persistence.persistentVolumeClaim.size="${PVC_SIZE:-100Gi}" \ - --set central.persistence.none=true \ - --set central.exposure.loadBalancer.enabled=true \ - --set system.enablePodSecurityPolicies=false \ - --set central.image.tag="${EARLIER_TAG}" \ - --set central.db.image.tag="${EARLIER_TAG}" \ - --set scanner.image.tag="${EARLIER_TAG}" \ - --set scanner.dbImage.tag="${EARLIER_TAG}" - - # Installing this way returns faster than the scripts but everything isn't running when it finishes like with - # the scripts. So we will give it a minute for things to get started before we proceed - sleep 60 - - ROX_USERNAME="admin" + local roxie_envrc + roxie_envrc="$(mktemp)" + roxie deploy central --verbose \ + --single-namespace \ + --envrc "${roxie_envrc}" \ + --tag "${EARLIER_TAG}" \ + --resources ci \ + --pause-reconciliation `# subsequent tests mangle resources and image` \ + --exposure loadbalancer \ + --set central.spec.central.db.persistence.persistentVolumeClaim.storageClassName=premium-rwo `# i.e. type: pd-ssd` \ + --set central.spec.scannerV4.db.persistence.persistentVolumeClaim.storageClassName=premium-rwo \ + "$@" + # shellcheck disable=SC1090 + source "${roxie_envrc}" + rm -f "${roxie_envrc}" ci_export "ROX_USERNAME" "$ROX_USERNAME" ci_export "ROX_ADMIN_PASSWORD" "$ROX_ADMIN_PASSWORD" } -# upgrade_central_helm_to_head applies the HEAD chart to the existing -# stackrox-central-services release so Scanner V4 is installed. kubectl set -# image does not create V4, and HEAD Central does not use leftover Scanner V2. -upgrade_central_helm_to_head() { +# upgrade_operator_to_head upgrades the operator to the HEAD version +# and resumes central reconciliation so the upgrade is processed. +upgrade_operator_to_head() { local namespace="${1:-stackrox}" local image_tag="${2:-$CURRENT_TAG}" - local registry="${3:-$REGISTRY}" - info "Upgrading central Helm release to HEAD chart with Scanner V4" + info "Upgrading central to HEAD version with Scanner V4" - if ! helm -n "$namespace" status stackrox-central-services >/dev/null 2>&1; then - die "Helm release stackrox-central-services not found in namespace ${namespace}" - fi + roxie deploy operator --verbose \ + --single-namespace \ + --tag "${image_tag}" - local chart_dir - chart_dir="$(mktemp -d)" - "$TEST_ROOT/bin/$TEST_HOST_PLATFORM/roxctl" helm output central-services \ - --image-defaults opensource \ - --output-dir "${chart_dir}" --remove - - # Retrieve the generated-values secret from installation time. - # The helm chart needs the CA stored there to sign the V4 TLS certs. - local helm_generated_values_file - helm_generated_values_file="$(mktemp)" - local generated_secrets_json - generated_secrets_json="$(kubectl -n "$namespace" get secrets -o json | jq '[.items[] - | select(.metadata.name | startswith("stackrox-generated-")) - | select(.data["generated-values.yaml"] != null)]')" - local generated_secret_count - generated_secret_count="$(jq 'length' <<<"$generated_secrets_json")" - if [[ "$generated_secret_count" -lt 1 ]]; then - die "Expected a Helm generated-values secret in ${namespace}, found 0" - fi - # Helm pre-upgrade hooks use resource-policy=keep, so extra generated-values - # secrets can exist. V4 certs must use the install-time CA in the oldest. - local helm_generated_secret_name - helm_generated_secret_name="$(jq -r 'sort_by(.metadata.creationTimestamp)[0].metadata.name' <<<"$generated_secrets_json")" - if [[ "$generated_secret_count" -gt 1 ]]; then - info "Found ${generated_secret_count} generated-values secrets; using install-time ${helm_generated_secret_name}" - fi - jq -r --arg name "$helm_generated_secret_name" \ - '.[] | select(.metadata.name == $name) | .data["generated-values.yaml"] | @base64d' \ - <<<"$generated_secrets_json" > "$helm_generated_values_file" - if [[ ! -s "$helm_generated_values_file" ]]; then - die "Helm generated-values secret ${helm_generated_secret_name} was empty" - fi - - # 4.6 installs this CRD outside Helm ownership; HEAD templates it. Helm - # refuses the upgrade until the existing CRD is adopted. - if kubectl get crd securitypolicies.config.stackrox.io >/dev/null 2>&1; then - kubectl annotate crd/securitypolicies.config.stackrox.io \ - meta.helm.sh/release-name=stackrox-central-services \ - meta.helm.sh/release-namespace="${namespace}" \ - --overwrite - kubectl label crd/securitypolicies.config.stackrox.io \ - app.kubernetes.io/managed-by=Helm \ - --overwrite - fi - - local helm_extra_args=() - if [[ -n "${SCANNER_V4_DB_STORAGE_CLASS:-}" ]]; then - if [[ "${SCANNER_V4_DB_STORAGE_CLASS}" == "faster" ]]; then - kubectl apply -f "${TEST_ROOT}/deploy/common/ssd-storageclass.yaml" - fi - helm_extra_args+=(--set "scannerV4.db.persistence.persistentVolumeClaim.storageClass=${SCANNER_V4_DB_STORAGE_CLASS}") - fi - # The image walk uses kubectl patch/set, so Helm 4 SSA will not overwrite - # those fields (central-config, CPU/memory) without --force-conflicts. - if helm upgrade --help 2>&1 | grep -q -- '--force-conflicts'; then - helm_extra_args+=(--force-conflicts) - fi - - # The chart emits the Scanner V4 DB PVC only when createClaim is true; - # this is an upgrade so .Release.IsInstall is false. # SCANNER_V4_MATCHER_READINESS=vulnerability keeps matcher unready until # the vuln DB is loaded, which wait_for_scanner_V4 then waits on. - helm -n "$namespace" upgrade --reuse-values \ - -f "$helm_generated_values_file" \ - -f - \ - "${helm_extra_args[@]}" \ - stackrox-central-services "${chart_dir}" < "${init_bundle}" + kubectl apply -n stackrox -f "${init_bundle}" + rm -f "${init_bundle}" info "Installing sensor" - ./sensor-remote/sensor.sh - kubectl -n stackrox set image deploy/sensor "*=$REGISTRY/main:$CURRENT_TAG" - kubectl -n stackrox set image deploy/admission-control "*=$REGISTRY/main:$CURRENT_TAG" - kubectl -n stackrox set image ds/collector "collector=$REGISTRY/collector:${COLLECTOR_TAG}" \ - "compliance=$REGISTRY/main:$CURRENT_TAG" + roxie deploy secured-cluster --verbose \ + --single-namespace \ + --tag "${CURRENT_TAG}" \ + --resources ci \ + --set securedCluster.spec.clusterName=remote sensor_wait - # Bounce collectors to avoid restarts on initial module pull - kubectl -n stackrox delete pod -l app=collector --grace-period=0 wait_for_central_reconciliation @@ -283,50 +286,8 @@ force_rollback_to_previous_postgres() { kubectl -n stackrox patch configmap/central-config -p "$config_patch" kubectl -n stackrox set image deploy/central "central=$REGISTRY/main:${EARLIER_TAG}" - # Do not rollback central-db image, since downgrade from PG15 to PG13 is - # not possible. -} - -deploy_scaled_workload() { - info "Deploying a scaled workload" - - PATH="bin/$TEST_HOST_PLATFORM:$PATH" roxctl version - - PATH="bin/$TEST_HOST_PLATFORM:$PATH" roxctl helm output secured-cluster-services --image-defaults opensource --output-dir /tmp/early-stackrox-secured-services-chart --remove - - PATH="bin/$TEST_HOST_PLATFORM:$PATH" roxctl -e "$API_ENDPOINT" --ca "" --insecure-skip-tls-verify \ - central init-bundles generate scale-remote --output /tmp/cluster-init-bundle.yaml - - helm install -n stackrox --create-namespace \ - stackrox-secured-cluster-services /tmp/early-stackrox-secured-services-chart \ - -f /tmp/cluster-init-bundle.yaml \ - --set system.enablePodSecurityPolicies=false \ - --set clusterName=scale-remote \ - --set image.main.tag="${EARLIER_TAG}" \ - --set image.collector.tag="${EARLIER_TAG}" \ - --set centralEndpoint="$API_ENDPOINT" - - sensor_wait - - ./scale/launch_workload.sh scale-test - - # The historical scale script requests 5 CPUs per component. Leave room for - # both scanners by reducing Central and Central DB's CPU reservations. - kubectl -n stackrox patch deploy/central --type=strategic -p \ - '{"spec":{"template":{"spec":{"containers":[{"name":"central","resources":{"requests":{"cpu":"2"}}}]}}}}' - # Init-container requests also count toward the pod's CPU reservation. - kubectl -n stackrox patch deploy/central-db --type=strategic -p \ - '{"spec":{"template":{"spec":{"containers":[{"name":"central-db","resources":{"requests":{"cpu":"2"}}}],"initContainers":[{"name":"init-db","resources":{"requests":{"cpu":"2"}}}]}}}}' - wait_for_api - - info "Sleep for a bit to let the scale build" - # shellcheck disable=SC2034 - for i in $(seq 1 150); do - echo -n . - sleep 5 - done - - info "Done with our nap for scaling" + # Keep the upgraded central-db image when rolling Central back, since the + # upgraded data directory cannot be used by an older PostgreSQL major version. } if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then diff --git a/tests/upgrade/postgres_upgrade_run.sh b/tests/upgrade/postgres_upgrade_run.sh index 510e3d584cb10..4fc0762258ab2 100755 --- a/tests/upgrade/postgres_upgrade_run.sh +++ b/tests/upgrade/postgres_upgrade_run.sh @@ -31,6 +31,9 @@ check_postgres_upgrade() { current_version="$(postgres_major_version < "$TEST_ROOT/image/postgres/Dockerfile")" || \ die "Cannot determine PostgreSQL major version for the current checkout" + EARLIER_POSTGRES_VERSION="$earlier_version" + CURRENT_POSTGRES_VERSION="$current_version" + POSTGRES_UPGRADE_REQUIRED=false if (( current_version > earlier_version )); then POSTGRES_UPGRADE_REQUIRED=true @@ -41,6 +44,27 @@ check_postgres_upgrade() { info "$POSTGRES_UPGRADE_REASON" } +check_postgres_version() { + local expected_version="$1" + local db_password actual_version + + kubectl -n stackrox rollout status deployment/central-db --timeout="${MAX_WAIT_SECONDS:-300}s" || \ + die "Central DB rollout did not complete" + + db_password="$(kubectl -n stackrox get secret central-db-password -o jsonpath='{.data.password}' | base64 -d)" || \ + die "Cannot read the Central DB password" + + # Pass the password on stdin so it does not appear in the exec command. + # shellcheck disable=SC2016 + actual_version="$(kubectl -n stackrox exec -i deploy/central-db -c central-db -- \ + bash -c 'PGPASSWORD="$(cat)" \ + psql -X -w -h 127.0.0.1 -U postgres -d postgres -v ON_ERROR_STOP=1 -Atc "SHOW server_version"' <<< "$db_password")" || \ + die "Cannot query the running PostgreSQL version" + + info "Central DB is running PostgreSQL $actual_version; expected major version $expected_version" + test_equals_non_silent "${actual_version%%.*}" "$expected_version" +} + test_upgrade() { info "Starting postgres upgrade test" @@ -56,18 +80,13 @@ test_upgrade() { # repo for old version with legacy database REPO_FOR_TIME_TRAVEL="/tmp/rox-postgres-upgrade-test" - DEPLOY_DIR="deploy/k8s" - QUAY_REPO="stackrox-io" + QUAY_REPO="rhacs-eng" REGISTRY="quay.io/$QUAY_REPO" - export OUTPUT_FORMAT="helm" - export STORAGE="pvc" export CLUSTER_TYPE_FOR_TEST=K8S if is_CI; then export ROXCTL_IMAGE_REPO="quay.io/$QUAY_REPO/roxctl" - require_environment "LONGTERM_LICENSE" - export ROX_LICENSE_KEY="${LONGTERM_LICENSE}" fi preamble @@ -79,7 +98,7 @@ test_upgrade() { test_upgrade_path "$log_output_dir" - remove_existing_stackrox_resources + roxie teardown both --verbose --single-namespace test_not_enough_disk_space "$log_output_dir" } @@ -103,12 +122,20 @@ test_upgrade_path() { export MAX_WAIT_SECONDS=600 ######################################################################################## - # Use roxctl to generate helm files and deploy older central # + # Use roxie to deploy older central # ######################################################################################## - deploy_earlier_postgres_central + deploy_earlier_tag_central wait_for_api + check_postgres_version "$EARLIER_POSTGRES_VERSION" setup_client_TLS_certs + # Older checkouts force GOTOOLCHAIN=local during dependency checks. + # Put the toolchain selected for this checkout first on PATH for the build. + # Needed for restore_backup to find roxctl. + local build_goroot + build_goroot="$(go env GOROOT)" + PATH="${build_goroot}/bin:${PATH}" make cli + # It's damn fiddly, restore is needed because later test will search for a # default secured cluster, created by it :( restore_backup @@ -145,11 +172,12 @@ test_upgrade_path() { # Extend the MUTEX timeout for this case as a restart of the db will cause locks to be held longer as it should kubectl -n stackrox set env deploy/central MUTEX_WATCHDOG_TIMEOUT_SECS=600 - # Upgrade the image to PG15 + # Upgrade the image to PG16 info "Upgrade ${EARLIER_TAG} => ${CURRENT_TAG}" kubectl -n stackrox set image deploy/central "*=${REGISTRY}/main:${CURRENT_TAG}" kubectl -n stackrox set image deploy/central-db "*=${REGISTRY}/central-db:${CURRENT_TAG}" wait_for_api + check_postgres_version "$CURRENT_POSTGRES_VERSION" ######################################################################################## # Bounce central to ensure everything starts back up. # @@ -158,8 +186,6 @@ test_upgrade_path() { kubectl -n stackrox delete po "$(kubectl -n stackrox get po -l app=central -o=jsonpath='{.items[0].metadata.name}')" --grace-period=0 wait_for_api sensor_wait - # Bounce collectors to avoid restarts on initial module pull - kubectl -n stackrox delete pod -l app=collector --grace-period=0 # Verify data is still there checkForPostgresAccessScopes @@ -177,6 +203,7 @@ test_upgrade_path() { kubectl -n stackrox delete po "$(kubectl -n stackrox get po -l app=central-db -o=jsonpath='{.items[0].metadata.name}')" --grace-period=0 wait_for_api wait_for_central_db + check_postgres_version "$CURRENT_POSTGRES_VERSION" # Verify data is still there checkForPostgresAccessScopes @@ -215,10 +242,10 @@ test_not_enough_disk_space() { ######################################################################################## # Deploy older central using small PVC to face disk space shortage # ######################################################################################## - PVC_SIZE="1Gi" - deploy_earlier_postgres_central - unset PVC_SIZE + deploy_earlier_tag_central \ + --set central.spec.central.db.persistence.persistentVolumeClaim.size=1Gi wait_for_api + check_postgres_version "$EARLIER_POSTGRES_VERSION" setup_client_TLS_certs # It's damn fiddly, restore is needed because later test will search for a @@ -263,7 +290,7 @@ test_not_enough_disk_space() { # Extend the MUTEX timeout for this case as a restart of the db will cause locks to be held longer as it should kubectl -n stackrox set env deploy/central MUTEX_WATCHDOG_TIMEOUT_SECS=600 - # Upgrade the image to PG15 + # Upgrade the image to PG16 info "Upgrade ${EARLIER_TAG} => ${CURRENT_TAG}" kubectl -n stackrox set image \ deploy/central "*=${REGISTRY}/main:${CURRENT_TAG}" @@ -276,92 +303,17 @@ test_not_enough_disk_space() { '{"spec": {"resources": {"requests": {"storage": "4Gi"}}}}' kubectl -n stackrox rollout restart deployment/central-db wait_for_api + check_postgres_version "$CURRENT_POSTGRES_VERSION" # Make sure we can restore from a physical backup and run with old binaries # after upgrade if needed. - kubectl -n stackrox set env deploy/central-db RESTORE_BACKUP=true - kubectl -n stackrox set env deploy/central-db FORCE_OLD_BINARIES=true + kubectl -n stackrox set env deploy/central-db RESTORE_BACKUP=true FORCE_OLD_BINARIES=true wait_for_api + check_postgres_version "$EARLIER_POSTGRES_VERSION" collect_and_check_stackrox_logs "$log_output_dir" "disk_space_01_final" } -force_rollback_to_previous_postgres() { - info "Forcing a rollback to ${EARLIER_TAG}" - - local upgradeStatus - upgradeStatus=$(curl -sSk -X GET --config <(curl_cfg user "admin:${ROX_ADMIN_PASSWORD}") https://"${API_ENDPOINT}"/v1/centralhealth/upgradestatus) - echo "upgrade status: ${upgradeStatus}" - test_equals_non_silent "$(echo "$upgradeStatus" | jq '.upgradeStatus.version' -r)" "${CURRENT_TAG}" - test_equals_non_silent "$(echo "$upgradeStatus" | jq '.upgradeStatus.canRollbackAfterUpgrade' -r)" "true" - - kubectl -n stackrox get configmap/central-config -o yaml | yq e '{"data": .data}' - >/tmp/force_rollback_patch - local central_config - central_config=$(yq e '.data["central-config.yaml"]' /tmp/force_rollback_patch | yq e ".maintenance.forceRollbackVersion = \"${EARLIER_TAG}\"" -) - local config_patch - config_patch=$(yq e ".data[\"central-config.yaml\"] |= \"$central_config\"" /tmp/force_rollback_patch) - echo "config patch: $config_patch" - - # downgrading to a version that does not understand process listening on ports - # so turning that off in sensor and collector to prevent central crashes. - # Sensor and Collector will be deleted a few steps after this so no need - # to turn these back on. Going forward unexpected messages will result in - # an `UNEXPECTED` log instead of crashing central. However that change is - # not present in the initial 3.74 version. - kubectl -n stackrox set env deploy/sensor ROX_PROCESSES_LISTENING_ON_PORT=false - - kubectl -n stackrox patch configmap/central-config -p "$config_patch" - kubectl -n stackrox set image deploy/central "central=$REGISTRY/main:${EARLIER_TAG}" - - # Do not rollback central-db image, since downgrade from PG15 to PG13 is - # not possible. -} - -deploy_scaled_workload() { - info "Deploying a scaled workload" - WAIT_ITERATIONS="${1:-150}" - - PATH="bin/$TEST_HOST_PLATFORM:$PATH" roxctl version - - PATH="bin/$TEST_HOST_PLATFORM:$PATH" roxctl helm output secured-cluster-services --image-defaults opensource --output-dir /tmp/early-stackrox-secured-services-chart --remove - - # Make sure no init bundle from previous runs is there - rm -f /tmp/cluster-init-bundle.yaml - PATH="bin/$TEST_HOST_PLATFORM:$PATH" roxctl -e "$API_ENDPOINT" --ca "" --insecure-skip-tls-verify \ - central init-bundles generate scale-remote --output /tmp/cluster-init-bundle.yaml - - helm install -n stackrox --create-namespace \ - stackrox-secured-cluster-services /tmp/early-stackrox-secured-services-chart \ - -f /tmp/cluster-init-bundle.yaml \ - --set system.enablePodSecurityPolicies=false \ - --set clusterName=scale-remote \ - --set image.main.tag="${EARLIER_TAG}" \ - --set image.collector.tag="${EARLIER_TAG}" \ - --set centralEndpoint="$API_ENDPOINT" - - sensor_wait - - ./scale/launch_workload.sh scale-test - - # The historical scale script requests 5 CPUs per component. Leave room for - # both scanners by reducing Central and Central DB's CPU reservations. - kubectl -n stackrox patch deploy/central --type=strategic -p \ - '{"spec":{"template":{"spec":{"containers":[{"name":"central","resources":{"requests":{"cpu":"2"}}}]}}}}' - # Init-container requests also count toward the pod's CPU reservation. - kubectl -n stackrox patch deploy/central-db --type=strategic -p \ - '{"spec":{"template":{"spec":{"containers":[{"name":"central-db","resources":{"requests":{"cpu":"2"}}}],"initContainers":[{"name":"init-db","resources":{"requests":{"cpu":"2"}}}]}}}}' - wait_for_api - - info "Sleep for a bit to let the scale build" - # shellcheck disable=SC2034 - for i in $(seq 1 $WAIT_ITERATIONS); do - echo -n . - sleep 5 - done - - info "Done with our nap for scaling" -} - if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then check_postgres_upgrade if [[ "${1:-}" == "--check" ]]; then diff --git a/tests/versions_test.go b/tests/versions_test.go index b5adb45c0f659..29eafba439517 100644 --- a/tests/versions_test.go +++ b/tests/versions_test.go @@ -5,6 +5,7 @@ package tests import ( "encoding/json" "net/http" + "strings" "testing" "github.com/stackrox/rox/pkg/testutils/centralgrpc" @@ -26,8 +27,13 @@ func TestVersions(t *testing.T) { var versions version.Versions require.NoError(t, json.NewDecoder(resp.Body).Decode(&versions)) - kind := version.GetVersionKind(versions.MainVersion) - require.NotEqualf(t, version.InvalidKind, kind, "invalid main version %s", versions.MainVersion) + mainVersion := versions.MainVersion + if usingKonfluxImages() { + mainVersion = strings.TrimSuffix(mainVersion, "-fast") + } + + kind := version.GetVersionKind(mainVersion) + require.NotEqualf(t, version.InvalidKind, kind, "invalid main version %s", mainVersion) if kind == version.DevelopmentKind || kind == version.NightlyKind { t.Skip("nothing to be checked on development versions") } diff --git a/tests/vm_scanning_suite_test.go b/tests/vm_scanning_suite_test.go index c2266513aa0cd..f529470ab7e9b 100644 --- a/tests/vm_scanning_suite_test.go +++ b/tests/vm_scanning_suite_test.go @@ -200,9 +200,9 @@ func (s *VMScanningSuite) TearDownSuite() { defer s.cancel() } - // When VM_SCAN_SKIP_CLEANUP is set, leave VMs and the namespace intact so a - // developer can SSH into the guests or inspect cluster state after a failure - // without having to re-provision from scratch. + // When VM_SCAN_SKIP_CLEANUP is set, skip VM/namespace deletion here so CI + // post-test can collect guest journals first; local runs use this to debug + // without re-provisioning. if s.cfg != nil && s.cfg.SkipCleanup { s.logf("teardown: VM_SCAN_SKIP_CLEANUP is set — skipping VM and namespace deletion (VMs and namespace left intact for debugging)") s.closeConn() diff --git a/tools/generate-helpers/config-as-code-helper/main.go b/tools/generate-helpers/config-as-code-helper/main.go index cba3d849424c0..5b69d7f5b28de 100644 --- a/tools/generate-helpers/config-as-code-helper/main.go +++ b/tools/generate-helpers/config-as-code-helper/main.go @@ -83,19 +83,31 @@ func generateTemplateData(t reflect.Type, visited set.StringSet) []ConvertType { continue } + if _, ok := field.Tag.Lookup("protobuf_oneof"); ok { + continue + } + fieldType := field.Type.String() isSlice := strings.HasPrefix(fieldType, "[]") isStringer := strings.Contains(yamlTag, ",stringer") if isStringer { yamlTag = strings.Replace(yamlTag, ",stringer", "", 1) fieldType = utils.IfThenElse(isSlice, "[]string", "string") + needUpdate = true } isTimeStamp := strings.Contains(yamlTag, ",timestamp") if isTimeStamp { yamlTag = strings.Replace(yamlTag, ",timestamp", "", 1) fieldType = utils.IfThenElse(isSlice, "[]string", "string") + needUpdate = true } + // A stringer or timestamp field is rewritten as a string. A message that + // contains only those fields still needs its own converted type, otherwise + // the parent embeds the proto and YAML uses the Go field name and wire value. + if isStringer || isTimeStamp { + needUpdate = true + } needConversion := !isStringer && !isTimeStamp && checkForConversion(field) if needConversion { if !visited.Contains(field.Name) { @@ -120,6 +132,41 @@ func generateTemplateData(t reflect.Type, visited set.StringSet) []ConvertType { NeedConversion: needConversion, }) } + + if typeName == "Exclusion" { + // Oneof arms are not exported fields on Exclusion (Matcher is an + // interface). Flatten them using the same crYaml tags as the proto. + for _, extra := range []struct { + name string + yamlTag string + typ reflect.Type + }{ + {"Deployment", ",omitempty", reflect.TypeFor[*storage.Exclusion_Deployment]()}, + {"ExcludeByKind", "excludeByKind,omitempty", reflect.TypeFor[*storage.Exclusion_ExcludeByKind]()}, + } { + fieldType := extra.typ.String() + needConversion := protoreflect.IsProtoMessage(getBaseType(extra.typ)) + if needConversion { + if !visited.Contains(extra.name) { + newList := generateTemplateData(getBaseType(extra.typ), visited) + conversionList = append(conversionList, newList...) + if len(newList) == 0 { + needConversion = false + } + } + needUpdate = true + } + fields = append(fields, Field{ + Name: extra.name, + Type: fieldType, + TrimmedType: strings.Replace(fieldType, "storage.", "", 1), + BaseType: getBaseType(extra.typ).Name(), + YamlTag: extra.yamlTag, + NeedConversion: needConversion, + }) + } + } + if needUpdate { conversionList = append(conversionList, ConvertType{ Fields: fields, diff --git a/tools/generate-helpers/config-as-code-helper/policy.go.tpl b/tools/generate-helpers/config-as-code-helper/policy.go.tpl index 2f012b7c93f76..67f64ad0baeaa 100644 --- a/tools/generate-helpers/config-as-code-helper/policy.go.tpl +++ b/tools/generate-helpers/config-as-code-helper/policy.go.tpl @@ -27,9 +27,9 @@ func convert{{.TypeName}}(p *storage.{{.TypeName}}) *{{.TypeName}} { return &{{.TypeName}}{ {{- range .Fields }} - {{- $fieldName := printf "p.%s" .Name }} + {{- $fieldName := printf "p.Get%s()" .Name }} {{- if .IsTimestamp }} - {{- $fieldName = printf "timestampToFormatRFC3339(%s)" $fieldName }} + {{- $fieldName = printf "timestampToFormatRFC3339(p.Get%s())" .Name }} {{- end }} {{ .Name }}: {{- if .IsStringer -}} diff --git a/tools/generate-helpers/release-versions/main.go b/tools/generate-helpers/release-versions/main.go new file mode 100644 index 0000000000000..22fb7efc69d08 --- /dev/null +++ b/tools/generate-helpers/release-versions/main.go @@ -0,0 +1,308 @@ +// release-versions generates initial-release database sequences from Git tags. +package main + +import ( + "bytes" + "errors" + "flag" + "fmt" + "go/ast" + "go/format" + "go/parser" + "go/token" + "maps" + "os" + "os/exec" + "path/filepath" + "regexp" + "slices" + "strconv" + "strings" + + "github.com/stackrox/rox/pkg/version/productstreams" +) + +const sequencePath = "pkg/migrations/internal/seq_num.go" + +var initialRelease = regexp.MustCompile(`^[1-9][0-9]*\.(0|[1-9][0-9]*)\.0(-rc\.(0|[1-9][0-9]*))?$`) + +func main() { + target := flag.String("target", "", "target version (defaults to make tag)") + flag.Parse() + if err := run(*target); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } +} + +func command(dir, name string, args ...string) (string, error) { + cmd := exec.Command(name, args...) + cmd.Dir = dir + var stderr bytes.Buffer + cmd.Stderr = &stderr + data, err := cmd.Output() + if err != nil { + return "", fmt.Errorf("%s %v: %w: %s", name, args, err, stderr.String()) + } + return string(data), nil +} + +func run(target string) error { + root, err := command("", "git", "rev-parse", "--show-toplevel") + if err != nil { + return fmt.Errorf("find repository root: %w", err) + } + dir := strings.TrimSpace(root) + if target == "" { + data, err := command(dir, "make", "--quiet", "--no-print-directory", "tag") + if err != nil { + return fmt.Errorf("determine target version with make tag: %w", err) + } + target = strings.TrimSpace(data) + } + data, err := generate(dir, target) + if err != nil { + return fmt.Errorf("generate release versions: %w", err) + } + outputPath := filepath.Join(dir, "pkg/migrations/release_versions.go") + if err := os.WriteFile(outputPath, data, 0644); err != nil { + return fmt.Errorf("write generated release versions to %s: %w", outputPath, err) + } + return nil +} + +func generate(dir, target string) ([]byte, error) { + targetXy, err := parseTargetStream(target) + if err != nil { + return nil, err + } + tags, err := listReleaseTags(dir) + if err != nil { + return nil, err + } + releaseTags, err := selectReleaseTags(tags, targetXy) + if err != nil { + return nil, err + } + sequences, err := readReleaseSequences(dir, releaseTags) + if err != nil { + return nil, err + } + if err := ensureTargetSequence(dir, target, targetXy, sequences); err != nil { + return nil, err + } + streams, err := validateReleaseSequences(target, targetXy, sequences) + if err != nil { + return nil, err + } + return renderReleaseVersions(streams, sequences) +} + +func parseTargetStream(target string) (productstreams.XYVersion, error) { + targetXy, err := productstreams.ParseXYFromVersionString(target) + if err != nil { + return productstreams.XYVersion{}, fmt.Errorf("parse target stream from %q: %w", target, err) + } + if err := validateReleaseStream(targetXy); err != nil { + return productstreams.XYVersion{}, fmt.Errorf("validate target stream %s: %w", targetXy, err) + } + return targetXy, nil +} + +func listReleaseTags(dir string) (string, error) { + shallow, err := command(dir, "git", "rev-parse", "--is-shallow-repository") + if err != nil { + return "", fmt.Errorf("check whether checkout is shallow: %w", err) + } + if strings.TrimSpace(shallow) != "false" { + return "", errors.New("release generation requires a full checkout with release tags") + } + tags, err := command(dir, "git", "tag", "--list", + "[0-9]*.[0-9]*.0", + "[0-9]*.[0-9]*.0-rc.[0-9]*", + ) + if err != nil { + return "", fmt.Errorf("list release tags: %w", err) + } + return tags, nil +} + +func selectReleaseTags(tags string, targetXy productstreams.XYVersion) (map[productstreams.XYVersion]string, error) { + releaseTags := make(map[productstreams.XYVersion]string) + for tag := range strings.FieldsSeq(tags) { + if !initialRelease.MatchString(tag) { + continue + } + stream, err := productstreams.ParseXYFromVersionString(tag) + if err != nil { + return nil, fmt.Errorf("parse release stream from tag %q: %w", tag, err) + } + if stream.Compare(productstreams.XYVersion{X: 4, Y: 4}) < 0 || stream.Compare(targetXy) > 0 { + continue + } + if err := validateReleaseStream(stream); err != nil { + return nil, fmt.Errorf("validate historical release stream %s: %w", stream, err) + } + selected, exists := releaseTags[stream] + if !exists { + releaseTags[stream] = tag + continue + } + newer, err := newerInitialRelease(tag, selected) + if err != nil { + return nil, err + } + if newer { + releaseTags[stream] = tag + } + } + return releaseTags, nil +} + +func readReleaseSequences(dir string, releaseTags map[productstreams.XYVersion]string) (map[productstreams.XYVersion]int, error) { + sequences := make(map[productstreams.XYVersion]int) + for stream, tag := range releaseTags { + data, err := command(dir, "git", "show", "refs/tags/"+tag+":"+sequencePath) + if err != nil { + return nil, fmt.Errorf("read %s from release tag %s: %w", sequencePath, tag, err) + } + sequence, err := parseSequence(data) + if err != nil { + return nil, fmt.Errorf("release %s: %w", tag, err) + } + sequences[stream] = sequence + } + return sequences, nil +} + +// ensureTargetSequence adds a working-tree baseline only when no tag baseline exists. +// The target must have been validated by parseTargetStream. +func ensureTargetSequence(dir, target string, targetXy productstreams.XYVersion, sequences map[productstreams.XYVersion]int) error { + if _, exists := sequences[targetXy]; exists { + return nil + } + patch, _, _ := strings.Cut(strings.Split(target, ".")[2], "-") + if patch != "0" && patch != "x" { + return fmt.Errorf("missing initial release tag %s.0 for patch target %s", targetXy, target) + } + data, err := os.ReadFile(filepath.Join(dir, sequencePath)) + if err != nil { + return fmt.Errorf("read current database sequence from %s: %w", sequencePath, err) + } + sequence, err := parseSequence(string(data)) + if err != nil { + return fmt.Errorf("parse current database sequence from %s: %w", sequencePath, err) + } + sequences[targetXy] = sequence + return nil +} + +func validateReleaseSequences(target string, targetXy productstreams.XYVersion, sequences map[productstreams.XYVersion]int) ([]productstreams.XYVersion, error) { + stream := targetXy + for i := range 4 { + if err := validateReleaseStream(stream); err != nil { + return nil, fmt.Errorf("validate release stream %s required by %s: %w", stream, target, err) + } + // Every supported stream needs a baseline; RC-only streams count too. + if _, exists := sequences[stream]; !exists { + return nil, fmt.Errorf("missing initial release tag %s.0 required by %s; fetch release tags before generation", stream, target) + } + if i == 3 { + break + } + var err error + stream, err = productstreams.GetPreviousYStream(stream) + if err != nil { + return nil, fmt.Errorf("find minimum supported stream for %s: %w", target, err) + } + } + streams := slices.Collect(maps.Keys(sequences)) + slices.SortFunc(streams, productstreams.XYVersion.Compare) + last := 0 + for _, stream := range streams { + sequence := sequences[stream] + if sequence < last { + return nil, fmt.Errorf("release %s sequence %d is less than previous sequence %d", stream, sequence, last) + } + last = sequence + } + return streams, nil +} + +// renderReleaseVersions expects sorted streams returned by validateReleaseSequences. +func renderReleaseVersions(streams []productstreams.XYVersion, sequences map[productstreams.XYVersion]int) ([]byte, error) { + var output bytes.Buffer + fmt.Fprintln(&output, `// Code generated by release-versions. DO NOT EDIT. + +package migrations + +var releaseVersions = []ReleaseVersion{`) + for _, stream := range streams { + sequence := sequences[stream] + fmt.Fprintf(&output, "{Version: %q, Sequence: %d},\n", stream.String(), sequence) + } + fmt.Fprintln(&output, "}") + formatted, err := format.Source(output.Bytes()) + if err != nil { + return nil, fmt.Errorf("format generated release versions: %w", err) + } + return formatted, nil +} + +func validateReleaseStream(stream productstreams.XYVersion) error { + previous, err := productstreams.GetPreviousYStream(productstreams.GetNextYStream(stream)) + if err != nil { + return err + } + if previous != stream { + return fmt.Errorf("invalid release stream %s", stream) + } + return nil +} + +// GA supersedes RCs; otherwise select the highest numeric RC ordinal. +func newerInitialRelease(candidate, selected string) (bool, error) { + _, candidateRC, candidateIsRC := strings.Cut(candidate, "-rc.") + _, selectedRC, selectedIsRC := strings.Cut(selected, "-rc.") + if !candidateIsRC { + return true, nil + } + if !selectedIsRC { + return false, nil + } + candidateOrdinal, err := strconv.Atoi(candidateRC) + if err != nil { + return false, fmt.Errorf("parse RC ordinal from %q: %w", candidate, err) + } + selectedOrdinal, err := strconv.Atoi(selectedRC) + if err != nil { + return false, fmt.Errorf("parse RC ordinal from %q: %w", selected, err) + } + return candidateOrdinal > selectedOrdinal, nil +} + +func parseSequence(data string) (int, error) { + file, err := parser.ParseFile(token.NewFileSet(), "seq_num.go", data, 0) + if err != nil { + return 0, fmt.Errorf("parse seq_num.go: %w", err) + } + sequence := 0 + ast.Inspect(file, func(node ast.Node) bool { + decl, ok := node.(*ast.ValueSpec) + if !ok { + return true + } + for i, name := range decl.Names { + if name.Name == "CurrentDBVersionSeqNum" && i < len(decl.Values) { + if literal, ok := decl.Values[i].(*ast.BasicLit); ok && literal.Kind == token.INT { + sequence, _ = strconv.Atoi(literal.Value) + } + } + } + return true + }) + if sequence <= 0 { + return 0, errors.New("missing positive CurrentDBVersionSeqNum in seq_num.go") + } + return sequence, nil +} diff --git a/tools/generate-helpers/release-versions/main_test.go b/tools/generate-helpers/release-versions/main_test.go new file mode 100644 index 0000000000000..3a8c2794b7844 --- /dev/null +++ b/tools/generate-helpers/release-versions/main_test.go @@ -0,0 +1,622 @@ +package main + +import ( + "fmt" + "os" + "os/exec" + "path/filepath" + "strings" + "testing" + + "github.com/stackrox/rox/pkg/version/productstreams" + "github.com/stretchr/testify/require" +) + +func repository(t *testing.T) (string, func(...string), func(int, string)) { + t.Helper() + dir := t.TempDir() + git := func(args ...string) { + t.Helper() + cmd := exec.Command("git", args...) + cmd.Dir = dir + output, err := cmd.CombinedOutput() + require.NoError(t, err, "%s", output) + } + git("init", "-q", "--initial-branch=master") + git("config", "core.hooksPath", "/dev/null") + git("config", "user.email", "test@example.com") + git("config", "user.name", "Test") + git("config", "commit.gpgsign", "false") + git("config", "tag.gpgsign", "false") + require.NoError(t, os.MkdirAll(filepath.Join(dir, "pkg/migrations/internal"), 0755)) + write := func(sequence int, tag string) { + t.Helper() + require.NoError(t, os.WriteFile(filepath.Join(dir, sequencePath), []byte(fmt.Sprintf("package internal\nvar CurrentDBVersionSeqNum int = %d\n", sequence)), 0644)) + git("add", sequencePath) + git("commit", "-qm", "sequence", "--allow-empty") + if tag != "" { + git("tag", tag) + } + } + return dir, git, write +} + +func TestDevelopmentBaseline(t *testing.T) { + dir, git, write := repository(t) + write(220, "4.10.0") + write(225, "4.11.0") + write(226, "5.0.0") + write(227, "5.1.x") + before, err := generate(dir, "5.1.x-105-g66fd4ac964") + require.NoError(t, err) + require.Contains(t, string(before), `Version: "5.1", Sequence: 227`) + write(228, "") + before, err = generate(dir, "5.1.x-106-gabcdef-dirty") + require.NoError(t, err) + require.Contains(t, string(before), `Version: "5.1", Sequence: 228`) + git("tag", "5.1.0") + after, err := generate(dir, "5.1.0") + require.NoError(t, err) + require.Equal(t, before, after) + write(229, "5.1.1") + after, err = generate(dir, "5.1.1") + require.NoError(t, err) + require.Equal(t, before, after) + write(240, "5.2.0") + after, err = generate(dir, "5.1.1") + require.NoError(t, err) + require.Equal(t, before, after) +} + +func TestHistoricalPatchMigration(t *testing.T) { + dir, _, write := repository(t) + write(205, "4.5.0") + write(206, "4.5.1") + write(209, "4.6.0") + write(209, "4.7.0") + write(211, "4.8.x") + data, err := generate(dir, "4.8.x") + require.NoError(t, err) + require.Contains(t, string(data), `Version: "4.5", Sequence: 205`) + require.NotContains(t, string(data), "206") +} + +func TestGenerationFailures(t *testing.T) { + for name, tc := range map[string]struct { + target, tag, source, want string + completeHistory bool + }{ + "missing floor": {target: "5.1.x", want: "4.10.0", completeHistory: true}, + "nightly is not release": {target: "5.1.x", tag: "4.10.0-nightly-20260930", want: "4.10.0", completeHistory: true}, + "missing patch baseline": {target: "5.1.1", tag: "4.10.0", want: "5.1.0", completeHistory: true}, + "patch RC missing baseline": {target: "5.1.1-rc.0", tag: "4.10.0", want: "5.1.0", completeHistory: true}, + "patch nightly missing baseline": {target: "5.1.1-nightly-20260930", tag: "4.10.0", want: "5.1.0", completeHistory: true}, + "invalid target": {target: "garbage", want: "parse target stream"}, + "phantom target": {target: "4.12.x", want: "stream"}, + "malformed sequence": {target: "5.1.x", tag: "4.10.0", source: "package internal\nvar CurrentDBVersionSeqNum = 0", want: "positive"}, + } { + t.Run(name, func(t *testing.T) { + dir, _, write := repository(t) + write(220, tc.tag) + if tc.completeHistory { + write(225, "4.11.0") + write(226, "5.0.0") + } + if tc.source != "" { + require.NoError(t, os.WriteFile(filepath.Join(dir, sequencePath), []byte(tc.source), 0644)) + } + _, err := generate(dir, tc.target) + require.ErrorContains(t, err, tc.want) + }) + } +} + +func TestPendingStreams(t *testing.T) { + for name, tc := range map[string]struct { + floor, latest, pending, target string + withRC bool + }{ + "major boundary RC-only": {"4.10", "4.11", "5.0", "5.1.x", true}, + "future development": {"5.3", "5.4", "5.5", "5.6.x", true}, + "RC target": {"5.3", "5.4", "5.5", "5.6.0-rc.1", true}, + "no RC tags": {"5.3", "5.4", "5.5", "5.6.x", false}, + } { + t.Run(name, func(t *testing.T) { + dir, _, write := repository(t) + write(220, tc.floor+".0") + write(225, tc.latest+".0") + if tc.withRC { + write(226, tc.pending+".0-rc.1") + } + write(227, "") + data, err := generate(dir, tc.target) + if !tc.withRC { + require.ErrorContains(t, err, tc.pending+".0") + return + } + require.NoError(t, err) + require.Contains(t, string(data), fmt.Sprintf(`Version: %q, Sequence: 220`, tc.floor)) + require.Contains(t, string(data), fmt.Sprintf(`Version: %q, Sequence: 225`, tc.latest)) + require.Contains(t, string(data), fmt.Sprintf(`Version: %q, Sequence: 226`, tc.pending)) + require.Contains(t, string(data), fmt.Sprintf(`Version: %q, Sequence: 227`, strings.Join(strings.Split(tc.target, ".")[:2], "."))) + }) + } +} + +func TestMissingIntermediateRelease(t *testing.T) { + for name, rc := range map[string]bool{"no tags": false, "old RC tags": true} { + t.Run(name, func(t *testing.T) { + dir, _, write := repository(t) + write(220, "5.3.0") + if rc { + write(221, "5.4.0-rc.1") + } + write(225, "5.5.0") + write(227, "") + _, err := generate(dir, "5.6.x") + if rc { + require.NoError(t, err) + } else { + require.ErrorContains(t, err, "missing initial release tag 5.4.0") + } + }) + } +} + +func TestPendingStreamLifecycle(t *testing.T) { + dir, _, write := repository(t) + write(220, "5.3.0") + write(225, "5.4.0") + write(226, "5.5.0-rc.1") + write(230, "") + before, err := generate(dir, "5.6.x") + require.NoError(t, err) + require.Contains(t, string(before), `Version: "5.5", Sequence: 226`) + write(228, "5.5.0") + write(230, "") + baseline, err := generate(dir, "5.6.x") + require.NoError(t, err) + require.Contains(t, string(baseline), `Version: "5.5", Sequence: 228`) + require.Contains(t, string(baseline), `Version: "5.6", Sequence: 230`) + for _, tag := range []string{"5.5.1-rc.1", "5.5.1"} { + write(229, tag) + write(230, "") + after, err := generate(dir, "5.6.x") + require.NoError(t, err) + require.Equal(t, baseline, after) + } +} + +func TestGenerationCommand(t *testing.T) { + dir, git, write := repository(t) + write(220, "4.10.0") + write(225, "4.11.0") + write(226, "5.0.0") + write(227, "5.1.x") + versionMakefile(t, dir) + t.Chdir(dir) + t.Setenv("BUILD_TAG", "") + require.NoError(t, run("")) + output := filepath.Join(dir, "pkg/migrations/release_versions.go") + before, err := os.ReadFile(output) + require.NoError(t, err) + require.Contains(t, string(before), `Version: "5.1", Sequence: 227`) + git("tag", "5.1.0") + t.Setenv("BUILD_TAG", "5.1.0") + require.NoError(t, run("")) + after, err := os.ReadFile(output) + require.NoError(t, err) + require.Equal(t, before, after) + t.Setenv("BUILD_TAG", "5.5.x") + require.ErrorContains(t, run(""), "5.4.0") + after, err = os.ReadFile(output) + require.NoError(t, err) + require.Equal(t, before, after, "failed generation must not overwrite the output") + require.NoError(t, run("5.1.0"), "explicit target takes precedence over BUILD_TAG") +} + +func versionMakefile(t *testing.T, dir string) { + t.Helper() + require.NoError(t, os.MkdirAll(filepath.Join(dir, "make"), 0755)) + for _, name := range []string{"env.mk", "goproxy.mk"} { + data, err := os.ReadFile(filepath.Join("../../../make", name)) + require.NoError(t, err) + require.NoError(t, os.WriteFile(filepath.Join(dir, "make", name), data, 0644)) + } + require.NoError(t, os.WriteFile(filepath.Join(dir, "Makefile"), []byte("include make/env.mk\ntag:\n\t@echo $(TAG)\n"), 0644)) +} + +func TestReleaseBranchLifecycle(t *testing.T) { + dir, git, write := repository(t) + versionMakefile(t, dir) + t.Chdir(dir) + t.Setenv("BUILD_TAG", "") + write(220, "4.10.0") + write(225, "4.11.0") + write(227, "5.0.0") + git("tag", "-a", "5.1.x", "-m", "development") + write(228, "") + git("checkout", "-qb", "release-5.1") + write(228, "") + git("tag", "-a", "5.1.0-rc.0", "-m", "release candidate") + git("checkout", "master") + write(230, "") + git("tag", "-a", "5.2.x", "-m", "next development stream") + git("checkout", "release-5.1") + write(229, "") + git("tag", "-a", "5.1.0-rc.1", "-m", "release candidate with migration") + + readOutput := func() []byte { + t.Helper() + require.NoError(t, run("")) + data, err := os.ReadFile("pkg/migrations/release_versions.go") + require.NoError(t, err) + return data + } + before := readOutput() + require.Contains(t, string(before), `Version: "5.1", Sequence: 229`) + require.NotContains(t, string(before), `Version: "5.2"`) + git("tag", "-a", "5.1.0", "-m", "GA") + git("branch", "5.1.0", "master") // A branch name must not shadow the GA tag. + require.Equal(t, before, readOutput()) + git("checkout", "--detach", "refs/tags/5.1.0") + require.Equal(t, before, readOutput(), "detached release CI checkout") + git("checkout", "release-5.1") + write(231, "") + git("tag", "-a", "5.1.1-rc.0", "-m", "patch migration") + require.Equal(t, before, readOutput()) + git("tag", "-a", "5.1.1", "-m", "patch GA") + require.Equal(t, before, readOutput()) + + git("checkout", "master") + write(240, "") + git("tag", "-a", "5.2.0", "-m", "newer GA") + after := readOutput() + require.Contains(t, string(after), `Version: "5.1", Sequence: 229`, "GA on another branch is authoritative") + require.Contains(t, string(after), `Version: "5.2", Sequence: 240`) + git("checkout", "release-5.1") + require.Equal(t, before, readOutput(), "newer release tags must not change old branch output") +} + +func TestMultipleReleaseBranchesWithRCs(t *testing.T) { + dir, git, write := repository(t) + write(220, "4.10.0") + git("checkout", "-qb", "release-4.10") + git("checkout", "master") + write(225, "4.11.0") + git("checkout", "-qb", "release-4.11") + git("checkout", "master") + write(227, "5.0.0-rc.0") + write(228, "5.1.x") + + before, err := generate(dir, "5.1.x") + require.NoError(t, err) + require.Contains(t, string(before), `Version: "4.10", Sequence: 220`) + require.Contains(t, string(before), `Version: "4.11", Sequence: 225`) + require.Contains(t, string(before), `Version: "5.1", Sequence: 228`) + require.Contains(t, string(before), `Version: "5.0", Sequence: 227`) + + // Each release line can advance independently while RCs are in flight. + git("checkout", "release-4.10") + write(221, "4.10.10-rc.1") + git("checkout", "release-4.11") + for sequence, tag := range []string{ + "4.11.1-rc.0", + "4.11.1-rc.1", + "4.11.1-rc.2", + "4.11.1-rc.3", + "4.11.1-rc.4", + } { + write(226+sequence, tag) + } + git("checkout", "-qb", "release-5.0", "refs/tags/4.11.0") + write(227, "5.0.0-rc.2") + git("checkout", "master") + + after, err := generate(dir, "5.1.x") + require.NoError(t, err) + require.Equal(t, before, after, "patch RCs must not change baselines, and an unchanged initial RC sequence is stable") +} + +func TestNightlyVersionDiscovery(t *testing.T) { + dir, git, write := repository(t) + versionMakefile(t, dir) + t.Chdir(dir) + t.Setenv("BUILD_TAG", "") + write(220, "4.10.0") + write(225, "4.11.0") + write(226, "5.0.0") + write(227, "") + git("tag", "-a", "5.1.x", "-m", "development") + write(228, "") + git("tag", "-a", "5.1.x-nightly-20260930", "-m", "nightly") + tag, err := command(dir, "make", "--quiet", "tag") + require.NoError(t, err) + require.Contains(t, tag, "5.1.x-1-g") + require.NotContains(t, tag, "nightly") + require.NoError(t, run("")) + before, err := os.ReadFile("pkg/migrations/release_versions.go") + require.NoError(t, err) + t.Setenv("BUILD_TAG", "5.1.x-nightly-20260930") + require.NoError(t, run("")) + after, err := os.ReadFile("pkg/migrations/release_versions.go") + require.NoError(t, err) + require.Equal(t, before, after, "explicit nightly BUILD_TAG must select the same stream") + require.NoError(t, os.WriteFile(sequencePath, []byte("package internal\nvar CurrentDBVersionSeqNum = 229\n"), 0644)) + t.Setenv("BUILD_TAG", "") + tag, err = command(dir, "make", "--quiet", "tag") + require.NoError(t, err) + require.Contains(t, tag, "-dirty") + require.NoError(t, run("")) + after, err = os.ReadFile("pkg/migrations/release_versions.go") + require.NoError(t, err) + require.Contains(t, string(after), `Version: "5.1", Sequence: 229`) +} + +func TestPatchVersionSuffixes(t *testing.T) { + dir, _, write := repository(t) + write(220, "4.10.0") + write(225, "4.11.0") + write(226, "5.0.0") + write(227, "5.1.0") + write(228, "5.1.1") + baseline, err := generate(dir, "5.1.0") + require.NoError(t, err) + for name, target := range map[string]string{ + "patch RC": "5.1.2-rc.1", + "patch nightly": "5.1.1-nightly-20260930", + "RC nightly": "5.1.2-rc.1-nightly-20260930", + "patch git describe": "5.1.1-12-gabcdef-dirty", + "release branch nightly": "5.1.x-nightly-20260930", + } { + t.Run(name, func(t *testing.T) { + data, err := generate(dir, target) + require.NoError(t, err) + require.Equal(t, baseline, data) + }) + } +} + +func TestInvalidHistoricalSequence(t *testing.T) { + for name, sequence := range map[string]int{"invalid": 0, "decreasing": 230} { + t.Run(name, func(t *testing.T) { + dir, _, write := repository(t) + write(sequence, "4.10.0") + write(225, "4.11.0") + write(226, "5.0.0") + write(227, "5.1.x") + _, err := generate(dir, "5.1.x") + require.Error(t, err) + }) + } +} + +func TestPhantomHistoricalStream(t *testing.T) { + dir, _, write := repository(t) + write(220, "4.10.0") + write(225, "4.12.0") + write(227, "5.1.x") + _, err := generate(dir, "5.1.x") + require.ErrorContains(t, err, "4.12") +} + +func TestCommandIgnoresSuccessfulStderr(t *testing.T) { + output, err := command(t.TempDir(), "sh", "-c", "printf '5.1.x'; printf 'warning' >&2") + require.NoError(t, err) + require.Equal(t, "5.1.x", output) +} + +func TestMajorBoundaryAndReleaseCandidate(t *testing.T) { + dir, _, write := repository(t) + write(211, "4.8.0") + write(213, "4.9.0") + write(220, "4.10.0") + write(225, "4.11.0") + write(227, "5.0.0-rc.1") + data, err := generate(dir, "5.0.0-rc.1") + require.NoError(t, err) + require.Contains(t, string(data), `Version: "5.0", Sequence: 227`) + write(228, "5.0.0-rc.2") + data, err = generate(dir, "5.0.0-rc.2") + require.NoError(t, err) + require.Contains(t, string(data), `Version: "5.0", Sequence: 228`) +} + +func TestShallowCheckout(t *testing.T) { + dir, _, write := repository(t) + write(220, "4.10.0") + write(227, "5.1.x") + clone := filepath.Join(t.TempDir(), "clone") + _, err := command("", "git", "clone", "--depth=1", "file://"+dir, clone) + require.NoError(t, err) + _, err = generate(clone, "5.1.x") + require.ErrorContains(t, err, "full checkout") +} + +func TestParseSequence(t *testing.T) { + for name, tc := range map[string]struct { + source string + want int + }{ + "typed": {"package internal\nvar CurrentDBVersionSeqNum int = 205", 205}, + "untyped": {"package internal\nvar CurrentDBVersionSeqNum = 227", 227}, + "missing": {"package internal", 0}, + "malformed": {"not Go", 0}, + "negative": {"package internal\nvar CurrentDBVersionSeqNum = -1", 0}, + "expression": {"package internal\nvar CurrentDBVersionSeqNum = 200 + 1", 0}, + } { + t.Run(name, func(t *testing.T) { + got, err := parseSequence(tc.source) + if tc.want == 0 { + require.Error(t, err) + } else { + require.NoError(t, err) + require.Equal(t, tc.want, got) + } + }) + } +} + +func TestInitialReleaseCandidateSelection(t *testing.T) { + dir, _, write := repository(t) + write(220, "4.10.0") + write(225, "4.11.0") + write(226, "5.0.0-rc.2") + write(227, "5.0.0-rc.10") + write(229, "5.0.1-rc.0") + write(0, "5.0.0-rc.11-nightly-20261008") + write(230, "") + data, err := generate(dir, "5.1.x") + require.NoError(t, err) + require.Contains(t, string(data), `Version: "5.0", Sequence: 227`) + // GA is authoritative even when its sequence differs from the last RC. + write(228, "5.0.0") + write(230, "") + data, err = generate(dir, "5.1.x") + require.NoError(t, err) + require.Contains(t, string(data), `Version: "5.0", Sequence: 228`) +} + +func TestDecreasingReleaseCandidateSequence(t *testing.T) { + dir, _, write := repository(t) + write(220, "4.10.0") + write(225, "4.11.0") + write(229, "5.0.0-rc.0") + write(228, "") + _, err := generate(dir, "5.1.x") + require.ErrorContains(t, err, "less than previous sequence") +} + +func TestPatchTargetWithReleaseCandidateBaseline(t *testing.T) { + dir, _, write := repository(t) + write(213, "4.9.0") + write(220, "4.10.0") + write(225, "4.11.0") + write(227, "5.0.0-rc.0") + write(229, "5.0.1-rc.0") + data, err := generate(dir, "5.0.1-rc.0") + require.NoError(t, err) + require.Contains(t, string(data), `Version: "5.0", Sequence: 227`) +} + +func TestParseTargetStream(t *testing.T) { + for name, tc := range map[string]struct { + target string + want productstreams.XYVersion + wantError string + }{ + "stream": {target: "5.1", want: productstreams.XYVersion{X: 5, Y: 1}}, + "development": {target: "5.1.x-106-gabcdef-dirty", want: productstreams.XYVersion{X: 5, Y: 1}}, + "patch RC": {target: "5.1.2-rc.1", want: productstreams.XYVersion{X: 5, Y: 1}}, + "nightly": {target: "5.1.x-nightly-20260930", want: productstreams.XYVersion{X: 5, Y: 1}}, + "invalid syntax": {target: "garbage", wantError: `parse target stream from "garbage"`}, + "phantom stream": {target: "4.12.x", wantError: "validate target stream 4.12"}, + } { + t.Run(name, func(t *testing.T) { + got, err := parseTargetStream(tc.target) + if tc.wantError != "" { + require.ErrorContains(t, err, tc.wantError) + return + } + require.NoError(t, err) + require.Equal(t, tc.want, got) + }) + } +} + +func TestSelectReleaseTags(t *testing.T) { + for name, tc := range map[string]struct { + tags string + want map[productstreams.XYVersion]string + wantError string + }{ + "numeric RC ordering": { + tags: "5.0.0-rc.10 5.0.0-rc.2 5.0.0-rc.9", + want: map[productstreams.XYVersion]string{{X: 5, Y: 0}: "5.0.0-rc.10"}, + }, + "GA supersedes RC in either order": { + tags: "4.11.0-rc.10 4.11.0 5.0.0 5.0.0-rc.10", + want: map[productstreams.XYVersion]string{{X: 4, Y: 11}: "4.11.0", {X: 5, Y: 0}: "5.0.0"}, + }, + "ignored tags and stream bounds": { + tags: "4.3.0 4.4.0 5.1.0 5.2.0 5.0.1 5.0.1-rc.0 5.0.0-nightly-20261008 5.0.0-rc.11-nightly-20261008 5.0.x 5.0.0-rc.01 garbage", + want: map[productstreams.XYVersion]string{{X: 4, Y: 4}: "4.4.0", {X: 5, Y: 1}: "5.1.0"}, + }, + "no candidates": {want: map[productstreams.XYVersion]string{}}, + "phantom historical stream": {tags: "4.12.0", wantError: "validate historical release stream 4.12"}, + } { + t.Run(name, func(t *testing.T) { + got, err := selectReleaseTags(tc.tags, productstreams.XYVersion{X: 5, Y: 1}) + if tc.wantError != "" { + require.ErrorContains(t, err, tc.wantError) + return + } + require.NoError(t, err) + require.Equal(t, tc.want, got) + }) + } +} + +func TestValidateReleaseSequences(t *testing.T) { + for name, tc := range map[string]struct { + missing productstreams.XYVersion + change productstreams.XYVersion + sequence int + wantError string + }{ + "major boundary and sorted streams": {}, + "equal sequences": {change: productstreams.XYVersion{X: 5, Y: 0}, sequence: 225}, + "missing floor": {missing: productstreams.XYVersion{X: 4, Y: 10}, wantError: "missing initial release tag 4.10.0 required by 5.1.x"}, + "missing intermediate": {missing: productstreams.XYVersion{X: 5, Y: 0}, wantError: "missing initial release tag 5.0.0 required by 5.1.x"}, + "missing target": {missing: productstreams.XYVersion{X: 5, Y: 1}, wantError: "missing initial release tag 5.1.0 required by 5.1.x"}, + "decreasing": {change: productstreams.XYVersion{X: 5, Y: 0}, sequence: 224, wantError: "release 5.0 sequence 224 is less than previous sequence 225"}, + "older historical sequence also checked": {change: productstreams.XYVersion{X: 4, Y: 9}, sequence: 221, wantError: "release 4.10 sequence 220 is less than previous sequence 221"}, + } { + t.Run(name, func(t *testing.T) { + sequences := map[productstreams.XYVersion]int{ + {X: 5, Y: 1}: 227, + {X: 4, Y: 11}: 225, + {X: 4, Y: 9}: 213, + {X: 5, Y: 0}: 226, + {X: 4, Y: 10}: 220, + } + delete(sequences, tc.missing) + if tc.change.X != 0 { + sequences[tc.change] = tc.sequence + } + got, err := validateReleaseSequences("5.1.x", productstreams.XYVersion{X: 5, Y: 1}, sequences) + if tc.wantError != "" { + require.ErrorContains(t, err, tc.wantError) + return + } + require.NoError(t, err) + require.Equal(t, []productstreams.XYVersion{{X: 4, Y: 9}, {X: 4, Y: 10}, {X: 4, Y: 11}, {X: 5, Y: 0}, {X: 5, Y: 1}}, got) + }) + } +} + +func TestValidateReleaseSequencesRejectsPhantomTarget(t *testing.T) { + _, err := validateReleaseSequences("4.12.x", productstreams.XYVersion{X: 4, Y: 12}, nil) + require.ErrorContains(t, err, "validate release stream 4.12 required by 4.12.x: invalid release stream 4.12") +} + +func TestRenderReleaseVersions(t *testing.T) { + streams := []productstreams.XYVersion{{X: 4, Y: 11}, {X: 5, Y: 0}} + sequences := map[productstreams.XYVersion]int{{X: 5, Y: 0}: 226, {X: 4, Y: 11}: 225} + got, err := renderReleaseVersions(streams, sequences) + require.NoError(t, err) + require.Equal(t, `// Code generated by release-versions. DO NOT EDIT. + +package migrations + +var releaseVersions = []ReleaseVersion{ + {Version: "4.11", Sequence: 225}, + {Version: "5.0", Sequence: 226}, +} +`, string(got)) +} + +func TestInitialReleaseOrdinalOverflow(t *testing.T) { + _, err := selectReleaseTags("5.1.0-rc.1 5.1.0-rc.999999999999999999999999999999", productstreams.XYVersion{X: 5, Y: 1}) + require.ErrorContains(t, err, "parse RC ordinal") +} diff --git a/tools/linters/go.mod b/tools/linters/go.mod index ab424a14e39e4..7a7478d0cf497 100644 --- a/tools/linters/go.mod +++ b/tools/linters/go.mod @@ -1,38 +1,39 @@ module github.com/stackrox/stackrox/tools/linters -go 1.26.3 +go 1.26.7 require ( - github.com/golangci/golangci-lint/v2 v2.12.2 + actionlint.kjanat.dev v1.17.0 + github.com/golangci/golangci-lint/v2 v2.14.0 github.com/nilslice/protolock v0.17.0 - github.com/rhysd/actionlint v1.7.12 github.com/sethvargo/ratchet v0.12.0 - golang.org/x/vuln v1.6.0 + golang.org/x/vuln v1.9.0 ) require ( - 4d63.com/gocheckcompilerdirectives v1.3.0 // indirect + 4d63.com/gocheckcompilerdirectives v1.4.0 // indirect 4d63.com/gochecknoglobals v0.2.2 // indirect - charm.land/lipgloss/v2 v2.0.3 // indirect - codeberg.org/chavacava/garif v0.2.0 // indirect + charm.land/lipgloss/v2 v2.0.6 // indirect + codeberg.org/chavacava/garif v0.2.1 // indirect codeberg.org/polyfloyd/go-errorlint v1.9.0 // indirect dev.gaijin.team/go/exhaustruct/v4 v4.0.0 // indirect - dev.gaijin.team/go/golib v0.6.0 // indirect - github.com/4meepo/tagalign v1.4.3 // indirect - github.com/Abirdcfly/dupword v0.1.7 // indirect + dev.gaijin.team/go/exhaustruct/v5 v5.2.0 // indirect + dev.gaijin.team/go/golib v0.8.1 // indirect + github.com/4meepo/tagalign v1.4.4 // indirect + github.com/Abirdcfly/dupword v0.1.8 // indirect github.com/AdminBenni/iota-mixing v1.0.0 // indirect - github.com/AlwxSin/noinlineerr v1.0.5 // indirect - github.com/Antonboom/errname v1.1.1 // indirect - github.com/Antonboom/nilnil v1.1.1 // indirect + github.com/AlwxSin/noinlineerr v1.0.6 // indirect + github.com/Antonboom/errname v1.1.2 // indirect + github.com/Antonboom/nilnil v1.1.2 // indirect github.com/Antonboom/testifylint v1.6.4 // indirect github.com/BurntSushi/toml v1.6.0 // indirect - github.com/ClickHouse/clickhouse-go-linter v1.2.0 // indirect + github.com/ClickHouse/clickhouse-go-linter v1.2.1 // indirect github.com/Djarvur/go-err113 v0.1.1 // indirect github.com/Masterminds/semver/v3 v3.5.0 // indirect github.com/MirrexOne/unqueryvet v1.5.4 // indirect github.com/OpenPeeDeeP/depguard/v2 v2.2.1 // indirect - github.com/alecthomas/chroma/v2 v2.24.1 // indirect - github.com/alecthomas/go-check-sumtype v0.3.1 // indirect + github.com/alecthomas/chroma/v2 v2.27.0 // indirect + github.com/alecthomas/go-check-sumtype v0.5.1-0.20260828200218-ae6904d28606 // indirect github.com/alexkohler/nakedret/v2 v2.0.6 // indirect github.com/alexkohler/prealloc v1.1.0 // indirect github.com/alfatraining/structtag v1.0.0 // indirect @@ -45,19 +46,19 @@ require ( github.com/blizzy78/varnamelen v0.8.0 // indirect github.com/bmatcuk/doublestar/v4 v4.10.0 // indirect github.com/bombsimon/wsl/v4 v4.7.0 // indirect - github.com/bombsimon/wsl/v5 v5.8.0 // indirect + github.com/bombsimon/wsl/v5 v5.9.0 // indirect github.com/braydonk/yaml v0.9.0 // indirect github.com/breml/bidichk v0.3.3 // indirect github.com/breml/errchkjson v0.4.1 // indirect github.com/butuzov/ireturn v0.4.1 // indirect - github.com/butuzov/mirror v1.3.0 // indirect + github.com/butuzov/mirror v1.3.3 // indirect github.com/catenacyber/perfsprint v0.10.1 // indirect github.com/ccojocar/zxcvbn-go v1.0.4 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/charithe/durationcheck v0.0.11 // indirect github.com/charmbracelet/colorprofile v0.4.3 // indirect - github.com/charmbracelet/ultraviolet v0.0.0-20251205161215-1948445e3318 // indirect - github.com/charmbracelet/x/ansi v0.11.7 // indirect + github.com/charmbracelet/ultraviolet v0.0.0-20260811164956-006e29f97886 // indirect + github.com/charmbracelet/x/ansi v0.11.8 // indirect github.com/charmbracelet/x/term v0.2.2 // indirect github.com/charmbracelet/x/termios v0.1.1 // indirect github.com/charmbracelet/x/windows v0.2.2 // indirect @@ -67,20 +68,19 @@ require ( github.com/curioswitch/go-reassign v0.3.0 // indirect github.com/daixiang0/gci v0.13.7 // indirect github.com/dave/dst v0.27.3 // indirect - github.com/davecgh/go-spew v1.1.1 // indirect github.com/denis-tingaikin/go-header v0.5.0 // indirect - github.com/dlclark/regexp2 v1.12.0 // indirect + github.com/dlclark/regexp2/v2 v2.2.1 // indirect github.com/docker/cli v29.6.2+incompatible // indirect github.com/docker/docker-credential-helpers v0.9.8 // indirect github.com/emicklei/proto v1.9.1 // indirect github.com/ettle/strcase v0.2.0 // indirect github.com/fatih/color v1.19.0 // indirect github.com/fatih/structtag v1.2.0 // indirect - github.com/firefart/nonamedreturns v1.0.6 // indirect - github.com/fsnotify/fsnotify v1.5.4 // indirect + github.com/firefart/nonamedreturns v1.0.8 // indirect + github.com/fsnotify/fsnotify v1.9.0 // indirect github.com/fzipp/gocyclo v0.6.0 // indirect - github.com/ghostiam/protogetter v0.3.20 // indirect - github.com/go-critic/go-critic v0.14.3 // indirect + github.com/ghostiam/protogetter v1.0.1 // indirect + github.com/go-critic/go-critic v0.15.0 // indirect github.com/go-toolsmith/astcast v1.1.0 // indirect github.com/go-toolsmith/astcopy v1.1.0 // indirect github.com/go-toolsmith/astequal v1.2.0 // indirect @@ -91,13 +91,14 @@ require ( github.com/go-viper/mapstructure/v2 v2.5.0 // indirect github.com/go-xmlfmt/xmlfmt v1.1.3 // indirect github.com/gobwas/glob v0.2.3 // indirect - github.com/godoc-lint/godoc-lint v0.11.2 // indirect - github.com/gofrs/flock v0.13.0 // indirect + github.com/godoc-lint/godoc-lint v0.11.4 // indirect + github.com/gofrs/flock v0.13.1 // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/golangci/asciicheck v0.5.0 // indirect + github.com/golangci/canonicalheader v0.0.0-20260827115959-a25c71c521f6 // indirect github.com/golangci/dupl v0.0.0-20260401084720-c99c5cf5c202 // indirect github.com/golangci/go-printf-func-name v0.1.1 // indirect - github.com/golangci/gofmt v0.0.0-20250106114630-d62b90e6713d // indirect + github.com/golangci/gofmt v0.0.0-20260820135601-e84e05053792 // indirect github.com/golangci/golines v0.15.0 // indirect github.com/golangci/misspell v0.8.0 // indirect github.com/golangci/plugin-module-register v0.1.2 // indirect @@ -120,24 +121,23 @@ require ( github.com/hashicorp/hcl v1.0.0 // indirect github.com/hexops/gotextdiff v1.0.3 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/jgautheron/goconst v1.10.0 // indirect + github.com/jgautheron/goconst v1.11.0 // indirect github.com/jjti/go-spancheck v0.6.5 // indirect github.com/julz/importas v0.2.0 // indirect github.com/karamaru-alpha/copyloopvar v1.2.2 // indirect - github.com/kisielk/errcheck v1.10.0 // indirect + github.com/kisielk/errcheck v1.20.0 // indirect github.com/kkHAIKE/contextcheck v1.1.6 // indirect github.com/klauspost/compress v1.19.0 // indirect github.com/kulti/thelper v0.7.1 // indirect github.com/kunwardeep/paralleltest v1.0.15 // indirect - github.com/lasiar/canonicalheader v1.1.2 // indirect github.com/ldez/exptostd v0.4.5 // indirect - github.com/ldez/gomoddirectives v0.8.0 // indirect + github.com/ldez/gomoddirectives v0.10.0 // indirect github.com/ldez/grignotin v0.10.1 // indirect github.com/ldez/structtags v0.6.1 // indirect - github.com/ldez/tagliatelle v0.7.2 // indirect + github.com/ldez/tagliatelle v0.8.0 // indirect github.com/ldez/usetesting v0.5.0 // indirect github.com/leonklingele/grouper v1.1.2 // indirect - github.com/lucasb-eyer/go-colorful v1.4.0 // indirect + github.com/lucasb-eyer/go-colorful v1.4.1 // indirect github.com/macabu/inamedparam v0.2.0 // indirect github.com/magiconair/properties v1.8.6 // indirect github.com/manuelarte/embeddedstructfieldcheck v0.4.0 // indirect @@ -145,25 +145,24 @@ require ( github.com/maratori/testableexamples v1.0.1 // indirect github.com/maratori/testpackage v1.1.2 // indirect github.com/matoous/godox v1.1.0 // indirect - github.com/mattn/go-colorable v0.1.14 // indirect - github.com/mattn/go-isatty v0.0.20 // indirect - github.com/mattn/go-runewidth v0.0.23 // indirect - github.com/mattn/go-shellwords v1.0.12 // indirect + github.com/mattn/go-colorable v0.1.15 // indirect + github.com/mattn/go-isatty v0.0.24 // indirect + github.com/mattn/go-runewidth v0.0.28 // indirect + github.com/mattn/go-shellwords v1.0.14 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect - github.com/mgechev/revive v1.15.0 // indirect + github.com/mgechev/revive v1.17.0 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/moricho/tparallel v0.3.2 // indirect github.com/muesli/cancelreader v0.2.2 // indirect github.com/nakabonne/nestif v0.3.1 // indirect - github.com/nishanths/exhaustive v0.12.0 // indirect + github.com/nishanths/exhaustive v0.13.0 // indirect github.com/nishanths/predeclared v0.2.2 // indirect - github.com/nunnatsa/ginkgolinter v0.23.0 // indirect + github.com/nunnatsa/ginkgolinter v0.24.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.1 // indirect github.com/pelletier/go-toml v1.9.5 // indirect - github.com/pelletier/go-toml/v2 v2.3.1 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/pelletier/go-toml/v2 v2.4.3 // indirect github.com/prometheus/client_golang v1.12.1 // indirect github.com/prometheus/client_model v0.2.0 // indirect github.com/prometheus/common v0.32.1 // indirect @@ -173,22 +172,22 @@ require ( github.com/quasilyte/gogrep v0.5.0 // indirect github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727 // indirect github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 // indirect - github.com/raeperd/recvcheck v0.2.0 // indirect + github.com/raeperd/recvcheck v0.3.1 // indirect github.com/rivo/uniseg v0.4.7 // indirect github.com/robfig/cron/v3 v3.0.1 // indirect - github.com/rogpeppe/go-internal v1.14.1 // indirect + github.com/rogpeppe/go-internal v1.16.0 // indirect github.com/ryancurrah/gomodguard v1.4.1 // indirect github.com/ryancurrah/gomodguard/v2 v2.1.3 // indirect github.com/ryanrolds/sqlclosecheck v0.6.0 // indirect github.com/sanposhiho/wastedassign/v2 v2.1.0 // indirect - github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 // indirect + github.com/santhosh-tekuri/jsonschema/v6 v6.0.3 // indirect github.com/sashamelentyev/interfacebloat v1.1.0 // indirect github.com/sashamelentyev/usestdlibvars v1.29.0 // indirect - github.com/securego/gosec/v2 v2.26.1 // indirect - github.com/sirupsen/logrus v1.9.4 // indirect + github.com/securego/gosec/v2 v2.29.0 // indirect + github.com/sirupsen/logrus v1.10.2 // indirect github.com/sivchari/containedctx v1.0.3 // indirect github.com/sonatard/noctx v0.5.1 // indirect - github.com/sourcegraph/go-diff v0.8.0 // indirect + github.com/sourcegraph/go-diff v0.9.0 // indirect github.com/spf13/afero v1.15.0 // indirect github.com/spf13/cast v1.5.0 // indirect github.com/spf13/cobra v1.10.2 // indirect @@ -197,20 +196,20 @@ require ( github.com/spf13/viper v1.13.0 // indirect github.com/ssgreg/nlreturn/v2 v2.2.1 // indirect github.com/stbenjam/no-sprintf-host-port v0.3.1 // indirect - github.com/stretchr/objx v0.5.2 // indirect - github.com/stretchr/testify v1.11.1 // indirect + github.com/stretchr/objx v0.5.3 // indirect + github.com/stretchr/testify v1.12.1 // indirect github.com/subosito/gotenv v1.4.1 // indirect github.com/tetafro/godot v1.5.6 // indirect - github.com/timakin/bodyclose v0.0.0-20260129054331-73d1f95b84b4 // indirect - github.com/timonwong/loggercheck v0.11.0 // indirect + github.com/timakin/bodyclose v0.0.0-20260723120731-857993a2939c // indirect + github.com/timonwong/loggercheck v0.12.0 // indirect github.com/tomarrell/wrapcheck/v2 v2.12.0 // indirect github.com/tommy-muehle/go-mnd/v2 v2.5.1 // indirect github.com/ultraware/funlen v0.2.0 // indirect github.com/ultraware/whitespace v0.2.0 // indirect github.com/uudashr/gocognit v1.2.1 // indirect - github.com/uudashr/iface v1.4.2 // indirect + github.com/uudashr/iface v1.5.1 // indirect github.com/xen0n/gosmopolitan v1.3.0 // indirect - github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect + github.com/xo/terminfo v1.0.0 // indirect github.com/yagipy/maintidx v1.0.0 // indirect github.com/yeya24/promlinter v0.3.0 // indirect github.com/ykadowak/zerologlint v0.1.5 // indirect @@ -218,24 +217,24 @@ require ( go-simpler.org/musttag v0.14.0 // indirect go-simpler.org/sloglint v0.12.0 // indirect go.augendre.info/arangolint v0.4.0 // indirect - go.augendre.info/fatcontext v0.9.0 // indirect + go.augendre.info/fatcontext v0.10.1 // indirect go.uber.org/multierr v1.10.0 // indirect go.uber.org/zap v1.27.0 // indirect - go.yaml.in/yaml/v3 v3.0.4 // indirect - go.yaml.in/yaml/v4 v4.0.0-rc.3 // indirect - golang.org/x/exp/typeparams v0.0.0-20260209203927-2842357ff358 // indirect - golang.org/x/mod v0.38.0 // indirect + go.yaml.in/yaml/v3 v3.0.5 // indirect + go.yaml.in/yaml/v4 v4.0.0-rc.6 // indirect + golang.org/x/exp/typeparams v0.0.0-20260811152304-ee035b5b010f // indirect + golang.org/x/mod v0.42.0 // indirect golang.org/x/oauth2 v0.36.0 // indirect - golang.org/x/sync v0.22.0 // indirect - golang.org/x/sys v0.47.0 // indirect - golang.org/x/telemetry v0.0.0-20260708182218-49f421fb7959 // indirect - golang.org/x/text v0.36.0 // indirect - golang.org/x/tools v0.48.0 // indirect - google.golang.org/protobuf v1.36.10 // indirect + golang.org/x/sync v0.24.0 // indirect + golang.org/x/sys v0.49.0 // indirect + golang.org/x/telemetry v0.0.0-20261010193011-f71dc92da45e // indirect + golang.org/x/text v0.41.0 // indirect + golang.org/x/tools v0.52.0 // indirect + google.golang.org/protobuf v1.36.12 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - honnef.co/go/tools v0.7.0 // indirect - mvdan.cc/gofumpt v0.9.2 // indirect - mvdan.cc/unparam v0.0.0-20251027182757-5beb8c8f8f15 // indirect + honnef.co/go/tools v0.8.1 // indirect + mvdan.cc/gofumpt v0.12.0 // indirect + mvdan.cc/unparam v0.0.0-20260823230713-2fa3d841b0c8 // indirect ) diff --git a/tools/linters/go.sum b/tools/linters/go.sum index cee066806ecae..48f60e63ee6e5 100644 --- a/tools/linters/go.sum +++ b/tools/linters/go.sum @@ -1,9 +1,11 @@ -4d63.com/gocheckcompilerdirectives v1.3.0 h1:Ew5y5CtcAAQeTVKUVFrE7EwHMrTO6BggtEj8BZSjZ3A= -4d63.com/gocheckcompilerdirectives v1.3.0/go.mod h1:ofsJ4zx2QAuIP/NO/NAh1ig6R1Fb18/GI7RVMwz7kAY= +4d63.com/gocheckcompilerdirectives v1.4.0 h1:ZLq62rbGWVmQhiZ8kuNVIT/M09xCSTdJz9K3xOdT/CY= +4d63.com/gocheckcompilerdirectives v1.4.0/go.mod h1:9ZOAiMOjqC/nRwci2fcUXVHUNLG/cH6r6rhUh+jTFtQ= 4d63.com/gochecknoglobals v0.2.2 h1:H1vdnwnMaZdQW/N+NrkT1SZMTBmcwHe9Vq8lJcYYTtU= 4d63.com/gochecknoglobals v0.2.2/go.mod h1:lLxwTQjL5eIesRbvnzIP3jZtG140FnTdz+AlMa+ogt0= -charm.land/lipgloss/v2 v2.0.3 h1:yM2zJ4Cf5Y51b7RHIwioil4ApI/aypFXXVHSwlM6RzU= -charm.land/lipgloss/v2 v2.0.3/go.mod h1:7myLU9iG/3xluAWzpY/fSxYYHCgoKTie7laxk6ATwXA= +actionlint.kjanat.dev v1.17.0 h1:wzkg6amflGqcF4QlkSERw0L1kdGSb/z2KZ7aidlgD74= +actionlint.kjanat.dev v1.17.0/go.mod h1:9BVNedMJ1sZTdAiszWaOl+H4IiKD9PfKrkwqpxeB+ao= +charm.land/lipgloss/v2 v2.0.6 h1:EaGKeuA8FvF+v2BT5VmZd2LoYLaMZJXA5n34th8nCIQ= +charm.land/lipgloss/v2 v2.0.6/go.mod h1:ipDDJNSGa1hlwDtSfW1s2/xR8Vdhbut4PXh2zEKZd0Q= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= @@ -36,35 +38,37 @@ cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0Zeo cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= -codeberg.org/chavacava/garif v0.2.0 h1:F0tVjhYbuOCnvNcU3YSpO6b3Waw6Bimy4K0mM8y6MfY= -codeberg.org/chavacava/garif v0.2.0/go.mod h1:P2BPbVbT4QcvLZrORc2T29szK3xEOlnl0GiPTJmEqBQ= +codeberg.org/chavacava/garif v0.2.1 h1:K9oYxSlvlXrHXyW26Z4q61bTpJDo1wbvXcYKar/F/LM= +codeberg.org/chavacava/garif v0.2.1/go.mod h1:oHnDSmc0f9K1MeE+MQD/yjkiIB5Xsn5y3S9Dg96Xk84= codeberg.org/polyfloyd/go-errorlint v1.9.0 h1:VkdEEmA1VBpH6ecQoMR4LdphVI3fA4RrCh2an7YmodI= codeberg.org/polyfloyd/go-errorlint v1.9.0/go.mod h1:GPRRu2LzVijNn4YkrZYJfatQIdS+TrcK8rL5Xs24qw8= dev.gaijin.team/go/exhaustruct/v4 v4.0.0 h1:873r7aNneqoBB3IaFIzhvt2RFYTuHgmMjoKfwODoI1Y= dev.gaijin.team/go/exhaustruct/v4 v4.0.0/go.mod h1:aZ/k2o4Y05aMJtiux15x8iXaumE88YdiB0Ai4fXOzPI= -dev.gaijin.team/go/golib v0.6.0 h1:v6nnznFTs4bppib/NyU1PQxobwDHwCXXl15P7DV5Zgo= -dev.gaijin.team/go/golib v0.6.0/go.mod h1:uY1mShx8Z/aNHWDyAkZTkX+uCi5PdX7KsG1eDQa2AVE= +dev.gaijin.team/go/exhaustruct/v5 v5.2.0 h1:HZB49Fshn8qHJCi9VzYtrcnR1jbLteRHXfnQz8Adams= +dev.gaijin.team/go/exhaustruct/v5 v5.2.0/go.mod h1:5IshpqhgGdKIrF8lBmi3M6YKmI7T3hMzAL9nrflko88= +dev.gaijin.team/go/golib v0.8.1 h1:JYju4x9BSo+QD/AYeHULVDcvEhiFg8wOi6pT0IaZF5E= +dev.gaijin.team/go/golib v0.8.1/go.mod h1:c5fu7t1RSGMxSQgcUYO1sODbzsYnOCXJLmHeNG1Eb+0= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -github.com/4meepo/tagalign v1.4.3 h1:Bnu7jGWwbfpAie2vyl63Zup5KuRv21olsPIha53BJr8= -github.com/4meepo/tagalign v1.4.3/go.mod h1:00WwRjiuSbrRJnSVeGWPLp2epS5Q/l4UEy0apLLS37c= -github.com/Abirdcfly/dupword v0.1.7 h1:2j8sInznrje4I0CMisSL6ipEBkeJUJAmK1/lfoNGWrQ= -github.com/Abirdcfly/dupword v0.1.7/go.mod h1:K0DkBeOebJ4VyOICFdppB23Q0YMOgVafM0zYW0n9lF4= +github.com/4meepo/tagalign v1.4.4 h1:052JDGj2pBORc6NkD30dDXpsUpoYszDeUwr9/5YZuVA= +github.com/4meepo/tagalign v1.4.4/go.mod h1:00WwRjiuSbrRJnSVeGWPLp2epS5Q/l4UEy0apLLS37c= +github.com/Abirdcfly/dupword v0.1.8 h1:SrhcUuGsROBuChFxHALRYzyyPODWn9zwghmzPvD9Cd8= +github.com/Abirdcfly/dupword v0.1.8/go.mod h1:XZrhVnI7YGpsTiWZANSQaBJ4QpL/Tq5vIEdKJJAs9WI= github.com/AdminBenni/iota-mixing v1.0.0 h1:Os6lpjG2dp/AE5fYBPAA1zfa2qMdCAWwPMCgpwKq7wo= github.com/AdminBenni/iota-mixing v1.0.0/go.mod h1:i4+tpAaB+qMVIV9OK3m4/DAynOd5bQFaOu+2AhtBCNY= -github.com/AlwxSin/noinlineerr v1.0.5 h1:RUjt63wk1AYWTXtVXbSqemlbVTb23JOSRiNsshj7TbY= -github.com/AlwxSin/noinlineerr v1.0.5/go.mod h1:+QgkkoYrMH7RHvcdxdlI7vYYEdgeoFOVjU9sUhw/rQc= -github.com/Antonboom/errname v1.1.1 h1:bllB7mlIbTVzO9jmSWVWLjxTEbGBVQ1Ff/ClQgtPw9Q= -github.com/Antonboom/errname v1.1.1/go.mod h1:gjhe24xoxXp0ScLtHzjiXp0Exi1RFLKJb0bVBtWKCWQ= -github.com/Antonboom/nilnil v1.1.1 h1:9Mdr6BYd8WHCDngQnNVV0b554xyisFioEKi30sksufQ= -github.com/Antonboom/nilnil v1.1.1/go.mod h1:yCyAmSw3doopbOWhJlVci+HuyNRuHJKIv6V2oYQa8II= +github.com/AlwxSin/noinlineerr v1.0.6 h1:KAvuxunTe9QxvqrFB7nZTdb/7Wzas4AvifslTnG0Ld8= +github.com/AlwxSin/noinlineerr v1.0.6/go.mod h1:+QgkkoYrMH7RHvcdxdlI7vYYEdgeoFOVjU9sUhw/rQc= +github.com/Antonboom/errname v1.1.2 h1:dxwONZJua3VB8Xh/VaCjqAcqF645sWWv7xj26zy7tdQ= +github.com/Antonboom/errname v1.1.2/go.mod h1:YeZIpgLMxT+SNkruGgYkLhzq/9vs3fsolTZegKaKDZI= +github.com/Antonboom/nilnil v1.1.2 h1:aNlFuJhaEseXe4fHO3xbjXlSeEiQVYa2lEkWD2s2hAY= +github.com/Antonboom/nilnil v1.1.2/go.mod h1:0ynwvphOLmAuMwTNDyBnDZmSwZoDpcFXmUHmzoHH2WA= github.com/Antonboom/testifylint v1.6.4 h1:gs9fUEy+egzxkEbq9P4cpcMB6/G0DYdMeiFS87UiqmQ= github.com/Antonboom/testifylint v1.6.4/go.mod h1:YO33FROXX2OoUfwjz8g+gUxQXio5i9qpVy7nXGbxDD4= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk= github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/ClickHouse/clickhouse-go-linter v1.2.0 h1:zbm174up3hTKjp0wKZVnTzRiG7tSF5XZF0FJG/MuCBI= -github.com/ClickHouse/clickhouse-go-linter v1.2.0/go.mod h1:pLorS7ffPTfuUV9M0SJgfHA/h/WQPQUk2FWG9x74cQ4= +github.com/ClickHouse/clickhouse-go-linter v1.2.1 h1:zGEKIyd5YL08ieWG/LOUmlau2DxbxPVOfAeo+4Jz3ck= +github.com/ClickHouse/clickhouse-go-linter v1.2.1/go.mod h1:pLorS7ffPTfuUV9M0SJgfHA/h/WQPQUk2FWG9x74cQ4= github.com/Djarvur/go-err113 v0.1.1 h1:eHfopDqXRwAi+YmCUas75ZE0+hoBHJ2GQNLYRSxao4g= github.com/Djarvur/go-err113 v0.1.1/go.mod h1:IaWJdYFLg76t2ihfflPZnM1LIQszWOsFDh2hhhAVF6k= github.com/Masterminds/semver/v3 v3.5.0 h1:kQceYJfbupGfZOKZQg0kou0DgAKhzDg2NZPAwZ/2OOE= @@ -75,10 +79,10 @@ github.com/OpenPeeDeeP/depguard/v2 v2.2.1 h1:vckeWVESWp6Qog7UZSARNqfu/cZqvki8zsu github.com/OpenPeeDeeP/depguard/v2 v2.2.1/go.mod h1:q4DKzC4UcVaAvcfd41CZh0PWpGgzrVxUYBlgKNGquUo= github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0= github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= -github.com/alecthomas/chroma/v2 v2.24.1 h1:m5ffpfZbIb++k8AqFEKy9uVgY12xIQtBsQlc6DfZJQM= -github.com/alecthomas/chroma/v2 v2.24.1/go.mod h1:l+ohZ9xRXIbGe7cIW+YZgOGbvuVLjMps/FYN/CwuabI= -github.com/alecthomas/go-check-sumtype v0.3.1 h1:u9aUvbGINJxLVXiFvHUlPEaD7VDULsrxJb4Aq31NLkU= -github.com/alecthomas/go-check-sumtype v0.3.1/go.mod h1:A8TSiN3UPRw3laIgWEUOHHLPa6/r9MtoigdlP5h3K/E= +github.com/alecthomas/chroma/v2 v2.27.0 h1:FodwmyOBgJULFYmDqibcp9pvfDLWdtPRh9v/r5BXYZs= +github.com/alecthomas/chroma/v2 v2.27.0/go.mod h1:NjJ3ciIgrqBNeIkWZ4e46nseoLDslxU1LmfCoL+wcY8= +github.com/alecthomas/go-check-sumtype v0.5.1-0.20260828200218-ae6904d28606 h1:dXPJ+uOJLWb8rjlAuLX7JPiRJtZKw/53Ol+WxdDJ1Eo= +github.com/alecthomas/go-check-sumtype v0.5.1-0.20260828200218-ae6904d28606/go.mod h1:ZH31IJyNpxpStxP2ayCcPQz+3T2G/OnxIQqe4LWvSDw= github.com/alecthomas/repr v0.5.2 h1:SU73FTI9D1P5UNtvseffFSGmdNci/O6RsqzeXJtP0Qs= github.com/alecthomas/repr v0.5.2/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= @@ -112,8 +116,8 @@ github.com/bmatcuk/doublestar/v4 v4.10.0 h1:zU9WiOla1YA122oLM6i4EXvGW62DvKZVxIe6 github.com/bmatcuk/doublestar/v4 v4.10.0/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= github.com/bombsimon/wsl/v4 v4.7.0 h1:1Ilm9JBPRczjyUs6hvOPKvd7VL1Q++PL8M0SXBDf+jQ= github.com/bombsimon/wsl/v4 v4.7.0/go.mod h1:uV/+6BkffuzSAVYD+yGyld1AChO7/EuLrCF/8xTiapg= -github.com/bombsimon/wsl/v5 v5.8.0 h1:JTkyfs4yl8SPejrCF2GdABXE+mO1WvM7iUYzRWlsxDs= -github.com/bombsimon/wsl/v5 v5.8.0/go.mod h1:AbOLsulgkqP4ZnitHf9gwPtCOGlrzkk0jb0uNxRSY0o= +github.com/bombsimon/wsl/v5 v5.9.0 h1:WCrgZ7RQnZO5oEwbVTlYgBdU3wL294kR1BSWV8vTfsU= +github.com/bombsimon/wsl/v5 v5.9.0/go.mod h1:kjo4HiAV5FDkHC8/uzJq9mBffEEd6WT/nvN7DoMovDM= github.com/braydonk/yaml v0.9.0 h1:ewGMrVmEVpsm3VwXQDR388sLg5+aQ8Yihp6/hc4m+h4= github.com/braydonk/yaml v0.9.0/go.mod h1:hcm3h581tudlirk8XEUPDBAimBPbmnL0Y45hCRl47N4= github.com/breml/bidichk v0.3.3 h1:WSM67ztRusf1sMoqH6/c4OBCUlRVTKq+CbSeo0R17sE= @@ -122,8 +126,8 @@ github.com/breml/errchkjson v0.4.1 h1:keFSS8D7A2T0haP9kzZTi7o26r7kE3vymjZNeNDRDw github.com/breml/errchkjson v0.4.1/go.mod h1:a23OvR6Qvcl7DG/Z4o0el6BRAjKnaReoPQFciAl9U3s= github.com/butuzov/ireturn v0.4.1 h1:vWb3NO4t77iku/sjCQ/2pHTQeOmxEhjIriJqRLg1Y+I= github.com/butuzov/ireturn v0.4.1/go.mod h1:q+DXKzTDV5guNuXLnIab9fKXizTn2miZHLhxH7V/GB4= -github.com/butuzov/mirror v1.3.0 h1:HdWCXzmwlQHdVhwvsfBb2Au0r3HyINry3bDWLYXiKoc= -github.com/butuzov/mirror v1.3.0/go.mod h1:AEij0Z8YMALaq4yQj9CPPVYOyJQyiexpQEQgihajRfI= +github.com/butuzov/mirror v1.3.3 h1:v0RsWBhfFc1RQqE/f3sHpSttKDtodFn0gFmtYyD4/hA= +github.com/butuzov/mirror v1.3.3/go.mod h1:h9BzzwYnTiHO0GzgvaTqIg7VSsOUhdIv51cHFFBmX1w= github.com/catenacyber/perfsprint v0.10.1 h1:u7Riei30bk46XsG8nknMhKLXG9BcXz3+3tl/WpKm0PQ= github.com/catenacyber/perfsprint v0.10.1/go.mod h1:DJTGsi/Zufpuus6XPGJyKOTMELe347o6akPvWG9Zcsc= github.com/ccojocar/zxcvbn-go v1.0.4 h1:FWnCIRMXPj43ukfX000kvBZvV6raSxakYr1nzyNrUcc= @@ -137,10 +141,10 @@ github.com/charithe/durationcheck v0.0.11 h1:g1/EX1eIiKS57NTWsYtHDZ/APfeXKhye1Di github.com/charithe/durationcheck v0.0.11/go.mod h1:x5iZaixRNl8ctbM+3B2RrPG5t856TxRyVQEnbIEM2X4= github.com/charmbracelet/colorprofile v0.4.3 h1:QPa1IWkYI+AOB+fE+mg/5/4HRMZcaXex9t5KX76i20Q= github.com/charmbracelet/colorprofile v0.4.3/go.mod h1:/zT4BhpD5aGFpqQQqw7a+VtHCzu+zrQtt1zhMt9mR4Q= -github.com/charmbracelet/ultraviolet v0.0.0-20251205161215-1948445e3318 h1:OqDqxQZliC7C8adA7KjelW3OjtAxREfeHkNcd66wpeI= -github.com/charmbracelet/ultraviolet v0.0.0-20251205161215-1948445e3318/go.mod h1:Y6kE2GzHfkyQQVCSL9r2hwokSrIlHGzZG+71+wDYSZI= -github.com/charmbracelet/x/ansi v0.11.7 h1:kzv1kJvjg2S3r9KHo8hDdHFQLEqn4RBCb39dAYC84jI= -github.com/charmbracelet/x/ansi v0.11.7/go.mod h1:9qGpnAVYz+8ACONkZBUWPtL7lulP9No6p1epAihUZwQ= +github.com/charmbracelet/ultraviolet v0.0.0-20260811164956-006e29f97886 h1:rdnVWKgJpTVXKuKuJyxDJ+NFJdUaUqGvyGy61OcvlbA= +github.com/charmbracelet/ultraviolet v0.0.0-20260811164956-006e29f97886/go.mod h1:nAw0d9PhFp1qdzi2xhQU5YOu5sVpDIHWlaW2Uz/bCro= +github.com/charmbracelet/x/ansi v0.11.8 h1:JMFwp0CgDC2+jcOB162HH5k7I3FVbgFSMMYg7dSPBQQ= +github.com/charmbracelet/x/ansi v0.11.8/go.mod h1:ZNN+3mXny/516oTQPLMPIBeSINvNJJQ8uQXDgbeJxY0= github.com/charmbracelet/x/term v0.2.2 h1:xVRT/S2ZcKdhhOuSP4t5cLi5o+JxklsoEObBSgfgZRk= github.com/charmbracelet/x/term v0.2.2/go.mod h1:kF8CY5RddLWrsgVwpw4kAa6TESp6EB5y3uxGLeCqzAI= github.com/charmbracelet/x/termios v0.1.1 h1:o3Q2bT8eqzGnGPOYheoYS8eEleT5ZVNYNy8JawjaNZY= @@ -174,6 +178,8 @@ github.com/denis-tingaikin/go-header v0.5.0 h1:SRdnP5ZKvcO9KKRP1KJrhFR3RrlGuD+42 github.com/denis-tingaikin/go-header v0.5.0/go.mod h1:mMenU5bWrok6Wl2UsZjy+1okegmwQ3UgWl4V1D8gjlY= github.com/dlclark/regexp2 v1.12.0 h1:0j4c5qQmnC6XOWNjP3PIXURXN2gWx76rd3KvgdPkCz8= github.com/dlclark/regexp2 v1.12.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= +github.com/dlclark/regexp2/v2 v2.2.1 h1:mf4KkFUj0gJuarK8P+LgiS+Lit7m9N1yAwEfPbee7R0= +github.com/dlclark/regexp2/v2 v2.2.1/go.mod h1:avUrQvPaLz2DrFNHJF0taWAFFX2C1GMSSoeiqFjcBmU= github.com/docker/cli v29.6.2+incompatible h1:/bjePvcbbFTnRrMfWJBY7AjfICdsiLVgHn6LwTVOcqw= github.com/docker/cli v29.6.2+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/docker-credential-helpers v0.9.8 h1:bIREROb7So6PRlq6KTtdS9MPEjC29OQRkFNlvK2OX8Q= @@ -190,18 +196,18 @@ github.com/fatih/color v1.19.0 h1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w= github.com/fatih/color v1.19.0/go.mod h1:zNk67I0ZUT1bEGsSGyCZYZNrHuTkJJB+r6Q9VuMi0LE= github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4= github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94= -github.com/firefart/nonamedreturns v1.0.6 h1:vmiBcKV/3EqKY3ZiPxCINmpS431OcE1S47AQUwhrg8E= -github.com/firefart/nonamedreturns v1.0.6/go.mod h1:R8NisJnSIpvPWheCq0mNRXJok6D8h7fagJTF8EMEwCo= +github.com/firefart/nonamedreturns v1.0.8 h1:iB32Dl17zJl1zlVEj/WlUWgx0HiRyQ85OUw1WHa4/II= +github.com/firefart/nonamedreturns v1.0.8/go.mod h1:vxFNvm5AfP/8rgAKFzYmnqx0yp1HjrYsErZ9pHPTznA= github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps= -github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= -github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= +github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= +github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/fzipp/gocyclo v0.6.0 h1:lsblElZG7d3ALtGMx9fmxeTKZaLLpU8mET09yN4BBLo= github.com/fzipp/gocyclo v0.6.0/go.mod h1:rXPyn8fnlpa0R2csP/31uerbiVBugk5whMdlyaLkLoA= -github.com/ghostiam/protogetter v0.3.20 h1:oW7OPFit2FxZOpmMRPP9FffU4uUpfeE/rEdE1f+MzD0= -github.com/ghostiam/protogetter v0.3.20/go.mod h1:FjIu5Yfs6FT391m+Fjp3fbAYJ6rkL/J6ySpZBfnODuI= -github.com/go-critic/go-critic v0.14.3 h1:5R1qH2iFeo4I/RJU8vTezdqs08Egi4u5p6vOESA0pog= -github.com/go-critic/go-critic v0.14.3/go.mod h1:xwntfW6SYAd7h1OqDzmN6hBX/JxsEKl5up/Y2bsxgVQ= +github.com/ghostiam/protogetter v1.0.1 h1:q4LLTx2KJHJpxrfO3BniUDWoFkoqWD/UlHhClBlUlLM= +github.com/ghostiam/protogetter v1.0.1/go.mod h1:50Fh5yZfs6SFGrd1FlmT04NcguyEOxyjNK5zGD6/EvM= +github.com/go-critic/go-critic v0.15.0 h1:MDUZIQKytIl+vPEc0Igw51m4uLTdi1ZGz2jatEqNs1g= +github.com/go-critic/go-critic v0.15.0/go.mod h1:3BPrw8nHATGm3o2n37cssOmJzpySGKCdY8JWkCzxgKQ= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= @@ -211,10 +217,10 @@ github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vb github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= -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-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI= -github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow= +github.com/go-logr/logr v1.4.4 h1:tG4xh9yMsRCAiodLVTxyrkzSZ9+o0L1Kg/+cPVcbP/8= +github.com/go-logr/logr v1.4.4/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-quicktest/qt v1.102.0 h1:HSQxCeh5YZH3EL3W39ixjtyaEhcWSXQHtHnMBzSs474= +github.com/go-quicktest/qt v1.102.0/go.mod h1:p4lGIVX+8Wa6ZPNDvqcxq36XpUDLh42FLetFU7odllI= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= @@ -243,10 +249,10 @@ github.com/go-xmlfmt/xmlfmt v1.1.3 h1:t8Ey3Uy7jDSEisW2K3somuMKIpzktkWptA0iFCnRUW github.com/go-xmlfmt/xmlfmt v1.1.3/go.mod h1:aUCEOzzezBEjDBbFBoSiya/gduyIiWYRP6CnSFIV8AM= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= -github.com/godoc-lint/godoc-lint v0.11.2 h1:Bp0FkJWoSdNsBikdNgIcgtaoo+xz6I/Y9s5WSBQUeeM= -github.com/godoc-lint/godoc-lint v0.11.2/go.mod h1:iVpGdL1JCikNH2gGeAn3Hh+AgN5Gx/I/cxV+91L41jo= -github.com/gofrs/flock v0.13.0 h1:95JolYOvGMqeH31+FC7D2+uULf6mG61mEZ/A8dRYMzw= -github.com/gofrs/flock v0.13.0/go.mod h1:jxeyy9R1auM5S6JYDBhDt+E2TCo7DkratH4Pgi8P+Z0= +github.com/godoc-lint/godoc-lint v0.11.4 h1:N9zUYVJ/OkznBbrBsU/zODvua64EUplMBLQWStO3+1M= +github.com/godoc-lint/godoc-lint v0.11.4/go.mod h1:7usTXoxS0QYIi5usTSCPwR0w/6xLjcz450shXjLn3t4= +github.com/gofrs/flock v0.13.1 h1:jjREztyBeSKBZYAC+mgc1laB+xsgy4kYMf3FbKF2UBo= +github.com/gofrs/flock v0.13.1/go.mod h1:sf4BFiHwnvgxa25DlQoDqXQnwRMEOwqxRq37P6MzzmE= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -279,14 +285,16 @@ github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golangci/asciicheck v0.5.0 h1:jczN/BorERZwK8oiFBOGvlGPknhvq0bjnysTj4nUfo0= github.com/golangci/asciicheck v0.5.0/go.mod h1:5RMNAInbNFw2krqN6ibBxN/zfRFa9S6tA1nPdM0l8qQ= +github.com/golangci/canonicalheader v0.0.0-20260827115959-a25c71c521f6 h1:fVLolA3dG6s0brGetsiDAtcnpMSwVa2LqXJEw/9RJG4= +github.com/golangci/canonicalheader v0.0.0-20260827115959-a25c71c521f6/go.mod h1:1xo+NFW5S+bEf2DKXhaxuzvcDN5AR6o/KMwvqC5Mkpo= github.com/golangci/dupl v0.0.0-20260401084720-c99c5cf5c202 h1:CbTB8KpqnViI6lIXxp03Oclc4VFHi3K4BWC1TacsZ+A= github.com/golangci/dupl v0.0.0-20260401084720-c99c5cf5c202/go.mod h1:NUw9Zr2Sy7+HxzdjIULge71wI6yEg1lWQr7Evcu8K0E= github.com/golangci/go-printf-func-name v0.1.1 h1:hIYTFJqAGp1iwoIfsNTpoq1xZAarogrvjO9AfiW3B4U= github.com/golangci/go-printf-func-name v0.1.1/go.mod h1:Es64MpWEZbh0UBtTAICOZiB+miW53w/K9Or/4QogJss= -github.com/golangci/gofmt v0.0.0-20250106114630-d62b90e6713d h1:viFft9sS/dxoYY0aiOTsLKO2aZQAPT4nlQCsimGcSGE= -github.com/golangci/gofmt v0.0.0-20250106114630-d62b90e6713d/go.mod h1:ivJ9QDg0XucIkmwhzCDsqcnxxlDStoTl89jDMIoNxKY= -github.com/golangci/golangci-lint/v2 v2.12.2 h1:7+d1uY0bq1MU2UV3R5pW5Q7QWdcoq4naMRXM+gsJKrs= -github.com/golangci/golangci-lint/v2 v2.12.2/go.mod h1:opqHHuIcTG2R+4akzWMd4o1BnD9/1LcjICWOujr91U8= +github.com/golangci/gofmt v0.0.0-20260820135601-e84e05053792 h1:WL8YKrt3UbOBqSRU7GpP5BTtQTMWtVtj+mfPijgZeIg= +github.com/golangci/gofmt v0.0.0-20260820135601-e84e05053792/go.mod h1:te5hX0dW4C5r6YbXs+6ysNr8Q5UTmdIqGbb+mlFiYmA= +github.com/golangci/golangci-lint/v2 v2.14.0 h1:ot8QffRa4LzAAEgtvNYrVs9esxQ7xoTwoAv6uhp4ngA= +github.com/golangci/golangci-lint/v2 v2.14.0/go.mod h1:amHimCzNDP1GfVno3hIiNssjvqXmuW+CPZAHOdR4cIc= github.com/golangci/golines v0.15.0 h1:Qnph25g8Y1c5fdo1X7GaRDGgnMHgnxh4Gk4VfPTtRx0= github.com/golangci/golines v0.15.0/go.mod h1:AZjXd23tbHMpowhtnGlj9KCNsysj72aeZVVHnVcZx10= github.com/golangci/misspell v0.8.0 h1:qvxQhiE2/5z+BVRo1kwYA8yGz+lOlu5Jfvtx2b04Jbg= @@ -335,8 +343,8 @@ github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hf github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83 h1:z2ogiKUYzX5Is6zr/vP9vJGqPwcdqsWjOt+V8J7+bTc= -github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83/go.mod h1:MxpfABSjhmINe3F1It9d+8exIHFvUqtLIRCdOGNXqiI= +github.com/google/pprof v0.0.0-20260802141513-ef3492d7dac3 h1:LMLX+LgTNWpfvCBdFebv6EsYotImrt/Ppc5cXIriCSo= +github.com/google/pprof v0.0.0-20260802141513-ef3492d7dac3/go.mod h1:jl5iWTm0/hd5PjEYEOuwAJ57L/CibdZfrqZ5XA5GrCk= github.com/google/renameio v0.1.0 h1:GOZbcHa3HfsPKPlmyPyN2KEohoMXOhdMbHrvbpl2QaA= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= @@ -373,8 +381,8 @@ github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSo github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/jgautheron/goconst v1.10.0 h1:Ptt+OoE4NaEWKhLrWrrN3IpZdGLiqaf7WLnEX/iv4Jw= -github.com/jgautheron/goconst v1.10.0/go.mod h1:0p+wv1lFOiUr0IlNNT1nrm6+8DB8u2sU6KHGzFRXHDc= +github.com/jgautheron/goconst v1.11.0 h1:KgN90z5qXt5f0Uzf3cWXev3hfMMFUyNeKdpkSBRvLDk= +github.com/jgautheron/goconst v1.11.0/go.mod h1:0p+wv1lFOiUr0IlNNT1nrm6+8DB8u2sU6KHGzFRXHDc= github.com/jjti/go-spancheck v0.6.5 h1:lmi7pKxa37oKYIMScialXUK6hP3iY5F1gu+mLBPgYB8= github.com/jjti/go-spancheck v0.6.5/go.mod h1:aEogkeatBrbYsyW6y5TgDfihCulDYciL1B7rG2vSsrU= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= @@ -390,8 +398,8 @@ github.com/julz/importas v0.2.0 h1:y+MJN/UdL63QbFJHws9BVC5RpA2iq0kpjrFajTGivjQ= github.com/julz/importas v0.2.0/go.mod h1:pThlt589EnCYtMnmhmRYY/qn9lCf/frPOK+WMx3xiJY= github.com/karamaru-alpha/copyloopvar v1.2.2 h1:yfNQvP9YaGQR7VaWLYcfZUlRP2eo2vhExWKxD/fP6q0= github.com/karamaru-alpha/copyloopvar v1.2.2/go.mod h1:oY4rGZqZ879JkJMtX3RRkcXRkmUvH0x35ykgaKgsgJY= -github.com/kisielk/errcheck v1.10.0 h1:Lvs/YAHP24YKg08LA8oDw2z9fJVme090RAXd90S+rrw= -github.com/kisielk/errcheck v1.10.0/go.mod h1:kQxWMMVZgIkDq7U8xtG/n2juOjbLgZtedi0D+/VL/i8= +github.com/kisielk/errcheck v1.20.0 h1:9rwHBNKzd4wkDWcROy3DvFGNqEPlkxBg305rvk7HabI= +github.com/kisielk/errcheck v1.20.0/go.mod h1:O+f80MKNwX8Oor2jwgpeQ9An7uJm+hRSgT+h22knRJU= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kkHAIKE/contextcheck v1.1.6 h1:7HIyRcnyzxL9Lz06NGhiKvenXq7Zw6Q0UQu/ttjfJCE= github.com/kkHAIKE/contextcheck v1.1.6/go.mod h1:3dDbMRNBFaq8HFXWC1JyvDSPm43CmE6IuHam8Wr0rkg= @@ -411,24 +419,22 @@ github.com/kulti/thelper v0.7.1 h1:fI8QITAoFVLx+y+vSyuLBP+rcVIB8jKooNSCT2EiI98= github.com/kulti/thelper v0.7.1/go.mod h1:NsMjfQEy6sd+9Kfw8kCP61W1I0nerGSYSFnGaxQkcbs= github.com/kunwardeep/paralleltest v1.0.15 h1:ZMk4Qt306tHIgKISHWFJAO1IDQJLc6uDyJMLyncOb6w= github.com/kunwardeep/paralleltest v1.0.15/go.mod h1:di4moFqtfz3ToSKxhNjhOZL+696QtJGCFe132CbBLGk= -github.com/lasiar/canonicalheader v1.1.2 h1:vZ5uqwvDbyJCnMhmFYimgMZnJMjwljN5VGY0VKbMXb4= -github.com/lasiar/canonicalheader v1.1.2/go.mod h1:qJCeLFS0G/QlLQ506T+Fk/fWMa2VmBUiEI2cuMK4djI= github.com/ldez/exptostd v0.4.5 h1:kv2ZGUVI6VwRfp/+bcQ6Nbx0ghFWcGIKInkG/oFn1aQ= github.com/ldez/exptostd v0.4.5/go.mod h1:QRjHRMXJrCTIm9WxVNH6VW7oN7KrGSht69bIRwvdFsM= -github.com/ldez/gomoddirectives v0.8.0 h1:JqIuTtgvFC2RdH1s357vrE23WJF2cpDCPFgA/TWDGpk= -github.com/ldez/gomoddirectives v0.8.0/go.mod h1:jutzamvZR4XYJLr0d5Honycp4Gy6GEg2mS9+2YX3F1Q= +github.com/ldez/gomoddirectives v0.10.0 h1:Zb23rfMMkG21B6pZ9OfESzemByP5Qr7YvWcXQZE/S3Q= +github.com/ldez/gomoddirectives v0.10.0/go.mod h1:W9uh0721Vc7aaVoiMXTpKIveLmtHC9uaOSiHM4OK5a8= github.com/ldez/grignotin v0.10.1 h1:keYi9rYsgbvqAZGI1liek5c+jv9UUjbvdj3Tbn5fn4o= github.com/ldez/grignotin v0.10.1/go.mod h1:UlDbXFCARrXbWGNGP3S5vsysNXAPhnSuBufpTEbwOas= github.com/ldez/structtags v0.6.1 h1:bUooFLbXx41tW8SvkfwfFkkjPYvFFs59AAMgVg6DUBk= github.com/ldez/structtags v0.6.1/go.mod h1:YDxVSgDy/MON6ariaxLF2X09bh19qL7MtGBN5MrvbdY= -github.com/ldez/tagliatelle v0.7.2 h1:KuOlL70/fu9paxuxbeqlicJnCspCRjH0x8FW+NfgYUk= -github.com/ldez/tagliatelle v0.7.2/go.mod h1:PtGgm163ZplJfZMZ2sf5nhUT170rSuPgBimoyYtdaSI= +github.com/ldez/tagliatelle v0.8.0 h1:QczqVPa22JdT40p3m5JTTVmPMv5wDORqdomJn6/Pnn0= +github.com/ldez/tagliatelle v0.8.0/go.mod h1:K9n83Wv0wyVq3JYF9WI28BX8xXhDBVP3thHJ0ENuiEs= github.com/ldez/usetesting v0.5.0 h1:3/QtzZObBKLy1F4F8jLuKJiKBjjVFi1IavpoWbmqLwc= github.com/ldez/usetesting v0.5.0/go.mod h1:Spnb4Qppf8JTuRgblLrEWb7IE6rDmUpGvxY3iRrzvDQ= github.com/leonklingele/grouper v1.1.2 h1:o1ARBDLOmmasUaNDesWqWCIFH3u7hoFlM84YrjT3mIY= github.com/leonklingele/grouper v1.1.2/go.mod h1:6D0M/HVkhs2yRKRFZUoGjeDy7EZTfFBE9gl4kjmIGkA= -github.com/lucasb-eyer/go-colorful v1.4.0 h1:UtrWVfLdarDgc44HcS7pYloGHJUjHV/4FwW4TvVgFr4= -github.com/lucasb-eyer/go-colorful v1.4.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= +github.com/lucasb-eyer/go-colorful v1.4.1 h1:1EO+WB73+EH8EVbzlrG3KLAfEypQWVHIBqlTf+2hNss= +github.com/lucasb-eyer/go-colorful v1.4.1/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/macabu/inamedparam v0.2.0 h1:VyPYpOc10nkhI2qeNUdh3Zket4fcZjEWe35poddBCpE= github.com/macabu/inamedparam v0.2.0/go.mod h1:+Pee9/YfGe5LJ62pYXqB89lJ+0k5bsR8Wgz/C0Zlq3U= github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo= @@ -445,18 +451,18 @@ github.com/matoous/godox v1.1.0 h1:W5mqwbyWrwZv6OQ5Z1a/DHGMOvXYCBP3+Ht7KMoJhq4= github.com/matoous/godox v1.1.0/go.mod h1:jgE/3fUXiTurkdHOLT5WEkThTSuE7yxHv5iWPa80afs= github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE= github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= -github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= -github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= -github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= -github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-runewidth v0.0.23 h1:7ykA0T0jkPpzSvMS5i9uoNn2Xy3R383f9HDx3RybWcw= -github.com/mattn/go-runewidth v0.0.23/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= -github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk= -github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= +github.com/mattn/go-colorable v0.1.15 h1:+u9SLTRGnXv73cEsnsmoZBom+dMU88B2M0aDcWy0/jY= +github.com/mattn/go-colorable v0.1.15/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= +github.com/mattn/go-isatty v0.0.24 h1:tGZZoVgT/KiqK1c8ocVLeDS8BSWMRd47J3Lbz7vsReI= +github.com/mattn/go-isatty v0.0.24/go.mod h1:nMCL3Zebbrt45jsMDgnfIwz6ydEQApk5oEI3HqDio6A= +github.com/mattn/go-runewidth v0.0.28 h1:rPyg2ybwEKPebvpzVWe1gKBkH8EQFkxO4Y0hjBeLaBU= +github.com/mattn/go-runewidth v0.0.28/go.mod h1:3qAiGCV4Koz/yuveO58qUefmUTRm8r0IGEXZ9jeHp/8= +github.com/mattn/go-shellwords v1.0.14 h1:yUKzIgsCnosndOASY6/enly1EAuaXeFSQ7cdyA3OuYg= +github.com/mattn/go-shellwords v1.0.14/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/mgechev/revive v1.15.0 h1:vJ0HzSBzfNyPbHKolgiFjHxLek9KUijhqh42yGoqZ8Q= -github.com/mgechev/revive v1.15.0/go.mod h1:LlAKO3QQe9OJ0pVZzI2GPa8CbXGZ/9lNpCGvK4T/a8A= +github.com/mgechev/revive v1.17.0 h1:hwTTVC3svSv9X2FA2hiYT17Uws+lfxDWhPxzBZZCREg= +github.com/mgechev/revive v1.17.0/go.mod h1:uiVNUF8ruVh4WHm/aPs7kKEeuo+m6oO3tuOfniyzP/g= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= @@ -476,16 +482,16 @@ github.com/nakabonne/nestif v0.3.1 h1:wm28nZjhQY5HyYPx+weN3Q65k6ilSBxDb8v5S81B81 github.com/nakabonne/nestif v0.3.1/go.mod h1:9EtoZochLn5iUprVDmDjqGKPofoUEBL8U4Ngq6aY7OE= github.com/nilslice/protolock v0.17.0 h1:sYvcukABl62tZX77H6NuV+jtlwTIfQbn0ln0ixTqr4A= github.com/nilslice/protolock v0.17.0/go.mod h1:DYFqop7QlHjmBCaJKfcVO1Mw5b8JejJZgMvmFng/N9Y= -github.com/nishanths/exhaustive v0.12.0 h1:vIY9sALmw6T/yxiASewa4TQcFsVYZQQRUQJhKRf3Swg= -github.com/nishanths/exhaustive v0.12.0/go.mod h1:mEZ95wPIZW+x8kC4TgC+9YCUgiST7ecevsVDTgc2obs= +github.com/nishanths/exhaustive v0.13.0 h1:Rof0axOSNQaaPaPmLK5RxcRrJV2Vcv+vfUmuRy+2EAM= +github.com/nishanths/exhaustive v0.13.0/go.mod h1:3/adQjJgvD/UFHHSYBjpK9Wbz8DOvf0FBtzx+zI0Rlo= github.com/nishanths/predeclared v0.2.2 h1:V2EPdZPliZymNAn79T8RkNApBjMmVKh5XRpLm/w98Vk= github.com/nishanths/predeclared v0.2.2/go.mod h1:RROzoN6TnGQupbC+lqggsOlcgysk3LMK/HI84Mp280c= -github.com/nunnatsa/ginkgolinter v0.23.0 h1:x3o4DGYOWbBMP/VdNQKgSj+25aJKx2Pe6lHr8gBcgf8= -github.com/nunnatsa/ginkgolinter v0.23.0/go.mod h1:9qN1+0akwXEccwV1CAcCDfcoBlWXHB+ML9884pL4SZ4= -github.com/onsi/ginkgo/v2 v2.28.2 h1:DTrMfpqxiNUyQ3Y0zhn1n3cOO2euFgQPYIpkWwxVFps= -github.com/onsi/ginkgo/v2 v2.28.2/go.mod h1:CLtbVInNckU3/+gC8LzkGUb9oF+e8W8TdUsxPwvdOgE= -github.com/onsi/gomega v1.39.1 h1:1IJLAad4zjPn2PsnhH70V4DKRFlrCzGBNrNaru+Vf28= -github.com/onsi/gomega v1.39.1/go.mod h1:hL6yVALoTOxeWudERyfppUcZXjMwIMLnuSfruD2lcfg= +github.com/nunnatsa/ginkgolinter v0.24.0 h1:Mp0EagluLFP98JatP6nqp/gGEoljNG97uf9AcxcBVy8= +github.com/nunnatsa/ginkgolinter v0.24.0/go.mod h1:2ZMRuzX6+3XXyY6UZOwb6n+MCocVGbkIsDBC4vuWz5c= +github.com/onsi/ginkgo/v2 v2.32.1 h1:6tlvcDm/3sE8lGJbZ4+d4mO3RLy24/tQWOFzVSQNIfw= +github.com/onsi/ginkgo/v2 v2.32.1/go.mod h1:+aXOY+vzZ5mu2iI2HpTZUPmM//oQfsNFX6gU9kNcA44= +github.com/onsi/gomega v1.42.1 h1:iN1rCUX+44NZ1Dc97MPoeFYbFR0vh8zxoxMFwKdyZ6I= +github.com/onsi/gomega v1.42.1/go.mod h1:REff/hsDsodHoKlWsP2mAPhu1+5/6hVYNf9rIEBpeSg= 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= @@ -499,12 +505,11 @@ github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT9 github.com/otiai10/mint v1.3.1/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc= github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= -github.com/pelletier/go-toml/v2 v2.3.1 h1:MYEvvGnQjeNkRF1qUuGolNtNExTDwct51yp7olPtrEc= -github.com/pelletier/go-toml/v2 v2.3.1/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= +github.com/pelletier/go-toml/v2 v2.4.3 h1:GTRvJQutkOSftxIFD5xw9aepkYNuPWmVJpffdDPYVpY= +github.com/pelletier/go-toml/v2 v2.4.3/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= @@ -538,17 +543,15 @@ github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727 h1:TCg2WBOl github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727/go.mod h1:rlzQ04UMyJXu/aOvhd8qT+hvDrFpiwqp8MRXDY9szc0= github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 h1:M8mH9eK4OUR4lu7Gd+PU1fV2/qnDNfzT635KRSObncs= github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567/go.mod h1:DWNGW8A4Y+GyBgPuaQJuWiy0XYftx4Xm/y5Jqk9I6VQ= -github.com/raeperd/recvcheck v0.2.0 h1:GnU+NsbiCqdC2XX5+vMZzP+jAJC5fht7rcVTAhX74UI= -github.com/raeperd/recvcheck v0.2.0/go.mod h1:n04eYkwIR0JbgD73wT8wL4JjPC3wm0nFtzBnWNocnYU= -github.com/rhysd/actionlint v1.7.12 h1:vQ4GeJN86C0QH+gTUQcs8McmK62OLT3kmakPMtEWYnY= -github.com/rhysd/actionlint v1.7.12/go.mod h1:krOUhujIsJusovkaYzQ/VNH8PFexjNKqU0q5XI/4w+g= +github.com/raeperd/recvcheck v0.3.1 h1:p7nZMxiCWkdUtY2i3o+pv1h4fJCR5gpvqAXr9326hEY= +github.com/raeperd/recvcheck v0.3.1/go.mod h1:CbE7iRocQ4uc71JwpW/CPSh4BShnkre4A9Gb508RypU= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -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/rogpeppe/go-internal v1.16.0 h1:O9DK+vNMDVGLr2BeZqmpLeMjiMNkuXfcqntWbZV6S5g= +github.com/rogpeppe/go-internal v1.16.0/go.mod h1:DrUVZyrJU+txYW5/1kwtXQSMFio52ZOxX7yM1VHvnxs= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryancurrah/gomodguard v1.4.1 h1:eWC8eUMNZ/wM/PWuZBv7JxxqT5fiIKSIyTvjb7Elr+g= github.com/ryancurrah/gomodguard v1.4.1/go.mod h1:qnMJwV1hX9m+YJseXEBhd2s90+1Xn6x9dLz11ualI1I= @@ -558,14 +561,14 @@ github.com/ryanrolds/sqlclosecheck v0.6.0 h1:pEyL9okISdg1F1SEpJNlrEotkTGerv5BMk7 github.com/ryanrolds/sqlclosecheck v0.6.0/go.mod h1:xyX16hsDaCMXHrMJ3JMzGf5OpDfHTOTTQrT7HOFUmeU= github.com/sanposhiho/wastedassign/v2 v2.1.0 h1:crurBF7fJKIORrV85u9UUpePDYGWnwvv3+A96WvwXT0= github.com/sanposhiho/wastedassign/v2 v2.1.0/go.mod h1:+oSmSC+9bQ+VUAxA66nBb0Z7N8CK7mscKTDYC6aIek4= -github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 h1:KRzFb2m7YtdldCEkzs6KqmJw4nqEVZGK7IN2kJkjTuQ= -github.com/santhosh-tekuri/jsonschema/v6 v6.0.2/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU= +github.com/santhosh-tekuri/jsonschema/v6 v6.0.3 h1:1EYB5IzjZawrrnELUi78f9fPu57HuXjmddZPjrls/28= +github.com/santhosh-tekuri/jsonschema/v6 v6.0.3/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU= github.com/sashamelentyev/interfacebloat v1.1.0 h1:xdRdJp0irL086OyW1H/RTZTr1h/tMEOsumirXcOJqAw= github.com/sashamelentyev/interfacebloat v1.1.0/go.mod h1:+Y9yU5YdTkrNvoX0xHc84dxiN1iBi9+G8zZIhPVoNjQ= github.com/sashamelentyev/usestdlibvars v1.29.0 h1:8J0MoRrw4/NAXtjQqTHrbW9NN+3iMf7Knkq057v4XOQ= github.com/sashamelentyev/usestdlibvars v1.29.0/go.mod h1:8PpnjHMk5VdeWlVb4wCdrB8PNbLqZ3wBZTZWkrpZZL8= -github.com/securego/gosec/v2 v2.26.1 h1:gdkttGhQFVehqRJ8grKH4DrpqM/QlPKNHBnl8QgcEC4= -github.com/securego/gosec/v2 v2.26.1/go.mod h1:57UW4p0uoP3kxoTkhoo3axLdVAi+OWrLg/Ax/kdqtPE= +github.com/securego/gosec/v2 v2.29.0 h1:pF2HSLcnY5voqpxQumEe0O5YAAbvF4Syu4qeIewpLyc= +github.com/securego/gosec/v2 v2.29.0/go.mod h1:47C7ej+L2bC1NOxteU2aCGnCi4cZaoKzRt5y9plIvuY= github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/sethvargo/ratchet v0.12.0 h1:UY1sEevYHi28ryw9c8eHOLonc6oePZDUXHbzx0ESQqw= @@ -573,14 +576,14 @@ github.com/sethvargo/ratchet v0.12.0/go.mod h1:Unu+6zOcsnJj5x/hKgiaRKbGKPZkysoYk github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= -github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w= -github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g= +github.com/sirupsen/logrus v1.10.2 h1:G2SED73/qrAu6YwbdxOD6peLkCBI3z7L+ykJFTXJBBo= +github.com/sirupsen/logrus v1.10.2/go.mod h1:SLEg8TqYulVKKfIGHldVp2K2aYz2DKSVBq4g/H5bR7Q= github.com/sivchari/containedctx v1.0.3 h1:x+etemjbsh2fB5ewm5FeLNi5bUjK0V8n0RB+Wwfd0XE= github.com/sivchari/containedctx v1.0.3/go.mod h1:c1RDvCbnJLtH4lLcYD/GqwiBSSf4F5Qk0xld2rBqzJ4= github.com/sonatard/noctx v0.5.1 h1:wklWg9c9ZYugOAk7qG4yP4PBrlQsmSLPTvW1K4PRQMs= github.com/sonatard/noctx v0.5.1/go.mod h1:64XdbzFb18XL4LporKXp8poqZtPKbCrqQ402CV+kJas= -github.com/sourcegraph/go-diff v0.8.0 h1:ipIyu4cTsLbIrln4l0qtHA3r0a7gyK4ntKjtQytHhvY= -github.com/sourcegraph/go-diff v0.8.0/go.mod h1:hWlcO7Al+UZStZAP8rBumHpCK5ZHQ5BXsMls8p4+F5E= +github.com/sourcegraph/go-diff v0.9.0 h1:7xcVsayWHhvxZGrqt1o89vvBApCa4nyCgO7tNxDHJ+Q= +github.com/sourcegraph/go-diff v0.9.0/go.mod h1:hWlcO7Al+UZStZAP8rBumHpCK5ZHQ5BXsMls8p4+F5E= github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I= github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg= github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= @@ -601,13 +604,13 @@ github.com/stbenjam/no-sprintf-host-port v0.3.1 h1:AyX7+dxI4IdLBPtDbsGAyqiTSLpCP github.com/stbenjam/no-sprintf-host-port v0.3.1/go.mod h1:ODbZesTCHMVKthBHskvUUexdcNHAQRXk9NpSsL8p/HQ= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= -github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/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.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= -github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= +github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg= github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs= github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= github.com/tenntenn/modver v1.0.1 h1:2klLppGhDgzJrScMpkj9Ujy3rXPUspSjAcev9tSEBgA= @@ -616,10 +619,10 @@ github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3 h1:f+jULpR github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3/go.mod h1:ON8b8w4BN/kE1EOhwT0o+d62W65a6aPw1nouo9LMgyY= github.com/tetafro/godot v1.5.6 h1:IEkrFCwXaYHlOn4mGzGS3F3dkP6m9t0jpwqBFPIkKiA= github.com/tetafro/godot v1.5.6/go.mod h1:eOkMrVQurDui411nBY2FA05EYH01r14LuWY/NrVDVcU= -github.com/timakin/bodyclose v0.0.0-20260129054331-73d1f95b84b4 h1:SiHe5XLTn9sFWJ5pBwJ5FN/4j34q9ZlOAD//kMoMYp0= -github.com/timakin/bodyclose v0.0.0-20260129054331-73d1f95b84b4/go.mod h1:sDHLK7rb/59v/ZxZ7KtymgcoxuUMxjXq8gtu9VMOK8M= -github.com/timonwong/loggercheck v0.11.0 h1:jdaMpYBl+Uq9mWPXv1r8jc5fC3gyXx4/WGwTnnNKn4M= -github.com/timonwong/loggercheck v0.11.0/go.mod h1:HEAWU8djynujaAVX7QI65Myb8qgfcZ1uKbdpg3ZzKl8= +github.com/timakin/bodyclose v0.0.0-20260723120731-857993a2939c h1:d/FUP8GD4QiY/rdFzZmBu3KqL9eBNVzMu7W/5pGkaMM= +github.com/timakin/bodyclose v0.0.0-20260723120731-857993a2939c/go.mod h1:sDHLK7rb/59v/ZxZ7KtymgcoxuUMxjXq8gtu9VMOK8M= +github.com/timonwong/loggercheck v0.12.0 h1:7Cb9XGLJZSWYNjgL459DIhpSiEHupD+MLQ6vaN2Pl3g= +github.com/timonwong/loggercheck v0.12.0/go.mod h1:Wa6Y+SJHQR01mViZlLLY5GAZVOWOLNMbkXddMjFWQCs= github.com/tomarrell/wrapcheck/v2 v2.12.0 h1:H/qQ1aNWz/eeIhxKAFvkfIA+N7YDvq6TWVFL27Of9is= github.com/tomarrell/wrapcheck/v2 v2.12.0/go.mod h1:AQhQuZd0p7b6rfW+vUwHm5OMCGgp63moQ9Qr/0BpIWo= github.com/tommy-muehle/go-mnd/v2 v2.5.1 h1:NowYhSdyE/1zwK9QCLeRb6USWdoif80Ie+v+yU8u1Zw= @@ -630,12 +633,12 @@ github.com/ultraware/whitespace v0.2.0 h1:TYowo2m9Nfj1baEQBjuHzvMRbp19i+RCcRYrSW github.com/ultraware/whitespace v0.2.0/go.mod h1:XcP1RLD81eV4BW8UhQlpaR+SDc2givTvyI8a586WjW8= github.com/uudashr/gocognit v1.2.1 h1:CSJynt5txTnORn/DkhiB4mZjwPuifyASC8/6Q0I/QS4= github.com/uudashr/gocognit v1.2.1/go.mod h1:acaubQc6xYlXFEMb9nWX2dYBzJ/bIjEkc1zzvyIZg5Q= -github.com/uudashr/iface v1.4.2 h1:06Vq5RKVYThBsj0Bnw4oasMjD1r+7CE/bcKOA8dVSvg= -github.com/uudashr/iface v1.4.2/go.mod h1:pbeBPlbuU2qkNDn0mmfrxP2X+wjPMIQAy+r1MBXSXtg= +github.com/uudashr/iface v1.5.1 h1:BS3yrgaT55s3dAtJoxuTnsZNbsejq/mVuzp49YDyfBA= +github.com/uudashr/iface v1.5.1/go.mod h1:5UWoT6SvTdTww/KToRj6clO+n4Kg3/NrAQvgOB+5Ggw= github.com/xen0n/gosmopolitan v1.3.0 h1:zAZI1zefvo7gcpbCOrPSHJZJYA9ZgLfJqtKzZ5pHqQM= github.com/xen0n/gosmopolitan v1.3.0/go.mod h1:rckfr5T6o4lBtM1ga7mLGKZmLxswUoH1zxHgNXOsEt4= -github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= -github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= +github.com/xo/terminfo v1.0.0 h1:2ZpYzqWzyyytjk3TP6aJVDhkMAkc99/1xKQdA3TDTBY= +github.com/xo/terminfo v1.0.0/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= github.com/yagipy/maintidx v1.0.0 h1:h5NvIsCz+nRDapQ0exNv4aJ0yXSI0420omVANTv3GJM= github.com/yagipy/maintidx v1.0.0/go.mod h1:0qNf/I/CCZXSMhsRsrEPDZ+DkekpKLXAJfsTACwgXLk= github.com/yeya24/promlinter v0.3.0 h1:JVDbMp08lVCP7Y6NP3qHroGAO6z2yGKQtS5JsjqtoFs= @@ -658,8 +661,8 @@ go-simpler.org/sloglint v0.12.0 h1:UzWDlLWNE5FLqsvyq3tWYHuQMbqrervOhT8qPl4Mmw4= go-simpler.org/sloglint v0.12.0/go.mod h1:jBjjC2bm8rYrs88oTRlFX497kWjJsyZWYoNaXkGRI6I= go.augendre.info/arangolint v0.4.0 h1:xSCZjRoS93nXazBSg5d0OGCi9APPLNMmmLrC995tR50= go.augendre.info/arangolint v0.4.0/go.mod h1:l+f/b4plABuFISuKnTGD4RioXiCCgghv2xqst/xOvAA= -go.augendre.info/fatcontext v0.9.0 h1:Gt5jGD4Zcj8CDMVzjOJITlSb9cEch54hjRRlN3qDojE= -go.augendre.info/fatcontext v0.9.0/go.mod h1:L94brOAT1OOUNue6ph/2HnwxoNlds9aXDF2FcUntbNw= +go.augendre.info/fatcontext v0.10.1 h1:HCS/+eob32cM0mEtmZM6NiXdqvS5KiZmsR+fCubTpbc= +go.augendre.info/fatcontext v0.10.1/go.mod h1:pqpGvA9GlrXy+aXkp8L2dKz12Zp4g2FhzcAtwToU+2w= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= @@ -671,10 +674,11 @@ go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ= go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= -go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= -go.yaml.in/yaml/v4 v4.0.0-rc.3 h1:3h1fjsh1CTAPjW7q/EMe+C8shx5d8ctzZTrLcs/j8Go= -go.yaml.in/yaml/v4 v4.0.0-rc.3/go.mod h1:aZqd9kCMsGL7AuUv/m/PvWLdg5sjJsZ4oHDEnfPPfY0= +go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= +go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= +go.yaml.in/yaml/v4 v4.0.0-rc.6 h1:1h7H1ohdUh93/FyE4YaDa1Zh64K6VVbjF4K6WUxMtH4= +go.yaml.in/yaml/v4 v4.0.0-rc.6/go.mod h1:aZqd9kCMsGL7AuUv/m/PvWLdg5sjJsZ4oHDEnfPPfY0= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -696,8 +700,8 @@ golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b h1:M2rDM6z3Fhozi9O7NWsxAkg/y golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8= golang.org/x/exp/typeparams v0.0.0-20220428152302-39d4317da171/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= golang.org/x/exp/typeparams v0.0.0-20230203172020-98cc5a0785f9/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= -golang.org/x/exp/typeparams v0.0.0-20260209203927-2842357ff358 h1:qWFG1Dj7TBjOjOvhEOkmyGPVoquqUKnIU0lEVLp8xyk= -golang.org/x/exp/typeparams v0.0.0-20260209203927-2842357ff358/go.mod h1:4Mzdyp/6jzw9auFDJ3OMF5qksa7UvPnzKqTVGcb04ms= +golang.org/x/exp/typeparams v0.0.0-20260811152304-ee035b5b010f h1:+lI8cDJ4uceLipg2f1ODay7fEuLkk0BIHXd6PB8icxo= +golang.org/x/exp/typeparams v0.0.0-20260811152304-ee035b5b010f/go.mod h1:PqrXSW65cXDZH0k4IeUbhmg/bcAZDbzNz3byBpKCsXo= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -722,8 +726,8 @@ golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.38.0 h1:MECBjubtXD7yj4HrhIUcywNaGeNVUdfVnxmPajOk4yk= -golang.org/x/mod v0.38.0/go.mod h1:V6Xz0pq8TQ3dGqVQ1FVHuelZpAL0uNhSkk9ogYP3c40= +golang.org/x/mod v0.42.0 h1:ICeL3t2L30ljSPHHxDnR4fqVcBt/zR2zJAb9juavmQg= +golang.org/x/mod v0.42.0/go.mod h1:u5fzIrRInkL8MaO1cbRkUHT64v303YIT5KEqpNr7jRs= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -758,8 +762,8 @@ golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.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.61.0 h1:RR+6j/BTBCrggOLugxa+66R8FvoN6MHlzDlZIbN9YFw= +golang.org/x/net v0.61.0/go.mod h1:WgDCOTH8iwtB66T4/GfI94AHo/hhdb7Hah9NDseQfiI= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -781,8 +785,8 @@ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= -golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/sync v0.24.0 h1:iqiDrGoDgyuvAKrhLQthUEzU/R378N78gXbBjxed7oA= +golang.org/x/sync v0.24.0/go.mod h1:sUUOizhqBxiL6pEWpqNLUiaJn1ShEbZ6BBqskPbjZm0= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -823,15 +827,13 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211105183446-c75c47738b0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs= -golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= -golang.org/x/telemetry v0.0.0-20260708182218-49f421fb7959 h1:RJhm5l6Fo4rmEIcndxDllNhhf/fAx8qIm4t6A7vpm2A= -golang.org/x/telemetry v0.0.0-20260708182218-49f421fb7959/go.mod h1:LV7u5Oco+Z/g6XI7PqN+EUUUGGkEcmB1uj2ceI0fOVg= +golang.org/x/sys v0.49.0 h1:XbzkgYJHdqh/8m2Uu0W/dQv8nktxx4BFHp1M0gROTXA= +golang.org/x/sys v0.49.0/go.mod h1:hNLxWAXmnKAxqDtdwIYC4bM9oQPEecfsnNMuSxOs3og= +golang.org/x/telemetry v0.0.0-20261010193011-f71dc92da45e h1:P6NGsugec4oASJ+yKMNgTndeo0h2CbixZLvQIe9h5R4= +golang.org/x/telemetry v0.0.0-20261010193011-f71dc92da45e/go.mod h1:GNu+nb+E5axfGmP+Uu3ZVQu5lE/Xk42MjZOHWUD94/o= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= @@ -843,8 +845,8 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg= -golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164= +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.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -895,14 +897,14 @@ golang.org/x/tools v0.1.1-0.20210302220138-2ac05c832e1a/go.mod h1:9bzcO0MWcOuT0t golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.48.0 h1:3+hClM1aLL5mjMKm5ovokw9epgRXPuu2tILgismM6RE= -golang.org/x/tools v0.48.0/go.mod h1:08xX0orndb/F7jJxGDicx061tyd5pcMto75YMAXr6lk= +golang.org/x/tools v0.52.0 h1:ryxg4U63oaUYk7uOLTF37n43f4qgtGvqV4Ioit1BaEs= +golang.org/x/tools v0.52.0/go.mod h1:ItcP5UNaVGG+Yc4eIYmaFLNJ4soEopkx5Y2d4lcEurs= golang.org/x/tools/go/expect v0.1.1-deprecated h1:jpBZDwmgPhXsKZC6WhL20P4b/wmnpsEAGHaNy0n/rJM= golang.org/x/tools/go/expect v0.1.1-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY= golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated h1:1h2MnaIAIXISqTFKdENegdpAgUXz6NrPEsbIeWaBRvM= golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated/go.mod h1:RVAQXBGNv1ib0J382/DPCRS/BPnsGebyM1Gj5VSDpG8= -golang.org/x/vuln v1.6.0 h1:FeMO9Rm/HwyduOztbvKcOw+zvDEPr4I4aQNSfevFcKY= -golang.org/x/vuln v1.6.0/go.mod h1:bWlG2493/sjR7ksvicBgMrznH3eYQEyK8ifUYBrqUbg= +golang.org/x/vuln v1.9.0 h1:12VaqnI9kJfqwRoPptxoqsUhQtdfrSbdksT7X/KY/sc= +golang.org/x/vuln v1.9.0/go.mod h1:E/Frwm0xp49fTyBW+6CQHdkvobknbwUM49yEpMGUauw= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -982,14 +984,13 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= -google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc= +google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= @@ -1011,12 +1012,12 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -honnef.co/go/tools v0.7.0 h1:w6WUp1VbkqPEgLz4rkBzH/CSU6HkoqNLp6GstyTx3lU= -honnef.co/go/tools v0.7.0/go.mod h1:pm29oPxeP3P82ISxZDgIYeOaf9ta6Pi0EWvCFoLG2vc= -mvdan.cc/gofumpt v0.9.2 h1:zsEMWL8SVKGHNztrx6uZrXdp7AX8r421Vvp23sz7ik4= -mvdan.cc/gofumpt v0.9.2/go.mod h1:iB7Hn+ai8lPvofHd9ZFGVg2GOr8sBUw1QUWjNbmIL/s= -mvdan.cc/unparam v0.0.0-20251027182757-5beb8c8f8f15 h1:ssMzja7PDPJV8FStj7hq9IKiuiKhgz9ErWw+m68e7DI= -mvdan.cc/unparam v0.0.0-20251027182757-5beb8c8f8f15/go.mod h1:4M5MMXl2kW6fivUT6yRGpLLPNfuGtU2Z0cPvFquGDYU= +honnef.co/go/tools v0.8.1 h1:+JKf3xJ1ni4CwrhVg4/pqsfPGP6vNAXcKbMXJodYx3w= +honnef.co/go/tools v0.8.1/go.mod h1:XA+OnlRA9EDh/ukGvXMNSZNKGwFQJ+5dER0ioUkOxks= +mvdan.cc/gofumpt v0.12.0 h1:1Lbudkz2kpM9Cjz2pL4M19u7q+GaEhCTNf7N9mfpcho= +mvdan.cc/gofumpt v0.12.0/go.mod h1:SmBHHrljiZu/uoypeKup3rFzP6eoC9UwCp2iH5E3jZA= +mvdan.cc/unparam v0.0.0-20260823230713-2fa3d841b0c8 h1:Re1NRyLpiAt9kB+ImaaoapwWiQXrKwER4tY8fLOkDew= +mvdan.cc/unparam v0.0.0-20260823230713-2fa3d841b0c8/go.mod h1:MrS/+zJ1M2xvGXhKktiHbNQeQPyg3Qel+KkzT+f7/i4= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/tools/linters/tools-import.go b/tools/linters/tools-import.go index c25dd3d3ed84e..ba96ee84647f5 100644 --- a/tools/linters/tools-import.go +++ b/tools/linters/tools-import.go @@ -8,9 +8,9 @@ package tools import ( // Tool dependencies, not used anywhere in the code. + _ "actionlint.kjanat.dev/cmd/actionlint" _ "github.com/golangci/golangci-lint/v2/cmd/golangci-lint" _ "github.com/nilslice/protolock" - _ "github.com/rhysd/actionlint/cmd/actionlint" _ "github.com/sethvargo/ratchet" _ "golang.org/x/vuln/cmd/govulncheck" ) diff --git a/tools/roxvet/common/traverse.go b/tools/roxvet/common/traverse.go index 5a4d2ce5fd22c..358a16d3498a9 100644 --- a/tools/roxvet/common/traverse.go +++ b/tools/roxvet/common/traverse.go @@ -2,6 +2,7 @@ package common import ( "go/ast" + "slices" "golang.org/x/tools/go/ast/inspector" ) @@ -11,7 +12,7 @@ import ( func FilteredPreorder(inspector *inspector.Inspector, fileFilter FileFilter, nodeTypes []ast.Node, fn func(n ast.Node)) { // Limit the nodeTypes slice's capacity to its length. This ensures that any call to `append` will not write into // the backing storage, but allocate a new slice. - effectiveTypes := nodeTypes[:len(nodeTypes):len(nodeTypes)] + effectiveTypes := slices.Clip(nodeTypes) hadFile := hasFile(nodeTypes) if !hadFile { effectiveTypes = append(effectiveTypes, (*ast.File)(nil)) @@ -33,7 +34,7 @@ func FilteredPreorder(inspector *inspector.Inspector, fileFilter FileFilter, nod // FilteredNodes calls `inspector.Nodes(nodeTypes, fn)`, but filters out all files that do not pass the given // fileFilter. func FilteredNodes(inspector *inspector.Inspector, fileFilter FileFilter, nodeTypes []ast.Node, fn func(n ast.Node, push bool) bool) { - effTypes := nodeTypes[:len(nodeTypes):len(nodeTypes)] + effTypes := slices.Clip(nodeTypes) hadFile := hasFile(nodeTypes) if !hadFile { effTypes = append(effTypes, (*ast.File)(nil)) diff --git a/tools/test/go.mod b/tools/test/go.mod index 4eeed19adcd11..a3abe16a62fec 100644 --- a/tools/test/go.mod +++ b/tools/test/go.mod @@ -4,7 +4,7 @@ go 1.26.3 require ( github.com/jstemmer/go-junit-report/v2 v2.1.0 - github.com/stackrox/image-prefetcher/deploy v0.5.2 + github.com/stackrox/image-prefetcher/deploy v0.6.0 github.com/stackrox/prometheus-metric-parser v0.0.0-20240718111853-be492d554cd5 ) diff --git a/tools/test/go.sum b/tools/test/go.sum index b84af8b3dd2ab..9e50e8218aac6 100644 --- a/tools/test/go.sum +++ b/tools/test/go.sum @@ -63,8 +63,8 @@ github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4 github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/stackrox/image-prefetcher/deploy v0.5.2 h1:b8VP/i6xD46aAxMHq9nLgyq4vMYqOxrkssUkxGMskh0= -github.com/stackrox/image-prefetcher/deploy v0.5.2/go.mod h1:bAhl/GuKJnAchWpAYkEaoqfaJHqCkbBKhLDWHm1GXLs= +github.com/stackrox/image-prefetcher/deploy v0.6.0 h1:KLH2unXZSFEbPOakWliu9XXRmzInytkGiNk3zQtuWxs= +github.com/stackrox/image-prefetcher/deploy v0.6.0/go.mod h1:rz+MNwLZL+Ur3rNABFPggUIfW/z/BOdfKfmCS/3L83g= github.com/stackrox/prometheus-metric-parser v0.0.0-20240718111853-be492d554cd5 h1:gAg8XyBlWRq8Ia4uTPmhNUnVYtiDN4eiqviJ+tD87xw= github.com/stackrox/prometheus-metric-parser v0.0.0-20240718111853-be492d554cd5/go.mod h1:y4d4bwvOygvByIxM/g0XCzBKMryqXilCk4Lj+mMBsVM= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= diff --git a/ui/apps/platform/README.md b/ui/apps/platform/README.md index 456c6968fbfaf..9ef4fa12b792f 100644 --- a/ui/apps/platform/README.md +++ b/ui/apps/platform/README.md @@ -452,6 +452,12 @@ Given a feature flag environment variable `"ROX_WHATEVER"` in pkg/features/list. customize_envVars+=$'\n value: "true"' ``` + * Add `env_with_default ROX_WHATEVER "true"` to `collect_feature_flags` function in tests/e2e/lib-compat.sh + + roxie-based e2e deployments read feature flag overrides from this separate list, not from + `export_test_environment`. Skipping this step makes `TestFeatureFlagSettings` fail in + roxie-based jobs because the Go test process and the deployed Central disagree on the flag's value. + The value of feature flags for **demo** and **release** builds is in pkg/features/list.go 5. To turn on a feature flag for **local deployment**, do either or both of the following: @@ -523,6 +529,8 @@ Given a feature flag environment variable `"ROX_WHATEVER"` in pkg/features/list. customize_envVars+=$'\n value: "true"' ``` + * Delete `env_with_default ROX_WHATEVER "true"` from `collect_feature_flags` function in tests/e2e/lib-compat.sh + ### Routes #### Add a route diff --git a/ui/apps/platform/TESTING_E2E.md b/ui/apps/platform/TESTING_E2E.md index 8f8756a146f28..bc4d28eddacba 100644 --- a/ui/apps/platform/TESTING_E2E.md +++ b/ui/apps/platform/TESTING_E2E.md @@ -116,7 +116,7 @@ This sets `localStorage.access_token` from the `ROX_AUTH_TOKEN` env var in `befo ## Feature Flag Gating -Tests that depend on a feature flag should skip when the flag is not enabled. The test runner scripts (`scripts/cypress.sh`) fetch flags from the deployment API and export them as `CYPRESS_ROX_*` env vars. +Tests that depend on a feature flag should skip when the flag is not enabled. The test runner scripts (`scripts/cypress.sh`) fetch flags from the deployment API and export them as `CYPRESS_ROX_*` env vars, which `cypress.config.js` copies to `Cypress.expose()` for synchronous access in tests. For individual tests, skip inside the `it` block: @@ -263,7 +263,7 @@ Globally applicable helper functions (`cypress/helpers/*`). Use these frequently | `helpers/request.js` | API interception and waiting | `interceptRequests`, `waitForResponses`, `interactAndWaitForResponses`, `interceptAndWatchRequests`, `interceptAndOverridePermissions`, `interceptAndOverrideFeatureFlags` | | `helpers/visit.js` | Page navigation with auto auth handling and request awaiting | `visit`, `visitWithStaticResponseForPermissions`, `visitWithStaticResponseForAuthStatus` | | `helpers/basicAuth.js` | Test authentication setup | `withAuth` (default export) | -| `helpers/features.js` | Feature flag and orchestrator checks | `hasFeatureFlag`, `hasOrchestratorFlavor` | +| `helpers/features.js` | Feature flag and orchestrator checks via `Cypress.expose()` | `hasFeatureFlag`, `hasOrchestratorFlavor` | | `helpers/nav.ts` | Left navigation interactions | `visitFromLeftNav`, `visitFromLeftNavExpandable` | | `helpers/formHelpers.js` | Form element interactions | `getInputByLabel`, `getSelectButtonByLabel`, `getSelectOption`, `generateNameWithDate` | | `helpers/tableHelpers.ts` | Table row/column interactions | `getTableRowLinkByName`, `openTableRowActionMenu`, `sortByTableHeader`, `assertOnEachRowForColumn` | diff --git a/ui/apps/platform/cypress.config.js b/ui/apps/platform/cypress.config.js index d41c983d4040a..d0b0de761e573 100644 --- a/ui/apps/platform/cypress.config.js +++ b/ui/apps/platform/cypress.config.js @@ -7,6 +7,24 @@ const cypressVite = require('cypress-vite'); // and make the path available at test runtime when injecting the axe core. const axeCorePath = require.resolve('axe-core/axe.min.js'); +const PUBLIC_ENV_KEYS = ['ORCHESTRATOR_FLAVOR']; + +/* + * Cypress only fills expose from the config file or the --expose CLI flag, so copy the public + * CYPRESS_* values exported by scripts/cypress.sh. Feature flag names come from /v1/featureflags + * at runtime, so match them by prefix and boolean value. Secrets such as ROX_AUTH_TOKEN, + * DEMO_PASSWORD, and the OCP console credentials stay readable only through cy.env(). + */ +function getPublicEnv(env = {}) { + return Object.fromEntries( + Object.entries(env).filter( + ([key, value]) => + PUBLIC_ENV_KEYS.includes(key) || + (key.startsWith('ROX_') && typeof value === 'boolean') + ) + ); +} + /* * The helper function intended to provide automatic code completion for configuration in many popular code editors * had subtle side-effect to cause some typescript-eslint/no-unsafe-return errors in unit test files. @@ -16,6 +34,7 @@ const axeCorePath = require.resolve('axe-core/axe.min.js'); */ module.exports = { + allowCypressEnv: false, chromeWebSecurity: false, // Browser options defaultCommandTimeout: 8000, // Timeouts options numTestsKeptInMemory: 0, // Global options @@ -37,7 +56,11 @@ module.exports = { viewportWidth: 1440, // Viewport options setupNodeEvents: (on, config) => { // eslint-disable-next-line no-param-reassign - config.env.AXE_CORE_PATH = axeCorePath; + config.expose = { + ...config.expose, + ...getPublicEnv(config.env), + AXE_CORE_PATH: axeCorePath, + }; on('task', { beforeSuite(spec) { // eslint-disable-next-line no-console diff --git a/ui/apps/platform/cypress/helpers/features.js b/ui/apps/platform/cypress/helpers/features.js index fabd7989c2447..4102f03468a91 100644 --- a/ui/apps/platform/cypress/helpers/features.js +++ b/ui/apps/platform/cypress/helpers/features.js @@ -2,9 +2,9 @@ * Return whether or not the testing environment has a feature flag. */ export function hasFeatureFlag(flag) { - return Cypress.env(flag) || false; + return Cypress.expose(flag) || false; } export function hasOrchestratorFlavor(value) { - return Cypress.env('ORCHESTRATOR_FLAVOR') === value; + return Cypress.expose('ORCHESTRATOR_FLAVOR') === value; } diff --git a/ui/apps/platform/cypress/helpers/main.js b/ui/apps/platform/cypress/helpers/main.js index d5cc4298bd905..bc5dc00a201d2 100644 --- a/ui/apps/platform/cypress/helpers/main.js +++ b/ui/apps/platform/cypress/helpers/main.js @@ -1,6 +1,5 @@ import pf6 from '../selectors/pf6'; -import { hasFeatureFlag } from './features'; import { getRouteMatcherMapForGraphQL } from './request'; import { visit, visitConsole, visitWithStaticResponseForPermissions } from './visit'; @@ -42,9 +41,6 @@ const routeMatcherMapForViolationsByPolicyCategory = { url: '/v1/alerts/summary/counts?request.query=&group_by=CATEGORY', }, }; -const routeMatcherMapForComplianceLevelsByStandard = getRouteMatcherMapForGraphQL([ - getAggregatedResultsOpname, -]); function getRouteMatcherMap() { return { @@ -55,9 +51,6 @@ function getRouteMatcherMap() { ...routeMatcherMapForDeploymentsAtMostRisk, ...routeMatcherMapForAgingImages, ...routeMatcherMapForViolationsByPolicyCategory, - ...(hasFeatureFlag('ROX_DEPRECATED_COMPLIANCE_DASHBOARD') - ? routeMatcherMapForComplianceLevelsByStandard - : {}), }; } diff --git a/ui/apps/platform/cypress/integration/compliance/Compliance.helpers.js b/ui/apps/platform/cypress/integration/compliance/Compliance.helpers.js deleted file mode 100644 index f2afd1c987a40..0000000000000 --- a/ui/apps/platform/cypress/integration/compliance/Compliance.helpers.js +++ /dev/null @@ -1,211 +0,0 @@ -import { getRouteMatcherMapForGraphQL, interactAndWaitForResponses } from '../../helpers/request'; -import { visit } from '../../helpers/visit'; - -// path - -export const basePath = '/main/compliance'; - -function getEntitiesPath(entitiesKey) { - return `${basePath}/${entitiesKey}`; -} - -const segmentForEntity = { - clusters: 'cluster', - controls: 'control', - deployments: 'deployment', - namespaces: 'namespace', - nodes: 'node', -}; - -function getEntityPagePath(entitiesKey) { - return `${basePath}/${segmentForEntity[entitiesKey]}`; -} - -// opname - -const opnamesWithoutStandards = [ - 'clustersCount', - 'namespacesCount', - 'nodesCount', - 'deploymentsCount', - 'runStatuses', - 'getAggregatedResultsAcrossEntity_CLUSTER', - 'getAggregatedResultsByEntity_CLUSTER', - 'getAggregatedResultsAcrossEntity_NAMESPACE', - 'getAggregatedResultsAcrossEntity_NODE', -]; - -export const routeMatcherMapWithoutStandards = - getRouteMatcherMapForGraphQL(opnamesWithoutStandards); - -// TODO are these reliable after hideScanResults feature? -/* -const opnamesOfStandards = [ - 'complianceStandards_CIS_Kubernetes_v1_5', - 'complianceStandards_HIPAA_164', - 'complianceStandards_NIST_800_190', - 'complianceStandards_NIST_SP_800_53_Rev_4', - 'complianceStandards_PCI_DSS_3_2', -]; -*/ - -const routeMatcherMapForComplianceDashboard = getRouteMatcherMapForGraphQL(opnamesWithoutStandards); - -const opnameForEntities = { - clusters: 'clustersList', // just clusters would be even better, and so on - controls: 'controls', - deployments: 'deploymentsList', - namespaces: 'namespaceList', // singular: too bad, so sad - nodes: 'nodesList', -}; - -const opnameForEntity = { - clusters: 'getClusterName', - deployments: 'getDeployment', - namespaces: 'getNamespace', - nodes: 'getNode', -}; - -// heading - -export const headingPlural = { - clusters: 'Clusters', - deployments: 'Deployments', - namespaces: 'Namespaces', - nodes: 'Nodes', -}; - -export const headingSingular = { - clusters: 'Cluster', - deployments: 'Deployment', - namespaces: 'Namespace', - nodes: 'Node', -}; - -// assert - -// assert instead of interact because query might be cached. -export function assertComplianceEntityPage(entitiesKey) { - cy.location('pathname').should('contain', getEntityPagePath(entitiesKey)); // contain because pathname has id - cy.get(`h1 + div:contains("${headingSingular[entitiesKey]}")`); -} - -// visit - -export function visitComplianceDashboard() { - visit(basePath, routeMatcherMapForComplianceDashboard); - - cy.get(`h1:contains("Compliance")`); -} - -/* - * Assume location is compliance dashboard. - */ -function scanCompliance() { - const routeMatcherMapForTriggerScan = getRouteMatcherMapForGraphQL(['triggerScan']); - const routeMatcherMap = { - ...routeMatcherMapForTriggerScan, - ...routeMatcherMapForComplianceDashboard, - }; - - const scanButton = '[data-testid="scan-button"]'; - - interactAndWaitForResponses( - () => { - cy.get(scanButton).click(); - }, - routeMatcherMap, - undefined, - { timeout: 20000 } - ); - - // Note: scan results are polled for every 10 seconds, this should give us plenty of time - // to await completion of the scan. - cy.get('div:contains("Compliance scanning complete")', { - timeout: 60000, - }); -} - -/* - * Call directly in first test (of a test file for another container) which assumes that scan results are available. - * Although compliance test file bends the guideline that tests should not depend on side effects within the same test file, - * configmanagement test files break the guideline if they assume that compliance test file has already run. - * Cypress 10 apparently no longer runs tests files in a determinate order. - */ -export function triggerScan() { - visitComplianceDashboard(); - scanCompliance(); -} - -/* - * For example, visitComplianceEntities('clusters') - */ -export function visitComplianceEntities(entitiesKey) { - const routeMatcherMap = getRouteMatcherMapForGraphQL([ - 'searchOptions', - opnameForEntities[entitiesKey], - ]); - - visit(getEntitiesPath(entitiesKey), routeMatcherMap); - - cy.get(`h1:contains("${headingPlural[entitiesKey]}")`); -} - -/* - * For example, visitComplianceStandard('CIS Docker v1.2.0') - */ -export function visitComplianceStandard(standardName) { - const routeMatcherMap = getRouteMatcherMapForGraphQL([ - 'searchOptions', - 'getComplianceStandards', - 'controls', - ]); - - visit(`${getEntitiesPath('controls')}?s[standard]=${standardName}`, routeMatcherMap); - - cy.get(`h1:contains("${standardName}")`); -} - -export function interactAndWaitForComplianceEntities(interactionCallback, entitiesKey) { - const opname = opnameForEntities[entitiesKey]; - const routeMatcherMap = getRouteMatcherMapForGraphQL([opname]); - interactAndWaitForResponses(interactionCallback, routeMatcherMap); - - cy.location('pathname').should('eq', getEntitiesPath(entitiesKey)); - cy.get(`h1:contains("${headingPlural[entitiesKey]}")`); -} - -export function interactAndWaitForComplianceEntityInSidePanel(interactionCallback, entitiesKey) { - const opname = opnameForEntity[entitiesKey]; - const routeMatcherMap = getRouteMatcherMapForGraphQL([opname]); - interactAndWaitForResponses(interactionCallback, routeMatcherMap); - - cy.location('pathname').should('contain', getEntitiesPath(entitiesKey)); // contain because pathname has id -} - -export function interactAndWaitForComplianceStandard(interactionCallback) { - const entitiesKey = 'controls'; - const opname = opnameForEntities[entitiesKey]; - const routeMatcherMap = getRouteMatcherMapForGraphQL([opname]); - interactAndWaitForResponses(interactionCallback, routeMatcherMap); - - cy.location('pathname').should('eq', getEntitiesPath(entitiesKey)); - cy.get('h1 + div:contains("Standard")'); -} - -// verify - -/* - * For example, verifyDashboardEntityLink('clusters', /^\d+ clusters?/)) - */ -export function verifyDashboardEntityLink(entitiesKey, entityRegExp) { - cy.get('[data-testid="page-header"]') - .contains('a', entityRegExp) - .then(($a) => { - const [, count] = /^(\d+) /.exec($a.text()); - interactAndWaitForComplianceEntities(() => { - cy.wrap($a).click(); - }, entitiesKey); - cy.get(`[data-testid="panel-header"]:contains("${count}")`); - }); -} diff --git a/ui/apps/platform/cypress/integration/compliance/Compliance.selectors.js b/ui/apps/platform/cypress/integration/compliance/Compliance.selectors.js deleted file mode 100644 index 9d761aa263966..0000000000000 --- a/ui/apps/platform/cypress/integration/compliance/Compliance.selectors.js +++ /dev/null @@ -1,13 +0,0 @@ -export const selectors = { - list: { - table: { - firstGroup: '.table-group-active:first', - firstTableGroup: '.rt-table:first', - firstRow: 'div.rt-tr-group > .rt-tr.-odd:first', - firstRowName: 'div.rt-tr-group > .rt-tr.-odd:first .rt-td a', - secondRowName: 'div.rt-tr-group > .rt-tr.-even:first .rt-td a', - firstStandard: 'div.rt-table .rt-thead .rt-th:nth-child(3)', - firstPercentage: 'div.rt-tr-group > .rt-tr.-odd > .rt-td:nth-child(3)', - }, - }, -}; diff --git a/ui/apps/platform/cypress/integration/compliance/complianceDashboard.test.js b/ui/apps/platform/cypress/integration/compliance/complianceDashboard.test.js deleted file mode 100644 index 51b372c1ae4b7..0000000000000 --- a/ui/apps/platform/cypress/integration/compliance/complianceDashboard.test.js +++ /dev/null @@ -1,96 +0,0 @@ -import withAuth from '../../helpers/basicAuth'; -import { hasFeatureFlag } from '../../helpers/features'; -import { getRegExpForTitleWithBranding } from '../../helpers/title'; - -import { - interactAndWaitForComplianceStandard, - triggerScan, - verifyDashboardEntityLink, - visitComplianceDashboard, -} from './Compliance.helpers'; -import { selectors } from './Compliance.selectors'; - -function getWidgetSelector(headerText) { - return `.widget:has([data-testid="widget-header"]:contains("${headerText}"))`; -} - -function getStandardAcrossEntitiesLink(entityNameOrdinaryCasePlural) { - return `${getWidgetSelector(`Passing standards across ${entityNameOrdinaryCasePlural}`)} a`; -} - -describe('Compliance Dashboard', () => { - withAuth(); - - before(function () { - if (!hasFeatureFlag('ROX_DEPRECATED_COMPLIANCE_DASHBOARD')) { - this.skip(); - } - }); - - it('should scan for compliance data', () => { - triggerScan(); // prerequisite for the following tests - }); - - it('should have title', () => { - visitComplianceDashboard(); - - cy.title().should('match', getRegExpForTitleWithBranding('Compliance')); - }); - - it('should show the same amount of clusters as list', () => { - visitComplianceDashboard(); - - verifyDashboardEntityLink('clusters', /^\d+ clusters?/); // include ^ but omit $ - }); - - it('should show the same amount of namespaces as list', () => { - visitComplianceDashboard(); - - verifyDashboardEntityLink('namespaces', /^\d+ namespaces?/); // include ^ but omit $ - }); - - it('should show the same amount of nodes as list', () => { - visitComplianceDashboard(); - - verifyDashboardEntityLink('nodes', /^\d+ nodes?/); // include ^ but omit $ - }); - - it('should show the same amount of deployments as list', () => { - visitComplianceDashboard(); - - verifyDashboardEntityLink('deployments', /^\d+ deployments?/); // include ^ but omit $ - }); - - it('should link from Passing Standards Across Clusters widget to standards grouped by clusters list', () => { - visitComplianceDashboard(); - - interactAndWaitForComplianceStandard(() => { - cy.get(getStandardAcrossEntitiesLink('clusters')).first().click(); - }); - cy.location('search').should('contain', '?s[groupBy]=CLUSTER'); // followed by a standard - cy.get('[data-testid="panel-header"]').contains('cluster'); - cy.get(selectors.list.table.firstGroup).should('be.visible'); - }); - - it('should link from Passing Standards Across Namespaces widget to standards grouped by namespaces list', () => { - visitComplianceDashboard(); - - interactAndWaitForComplianceStandard(() => { - cy.get(getStandardAcrossEntitiesLink('namespaces')).first().click(); - }); - cy.location('search').should('contain', '?s[groupBy]=NAMESPACE'); // followed by a standard - cy.get('[data-testid="panel-header"]').contains('namespace'); - cy.get(selectors.list.table.firstGroup).should('be.visible'); - }); - - it('should link from Passing Standards Across Nodes widget to standards grouped by nodes list', () => { - visitComplianceDashboard(); - - interactAndWaitForComplianceStandard(() => { - cy.get(getStandardAcrossEntitiesLink('nodes')).first().click(); - }); - cy.location('search').should('contain', '?s[groupBy]=NODE'); // followed by a standard - cy.get('[data-testid="panel-header"]').contains('node'); - cy.get(selectors.list.table.firstGroup).should('be.visible'); - }); -}); diff --git a/ui/apps/platform/cypress/integration/compliance/complianceList.test.js b/ui/apps/platform/cypress/integration/compliance/complianceList.test.js deleted file mode 100644 index 32a6ca5097d6c..0000000000000 --- a/ui/apps/platform/cypress/integration/compliance/complianceList.test.js +++ /dev/null @@ -1,134 +0,0 @@ -import withAuth from '../../helpers/basicAuth'; -import { hasFeatureFlag } from '../../helpers/features'; -import searchSelectors from '../../selectors/search'; - -import { - assertComplianceEntityPage, - interactAndWaitForComplianceEntities, - interactAndWaitForComplianceEntityInSidePanel, - triggerScan, - visitComplianceEntities, - visitComplianceStandard, -} from './Compliance.helpers'; -import { selectors } from './Compliance.selectors'; - -describe('Compliance entities list', () => { - withAuth(); - - before(function () { - if (!hasFeatureFlag('ROX_DEPRECATED_COMPLIANCE_DASHBOARD')) { - this.skip(); - } - }); - - // TODO: ROX-30939: fix test for OCP4.20 "1 namespace" (openshift-cloud-credential-operator) - it.skip('should filter namespaces table with passing controls', () => { - triggerScan(); // in case complianceDashboard.test.js is skipped - visitComplianceEntities('namespaces'); - - interactAndWaitForComplianceEntities(() => { - cy.get(searchSelectors.input).type('Compliance State:'); - cy.get(searchSelectors.input).type('{enter}'); - cy.get(searchSelectors.input).type('Pass'); - cy.get(searchSelectors.input).type('{enter}'); - }, 'namespaces'); - cy.get('.rt-tbody .rt-tr').should('not.exist'); - cy.get('[data-testid="panel-header"]').should('contain', '0 namespaces'); - }); - - it('should filter namespaces table with failing controls', () => { - visitComplianceEntities('namespaces'); - - interactAndWaitForComplianceEntities(() => { - cy.get(searchSelectors.input).type('Compliance State:'); - cy.get(searchSelectors.input).type('{enter}'); - cy.get(searchSelectors.input).type('Fail'); - cy.get(searchSelectors.input).type('{enter}'); - }, 'namespaces'); - cy.get('.rt-tbody .rt-tr'); - cy.get('[data-testid="panel-header"]').should('contain', 'namespace'); - }); - - it('should open/close side panel when clicking on a table row', () => { - visitComplianceEntities('clusters'); - - cy.get(selectors.list.table.firstRowName) - .invoke('text') - .then((name) => { - cy.get('[data-testid="panel-header"]').should('contain', 'cluster'); - cy.get(selectors.list.table.firstRow).click(); - cy.get('[data-testid="side-panel"]').should('exist'); - cy.get('[data-testid="side-panel-header"]').contains(name); - cy.get('[data-testid="side-panel"] [aria-label="Close"]').click(); - cy.get('[data-testid="side-panel"]').should('not.exist'); - cy.get('[data-testid="panel-header"]').should('contain', 'cluster'); - }); - }); - - it('should have the same percentage from list to entity widget', () => { - visitComplianceEntities('clusters'); - - cy.get(selectors.list.table.firstRow).click(); - cy.get(selectors.list.table.firstStandard) - .invoke('text') - .then((firstStandard) => { - cy.get(selectors.list.table.firstPercentage) - .invoke('text') - .then((firstTablePercentage) => { - cy.get(`[data-testid="${firstStandard}-compliance"] div.rv-sunburst text`) - .invoke('text') - .then((firstWidgetPercentage) => - expect(firstTablePercentage).to.equal(firstWidgetPercentage) - ); - }); - }); - }); - - it('should link to entity page when clicking on side panel header', () => { - visitComplianceEntities('clusters'); - - cy.get(selectors.list.table.firstRowName) - .invoke('text') - .then((name) => { - interactAndWaitForComplianceEntityInSidePanel(() => { - cy.get(selectors.list.table.firstRow).click(); - }, 'clusters'); - cy.get('[data-testid="side-panel-header"]').contains(name); - cy.get('[data-testid="side-panel-header"]').click(); - assertComplianceEntityPage('clusters'); - }); - }); - - it('should be sorted by version in standards list', () => { - visitComplianceStandard('CIS Kubernetes v1.5'); - - cy.get(selectors.list.table.firstRowName) - .invoke('text') - .then((text1) => { - cy.get(selectors.list.table.secondRowName) - .invoke('text') - .then((text2) => { - const arr1 = text1.split(' ')[0]; - const controlArr1 = arr1.split('.'); - const arr2 = text2.split(' ')[0]; - const controlArr2 = arr2.split('.'); - expect(parseInt(controlArr1[0], 10)).to.be.at.most( - parseInt(controlArr2[0], 10) - ); - if (controlArr1[1] && controlArr2[1]) { - expect(parseInt(controlArr1[1], 10)).to.be.at.most( - parseInt(controlArr2[1], 10) - ); - } - }); - }); - }); - - it('should collapse/open table grouping', () => { - visitComplianceStandard('PCI DSS 3.2.1'); - - cy.get(selectors.list.table.firstTableGroup).should('be.visible'); - cy.get(selectors.list.table.firstGroup).click(); - cy.get(selectors.list.table.firstTableGroup).should('not.be.visible'); - }); -}); diff --git a/ui/apps/platform/cypress/integration/compliance/hideScanResults.helpers.js b/ui/apps/platform/cypress/integration/compliance/hideScanResults.helpers.js deleted file mode 100644 index ff12927049367..0000000000000 --- a/ui/apps/platform/cypress/integration/compliance/hideScanResults.helpers.js +++ /dev/null @@ -1,52 +0,0 @@ -import { interactAndWaitForResponses } from '../../helpers/request'; - -import { routeMatcherMapWithoutStandards } from './Compliance.helpers'; - -// selectors - -export const selectorForModal = `[role="dialog"]:has('h1:contains("Manage standards")')`; - -export function selectorInModal(selector) { - return `${selectorForModal} ${selector}`; -} -export function selectorForWidget(title) { - return `.widget:has('[data-testid="widget-header"]:contains("${title}")')`; -} - -export function selectorInWidget(title, selector) { - return `${selectorForWidget(title)} ${selector}`; -} - -// interactions - -export function openModal() { - const routeMatcherMap = { - 'compliance/standards': { - method: 'GET', - url: '/v1/compliance/standards', - }, - }; - - interactAndWaitForResponses(() => { - cy.get('button:contains("Manage standards")').click(); - }, routeMatcherMap); - - cy.get(selectorForModal); -} - -export function clickSaveAndWaitForPatchComplianceStandards(standardIds) { - const routeMatcherMapForPatch = Object.fromEntries( - standardIds.map((standardId) => [ - `PATCH_${standardId}`, - { method: 'PATCH', url: `/v1/compliance/standards/${standardId}` }, - ]) - ); - const routeMatcherMap = { - ...routeMatcherMapForPatch, - ...routeMatcherMapWithoutStandards, - }; - - return interactAndWaitForResponses(() => { - cy.get(selectorInModal('button:contains("Save")')).click(); - }, routeMatcherMap); // TODO GraphQL requests? -} diff --git a/ui/apps/platform/cypress/integration/compliance/hideScanResults.test.js b/ui/apps/platform/cypress/integration/compliance/hideScanResults.test.js deleted file mode 100644 index ddb88bd6ded79..0000000000000 --- a/ui/apps/platform/cypress/integration/compliance/hideScanResults.test.js +++ /dev/null @@ -1,128 +0,0 @@ -import withAuth from '../../helpers/basicAuth'; -import { hasFeatureFlag } from '../../helpers/features'; -import { getInputByLabel } from '../../helpers/formHelpers'; - -import { triggerScan, visitComplianceDashboard } from './Compliance.helpers'; -import { - clickSaveAndWaitForPatchComplianceStandards, - openModal, - selectorForModal, - selectorInModal, - selectorInWidget, -} from './hideScanResults.helpers'; - -const titleAcrossClusters = 'Passing standards across clusters'; -const titleAcrossNamespaces = 'Passing standards across namespaces'; - -const forHIPAA = { - standardId: 'HIPAA_164', - barLink: 'a:contains("HIPAA")', - checkboxLabel: 'HIPAA 164', -}; - -const forNIST190 = { - standardId: 'NIST_800_190', - barLink: 'a:contains("NIST SP 800-190")', - checkboxLabel: 'NIST SP 800-190', -}; - -const forNIST53 = { - standardId: 'NIST_SP_800_53_Rev_4', - barLink: 'a:contains("NIST SP 800-53")', - checkboxLabel: 'NIST SP 800-53', -}; - -function assertExistenceOfStandard(forStandard, existence) { - const { barLink } = forStandard; - const existOrNot = existence ? 'exist' : 'not.exist'; - - cy.get(selectorInWidget(titleAcrossClusters, barLink)).should(existOrNot); - cy.get(selectorInWidget(titleAcrossNamespaces, barLink)).should(existOrNot); - - // TODO columns in entity tables -} - -function assertCheckedAndClickStandard(forStandard, checked) { - const { checkboxLabel } = forStandard; - const haveAttrOrNot = checked ? 'have.attr' : 'not.have.attr'; - - getInputByLabel(checkboxLabel).should(haveAttrOrNot, 'checked'); - getInputByLabel(checkboxLabel).click(); -} - -describe('Compliance hideScanResults', () => { - withAuth(); - - before(function () { - if (!hasFeatureFlag('ROX_DEPRECATED_COMPLIANCE_DASHBOARD')) { - this.skip(); - } - }); - - it('should open modal and then cancel', () => { - triggerScan(); // in case complianceDashboard.test.js is skipped - openModal(); - - cy.get(selectorInModal('button:contains("Save")')).should('be.disabled'); - cy.get(selectorInModal('button:contains("Cancel")')).click(); - cy.get(selectorForModal).should('not.exist'); - }); - - it('should hide HIPAA standard', () => { - visitComplianceDashboard(); - - assertExistenceOfStandard(forHIPAA, true); - - openModal(); - - assertCheckedAndClickStandard(forHIPAA, true); - clickSaveAndWaitForPatchComplianceStandards([forHIPAA.standardId]); - - assertExistenceOfStandard(forHIPAA, false); - }); - - // The following test depends on the preceding test. - - it('should show HIPAA standard and hide NIST standards', () => { - visitComplianceDashboard(); - - assertExistenceOfStandard(forHIPAA, false); - assertExistenceOfStandard(forNIST190, true); - assertExistenceOfStandard(forNIST53, true); - - openModal(); - assertCheckedAndClickStandard(forHIPAA, false); - assertCheckedAndClickStandard(forNIST190, true); - assertCheckedAndClickStandard(forNIST53, true); - - clickSaveAndWaitForPatchComplianceStandards([ - forHIPAA.standardId, - forNIST190.standardId, - forNIST53.standardId, - ]); - - assertExistenceOfStandard(forHIPAA, true); - assertExistenceOfStandard(forNIST190, false); - assertExistenceOfStandard(forNIST53, false); - }); - - // The following test depends on the preceding test. - - it('should show NIST standards', () => { - visitComplianceDashboard(); - - assertExistenceOfStandard(forNIST190, false); - assertExistenceOfStandard(forNIST53, false); - - openModal(); - assertCheckedAndClickStandard(forNIST190, false); - assertCheckedAndClickStandard(forNIST53, false); - - clickSaveAndWaitForPatchComplianceStandards([forNIST190.standardId, forNIST53.standardId]); - - assertExistenceOfStandard(forNIST190, true); - assertExistenceOfStandard(forNIST53, true); - }); - - // hideScanResults is same at the end as it was at the beginning -}); diff --git a/ui/apps/platform/cypress/integration/configmanagement/ConfigurationManagement.helpers.js b/ui/apps/platform/cypress/integration/configmanagement/ConfigurationManagement.helpers.js index 3e45540f1e401..a9b78b2fcc5e6 100644 --- a/ui/apps/platform/cypress/integration/configmanagement/ConfigurationManagement.helpers.js +++ b/ui/apps/platform/cypress/integration/configmanagement/ConfigurationManagement.helpers.js @@ -15,7 +15,6 @@ function getEntitiesPath(entitiesKey) { const segmentForEntity = { clusters: 'cluster', - controls: 'control', deployments: 'deployment', images: 'image', namespaces: 'namespace', @@ -34,7 +33,6 @@ function getEntityPagePath(entitiesKey, id = '') { // Heading on entities page has sentence case for entity type. const headingForEntities = { clusters: 'Clusters', - controls: 'Controls', deployments: 'Deployments', images: 'Images', namespaces: 'Namespaces', @@ -49,7 +47,6 @@ const headingForEntities = { // Heading on entity page or side panel has sentence case for entity type. const headingForEntity = { clusters: 'Cluster', - controls: 'Control', deployments: 'Deployment', images: 'Image', namespaces: 'Namespace', @@ -62,10 +59,8 @@ const headingForEntity = { }; function tableHeaderRegExp(entitiesKey) { - const singular = - entitiesKey === 'controls' ? 'CIS Control' : headingForEntity[entitiesKey].toLowerCase(); - const plural = - entitiesKey === 'controls' ? 'CIS Controls' : headingForEntities[entitiesKey].toLowerCase(); + const singular = headingForEntity[entitiesKey].toLowerCase(); + const plural = headingForEntities[entitiesKey].toLowerCase(); // Complexity to exclude 1 for plural. // Double backslash \\d needed for RegExp constructor unlike RegExp literal. @@ -74,7 +69,6 @@ function tableHeaderRegExp(entitiesKey) { const countNounRegExp = { // clusters has singular link by name - controls: /\d+ Controls?$/, deployments: /^\d+ deployments?$/, images: /\d+ images?$/, // namespaces @@ -89,7 +83,6 @@ const countNounRegExp = { // Title of widget is title case but has uppercase style. const widgetTitleForEntities = { clusters: 'Clusters', - controls: 'CIS Controls', deployments: 'Deployments', images: 'Images', namespaces: 'Namespaces', @@ -117,7 +110,6 @@ function getRouteMatcherMapForEntities(entitiesKey) { const opnameForEntity = { clusters: 'getCluster', - controls: 'getControl', deployments: 'getDeployment', images: 'getImage', namespaces: 'getNamespace', @@ -136,7 +128,6 @@ function getRouteMatcherMapForEntity(entitiesKey) { const typeOfEntity = { clusters: 'CLUSTER', - controls: 'CONTROL', deployments: 'DEPLOYMENT', images: 'IMAGE', namespaces: 'NAMESPACE', @@ -152,12 +143,8 @@ function opnameForPrimaryAndSecondaryEntities(entitiesKey1, entitiesKey2) { return `${opnameForEntity[entitiesKey1]}_${typeOfEntity[entitiesKey2]}`; } -const routeMatcherMapForConfigurationManagementDashboard = getRouteMatcherMapForGraphQL([ - 'complianceByControls', -]); - export function visitConfigurationManagementDashboard() { - visit(basePath, routeMatcherMapForConfigurationManagementDashboard); + visit(basePath); cy.get('h1:contains("Configuration Management")'); } @@ -245,22 +232,6 @@ export function interactAndWaitForConfigurationManagementSecondaryEntities( interactAndWaitForResponses(interactionCallback, routeMatcherMap); } -export function interactAndWaitForConfigurationManagementScan(interactionCallback) { - interactAndWaitForResponses( - interactionCallback, - routeMatcherMapForConfigurationManagementDashboard - ); - - // ROX-24912 - // Comment out for now and assume that scan results are available from compliance tests. - // Maybe interactAndWaitForResponses waits only for the first pair of responses from page visit. - // Apparently the negative assertion sometimes passes before ComplianceScanProgress renders. - // But auto-disappears prevents positive assertion as in scanCompliance helper function. - cy.get('div:contains("Compliance scanning in progress")', { - timeout: 60000, - }).should('not.exist'); -} - export function navigateToSingleEntityPage(entitiesKey) { // interactAndWaitForConfigurationManagementEntityPage(() => { // cy.get('[data-testid="side-panel"] [aria-label="link"]').click(); @@ -370,18 +341,11 @@ function verifyWidgetLinkToTable(entitiesKey1, entitiesKey2, contextSelector) { cy.get(`${selectors.countWidgets}:contains('${listEntity}') button`).click(); } - if ( - (entitiesKey1 === 'controls' && entitiesKey2 === 'nodes') || - (entitiesKey1 === 'nodes' && entitiesKey2 === 'controls') - ) { - clickCountWidget(); // no request - } else { - interactAndWaitForConfigurationManagementSecondaryEntities( - clickCountWidget, - entitiesKey1, - entitiesKey2 - ); - } + interactAndWaitForConfigurationManagementSecondaryEntities( + clickCountWidget, + entitiesKey1, + entitiesKey2 + ); cy.get(`${contextSelector} .rt-tr-group`); cy.get(`${contextSelector} [data-testid="panel-header"]`).contains( diff --git a/ui/apps/platform/cypress/integration/configmanagement/ConfigurationManagement.selectors.js b/ui/apps/platform/cypress/integration/configmanagement/ConfigurationManagement.selectors.js index 58dd303d26071..2600eb55be4dc 100644 --- a/ui/apps/platform/cypress/integration/configmanagement/ConfigurationManagement.selectors.js +++ b/ui/apps/platform/cypress/integration/configmanagement/ConfigurationManagement.selectors.js @@ -9,16 +9,6 @@ export const dashboardSelectors = { getWidget: (title) => { return `[data-testid="widget"]:contains('${title}')`; }, - cisStandardsAcrossClusters: { - widget: '[data-testid="compliance-by-controls"]', - select: { - input: '[data-testid="compliance-by-controls"] .react-select__control', - value: '[data-testid="compliance-by-controls"] .react-select__single-value', - options: '[data-testid="compliance-by-controls"] .react-select__option', - }, - passingControlsLink: 'a:contains("Controls Passing")', - failingControlsLinks: 'a:contains("Controls Failing")', - }, horizontalBars: '.rv-xy-plot__series.rv-xy-plot__series--bar > rect', }; diff --git a/ui/apps/platform/cypress/integration/configmanagement/ciscontrols.test.js b/ui/apps/platform/cypress/integration/configmanagement/ciscontrols.test.js deleted file mode 100644 index 51a1e8374087b..0000000000000 --- a/ui/apps/platform/cypress/integration/configmanagement/ciscontrols.test.js +++ /dev/null @@ -1,103 +0,0 @@ -import withAuth from '../../helpers/basicAuth'; -import { hasFeatureFlag, hasOrchestratorFlavor } from '../../helpers/features'; -import { triggerScan } from '../compliance/Compliance.helpers'; - -import { - clickOnCountWidget, - hasCountWidgetsFor, - hasTabsFor, - interactAndWaitForConfigurationManagementScan, - navigateToSingleEntityPage, - verifyWidgetLinkToTableFromSidePanel, - verifyWidgetLinkToTableFromSinglePage, - visitConfigurationManagementDashboard, - visitConfigurationManagementEntitiesWithSearch, - visitConfigurationManagementEntityInSidePanel, -} from './ConfigurationManagement.helpers'; - -const entitiesKey = 'controls'; - -describe('Configuration Management Controls', () => { - withAuth(); - - // Skip although Controls and CIS Kubernetes v1.5 are visible, because these tests assume compliance tests ran and triggered a scan (pardon rhyme). - before(function () { - if (!hasFeatureFlag('ROX_DEPRECATED_COMPLIANCE_DASHBOARD')) { - this.skip(); - } - }); - - before(function beforeHook() { - if (hasOrchestratorFlavor('openshift')) { - this.skip(); - } - }); - - it('should render the controls list and open the side panel when a row is clicked', () => { - // ROX-13537: See if compliance scan prevents failure of last tests because no Pass or no Fail status. - triggerScan(); - - visitConfigurationManagementDashboard(); - - // This and the following tests assumes that scan results are available - interactAndWaitForConfigurationManagementScan(() => { - cy.get('[data-testid="scan-button"]').click(); - }); - - visitConfigurationManagementEntityInSidePanel(entitiesKey); - }); - - it('should take you to a control single when the "navigate away" button is clicked', () => { - visitConfigurationManagementEntityInSidePanel(entitiesKey); - navigateToSingleEntityPage(entitiesKey); - }); - - it('should have the correct count widgets for a single entity view', () => { - visitConfigurationManagementEntityInSidePanel(entitiesKey); - navigateToSingleEntityPage(entitiesKey); - hasCountWidgetsFor(['Nodes']); - }); - - // ROX-13028: skip pending investigation why sometimes 0 nodes for control, therefore widget is disabled. - it.skip('should click on the nodes count widget in the entity page and show the nodes tab', () => { - visitConfigurationManagementEntityInSidePanel(entitiesKey); - navigateToSingleEntityPage(entitiesKey); - clickOnCountWidget('nodes', 'entityList'); - }); - - it('should have the correct tabs for a single entity view', () => { - visitConfigurationManagementEntityInSidePanel(entitiesKey); - navigateToSingleEntityPage(entitiesKey); - hasTabsFor(['nodes']); - }); - - describe('should go to nodes table from widget link', () => { - const entitiesKey2 = 'nodes'; - - it('in single page', () => { - verifyWidgetLinkToTableFromSinglePage(entitiesKey, entitiesKey2); - }); - - it('in side panel', () => { - verifyWidgetLinkToTableFromSidePanel(entitiesKey, entitiesKey2); - }); - }); - - it('should show no failing nodes in the control findings section of a passing control', () => { - visitConfigurationManagementEntitiesWithSearch(entitiesKey, '?s[Compliance%20State]=Pass'); - - // Click first row which has pass in Control Status column to open control in side panel. - cy.get(`.rt-td:nth-child(4):contains("Pass"):nth(0)`).click(); - // Control Findings - cy.get('[data-testid="widget"] .rt-tbody .rt-tr').should('have.length', 0); - }); - - it('should show failing nodes in the control findings section of a failing control', () => { - visitConfigurationManagementEntitiesWithSearch(entitiesKey, '?s[Compliance%20State]=Fail'); - - // Click first row which has fail in Control Status column to open control in side panel. - cy.get(`.rt-td:nth-child(4):contains("Fail"):nth(0)`).click(); - // Control Findings - cy.get('[data-testid="widget"] .rt-tbody .rt-tr').should('not.have.length', 0); - }); -}); diff --git a/ui/apps/platform/cypress/integration/configmanagement/clusters.test.js b/ui/apps/platform/cypress/integration/configmanagement/clusters.test.js index d0cd978d94576..e115a0ac38910 100644 --- a/ui/apps/platform/cypress/integration/configmanagement/clusters.test.js +++ b/ui/apps/platform/cypress/integration/configmanagement/clusters.test.js @@ -3,12 +3,10 @@ import withAuth from '../../helpers/basicAuth'; import { hasCountWidgetsFor, hasTabsFor, - interactAndWaitForConfigurationManagementScan, navigateToSingleEntityPage, verifyTableLinkToSidePanelTable, verifyWidgetLinkToTableFromSidePanel, verifyWidgetLinkToTableFromSinglePage, - visitConfigurationManagementDashboard, visitConfigurationManagementEntities, visitConfigurationManagementEntityInSidePanel, } from './ConfigurationManagement.helpers'; @@ -40,7 +38,6 @@ describe('Configuration Management Clusters', () => { 'Users & Groups', 'Service Accounts', 'Roles', - 'Controls', ]); }); @@ -56,7 +53,6 @@ describe('Configuration Management Clusters', () => { 'subjects', 'serviceaccounts', 'roles', - 'controls', ]); }); @@ -154,30 +150,6 @@ describe('Configuration Management Clusters', () => { }); }); - describe('should go to controls table from widget link', () => { - const entitiesKey2 = 'controls'; - - it('in single page', () => { - verifyWidgetLinkToTableFromSinglePage(entitiesKey, entitiesKey2); - }); - - it('in side panel', () => { - verifyWidgetLinkToTableFromSidePanel(entitiesKey, entitiesKey2); - }); - }); - - // ROX-13011: Prevent failures, pending investigation into reason why No Controls instead of link sometimes. - it.skip('should go from table link to controls table in side panel', () => { - visitConfigurationManagementDashboard(); - - // This test assumes that scan results are available - interactAndWaitForConfigurationManagementScan(() => { - cy.get('[data-testid="scan-button"]').click(); - }); - - verifyTableLinkToSidePanelTable(entitiesKey, 'controls'); - }); - it('should go from table link to subjects table in side panel', () => { verifyTableLinkToSidePanelTable(entitiesKey, 'subjects'); }); diff --git a/ui/apps/platform/cypress/integration/configmanagement/dashboard.test.js b/ui/apps/platform/cypress/integration/configmanagement/dashboard.test.js index 9c7d7f84d46f6..aba8af05eee90 100644 --- a/ui/apps/platform/cypress/integration/configmanagement/dashboard.test.js +++ b/ui/apps/platform/cypress/integration/configmanagement/dashboard.test.js @@ -1,11 +1,9 @@ import withAuth from '../../helpers/basicAuth'; -import { hasFeatureFlag, hasOrchestratorFlavor } from '../../helpers/features'; import { getRegExpForTitleWithBranding } from '../../helpers/title'; import { interactAndWaitForConfigurationManagementEntities, interactAndWaitForConfigurationManagementEntityInSidePanel, - // interactAndWaitForConfigurationManagementScan, visitConfigurationManagementDashboard, } from './ConfigurationManagement.helpers'; import { selectors } from './ConfigurationManagement.selectors'; @@ -56,33 +54,6 @@ describe('Configuration Management Dashboard', () => { }); }); - it('should show same number of controls between the tile and the controls list', function () { - // Skip although Controls and CIS Kubernetes v1.5 are visible, because these tests assume compliance tests ran and triggered a scan (pardon rhyme). - if (!hasFeatureFlag('ROX_DEPRECATED_COMPLIANCE_DASHBOARD')) { - this.skip(); - } - - const entitiesKey = 'controls'; - - visitConfigurationManagementDashboard(); - - cy.get(`${selectors.tileLinks}:eq(1) ${selectors.tileLinkValue}`) - .invoke('text') - .then((value) => { - const numControls = value; - - interactAndWaitForConfigurationManagementEntities(() => { - cy.get(`${selectors.tileLinks}:eq(1)`).click(); - }, entitiesKey); - - cy.get(`[data-testid="panel"] [data-testid="panel-header"]`) - .invoke('text') - .then((panelHeaderText) => { - expect(parseInt(panelHeaderText, 10)).to.equal(parseInt(numControls, 10)); - }); - }); - }); - it('should properly navigate to the policies list', () => { const entitiesKey = 'policies'; @@ -93,21 +64,6 @@ describe('Configuration Management Dashboard', () => { }, entitiesKey); }); - it('should properly navigate to the cis controls list', function () { - // Skip although Controls and CIS Kubernetes v1.5 are visible, because these tests assume compliance tests ran and triggered a scan (pardon rhyme). - if (!hasFeatureFlag('ROX_DEPRECATED_COMPLIANCE_DASHBOARD')) { - this.skip(); - } - - const entitiesKey = 'controls'; - - visitConfigurationManagementDashboard(); - - interactAndWaitForConfigurationManagementEntities(() => { - cy.get(`${selectors.tileLinks}:eq(1)`).click(); - }, entitiesKey); - }); - it('should properly navigate to the clusters list', () => { const entitiesKey = 'clusters'; @@ -219,23 +175,6 @@ describe('Configuration Management Dashboard', () => { }, entitiesKey); }); - it('go to controls list from View link in CIS widget', function () { - // Skip although Controls and CIS Kubernetes v1.5 are visible, because these tests assume compliance tests ran and triggered a scan (pardon rhyme). - if (!hasFeatureFlag('ROX_DEPRECATED_COMPLIANCE_DASHBOARD')) { - this.skip(); - } - - const entitiesKey = 'controls'; - - visitConfigurationManagementDashboard(); - - interactAndWaitForConfigurationManagementEntities(() => { - cy.get(selectors.cisStandardsAcrossClusters.widget) - .find('a:contains("View standard")') - .click(); - }, entitiesKey); - }); - it('should go to subjects (users and groups) list from View link in Users widget', () => { const entitiesKey = 'subjects'; @@ -289,62 +228,6 @@ describe('Configuration Management Dashboard', () => { cy.location('search').should('contain', '[Policy%20Status]='); // either Fail (for rated as Whatever) or Pass (for policies without violations) }); - it('clicking the "CIS Standard Across Clusters" widget\'s "passing controls" link should take you to the controls list and filter by passing controls', function () { - // Skip although Controls and CIS Kubernetes v1.5 are visible, because these tests assume compliance tests ran and triggered a scan (pardon rhyme). - if (!hasFeatureFlag('ROX_DEPRECATED_COMPLIANCE_DASHBOARD')) { - this.skip(); - } - - if (hasOrchestratorFlavor('openshift')) { - this.skip(); - } - - const entitiesKey = 'controls'; - - visitConfigurationManagementDashboard(); - - // This and the following test assumes that scan results are available - // ROX-24912 - // Comment out for now and assume that scan results are available from compliance tests. - // See function for more details. - /* - interactAndWaitForConfigurationManagementScan(() => { - cy.get('[data-testid="scan-button"]').click(); - }); - */ - - interactAndWaitForConfigurationManagementEntities(() => { - cy.get(selectors.cisStandardsAcrossClusters.widget) - .find(selectors.cisStandardsAcrossClusters.passingControlsLink) - .click(); - }, entitiesKey); - - cy.location('search').should('contain', '[Compliance%20State]=Pass'); - }); - - it('clicking the "CIS Standard Across Clusters" widget\'s "failing controls" link should take you to the controls list and filter by failing controls', function () { - // Skip although Controls and CIS Kubernetes v1.5 are visible, because these tests assume compliance tests ran and triggered a scan (pardon rhyme). - if (!hasFeatureFlag('ROX_DEPRECATED_COMPLIANCE_DASHBOARD')) { - this.skip(); - } - - if (hasOrchestratorFlavor('openshift')) { - this.skip(); - } - - const entitiesKey = 'controls'; - - visitConfigurationManagementDashboard(); - - interactAndWaitForConfigurationManagementEntities(() => { - cy.get(selectors.cisStandardsAcrossClusters.widget) - .find(selectors.cisStandardsAcrossClusters.failingControlsLinks) - .click(); - }, entitiesKey); - - cy.location('search').should('contain', '[Compliance%20State]=Fail'); - }); - it('should open side panel from link in Secrets widget', () => { const entitiesKey = 'secrets'; diff --git a/ui/apps/platform/cypress/integration/configmanagement/nodes.test.js b/ui/apps/platform/cypress/integration/configmanagement/nodes.test.js index 0a67650f72de9..87551bbab72f2 100644 --- a/ui/apps/platform/cypress/integration/configmanagement/nodes.test.js +++ b/ui/apps/platform/cypress/integration/configmanagement/nodes.test.js @@ -1,18 +1,10 @@ import withAuth from '../../helpers/basicAuth'; -import { hasFeatureFlag, hasOrchestratorFlavor } from '../../helpers/features'; -import { triggerScan } from '../compliance/Compliance.helpers'; import { - clickEntityTableRowThatHasLinkInColumn, - clickOnCountWidget, clickOnSingleEntityInTable, clickOnSingularEntityWidgetInSidePanel, - hasCountWidgetsFor, hasRelatedEntityFor, - hasTabsFor, navigateToSingleEntityPage, - verifyWidgetLinkToTableFromSidePanel, - verifyWidgetLinkToTableFromSinglePage, visitConfigurationManagementEntityInSidePanel, } from './ConfigurationManagement.helpers'; @@ -44,53 +36,4 @@ describe('Configuration Management Nodes', () => { navigateToSingleEntityPage(entitiesKey); hasRelatedEntityFor('Cluster'); }); - - it('should have the correct count widgets for a single entity view', () => { - visitConfigurationManagementEntityInSidePanel(entitiesKey); - navigateToSingleEntityPage(entitiesKey); - hasCountWidgetsFor(['Controls']); - }); - - it('should have the correct tabs for a single entity view', () => { - visitConfigurationManagementEntityInSidePanel(entitiesKey); - navigateToSingleEntityPage(entitiesKey); - hasTabsFor(['controls']); - }); - - it('should click on the controls count widget in the entity page and show the controls tab', function () { - // Skip although Controls and CIS Kubernetes v1.5 are visible, because these tests assume compliance tests ran and triggered a scan (pardon rhyme). - if (!hasFeatureFlag('ROX_DEPRECATED_COMPLIANCE_DASHBOARD')) { - this.skip(); - } - - if (hasOrchestratorFlavor('openshift')) { - this.skip(); - } - - triggerScan(); // because test assumes that scan results are available - - const columnIndexForControls = 7; - clickEntityTableRowThatHasLinkInColumn(entitiesKey, columnIndexForControls); - navigateToSingleEntityPage(entitiesKey); - clickOnCountWidget('controls', 'entityList'); - }); - - describe('should go to controls table from widget link', () => { - // Skip although Controls and CIS Kubernetes v1.5 are visible, because these tests assume compliance tests ran and triggered a scan (pardon rhyme). - before(function () { - if (!hasFeatureFlag('ROX_DEPRECATED_COMPLIANCE_DASHBOARD')) { - this.skip(); - } - }); - - const entitiesKey2 = 'controls'; - - it('in single page', () => { - verifyWidgetLinkToTableFromSinglePage(entitiesKey, entitiesKey2); - }); - - it('in side panel', () => { - verifyWidgetLinkToTableFromSidePanel(entitiesKey, entitiesKey2); - }); - }); }); diff --git a/ui/apps/platform/cypress/integration/cypressExpose.test.js b/ui/apps/platform/cypress/integration/cypressExpose.test.js new file mode 100644 index 0000000000000..b8bd7a530c1da --- /dev/null +++ b/ui/apps/platform/cypress/integration/cypressExpose.test.js @@ -0,0 +1,62 @@ +import { hasFeatureFlag, hasOrchestratorFlavor } from '../helpers/features'; + +// Assertions report key names only, so a failure never prints a secret value into the CI log. +const SECRET_KEYS = [ + 'ROX_AUTH_TOKEN', + 'OPENSHIFT_CONSOLE_USERNAME', + 'OPENSHIFT_CONSOLE_PASSWORD', + 'DEMO_PASSWORD', +]; + +describe('Cypress.expose public config', () => { + it('does not expose secrets to the browser', () => { + const exposed = Cypress.expose(); + const leakedKeys = SECRET_KEYS.filter((key) => key in exposed); + // Feature flags are always booleans, so any other ROX_* value is an unexpected leak. + const nonBooleanRoxKeys = Object.keys(exposed).filter( + (key) => key.startsWith('ROX_') && typeof exposed[key] !== 'boolean' + ); + + expect(leakedKeys, 'secrets in Cypress.expose()').to.be.empty; + expect(nonBooleanRoxKeys, 'non-boolean ROX_* values in Cypress.expose()').to.be.empty; + }); + + it('copies feature flags exported by scripts/cypress.sh as booleans', function () { + // scripts/cypress.sh always exports ROX_AUTH_TOKEN alongside the feature flags, so use it to + // detect a runner launch. If the copy breaks, flag-gated tests skip silently, so fail here. + cy.env(['ROX_AUTH_TOKEN']).then(({ ROX_AUTH_TOKEN }) => { + if (!ROX_AUTH_TOKEN) { + this.skip(); + } + + const flagKeys = Object.keys(Cypress.expose()).filter((key) => key.startsWith('ROX_')); + + expect(flagKeys, 'ROX_* feature flags in Cypress.expose()').to.not.be.empty; + }); + }); + + it('rejects the deprecated Cypress.env() because allowCypressEnv is false', () => { + expect(() => Cypress.env('AXE_CORE_PATH')).to.throw(); + }); + + it('feature helpers read from Cypress.expose', () => { + Cypress.expose({ + ROX_FAKE_FLAG: true, + ROX_OTHER_FLAG: false, + ORCHESTRATOR_FLAVOR: 'openshift', + }); + + expect(hasFeatureFlag('ROX_FAKE_FLAG')).to.equal(true); + expect(hasFeatureFlag('ROX_OTHER_FLAG')).to.equal(false); + expect(hasFeatureFlag('ROX_MISSING_FLAG')).to.equal(false); + expect(hasOrchestratorFlavor('openshift')).to.equal(true); + expect(hasOrchestratorFlavor('k8s')).to.equal(false); + }); + + it('injects axe-core from the exposed AXE_CORE_PATH', () => { + // Inject directly rather than through checkAccessibility so the assertion covers the + // exposed path itself and does not depend on the accessibility of the current page. + cy.injectAxe({ axeCorePath: Cypress.expose('AXE_CORE_PATH') }); + cy.window().its('axe').should('exist'); + }); +}); diff --git a/ui/apps/platform/cypress/integration/policies/policyWizardStep3-dnd.test.js b/ui/apps/platform/cypress/integration/policies/policyWizardStep3-dnd.test.js index 5302dbe7d207a..7f878f08194cb 100644 --- a/ui/apps/platform/cypress/integration/policies/policyWizardStep3-dnd.test.js +++ b/ui/apps/platform/cypress/integration/policies/policyWizardStep3-dnd.test.js @@ -1,6 +1,5 @@ import * as api from '../../constants/apiEndpoints'; import withAuth from '../../helpers/basicAuth'; -import { hasFeatureFlag } from '../../helpers/features'; import { cloneFirstPolicyFromTable, doPolicyRowAction, @@ -33,12 +32,6 @@ function clearPolicyCriteriaCards() { describe('Policy wizard, Step 3 Policy Criteria', () => { withAuth(); - before(function () { - if (hasFeatureFlag('ROX_POLICY_CRITERIA_MODAL')) { - this.skip(); - } - }); - it('should not allow user to edit policy criteria for default policies', () => { visitPolicies(); editFirstPolicyFromTable(); diff --git a/ui/apps/platform/cypress/integration/policies/policyWizardStep3.test.js b/ui/apps/platform/cypress/integration/policies/policyWizardStep3.test.js deleted file mode 100644 index c5a01305dbcfe..0000000000000 --- a/ui/apps/platform/cypress/integration/policies/policyWizardStep3.test.js +++ /dev/null @@ -1,480 +0,0 @@ -import * as api from '../../constants/apiEndpoints'; -import withAuth from '../../helpers/basicAuth'; -import { hasFeatureFlag } from '../../helpers/features'; -import { closeModalByButton } from '../../helpers/modal'; -import { - cloneFirstPolicyFromTable, - doPolicyRowAction, - editFirstPolicyFromTable, - goToStep3, - visitPolicies, -} from './Policies.helpers'; -import { selectors } from './Policies.selectors'; - -const TREE_VIEW_SEARCH_INPUT = '.pf-v6-c-tree-view__search input[name="search-input"]'; -const TREE_VIEW_FIRST_LEVEL_CHILD = '.pf-v6-c-tree-view__list-item .pf-v6-c-tree-view__list-item'; - -// open Policy Fields modal, select given field, and add it to the section card -function addPolicyField(fieldName) { - cy.get('.policy-section-card button:contains("Add policy field")').click(); - - // Note: we only use the first word of the field name to filter, because the PatternFly 4 - // TreeView search field has a bug where it doesn't accept spaces - const firstWordOfFieldName = fieldName.split(' ')[0]; - // cy.log(firstWordOfFieldName); - cy.get(TREE_VIEW_SEARCH_INPUT).type(firstWordOfFieldName); - - // Match entire title to distinguish CVSS From NVD CVSS. - cy.get(`${TREE_VIEW_FIRST_LEVEL_CHILD} .pf-v6-c-tree-view__node-title`) - .contains(new RegExp(`^${fieldName}$`)) - .click(); - - cy.get(`${TREE_VIEW_FIRST_LEVEL_CHILD} .pf-v6-c-tree-view__node`).should( - 'have.class', - 'pf-m-current' - ); - - closeModalByButton('Add policy field'); -} - -// open Policy Fields modal and ensure a given field is not available -function assertPolicyFieldNotAvailable(fieldName) { - cy.get('.policy-section-card button:contains("Add policy field")').click(); - - // Note: we only use the first word of the field name to filter, because the PatternFly 4 - // TreeView search field has a bug where it doesn't accept spaces - const firstWordOfFieldName = fieldName.split(' ')[0]; - // cy.log(firstWordOfFieldName); - cy.get(TREE_VIEW_SEARCH_INPUT).type(firstWordOfFieldName); - - cy.get( - `${TREE_VIEW_FIRST_LEVEL_CHILD} .pf-v6-c-tree-view__node-title:contains(${fieldName})` - ).should('not.exist'); - - closeModalByButton('Cancel'); -} - -function goToPoliciesAndCloneToStep3() { - visitPolicies(); - cloneFirstPolicyFromTable(); - goToStep3(); -} - -function clearPolicyCriteriaCards() { - // starting from clean slate - cy.get(selectors.step3.policyCriteria.groupCards).then((cards) => { - if (cards.length > 0) { - cy.get(selectors.step3.policyCriteria.deleteBtn).eq(0).click(); - } - }); -} - -describe.skip('Policy wizard, Step 3 Policy Criteria', () => { - withAuth(); - - before(function () { - if (!hasFeatureFlag('ROX_POLICY_CRITERIA_MODAL')) { - this.skip(); - } - }); - - it('should not allow user to edit policy criteria for default policies', () => { - visitPolicies(); - editFirstPolicyFromTable(); - goToStep3(); - - cy.get(selectors.step3.defaultPolicyAlert).should('exist'); - cy.get(selectors.step3.policyCriteria.value.numberInput).should('be.disabled'); - cy.get(selectors.step3.policySection.addBtn).should('not.exist'); - }); - - it('should have a modal with nested policy field keys', () => { - goToPoliciesAndCloneToStep3(); - - // open policy fields modal - cy.get('.policy-section-card button:contains("Add policy field")').click(); - cy.get('.pf-v6-c-modal-box__title-text:contains("Add policy criteria field")'); - - // check that all Deploy-time categories are available - // after filtering for Lifecycle was added, the number of groups for a Deploy-only policy is 7 - const GROUPS_AVAILABLE_FOR_DEPLOY_POLICY = [ - 'Image registry', - 'Image contents', - 'Image scanning', - 'Container configuration', - 'Deployment metadata', - 'Storage', - 'Networking', - 'Service account', - ]; - cy.get('.pf-v6-c-tree-view__list-item').each((element, index) => { - element.get( - `.pf-v6-c-tree-view__node-title:contains(${GROUPS_AVAILABLE_FOR_DEPLOY_POLICY[index]})` - ); - }); - - // now, check the criteria in a category - const FIELDS_AVAILABLE_FOR_DEPLOY_POLICY = [ - 'Image registry', - 'Image name', - 'Image tag', - 'Require image signature', - ]; - cy.get('.pf-v6-c-tree-view__list-item:first').click(); - cy.get(TREE_VIEW_FIRST_LEVEL_CHILD).each((element, index) => { - element.get( - `.pf-v6-c-tree-view__node-title:contains(${FIELDS_AVAILABLE_FOR_DEPLOY_POLICY[index]})` - ); - }); - - closeModalByButton('Cancel'); - }); - - describe('Policy section', () => { - it('should allow the user to add and delete a policy section card', () => { - goToPoliciesAndCloneToStep3(); - - // add policy section card - cy.get(selectors.step3.policySection.cards).then((sections) => { - cy.get(selectors.step3.policySection.addBtn).click(); - cy.get(selectors.step3.policySection.cards).then((newSections) => { - expect(newSections).to.have.length(sections.length + 1); - }); - }); - cy.get(selectors.step3.policySection.orDivider).should('exist'); - - // delete policy section card - cy.get(selectors.step3.policySection.cards).then((sections) => { - cy.get(selectors.step3.policySection.deleteBtn).first().click(); - cy.get(selectors.step3.policySection.cards).then((newSections) => { - expect(newSections).to.have.length(sections.length - 1); - }); - }); - }); - - it('should allow editing a policy section name and retain new name value', () => { - goToPoliciesAndCloneToStep3(); - - cy.get(selectors.step3.policySection.nameEditBtn).click(); - cy.get(selectors.step3.policySection.nameInput).clear(); - cy.get(selectors.step3.policySection.nameInput).type('New Section'); - cy.get(selectors.step3.policySection.nameSaveBtn).click(); - cy.get(selectors.step3.policySection.name).contains('New Section'); - }); - - it('should allow the user to add/delete a policy field card in the same policy section', () => { - goToPoliciesAndCloneToStep3(); - - // add policy field card - cy.get(selectors.step3.policyCriteria.groupCards).then((cards) => { - addPolicyField('Image registry', 0); - cy.get(selectors.step3.policyCriteria.groupCards).then((newCards) => { - expect(newCards).to.have.length(cards.length + 1); - }); - }); - - // delete policy field card - cy.get(selectors.step3.policyCriteria.groupCards).then((cards) => { - cy.get(selectors.step3.policyCriteria.deleteBtn).eq(0).click(); - cy.get(selectors.step3.policyCriteria.groupCards).then((newCards) => { - expect(newCards).to.have.length(cards.length - 1); - }); - }); - }); - - it('should allow the user to add multiple non-duplicate policy field cards in the same policy section', () => { - goToPoliciesAndCloneToStep3(); - - cy.get(selectors.step3.policyCriteria.groupCards).then((cards) => { - addPolicyField('Image registry'); - addPolicyField('Image name'); - addPolicyField('Image tag'); - cy.get(selectors.step3.policyCriteria.groupCards).then((newCards) => { - expect(newCards).to.have.length(cards.length + 3); - }); - }); - }); - - it('should not be able to add duplicate policy field cards in the same policy section', () => { - goToPoliciesAndCloneToStep3(); - - cy.get(selectors.step3.policyCriteria.groupCards).then((cards) => { - addPolicyField('Image name'); - assertPolicyFieldNotAvailable('Image name'); - cy.get(selectors.step3.policyCriteria.groupCards).then((newCards) => { - expect(newCards).to.have.length(cards.length + 1); - }); - }); - }); - }); - - describe('Policy field card', () => { - describe('values', () => { - it('should add/delete multiple field values for the same field if applicable', () => { - goToPoliciesAndCloneToStep3(); - clearPolicyCriteriaCards(); - - // add field values for Image Registry - addPolicyField('Image name'); - cy.get(selectors.step3.policyCriteria.value.deleteBtn).should('not.exist'); - cy.get(selectors.step3.policyCriteria.value.addBtn).first().click(); - cy.get(selectors.step3.policyCriteria.value.textInput).then((inputs) => { - expect(inputs).to.have.length(2); - cy.get(selectors.step3.policyCriteria.value.deleteBtn).should('have.length', 2); - }); - cy.get(selectors.step3.policyCriteria.booleanOperator).should('have.length', 1); - cy.get(selectors.step3.policyCriteria.booleanOperator).should('not.be.disabled'); - - // delete field value - cy.get(selectors.step3.policyCriteria.value.deleteBtn).first().click(); - cy.get(selectors.step3.policyCriteria.value.textInput).then((inputs) => { - expect(inputs).to.have.length(1); - cy.get(selectors.step3.policyCriteria.value.deleteBtn).should('not.exist'); - cy.get(selectors.step3.policyCriteria.booleanOperator).should('not.exist'); - }); - }); - - it('should not add multiple field values for the same field if not applicable', () => { - goToPoliciesAndCloneToStep3(); - clearPolicyCriteriaCards(); - - addPolicyField('Mounted volume writability'); - cy.get(selectors.step3.policyCriteria.value.radioGroup).should('exist'); - cy.get(selectors.step3.policyCriteria.value.addBtn).should('not.exist'); - }); - }); - - describe('negation', () => { - it('should negate field if applicable and change wording', () => { - goToPoliciesAndCloneToStep3(); - clearPolicyCriteriaCards(); - - addPolicyField('Image registry'); - cy.get(selectors.step3.policyCriteria.value.negateCheckbox).should( - 'not.be.checked' - ); - cy.get(selectors.step3.policyCriteria.value.negateCheckbox).click(); - cy.get(selectors.step3.policyCriteria.value.negateCheckbox).should('be.checked'); - cy.get(selectors.step3.policyCriteria.groupCardTitle).first().contains('not'); - }); - - it('should not show negate field if not applicable', () => { - goToPoliciesAndCloneToStep3(); - clearPolicyCriteriaCards(); - - addPolicyField('Mounted volume writability'); - cy.get(selectors.step3.policyCriteria.value.negateCheckbox).should('not.exist'); - }); - }); - - describe('boolean operator', () => { - it('should toggle AND/OR if applicable', () => { - goToPoliciesAndCloneToStep3(); - clearPolicyCriteriaCards(); - - addPolicyField('Image registry'); - cy.get(selectors.step3.policyCriteria.value.addBtn).first().click(); - cy.get(selectors.step3.policyCriteria.booleanOperator).should('not.be.disabled'); - cy.get(selectors.step3.policyCriteria.booleanOperator).contains('or'); - cy.get(selectors.step3.policyCriteria.booleanOperator).click(); - cy.get(selectors.step3.policyCriteria.booleanOperator).contains('and'); - }); - - it('should have AND/OR toggle disabled if not applicable', () => { - goToPoliciesAndCloneToStep3(); - clearPolicyCriteriaCards(); - - addPolicyField('Image age'); - cy.get(selectors.step3.policyCriteria.value.addBtn).first().click(); - cy.get(selectors.step3.policyCriteria.booleanOperator).should('be.disabled'); - cy.get(selectors.step3.policyCriteria.booleanOperator).contains('or'); - }); - }); - - describe('input', () => { - it('should populate boolean radio buttons w default value and respect changed values', () => { - goToPoliciesAndCloneToStep3(); - clearPolicyCriteriaCards(); - - addPolicyField('Image scan status'); - cy.get(selectors.step3.policyCriteria.value.radioGroup).should('exist'); - cy.get( - `${selectors.step3.policyCriteria.value.radioGroupItem}:contains('Scanned') button` - ).should('not.have.class', 'pf-m-selected'); - cy.get( - `${selectors.step3.policyCriteria.value.radioGroupItem}:contains('Not scanned') button` - ).should('have.class', 'pf-m-selected'); - cy.get( - `${selectors.step3.policyCriteria.value.radioGroupItem}:contains('Scanned') button` - ).click(); - cy.get( - `${selectors.step3.policyCriteria.value.radioGroupItem}:contains('Scanned') button` - ).should('have.class', 'pf-m-selected'); - cy.get( - `${selectors.step3.policyCriteria.value.radioGroupItem}:contains('Not scanned') button` - ).should('not.have.class', 'pf-m-selected'); - }); - - it('should populate string radio buttons w default value and respect changed values', () => { - goToPoliciesAndCloneToStep3(); - clearPolicyCriteriaCards(); - - addPolicyField('Seccomp profile type'); - cy.get(selectors.step3.policyCriteria.value.radioGroupString).should('exist'); - cy.get( - `${selectors.step3.policyCriteria.value.radioGroupStringItem} button.pf-m-selected` - ).should('have.length', 0); - cy.get( - `${selectors.step3.policyCriteria.value.radioGroupStringItem}:contains('Unconfined') button` - ).click(); - cy.get( - `${selectors.step3.policyCriteria.value.radioGroupStringItem}:contains('Unconfined') button` - ).should('have.class', 'pf-m-selected'); - cy.get( - `${selectors.step3.policyCriteria.value.radioGroupStringItem} button.pf-m-selected` - ).should('have.length', 1); - }); - - it('should populate text input and respect changed values', () => { - goToPoliciesAndCloneToStep3(); - clearPolicyCriteriaCards(); - - addPolicyField('Image registry'); - cy.get(selectors.step3.policyCriteria.value.textInput).should('have.value', ''); - cy.get(selectors.step3.policyCriteria.value.textInput).type('test'); - cy.get(selectors.step3.policyCriteria.value.textInput).should('have.value', 'test'); - }); - - it('should populate select dropdown and respect changed values', () => { - goToPoliciesAndCloneToStep3(); - clearPolicyCriteriaCards(); - - addPolicyField('Drop capabilities'); - cy.get(selectors.step3.policyCriteria.value.select).should('have.value', ''); - cy.get(selectors.step3.policyCriteria.value.select).click(); - cy.get(selectors.step3.policyCriteria.value.selectOption) - .first() - .then((option) => { - cy.wrap(option).click(); - cy.get(selectors.step3.policyCriteria.value.select).contains(option.text()); - }); - }); - - it('should populate policy field input nested group and parse value string to object and respect changed values', () => { - goToPoliciesAndCloneToStep3(); - clearPolicyCriteriaCards(); - - addPolicyField('CVSS'); - cy.get(selectors.step3.policyCriteria.value.select).should('have.value', ''); - cy.get(selectors.step3.policyCriteria.value.numberInput).should('have.value', ''); - cy.get(selectors.step3.policyCriteria.value.select).click(); - cy.get(selectors.step3.policyCriteria.value.selectOption) - .first() - .then((option) => { - cy.wrap(option).click(); - cy.get(selectors.step3.policyCriteria.value.select).contains(option.text()); - }); - cy.get(selectors.step3.policyCriteria.value.numberInput).type('10'); - cy.get(selectors.step3.policyCriteria.value.numberInput).should('have.value', '10'); - }); - }); - - describe('table modal', () => { - beforeEach(() => { - cy.intercept('GET', api.integrations.signatureIntegrations, { - fixture: 'integrations/signatureIntegrations.json', - }).as('getSignatureIntegrations'); - - goToPoliciesAndCloneToStep3(); - clearPolicyCriteriaCards(); - addPolicyField('Require image signature'); - cy.wait('@getSignatureIntegrations'); - }); - - it('should populate table modal select and respect changed values on save', () => { - cy.get(selectors.step3.policyCriteria.value.tableModal.textInput).should( - 'have.value', - 'Add trusted image signers' - ); - cy.get(selectors.step3.policyCriteria.value.tableModal.openButton).click(); - cy.get(selectors.step3.policyCriteria.value.tableModal.firstRowCheckbox).click(); - cy.get(selectors.step3.policyCriteria.value.tableModal.saveBtn).click(); - cy.get(selectors.step3.policyCriteria.value.tableModal.textInput).should( - 'have.value', - 'Selected 1 trusted image signer' - ); - }); - - it('should populate table modal select and not change values on cancel', () => { - cy.get(selectors.step3.policyCriteria.value.tableModal.openButton).click(); - cy.get(selectors.step3.policyCriteria.value.tableModal.firstRowCheckbox).click(); - cy.get(selectors.step3.policyCriteria.value.tableModal.cancelBtn).click(); - cy.get(selectors.step3.policyCriteria.value.tableModal.textInput).should( - 'have.value', - 'Add trusted image signers' - ); - }); - - it('should go to link when table row is clicked', () => { - cy.get(selectors.step3.policyCriteria.value.tableModal.openButton).click(); - cy.get(selectors.step3.policyCriteria.value.tableModal.firstRowName).click(); - cy.location('pathname').should('contain', 'signatureIntegrations'); - }); - }); - }); - - describe('Existing values', () => { - it('should populate boolean radio buttons', () => { - visitPolicies(); - doPolicyRowAction(`${selectors.table.rows}:contains('root filesystem')`, 'Clone'); - goToStep3(); - cy.get(selectors.step3.policyCriteria.value.radioGroup).should('exist'); - cy.get( - `${selectors.step3.policyCriteria.value.radioGroupItem}:contains('Writable') button` - ).should('have.class', 'pf-m-selected'); - cy.get( - `${selectors.step3.policyCriteria.value.radioGroupItem} button.pf-m-selected` - ).should('have.length', 1); - }); - - it('should populate string radio buttons', () => { - visitPolicies(); - doPolicyRowAction(`${selectors.table.rows}:contains('seccomp profile')`, 'Clone'); - goToStep3(); - cy.get(selectors.step3.policyCriteria.value.radioGroupString).should('exist'); - cy.get( - `${selectors.step3.policyCriteria.value.radioGroupStringItem}:contains('Unconfined') button` - ).should('have.class', 'pf-m-selected'); - cy.get( - `${selectors.step3.policyCriteria.value.radioGroupStringItem} button.pf-m-selected` - ).should('have.length', 1); - }); - - it('should populate text input', () => { - visitPolicies(); - doPolicyRowAction(`${selectors.table.rows}:contains('Latest tag')`, 'Clone'); - goToStep3(); - cy.get(selectors.step3.policyCriteria.value.textInput).should('have.value', 'latest'); - }); - - it('should populate select dropdown', () => { - visitPolicies(); - doPolicyRowAction(`${selectors.table.rows}:contains('capability')`, 'Clone'); - goToStep3(); - cy.get(selectors.step3.policyCriteria.value.select).contains('SYS_ADMIN'); - }); - - it('should populate policy field input nested group', () => { - visitPolicies(); - doPolicyRowAction(`${selectors.table.rows}:contains('CVSS >= 6')`, 'Clone'); - goToStep3(); - cy.get(selectors.step3.policyCriteria.value.select).contains( - 'Is greater than or equal to' - ); - cy.get(selectors.step3.policyCriteria.value.numberInput).should( - 'have.value', - '6.000000' - ); - }); - }); -}); diff --git a/ui/apps/platform/cypress/support/commands.js b/ui/apps/platform/cypress/support/commands.js index 7a744a01b2f2c..59ebccb397525 100644 --- a/ui/apps/platform/cypress/support/commands.js +++ b/ui/apps/platform/cypress/support/commands.js @@ -16,7 +16,7 @@ Cypress.Commands.add('checkAccessibility', (context = null) => { // inject axe only if it hasn't been injected yet cy.window().then((win) => { if (!win.axe) { - cy.injectAxe({ axeCorePath: Cypress.env('AXE_CORE_PATH') }); + cy.injectAxe({ axeCorePath: Cypress.expose('AXE_CORE_PATH') }); } }); diff --git a/ui/apps/platform/eslint-plugins/pluginLimited.js b/ui/apps/platform/eslint-plugins/pluginLimited.js index f5697047dcc81..031212372b678 100644 --- a/ui/apps/platform/eslint-plugins/pluginLimited.js +++ b/ui/apps/platform/eslint-plugins/pluginLimited.js @@ -379,6 +379,38 @@ const rules = { }; }, }, + 'no-import-from-Containers-in-Components': { + // Prevent soft circular dependency: + // * A generic file in Components has import from a specific file in Containers. + // * Another specific file in Containers might have import of that generic file from Components. + // Although the rule cannot know for sure, Components exists for import into Containers. + meta: { + type: 'problem', + docs: { + description: + 'Fix soft circular dependency between specific Containers and generic Components', + }, + schema: [], + }, + create(context) { + return { + ImportDeclaration(node) { + const { filename } = context; + if ( + filename.includes('ui/apps/platform/src/Components/') && + typeof node.source?.value === 'string' && + node.source.value.startsWith('Containers/') + ) { + context.report({ + node, + message: + 'Fix soft circular dependency between specific Containers and generic Components', + }); + } + }, + }; + }, + }, 'no-logical-or-preceding-array-or-object': { // Consistently write more precise nullish coalescing operator. meta: { diff --git a/ui/apps/platform/eslint-plugins/pluginPatternFly.js b/ui/apps/platform/eslint-plugins/pluginPatternFly.js index 38edad0b83686..90a28cf80bb67 100644 --- a/ui/apps/platform/eslint-plugins/pluginPatternFly.js +++ b/ui/apps/platform/eslint-plugins/pluginPatternFly.js @@ -46,6 +46,145 @@ const rules = { }; }, }, + 'PageSection-type-breadcrumb': { + // Require that PageSection element has type="breadcrumb" prop if Breadcrumb is a child. + meta: { + type: 'problem', + docs: { + description: + 'Require that PageSection element has type="breadcrumb" prop if Breadcrumb is a child.', + }, + schema: [], + }, + create(context) { + return { + JSXOpeningElement(node) { + if ( + node.name?.name === 'PageSection' && + !node.attributes.some( + (attribute) => + attribute.name?.name === 'type' && + attribute.value?.value === 'breadcrumb' + ) + ) { + const ancestors = context.sourceCode.getAncestors(node); + if ( + ancestors.length > 0 && + Array.isArray(ancestors[ancestors.length - 1].children) && + ancestors[ancestors.length - 1].children.some( + (child) => child.openingElement?.name?.name === 'Breadcrumb' + ) + ) { + context.report({ + node, + message: + 'PageSection element needs type="breadcrumb" prop if Breadcrumb is a child', + }); + } + } + }, + }; + }, + }, + 'PageSection-type-tabs': { + // Require that PageSection element has type="tabs" prop if Tabs is a child. + meta: { + type: 'problem', + docs: { + description: + 'Require that PageSection element has type="tabs" prop if Tabs is a child.', + }, + schema: [], + }, + create(context) { + return { + JSXOpeningElement(node) { + if ( + node.name?.name === 'PageSection' && + !node.attributes.some( + (attribute) => + attribute.name?.name === 'type' && attribute.value?.value === 'tabs' + ) + ) { + const ancestors = context.sourceCode.getAncestors(node); + if ( + ancestors.length > 0 && + Array.isArray(ancestors[ancestors.length - 1].children) && + ancestors[ancestors.length - 1].children.some( + (child) => child.openingElement?.name?.name === 'Tabs' + ) + ) { + context.report({ + node, + message: + 'PageSection element needs type="tabs" prop if Tabs is a child', + }); + } + } + }, + }; + }, + }, + 'PageSection-type-wizard': { + // Require that PageSection element has type="wizard" prop if Wizard is a child. + meta: { + type: 'problem', + docs: { + description: + 'Require that PageSection element has type="wizard" prop if Wizard is a child.', + }, + schema: [], + }, + create(context) { + return { + JSXOpeningElement(node) { + if ( + node.name?.name === 'PageSection' && + !node.attributes.some( + (attribute) => + attribute.name?.name === 'type' && + attribute.value?.value === 'wizard' + ) + ) { + const ancestors = context.sourceCode.getAncestors(node); + if ( + ancestors.length > 0 && + Array.isArray(ancestors[ancestors.length - 1].children) && + ancestors[ancestors.length - 1].children.some( + (child) => child.openingElement?.name?.name === 'Wizard' + ) + ) { + context.report({ + node, + message: + 'PageSection element needs type="wizard" prop if Wizard is a child', + }); + } else if ( + ancestors.length > 1 && + Array.isArray(ancestors[ancestors.length - 1].children) + ) { + const childFound = ancestors[ancestors.length - 1].children.find( + (child) => child.openingElement?.name?.name === 'FormikProvider' + ); + if ( + childFound && + Array.isArray(childFound.children) && + childFound.children.some( + (child) => child.openingElement?.name?.name === 'Wizard' + ) + ) { + context.report({ + node, + message: + 'PageSection element needs type="wizard" prop if Wizard is a descendant', + }); + } + } + } + }, + }; + }, + }, 'Td-dataLabel-Th-text': { // Require that if Td element has dataLabel prop with string value, // then Th element with same index has corresponding text (or screenReaderText). diff --git a/ui/apps/platform/eslint.config.js b/ui/apps/platform/eslint.config.js index a6f13c2547d36..1f61d3908f56e 100644 --- a/ui/apps/platform/eslint.config.js +++ b/ui/apps/platform/eslint.config.js @@ -788,7 +788,6 @@ module.exports = [ }, { files: ['src/*/**/*.{jsx,ts,tsx}'], // product files, except for unit tests (including test-utils folder) - ignores: ['src/Containers/Compliance/**'], // languageOptions from previous configuration object @@ -797,14 +796,13 @@ module.exports = [ limited: pluginLimited, }, rules: { - 'import/no-cycle': ['error', { maxDepth: '∞' }], // classic compliance has 7 errors + 'import/no-cycle': ['error', { maxDepth: '∞' }], 'limited/react-export-default': 'error', }, }, { files: ['**/*.{js,jsx,ts,tsx}'], // generic configuration includes cypress and src folders ignores: [ - 'src/Containers/Compliance/**', // deprecated 'src/Containers/VulnMgmt/**', // deprecated ], @@ -823,7 +821,6 @@ module.exports = [ { files: ['src/*/**/*.{js,jsx,ts,tsx}'], ignores: [ - 'src/Containers/Compliance/**', // deprecated 'src/Containers/VulnMgmt/**', // deprecated 'src/Containers/Workflow/**', // deprecated ], @@ -839,13 +836,36 @@ module.exports = [ 'limited/no-relative-path-to-src-in-import': 'error', }, }, + { + files: ['src/*/**/*.{js,jsx,ts,tsx}'], + ignores: [ + 'src/Components/CompoundSearchFilter/attributes/imageCVE.ts', // refactor: originDisplayNames + 'src/Components/CompoundSearchFilter/attributes/imageComponent.ts', // refactor: sourceTypeLabels, sourceTypes + 'src/Components/CompoundSearchFilter/attributes/virtualMachine.ts', // refactor: sourceTypeLabels, sourceTypes + 'src/Components/EmailNotifier/EmailNotifierForm.tsx', // refactor + 'src/Components/EmailNotifier/EmailNotifierModal.tsx', // refactor + 'src/Components/RelatedEntity.jsx', // deprecated + 'src/Components/RelatedEntityListCount.jsx', // deprecated + 'src/Components/URLSearchInputWithAutocomplete.jsx', // deprecated + 'src/Components/workflow/EntitiesMenu/EntitiesMenu.jsx', // deprecated + ], + + // languageOptions from previous configuration object + + // Key of plugin is namespace of its rules. + plugins: { + limited: pluginLimited, + }, + rules: { + 'limited/no-import-from-Containers-in-Components': 'error', + }, + }, { files: ['src/*/**/*.{js,jsx,ts,tsx}'], ignores: [ 'src/Components/GroupedTabs.jsx', // deprecated 'src/Components/ReactSelect/ReactSelect.jsx', // deprecated 'src/Components/URLSearchInputWithAutocomplete.jsx', // deprecated - 'src/Containers/Compliance/**', // deprecated 'src/Containers/VulnMgmt/**', // deprecated 'src/sagas/authSagas.js', // deprecated 'src/sagas/groupSagas.js', // deprecated @@ -913,7 +933,6 @@ module.exports = [ 'src/Components/visuals/**', // deprecated 'src/Components/workflow/**', // deprecated - 'src/Containers/Compliance/**', // deprecated 'src/Containers/ConfigManagement/**', 'src/Containers/Images/**', // deprecated 'src/Containers/Login/**', // rewrite in PatternFly, and then delete; also in tailwind.config.js file @@ -939,7 +958,6 @@ module.exports = [ ignores: [ 'cypress/constants/**', 'cypress/integration/clusters/**', // delete obsolete tests - 'cypress/integration/compliance/**', // deprecated 'cypress/integration/configmanagement/**', // deprecated/modernized 'cypress/integration/integrations/**', // replace 'cypress/integration/networkGraph/**', // replace @@ -950,7 +968,6 @@ module.exports = [ 'src/Components/**', // replace non-deprecated 'src/Containers/AccessControl/**', // replace 'src/Containers/Clusters/**', // delete obsolete tests - 'src/Containers/Compliance/**', // deprecated 'src/Containers/ConfigManagement/**', // deprecated/modernized 'src/Containers/Integrations/**', // replace 'src/Containers/Login/**', // replace @@ -990,7 +1007,6 @@ module.exports = [ 'src/Components/Tabs.jsx', // deprecated 'src/Components/visuals/Sunburst.jsx', // deprecated 'src/Components/visuals/SunburstDetailSection.jsx', // deprecated - 'src/Containers/Compliance/**', // deprecated 'src/Containers/Login/LoginPage.jsx', // rewrite 'src/Containers/MainPage/asyncComponent.tsx', // replace 'src/Containers/Risk/KeyValuePairs.jsx', // rewrite? @@ -1013,7 +1029,6 @@ module.exports = [ 'src/**/*.test.jsx', // unit test 'src/**/*.cy.jsx', // component test 'src/Components/**', // research - 'src/Containers/Compliance/**', // deprecated 'src/Containers/ConfigManagement/**', // deferred 'src/Containers/Images/**', // deprecated 'src/Containers/Login/LoginPage.jsx', // rewrite pending PatternFly 6 @@ -1102,7 +1117,6 @@ module.exports = [ 'src/Components/**', // most or all deprecated 'src/constants/entityPageProps.js', // deprecated 'src/constants/reduxFormPropTypes.js', // deprecated - 'src/Containers/Compliance/**', // deprecated 'src/Containers/ConfigManagement/**', // deprecated 'src/Containers/Images/**', // deprecated 'src/Containers/Login/LoginPage.jsx', // rewrite in TypeScript @@ -1136,7 +1150,6 @@ module.exports = [ 'src/Containers/AccessControl/AuthProviders/AuthProviders.tsx', // replace 'src/Containers/AccessControl/AuthProviders/AuthProvidersList.tsx', // replace 'src/Containers/Clusters/DownloadHelmValues.tsx', // replace - 'src/Containers/Compliance/**', // deprecated 'src/Containers/Login/LoginPage.jsx', // replace 'src/Containers/Login/TestLoginResultsPage.jsx', // replace 'src/Containers/MainPage/AcsFeedbackModal.tsx', // replace diff --git a/ui/apps/platform/src/Components/ControlDetails.jsx b/ui/apps/platform/src/Components/ControlDetails.jsx deleted file mode 100644 index 347017c03c5c6..0000000000000 --- a/ui/apps/platform/src/Components/ControlDetails.jsx +++ /dev/null @@ -1,43 +0,0 @@ -import PropTypes from 'prop-types'; - -import { standardLabels } from 'messages/standards'; - -import Widget from 'Components/Widget'; - -const ControlDetails = ({ standardId, standardName, control, description, className }) => ( - -
-
- Standard: - - {standardLabels[standardId] || standardName} - -
-
- Control: - {control} -
-
-
{description}
-
-); - -ControlDetails.propTypes = { - standardId: PropTypes.string.isRequired, - control: PropTypes.string.isRequired, - description: PropTypes.string.isRequired, - className: PropTypes.string, - standardName: PropTypes.string, -}; - -ControlDetails.defaultProps = { - className: '', - standardName: '', -}; - -export default ControlDetails; diff --git a/ui/apps/platform/src/Components/CountWidget.jsx b/ui/apps/platform/src/Components/CountWidget.jsx deleted file mode 100644 index 606f477897b01..0000000000000 --- a/ui/apps/platform/src/Components/CountWidget.jsx +++ /dev/null @@ -1,54 +0,0 @@ -import { connect } from 'react-redux'; -import PropTypes from 'prop-types'; -import { Link } from 'react-router-dom-v5-compat'; -import Widget from 'Components/Widget'; - -const CountWidget = ({ title, count, description, linkUrl }) => { - const countNode = ( -
0 ? `hover:bg-tertiary-200` : `` - } text-6xl px-5 py-8 h-full flex items-center justify-center`} - > - {count} -
- ); - const descNode = description &&
{description}
; - return ( - - {linkUrl ? ( - - {countNode} - {descNode} - - ) : ( - <> - {countNode} - {descNode} - - )} - - ); -}; - -CountWidget.propTypes = { - title: PropTypes.string.isRequired, - count: PropTypes.number, - description: PropTypes.string, - linkUrl: PropTypes.string, -}; - -CountWidget.defaultProps = { - description: null, - linkUrl: null, - count: 0, -}; - -export default connect()(CountWidget); diff --git a/ui/apps/platform/src/Components/EntityIcon.jsx b/ui/apps/platform/src/Components/EntityIcon.jsx index 545764667c7c7..11503a90aba0e 100644 --- a/ui/apps/platform/src/Components/EntityIcon.jsx +++ b/ui/apps/platform/src/Components/EntityIcon.jsx @@ -12,7 +12,6 @@ import policy from 'images/side-panel-icons/policy.svg'; import role from 'images/side-panel-icons/role.svg'; import secrets from 'images/side-panel-icons/secrets.svg'; import serviceAccount from 'images/side-panel-icons/service-account.svg'; -import control from 'images/side-panel-icons/control.svg'; const imageMap = { [entityTypes.CLUSTER]: cluster, @@ -32,7 +31,6 @@ const imageMap = { [entityTypes.ROLE]: role, [entityTypes.SECRET]: secrets, [entityTypes.SERVICE_ACCOUNT]: serviceAccount, - [entityTypes.CONTROL]: control, }; const EntityIcon = ({ className, entityType }) => ( diff --git a/ui/apps/platform/src/Components/ExportButton.jsx b/ui/apps/platform/src/Components/ExportButton.jsx deleted file mode 100644 index 7988a13464865..0000000000000 --- a/ui/apps/platform/src/Components/ExportButton.jsx +++ /dev/null @@ -1,116 +0,0 @@ -import { Component } from 'react'; -import PropTypes from 'prop-types'; -import { FileText } from 'react-feather'; -import onClickOutside from 'react-onclickoutside'; -import { Button } from '@patternfly/react-core'; - -import downloadCSV from 'services/CSVDownloadService'; -import ButtonClassic from 'Components/Button'; -import entityTypes from 'constants/entityTypes'; -import useCaseTypes from 'constants/useCaseTypes'; -import { addBrandedTimestampToString } from 'utils/dateUtils'; - -const queryParamMap = { - [entityTypes.CLUSTER]: 'clusterId', - [entityTypes.STANDARD]: 'standardId', - ALL: '', -}; - -class ExportButton extends Component { - static propTypes = { - className: PropTypes.string, - textClass: PropTypes.string, - fileName: PropTypes.string, - type: PropTypes.string, - id: PropTypes.string, - page: PropTypes.string, - disabled: PropTypes.bool, - }; - - static defaultProps = { - className: 'btn btn-base h-10', - textClass: null, - fileName: 'compliance', - type: null, - id: '', - page: '', - disabled: false, - }; - - constructor(props) { - super(props); - - this.state = { - toggleWidget: false, - }; - } - - handleClickOutside = () => this.setState({ toggleWidget: false }); - - downloadCSVFile = () => { - const { id, fileName, type } = this.props; - const csvName = addBrandedTimestampToString(fileName); - - let queryStr = ''; - if (queryParamMap[type] && id) { - queryStr = `${queryParamMap[type]}=${id}`; - } - - downloadCSV(csvName, '/api/compliance/export/csv', queryStr); - }; - - isSupported = () => { - const { page, type } = this.props; - return page === useCaseTypes.COMPLIANCE && Object.keys(queryParamMap).includes(type); - }; - - renderContent = () => { - if (!this.state.toggleWidget) { - return null; - } - - return this.isSupported() ? ( -
-
-
    -
  • -
    - -
    -
  • -
-
- ) : null; - }; - - toggleWidget = () => { - this.setState(({ toggleWidget }) => ({ toggleWidget: !toggleWidget })); - }; - - render() { - return ( -
- } - onClick={this.toggleWidget} - /> - {this.renderContent()} -
- ); - } -} - -export default onClickOutside(ExportButton); diff --git a/ui/apps/platform/src/Components/IconWidget.jsx b/ui/apps/platform/src/Components/IconWidget.jsx deleted file mode 100644 index c81b78d283ad5..0000000000000 --- a/ui/apps/platform/src/Components/IconWidget.jsx +++ /dev/null @@ -1,59 +0,0 @@ -import { connect } from 'react-redux'; -import PropTypes from 'prop-types'; -import { Link } from 'react-router-dom-v5-compat'; -import Widget from 'Components/Widget'; -import Loader from 'Components/Loader'; - -const IconWidget = ({ title, icon, description, linkUrl, loading, textSizeClass }) => { - const contents = loading ? ( - - ) : ( - <> -
- {title} -
-
- {description} -
- - ); - - return ( - - {linkUrl && !loading ? ( - - {contents} - - ) : ( -
{contents}
- )} -
- ); -}; - -IconWidget.propTypes = { - title: PropTypes.string.isRequired, - icon: PropTypes.node.isRequired, - description: PropTypes.string, - textSizeClass: PropTypes.string, - linkUrl: PropTypes.string, - loading: PropTypes.bool, -}; - -IconWidget.defaultProps = { - description: null, - linkUrl: null, - textSizeClass: 'text-2xl', - loading: false, -}; - -export default connect()(IconWidget); diff --git a/ui/apps/platform/src/Components/InfoWidget.jsx b/ui/apps/platform/src/Components/InfoWidget.jsx deleted file mode 100644 index b14be1fbe5976..0000000000000 --- a/ui/apps/platform/src/Components/InfoWidget.jsx +++ /dev/null @@ -1,51 +0,0 @@ -import { connect } from 'react-redux'; -import PropTypes from 'prop-types'; -import { Link } from 'react-router-dom-v5-compat'; -import Widget from 'Components/Widget'; -import Loader from 'Components/Loader'; - -const InfoWidget = ({ title, headline, description, linkUrl, loading }) => { - const contents = loading ? ( - - ) : ( -
-
{headline}
-
{description}
-
- ); - return ( - - {linkUrl && !loading ? ( - - {contents} - - ) : ( - contents - )} - - ); -}; - -InfoWidget.propTypes = { - title: PropTypes.string.isRequired, - headline: PropTypes.string, - description: PropTypes.string, - linkUrl: PropTypes.string, - loading: PropTypes.bool, -}; - -InfoWidget.defaultProps = { - description: null, - headline: null, - linkUrl: null, - loading: false, -}; - -export default connect()(InfoWidget); diff --git a/ui/apps/platform/src/Components/NotifierConfiguration/NotifierConfigurationView.tsx b/ui/apps/platform/src/Components/NotifierConfiguration/NotifierConfigurationView.tsx deleted file mode 100644 index 9459a9416d830..0000000000000 --- a/ui/apps/platform/src/Components/NotifierConfiguration/NotifierConfigurationView.tsx +++ /dev/null @@ -1,111 +0,0 @@ -import { useState } from 'react'; -import type { ReactElement } from 'react'; -import { Button, Flex, FlexItem, Title } from '@patternfly/react-core'; -import { Table, Tbody, Td, Th, Thead, Tr } from '@patternfly/react-table'; - -import { isDefaultEmailTemplate } from 'Components/EmailTemplate/EmailTemplate.utils'; -import EmailTemplateModal from 'Components/EmailTemplate/EmailTemplateModal'; -import type { TemplatePreviewArgs } from 'Components/EmailTemplate/EmailTemplateModal'; -import useIndexKey from 'hooks/useIndexKey'; -import type { NotifierConfiguration } from 'services/ReportsService.types'; - -export type NotifierConfigurationViewProps = { - headingLevel: 'h2' | 'h3'; - customBodyDefault: string; - customSubjectDefault: string; - notifierConfigurations: NotifierConfiguration[]; - renderTemplatePreview?: (args: TemplatePreviewArgs) => ReactElement; -}; - -function NotifierConfigurationView({ - headingLevel, - customBodyDefault, - customSubjectDefault, - notifierConfigurations, - renderTemplatePreview, -}: NotifierConfigurationViewProps): ReactElement { - const { keyFor } = useIndexKey(); - const [notifierConfigurationSelected, setNotifierConfigurationSelected] = - useState(null); - - return ( - <> - - - Delivery destinations - - - {notifierConfigurations.length === 0 ? ( - 'No delivery destinations' - ) : ( - - - - - - - - - - {notifierConfigurations.map((notifierConfiguration, index) => { - const { emailConfig, notifierName } = notifierConfiguration; - const { customBody, customSubject, mailingLists } = emailConfig; - const isDefaultEmailTemplateApplied = isDefaultEmailTemplate({ - customBody, - customSubject, - }); - return ( - - - - - - ); - })} - -
Email notifierDistribution listEmail template
{notifierName} - {mailingLists.join(', ')} - - -
- )} -
-
- {notifierConfigurationSelected && ( - { - setNotifierConfigurationSelected(null); - }} - renderTemplatePreview={renderTemplatePreview} - title={ - isDefaultEmailTemplate({ - customBody: notifierConfigurationSelected.emailConfig.customBody, - customSubject: notifierConfigurationSelected.emailConfig.customSubject, - }) - ? 'Default template applied' - : 'Custom template applied' - } - /> - )} - - ); -} - -export default NotifierConfigurationView; diff --git a/ui/apps/platform/src/Components/ReportJob/ReportJobStatus.test.tsx b/ui/apps/platform/src/Components/ReportJob/ReportJobStatus.test.tsx index 19f638afd45ae..575c38f7b72e1 100644 --- a/ui/apps/platform/src/Components/ReportJob/ReportJobStatus.test.tsx +++ b/ui/apps/platform/src/Components/ReportJob/ReportJobStatus.test.tsx @@ -71,7 +71,7 @@ describe('ReportJobStatus', () => { reportRequestType: 'ON_DEMAND', reportNotificationMethod: 'DOWNLOAD', completedAt: '', - errorMsg: 'This is an error message', + errorMsg: 'scan failed on Cluster "Prod-East"', }} isDownloadAvailable={false} areDownloadActionsDisabled={false} @@ -88,7 +88,7 @@ describe('ReportJobStatus', () => { // trigger the hover action fireEvent.mouseEnter(statusIconElement); - const tooltipElement = await screen.findByText('This is an error message'); + const tooltipElement = await screen.findByText('scan failed on Cluster "Prod-East"'); expect(tooltipElement).toBeInTheDocument(); diff --git a/ui/apps/platform/src/Components/ReportJob/ReportJobStatus.tsx b/ui/apps/platform/src/Components/ReportJob/ReportJobStatus.tsx index e9f204ef47638..67c8854ae405f 100644 --- a/ui/apps/platform/src/Components/ReportJob/ReportJobStatus.tsx +++ b/ui/apps/platform/src/Components/ReportJob/ReportJobStatus.tsx @@ -9,7 +9,6 @@ import { PendingIcon, } from '@patternfly/react-icons'; import { Button, Flex, FlexItem, Tooltip } from '@patternfly/react-core'; -import capitalize from 'lodash/capitalize'; import type { ReportStatus } from 'types/reportJob'; import PartialReportModal from './PartialReportModal'; @@ -47,9 +46,7 @@ function ReportJobStatus({ statusIconColorClass = 'pf-v6-u-icon-color-status-danger'; statusTextColorClass = 'pf-v6-u-text-color-status-danger'; statusIcon = ( - + ); diff --git a/ui/apps/platform/src/Components/Reports/View/DeliveryView.tsx b/ui/apps/platform/src/Components/Reports/View/DeliveryView.tsx index ea4548cf21741..3f88899f15278 100644 --- a/ui/apps/platform/src/Components/Reports/View/DeliveryView.tsx +++ b/ui/apps/platform/src/Components/Reports/View/DeliveryView.tsx @@ -1,20 +1,11 @@ import type { ReactElement } from 'react'; -import { - DescriptionList, - DescriptionListDescription, - DescriptionListGroup, - DescriptionListTerm, - Flex, - FlexItem, - Title, -} from '@patternfly/react-core'; +import { DescriptionList, Flex, FlexItem, Title } from '@patternfly/react-core'; import type { BreakpointModifiers } from '@patternfly/react-core'; -import { formatRecurringSchedule } from 'utils/dateUtils'; - import type { DeliveryType } from '../reports.types'; -import NotifierConfigurationDescriptionList from './NotifierConfigurationDescriptionList'; +import NotifierConfigurationsDescriptionListGroup from './NotifierConfigurationsDescriptionListGroup'; +import ScheduleDescriptionListGroup from './ScheduleDescriptionListGroup'; export type DeliveryViewProps = { headingLevel: 'h2' | 'h3'; @@ -27,7 +18,6 @@ function DeliveryView({ horizontalTermWidthModifier, values, }: DeliveryViewProps): ReactElement { - /* eslint-disable react/no-array-index-key */ return ( @@ -39,37 +29,12 @@ function DeliveryView({ isHorizontal horizontalTermWidthModifier={horizontalTermWidthModifier} > - - Destinations - - {values.notifiers.length === 0 ? ( - '-' - ) : ( - - {values.notifiers.map((notifier, index) => ( - - ))} - - )} - - - - Schedule - - {values.schedule ? formatRecurringSchedule(values.schedule) : '-'} - - + + ); - /* eslint-enable react/no-array-index-key */ } export default DeliveryView; diff --git a/ui/apps/platform/src/Components/Reports/View/DetailsDescriptionListGroups.tsx b/ui/apps/platform/src/Components/Reports/View/DetailsDescriptionListGroups.tsx new file mode 100644 index 0000000000000..0d7e409dc218e --- /dev/null +++ b/ui/apps/platform/src/Components/Reports/View/DetailsDescriptionListGroups.tsx @@ -0,0 +1,35 @@ +import type { ReactElement } from 'react'; +import { + DescriptionListDescription, + DescriptionListGroup, + DescriptionListTerm, +} from '@patternfly/react-core'; + +// Generic component for use cases: +// Parameters of compliance scan schedule configuration +// Details of vulnerability report configuration + +export type DetailsDescriptionListGroupsProps = { + description: string; + name: string; +}; + +function DetailsDescriptionListGroups({ + description, + name, +}: DetailsDescriptionListGroupsProps): ReactElement { + return ( + <> + + Name + {name || '-'} + + + Description + {description || '-'} + + + ); +} + +export default DetailsDescriptionListGroups; diff --git a/ui/apps/platform/src/Components/Reports/View/DetailsView.tsx b/ui/apps/platform/src/Components/Reports/View/DetailsView.tsx index 92d9f6b8e7b0a..5cde41afc9feb 100644 --- a/ui/apps/platform/src/Components/Reports/View/DetailsView.tsx +++ b/ui/apps/platform/src/Components/Reports/View/DetailsView.tsx @@ -1,17 +1,11 @@ import type { ReactElement } from 'react'; -import { - DescriptionList, - DescriptionListDescription, - DescriptionListGroup, - DescriptionListTerm, - Flex, - FlexItem, - Title, -} from '@patternfly/react-core'; +import { DescriptionList, Flex, FlexItem, Title } from '@patternfly/react-core'; import type { BreakpointModifiers } from '@patternfly/react-core'; import type { DetailsType } from '../reports.types'; +import DetailsDescriptionListGroups from './DetailsDescriptionListGroups'; + export type DetailsViewProps = { headingLevel: 'h2' | 'h3'; horizontalTermWidthModifier: BreakpointModifiers; @@ -34,18 +28,10 @@ function DetailsView({ isHorizontal horizontalTermWidthModifier={horizontalTermWidthModifier} > - - Name - - {values.name || '-'} - - - - Description - - {values.description || '-'} - - + diff --git a/ui/apps/platform/src/Components/Reports/View/NotifierConfigurationsDescriptionListGroup.tsx b/ui/apps/platform/src/Components/Reports/View/NotifierConfigurationsDescriptionListGroup.tsx new file mode 100644 index 0000000000000..978195737cbb9 --- /dev/null +++ b/ui/apps/platform/src/Components/Reports/View/NotifierConfigurationsDescriptionListGroup.tsx @@ -0,0 +1,47 @@ +import type { ReactElement } from 'react'; +import { + DescriptionListDescription, + DescriptionListGroup, + DescriptionListTerm, + Flex, +} from '@patternfly/react-core'; + +import type { NotifierConfiguration } from 'services/ReportsService.types'; + +import NotifierConfigurationDescriptionList from './NotifierConfigurationDescriptionList'; + +// Generic component for use cases: +// Delivery of compliance scan schedule configuration does not have schedules +// Delivery of vulnerability report configuration does have schedules + +export type NotifierConfigurationsDescriptionListGroupProps = { + notifiers: NotifierConfiguration[]; +}; + +function NotifierConfigurationsDescriptionListGroup({ + notifiers, +}: NotifierConfigurationsDescriptionListGroupProps): ReactElement { + /* eslint-disable react/no-array-index-key */ + return ( + + Destinations + + {notifiers.length === 0 ? ( + '-' + ) : ( + + {notifiers.map((notifier, index) => ( + + ))} + + )} + + + ); + /* eslint-enable react/no-array-index-key */ +} + +export default NotifierConfigurationsDescriptionListGroup; diff --git a/ui/apps/platform/src/Components/Reports/View/ScheduleDescriptionListGroup.tsx b/ui/apps/platform/src/Components/Reports/View/ScheduleDescriptionListGroup.tsx new file mode 100644 index 0000000000000..cc74e0ae9930d --- /dev/null +++ b/ui/apps/platform/src/Components/Reports/View/ScheduleDescriptionListGroup.tsx @@ -0,0 +1,32 @@ +import type { ReactElement } from 'react'; +import { + DescriptionListDescription, + DescriptionListGroup, + DescriptionListTerm, +} from '@patternfly/react-core'; + +import type { Schedule } from 'types/schedule.proto'; +import { formatRecurringSchedule } from 'utils/dateUtils'; + +// Generic component for use cases: +// Parameters of compliance scan schedule configuration +// Delivery of vulnerability report configuration + +export type ScheduleDescriptionListGroupProps = { + schedule: Schedule | null; +}; + +function ScheduleDescriptionListGroup({ + schedule, +}: ScheduleDescriptionListGroupProps): ReactElement { + return ( + + Schedule + + {schedule ? formatRecurringSchedule(schedule) : '-'} + + + ); +} + +export default ScheduleDescriptionListGroup; diff --git a/ui/apps/platform/src/Components/SearchInput/SearchInput.jsx b/ui/apps/platform/src/Components/SearchInput/SearchInput.jsx index fc8373d45b82d..99fc6726f6590 100644 --- a/ui/apps/platform/src/Components/SearchInput/SearchInput.jsx +++ b/ui/apps/platform/src/Components/SearchInput/SearchInput.jsx @@ -32,7 +32,6 @@ export const searchInputPropTypes = { sendAutoCompleteRequest: PropTypes.func, clearAutoComplete: PropTypes.func, autoCompleteCategories: PropTypes.arrayOf(PropTypes.string), - setAllSearchOptions: PropTypes.func, isDisabled: PropTypes.bool, prependAutocompleteQuery: PropTypes.arrayOf( PropTypes.shape({ @@ -53,7 +52,6 @@ export const searchInputDefaultProps = { sendAutoCompleteRequest: null, clearAutoComplete: null, autoCompleteCategories: [], - setAllSearchOptions: () => {}, isDisabled: false, prependAutocompleteQuery: [], }; @@ -108,8 +106,6 @@ class SearchInput extends Component { } sendAutoCompleteRequest = (searchOptions, input) => { - this.props.setAllSearchOptions(searchOptions); - // Don't populate autocomplete if the text box is totally empty, // since we want people to see just the chips in that case. if (!searchOptions.length && !input.length) { diff --git a/ui/apps/platform/src/Components/URLSearchInputWithAutocomplete.jsx b/ui/apps/platform/src/Components/URLSearchInputWithAutocomplete.jsx index 642611d65ef5f..575335cd00c91 100644 --- a/ui/apps/platform/src/Components/URLSearchInputWithAutocomplete.jsx +++ b/ui/apps/platform/src/Components/URLSearchInputWithAutocomplete.jsx @@ -3,10 +3,8 @@ import { useLocation, useNavigate } from 'react-router-dom-v5-compat'; import PropTypes from 'prop-types'; import { components } from 'react-select'; import queryString from 'qs'; -import { connect } from 'react-redux'; import { Filter } from 'react-feather'; -import { actions as searchAutoCompleteActions } from 'reducers/searchAutocomplete'; import { Creatable } from 'Components/ReactSelect'; import searchOptionsToQuery from 'services/searchOptionsToQuery'; import searchContext from 'Containers/searchContext'; @@ -99,7 +97,6 @@ export const removeValuesForKey = (oldOptions, newOptions) => { const URLSearchInputWithAutocomplete = ({ autoCompleteResults, categoryOptions, - setAllSearchOptions, clearAutocomplete, fetchAutocomplete, placeholder, @@ -235,7 +232,6 @@ const URLSearchInputWithAutocomplete = ({ function updateAutocompleteState(searchOptions) { return (input) => { - setAllSearchOptions(searchOptions); if (searchOptions.length === 0) { if (clearAutocomplete) { clearAutocomplete(); @@ -311,7 +307,6 @@ URLSearchInputWithAutocomplete.propTypes = { autoCompleteResults: PropTypes.arrayOf(PropTypes.string), fetchAutocomplete: PropTypes.func, clearAutocomplete: PropTypes.func, - setAllSearchOptions: PropTypes.func.isRequired, prependAutocompleteQuery: PropTypes.arrayOf( PropTypes.shape({ value: PropTypes.string, @@ -328,8 +323,4 @@ URLSearchInputWithAutocomplete.defaultProps = { prependAutocompleteQuery: [], }; -const mapDispatchToProps = { - setAllSearchOptions: searchAutoCompleteActions.setAllSearchOptions, -}; - -export default connect(null, mapDispatchToProps)(URLSearchInputWithAutocomplete); +export default URLSearchInputWithAutocomplete; diff --git a/ui/apps/platform/src/Components/visuals/ArcSingle.jsx b/ui/apps/platform/src/Components/visuals/ArcSingle.jsx deleted file mode 100644 index 52571ec45bcf9..0000000000000 --- a/ui/apps/platform/src/Components/visuals/ArcSingle.jsx +++ /dev/null @@ -1,58 +0,0 @@ -import { ArcSeries, LabelSeries, XYPlot } from 'react-vis'; -import PropTypes from 'prop-types'; - -const RADIUS = 3; -const VALUE_THICKNESS = 0.2; -const FULL_ANGLE = 2 * Math.PI; - -function getDataFromValue(value) { - return [ - { - angle0: 0, - angle: (FULL_ANGLE * value) / 100, - radius0: RADIUS - VALUE_THICKNESS, - radius: RADIUS, - color: 'var(--success-500)', - }, - { - angle0: (FULL_ANGLE * value) / 100, - angle: FULL_ANGLE, - radius0: RADIUS - VALUE_THICKNESS * 0.8, - radius: RADIUS - VALUE_THICKNESS * 0.2, - color: 'var(--base-400)', - }, - ]; -} -const LABEL_STYLE = { - fontSize: '36px', - fill: 'var(--primary-800)', - fontWeight: 500, -}; - -const ArcSingle = ({ value }) => { - const data = getDataFromValue(value); - - return ( - - - - - - ); -}; - -ArcSingle.propTypes = { - value: PropTypes.number.isRequired, -}; - -export default ArcSingle; diff --git a/ui/apps/platform/src/Containers/BaseImages/BaseImagesModal.cy.jsx b/ui/apps/platform/src/Containers/BaseImages/BaseImagesModal.cy.jsx index 524b31099b97d..f8dfb7e4f543c 100644 --- a/ui/apps/platform/src/Containers/BaseImages/BaseImagesModal.cy.jsx +++ b/ui/apps/platform/src/Containers/BaseImages/BaseImagesModal.cy.jsx @@ -49,9 +49,59 @@ describe('BaseImagesModal', () => { cy.get('input#baseImagePath').type('ubuntu'); cy.get('input#baseImagePath').blur(); - cy.contains( - 'Base image path must include both repository and tag separated by ":"' - ).should('be.visible'); + cy.contains('Base image path must include a tag pattern after ":"').should( + 'be.visible' + ); + cy.contains('example-registry.io/path/to/image:tag').should('be.visible'); + cy.contains('For registries with a port, use registry:port/repo:tag').should( + 'be.visible' + ); + }); + + it('should show error when the pattern is placed on the path of a registry with a port', () => { + const onClose = cy.stub(); + const onSuccess = cy.stub(); + + cy.mount( + + + + ); + + // Registry port colon must not be mistaken for the tag separator. + cy.get('input#baseImagePath').type('registry:5000/repo*'); + cy.get('input#baseImagePath').blur(); + cy.contains('Base image path must include a tag pattern after ":"').should( + 'be.visible' + ); + }); + + it('should reject invalid tag pattern characters', () => { + cy.mount( + + + + ); + + cy.get('input#baseImagePath').type('registry:5000/repo:1.@bad'); + cy.get('input#baseImagePath').blur(); + cy.contains('Tag pattern must not contain "/", ":", "@", or whitespace').should( + 'be.visible' + ); + cy.findByRole('button', { name: 'Save' }).should('be.disabled'); + }); + + it('should reject tag patterns longer than 128 bytes', () => { + cy.mount( + + + + ); + + cy.get('input#baseImagePath').type(`ubuntu:${'a'.repeat(129)}`); + cy.get('input#baseImagePath').blur(); + cy.contains('Tag pattern must be at most 128 bytes').should('be.visible'); + cy.findByRole('button', { name: 'Save' }).should('be.disabled'); }); it('should enable save button when form is valid', () => { diff --git a/ui/apps/platform/src/Containers/BaseImages/BaseImagesModal.tsx b/ui/apps/platform/src/Containers/BaseImages/BaseImagesModal.tsx index b6c16d64d39e1..626eec8fa38f6 100644 --- a/ui/apps/platform/src/Containers/BaseImages/BaseImagesModal.tsx +++ b/ui/apps/platform/src/Containers/BaseImages/BaseImagesModal.tsx @@ -60,35 +60,63 @@ const addValidationSchema = yup.object({ baseImagePath: yup .string() .required('Base image path is required') - .test( - 'has-colon', - 'Base image path must include both repository and tag separated by ":"', - (value) => { - if (!value?.includes(':')) { - return false; - } - const lastColonIndex = value.lastIndexOf(':'); - const tagPattern = value.substring(lastColonIndex + 1); - return tagPattern.length > 0; + .test('has-tag', 'Base image path must include a tag pattern after ":"', (value) => { + if (!value) { + return false; + } + const { tagPattern } = parseBaseImagePath(value); + return tagPattern.length > 0; + }) + .test('valid-tag-pattern', function (value) { + if (!value) { + return true; } - ), + const { tagPattern } = parseBaseImagePath(value); + const error = tagPattern && getTagPatternError(tagPattern); + return error ? this.createError({ message: error }) : true; + }), }); const editValidationSchema = yup.object({ - baseImageTagPattern: yup.string().required('Tag pattern is required'), + baseImageTagPattern: yup + .string() + .required('Tag pattern is required') + .test('valid-tag-pattern', function (value) { + const error = value && getTagPatternError(value); + return error ? this.createError({ message: error }) : true; + }), }); type AddFormData = yup.InferType; type EditFormData = yup.InferType; +export function getTagPatternError(tagPattern: string): string | undefined { + if (new TextEncoder().encode(tagPattern).length > 128) { + return 'Tag pattern must be at most 128 bytes'; + } + if (/[/:@\p{White_Space}]/u.test(tagPattern)) { + return 'Tag pattern must not contain "/", ":", "@", or whitespace'; + } + return undefined; +} + /** * Parses a base image path into repository path and tag pattern. - * Format: "docker.io/library/ubuntu:22.04" -> { repoPath: "docker.io/library/ubuntu", tagPattern: "22.04" } + * Only a colon in the final path segment separates the tag; a colon before the last + * "/" belongs to a registry port. Returns an empty tagPattern when no tag is present. + * Examples: + * "docker.io/library/ubuntu:22.04" -> { repoPath: "docker.io/library/ubuntu", tagPattern: "22.04" } + * "registry:5000/library/ubuntu:1.*" -> { repoPath: "registry:5000/library/ubuntu", tagPattern: "1.*" } + * "registry:5000/library/ubuntu" -> { repoPath: "registry:5000/library/ubuntu", tagPattern: "" } */ export function parseBaseImagePath(path: string): { repoPath: string; tagPattern: string } { - const lastColonIndex = path.lastIndexOf(':'); - const repoPath = path.substring(0, lastColonIndex); - const tagPattern = path.substring(lastColonIndex + 1); + const lastSlashIndex = path.lastIndexOf('/'); + const tagColonIndex = path.indexOf(':', lastSlashIndex + 1); + if (tagColonIndex === -1) { + return { repoPath: path, tagPattern: '' }; + } + const repoPath = path.substring(0, tagColonIndex); + const tagPattern = path.substring(tagColonIndex + 1); return { repoPath, tagPattern }; } @@ -298,8 +326,9 @@ function BaseImagesModal({ )} Include repository path and tag (e.g., - example-registry.io/path/to/image:tag). Tag can be a - pattern (e.g., 1.*) + example-registry.io/path/to/image:tag). The tag can be a + pattern such as 1.*. For registries with a port, use + registry:port/repo:tag (e.g., registry:5000/repo:1.*). diff --git a/ui/apps/platform/src/Containers/BaseImages/parseBaseImagePath.test.ts b/ui/apps/platform/src/Containers/BaseImages/parseBaseImagePath.test.ts index fa9243515bb4b..19845e80c91c9 100644 --- a/ui/apps/platform/src/Containers/BaseImages/parseBaseImagePath.test.ts +++ b/ui/apps/platform/src/Containers/BaseImages/parseBaseImagePath.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from 'vitest'; -import { parseBaseImagePath } from './BaseImagesModal'; +import { getTagPatternError, parseBaseImagePath } from './BaseImagesModal'; describe('parseBaseImagePath', () => { it('should parse simple image path with single colon', () => { @@ -29,4 +29,49 @@ describe('parseBaseImagePath', () => { tagPattern: '1.*', }); }); + + it('should keep the tag pattern separate for a registry with a port', () => { + const result = parseBaseImagePath('registry:5000/library/ubuntu:1.*'); + + expect(result).toEqual({ + repoPath: 'registry:5000/library/ubuntu', + tagPattern: '1.*', + }); + }); + + it('should return an empty tag when a registry with a port has no tag', () => { + const result = parseBaseImagePath('registry:5000/library/ubuntu'); + + expect(result).toEqual({ + repoPath: 'registry:5000/library/ubuntu', + tagPattern: '', + }); + }); + + it('should return an empty tag when the pattern is placed on the path instead of the tag', () => { + const result = parseBaseImagePath('registry:5000/library/ubuntu*'); + + expect(result).toEqual({ + repoPath: 'registry:5000/library/ubuntu*', + tagPattern: '', + }); + }); +}); + +describe('getTagPatternError', () => { + it('accepts a tag pattern at the length limit', () => { + expect(getTagPatternError('a'.repeat(128))).toBeUndefined(); + }); + + it('rejects tag patterns longer than 128 bytes', () => { + expect(getTagPatternError('a'.repeat(129))).toBe('Tag pattern must be at most 128 bytes'); + }); + + it('rejects repository separators and whitespace', () => { + ['repo/tag', 'port:tag', 'sha256@digest', '1. *', '1.\u00a0*'].forEach((pattern) => { + expect(getTagPatternError(pattern)).toBe( + 'Tag pattern must not contain "/", ":", "@", or whitespace' + ); + }); + }); }); diff --git a/ui/apps/platform/src/Containers/Clusters/ClusterRegistrationSecrets/SecureClusterModal.tsx b/ui/apps/platform/src/Containers/Clusters/ClusterRegistrationSecrets/SecureClusterModal.tsx index 8cde6f59163ed..8617becaec418 100644 --- a/ui/apps/platform/src/Containers/Clusters/ClusterRegistrationSecrets/SecureClusterModal.tsx +++ b/ui/apps/platform/src/Containers/Clusters/ClusterRegistrationSecrets/SecureClusterModal.tsx @@ -1,44 +1,21 @@ -import { useState } from 'react'; import type { ReactElement } from 'react'; -import { - Alert, - Button, - Divider, - Flex, - FlexItem, - Tab, - TabContent, - TabTitleText, - Tabs, -} from '@patternfly/react-core'; +import { Alert, Button, Flex } from '@patternfly/react-core'; import { Modal } from '@patternfly/react-core/deprecated'; -import SecureClusterUsingHelmChart from './SecureClusterUsingHelmChart'; import SecureClusterUsingOperator from './SecureClusterUsingOperator'; -type TabKey = 'Operator' | 'Helm'; - const headingLevel = 'h2'; -const idHelm = 'SecureClusterUsingHelm'; -const idOperator = 'SecureClusterUsingOperator'; - export type SecureClusterModalProps = { isModalOpen: boolean; setIsModalOpen: (isOpen: boolean) => void; }; function SecureClusterModal({ isModalOpen, setIsModalOpen }): ReactElement { - const [activeKey, setActiveKey] = useState('Operator'); - function onClose() { setIsModalOpen(false); } - function onSelect(_event, tabKey) { - setActiveKey(tabKey); - } - return ( - - - Operator} - /> - Helm chart (deprecated)} - /> - - - - {activeKey === 'Operator' && ( - - - - )} - {activeKey === 'Helm' && ( - - - - )} + @@ -42,28 +26,8 @@ function SecureClusterPage(): ReactElement { Secure a cluster with a cluster registration secret - - setActiveTabKey(tabKey)} - usePageInsets - mountOnEnter - unmountOnExit - > - {operatorTab}}> - - - - - Helm chart (deprecated)} - > - - - - - + + \\', - '--set clusterName= \\', - '--set centralEndpoint= \\', - '--set imagePullSecrets.username= \\', - '--set imagePullSecrets.password=', -].join('\n'); - -export type SecureClusterUsingHelmChartProps = { - headingLevel: 'h2' | 'h3'; -}; - -function SecureClusterUsingHelmChart({ - headingLevel, -}: SecureClusterUsingHelmChartProps): ReactElement { - const subHeadingLevel = headingLevel === 'h2' ? 'h3' : 'h4'; - const { wasCopied, copyToClipboard } = useClipboardCopy(); - - const actions = ( - - copyToClipboard(codeBlock)} - variant="plain" - > - {wasCopied ? 'Copied to clipboard' : 'Copy to clipboard'} - - - ); - - return ( - - - - The rhacs/secured-cluster-services Helm chart is - deprecated since version 4.11 and will be removed in 5.1. - - } - /> - - Prerequisites - - -

- You must have previously added the Helm chart repository, or add it using - the following command: -

- - helm repo add rhacs https://mirror.openshift.com/pub/rhacs/charts/ - -
- -

You must download the YAML file for a cluster registration secret.

-
- -

- You must have access to the Red Hat Container Registry and a pull secret for - authentication. -

-
- -

- You must have the address and the port number that you are exposing the - Central service on. -

-
- - -

Run a command similar to the following:

- - {codeBlock} - -
-
-
-
- ); -} - -export default SecureClusterUsingHelmChart; diff --git a/ui/apps/platform/src/Containers/Collections/errorUtils.test.ts b/ui/apps/platform/src/Containers/Collections/errorUtils.test.ts new file mode 100644 index 0000000000000..ad1a9737cf215 --- /dev/null +++ b/ui/apps/platform/src/Containers/Collections/errorUtils.test.ts @@ -0,0 +1,32 @@ +import { parseConfigError } from './errorUtils'; + +describe('parseConfigError', () => { + it('classifies a collection loop and extracts the loopId from a lower-case-leading message', () => { + const err = new Error( + "edge between 'aaaaaaaa-1111' and 'bbbbbbbb-2222' would create a loop" + ); + + const result = parseConfigError(err); + + expect(result.type).toBe('CollectionLoop'); + expect(result).toHaveProperty('loopId', 'bbbbbbbb-2222'); + }); + + it('classifies an invalid rule and preserves the backend message in details', () => { + const err = new Error('failed to compile regex "["'); + + const result = parseConfigError(err); + + expect(result.type).toBe('InvalidRule'); + expect(result.details).toBe('failed to compile regex "["'); + }); + + it('falls back to UnknownError for an unrecognized message', () => { + const err = new Error('something entirely unexpected happened'); + + const result = parseConfigError(err); + + expect(result.type).toBe('UnknownError'); + expect(result.details).toBe('something entirely unexpected happened'); + }); +}); diff --git a/ui/apps/platform/src/Containers/Compliance/ComplianceSearchInput.jsx b/ui/apps/platform/src/Containers/Compliance/ComplianceSearchInput.jsx deleted file mode 100644 index 259c6fb7f0ede..0000000000000 --- a/ui/apps/platform/src/Containers/Compliance/ComplianceSearchInput.jsx +++ /dev/null @@ -1,51 +0,0 @@ -import PropTypes from 'prop-types'; -import { CLIENT_SIDE_SEARCH_OPTIONS as SEARCH_OPTIONS } from 'constants/searchOptions'; - -import Query from 'Components/CacheFirstQuery'; -import URLSearchInput from 'Components/URLSearchInput'; - -import { SEARCH_OPTIONS_QUERY } from 'queries/search'; - -const addComplianceStateOption = (searchOptions) => { - let modifiedSearchOptions = []; - if (searchOptions) { - modifiedSearchOptions = [...searchOptions]; - modifiedSearchOptions.push(SEARCH_OPTIONS.COMPLIANCE.STATE); - } - return modifiedSearchOptions; -}; - -const ComplianceSearchInput = ({ placeholder, categories, shouldAddComplianceState }) => ( - - {({ data }) => { - if (!data) { - return null; - } - let { searchOptions } = data; - if (shouldAddComplianceState) { - searchOptions = addComplianceStateOption(searchOptions); - } - return ( - - ); - }} - -); - -ComplianceSearchInput.propTypes = { - placeholder: PropTypes.string.isRequired, - categories: PropTypes.arrayOf(PropTypes.string), - shouldAddComplianceState: PropTypes.bool, -}; - -ComplianceSearchInput.defaultProps = { - categories: [], - shouldAddComplianceState: false, -}; - -export default ComplianceSearchInput; diff --git a/ui/apps/platform/src/Containers/Compliance/Dashboard/ComplianceDashboardPage.tsx b/ui/apps/platform/src/Containers/Compliance/Dashboard/ComplianceDashboardPage.tsx deleted file mode 100644 index 7fa1e8d75fcc6..0000000000000 --- a/ui/apps/platform/src/Containers/Compliance/Dashboard/ComplianceDashboardPage.tsx +++ /dev/null @@ -1,240 +0,0 @@ -import { useState } from 'react'; -import type { ReactElement } from 'react'; -import { useApolloClient } from '@apollo/client'; -import { Alert } from '@patternfly/react-core'; - -import Button from 'Components/Button'; -import ComplianceUsageDisclaimer, { - COMPLIANCE_DISCLAIMER_KEY, -} from 'Components/ComplianceUsageDisclaimer'; -import ExportButton from 'Components/ExportButton'; -import PageHeader from 'Components/PageHeader'; -import { resourceTypes } from 'constants/entityTypes'; -import useCaseTypes from 'constants/useCaseTypes'; -import { useBooleanLocalStorage } from 'hooks/useLocalStorage'; -import usePermissions from 'hooks/usePermissions'; -import { fetchComplianceStandardsSortedByName } from 'services/ComplianceService'; -import type { ComplianceStandardMetadata } from 'services/ComplianceService'; -import { getAxiosErrorMessage } from 'utils/responseErrorUtils'; - -import { - AGGREGATED_RESULTS_ACROSS_ENTITY, - AGGREGATED_RESULTS_STANDARDS_BY_ENTITY, -} from 'queries/controls'; -import ScanButton from '../ScanButton'; -import StandardsByEntity from '../widgets/StandardsByEntity'; -import StandardsAcrossEntity from '../widgets/StandardsAcrossEntity'; - -import ManageStandardsError from './ManageStandardsError'; -import ManageStandardsModal from './ManageStandardsModal'; -import ComplianceDashboardTile, { - CLUSTERS_COUNT, - DEPLOYMENTS_COUNT, - NAMESPACES_COUNT, - NODES_COUNT, -} from './ComplianceDashboardTile'; -import ComplianceScanProgress from './ComplianceScanProgress'; -import { useComplianceRunStatuses } from './useComplianceRunStatuses'; -import { isComplianceRouteEnabled } from '../complianceRBAC'; - -const queriesToRefetchOnPollingComplete = [ - CLUSTERS_COUNT, - NODES_COUNT, - NAMESPACES_COUNT, - DEPLOYMENTS_COUNT, - AGGREGATED_RESULTS_STANDARDS_BY_ENTITY(resourceTypes.CLUSTER), - AGGREGATED_RESULTS_ACROSS_ENTITY(resourceTypes.CLUSTER), - AGGREGATED_RESULTS_ACROSS_ENTITY(resourceTypes.NAMESPACE), - AGGREGATED_RESULTS_ACROSS_ENTITY(resourceTypes.NODE), -]; - -function ComplianceDashboardPage(): ReactElement { - const [isDisclaimerAccepted, setIsDisclaimerAccepted] = useBooleanLocalStorage( - COMPLIANCE_DISCLAIMER_KEY, - false - ); - const { hasReadAccess, hasReadWriteAccess } = usePermissions(); - - // Counts and widgets (with one exception) have same conditional rendering as routes. - // Do not require for StandardsAcrossEntity cluster, so page has at least one widget. - // Do require for StandardsByEntity cluster, because it has links to clusters. - const isComplianceRouteEnabledForClusters = isComplianceRouteEnabled( - hasReadAccess, - 'compliance/clusters' - ); - const isComplianceRouteEnabledForDeployments = isComplianceRouteEnabled( - hasReadAccess, - 'compliance/deployments' - ); - const isComplianceRouteEnabledForNamespaces = isComplianceRouteEnabled( - hasReadAccess, - 'compliance/namespaces' - ); - const isComplianceRouteEnabledForNodes = isComplianceRouteEnabled( - hasReadAccess, - 'compliance/nodes' - ); - - const hasWriteAccessForCompliance = hasReadWriteAccess('Compliance'); - - const [isFetchingStandards, setIsFetchingStandards] = useState(false); - const [errorMessageFetching, setErrorMessageFetching] = useState(''); - const [standards, setStandards] = useState([]); - const [isManageStandardsModalOpen, setIsManageStandardsModalOpen] = useState(false); - - const client = useApolloClient(); - - const { runs, error, restartPolling, inProgressScanDetected, isCurrentScanIncomplete } = - useComplianceRunStatuses(queriesToRefetchOnPollingComplete); - - function clickManageStandardsButton() { - setIsFetchingStandards(true); - fetchComplianceStandardsSortedByName() - .then((standardsFetched) => { - setErrorMessageFetching(''); - setStandards(standardsFetched); - setIsManageStandardsModalOpen(true); - }) - .catch((error) => { - setErrorMessageFetching(getAxiosErrorMessage(error)); - setStandards([]); - }) - .finally(() => { - setIsFetchingStandards(false); - }); - } - - function onCloseManageStandardsError() { - setErrorMessageFetching(''); - } - - function onChangeManageStandardsModal() { - setIsManageStandardsModalOpen(false); - - /* - * Same method as for Scan button to clear store of any cached query data, - * so backend filters out standards in query data according to saved update - * to hideScanResults properties. - */ - return client.resetStore(); - } - - function onCancelManageStandardsModal() { - setIsManageStandardsModalOpen(false); - } - - return ( - <> - -
-
- {isComplianceRouteEnabledForClusters && ( - - )} - {isComplianceRouteEnabledForNamespaces && ( - - )} - {isComplianceRouteEnabledForNodes && ( - - )} - {isComplianceRouteEnabledForDeployments && ( - - )} - {hasWriteAccessForCompliance && ( - - )} - {hasWriteAccessForCompliance && ( -
-
- )} - -
-
-
-
- {!isDisclaimerAccepted && ( - setIsDisclaimerAccepted(true)} - className="pf-v6-u-mb-lg" - /> - )} - {(inProgressScanDetected || !!error) && ( -
- {error ? ( - - {getAxiosErrorMessage(error)} - - ) : ( - - )} -
- )} -
- - {isComplianceRouteEnabledForClusters && ( - - )} - {isComplianceRouteEnabledForNamespaces && ( - - )} - {isComplianceRouteEnabledForNodes && ( - - )} -
-
- {errorMessageFetching ? ( - - ) : isManageStandardsModalOpen ? ( - - ) : null} - - ); -} - -export default ComplianceDashboardPage; diff --git a/ui/apps/platform/src/Containers/Compliance/Dashboard/ComplianceDashboardTile.tsx b/ui/apps/platform/src/Containers/Compliance/Dashboard/ComplianceDashboardTile.tsx deleted file mode 100644 index c112b4ab707f0..0000000000000 --- a/ui/apps/platform/src/Containers/Compliance/Dashboard/ComplianceDashboardTile.tsx +++ /dev/null @@ -1,85 +0,0 @@ -import { Link } from 'react-router-dom-v5-compat'; -import { gql, useQuery } from '@apollo/client'; -import type { DocumentNode } from '@apollo/client'; - -import { complianceBasePath } from 'routePaths'; - -import { - entityCountNounOrdinaryCase, - entityNounOrdinaryCasePlural, -} from '../entitiesForCompliance'; - -export const CLUSTERS_COUNT = gql` - query clustersCount { - results: complianceClusterCount - } -`; - -export const NODES_COUNT = gql` - query nodesCount { - results: complianceNodeCount - } -`; - -export const NAMESPACES_COUNT = gql` - query namespacesCount { - results: complianceNamespaceCount - } -`; - -export const DEPLOYMENTS_COUNT = gql` - query deploymentsCount { - results: complianceDeploymentCount - } -`; - -// Subset of ComplianceEntityType -type DashboardTileEntityType = 'CLUSTER' | 'DEPLOYMENT' | 'NAMESPACE' | 'NODE'; - -const queryMap: Record = { - CLUSTER: CLUSTERS_COUNT, - DEPLOYMENT: DEPLOYMENTS_COUNT, - NAMESPACE: NAMESPACES_COUNT, - NODE: NODES_COUNT, -}; - -const entityPathSegmentMap: Record = { - CLUSTER: 'clusters', - DEPLOYMENT: 'deployments', - NAMESPACE: 'namespaces', - NODE: 'nodes', -}; - -export type ComplianceDashboardTileProps = { - entityType: DashboardTileEntityType; -}; - -function ComplianceDashboardTile({ entityType }: ComplianceDashboardTileProps) { - const QUERY = queryMap[entityType]; - const url = `${complianceBasePath}/${entityPathSegmentMap[entityType]}`; - - const { loading, data, error } = useQuery(QUERY); - - if (loading || error || !data) { - return ( -
-
-
{entityNounOrdinaryCasePlural[entityType]}
-
{error ? '(not scanned)' : 'scanned'}
-
-
- ); - } - - const count: number = typeof data?.results === 'number' ? data.results : 0; - return ( - -
-
{entityCountNounOrdinaryCase(count, entityType)}
-
(scanned)
-
- - ); -} - -export default ComplianceDashboardTile; diff --git a/ui/apps/platform/src/Containers/Compliance/Dashboard/ComplianceScanProgress.tsx b/ui/apps/platform/src/Containers/Compliance/Dashboard/ComplianceScanProgress.tsx deleted file mode 100644 index 5847135dc0929..0000000000000 --- a/ui/apps/platform/src/Containers/Compliance/Dashboard/ComplianceScanProgress.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import { Card, CardBody, CardTitle, Progress } from '@patternfly/react-core'; -import type { CardProps } from '@patternfly/react-core'; - -import type { ComplianceRunStatusResponse } from './useComplianceRunStatuses'; - -export type ComplianceScanProgressProps = { - runs: ComplianceRunStatusResponse['complianceRunStatuses']['runs']; -} & CardProps; - -function ComplianceScanProgress({ runs, ...props }: ComplianceScanProgressProps) { - const unfinishedRunCount = runs.filter((run) => run.state !== 'FINISHED').length; - const finishedRunCount = runs.length - unfinishedRunCount; - - const title = - unfinishedRunCount === 0 - ? 'Compliance scanning complete' - : 'Compliance scanning in progress'; - - return ( - - {title} - - - - - ); -} - -export default ComplianceScanProgress; diff --git a/ui/apps/platform/src/Containers/Compliance/Dashboard/ManageStandardsError.tsx b/ui/apps/platform/src/Containers/Compliance/Dashboard/ManageStandardsError.tsx deleted file mode 100644 index 305eb50003f9a..0000000000000 --- a/ui/apps/platform/src/Containers/Compliance/Dashboard/ManageStandardsError.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import type { ReactElement } from 'react'; -import { Alert } from '@patternfly/react-core'; -import { Modal } from '@patternfly/react-core/deprecated'; - -export type ManageStandardsErrorProps = { - onClose: () => void; - errorMessage: string; -}; - -function ManageStandardsError({ onClose, errorMessage }: ManageStandardsErrorProps): ReactElement { - return ( - - - {errorMessage} - - - ); -} - -export default ManageStandardsError; diff --git a/ui/apps/platform/src/Containers/Compliance/Dashboard/ManageStandardsModal.tsx b/ui/apps/platform/src/Containers/Compliance/Dashboard/ManageStandardsModal.tsx deleted file mode 100644 index 789620a0ea71b..0000000000000 --- a/ui/apps/platform/src/Containers/Compliance/Dashboard/ManageStandardsModal.tsx +++ /dev/null @@ -1,144 +0,0 @@ -import { useState } from 'react'; -import type { ReactElement } from 'react'; -import { Alert, Button, Checkbox, Form } from '@patternfly/react-core'; -import { Modal } from '@patternfly/react-core/deprecated'; -import { useFormik } from 'formik'; - -import { patchComplianceStandard } from 'services/ComplianceService'; -import type { ComplianceStandardMetadata } from 'services/ComplianceService'; -import { getAxiosErrorMessage } from 'utils/responseErrorUtils'; - -export type ManageStandardsModalProps = { - onCancel: () => void; - onChange: () => void; - standards: ComplianceStandardMetadata[]; -}; - -/* - * Formik values have standard id as key and showScanResults as value. - * Therefore, negate hideScanResults value here and - * showComplianceScanMap[id] value in onSubmit function below. - */ -function getShowScanResultsMap(standards: ComplianceStandardMetadata[]): Record { - return Object.fromEntries(standards.map(({ id, hideScanResults }) => [id, !hideScanResults])); -} - -function ManageStandardsModal({ - onCancel, - onChange, - standards, -}: ManageStandardsModalProps): ReactElement { - const [errorMessage, setErrorMessage] = useState(''); - const [countFulfilledWhenRejected, setCountFulfilledWhenRejected] = useState(0); - const { dirty, handleSubmit, isSubmitting, setFieldValue, setSubmitting, values } = useFormik< - Record - >({ - initialValues: getShowScanResultsMap(standards), - onSubmit: (showScanResultsMap) => { - setErrorMessage(''); - // Filter standards for which hideScanResults property has changed, - // and them map to promises for patch requests. - // Negate hideScanResults is correct even if property is absent. - const patchRequestPromises = standards - .filter(({ hideScanResults, id }) => !hideScanResults !== showScanResultsMap[id]) - .map(({ id }) => patchComplianceStandard(id, !showScanResultsMap[id])); - - Promise.allSettled(patchRequestPromises) - .then((results) => { - let numberOfRejectedPromises = 0; - let reasonMessage = ''; - results.forEach((result) => { - if (result.status === 'rejected') { - if (numberOfRejectedPromises === 0) { - reasonMessage = getAxiosErrorMessage(result.reason); - } - numberOfRejectedPromises += 1; - } - }); - setSubmitting(false); - - if (numberOfRejectedPromises === 0) { - onChange(); - } else { - setErrorMessage(reasonMessage); - setCountFulfilledWhenRejected(results.length - numberOfRejectedPromises); - } - }) - .catch((error) => { - setErrorMessage(getAxiosErrorMessage(error)); - setSubmitting(false); - }); - }, - }); - - return ( - { - handleSubmit(); - }} - isDisabled={!dirty || isSubmitting} - isLoading={isSubmitting} - > - Save - , - countFulfilledWhenRejected === 0 ? ( - - ) : ( - - ), - ]} - > -
- {standards.map(({ id, name }) => { - return ( - { - return setFieldValue(id, value); - }} - /> - ); - })} - - {errorMessage && ( - - {errorMessage} - - )} -
- ); -} - -export default ManageStandardsModal; diff --git a/ui/apps/platform/src/Containers/Compliance/Dashboard/useComplianceRunStatuses.ts b/ui/apps/platform/src/Containers/Compliance/Dashboard/useComplianceRunStatuses.ts deleted file mode 100644 index 9957b4538d728..0000000000000 --- a/ui/apps/platform/src/Containers/Compliance/Dashboard/useComplianceRunStatuses.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { useState } from 'react'; -import { gql, useApolloClient, useQuery } from '@apollo/client'; -import type { DocumentNode } from '@apollo/client'; - -export type ComplianceRunStatusResponse = { - complianceRunStatuses: { - runs: { state: string }[]; - }; -}; - -function isCurrentScanIncomplete( - runs: ComplianceRunStatusResponse['complianceRunStatuses']['runs'] -) { - return runs.some((run) => run.state !== 'FINISHED'); -} - -const complianceRunStatusesQuery = gql` - query runStatuses($latest: Boolean) { - complianceRunStatuses(latest: $latest) { - runs { - state - } - } - } -`; - -const pollInterval = 10000; -const variables = { - latest: true, -}; - -export type UseComplianceRunStatusesResponse = { - /* The latest compliance scan runs */ - runs: ComplianceRunStatusResponse['complianceRunStatuses']['runs']; - error: unknown; - /* Fetches the latest compliance scan runs, and restarts polling, if necessary */ - restartPolling: () => void; - /* Whether or not an in progress scan was detected during the lifetime of this hook */ - inProgressScanDetected: boolean; - /* Whether or not the latest scan is incomplete */ - isCurrentScanIncomplete: boolean; -}; - -export function useComplianceRunStatuses( - queriesToRefetchOnPollingComplete: DocumentNode[] -): UseComplianceRunStatusesResponse { - const [inProgressScanDetected, setInProgressScanDetected] = useState(false); - - const client = useApolloClient(); - - const { startPolling, stopPolling, error, data, refetch } = useQuery< - ComplianceRunStatusResponse, - typeof variables - >(complianceRunStatusesQuery, { - variables, - pollInterval, - onCompleted, - onError, - errorPolicy: 'all', - notifyOnNetworkStatusChange: true, - }); - - function onCompleted(data: ComplianceRunStatusResponse) { - if (isCurrentScanIncomplete(data.complianceRunStatuses.runs)) { - setInProgressScanDetected(true); - } else { - stopPolling(); - } - - return client.refetchQueries({ include: queriesToRefetchOnPollingComplete }); - } - - function onError() { - stopPolling(); - } - - return { - error, - runs: data?.complianceRunStatuses.runs ?? [], - restartPolling: () => - refetch().then(({ data }) => { - if (isCurrentScanIncomplete(data.complianceRunStatuses.runs)) { - setInProgressScanDetected(true); - startPolling(pollInterval); - } - }), - inProgressScanDetected, - isCurrentScanIncomplete: isCurrentScanIncomplete(data?.complianceRunStatuses.runs ?? []), - }; -} diff --git a/ui/apps/platform/src/Containers/Compliance/Entity/Cluster.jsx b/ui/apps/platform/src/Containers/Compliance/Entity/Cluster.jsx deleted file mode 100644 index 00386c8b4cac7..0000000000000 --- a/ui/apps/platform/src/Containers/Compliance/Entity/Cluster.jsx +++ /dev/null @@ -1,215 +0,0 @@ -import { useContext } from 'react'; - -import entityTypes from 'constants/entityTypes'; -import EntityCompliance from 'Containers/Compliance/widgets/EntityCompliance'; -import ResourceCount from 'Containers/Compliance/widgets/ResourceCount'; -import ClusterVersion from 'Containers/Compliance/widgets/ClusterVersion'; -import Query from 'Components/CacheFirstQuery'; -import usePermissions from 'hooks/usePermissions'; -import { CLUSTER_NAME as QUERY } from 'queries/cluster'; -import ComplianceList from 'Containers/Compliance/List/List'; -import ComplianceByStandards from 'Containers/Compliance/widgets/ComplianceByStandards'; -import Loader from 'Components/Loader'; -import { entityPagePropTypes, entityPageDefaultProps } from 'constants/entityPageProps'; -import searchContext from 'Containers/searchContext'; -import isGQLLoading from 'utils/gqlLoading'; - -import Header from './Header'; -import ResourceTabs from './ResourceTabs'; -import { isComplianceRouteEnabled } from '../complianceRBAC'; - -function getResourceTabs({ - isComplianceRouteEnabledForDeployments, - isComplianceRouteEnabledForNamespaces, - isComplianceRouteEnabledForNodes, -}) { - const resourceTabs = [entityTypes.CONTROL]; - if (isComplianceRouteEnabledForNamespaces) { - resourceTabs.push(entityTypes.NAMESPACE); - } - if (isComplianceRouteEnabledForNodes) { - resourceTabs.push(entityTypes.NODE); - } - if (isComplianceRouteEnabledForDeployments) { - resourceTabs.push(entityTypes.DEPLOYMENT); - } - return resourceTabs; -} - -function processData(data) { - if (!data || !data.cluster) { - return {}; - } - return data.cluster; -} - -const ClusterPage = ({ - entityId, - listEntityType1, - entityId1, - entityType2, - entityListType2, - entityId2, - query, - sidePanelMode, -}) => { - const searchParam = useContext(searchContext); - - const { hasReadAccess } = usePermissions(); - const isComplianceRouteEnabledForDeployments = isComplianceRouteEnabled( - hasReadAccess, - 'compliance/deployments' - ); - const isComplianceRouteEnabledForNamespaces = isComplianceRouteEnabled( - hasReadAccess, - 'compliance/namespaces' - ); - const isComplianceRouteEnabledForNodes = isComplianceRouteEnabled( - hasReadAccess, - 'compliance/nodes' - ); - const isComplianceRouteEnabledForEntities = - isComplianceRouteEnabledForDeployments || - isComplianceRouteEnabledForNamespaces || - isComplianceRouteEnabledForNodes; - - const resourceTabs = getResourceTabs({ - isComplianceRouteEnabledForDeployments, - isComplianceRouteEnabledForNamespaces, - isComplianceRouteEnabledForNodes, - }); - - return ( - - {({ loading, data }) => { - if (isGQLLoading(loading, data)) { - return ; - } - const cluster = processData(data); - const { name, id } = cluster; - let contents; - - if (listEntityType1 && !sidePanelMode) { - const listQuery = { - groupBy: - listEntityType1 === entityTypes.CONTROL ? entityTypes.STANDARD : '', - 'Cluster Id': entityId, - ...query[searchParam], - }; - contents = ( -
- -
- ); - } else { - contents = ( -
-
-
-
- -
-
- -
-
- - - {sidePanelMode && isComplianceRouteEnabledForEntities && ( -
- {isComplianceRouteEnabledForNamespaces && ( -
- -
- )} - {isComplianceRouteEnabledForNodes && ( -
- -
- )} - {isComplianceRouteEnabledForDeployments && ( -
- -
- )} -
- )} -
-
- ); - } - - return ( -
- {!sidePanelMode && ( - <> -
- - - )} - {contents} -
- ); - }} -
- ); -}; - -ClusterPage.propTypes = entityPagePropTypes; -ClusterPage.defaultProps = entityPageDefaultProps; - -export default ClusterPage; diff --git a/ui/apps/platform/src/Containers/Compliance/Entity/Control.jsx b/ui/apps/platform/src/Containers/Compliance/Entity/Control.jsx deleted file mode 100644 index a1e8119ba59e0..0000000000000 --- a/ui/apps/platform/src/Containers/Compliance/Entity/Control.jsx +++ /dev/null @@ -1,166 +0,0 @@ -import { useContext } from 'react'; - -import entityTypes from 'constants/entityTypes'; -import Widget from 'Components/Widget'; -import Query from 'Components/CacheFirstQuery'; -import { CONTROL_QUERY as QUERY } from 'queries/controls'; -import ControlDetails from 'Components/ControlDetails'; -import ControlRelatedResourceList from 'Containers/Compliance/widgets/ControlRelatedResourceList'; -import { entityPagePropTypes, entityPageDefaultProps } from 'constants/entityPageProps'; -import useCases from 'constants/useCaseTypes'; -import ComplianceList from 'Containers/Compliance/List/List'; -import Loader from 'Components/Loader'; -import PageNotFound from 'Components/PageNotFound'; -import searchContext from 'Containers/searchContext'; -import isGQLLoading from 'utils/gqlLoading'; - -import Header from './Header'; -import ResourceTabs from './ResourceTabs'; - -const ControlPage = ({ - entityId, - listEntityType1, - entityId1, - entityType2, - entityListType2, - entityId2, - query, - sidePanelMode, -}) => { - const searchParam = useContext(searchContext); - - return ( - - {({ data, loading }) => { - if (isGQLLoading(loading, data)) { - return ; - } - - if (!data || !data.results) { - return ( - - ); - } - - const { results: control, complianceStandards: standards } = data; - const standard = standards.find((item) => item.id === control.standardId); - const { name, standardId, interpretationText, description } = control; - const standardName = standard ? standard.name : ''; - let contents; - - if (listEntityType1 && !sidePanelMode) { - const listQuery = { - control: name, - ...query[searchParam], - }; - - contents = ( -
- -
- ); - } else { - contents = ( -
-
- - {!!interpretationText.length && ( - -
- {interpretationText} -
-
- )} - {sidePanelMode && ( - <> - - - - - - )} -
-
- ); - } - - return ( -
- {!sidePanelMode && ( - <> -
- - - )} - {contents} -
- ); - }} -
- ); -}; -ControlPage.propTypes = entityPagePropTypes; -ControlPage.defaultProps = entityPageDefaultProps; - -export default ControlPage; diff --git a/ui/apps/platform/src/Containers/Compliance/Entity/Deployment.jsx b/ui/apps/platform/src/Containers/Compliance/Entity/Deployment.jsx deleted file mode 100644 index 7cfd73c681c39..0000000000000 --- a/ui/apps/platform/src/Containers/Compliance/Entity/Deployment.jsx +++ /dev/null @@ -1,194 +0,0 @@ -import { useContext } from 'react'; -import { useLocation } from 'react-router-dom-v5-compat'; -import { gql } from '@apollo/client'; -import pluralize from 'pluralize'; - -import entityTypes from 'constants/entityTypes'; -import Widget from 'Components/Widget'; -import Query from 'Components/CacheFirstQuery'; -import Loader from 'Components/Loader'; -import { entityPagePropTypes, entityPageDefaultProps } from 'constants/entityPageProps'; -import URLService from 'utils/URLService'; -import ComplianceList from 'Containers/Compliance/List/List'; -import EntityCompliance from 'Containers/Compliance/widgets/EntityCompliance'; -import Cluster from 'images/cluster.svg'; -import Namespace from 'images/ns-icon.svg'; -import IconWidget from 'Components/IconWidget'; -import Labels from 'Containers/Compliance/widgets/Labels'; -import ComplianceByStandards from 'Containers/Compliance/widgets/ComplianceByStandards'; -import isGQLLoading from 'utils/gqlLoading'; -import searchContext from 'Containers/searchContext'; -import useWorkflowMatch from 'hooks/useWorkflowMatch'; - -import Header from './Header'; -import ResourceTabs from './ResourceTabs'; - -export const DEPLOYMENT_QUERY = gql` - query getDeployment($id: ID!) { - deployment(id: $id) { - id - clusterId - clusterName - labels { - key - value - } - name - namespace - namespaceId - } - } -`; - -function processData(data) { - if (!data || !data.deployment) { - return {}; - } - - const result = { ...data.deployment }; - return result; -} - -const DeploymentPage = ({ - entityId, - listEntityType1, - entityId1, - entityType2, - entityListType2, - entityId2, - query, - sidePanelMode, -}) => { - const searchParam = useContext(searchContext); - const match = useWorkflowMatch(); - const location = useLocation(); - - return ( - - {({ loading, data }) => { - if (isGQLLoading(loading, data)) { - return ; - } - const deployment = processData(data); - const { name, id, labels, clusterName, namespace, clusterId, namespaceId } = - deployment; - let contents; - - if (listEntityType1 && !sidePanelMode) { - const listQuery = { - groupBy: - listEntityType1 === entityTypes.CONTROL ? entityTypes.STANDARD : '', - deployment: name, - ...query[searchParam], - }; - contents = ( -
- -
- ); - } else { - const clusterUrl = URLService.getURL(match, location) - .base(entityTypes.CLUSTER, clusterId) - .url(); - - const namespaceUrl = URLService.getURL(match, location) - .base(entityTypes.NAMESPACE, namespaceId) - .url(); - - contents = ( -
-
-
-
- -
-
- -
-
- -
-
- - - - - -
-
- ); - } - - return ( -
- {!sidePanelMode && ( - <> -
- - - )} - {contents} -
- ); - }} -
- ); -}; -DeploymentPage.propTypes = entityPagePropTypes; -DeploymentPage.defaultProps = entityPageDefaultProps; - -export default DeploymentPage; diff --git a/ui/apps/platform/src/Containers/Compliance/Entity/Header.jsx b/ui/apps/platform/src/Containers/Compliance/Entity/Header.jsx deleted file mode 100644 index 0b26de161dbf9..0000000000000 --- a/ui/apps/platform/src/Containers/Compliance/Entity/Header.jsx +++ /dev/null @@ -1,63 +0,0 @@ -import PropTypes from 'prop-types'; -import pluralize from 'pluralize'; - -import PageHeader from 'Components/PageHeader'; -import ScanButton from 'Containers/Compliance/ScanButton'; -import ExportButton from 'Components/ExportButton'; -import useCaseTypes from 'constants/useCaseTypes'; -import entityTypes from 'constants/entityTypes'; -import usePermissions from 'hooks/usePermissions'; - -import { entityNounSentenceCaseSingular } from '../entitiesForCompliance'; - -const EntityHeader = ({ entityType, listEntityType, entityName, entityId, searchComponent }) => { - const { hasReadWriteAccess } = usePermissions(); - const hasWriteAccessForCompliance = hasReadWriteAccess('Compliance'); - - const header = entityName || 'Loading...'; - const subHeader = entityNounSentenceCaseSingular[entityType]; - // Leave raw entity types in case customer depends on this convention for export file name. - let exportFilename = listEntityType - ? `${pluralize(listEntityType)} ACROSS ${entityType} "${entityName.toUpperCase()}"` - : `${entityType} "${entityName}"`; - exportFilename = `${exportFilename} Report`; - const scanCluster = entityType === entityTypes.CLUSTER ? entityId : '*'; - const scanStandard = entityType === entityTypes.STANDARD ? entityId : '*'; - - return ( - - {searchComponent} -
- {hasWriteAccessForCompliance && scanCluster && ( - - )} - {(entityType === entityTypes.CLUSTER || entityType === entityTypes.STANDARD) && ( - - )} -
-
- ); -}; - -EntityHeader.propTypes = { - entityType: PropTypes.string, - listEntityType: PropTypes.string, - entityName: PropTypes.string, - entityId: PropTypes.string, - searchComponent: PropTypes.node, -}; - -EntityHeader.defaultProps = { - entityType: '', - listEntityType: '', - entityName: '', - entityId: '', - searchComponent: null, -}; - -export default EntityHeader; diff --git a/ui/apps/platform/src/Containers/Compliance/Entity/Namespace.jsx b/ui/apps/platform/src/Containers/Compliance/Entity/Namespace.jsx deleted file mode 100644 index dd1f037fd3766..0000000000000 --- a/ui/apps/platform/src/Containers/Compliance/Entity/Namespace.jsx +++ /dev/null @@ -1,205 +0,0 @@ -import { useContext } from 'react'; -import pluralize from 'pluralize'; -import { gql } from '@apollo/client'; - -import ComplianceByStandards from 'Containers/Compliance/widgets/ComplianceByStandards'; -import Query from 'Components/CacheFirstQuery'; -import IconWidget from 'Components/IconWidget'; -import Cluster from 'images/cluster.svg'; -import Widget from 'Components/Widget'; -import ComplianceList from 'Containers/Compliance/List/List'; -import ResourceCount from 'Containers/Compliance/widgets/ResourceCount'; -import PageNotFound from 'Components/PageNotFound'; -import isGQLLoading from 'utils/gqlLoading'; -import Loader from 'Components/Loader'; -import Labels from 'Containers/Compliance/widgets/Labels'; -import EntityCompliance from 'Containers/Compliance/widgets/EntityCompliance'; -import entityTypes from 'constants/entityTypes'; -import useCases from 'constants/useCaseTypes'; -import { entityPagePropTypes, entityPageDefaultProps } from 'constants/entityPageProps'; -import searchContext from 'Containers/searchContext'; - -import Header from './Header'; -import ResourceTabs from './ResourceTabs'; - -export const QUERY = gql` - query getNamespace($id: ID!) { - results: namespace(id: $id) { - metadata { - clusterName - name - id - labels { - key - value - } - } - } - } -`; - -function processData(data, entityId) { - const defaultValue = { - labels: [], - name: '', - clusterName: '', - id: entityId, - }; - - if (!data || !data.results || !data.results.metadata) { - return defaultValue; - } - - const { metadata, ...rest } = data.results; - - return { - ...rest, - ...metadata, - }; -} - -const NamespacePage = ({ - entityId, - listEntityType1, - entityId1, - entityType2, - entityListType2, - entityId2, - query, - sidePanelMode, -}) => { - const searchParam = useContext(searchContext); - return ( - - {({ loading, data }) => { - if (isGQLLoading(loading, data)) { - return ; - } - if (!data.results) { - return ( - - ); - } - const namespace = processData(data); - const { name, id, clusterName, labels } = namespace; - let contents; - - if (listEntityType1 && !sidePanelMode) { - const listQuery = { - groupBy: - listEntityType1 === entityTypes.CONTROL ? entityTypes.STANDARD : '', - Namespace: namespace?.name, - ...query[searchParam], - }; - contents = ( -
- -
- ); - } else { - contents = ( -
-
-
-
- -
-
- -
-
- - - - - - {sidePanelMode && ( - <> -
-
- -
-
- - )} -
-
- ); - } - - return ( -
- {!sidePanelMode && ( - <> -
- - - )} - {contents} -
- ); - }} -
- ); -}; -NamespacePage.propTypes = entityPagePropTypes; -NamespacePage.defaultProps = entityPageDefaultProps; - -export default NamespacePage; diff --git a/ui/apps/platform/src/Containers/Compliance/Entity/Node.jsx b/ui/apps/platform/src/Containers/Compliance/Entity/Node.jsx deleted file mode 100644 index 8d033be499762..0000000000000 --- a/ui/apps/platform/src/Containers/Compliance/Entity/Node.jsx +++ /dev/null @@ -1,238 +0,0 @@ -import { useContext } from 'react'; -import pluralize from 'pluralize'; - -import entityTypes from 'constants/entityTypes'; -import { NODE_QUERY } from 'queries/node'; -import Cluster from 'images/cluster.svg'; -import IpAddress from 'images/ip-address.svg'; -import Hostname from 'images/hostname.svg'; -import ContainerRuntime from 'images/container-runtime.svg'; -import ComplianceByStandards from 'Containers/Compliance/widgets/ComplianceByStandards'; -import Widget from 'Components/Widget'; -import Query from 'Components/CacheFirstQuery'; -import IconWidget from 'Components/IconWidget'; -import InfoWidget from 'Components/InfoWidget'; -import Labels from 'Containers/Compliance/widgets/Labels'; -import EntityCompliance from 'Containers/Compliance/widgets/EntityCompliance'; -import Loader from 'Components/Loader'; -import ComplianceList from 'Containers/Compliance/List/List'; -import PageNotFound from 'Components/PageNotFound'; -import { entityPagePropTypes, entityPageDefaultProps } from 'constants/entityPageProps'; -import useCases from 'constants/useCaseTypes'; -import isGQLLoading from 'utils/gqlLoading'; -import { getDateTime, getDistanceStrictAsPhrase } from 'utils/dateUtils'; -import searchContext from 'Containers/searchContext'; - -import Header from './Header'; -import ResourceTabs from './ResourceTabs'; - -function processData(data) { - if (!data || !data.node) { - return { - name: '', - }; - } - - const result = { ...data.node }; - const [ipAddress] = result.internalIpAddresses; - result.ipAddress = ipAddress; - - return result; -} - -const NodePage = ({ - entityId, - listEntityType1, - entityId1, - entityType2, - entityListType2, - entityId2, - query, - sidePanelMode, -}) => { - const searchParam = useContext(searchContext); - return ( - - {({ loading, data }) => { - if (isGQLLoading(loading, data)) { - return ; - } - - if (!data.node) { - return ( - - ); - } - const node = processData(data); - const { - name, - id, - containerRuntimeVersion, - clusterName, - osImage, - ipAddress, - joinedAt, - kernelVersion, - labels, - } = node; - let contents; - - if (listEntityType1 && !sidePanelMode) { - const listQuery = { - groupBy: - listEntityType1 === entityTypes.CONTROL ? entityTypes.STANDARD : '', - node: name, - ...query[searchParam], - }; - contents = ( -
- -
- ); - } else { - contents = ( -
-
-
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- - - - - -
-
- ); - } - - return ( -
- {!sidePanelMode && ( - <> -
- - - )} - {contents} -
- ); - }} -
- ); -}; -NodePage.propTypes = entityPagePropTypes; -NodePage.defaultProps = entityPageDefaultProps; - -export default NodePage; diff --git a/ui/apps/platform/src/Containers/Compliance/Entity/Page.jsx b/ui/apps/platform/src/Containers/Compliance/Entity/Page.jsx deleted file mode 100644 index 385d0511f176a..0000000000000 --- a/ui/apps/platform/src/Containers/Compliance/Entity/Page.jsx +++ /dev/null @@ -1,56 +0,0 @@ -import PropTypes from 'prop-types'; -import { useLocation } from 'react-router-dom-v5-compat'; -import URLService from 'utils/URLService'; -import entityTypes from 'constants/entityTypes'; -import useWorkflowMatch from 'hooks/useWorkflowMatch'; - -import NodePage from './Node'; -import NamespacePage from './Namespace'; -import ClusterPage from './Cluster'; -import ControlPage from './Control'; -import DeploymentPage from './Deployment'; -import StandardPage from './Standard'; - -const ComplianceEntityPage = () => { - const location = useLocation(); - const match = useWorkflowMatch(); - - const params = URLService.getParams(match, location); - - const pageProps = { - entityId: params.pageEntityId, - listEntityType1: params.entityListType1, - entityType1: params.entityType1, - entityId1: params.entityId1, - entityType2: params.entityType2, - entityListType2: params.entityListType2, - entityId2: params.entityId2, - query: params.query, - }; - - const pageTypeMap = { - [entityTypes.CLUSTER]: , - [entityTypes.NODE]: , - [entityTypes.NAMESPACE]: , - [entityTypes.CONTROL]: , - [entityTypes.DEPLOYMENT]: , - [entityTypes.STANDARD]: , - }; - - return pageTypeMap[params.pageEntityType]; -}; - -ComplianceEntityPage.propTypes = { - params: PropTypes.shape({ - entityId: PropTypes.string, - entityType: PropTypes.string, - }), - sidePanelMode: PropTypes.bool, -}; - -ComplianceEntityPage.defaultProps = { - params: null, - sidePanelMode: false, -}; - -export default ComplianceEntityPage; diff --git a/ui/apps/platform/src/Containers/Compliance/Entity/ResourceTabs.jsx b/ui/apps/platform/src/Containers/Compliance/Entity/ResourceTabs.jsx deleted file mode 100644 index 0fdf5148715b0..0000000000000 --- a/ui/apps/platform/src/Containers/Compliance/Entity/ResourceTabs.jsx +++ /dev/null @@ -1,103 +0,0 @@ -import PropTypes from 'prop-types'; -import { Link, useLocation } from 'react-router-dom-v5-compat'; -import useWorkflowMatch from 'hooks/useWorkflowMatch'; -import { resourceLabels } from 'messages/common'; -import URLService from 'utils/URLService'; -import pluralize from 'pluralize'; -import Query from 'Components/ThrowingQuery'; -import { SEARCH_WITH_CONTROLS as QUERY } from 'queries/search'; -import queryService from 'utils/queryService'; -import { getResourceCountFromAggregatedResults } from '../complianceUtils'; - -const ResourceTabs = ({ entityType, entityId, resourceTabs, selectedType }) => { - const match = useWorkflowMatch(); - const location = useLocation(); - - function getLinkToListType(listEntityType) { - return URLService.getURL(match, location) - .base(entityType, entityId) - .push(listEntityType) - .query() - .url(); - } - - function processData(data) { - const tabData = [ - { - title: 'Overview', - link: getLinkToListType(), - type: null, - }, - ]; - - if (resourceTabs.length && data) { - resourceTabs.forEach((type) => { - const count = getResourceCountFromAggregatedResults(type, data); - if (count > 0) { - tabData.push({ - title: `${count} ${pluralize(resourceLabels[type], count)}`, - link: getLinkToListType(type), - type, - }); - } - }); - } - - return tabData; - } - - function getVariables() { - // entitytype.NODE and namespace don't play well in groupBy - return { - query: queryService.objectToWhereClause({ [`${entityType} ID`]: entityId }), - }; - } - - return ( - - {({ loading, data }) => { - if (loading) { - return null; - } - const tabData = processData(data); - return ( -
    - {tabData.map((datum, i) => { - const borderLeft = !i ? 'border-l' : ''; - const bgColor = - datum.type === selectedType || (!selectedType && !datum.type) - ? 'bg-primary-200' - : 'bg-base-100'; - const textColor = 'text-base-600'; - - return ( -
  • - - {datum.title} - -
  • - ); - })} -
- ); - }} -
- ); -}; - -ResourceTabs.propTypes = { - entityType: PropTypes.string.isRequired, - entityId: PropTypes.string.isRequired, - selectedType: PropTypes.string, - resourceTabs: PropTypes.arrayOf(PropTypes.string), -}; - -ResourceTabs.defaultProps = { - resourceTabs: PropTypes.arrayOf(PropTypes.string), - selectedType: null, -}; - -export default ResourceTabs; diff --git a/ui/apps/platform/src/Containers/Compliance/Entity/Standard.jsx b/ui/apps/platform/src/Containers/Compliance/Entity/Standard.jsx deleted file mode 100644 index 44c20dc5d966b..0000000000000 --- a/ui/apps/platform/src/Containers/Compliance/Entity/Standard.jsx +++ /dev/null @@ -1,45 +0,0 @@ -import entityTypes from 'constants/entityTypes'; -import ComplianceList from 'Containers/Compliance/List/List'; -import { entityPagePropTypes, entityPageDefaultProps } from 'constants/entityPageProps'; -import ComplianceSearchInput from '../ComplianceSearchInput'; -import Header from '../List/Header'; - -const StandardPage = ({ - entityId, - listEntityType1, - entityId1, - entityType2, - entityListType2, - entityId2, -}) => { - const listQuery = { - 'Standard Id': entityId, - }; - return ( -
-
- } - /> - -
- ); -}; - -StandardPage.propTypes = entityPagePropTypes; -StandardPage.defaultProps = entityPageDefaultProps; - -export default StandardPage; diff --git a/ui/apps/platform/src/Containers/Compliance/List/Header.jsx b/ui/apps/platform/src/Containers/Compliance/List/Header.jsx deleted file mode 100644 index 44f7ca980ce1b..0000000000000 --- a/ui/apps/platform/src/Containers/Compliance/List/Header.jsx +++ /dev/null @@ -1,61 +0,0 @@ -import PropTypes from 'prop-types'; -import lowerCase from 'lodash/lowerCase'; -import startCase from 'lodash/startCase'; -import findKey from 'lodash/findKey'; - -import PageHeader from 'Components/PageHeader'; -import ExportButton from 'Components/ExportButton'; -import ScanButton from 'Containers/Compliance/ScanButton'; -import { standardLabels } from 'messages/standards'; -import useCaseTypes from 'constants/useCaseTypes'; -import usePermissions from 'hooks/usePermissions'; - -// Disable to prevent merge conflict below if we need to cherry pick fix to previous release. -/* eslint-disable prettier/prettier */ -const ListHeader = ({ entityType, searchComponent, standard }) => { - const { hasReadWriteAccess } = usePermissions(); - const hasWriteAccessForCompliance = hasReadWriteAccess('Compliance'); - - const standardId = findKey(standardLabels, (key) => key === standard) || standard; - - // ROX-25189: Add nullish coalescing to include compliance operator standard id: - // in page header - // in file name - const headerText = standardId - ? standardLabels[standardId] ?? standardId - : `${startCase(lowerCase(entityType))}s`; - - // If standardId is truthy, then standard page entity is CONTROL for address controls?s[standard]=WHAT_EVER&s[groupBy]=CATEGORY - const subHeaderText = standardId ? 'Standard' : 'Resource list'; - return ( - -
{searchComponent}
-
- {hasWriteAccessForCompliance && standardId && ( - - )} - {standardId && ( - - )} -
-
- ); -}; -/* eslint-enable prettier/prettier */ -ListHeader.propTypes = { - searchComponent: PropTypes.element, - entityType: PropTypes.string.isRequired, - standard: PropTypes.string, -}; - -ListHeader.defaultProps = { - searchComponent: null, - standard: null, -}; - -export default ListHeader; diff --git a/ui/apps/platform/src/Containers/Compliance/List/List.jsx b/ui/apps/platform/src/Containers/Compliance/List/List.jsx deleted file mode 100644 index 586ef75dcdd82..0000000000000 --- a/ui/apps/platform/src/Containers/Compliance/List/List.jsx +++ /dev/null @@ -1,78 +0,0 @@ -import PropTypes from 'prop-types'; -import { useLocation, useNavigate } from 'react-router-dom-v5-compat'; -import lowerCase from 'lodash/lowerCase'; -import pluralize from 'pluralize'; - -import URLService from 'utils/URLService'; -import { PageBody } from 'Components/Panel'; -import { searchCategories as searchCategoryTypes } from 'constants/entityTypes'; -import searchContext from 'Containers/searchContext'; -import { searchParams } from 'constants/searchParams'; -import useWorkflowMatch from 'hooks/useWorkflowMatch'; -import ListTable from './Table'; -import SidePanel from './SidePanel'; -import SidePanelAdjacentArea from './SidePanelAdjacentArea'; -import ComplianceSearchInput from '../ComplianceSearchInput'; - -const ComplianceList = ({ entityType, query, selectedRowId, noSearch }) => { - const navigate = useNavigate(); - const location = useLocation(); - const match = useWorkflowMatch(); - function setSelectedRowId(row) { - const { id } = row; - const url = URLService.getURL(match, location) - .set('entityListType1', entityType) - .set('entityId1', id) - .url(); - navigate(url); - } - - const placeholder = `Filter ${pluralize(lowerCase(entityType))}`; - - const searchComponent = noSearch ? null : ( - - ); - - return ( - -
- -
- {selectedRowId && ( - - - - - - )} -
- ); -}; - -export default ComplianceList; - -ComplianceList.propTypes = { - entityType: PropTypes.string.isRequired, - query: PropTypes.shape({}), - selectedRowId: PropTypes.string, - // entityType2: PropTypes.string, - // entityId2: PropTypes.string, - noSearch: PropTypes.bool, -}; - -ComplianceList.defaultProps = { - query: null, - selectedRowId: null, - noSearch: false, - // entityType2: null, - // entityId2: null -}; diff --git a/ui/apps/platform/src/Containers/Compliance/List/Page.jsx b/ui/apps/platform/src/Containers/Compliance/List/Page.jsx deleted file mode 100644 index 6c3d6ba9f21a2..0000000000000 --- a/ui/apps/platform/src/Containers/Compliance/List/Page.jsx +++ /dev/null @@ -1,58 +0,0 @@ -import { useContext } from 'react'; -import PropTypes from 'prop-types'; -import { useLocation } from 'react-router-dom-v5-compat'; -import lowerCase from 'lodash/lowerCase'; -import pluralize from 'pluralize'; - -import URLService from 'utils/URLService'; -import ComplianceList from 'Containers/Compliance/List/List'; -import searchContext from 'Containers/searchContext'; -import useWorkflowMatch from 'hooks/useWorkflowMatch'; -import ComplianceSearchInput from '../ComplianceSearchInput'; -import Header from './Header'; - -const ComplianceListPage = () => { - const location = useLocation(); - const match = useWorkflowMatch(); - const params = URLService.getParams(match, location); - const searchParam = useContext(searchContext); - const query = { ...params.query[searchParam] }; - const { pageEntityListType, entityId1, entityType2, entityListType2, entityId2 } = params; - const placeholder = `Filter ${pluralize(lowerCase(pageEntityListType))}`; - return ( -
-
- } - standard={query.Standard || query.standard} - /> - -
- ); -}; - -ComplianceListPage.propTypes = { - params: PropTypes.shape({ - entityType: PropTypes.string.isRequired, - }), -}; - -ComplianceListPage.defaultProps = { - params: null, -}; - -export default ComplianceListPage; diff --git a/ui/apps/platform/src/Containers/Compliance/List/SidePanel.jsx b/ui/apps/platform/src/Containers/Compliance/List/SidePanel.jsx deleted file mode 100644 index 79d7566d93c3b..0000000000000 --- a/ui/apps/platform/src/Containers/Compliance/List/SidePanel.jsx +++ /dev/null @@ -1,102 +0,0 @@ -import PropTypes from 'prop-types'; -import { ExternalLinkAltIcon } from '@patternfly/react-icons'; -import { Link, useLocation, useNavigate } from 'react-router-dom-v5-compat'; - -import Query from 'Components/CacheFirstQuery'; -import CloseButton from 'Components/CloseButton'; -import { PanelNew, PanelBody, PanelHead, PanelHeadEnd } from 'Components/Panel'; -import { resourceTypes, standardEntityTypes } from 'constants/entityTypes'; -import ControlPage from 'Containers/Compliance/Entity/Control'; -import useWorkflowMatch from 'hooks/useWorkflowMatch'; -import URLService from 'utils/URLService'; -import getEntityName from 'utils/getEntityName'; -import { entityNameQueryMap } from 'utils/queryMap'; -import { truncate } from 'utils/textUtils'; -import NamespacePage from '../Entity/Namespace'; -import ClusterPage from '../Entity/Cluster'; -import NodePage from '../Entity/Node'; -import DeploymentPage from '../Entity/Deployment'; - -const MAX_CONTROL_TITLE = 120; - -const ComplianceListSidePanel = ({ entityType, entityId }) => { - const navigate = useNavigate(); - const location = useLocation(); - const match = useWorkflowMatch(); - - function getEntityPage() { - switch (entityType) { - case resourceTypes.NODE: - return ; - case resourceTypes.NAMESPACE: - return ; - case resourceTypes.CLUSTER: - return ; - case resourceTypes.DEPLOYMENT: - return ; - case standardEntityTypes.CONTROL: - return ; - default: - return null; - } - } - - function closeSidePanel() { - const baseURL = URLService.getURL(match, location).clearSidePanelParams().url(); - navigate(baseURL); - } - const headerUrl = URLService.getURL(match, location).base(entityType, entityId).url(); - - return ( - - {({ loading, data }) => { - let linkText = 'loading...'; - if (!loading && data) { - linkText = truncate(getEntityName(entityType, data), MAX_CONTROL_TITLE); - } - const headerTextComponent = ( -
-
- -
- {linkText} -
- - - - -
-
- ); - const entityPage = getEntityPage(); - return ( - - - {headerTextComponent} - - - - - {entityPage} - - ); - }} -
- ); -}; - -ComplianceListSidePanel.propTypes = { - entityType: PropTypes.string.isRequired, - entityId: PropTypes.string.isRequired, -}; - -export default ComplianceListSidePanel; diff --git a/ui/apps/platform/src/Containers/Compliance/List/SidePanelAdjacentArea.tsx b/ui/apps/platform/src/Containers/Compliance/List/SidePanelAdjacentArea.tsx deleted file mode 100644 index 9c00dbdcc6977..0000000000000 --- a/ui/apps/platform/src/Containers/Compliance/List/SidePanelAdjacentArea.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import type { ReactElement, ReactNode } from 'react'; - -type Width = '1/3' | '2/5' | '1/2' | '3/5'; - -export type SidePanelAdjacentAreaProps = { - children: ReactNode; - width?: Width; -}; - -const widthClassNames: Record = { - '1/3': 'md:w-1/3', // Compliance - '2/5': 'md:w-2/5 xl:w-1/3 xxl:w-1/4', // Violations - '1/2': 'md:w-1/2', // Integrations - '3/5': 'md:w-3/5 lg:w-1/2 xl:w-2/5 xxl:w-1/3', // Risk -}; - -/* - * Render an area (without animation nor overlay) that contains the content of a side panel. - * Assume its parent has position relative. - * Assume it follows its main panel sibling. - * - * If page width is less than medium, the side panel completely covers the main panel. - * If page width is at least medium, the side panel has a fraction of page width (not including side nav). - */ -function SidePanelAdjacentArea({ - children, - width = '1/2', -}: SidePanelAdjacentAreaProps): ReactElement { - return ( -
- {children} -
- ); -} - -export default SidePanelAdjacentArea; diff --git a/ui/apps/platform/src/Containers/Compliance/List/Table.jsx b/ui/apps/platform/src/Containers/Compliance/List/Table.jsx deleted file mode 100644 index a4fb72bdc44a6..0000000000000 --- a/ui/apps/platform/src/Containers/Compliance/List/Table.jsx +++ /dev/null @@ -1,388 +0,0 @@ -import { useState } from 'react'; -import PropTypes from 'prop-types'; -import pluralize from 'pluralize'; -import orderBy from 'lodash/orderBy'; -import { useQuery } from '@apollo/client'; -import { Alert } from '@patternfly/react-core'; - -import entityTypes, { standardTypes } from 'constants/entityTypes'; -import { standardLabels } from 'messages/standards'; -import { CLIENT_SIDE_SEARCH_OPTIONS as SEARCH_OPTIONS } from 'constants/searchOptions'; -import Table from 'Components/Table'; -import { PanelNew, PanelBody, PanelHead, PanelHeadEnd, PanelTitle } from 'Components/Panel'; -import Loader from 'Components/Loader'; -import TablePagination from 'Components/TablePagination'; -import Query from 'Components/CacheFirstQuery'; -import NoResultsMessage from 'Components/NoResultsMessage'; - -import { CLUSTERS_QUERY, NAMESPACES_QUERY, NODES_QUERY, DEPLOYMENTS_QUERY } from 'queries/table'; -import { LIST_STANDARD, STANDARDS_QUERY } from 'queries/standard'; -import queryService from 'utils/queryService'; - -import { complianceEntityTypes, entityCountNounOrdinaryCase } from '../entitiesForCompliance'; -import TableGroup from './TableGroup'; -import { getColumnsByEntity, getColumnsByStandard, getColumnsForControl } from './tableColumns'; - -function getQuery(entityType) { - switch (entityType) { - case entityTypes.CLUSTER: - return CLUSTERS_QUERY; - case entityTypes.NAMESPACE: - return NAMESPACES_QUERY; - case entityTypes.NODE: - return NODES_QUERY; - case entityTypes.DEPLOYMENT: - return DEPLOYMENTS_QUERY; - case entityTypes.CONTROL: - return LIST_STANDARD; - default: - return null; - } -} - -function getVariables(entityType, query) { - const groupBy = - entityType === entityTypes.CONTROL - ? ['CONTROL', 'CATEGORY', ...(query.groupBy ? [query.groupBy] : [])] - : null; - return { - where: queryService.objectToWhereClause(query), - groupBy, - }; -} - -function complianceRate(numPassing, numFailing) { - return numPassing + numFailing > 0 - ? `${Math.round((numPassing / (numPassing + numFailing)) * 100)}%` - : 'N/A'; -} - -function formatResourceData(data, resourceType) { - if (!data || !data.results || data.results.results.length === 0) { - return null; - } - const formattedData = { results: [] }; - const entityMap = {}; - let standardKeyIndex = 0; - let entityKeyIndex = 0; - data.results.results[0].aggregationKeys.forEach(({ scope }, idx) => { - if (scope === 'STANDARD') { - standardKeyIndex = idx; - } - if (scope === resourceType) { - entityKeyIndex = idx; - } - }); - data.results.results.forEach(({ aggregationKeys, keys, numPassing, numFailing }) => { - const curEntity = aggregationKeys[entityKeyIndex].id; - const curStandard = aggregationKeys[standardKeyIndex].id; - const entity = keys[entityKeyIndex]; - if (entity.__typename === '') { - return; - } - const entityMetaData = entity.metadata || {}; - - entityMap[curEntity] = entityMap[curEntity] || { - name: entity?.name || entity?.metadata?.name, - cluster: entity?.clusterName || entityMetaData?.clusterName || entity?.name, - namespace: entity?.namespace, - id: curEntity, - overall: { - numPassing: 0, - numFailing: 0, - average: 0, - }, - }; - - if (numPassing + numFailing > 0) { - entityMap[curEntity][curStandard] = complianceRate(numPassing, numFailing); - } - entityMap[curEntity].overall.numPassing += numPassing; - entityMap[curEntity].overall.numFailing += numFailing; - }); - - Object.keys(entityMap).forEach((cluster) => { - const overallCluster = { ...entityMap[cluster] }; - const { numPassing, numFailing } = overallCluster.overall; - overallCluster.overall.average = complianceRate(numPassing, numFailing); - formattedData.results.push(overallCluster); - }); - - return formattedData; -} - -function formatStandardData(data) { - if (!data.results || !data.results.results || data.results.results.length === 0) { - return null; - } - const formattedData = { results: [], totalRows: 0 }; - const groups = {}; - let controlKeyIndex = null; - let categoryKeyIndex = null; - let groupByKeyIndex = null; - data.results.results[0].aggregationKeys.forEach(({ scope }, idx) => { - if (scope === 'CONTROL') { - controlKeyIndex = idx; - } - if (scope === 'CATEGORY') { - categoryKeyIndex = idx; - } - if (scope !== 'CATEGORY' && scope !== 'CONTROL') { - groupByKeyIndex = idx; - } - }); - data.results.results.forEach(({ keys, numPassing, numFailing }) => { - const groupKey = groupByKeyIndex === null ? categoryKeyIndex : groupByKeyIndex; - const { - id: standard, - name, - clusterName, - description: groupDescription, - metadata, - __typename, - } = keys[groupKey]; - // the check below is to address ROX-1420 - if (__typename !== '') { - let groupName = name || standardLabels[standard] || standard; - if (__typename === 'Node') { - groupName = `${clusterName}/${name}`; - } else if (__typename === 'Namespace') { - groupName = `${metadata?.clusterName}/${metadata?.name}`; - } - if (!groups[groupName]) { - const groupId = parseInt(groupName, 10) || groupName; - groups[groupName] = { - groupId, - name: `${groupName} ${groupDescription ? `- ${groupDescription}` : ''}`, - rows: [], - }; - } - if (controlKeyIndex) { - const { id, name: controlName, description, standardId } = keys[controlKeyIndex]; - groups[groupName].rows.push({ - id, - description, - standardId, - standard: standardLabels[standardId], - control: controlName, - compliance: complianceRate(numPassing, numFailing), - group: groupName, - }); - } - } - }); - Object.keys(groups).forEach((group) => { - formattedData.results.push(groups[group]); - formattedData.totalRows += groups[group].rows.length; - }); - formattedData.results = orderBy(formattedData.results, ['groupId', 'name'], ['asc', 'asc']); - return formattedData; -} - -function canFilterByComplianceState(filterQuery, complianceStateKey) { - return ( - filterQuery[complianceStateKey] && - (!Array.isArray(filterQuery[complianceStateKey]) || - filterQuery[complianceStateKey].length <= 1) - ); -} - -const ListTable = ({ searchComponent, entityType, query, selectedRowId, updateSelectedRow }) => { - const [page, setPage] = useState(0); - // This is a client-side implementation of filtering by the "Compliance State" Search Option - function filterByComplianceState(data, filterQuery, isControlList) { - const complianceStateKey = SEARCH_OPTIONS.COMPLIANCE.STATE; - if (!canFilterByComplianceState(filterQuery, complianceStateKey)) { - return data.results; - } - const val = filterQuery[complianceStateKey].toLowerCase(); - const isPassing = val === 'pass'; - const isFailing = val === 'fail'; - const { results } = data; - if (isControlList) { - return results - .map((result) => { - const newResult = { ...result }; - newResult.rows = result.rows.filter((row) => { - const intValue = parseInt(row.compliance, 10); // strValue comes in the format "100.00%" - if (Number.isNaN(intValue)) { - return false; - } - if (isPassing) { - return intValue === 100; - } - if (isFailing) { - return intValue !== 100; - } - return true; - }); - return newResult; - }) - .filter((result) => result.rows.length); - } - - return results.filter((item) => - Object.values(standardTypes).reduce((acc, standardId) => { - if (!item[standardId]) { - return acc; - } - const intValue = parseInt(item[standardId], 10); - if (isPassing) { - if (acc === false) { - return acc; - } - return intValue === 100; - } - if (isFailing) { - if (acc === true) { - return acc; - } - return intValue !== 100; - } - return acc; - }, null) - ); - } - - function getTotalRows(data, isStandard) { - if (!isStandard) { - return data.length; - } - return data.reduce((acc, group) => acc + group.rows.length, 0); - } - - const { loading: loadingStandards, data: standardsData } = useQuery(STANDARDS_QUERY); - if (loadingStandards) { - return ; - } - const { standardId } = query; - const gqlQuery = getQuery(entityType); - const variables = getVariables(entityType, query); - const isControlList = entityType === entityTypes.CONTROL; - const formatData = isControlList ? formatStandardData : formatResourceData; - let tableColumns; - if (standardId) { - tableColumns = getColumnsByStandard(standardId); - } else if (isControlList) { - tableColumns = getColumnsForControl(query); - } else { - tableColumns = getColumnsByEntity(entityType, standardsData.results); - } - let tableData; - - return ( - - {({ loading, data }) => { - let contents = ; - let headerComponent; - let headerText; - let totalRows = 0; - - if (!loading || (data && data.results)) { - const formattedData = formatData(data, entityType); - if (!formattedData) { - headerText = entityCountNounOrdinaryCase(0, entityType); - contents = ; - } else { - tableData = filterByComplianceState(formattedData, query, isControlList); - totalRows = getTotalRows(tableData, isControlList); - const entityCountNoun = entityCountNounOrdinaryCase(totalRows, entityType); - const { groupBy } = query; - - // Resouces: CLUSTER, NAMESPACE, NODE, DEPLOYMENT. - // Or CATEGORY from View Standard link of sunburst graph on dashboard. - // Or STANDARD on Controls tab of resource single page. - // Otherwise undefined. - const { length } = tableData; - const groupedByText = groupBy - ? ` across ${ - complianceEntityTypes.includes(groupBy) - ? entityCountNounOrdinaryCase(length, groupBy) - : `${length} ${pluralize(groupBy.toLowerCase(), length)}` - }` - : ''; - headerText = `${entityCountNoun}${groupedByText}`; - - const tableElement = isControlList ? ( - - ) : ( - - ); - contents = ( - <> - {data.results.errorMessage && ( - - {data.results.errorMessage} - - )} - {tableElement} - - ); - } - headerComponent = isControlList ? null : ( - <> -
{searchComponent}
- - - ); - } - return ( - - - - {headerComponent} - - {contents} - - ); - }} - - ); -}; - -ListTable.propTypes = { - searchComponent: PropTypes.node, - entityType: PropTypes.string, - query: PropTypes.shape({ - standardId: PropTypes.string, - groupBy: PropTypes.string, - }), - selectedRowId: PropTypes.string, - updateSelectedRow: PropTypes.func.isRequired, -}; - -ListTable.defaultProps = { - searchComponent: null, - selectedRowId: null, - entityType: null, - query: null, -}; - -export default ListTable; diff --git a/ui/apps/platform/src/Containers/Compliance/List/TableGroup.jsx b/ui/apps/platform/src/Containers/Compliance/List/TableGroup.jsx deleted file mode 100644 index cac5d310637a4..0000000000000 --- a/ui/apps/platform/src/Containers/Compliance/List/TableGroup.jsx +++ /dev/null @@ -1,102 +0,0 @@ -import { Component } from 'react'; -import PropTypes from 'prop-types'; -import Table from 'Components/Table'; -import Collapsible from 'react-collapsible'; -import { ChevronDown, ChevronUp } from 'react-feather'; - -import { entityCountNounOrdinaryCase } from '../entitiesForCompliance'; - -const icons = { - opened: , - closed: , -}; - -class TableGroup extends Component { - static propTypes = { - groups: PropTypes.arrayOf( - PropTypes.shape({ - name: PropTypes.string.isRequired, - rows: PropTypes.arrayOf(PropTypes.shape()), - }) - ).isRequired, - totalRows: PropTypes.number.isRequired, - onRowClick: PropTypes.func.isRequired, - tableColumns: PropTypes.arrayOf(PropTypes.shape({})).isRequired, - idAttribute: PropTypes.string.isRequired, - entityType: PropTypes.string.isRequired, - selectedRowId: PropTypes.string, - }; - - static defaultProps = { - selectedRowId: null, - }; - - renderSubTable = ({ rows }) => { - const { tableColumns, onRowClick, selectedRowId, idAttribute } = this.props; - if (rows.length === 0) { - return null; - } - return ( -
- ); - }; - - renderGroupByCollapsible = (state, { name, rows }) => ( -
-
-
-
- {icons[state]} -
-

{name}

-
-
- {entityCountNounOrdinaryCase(rows.length, this.props.entityType)} -
-
-
- ); - - renderWhenOpened = (group) => this.renderGroupByCollapsible('opened', group); - - renderWhenClosed = (group) => this.renderGroupByCollapsible('closed', group); - - render() { - const { groups, totalRows } = this.props; - return ( -
- {groups.map((group, idx) => ( - - {this.renderSubTable(group)} - - ))} -
- ); - } -} - -export default TableGroup; diff --git a/ui/apps/platform/src/Containers/Compliance/List/tableColumns.jsx b/ui/apps/platform/src/Containers/Compliance/List/tableColumns.jsx deleted file mode 100644 index f32ff66ba3da8..0000000000000 --- a/ui/apps/platform/src/Containers/Compliance/List/tableColumns.jsx +++ /dev/null @@ -1,242 +0,0 @@ -import isEmpty from 'lodash/isEmpty'; -import qs from 'qs'; - -import { defaultHeaderClassName, defaultColumnClassName } from 'Components/Table'; -import TableCellLink from 'Components/TableCellLink'; -import { resourceTypes, standardBaseTypes } from 'constants/entityTypes'; -import { sortVersion } from 'sorters/sorters'; -import { complianceBasePath } from 'routePaths'; - -const getColumnValue = (row, accessor) => (row[accessor] ? row[accessor] : 'N/A'); - -const columnsForStandard = (function getColumnsForStandards() { - const ret = {}; - Object.entries(standardBaseTypes).forEach(([baseType, columnName]) => { - ret[baseType] = { - accessor: baseType, - Header: columnName, - Cell: ({ original }) => getColumnValue(original, baseType), - }; - }); - return ret; -})(); - -function getColumnForStandard(id) { - return { - accessor: id, - Header: standardBaseTypes[id] || id, - Cell: ({ original }) => getColumnValue(original, id), - }; -} - -const getClusterColumns = (standards) => [ - { - accessor: 'id', - Header: 'id', - headerClassName: 'hidden', - className: 'hidden', - }, - { - accessor: 'name', - Header: 'Cluster', - Cell: ({ original, pdf }) => { - const url = `${complianceBasePath}/clusters/${original.id}`; - return ( - - {original.name} - - ); - }, - }, - ...standards.map(({ id }) => getColumnForStandard(id)), - { - accessor: 'overall.average', - Header: 'Overall', - }, -]; - -// TODO verify that this is obsolete. -// If not obsolete, it might need query argument like getColumnsForControl function. -const getStandardColumns = (standard) => [ - { - accessor: 'id', - Header: 'id', - headerClassName: 'hidden', - className: 'hidden', - }, - { - accessor: 'control', - sortMethod: sortVersion, - Header: `${standard} Controls`, - headerClassName: `w-5/6 ${defaultHeaderClassName}`, - className: `w-5/6 ${defaultColumnClassName}`, - Cell: ({ original, pdf }) => { - const url = `${complianceBasePath}/controls/${original.id}`; - return ( - - {`${original.control} - ${original.description}`} - - ); - }, - }, - { - accessor: 'compliance', - Header: 'Compliance', - headerClassName: `w-1/8 flex justify-end pr-4 ${defaultHeaderClassName}`, - className: `w-1/8 justify-end pr-4 ${defaultColumnClassName}`, - }, -]; - -const getNodeColumns = (standards) => [ - { - accessor: 'id', - Header: 'id', - headerClassName: 'hidden', - className: 'hidden', - }, - { - accessor: 'name', - Header: 'Node', - Cell: ({ original, pdf }) => { - const url = `${complianceBasePath}/nodes/${original.id}`; - return ( - - {original.name} - - ); - }, - }, - { - accessor: 'cluster', - Header: 'Cluster', - }, - ...standards.map(({ id }) => columnsForStandard[id]), - { - accessor: 'overall.average', - Header: 'Overall', - }, -]; - -const getNamespaceColumns = (standards) => [ - { - accessor: 'id', - Header: 'id', - headerClassName: 'hidden', - className: 'hidden', - }, - { - accessor: 'name', - Header: 'Namespace', - Cell: ({ original, pdf }) => { - const url = `${complianceBasePath}/namespaces/${original.id}`; - return ( - - {original.name} - - ); - }, - }, - { - accessor: 'cluster', - Header: 'Cluster', - }, - ...standards.map(({ id }) => columnsForStandard[id]), - { - accessor: 'overall.average', - Header: 'Overall', - }, -]; - -const getDeploymentColumns = (standards) => [ - { - accessor: 'id', - Header: 'id', - headerClassName: 'hidden', - className: 'hidden', - }, - { - accessor: 'name', - Header: 'Name', - Cell: ({ original, pdf }) => { - const url = `${complianceBasePath}/deployments/${original.id}`; - return ( - - {original.name} - - ); - }, - }, - { - accessor: 'cluster', - Header: 'Cluster Name', - }, - { - accessor: 'namespace', - Header: 'Namespace', - }, - ...standards.map(({ id }) => columnsForStandard[id]), - { - accessor: 'overall.average', - Header: 'Overall', - }, -]; - -export function getColumnsForControl(query) { - return [ - { - accessor: 'id', - Header: 'id', - headerClassName: 'hidden', - className: 'hidden', - }, - { - accessor: 'control', - sortMethod: sortVersion, - Header: `Control`, - headerClassName: `w-5/6 ${defaultHeaderClassName}`, - className: `w-5/6 ${defaultColumnClassName}`, - Cell: ({ original, pdf }) => { - const search = isEmpty(query) - ? '' - : qs.stringify(query, { - addQueryPrefix: true, - arrayFormat: 'indices', - encodeValuesOnly: true, - }); - const url = `${complianceBasePath}/controls/${original.id}${search}`; - return ( - - {`${original.control} - ${original.description}`} - - ); - }, - }, - { - accessor: 'compliance', - Header: 'Compliance', - headerClassName: `w-1/8 flex justify-end pr-4 ${defaultHeaderClassName}`, - className: `w-1/8 justify-end pr-4 ${defaultColumnClassName}`, - }, - ]; -} - -export function getColumnsByEntity(entityType, standards) { - const filteredStandards = standards.filter(({ scopes }) => scopes.includes(entityType)); - switch (entityType) { - case resourceTypes.CLUSTER: - return getClusterColumns(filteredStandards); - case resourceTypes.NODE: - return getNodeColumns(filteredStandards); - case resourceTypes.NAMESPACE: - return getNamespaceColumns(filteredStandards); - case resourceTypes.DEPLOYMENT: - return getDeploymentColumns(filteredStandards); - default: - return []; - } -} - -// TODO verify that this is obsolete. -export function getColumnsByStandard(standardID) { - return getStandardColumns(standardBaseTypes[standardID]); -} diff --git a/ui/apps/platform/src/Containers/Compliance/List/tableColumns.test.js b/ui/apps/platform/src/Containers/Compliance/List/tableColumns.test.js deleted file mode 100644 index 717839b7a0a92..0000000000000 --- a/ui/apps/platform/src/Containers/Compliance/List/tableColumns.test.js +++ /dev/null @@ -1,68 +0,0 @@ -/* eslint vitest/expect-expect: ["error", { "assertFunctionNames": ["expectColumnsToContain", "expectColumnsNotToContain"] }] */ - -import { resourceTypes, standardTypes } from 'constants/entityTypes'; -import { getColumnsByEntity, getColumnsByStandard } from './tableColumns'; - -function expectColumnsToContain(columns, shouldContain) { - const accessors = columns.map((c) => c.accessor); - expect(accessors).toEqual(expect.arrayContaining(shouldContain)); -} - -function expectColumnsNotToContain(columns, shouldNotContain) { - const accessors = columns.map((c) => c.accessor); - expect(accessors).toEqual(expect.not.arrayContaining(shouldNotContain)); -} - -const standards = [ - { - id: 'CIS_Kubernetes_v1_5', - name: 'CIS Kubernetes v1.5', - scopes: ['CLUSTER', 'NODE'], - }, - { - id: 'HIPAA_164', - name: 'HIPAA 164', - scopes: ['CLUSTER', 'NAMESPACE', 'DEPLOYMENT'], - }, - { - id: 'NIST_800_190', - name: 'NIST SP 800-190', - scopes: ['CLUSTER', 'NAMESPACE', 'DEPLOYMENT', 'NODE'], - }, - { - id: 'NIST_SP_800_53_Rev_4', - name: 'NIST SP 800-53', - scopes: ['CLUSTER', 'NAMESPACE', 'DEPLOYMENT'], - }, - { - id: 'PCI_DSS_3_2', - name: 'PCI DSS 3.2.1', - scopes: ['CLUSTER', 'NAMESPACE', 'DEPLOYMENT'], - }, -]; - -describe('Get columns', () => { - it('can get columns by entity with exclusion', () => { - expectColumnsToContain(getColumnsByEntity(resourceTypes.CLUSTER, standards), [ - 'id', - standardTypes.NIST_800_190, - standardTypes.NIST_SP_800_53_Rev_4, - ]); - - expectColumnsToContain(getColumnsByEntity(resourceTypes.NODE, standards), [ - standardTypes.NIST_800_190, - ]); - - expectColumnsNotToContain(getColumnsByEntity(resourceTypes.NODE, standards), [ - standardTypes.NIST_SP_800_53_Rev_4, - ]); - }); - - it('can get columns by standard', () => { - expectColumnsToContain(getColumnsByStandard(standardTypes.NIST_SP_800_53_Rev_4), [ - 'id', - 'compliance', - 'control', - ]); - }); -}); diff --git a/ui/apps/platform/src/Containers/Compliance/Page.jsx b/ui/apps/platform/src/Containers/Compliance/Page.jsx deleted file mode 100644 index ea130f752c833..0000000000000 --- a/ui/apps/platform/src/Containers/Compliance/Page.jsx +++ /dev/null @@ -1,37 +0,0 @@ -import { memo } from 'react'; -import { Route, Routes } from 'react-router-dom-v5-compat'; -import isEqual from 'lodash/isEqual'; - -import PageNotFound from 'Components/PageNotFound'; -import searchContext from 'Containers/searchContext'; - -import Dashboard from './Dashboard/ComplianceDashboardPage'; -import Entity from './Entity/Page'; -import List from './List/Page'; - -const complianceListPath = `:entityId1?/:entityType2?/:entityId2?`; -const complianceEntityPath = `:pageEntityId?/:entityType1?/:entityId1?/:entityType2?/:entityId2?`; - -const Page = () => ( - - - } /> - } /> - } /> - } /> - } /> - } /> - - } /> - } /> - } /> - } /> - } /> - } /> - - } /> - - -); - -export default memo(Page, isEqual); diff --git a/ui/apps/platform/src/Containers/Compliance/ScanButton.jsx b/ui/apps/platform/src/Containers/Compliance/ScanButton.jsx deleted file mode 100644 index 235d4cd851752..0000000000000 --- a/ui/apps/platform/src/Containers/Compliance/ScanButton.jsx +++ /dev/null @@ -1,90 +0,0 @@ -import { Component } from 'react'; -import { connect } from 'react-redux'; -import PropTypes from 'prop-types'; -import { Mutation } from '@apollo/client/react/components'; -import { RefreshCcw } from 'react-feather'; -import { Spinner } from '@patternfly/react-core'; - -import { actions as notificationActions } from 'reducers/notifications'; -import { TRIGGER_SCAN } from 'queries/standard'; -import Button from 'Components/Button'; - -class ScanButton extends Component { - static propTypes = { - className: PropTypes.string, - text: PropTypes.string.isRequired, - textCondensed: PropTypes.string, - textClass: PropTypes.string, - clusterId: PropTypes.string, - standardId: PropTypes.string, - loaderSize: PropTypes.number, - addToast: PropTypes.func.isRequired, - removeToast: PropTypes.func.isRequired, - onScanTriggered: PropTypes.func, - scanInProgress: PropTypes.bool, - }; - - static defaultProps = { - className: 'btn btn-base h-10', - clusterId: '*', - textClass: null, - textCondensed: null, - standardId: '*', - loaderSize: 20, - onScanTriggered: () => {}, - }; - - onClick = (triggerScan) => () => { - const { clusterId, standardId } = this.props; - triggerScan({ variables: { clusterId, standardId } }) - .then(() => { - this.props.onScanTriggered(); - }) - .catch((e) => { - this.props.addToast(e.message); - setTimeout(this.props.removeToast, 2000); - }); - }; - - render() { - const { className, text, textCondensed, textClass, loaderSize, scanInProgress } = - this.props; - - return ( - - {(triggerScan, { loading }) => { - return ( - - - ); - - contents = ( - - ); - } else if ( - data?.checks?.results?.length === 0 && - data?.results?.results?.length === 0 - ) { - contents = ( -
- No data available. Please run a scan. -
- ); - } - - return ( - - {contents} - - ); - }} - - ); -}; - -ComplianceByStandard.propTypes = { - standardName: PropTypes.string.isRequired, - standardId: PropTypes.string.isRequired, - entityName: PropTypes.string, - entityType: PropTypes.string, - entityId: PropTypes.string, - className: PropTypes.string, -}; - -ComplianceByStandard.defaultProps = { - entityId: null, - entityType: null, - entityName: null, - className: '', -}; - -export default ComplianceByStandard; diff --git a/ui/apps/platform/src/Containers/Compliance/widgets/ComplianceByStandards.tsx b/ui/apps/platform/src/Containers/Compliance/widgets/ComplianceByStandards.tsx deleted file mode 100644 index 6bf3709fe269e..0000000000000 --- a/ui/apps/platform/src/Containers/Compliance/widgets/ComplianceByStandards.tsx +++ /dev/null @@ -1,66 +0,0 @@ -import type { ReactElement } from 'react'; -import { useQuery } from '@apollo/client'; - -import Loader from 'Components/Loader'; -import { STANDARDS_QUERY } from 'queries/standard'; -import type { ComplianceStandardScope } from 'services/ComplianceService'; - -import ComplianceByStandard from './ComplianceByStandard'; - -type StandardsQueryDataType = { - results: { - id: string; - name: string; - scopes: ComplianceStandardScope[]; - hidden: boolean; - }[]; -}; - -type ComplianceByStandardsProps = { - entityId: string; - entityName: string; - entityType: ComplianceStandardScope; -}; - -function ComplianceByStandards({ - entityId, - entityName, - entityType, -}: ComplianceByStandardsProps): ReactElement { - const { loading, data, error } = useQuery(STANDARDS_QUERY); - if (loading) { - return ; - } - - if (error) { - return ( -
- A database error has occurred. Please check that you have the correct permissions to - view this information. -
- ); - } - - const standards = !data?.results - ? [] - : !entityType - ? data.results - : data.results.filter(({ scopes }) => scopes.includes(entityType)); - - return ( - <> - {standards.map(({ name: standardName, id: standardId }) => ( - - ))} - - ); -} - -export default ComplianceByStandards; diff --git a/ui/apps/platform/src/Containers/Compliance/widgets/ControlRelatedResourceList.jsx b/ui/apps/platform/src/Containers/Compliance/widgets/ControlRelatedResourceList.jsx deleted file mode 100644 index 832a72e6040a3..0000000000000 --- a/ui/apps/platform/src/Containers/Compliance/widgets/ControlRelatedResourceList.jsx +++ /dev/null @@ -1,148 +0,0 @@ -import { useContext } from 'react'; -import PropTypes from 'prop-types'; -import URLService from 'utils/URLService'; -import entityTypes from 'constants/entityTypes'; -import useCases from 'constants/useCaseTypes'; -import useWorkflowMatch from 'hooks/useWorkflowMatch'; -import { AGGREGATED_RESULTS as QUERY } from 'queries/controls'; -import queryService from 'utils/queryService'; -import { useLocation, Link } from 'react-router-dom-v5-compat'; -import searchContext from 'Containers/searchContext'; - -import { entityNounOrdinaryCase } from '../entitiesForCompliance'; -import LinkListWidget from './LinkListWidget'; - -const ControlRelatedResourceList = ({ - listEntityType, - pageEntityType, - pageEntity, - limit, - standard, - className, -}) => { - const linkContext = useCases.COMPLIANCE; - const searchParam = useContext(searchContext); - const location = useLocation(); - const match = useWorkflowMatch(); - - function processData(data) { - if (!data || !data.results) { - return []; - } - - const { clusters } = data; - let options = clusters; - if (listEntityType === entityTypes.NAMESPACE) { - options = - clusters?.reduce( - (acc, cluster) => - acc.concat( - cluster.namespaces.map((ns) => ({ - ...ns.metadata, - name: `${cluster?.name}/${ns?.metadata?.name}`, - })) - ), - [] - ) || []; - } else if (listEntityType === entityTypes.NODE) { - options = - clusters?.reduce( - (acc, cluster) => - acc.concat( - cluster.nodes.map((node) => ({ - ...node, - name: `${cluster?.name}/${node?.name}`, - })) - ), - [] - ) || []; - } else if (listEntityType === entityTypes.DEPLOYMENT) { - options = data.deployments || []; - } - - function getEntityName(id) { - const found = options.find((item) => item.id === id); - return found ? found.name : null; - } - - const ids = data.results.results - .filter((item) => item.numPassing > 0 || item.numFailing > 0) - .map((item) => item.aggregationKeys.find((key) => key.scope === listEntityType).id); - - const filteredIds = []; - ids.forEach((id) => { - if (!filteredIds.includes(id)) { - filteredIds.push(id); - } - }); - - const result = filteredIds.map((id) => ({ - label: getEntityName(id), - link: URLService.getURL(match, location).base(listEntityType, id).url(), - })); - - return result; - } - - function getHeadline(items) { - if (!items) { - return 'Loading...'; - } - const count = typeof items.length !== 'undefined' ? items.length : 0; - return `${count} related ${entityNounOrdinaryCase(count, listEntityType)}`; - } - - const viewAllLink = - pageEntity && pageEntity.id && listEntityType !== entityTypes.SECRET ? ( - - - - ) : null; - - const variables = { - groupBy: [pageEntityType, listEntityType], - unit: entityTypes.CONTROL, - where: queryService.objectToWhereClause({ [`${pageEntityType} ID`]: pageEntity.id }), - }; - - return ( - - ); -}; - -ControlRelatedResourceList.propTypes = { - listEntityType: PropTypes.string.isRequired, - pageEntityType: PropTypes.string.isRequired, - pageEntity: PropTypes.shape({ - id: PropTypes.string, - name: PropTypes.string, - }), - limit: PropTypes.number, - standard: PropTypes.string.isRequired, - className: PropTypes.string, -}; - -ControlRelatedResourceList.defaultProps = { - pageEntity: null, - limit: 10, - className: '', -}; - -export default ControlRelatedResourceList; diff --git a/ui/apps/platform/src/Containers/Compliance/widgets/EntityCompliance.jsx b/ui/apps/platform/src/Containers/Compliance/widgets/EntityCompliance.jsx deleted file mode 100644 index 17713fdf7f18e..0000000000000 --- a/ui/apps/platform/src/Containers/Compliance/widgets/EntityCompliance.jsx +++ /dev/null @@ -1,131 +0,0 @@ -import { useContext } from 'react'; -import PropTypes from 'prop-types'; -import { useLocation, useNavigate } from 'react-router-dom-v5-compat'; - -import Widget from 'Components/Widget'; -import ArcSingle from 'Components/visuals/ArcSingle'; -import Query from 'Components/CacheFirstQuery'; -import Loader from 'Components/Loader'; -import entityTypes, { standardBaseTypes } from 'constants/entityTypes'; -import useWorkflowMatch from 'hooks/useWorkflowMatch'; -import URLService from 'utils/URLService'; -import { AGGREGATED_RESULTS } from 'queries/controls'; -import queryService from 'utils/queryService'; -import NoResultsMessage from 'Components/NoResultsMessage'; -import { standardLabels } from 'messages/standards'; -import searchContext from 'Containers/searchContext'; - -import { entityNounSentenceCaseSingular } from '../entitiesForCompliance'; -import VerticalBarChart from './VerticalBarChart'; - -const EntityCompliance = ({ entityId, entityType, entityName, clusterName }) => { - const entityTypeLabel = entityNounSentenceCaseSingular[entityType]; - const searchParam = useContext(searchContext); - const match = useWorkflowMatch(); - const location = useLocation(); - const navigate = useNavigate(); - - function getBarData(results) { - return results - .filter((item) => item.numPassing + item.numFailing) - .map((item) => ({ - x: standardBaseTypes[item.aggregationKeys[0].id] || item.aggregationKeys[0].id, - y: (item.numPassing / (item.numPassing + item.numFailing)) * 100, - standard: item.aggregationKeys[0].id, - })); - } - - function getTotals(results) { - return results.reduce( - (acc, curr) => { - acc.numPassing += curr.numPassing; - acc.total += curr.numPassing + curr.numFailing; - return acc; - }, - { numPassing: 0, total: 0 } - ); - } - function valueClick(datum) { - const URL = URLService.getURL(match, location) - .base(entityTypes.CONTROL) - .query({ - [searchParam]: { - [entityType]: entityName, - [entityTypes.CLUSTER]: clusterName, - standard: standardLabels[datum.standard] || datum.x, - }, - }) - .url(); - - navigate(URL); - } - - const whereClause = { [`${entityType} ID`]: entityId, [entityTypes.CLUSTER]: clusterName }; - const variables = { - unit: entityTypes.CHECK, - groupBy: [entityTypes.STANDARD, entityType], - where: queryService.objectToWhereClause(whereClause), - }; - return ( - - {({ loading, data }) => { - let contents = ; - if (!loading && data && data.controls) { - // Frontend filtering of control results. - const { complianceStandards } = data; - const results = data.controls.results.filter((result) => { - const standardId = result.aggregationKeys[0].id; - return complianceStandards.some(({ id }) => id === standardId); - }); - - if (!results.length) { - contents = ( - - ); - } else { - const barData = getBarData(results); - const totals = getTotals(results); - const pct = - totals.total > 0 - ? Math.round((totals.numPassing / totals.total) * 100) - : 0; - contents = ( - <> -
-
- -
-
- -
-
- - ); - } - } - return {contents}; - }} -
- ); -}; -EntityCompliance.propTypes = { - entityId: PropTypes.string.isRequired, - entityType: PropTypes.string.isRequired, - entityName: PropTypes.string, - clusterName: PropTypes.string, -}; - -EntityCompliance.defaultProps = { - entityName: null, - clusterName: null, -}; - -export default EntityCompliance; diff --git a/ui/apps/platform/src/Containers/Compliance/widgets/HorizontalBarChart.jsx b/ui/apps/platform/src/Containers/Compliance/widgets/HorizontalBarChart.jsx deleted file mode 100644 index 2f6d9b1b86f5f..0000000000000 --- a/ui/apps/platform/src/Containers/Compliance/widgets/HorizontalBarChart.jsx +++ /dev/null @@ -1,207 +0,0 @@ -import { - FlexibleWidthXYPlot, - XAxis, - YAxis, - VerticalGridLines, - HorizontalBarSeries, - LabelSeries, -} from 'react-vis'; -import { Link, useNavigate } from 'react-router-dom-v5-compat'; -import PropTypes from 'prop-types'; -import merge from 'deepmerge'; - -import { getColor } from './colorsForCompliance'; - -const minimalMargin = { top: -15, bottom: 0, left: 0, right: 0 }; - -const sortByYValue = (a, b) => { - if (a.y < b.y) { - return -1; - } - if (a.y > b.y) { - return 1; - } - return 0; -}; - -const HorizontalBarChart = ({ - data, - containerProps = {}, - plotProps = {}, - seriesProps = {}, - valueFormat = (x) => x, - tickValues = [0, 25, 50, 75, 100], - minimal = false, -}) => { - const navigate = useNavigate(); - - const showLabel = (value) => value >= 10; - - const getLabelData = () => - data.sort(sortByYValue).map((item) => { - let label = ''; - // This prevents overlap between the value label and the axis label - if (showLabel(item.x)) { - label = (valueFormat && valueFormat(item.x).toString()) || ''; - } - const val = { - x: item.x - 5, - y: item.y, - yOffset: minimal ? -6 : -3, - label, - }; - // x offset for label - val.x -= val.label.length; - return val; - }); - - const onValueClickHandler = (datum) => { - if (datum.link) { - navigate(datum.link); - } - }; - - const getContainerProps = () => { - const defaultContainerProps = { - className: 'relative chart-container w-full horizontal-bar-responsive', - }; - return merge(defaultContainerProps, containerProps); - }; - - const getPlotProps = () => { - const sortedData = data.sort(sortByYValue); - // This determines how far to push the bar graph to the right based on the longest axis label character's length - const maxLength = sortedData.reduce((acc, curr) => Math.max(curr.y.length, acc), 0); - - // Magic number that makes the horizontal bars a reasonable size if there are fewer than 6 - // but shrinks them if there are 6 or more. - const yRangeMultiplier = Math.min(41, 210 / sortedData.length); - const yRange = [...sortedData.map((item, i) => (i + 1) * yRangeMultiplier), 0]; - const defaultPlotProps = { - height: minimal ? 25 : 350, - xDomain: [0, 102], - yType: 'category', - yRange, - margin: minimal ? minimalMargin : { top: 33.3, left: Math.ceil(maxLength * 7.5) }, - stackBy: 'x', - }; - return merge(defaultPlotProps, plotProps); - }; - - const getSeriesProps = () => { - const defaultSeriesProps = { - style: { - height: minimal ? 15 : 20, - rx: '2px', - cursor: `${minimal ? '' : 'pointer'}`, - }, - onValueMouseOver: null, - onValueMouseOut: null, - onValueClick: onValueClickHandler, - }; - return merge(defaultSeriesProps, seriesProps); - }; - - const sortedData = data.sort(sortByYValue); - - // Generate y axis links - const axisLinks = sortedData.reduce((acc, curr) => { - if (curr.link) { - acc[curr.y] = curr.link; - } - return acc; - }, {}); - - const containerPropsMerged = getContainerProps(); - const plotPropsMerged = getPlotProps(); - const seriesPropsMerged = getSeriesProps(); - - function tickFormat(value) { - let inner = value; - if (axisLinks[value]) { - inner = ( - - {value} - - ); - } - - return {inner}; - } - - return ( -
- - {/* Empty area bar background */} - - {!minimal && } - - {!minimal && ( - - )} - - {/* Empty Background */} - ({ - x: 0, - x0: Math.ceil(sortedData[0].x / 5) * 5, - y: item.y, - link: item.link, - }))} - color="var(--pf-t--color--gray--20)" - style={{ - height: seriesPropsMerged.style.height, - rx: '2', - ry: '2', - cursor: `${minimal ? '' : 'pointer'}`, - }} - onValueClick={onValueClickHandler} - /> - - {/* Values */} - ({ ...item, color: getColor(item.x) }))} - {...seriesPropsMerged} - colorType="literal" - /> - - - {!minimal && ( - - )} - -
- ); -}; - -HorizontalBarChart.propTypes = { - data: PropTypes.arrayOf(PropTypes.shape({})).isRequired, - containerProps: PropTypes.shape({}), - plotProps: PropTypes.shape({}), - seriesProps: PropTypes.shape({}), - valueFormat: PropTypes.func, - tickValues: PropTypes.arrayOf(PropTypes.number), - minimal: PropTypes.bool, -}; - -export default HorizontalBarChart; diff --git a/ui/apps/platform/src/Containers/Compliance/widgets/Labels.jsx b/ui/apps/platform/src/Containers/Compliance/widgets/Labels.jsx deleted file mode 100644 index 304cbcf7c4b42..0000000000000 --- a/ui/apps/platform/src/Containers/Compliance/widgets/Labels.jsx +++ /dev/null @@ -1,35 +0,0 @@ -import PropTypes from 'prop-types'; -import { Tooltip } from '@patternfly/react-core'; - -const truncate = (key) => { - const index = key.indexOf('/'); - return index > 0 ? key.substr(index + 1) : key; -}; - -const Labels = ({ labels }) => ( -
    4 ? `columns-2` : ``} p-3 w-full leading-normal`}> - {labels.map((label) => ( -
  • - - - {`${truncate(label.key)}:`} - {label.value || '""'} - - -
  • - ))} -
-); - -Labels.propTypes = { - labels: PropTypes.arrayOf(PropTypes.object).isRequired, -}; - -export default Labels; diff --git a/ui/apps/platform/src/Containers/Compliance/widgets/LinkListWidget.jsx b/ui/apps/platform/src/Containers/Compliance/widgets/LinkListWidget.jsx deleted file mode 100644 index ce4da2029b7a8..0000000000000 --- a/ui/apps/platform/src/Containers/Compliance/widgets/LinkListWidget.jsx +++ /dev/null @@ -1,132 +0,0 @@ -import PropTypes from 'prop-types'; -import { Link } from 'react-router-dom-v5-compat'; -import { Alert } from '@patternfly/react-core'; - -import Widget from 'Components/Widget'; -import Loader from 'Components/Loader'; -import Query from 'Components/ThrowingQuery'; -import NoResultsMessage from 'Components/NoResultsMessage'; - -function getLI(item) { - if (!item) { - return null; - } - - const content = item.link ? ( - - {item.label} - - ) : ( - item.label - ); - return ( -
  • - {content} -
  • - ); -} - -const LinkListWidget = ({ - query, - variables, - processData, - getHeadline, - className, - headerComponents, - limit, - id, - showEmpty, -}) => ( - - {({ loading, data, error }) => { - let contents; - let headline = getHeadline(); - - if (loading) { - contents = ; - } else if (error) { - contents = ( - - {error} - - ); - } else if (data) { - const items = processData(data); - headline = getHeadline(items); - - if (items.length === 0) { - if (!showEmpty) { - return null; - } - contents = ; - } else { - contents = ( -
      5 ? `columns-2` : `columns-1` - } p-3 py-1 w-full leading-normal overflow-hidden`} - > - {items.slice(0, limit).map((item) => getLI(item))} -
    - ); - } - } - - return ( - - {contents} - - ); - }} -
    -); - -LinkListWidget.propTypes = { - query: PropTypes.shape({}).isRequired, - variables: PropTypes.shape({}), - processData: PropTypes.func, - getHeadline: PropTypes.func, - className: PropTypes.string, - headerComponents: PropTypes.node, - limit: PropTypes.number, - showEmpty: PropTypes.bool, - id: PropTypes.string, -}; - -LinkListWidget.defaultProps = { - variables: null, - processData(data) { - return data; - }, - getHeadline() { - return null; - }, - className: '', - headerComponents: null, - limit: 10, - showEmpty: false, - id: 'link-list-widget', -}; - -export default LinkListWidget; diff --git a/ui/apps/platform/src/Containers/Compliance/widgets/ResourceCount.jsx b/ui/apps/platform/src/Containers/Compliance/widgets/ResourceCount.jsx deleted file mode 100644 index 838b8f6795c71..0000000000000 --- a/ui/apps/platform/src/Containers/Compliance/widgets/ResourceCount.jsx +++ /dev/null @@ -1,100 +0,0 @@ -import { useContext } from 'react'; -import { useLocation } from 'react-router-dom-v5-compat'; -import PropTypes from 'prop-types'; -import capitalize from 'lodash/capitalize'; - -import entityTypes from 'constants/entityTypes'; -import URLService from 'utils/URLService'; -import Widget from 'Components/Widget'; -import Query from 'Components/CacheFirstQuery'; -import Loader from 'Components/Loader'; -import CountWidget from 'Components/CountWidget'; -import useWorkflowMatch from 'hooks/useWorkflowMatch'; -import { SEARCH_WITH_CONTROLS as QUERY } from 'queries/search'; -import queryService from 'utils/queryService'; -import useCases from 'constants/useCaseTypes'; -import searchContext from 'Containers/searchContext'; - -import { getResourceCountFromAggregatedResults } from '../complianceUtils'; -import { entityNounSentenceCaseSingular } from '../entitiesForCompliance'; - -const ResourceCount = ({ entityType, relatedToResourceType, relatedToResource }) => { - const searchParam = useContext(searchContext); - const match = useWorkflowMatch(); - const location = useLocation(); - - function getUrl() { - if (entityType === entityTypes.SECRET) { - return URLService.getURL(match, location) - .set('context', useCases.SECRET) - .query({ - [searchParam]: { - [`${capitalize(relatedToResourceType)}`]: relatedToResource?.name, - }, - }) - .url(); - } - - return URLService.getURL(match, location) - .base(relatedToResourceType, relatedToResource.id) - .push(entityType) - .url(); - } - - function getVariables() { - let query; - switch (relatedToResourceType) { - case entityTypes.NAMESPACE: - query = { - namespace: relatedToResource.name, - cluster: relatedToResource.clusterName, - }; - break; - default: - query = { [`${capitalize(relatedToResourceType)} ID`]: relatedToResource.id }; - } - - return { - query: queryService.objectToWhereClause(query), - categories: [], - }; - } - - const variables = getVariables(); - const headerText = `${entityNounSentenceCaseSingular[entityType]} Count`; - const url = getUrl(); - - return ( - - {({ loading, data }) => { - const contents = ; - if (!loading && data) { - const resourceCount = getResourceCountFromAggregatedResults(entityType, data); - - return ; - } - return ( - - {contents} - - ); - }} - - ); -}; - -ResourceCount.propTypes = { - entityType: PropTypes.string.isRequired, - relatedToResourceType: PropTypes.string.isRequired, - relatedToResource: PropTypes.shape({ - id: PropTypes.string, - name: PropTypes.string, - clusterName: PropTypes.string, - }), -}; - -ResourceCount.defaultProps = { - relatedToResource: null, -}; - -export default ResourceCount; diff --git a/ui/apps/platform/src/Containers/Compliance/widgets/StandardsAcrossEntity.jsx b/ui/apps/platform/src/Containers/Compliance/widgets/StandardsAcrossEntity.jsx deleted file mode 100644 index 0682497ca2e46..0000000000000 --- a/ui/apps/platform/src/Containers/Compliance/widgets/StandardsAcrossEntity.jsx +++ /dev/null @@ -1,148 +0,0 @@ -import { useContext } from 'react'; -import PropTypes from 'prop-types'; -import { useLocation } from 'react-router-dom-v5-compat'; -import { useQuery } from '@apollo/client'; -import merge from 'lodash/merge'; - -import entityTypes, { standardBaseTypes } from 'constants/entityTypes'; -import { standardLabels } from 'messages/standards'; -import URLService from 'utils/URLService'; -import Widget from 'Components/Widget'; -import Loader from 'Components/Loader'; -import NoResultsMessage from 'Components/NoResultsMessage'; -import useWorkflowMatch from 'hooks/useWorkflowMatch'; -import { AGGREGATED_RESULTS_ACROSS_ENTITY } from 'queries/controls'; -import searchContext from 'Containers/searchContext'; - -import { entityNounOrdinaryCasePlural } from '../entitiesForCompliance'; -import HorizontalBarChart from './HorizontalBarChart'; - -function formatAsPercent(x) { - return `${x}%`; -} - -function setStandardsMapping(data, key, type) { - const mapping = {}; - data[key].results.forEach((result) => { - const standardId = result.aggregationKeys[0].id; - if (!data.complianceStandards.some(({ id }) => id === standardId)) { - return; // because it implies standardId has hideScanResults: true - } - const { numPassing, numFailing } = result; - if (numPassing === 0 && numFailing === 0) { - return; - } - if (!mapping[standardId]) { - mapping[standardId] = {}; - mapping[standardId][type] = { - passing: numPassing, - total: numPassing + numFailing, - }; - } else { - const { passing, total } = mapping[standardId][type]; - mapping[standardId][type] = { - passing: passing + numPassing, - total: total + (numPassing + numFailing), - }; - } - }); - - return mapping; -} - -const StandardsAcrossEntity = ({ entityType, bodyClassName, className }) => { - const searchParam = useContext(searchContext); - const match = useWorkflowMatch(); - const location = useLocation(); - const headerText = `Passing standards across ${entityNounOrdinaryCasePlural[entityType]}`; - - function processData(data, type) { - if (!data || !data.controls || !data.controls.results.length) { - return []; - } - const standardsMapping = merge( - {}, - setStandardsMapping(data, 'results', 'checks'), - setStandardsMapping(data, 'controls', 'controls') - ); - - const barData = Object.keys(standardsMapping).map((standardId) => { - const { controls } = standardsMapping[standardId]; - const { passing: passingControls, total: totalControls } = controls; - const percentagePassing = Math.round((passingControls / totalControls) * 100) || 0; - const link = URLService.getURL(match, location) - .base(entityTypes.CONTROL) - .query({ - [searchParam]: { - groupBy: type, - standard: standardLabels[standardId] || standardId, - }, - }) - .url(); - const dataPoint = { - y: standardBaseTypes[standardId] || standardId, - x: percentagePassing, - link, - }; - return dataPoint; - }); - return barData; - } - - const variables = { - groupBy: [entityTypes.STANDARD, entityType], - unit: entityTypes.CHECK, - }; - const { loading, error, data } = useQuery(AGGREGATED_RESULTS_ACROSS_ENTITY(entityType), { - variables, - }); - - if (error) { - return ( - -
    - A database error has occurred. Please check that you have the correct - permissions to view this information. -
    -
    - ); - } - - let contents; - if (!loading && data && data.complianceStandards) { - const results = processData(data, entityType); - if (!results.length) { - contents = ; - } else { - contents = ; - } - } else { - contents = ; - } - return ( - - {contents} - - ); -}; - -StandardsAcrossEntity.propTypes = { - entityType: PropTypes.string.isRequired, - bodyClassName: PropTypes.string, - className: PropTypes.string, -}; - -StandardsAcrossEntity.defaultProps = { - bodyClassName: 'px-4 pt-1', - className: '', -}; - -export default StandardsAcrossEntity; diff --git a/ui/apps/platform/src/Containers/Compliance/widgets/StandardsByEntity.jsx b/ui/apps/platform/src/Containers/Compliance/widgets/StandardsByEntity.jsx deleted file mode 100644 index b500f36f0d675..0000000000000 --- a/ui/apps/platform/src/Containers/Compliance/widgets/StandardsByEntity.jsx +++ /dev/null @@ -1,179 +0,0 @@ -import { useContext } from 'react'; -import PropTypes from 'prop-types'; -import { useLocation } from 'react-router-dom-v5-compat'; -import { useQuery } from '@apollo/client'; -import capitalize from 'lodash/capitalize'; -import sortBy from 'lodash/sortBy'; -import chunk from 'lodash/chunk'; - -import entityTypes from 'constants/entityTypes'; -import URLService from 'utils/URLService'; -import Widget from 'Components/Widget'; -import Loader from 'Components/Loader'; -import NoResultsMessage from 'Components/NoResultsMessage'; -import useWorkflowMatch from 'hooks/useWorkflowMatch'; -import { standardLabels } from 'messages/standards'; -import { AGGREGATED_RESULTS_STANDARDS_BY_ENTITY } from 'queries/controls'; -import searchContext from 'Containers/searchContext'; - -import { entityNounOrdinaryCaseSingular } from '../entitiesForCompliance'; -import VerticalClusterBar from './VerticalClusterBar'; - -function processData(match, location, data, entityType, searchParam) { - if (!data || !data.controls.results.length || !data.entityList) { - return []; - } - const standardsGrouping = {}; - const { controls, entityList, complianceStandards } = data; - controls.results.forEach((result) => { - const entity = entityList.find( - (entityObject) => entityObject.id === result.aggregationKeys[1].id - ); - if (!entity) { - return; - } - const standard = complianceStandards.find((c) => c.id === result.aggregationKeys[0].id); - const { numPassing, numFailing } = result; - if (!standard || (numPassing === 0 && numFailing === 0)) { - return; - } - const percentagePassing = Math.round((numPassing / (numPassing + numFailing)) * 100); - - const link = URLService.getURL(match, location) - .base(entityTypes.CONTROL) - .query({ - [searchParam]: { - [`${capitalize(entityType)}`]: entity?.name, - standard: standardLabels[standard.id] || standard.id, - }, - }) - .url(); - const dataPoint = { - x: entity?.name, - y: percentagePassing, - link, - }; - const standardGroup = standardsGrouping[standard.id]; - if (standardGroup) { - standardGroup.push(dataPoint); - } else { - standardsGrouping[standard.id] = [dataPoint]; - } - }); - const sortedStandardsGrouping = {}; - const GRAPHS_PER_PAGE = 3; - const pagedStandardsGrouping = []; - - Object.keys(standardsGrouping).forEach((standard) => { - sortedStandardsGrouping[standard] = sortBy(standardsGrouping[standard], ['x']); - const pageArray = chunk(sortedStandardsGrouping[standard], GRAPHS_PER_PAGE); - pageArray.forEach((page, pageIdx) => { - if (!pagedStandardsGrouping[pageIdx]) { - pagedStandardsGrouping[pageIdx] = {}; - } - pagedStandardsGrouping[pageIdx][standard] = pageArray[pageIdx]; - }); - }); - return pagedStandardsGrouping; -} - -function getLabelLinks(match, location, data, entityType) { - if (!data) { - return null; - } - const { entityList } = data; - const labelLinks = {}; - entityList.forEach((entity) => { - labelLinks[entity?.name] = URLService.getURL(match, location) - .base(entityType, entity?.id) - .url(); - }); - return labelLinks; -} - -const StandardsByEntity = ({ entityType, bodyClassName, className }) => { - const searchParam = useContext(searchContext); - const match = useWorkflowMatch(); - const location = useLocation(); - const headerText = `Passing standards by ${entityNounOrdinaryCaseSingular[entityType]}`; - - const variables = { - groupBy: [entityTypes.STANDARD, entityType], - unit: entityTypes.CHECK, - }; - const { loading, error, data } = useQuery(AGGREGATED_RESULTS_STANDARDS_BY_ENTITY(entityType), { - variables, - }); - - if (error) { - return ( - -
    - A database error has occurred. Please check that you have the correct - permissions to view this information. -
    -
    - ); - } - - let contents = ; - let pages; - - if (!loading) { - if (!data || !data.results || data.results.length === 0) { - contents = ; - } else { - const formattedData = { - results: data && data.results, - controls: data && data.controls, - complianceStandards: data.complianceStandards, - entityList: data && data.clusters, - }; - const results = processData(match, location, formattedData, entityType, searchParam); - const labelLinks = getLabelLinks(match, location, formattedData, entityType); - pages = results.length; - - if (pages) { - const VerticalBarChartPaged = ({ currentPage }) => ( - - ); - VerticalBarChartPaged.propTypes = { currentPage: PropTypes.number }; - VerticalBarChartPaged.defaultProps = { currentPage: 0 }; - contents = ; - } else { - contents = ; - } - } - } - - return ( - - {contents} - - ); -}; -StandardsByEntity.propTypes = { - entityType: PropTypes.string.isRequired, - bodyClassName: PropTypes.string, - className: PropTypes.string, -}; - -StandardsByEntity.defaultProps = { - bodyClassName: 'p-4', - className: '', -}; - -export default StandardsByEntity; diff --git a/ui/apps/platform/src/Containers/Compliance/widgets/VerticalBarChart.jsx b/ui/apps/platform/src/Containers/Compliance/widgets/VerticalBarChart.jsx deleted file mode 100644 index 6ecbc9cbe44e1..0000000000000 --- a/ui/apps/platform/src/Containers/Compliance/widgets/VerticalBarChart.jsx +++ /dev/null @@ -1,112 +0,0 @@ -import { Component } from 'react'; -import { - FlexibleWidthXYPlot, - XAxis, - YAxis, - VerticalGridLines, - HorizontalGridLines, - VerticalBarSeries, -} from 'react-vis'; -import PropTypes from 'prop-types'; -import merge from 'deepmerge'; - -import { verticalBarColors } from './colorsForCompliance'; - -const sortByXValue = (a, b) => { - if (a.x < b.x) { - return -1; - } - if (a.x > b.x) { - return 1; - } - return 0; -}; - -// Component state was needed only for deleted HoverHint, but we will not rewrite. -class VerticalBarChart extends Component { - static propTypes = { - data: PropTypes.arrayOf(PropTypes.object).isRequired, - containerProps: PropTypes.shape({}), - plotProps: PropTypes.shape({}), - seriesProps: PropTypes.shape({}), - tickValues: PropTypes.arrayOf(PropTypes.number), - tickFormat: PropTypes.func, - onValueClick: PropTypes.func, - legend: PropTypes.bool, - }; - - static defaultProps = { - containerProps: {}, - plotProps: {}, - seriesProps: {}, - tickValues: [25, 50, 75, 100], - tickFormat: (x) => `${x}%`, - onValueClick: null, - legend: true, - }; - - render() { - const { tickValues, tickFormat, onValueClick } = this.props; - - const data = this.props.data.sort(sortByXValue); - - // Default props - const defaultPlotProps = { - xType: 'ordinal', - height: 250, - colorType: 'category', - yDomain: [0, 110], - }; - - const defaultContainerProps = { - className: 'relative chart-container w-full horizontal-bar-responsive', - }; - - const defaultSeriesProps = { - barWidth: 0.25, - style: { - opacity: '.8', - ry: '2px', - cursor: 'pointer', - }, - - colorDomain: data.map((datum) => datum.y), - colorRange: verticalBarColors, - onValueClick: (datum) => { - if (onValueClick) { - onValueClick(datum); - } - }, - }; - - // Merge props - const containerProps = merge(defaultContainerProps, this.props.containerProps); - const plotProps = merge(defaultPlotProps, this.props.plotProps); - const seriesProps = merge(defaultSeriesProps, this.props.seriesProps); - const styleProps = this.props.legend ? { top: '-16px' } : {}; - - // format data with colors: - const letDataWithColors = data.map((datum, i) => ({ - ...datum, - color: verticalBarColors[i % verticalBarColors.length], - })); - - function formatTicks(value) { - return {value}; - } - - return ( -
    - - - - - - - -
    - ); - } -} - -export default VerticalBarChart; diff --git a/ui/apps/platform/src/Containers/Compliance/widgets/VerticalClusterBar.jsx b/ui/apps/platform/src/Containers/Compliance/widgets/VerticalClusterBar.jsx deleted file mode 100644 index 094a7b81a0bf8..0000000000000 --- a/ui/apps/platform/src/Containers/Compliance/widgets/VerticalClusterBar.jsx +++ /dev/null @@ -1,152 +0,0 @@ -import { - FlexibleXYPlot, - XAxis, - YAxis, - VerticalGridLines, - HorizontalGridLines, - VerticalBarSeries, -} from 'react-vis'; -import PropTypes from 'prop-types'; -import { Link, useNavigate } from 'react-router-dom-v5-compat'; -import DiscreteColorLegend from 'react-vis/dist/legends/discrete-color-legend'; -import merge from 'deepmerge'; - -import { standardBaseTypes } from 'constants/entityTypes'; - -import { verticalBarColors } from './colorsForCompliance'; - -function VerticalClusterBar({ - id = '', - data, - containerProps = {}, - plotProps = {}, - seriesProps = {}, - tickValues = [25, 50, 75, 100], - tickFormat = (x) => `${x}%`, - labelLinks = {}, -}) { - const navigate = useNavigate(); - - const getLegendData = () => { - return Object.keys(data) - .sort() - .map((key, i) => ({ - title: standardBaseTypes[key] || key, - color: verticalBarColors[i % verticalBarColors.length], - })); - }; - - // Default props - const defaultPlotProps = { - xType: 'ordinal', - yDomain: [0, 100], - height: 270, - }; - - const defaultContainerProps = { - className: 'relative chart-container w-full horizontal-bar-responsive', - }; - - const defaultSeriesProps = { - // animation: true, //causes onValueMouseOut to fail https://github.com/uber/react-vis/issues/381 - barWidth: 0.5, - style: { - opacity: '.85', - width: '10px', - ry: '2px', - cursor: 'pointer', - }, - onValueClick: (datum) => { - if (datum.link) { - navigate(datum.link); - } - }, - }; - - // Merge props - const containerPropsMerged = merge(defaultContainerProps, containerProps); - const plotPropsMerged = merge(defaultPlotProps, plotProps); - const seriesPropsMerged = merge(defaultSeriesProps, seriesProps); - - function formatTicks(value) { - let inner = value; - if (labelLinks[value]) { - inner = ( - - {value} - - ); - } - - return {inner}; - } - - // Calculate unique cluster names - let clusterNames = new Set(); - Object.keys(data).forEach((dataSetKey) => { - const dataSet = data[dataSetKey]; - dataSet.forEach((datum) => { - clusterNames.add(datum.x); - }); - }); - clusterNames = Array.from(clusterNames); - - // Create Barseries for each data set - const series = []; - Object.keys(data) - .sort() - .forEach((key, i) => { - series.push( - - ); - }); - - return ( -
    -
    - - - - - {series} - - - -
    - -
    -
    -
    - ); -} - -VerticalClusterBar.propTypes = { - id: PropTypes.string, - data: PropTypes.shape({}).isRequired, - containerProps: PropTypes.shape({}), - plotProps: PropTypes.shape({}), - seriesProps: PropTypes.shape({}), - tickValues: PropTypes.arrayOf(PropTypes.number), - tickFormat: PropTypes.func, - labelLinks: PropTypes.shape({}), -}; - -export default VerticalClusterBar; diff --git a/ui/apps/platform/src/Containers/Compliance/widgets/colorsForCompliance.ts b/ui/apps/platform/src/Containers/Compliance/widgets/colorsForCompliance.ts deleted file mode 100644 index cdd9e12529b41..0000000000000 --- a/ui/apps/platform/src/Containers/Compliance/widgets/colorsForCompliance.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { - COMPLIANCE_PASS_COLOR, - CRITICAL_SEVERITY_COLOR, - IMPORTANT_HIGH_SEVERITY_COLOR, - MODERATE_MEDIUM_SEVERITY_COLOR, - noViolationsColor, -} from 'constants/severityColors'; - -export function getColor(value: number) { - if (value === 100) { - return COMPLIANCE_PASS_COLOR; - } - if (value >= 70) { - return MODERATE_MEDIUM_SEVERITY_COLOR; - } - if (value >= 50) { - return IMPORTANT_HIGH_SEVERITY_COLOR; - } - if (Number.isNaN(value)) { - return noViolationsColor; // skipped - } - return CRITICAL_SEVERITY_COLOR; -} - -// For example, Passing Standards by Clusters chart. -export const verticalBarColors = [ - 'var(--base-700)', - 'var(--primary-700)', - 'var(--secondary-700)', - 'var(--base-400)', - 'var(--primary-400)', - 'var(--secondary-400)', -]; diff --git a/ui/apps/platform/src/Containers/ComplianceEnhanced/Coverage/ClusterDetailsPage.tsx b/ui/apps/platform/src/Containers/ComplianceEnhanced/Coverage/ClusterDetailsPage.tsx index be117d5e6a811..a431bbf61140d 100644 --- a/ui/apps/platform/src/Containers/ComplianceEnhanced/Coverage/ClusterDetailsPage.tsx +++ b/ui/apps/platform/src/Containers/ComplianceEnhanced/Coverage/ClusterDetailsPage.tsx @@ -132,7 +132,7 @@ function ClusterDetailsPage() { return ( <> - + - )} - - - + + Scan schedules @@ -20,16 +20,10 @@ function CreateScanConfigPage(): ReactElement { Create scan schedule - - - - Create scan schedule - - - - - + + Create scan schedule + ); } diff --git a/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/EditScanConfigDetail.tsx b/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/EditScanConfigDetail.tsx index 6ed9bef795cf7..44b54dafe5190 100644 --- a/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/EditScanConfigDetail.tsx +++ b/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/EditScanConfigDetail.tsx @@ -4,9 +4,6 @@ import { Breadcrumb, BreadcrumbItem, Bullseye, - Divider, - Flex, - FlexItem, PageSection, Spinner, Title, @@ -17,7 +14,7 @@ import PageTitle from 'Components/PageTitle'; import BreadcrumbItemLink from 'Components/BreadcrumbItemLink'; import type { ComplianceScanConfigurationStatus } from 'services/ComplianceScanConfigurationService'; import { getAxiosErrorMessage } from 'utils/responseErrorUtils'; -import ScanConfigWizardForm from './Wizard/ScanConfigWizardForm'; +import ScanConfigWizardPageSection from './Wizard/ScanConfigWizardPageSection'; import { defaultScanConfigFormValues } from './Wizard/useFormikScanConfig'; import { convertScanConfigToFormik } from './compliance.scanConfigs.utils'; @@ -38,53 +35,50 @@ function EditScanConfigDetail({ return ( <> - + Scan schedules - {!isLoading && !error && scanConfig && ( + {scanConfig && ( Edit {scanConfig.scanName} )} - - {!isLoading && !error && scanConfig && ( - - - Edit {scanConfig.scanName} - - - )} - - - - {isLoading ? ( + {isLoading ? ( + - ) : ( - error && ( - - {getAxiosErrorMessage(error)} - - ) - )} - {!isLoading && scanConfig && ( - - - - )} - + + ) : ( + <> + {scanConfig && ( + + Edit {scanConfig.scanName} + + )} + {error && ( + + + {getAxiosErrorMessage(error)} + + + )} + {scanConfig && ( + + )} + + )} ); } diff --git a/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/ScanConfigDetailPage.tsx b/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/ScanConfigDetailPage.tsx index 9b2898d1beb44..e6f587d39f9b8 100644 --- a/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/ScanConfigDetailPage.tsx +++ b/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/ScanConfigDetailPage.tsx @@ -8,7 +8,7 @@ import useRestQuery from 'hooks/useRestQuery'; import { getComplianceScanConfiguration } from 'services/ComplianceScanConfigurationService'; import EditScanConfigDetail from './EditScanConfigDetail'; import ViewScanConfigDetail from './ViewScanConfigDetail'; -import type { PageActions } from './compliance.scanConfigs.utils'; +import type { SchedulePageAction } from './compliance.scanConfigs.utils'; type ScanConfigDetailPageProps = { hasWriteAccessForCompliance: boolean; @@ -18,7 +18,7 @@ function ScanConfigDetailPage({ hasWriteAccessForCompliance, }: ScanConfigDetailPageProps): ReactElement { const { scanConfigId } = useParams() as { scanConfigId: string }; - const { pageAction } = usePageAction(); + const { pageAction } = usePageAction(); const scanConfigFetcher = useCallback(() => { const { request, cancel } = getComplianceScanConfiguration(scanConfigId); diff --git a/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/ScanConfigsPage.tsx b/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/ScanConfigsPage.tsx index 6e98bdcfacc63..3b25225ef7acc 100644 --- a/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/ScanConfigsPage.tsx +++ b/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/ScanConfigsPage.tsx @@ -4,7 +4,7 @@ import { Banner } from '@patternfly/react-core'; import usePageAction from 'hooks/usePageAction'; import usePermissions from 'hooks/usePermissions'; import { complianceEnhancedSchedulesPath } from 'routePaths'; -import type { PageActions } from './compliance.scanConfigs.utils'; +import type { SchedulePageAction } from './compliance.scanConfigs.utils'; import CreateScanConfigPage from './CreateScanConfigPage'; import ComplianceNotFoundPage from '../ComplianceNotFoundPage'; import ScanConfigDetailPage from './ScanConfigDetailPage'; @@ -17,7 +17,7 @@ function ScanConfigsPage() { * /main/compliance-enhanced/scan-configs?action=create * /main/compliance-enhanced/scan-configs/configId */ - const { pageAction } = usePageAction(); + const { pageAction } = usePageAction(); const { hasReadWriteAccess } = usePermissions(); const hasWriteAccessForCompliance = hasReadWriteAccess('Compliance'); diff --git a/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/ViewScanConfigDetail.tsx b/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/ViewScanConfigDetail.tsx index 2eedb04bd92ba..097da5d7bd50b 100644 --- a/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/ViewScanConfigDetail.tsx +++ b/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/ViewScanConfigDetail.tsx @@ -5,8 +5,6 @@ import { AlertActionCloseButton, Breadcrumb, BreadcrumbItem, - Card, - CardBody, Flex, FlexItem, PageSection, @@ -211,15 +209,7 @@ function ViewScanConfigDetail({ {activeScanConfigTab === 'CONFIGURATION_DETAILS' && ( - - - - - + )} {activeScanConfigTab === 'ALL_REPORT_JOBS' && scanConfig?.id && ( diff --git a/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/Wizard/ProfileSelection.tsx b/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/Wizard/ProfileSelection.tsx deleted file mode 100644 index 2de996d4ed04a..0000000000000 --- a/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/Wizard/ProfileSelection.tsx +++ /dev/null @@ -1,234 +0,0 @@ -import { useCallback, useState } from 'react'; -import type { FormEvent, ReactElement, RefObject } from 'react'; -import { useFormikContext } from 'formik'; -import type { FormikContextType } from 'formik'; -import { - Alert, - Content, - Divider, - Flex, - FlexItem, - Form, - PageSection, - Title, - Toolbar, - ToolbarContent, - ToolbarGroup, -} from '@patternfly/react-core'; -import { ExpandableRowContent, Table, Tbody, Td, Th, Thead, Tr } from '@patternfly/react-table'; - -import CompoundSearchFilter from 'Components/CompoundSearchFilter/components/CompoundSearchFilter'; -import CompoundSearchFilterLabels from 'Components/CompoundSearchFilter/components/CompoundSearchFilterLabels'; -import type { OnSearchCallback } from 'Components/CompoundSearchFilter/types'; -import { updateSearchFilter } from 'Components/CompoundSearchFilter/utils/utils'; -import TBodyUnified from 'Components/TableStateTemplates/TbodyUnified'; -import useRestQuery from 'hooks/useRestQuery'; -import useTableSelection from 'hooks/useTableSelection'; -import type { ComplianceProfileSummary } from 'services/ComplianceCommon'; -import { listProfileSummaries } from 'services/ComplianceProfileService'; -import type { SearchFilter } from 'types/search'; -import { getTableUIState } from 'utils/getTableUIState'; - -import { complianceProfileOperatorKindLabels } from '../../compliance.constants'; -import { profileSearchFilterConfig } from '../../searchFilterConfig'; -import type { ScanConfigFormValues } from '../compliance.scanConfigs.utils'; - -const searchFilterConfig = [profileSearchFilterConfig]; - -export type ProfileSelectionProps = { - alertRef: RefObject; - clusterIds: string[]; -}; - -function ProfileSelection({ alertRef, clusterIds }: ProfileSelectionProps): ReactElement { - const { - setFieldValue, - values: formikValues, - touched: formikTouched, - }: FormikContextType = useFormikContext(); - - const [searchFilter, setSearchFilter] = useState({}); - const [expandedProfileNames, setExpandedProfileNames] = useState([]); - const setProfileExpanded = (name: string, isExpanding = true) => - setExpandedProfileNames((prevExpanded) => { - const otherExpandedProfileNames = prevExpanded.filter( - (profileName) => profileName !== name - ); - return isExpanding ? [...otherExpandedProfileNames, name] : otherExpandedProfileNames; - }); - - const onSearch: OnSearchCallback = (payload) => { - setSearchFilter((prevSearchFilter) => updateSearchFilter(prevSearchFilter, payload)); - }; - - const listProfilesQuery = useCallback(() => { - if (clusterIds.length > 0) { - return listProfileSummaries(clusterIds, searchFilter); - } - return Promise.resolve([]); - }, [clusterIds, searchFilter]); - const { - data: profiles, - isLoading: isFetchingProfiles, - error: profilesFetchError, - } = useRestQuery(listProfilesQuery); - - const profileList = profiles ?? []; - - const profileIsPreSelected = useCallback( - (row) => formikValues.profiles.includes(row.name), - [formikValues.profiles] - ); - - const { selected, onSelect } = useTableSelection(profileList, profileIsPreSelected, 'name'); - - const handleSelect = ( - event: FormEvent, - isSelected: boolean, - rowId: number - ) => { - onSelect(event, isSelected, rowId); - - const visibleSelectedNames = profileList - .filter((_, index) => { - return index === rowId ? isSelected : selected[index]; - }) - .map((profile) => profile.name); - - const hiddenSelectedNames = formikValues.profiles.filter( - (name) => !profileList.some((p) => p.name === name) - ); - - setFieldValue('profiles', [...hiddenSelectedNames, ...visibleSelectedNames]); - }; - - const isProfileExpanded = (name: string) => expandedProfileNames.includes(name); - const totalColumns = 7; - - const tableState = getTableUIState({ - isLoading: isFetchingProfiles, - data: profiles, - error: profilesFetchError, - searchFilter, - }); - - return ( - <> - - - - Profiles - - Select profiles to be included in the scan - - - -
    - {formikTouched.profiles && formikValues.profiles.length === 0 && ( - - )} - - - - - - - - -
    - - - - - - - - - - - tableState={tableState} - colSpan={totalColumns} - filteredEmptyProps={{ onClearFilters: () => setSearchFilter({}) }} - renderer={({ data }) => - data.map( - ( - { - description, - name, - productType, - ruleCount, - title, - profileVersion, - operatorKind, - }, - rowIndex - ) => ( - - - - - - - - - - - - - ) - ) - } - /> -
    - - ProfileTypeRule setApplicabilityVersion
    - handleSelect(event, isSelected, rowIndex), - isSelected: selected[rowIndex], - }} - /> - - setProfileExpanded( - name, - !isProfileExpanded(name) - ), - }} - /> - {name} - {(operatorKind && - complianceProfileOperatorKindLabels[ - operatorKind - ]) ?? - '—'} - {ruleCount}{productType}{profileVersion || '-'}
    - - {title} - {description} - -
    - - - ); -} - -export default ProfileSelection; diff --git a/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/Wizard/ReportConfiguration.tsx b/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/Wizard/ReportConfiguration.tsx deleted file mode 100644 index 93093abad42ee..0000000000000 --- a/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/Wizard/ReportConfiguration.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import type { ReactElement } from 'react'; -import { useFormikContext } from 'formik'; -import type { FormikContextType } from 'formik'; -import { Divider, Flex, FlexItem, Form, PageSection, Title } from '@patternfly/react-core'; - -import NotifierConfigurationForm from 'Components/NotifierConfiguration/NotifierConfigurationForm'; -import usePermissions from 'hooks/usePermissions'; - -// import { getBodyDefault, getSubjectDefault } from '../compliance.scanConfigs.utils'; -import type { ScanConfigFormValues } from '../compliance.scanConfigs.utils'; - -function ReportConfiguration(): ReactElement { - const formik: FormikContextType = useFormikContext(); - const { hasReadWriteAccess } = usePermissions(); - const hasWriteAccessForIntegration = hasReadWriteAccess('Integration'); - - return ( - <> - - - - Report - - - Optionally configure e-mail delivery destinations for reports - - - - -
    - - - - ); -} - -export default ReportConfiguration; diff --git a/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/Wizard/ReviewConfig.tsx b/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/Wizard/ReviewConfig.tsx deleted file mode 100644 index b4c551c4d825b..0000000000000 --- a/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/Wizard/ReviewConfig.tsx +++ /dev/null @@ -1,118 +0,0 @@ -import { useFormikContext } from 'formik'; -import type { FormikContextType } from 'formik'; -import { - Alert, - Badge, - Divider, - Flex, - FlexItem, - List, - ListItem, - PageSection, - Title, -} from '@patternfly/react-core'; - -import NotifierConfigurationView from 'Components/NotifierConfiguration/NotifierConfigurationView'; -import type { ComplianceIntegration } from 'services/ComplianceIntegrationService'; - -import { - convertFormikParametersToSchedule, - getBodyDefault, - getSubjectDefault, -} from '../compliance.scanConfigs.utils'; -import type { ScanConfigFormValues } from '../compliance.scanConfigs.utils'; -import ScanConfigParametersView from '../components/ScanConfigParametersView'; -import ScanConfigProfilesView from '../components/ScanConfigProfilesView'; - -const headingLevel = 'h3'; - -export type ReviewConfigProps = { - clusters: ComplianceIntegration[]; - errorMessage: string; -}; - -function ReviewConfig({ clusters, errorMessage }: ReviewConfigProps) { - const { values: formikValues }: FormikContextType = useFormikContext(); - - const scanSchedule = convertFormikParametersToSchedule(formikValues.parameters); - - function findById(selectedIds: string[], items: T[], idKey: K): T[] { - return selectedIds - .map((id) => items.find((item) => String(item[idKey]) === id)) - .filter((item): item is T => item !== undefined); - } - - const selectedClusters = findById(formikValues.clusters, clusters, 'clusterId'); - - return ( - <> - - - - Review - - Review the scan schedule before you save changes - {errorMessage && ( - - {errorMessage} - - )} - - - - - - - - Clusters - {selectedClusters.length} - - - {selectedClusters.map((cluster) => ( - {cluster.clusterName} - ))} - - - - - - Compliance Operator runs a new scan schedule immediately upon creation, but does - not run until scheduled time when you save changes to an existing scan schedule. - - - - ); -} - -export default ReviewConfig; diff --git a/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/Wizard/ClusterSelection.tsx b/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/Wizard/ScanConfigClustersStep.tsx similarity index 69% rename from ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/Wizard/ClusterSelection.tsx rename to ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/Wizard/ScanConfigClustersStep.tsx index 9b0131e9e9afa..6a01dddfec7ae 100644 --- a/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/Wizard/ClusterSelection.tsx +++ b/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/Wizard/ScanConfigClustersStep.tsx @@ -7,7 +7,6 @@ import { Alert, Bullseye, Button, - Divider, Flex, FlexItem, Form, @@ -27,7 +26,7 @@ import type { ComplianceIntegration } from 'services/ComplianceIntegrationServic import type { ScanConfigFormValues } from '../compliance.scanConfigs.utils'; import ComplianceClusterStatus from '../components/ComplianceClusterStatus'; -export type ClusterSelectionProps = { +export type ScanConfigClustersStepProps = { alertRef: RefObject; clusters: ComplianceIntegration[]; isFetchingClusters: boolean; @@ -41,11 +40,11 @@ function InstallClustersButton() { ); } -function ClusterSelection({ +function ScanConfigClustersStep({ alertRef, clusters, isFetchingClusters, -}: ClusterSelectionProps): ReactElement { +}: ScanConfigClustersStepProps): ReactElement { const isRouteEnabled = useIsRouteEnabled(); const isRouteEnabledForClusters = isRouteEnabled('clusters'); const { @@ -151,55 +150,49 @@ function ClusterSelection({ } return ( - <> - - - - Clusters - - Select clusters to be included in the scan - - - -
    - -

    - Tip: The most common reason a cluster is marked Unhealthy is that the - Compliance Operator is either not installed or is below version 1.6. -

    -
    - {formikTouched.clusters && formikValues.clusters.length === 0 && ( + + + Clusters + - )} - - - - - - - - - {renderTableBodyContent()} -
    - NameOperator statusOperator version
    - - + > +

    + Tip: The most common reason a cluster is marked Unhealthy is that the + Compliance Operator is either not installed or is below version 1.6. +

    +
    + {formikTouched.clusters && formikValues.clusters.length === 0 && ( + + )} + + + + + + + + + {renderTableBodyContent()} +
    + NameOperator statusOperator version
    + +
    +
    ); } -export default ClusterSelection; +export default ScanConfigClustersStep; diff --git a/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/Wizard/ScanConfigDeliveryStep.tsx b/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/Wizard/ScanConfigDeliveryStep.tsx new file mode 100644 index 0000000000000..85f938cba0e56 --- /dev/null +++ b/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/Wizard/ScanConfigDeliveryStep.tsx @@ -0,0 +1,34 @@ +import type { ReactElement } from 'react'; +import { useFormikContext } from 'formik'; +import type { FormikContextType } from 'formik'; +import { Flex, Form, PageSection, Title } from '@patternfly/react-core'; + +import NotifierConfigurationForm from 'Components/NotifierConfiguration/NotifierConfigurationForm'; +import usePermissions from 'hooks/usePermissions'; + +import type { ScanConfigFormValues } from '../compliance.scanConfigs.utils'; + +function ScanConfigDeliveryStep(): ReactElement { + const formik: FormikContextType = useFormikContext(); + const { hasReadWriteAccess } = usePermissions(); + const hasWriteAccessForIntegration = hasReadWriteAccess('Integration'); + + return ( + + + Delivery +
    + + +
    +
    + ); +} + +export default ScanConfigDeliveryStep; diff --git a/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/Wizard/ScanConfigOptions.tsx b/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/Wizard/ScanConfigOptions.tsx index 9e683163df5e2..4406d7374c661 100644 --- a/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/Wizard/ScanConfigOptions.tsx +++ b/ui/apps/platform/src/Containers/ComplianceEnhanced/Schedules/Wizard/ScanConfigOptions.tsx @@ -2,13 +2,11 @@ import type { FormEvent, ReactElement } from 'react'; import { useFormikContext } from 'formik'; import type { FormikContextType } from 'formik'; import { - Divider, Flex, FlexItem, Form, + FormSection, PageSection, - Stack, - StackItem, TextArea, TextInput, TimePicker, @@ -20,7 +18,7 @@ import FormLabelGroup from 'Components/PatternFly/FormLabelGroup'; import RepeatScheduleDropdown from 'Components/PatternFly/RepeatScheduleDropdown'; import usePageAction from 'hooks/usePageAction'; -import type { PageActions, ScanConfigFormValues } from '../compliance.scanConfigs.utils'; +import type { ScanConfigFormValues, SchedulePageAction } from '../compliance.scanConfigs.utils'; import { helperTextForName, helperTextForNameEdit, helperTextForTime } from './useFormikScanConfig'; @@ -28,7 +26,7 @@ import './ScanConfigOptions.css'; function ScanConfigOptions(): ReactElement { const formik: FormikContextType = useFormikContext(); - const { pageAction } = usePageAction(); + const { pageAction } = usePageAction(); const isEditAction = pageAction === 'edit'; function handleSelectChange(id: string, value: string): void { @@ -46,187 +44,146 @@ function ScanConfigOptions(): ReactElement { } return ( - <> - - - - Parameters - - Set name and schedule to scan on a recurring basis - - - -
    - - - - - - + + Parameters + + + formik.handleChange(event)} + onBlur={formik.handleBlur} + /> + + +