diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 23b855f38..9651d240c 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -42,7 +42,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # v4 + uses: github/codeql-action/init@db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28 # v4 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -56,7 +56,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # v4 + uses: github/codeql-action/autobuild@db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28 # v4 # â„šī¸ Command-line programs to run using the OS shell. # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun @@ -69,4 +69,4 @@ jobs: # ./location_of_script_within_repo/buildscript.sh - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # v4 + uses: github/codeql-action/analyze@db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28 # v4 diff --git a/.github/workflows/codespell.yml b/.github/workflows/codespell.yml index 35f0d7383..7991f3002 100644 --- a/.github/workflows/codespell.yml +++ b/.github/workflows/codespell.yml @@ -12,7 +12,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden Runner - uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0 + uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1 with: egress-policy: audit diff --git a/.github/workflows/trivy.yml b/.github/workflows/trivy.yml index b64652a0b..cb897bfc3 100644 --- a/.github/workflows/trivy.yml +++ b/.github/workflows/trivy.yml @@ -1,215 +1,252 @@ -name: Trivy Vulnerability Scanner -on: - schedule: - - cron: '0 6 * * *' # Daily at 6:00 AM UTC - push: - branches: - - main - # Publish semver tags as releases. - tags: - - 'v*.*.*' - workflow_dispatch: {} - -permissions: - contents: read - packages: write - issues: write - -env: - REGISTRY: ghcr.io - HUB_AGENT_IMAGE_NAME: hub-agent - MEMBER_AGENT_IMAGE_NAME: member-agent - REFRESH_TOKEN_IMAGE_NAME: refresh-token - - GO_VERSION: '1.26.6' - -jobs: - export-registry: - runs-on: ubuntu-latest - outputs: - registry: ${{ steps.export.outputs.registry }} - steps: - - id: export - run: | - # registry must be in lowercase - # store the images under dev - # TODO: need to cleanup dev images periodically - echo "registry=$(echo "${{ env.REGISTRY }}/${{ github.repository }}" | tr '[:upper:]' '[:lower:]')" >> "$GITHUB_OUTPUT" - scan-images: - needs: export-registry - env: - REGISTRY: ${{ needs.export-registry.outputs.registry }} - runs-on: ubuntu-latest - steps: - - name: Set up Go ${{ env.GO_VERSION }} - uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7 - with: - go-version: ${{ env.GO_VERSION }} - - - name: Checkout code - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - - - name: Login to ${{ env.REGISTRY }} - uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f - with: - registry: ${{ env.REGISTRY }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: generate image version - run: echo "IMAGE_VERSION=$(git rev-parse --short=7 HEAD)" >> "$GITHUB_ENV" - - # Note: scheduled runs rebuild images to scan the latest code on main. - # This ensures we catch newly disclosed CVEs against the current source. - - name: Build and push images to registry with tag ${{ env.IMAGE_VERSION }} - run: | - make push - env: - REGISTRY: ${{ env.REGISTRY}} - TAG: ${{ env.IMAGE_VERSION }} - - - name: Scan ${{ env.REGISTRY }}/${{ env.HUB_AGENT_IMAGE_NAME }}:${{ env.IMAGE_VERSION }} - uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0 - with: - image-ref: ${{ env.REGISTRY }}/${{ env.HUB_AGENT_IMAGE_NAME }}:${{ env.IMAGE_VERSION }} - format: 'json' - output: 'trivy-hub-agent.json' - ignore-unfixed: true - vuln-type: 'os,library' - severity: 'CRITICAL,HIGH' - timeout: '5m0s' - env: - TRIVY_USERNAME: ${{ github.actor }} - TRIVY_PASSWORD: ${{ secrets.GITHUB_TOKEN }} - TRIVY_DB_REPOSITORY: mcr.microsoft.com/oss/v2/aquasecurity/trivy-db - - - name: Scan ${{ env.REGISTRY }}/${{ env.MEMBER_AGENT_IMAGE_NAME }}:${{ env.IMAGE_VERSION }} - uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0 - with: - image-ref: ${{ env.REGISTRY }}/${{ env.MEMBER_AGENT_IMAGE_NAME }}:${{ env.IMAGE_VERSION }} - format: 'json' - output: 'trivy-member-agent.json' - ignore-unfixed: true - vuln-type: 'os,library' - severity: 'CRITICAL,HIGH' - timeout: '5m0s' - env: - TRIVY_USERNAME: ${{ github.actor }} - TRIVY_PASSWORD: ${{ secrets.GITHUB_TOKEN }} - TRIVY_DB_REPOSITORY: mcr.microsoft.com/oss/v2/aquasecurity/trivy-db - - - name: Scan ${{ env.REGISTRY }}/${{ env.REFRESH_TOKEN_IMAGE_NAME }}:${{ env.IMAGE_VERSION }} - uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0 - with: - image-ref: ${{ env.REGISTRY }}/${{ env.REFRESH_TOKEN_IMAGE_NAME }}:${{ env.IMAGE_VERSION }} - format: 'json' - output: 'trivy-refresh-token.json' - ignore-unfixed: true - vuln-type: 'os,library' - severity: 'CRITICAL,HIGH' - timeout: '5m0s' - env: - TRIVY_USERNAME: ${{ github.actor }} - TRIVY_PASSWORD: ${{ secrets.GITHUB_TOKEN }} - TRIVY_DB_REPOSITORY: mcr.microsoft.com/oss/v2/aquasecurity/trivy-db - - - name: Check for vulnerabilities - id: check-vulns - run: | - has_vulns=false - for file in trivy-hub-agent.json trivy-member-agent.json trivy-refresh-token.json; do - count=$(jq '[.Results[]? | .Vulnerabilities[]?] | length' "$file") - if [ "$count" -gt 0 ]; then - has_vulns=true - break - fi - done - echo "has_vulns=$has_vulns" >> "$GITHUB_OUTPUT" - - - name: Fail on vulnerabilities (non-scheduled runs) - if: steps.check-vulns.outputs.has_vulns == 'true' && github.event_name != 'schedule' - run: | - echo "::error::Vulnerabilities found. See trivy scan output." - for file in trivy-hub-agent.json trivy-member-agent.json trivy-refresh-token.json; do - echo "--- $file ---" - jq -r '.Results[]? | .Vulnerabilities[]? | "\(.VulnerabilityID) \(.Severity) \(.PkgName) \(.InstalledVersion) -> \(.FixedVersion)"' "$file" - done - exit 1 - - - name: Build vulnerability summary - if: steps.check-vulns.outputs.has_vulns == 'true' && github.event_name == 'schedule' - id: vuln-summary - run: | - { - echo 'body<@\`" - echo "2. Run \`go mod tidy\` to clean up dependencies." - echo "" - echo "**OS / base-image CVEs:**" - echo "1. Update the base image in the relevant \`Dockerfile\` under \`docker/\`." - echo "" - echo "**Then verify:**" - echo "1. Run \`make build\` to verify the build passes." - echo "2. Run \`make test\` to verify tests pass." - echo "" - echo "**Review:** Request review from \`@kubefleet-dev/kubefleet-secops\` on the resulting PR." - echo 'EOF' - } >> "$GITHUB_OUTPUT" - - - name: Create or update security issue - if: steps.check-vulns.outputs.has_vulns == 'true' && github.event_name == 'schedule' - uses: actions/github-script@v9 - with: - script: | - const today = new Date().toISOString().split('T')[0]; - const title = `fix: address trivy CVEs found on ${today}`; - const securityTeam = '@kubefleet-dev/kubefleet-secops'; - const body = `${process.env.ISSUE_BODY}\n\n### Security owners\n${securityTeam}`; - - // Check if an open issue already exists for today - const existing = await github.rest.issues.listForRepo({ - owner: context.repo.owner, - repo: context.repo.repo, - state: 'open', - labels: 'security,trivy', - per_page: 100 - }); - const issue = existing.data.find(i => i.title === title); - if (issue) { - await github.rest.issues.update({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: issue.number, - body: body - }); - console.log('Updated the existing issue with the current scan and security team mention.'); - } else { - await github.rest.issues.create({ - owner: context.repo.owner, - repo: context.repo.repo, - title: title, - body: body, - labels: ['security', 'trivy'] - }); - } - env: - ISSUE_BODY: ${{ steps.vuln-summary.outputs.body }} +name: Trivy Vulnerability Scanner +on: + schedule: + - cron: '0 6 * * *' # Daily at 6:00 AM UTC; findings are reported into a single issue per ISO week + push: + branches: + - main + # Publish semver tags as releases. + tags: + - 'v*.*.*' + workflow_dispatch: {} + +permissions: + contents: read + packages: write + issues: write + +env: + REGISTRY: ghcr.io + HUB_AGENT_IMAGE_NAME: hub-agent + MEMBER_AGENT_IMAGE_NAME: member-agent + REFRESH_TOKEN_IMAGE_NAME: refresh-token + + GO_VERSION: '1.26.6' + +jobs: + export-registry: + runs-on: ubuntu-latest + outputs: + registry: ${{ steps.export.outputs.registry }} + steps: + - id: export + run: | + # registry must be in lowercase + # store the images under dev + # TODO: need to cleanup dev images periodically + echo "registry=$(echo "${{ env.REGISTRY }}/${{ github.repository }}" | tr '[:upper:]' '[:lower:]')" >> "$GITHUB_OUTPUT" + scan-images: + needs: export-registry + env: + REGISTRY: ${{ needs.export-registry.outputs.registry }} + runs-on: ubuntu-latest + steps: + - name: Set up Go ${{ env.GO_VERSION }} + uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7 + with: + go-version: ${{ env.GO_VERSION }} + + - name: Checkout code + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + + - name: Login to ${{ env.REGISTRY }} + uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: generate image version + run: echo "IMAGE_VERSION=$(git rev-parse --short=7 HEAD)" >> "$GITHUB_ENV" + + # Note: scheduled runs rebuild images to scan the latest code on main. + # This ensures we catch newly disclosed CVEs against the current source. + - name: Build and push images to registry with tag ${{ env.IMAGE_VERSION }} + run: | + make push + env: + REGISTRY: ${{ env.REGISTRY}} + TAG: ${{ env.IMAGE_VERSION }} + + - name: Scan ${{ env.REGISTRY }}/${{ env.HUB_AGENT_IMAGE_NAME }}:${{ env.IMAGE_VERSION }} + uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0 + with: + image-ref: ${{ env.REGISTRY }}/${{ env.HUB_AGENT_IMAGE_NAME }}:${{ env.IMAGE_VERSION }} + format: 'json' + output: 'trivy-hub-agent.json' + ignore-unfixed: true + vuln-type: 'os,library' + severity: 'CRITICAL,HIGH' + timeout: '5m0s' + env: + TRIVY_USERNAME: ${{ github.actor }} + TRIVY_PASSWORD: ${{ secrets.GITHUB_TOKEN }} + TRIVY_DB_REPOSITORY: mcr.microsoft.com/oss/v2/aquasecurity/trivy-db + + - name: Scan ${{ env.REGISTRY }}/${{ env.MEMBER_AGENT_IMAGE_NAME }}:${{ env.IMAGE_VERSION }} + uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0 + with: + image-ref: ${{ env.REGISTRY }}/${{ env.MEMBER_AGENT_IMAGE_NAME }}:${{ env.IMAGE_VERSION }} + format: 'json' + output: 'trivy-member-agent.json' + ignore-unfixed: true + vuln-type: 'os,library' + severity: 'CRITICAL,HIGH' + timeout: '5m0s' + env: + TRIVY_USERNAME: ${{ github.actor }} + TRIVY_PASSWORD: ${{ secrets.GITHUB_TOKEN }} + TRIVY_DB_REPOSITORY: mcr.microsoft.com/oss/v2/aquasecurity/trivy-db + + - name: Scan ${{ env.REGISTRY }}/${{ env.REFRESH_TOKEN_IMAGE_NAME }}:${{ env.IMAGE_VERSION }} + uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0 + with: + image-ref: ${{ env.REGISTRY }}/${{ env.REFRESH_TOKEN_IMAGE_NAME }}:${{ env.IMAGE_VERSION }} + format: 'json' + output: 'trivy-refresh-token.json' + ignore-unfixed: true + vuln-type: 'os,library' + severity: 'CRITICAL,HIGH' + timeout: '5m0s' + env: + TRIVY_USERNAME: ${{ github.actor }} + TRIVY_PASSWORD: ${{ secrets.GITHUB_TOKEN }} + TRIVY_DB_REPOSITORY: mcr.microsoft.com/oss/v2/aquasecurity/trivy-db + + - name: Check for vulnerabilities + id: check-vulns + run: | + has_vulns=false + for file in trivy-hub-agent.json trivy-member-agent.json trivy-refresh-token.json; do + count=$(jq '[.Results[]? | .Vulnerabilities[]?] | length' "$file") + if [ "$count" -gt 0 ]; then + has_vulns=true + break + fi + done + echo "has_vulns=$has_vulns" >> "$GITHUB_OUTPUT" + + - name: Fail on vulnerabilities (non-scheduled runs) + if: steps.check-vulns.outputs.has_vulns == 'true' && github.event_name != 'schedule' + run: | + echo "::error::Vulnerabilities found. See trivy scan output." + for file in trivy-hub-agent.json trivy-member-agent.json trivy-refresh-token.json; do + echo "--- $file ---" + jq -r '.Results[]? | .Vulnerabilities[]? | "\(.VulnerabilityID) \(.Severity) \(.PkgName) \(.InstalledVersion) -> \(.FixedVersion)"' "$file" + done + exit 1 + + - name: Build vulnerability summary + if: steps.check-vulns.outputs.has_vulns == 'true' && github.event_name == 'schedule' + id: vuln-summary + run: | + { + echo 'body<@\`" + echo "2. Run \`go mod tidy\` to clean up dependencies." + echo "" + echo "**OS / base-image CVEs:**" + echo "1. Update the base image in the relevant \`Dockerfile\` under \`docker/\`." + echo "" + echo "**Then verify:**" + echo "1. Run \`make build\` to verify the build passes." + echo "2. Run \`make test\` to verify tests pass." + echo "" + echo "**Review:** Request review from \`@kubefleet-dev/kubefleet-secops\` on the resulting PR." + echo 'EOF' + } >> "$GITHUB_OUTPUT" + + - name: Create or update security issue + if: steps.check-vulns.outputs.has_vulns == 'true' && github.event_name == 'schedule' + uses: actions/github-script@v9 + with: + script: | + // ISO week date (e.g. 2026-W37); the ISO year can differ from the calendar year near year end. + const isoWeek = (date) => { + const d = new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate())); + d.setUTCDate(d.getUTCDate() + 4 - (d.getUTCDay() || 7)); + const yearStart = new Date(Date.UTC(d.getUTCFullYear(), 0, 1)); + const week = Math.ceil(((d - yearStart) / 86400000 + 1) / 7); + return `${d.getUTCFullYear()}-W${String(week).padStart(2, '0')}`; + }; + + const currentWeek = isoWeek(new Date()); + const title = `fix: address trivy CVEs found in ${currentWeek}`; + const securityTeam = '@kubefleet-dev/kubefleet-secops'; + const body = `${process.env.ISSUE_BODY}\n\n### Security owners\n${securityTeam}`; + + // Every report this workflow files carries these labels; they are the only handle used to find prior reports. + const reportLabels = ['security', 'trivy']; + + const open = await github.paginate(github.rest.issues.listForRepo, { + owner: context.repo.owner, + repo: context.repo.repo, + state: 'open', + labels: reportLabels.join(','), + sort: 'created', + direction: 'desc', + per_page: 100 + }); + // listForRepo also returns pull requests, which are never trivy reports. + const reports = open.filter(i => !i.pull_request); + + const newest = reports[0]; + const thisWeek = newest && isoWeek(new Date(newest.created_at)) === currentWeek ? newest : null; + + if (thisWeek) { + await github.rest.issues.update({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: thisWeek.number, + body: body + }); + console.log(`Updated issue #${thisWeek.number} with the current scan.`); + } else { + const created = await github.rest.issues.create({ + owner: context.repo.owner, + repo: context.repo.repo, + title: title, + body: body, + labels: reportLabels + }); + console.log(`Created issue #${created.data.number} for ${currentWeek}.`); + } + + // Any other open report predates this week; the current one supersedes it. + for (const stale of reports.filter(i => i.number !== thisWeek?.number)) { + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: stale.number, + body: `Superseded by the scan reported in \`${title}\`; closing this stale report.` + }); + await github.rest.issues.update({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: stale.number, + state: 'closed', + state_reason: 'not_planned' + }); + console.log(`Closed stale issue #${stale.number} (created ${stale.created_at}).`); + } + env: + ISSUE_BODY: ${{ steps.vuln-summary.outputs.body }} diff --git a/.github/workflows/workflow-lint.yml b/.github/workflows/workflow-lint.yml index 87423fa1c..532818b2c 100644 --- a/.github/workflows/workflow-lint.yml +++ b/.github/workflows/workflow-lint.yml @@ -22,7 +22,7 @@ jobs: timeout-minutes: 10 steps: - name: Harden Runner - uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0 + uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1 with: egress-policy: audit diff --git a/apis/kubefleet.dev/placement/v1alpha1/clusterclaim_types.go b/apis/kubefleet.dev/placement/v1alpha1/clusterclaim_types.go index c9f3f9ab6..732e0aced 100644 --- a/apis/kubefleet.dev/placement/v1alpha1/clusterclaim_types.go +++ b/apis/kubefleet.dev/placement/v1alpha1/clusterclaim_types.go @@ -46,6 +46,7 @@ type ClusterClaim struct { Status ClusterClaimStatus `json:"status,omitempty"` } +// +kubebuilder:validation:XValidation:rule="has(self.clusterSelectorTerms) == has(oldSelf.clusterSelectorTerms)",message="the clusterSelectorTerms field cannot be added or removed after creation" type ClusterClaimSpec struct { // The reference to the placement policy that adds the cluster claim. // diff --git a/apis/kubefleet.dev/placement/v1alpha1/interface.go b/apis/kubefleet.dev/placement/v1alpha1/interface.go new file mode 100644 index 000000000..bfc3100d4 --- /dev/null +++ b/apis/kubefleet.dev/placement/v1alpha1/interface.go @@ -0,0 +1,150 @@ +/* +Copyright 2026 The KubeFleet Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1alpha1 + +import ( + "sigs.k8s.io/controller-runtime/pkg/client" +) + +// Verify the implementation of the accessor interfaces for the placement policy, +// placement resource snapshot, and placement binding resources. +var _ PlacementPolicyAccessor = &PlacementPolicy{} +var _ PlacementPolicyAccessor = &ClusterPlacementPolicy{} +var _ PlacementResourceSnapshotAccessor = &PlacementResourceSnapshot{} +var _ PlacementResourceSnapshotAccessor = &ClusterPlacementResourceSnapshot{} +var _ PlacementBindingAccessor = &PlacementBinding{} +var _ PlacementBindingAccessor = &ClusterPlacementBinding{} + +// PlacementPolicyAccessor provides unified access to the spec and status of placement policy resources, +// namespace-scoped and cluster-scoped. +// +// +kubebuilder:object:generate=false +type PlacementPolicyAccessor interface { + client.Object + + GetSpec() *PlacementPolicySpec + GetStatus() *PlacementPolicyStatus + + SetSpec(PlacementPolicySpec) + SetStatus(PlacementPolicyStatus) +} + +func (p *PlacementPolicy) GetSpec() *PlacementPolicySpec { + return &p.Spec +} + +func (p *PlacementPolicy) GetStatus() *PlacementPolicyStatus { + return &p.Status +} + +func (p *PlacementPolicy) SetSpec(spec PlacementPolicySpec) { + p.Spec = spec +} + +func (p *PlacementPolicy) SetStatus(status PlacementPolicyStatus) { + p.Status = status +} + +func (p *ClusterPlacementPolicy) GetSpec() *PlacementPolicySpec { + return &p.Spec +} + +func (p *ClusterPlacementPolicy) GetStatus() *PlacementPolicyStatus { + return &p.Status +} + +func (p *ClusterPlacementPolicy) SetSpec(spec PlacementPolicySpec) { + p.Spec = spec +} + +func (p *ClusterPlacementPolicy) SetStatus(status PlacementPolicyStatus) { + p.Status = status +} + +// PlacementResourceSnapshotAccessor provides unified access to the spec of placement resource snapshot resources, +// namespace-scoped and cluster-scoped. +// +// +kubebuilder:object:generate=false +type PlacementResourceSnapshotAccessor interface { + client.Object + + GetSpec() *PlacementResourceSnapshotSpec + + SetSpec(PlacementResourceSnapshotSpec) +} + +func (p *PlacementResourceSnapshot) GetSpec() *PlacementResourceSnapshotSpec { + return &p.Spec +} + +func (p *PlacementResourceSnapshot) SetSpec(spec PlacementResourceSnapshotSpec) { + p.Spec = spec +} + +func (p *ClusterPlacementResourceSnapshot) GetSpec() *PlacementResourceSnapshotSpec { + return &p.Spec +} + +func (p *ClusterPlacementResourceSnapshot) SetSpec(spec PlacementResourceSnapshotSpec) { + p.Spec = spec +} + +// PlacementBindingAccessor provides unified access to the spec and status of placement binding resources, +// namespace-scoped and cluster-scoped. +// +// +kubebuilder:object:generate=false +type PlacementBindingAccessor interface { + client.Object + + GetSpec() *PlacementBindingSpec + GetStatus() *PlacementBindingStatus + + SetSpec(PlacementBindingSpec) + SetStatus(PlacementBindingStatus) +} + +func (p *PlacementBinding) GetSpec() *PlacementBindingSpec { + return &p.Spec +} + +func (p *PlacementBinding) GetStatus() *PlacementBindingStatus { + return &p.Status +} + +func (p *PlacementBinding) SetSpec(spec PlacementBindingSpec) { + p.Spec = spec +} + +func (p *PlacementBinding) SetStatus(status PlacementBindingStatus) { + p.Status = status +} + +func (p *ClusterPlacementBinding) GetSpec() *PlacementBindingSpec { + return &p.Spec +} + +func (p *ClusterPlacementBinding) GetStatus() *PlacementBindingStatus { + return &p.Status +} + +func (p *ClusterPlacementBinding) SetSpec(spec PlacementBindingSpec) { + p.Spec = spec +} + +func (p *ClusterPlacementBinding) SetStatus(status PlacementBindingStatus) { + p.Status = status +} diff --git a/config/crd/bases/placement.kubefleet.dev_clusterclaims.yaml b/config/crd/bases/placement.kubefleet.dev_clusterclaims.yaml index 2bd4a6ad8..bb1fc81e0 100644 --- a/config/crd/bases/placement.kubefleet.dev_clusterclaims.yaml +++ b/config/crd/bases/placement.kubefleet.dev_clusterclaims.yaml @@ -220,6 +220,10 @@ spec: required: - placementPolicyRef type: object + x-kubernetes-validations: + - message: the clusterSelectorTerms field cannot be added or removed after + creation + rule: has(self.clusterSelectorTerms) == has(oldSelf.clusterSelectorTerms) status: description: The observed status of the cluster claim. properties: diff --git a/docker/hub-agent.Dockerfile b/docker/hub-agent.Dockerfile index 0d602d8cb..2e42a1de5 100644 --- a/docker/hub-agent.Dockerfile +++ b/docker/hub-agent.Dockerfile @@ -8,7 +8,7 @@ # Segmentation fault" / "cgo: gcc produced no output" in runtime/cgo and net) # and, when it did not crash, took over an hour per image. Only the final # distroless stage is per-target, and it runs no commands. -FROM --platform=$BUILDPLATFORM mcr.microsoft.com/oss/go/microsoft/golang:1.26.6-1 AS builder +FROM --platform=$BUILDPLATFORM mcr.microsoft.com/oss/go/microsoft/golang:1.26.8-1 AS builder WORKDIR /workspace # Copy the Go Modules manifests diff --git a/docker/member-agent.Dockerfile b/docker/member-agent.Dockerfile index e8eefcad6..f095a9adb 100644 --- a/docker/member-agent.Dockerfile +++ b/docker/member-agent.Dockerfile @@ -8,7 +8,7 @@ # Segmentation fault" / "cgo: gcc produced no output" in runtime/cgo and net) # and, when it did not crash, took over an hour per image. Only the final # distroless stage is per-target, and it runs no commands. -FROM --platform=$BUILDPLATFORM mcr.microsoft.com/oss/go/microsoft/golang:1.26.6-1 AS builder +FROM --platform=$BUILDPLATFORM mcr.microsoft.com/oss/go/microsoft/golang:1.26.8-1 AS builder WORKDIR /workspace # Copy the Go Modules manifests diff --git a/docker/refresh-token.Dockerfile b/docker/refresh-token.Dockerfile index 5a1ecde62..9ee6e5681 100644 --- a/docker/refresh-token.Dockerfile +++ b/docker/refresh-token.Dockerfile @@ -8,7 +8,7 @@ # Segmentation fault" / "cgo: gcc produced no output" in runtime/cgo and net) # and, when it did not crash, took over an hour per image. Only the final # distroless stage is per-target, and it runs no commands. -FROM --platform=$BUILDPLATFORM mcr.microsoft.com/oss/go/microsoft/golang:1.26.6-1 AS builder +FROM --platform=$BUILDPLATFORM mcr.microsoft.com/oss/go/microsoft/golang:1.26.8-1 AS builder WORKDIR /workspace # Copy the Go Modules manifests diff --git a/go.mod b/go.mod index 802badc37..dcd8744b6 100644 --- a/go.mod +++ b/go.mod @@ -24,7 +24,7 @@ require ( go.goms.io/fleet-networking v0.3.3 go.uber.org/atomic v1.11.0 go.uber.org/zap v1.27.0 - golang.org/x/sync v0.21.0 + golang.org/x/sync v0.22.0 golang.org/x/time v0.11.0 gomodules.xyz/jsonpatch/v2 v2.4.0 google.golang.org/grpc v1.82.1 @@ -113,14 +113,14 @@ require ( go.uber.org/multierr v1.11.0 // indirect go.yaml.in/yaml/v2 v2.4.2 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/crypto v0.53.0 // indirect + golang.org/x/crypto v0.56.0 // indirect golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // indirect - golang.org/x/net 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.46.0 // indirect - golang.org/x/term v0.44.0 // indirect - golang.org/x/text v0.39.0 // indirect - golang.org/x/tools v0.47.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/tools v0.48.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20260414002931-afd174a4e478 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478 // indirect gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect diff --git a/go.sum b/go.sum index b2373932b..37a86ef32 100644 --- a/go.sum +++ b/go.sum @@ -332,8 +332,8 @@ go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto= -golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsio= +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-20250305212735-054e65f0b394 h1:nDVHiLt8aIbd/VzvPWN6kSOPE7+F/fNFDSXLVYkE/Iw= golang.org/x/exp v0.0.0-20250305212735-054e65f0b394/go.mod h1:sIifuuw/Yco/y6yb6+bDNfyeQ/MdPUy/hKEMYQV17cM= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= @@ -342,35 +342,35 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o= -golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec= +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/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs= golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -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.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.1.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/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.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.39.0 h1:UbZz4pLOvn600D6Oh6GGEI6VAmndrEBLv8/6BEXzyus= -golang.org/x/text v0.39.0/go.mod h1:3UwRclnC2g0TU9x8PZiyfOajCd1zaUNHF9cvqcQZ+ZM= +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.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.47.0 h1:7Kn5x/d1svx/PzryTsqeoZN4TZwqeH5pGWjefhLi/1Q= -golang.org/x/tools v0.47.0/go.mod h1:dFHnyTvFWY212G+h7ZY4Vsp/K3U4/7W9TyVaAul8uCA= +golang.org/x/tools v0.48.0 h1:3+hClM1aLL5mjMKm5ovokw9epgRXPuu2tILgismM6RE= +golang.org/x/tools v0.48.0/go.mod h1:08xX0orndb/F7jJxGDicx061tyd5pcMto75YMAXr6lk= golang.org/x/xerrors v0.0.0-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=