Compare commits

..
44 Commits
Author SHA1 Message Date
renovate[bot]andlnx01 4fb77d3b8c Update all non-major dependencies (#1232)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-08-28 15:15:31 -03:00
jdesouza c2ccdb4a5d INS-2845: Prepare polaris for unifying circleci (#1228)
* Terraform plan

* Terraform plan

* Fix

* Fix

* Lint fixes

* Lint fixes

* Fix

* Remove file

* Fixed panic

* Fixed comment

* Reverted

* Removed unused param
2026-08-28 14:08:54 -03:00
renovate[bot]andlnx01 cdbb35f449 Update all non-major dependencies (#1231)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-08-26 11:50:04 -03:00
renovate[bot]andlnx01 c0e23a233b Update cimg/go Docker tag to v1.26.6 (#1229)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-08-21 10:45:05 -03:00
renovate[bot]andlnx01 3bfea9bf10 Update module github.com/sirupsen/logrus to v1.10.0 (#1227)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-08-20 14:05:50 -03:00
jdesouza 236ca14fca INS-2798: Refactor polaris to make it more compatible with other oss projects (#1226)
* INS-2798: Refactor polaris to make it more compatible with other oss projects

* INS-2798: Refactor polaris to make it more compatible with other oss projects

* Fix
2026-08-19 12:19:00 -03:00
renovate[bot]andlnx01 9fe4a72c77 Update module github.com/stretchr/testify to v1.12.0 (#1224)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-08-17 08:48:48 -03:00
Terraform User dafa0b74fc Managed by Terraform 2026-08-06 16:10:01 +00:00
f0a370c757 Update dependency rok8s to v16 (#1221)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: jdesouza <james@fairwinds.com>
2026-08-06 09:26:30 -03:00
cb741e3eb1 Update all non-major dependencies (#1219)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: jdesouza <james@fairwinds.com>
2026-08-06 09:14:18 -03:00
renovate[bot]andlnx01 b65142e8be Pin dependency rok8s to 14.4.0 (#1218)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-08-06 08:58:02 -03:00
Terraform User 31bc0fdb84 Managed by Terraform 2026-08-05 14:12:49 +00:00
292a1c257c Bump k8s.io/client-go from 0.35.4 to 0.36.3 (#1217)
* Bump k8s.io/client-go from 0.35.4 to 0.36.3

Bumps [k8s.io/client-go](https://github.com/kubernetes/client-go) from 0.35.4 to 0.36.3.
- [Changelog](https://github.com/kubernetes/client-go/blob/master/CHANGELOG.md)
- [Commits](https://github.com/kubernetes/client-go/compare/v0.35.4...v0.36.3)

---
updated-dependencies:
- dependency-name: k8s.io/client-go
  dependency-version: 0.36.3
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

* Bump sigs.k8s.io/controller-runtime to v0.24.1

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Vitor Vezani <vitor.vezani@fairwinds.com>
2026-08-03 11:03:11 -03:00
dependabot[bot]andlnx01 06ef2d3f70 Bump k8s.io/apimachinery from 0.36.2 to 0.36.3 (#1216)
Bumps [k8s.io/apimachinery](https://github.com/kubernetes/apimachinery) from 0.36.2 to 0.36.3.
- [Commits](https://github.com/kubernetes/apimachinery/compare/v0.36.2...v0.36.3)

---
updated-dependencies:
- dependency-name: k8s.io/apimachinery
  dependency-version: 0.36.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-03 10:44:28 -03:00
Vitor Rodrigo Vezani 4bdf5315b6 Add pod support to ResourceProvider and update related tests (#1214) 2026-07-21 13:19:25 -03:00
dependabot[bot]andlnx01 278cb9008c Bump golang.org/x/net from 0.51.0 to 0.55.0 (#1208)
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.51.0 to 0.55.0.
- [Commits](https://github.com/golang/net/compare/v0.51.0...v0.55.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-version: 0.55.0
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-06 18:09:58 -03:00
d6858e0fe4 Bump alpine from 3.23.4 to 3.24.1 (#1209)
Bumps alpine from 3.23.4 to 3.24.1.

---
updated-dependencies:
- dependency-name: alpine
  dependency-version: 3.24.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Vitor Rodrigo Vezani <vitor.vezani@fairwinds.com>
2026-07-06 17:36:37 -03:00
dependabot[bot]andlnx01 78fe4cb11c Bump k8s.io/apimachinery from 0.36.1 to 0.36.2 (#1210)
Bumps [k8s.io/apimachinery](https://github.com/kubernetes/apimachinery) from 0.36.1 to 0.36.2.
- [Commits](https://github.com/kubernetes/apimachinery/compare/v0.36.1...v0.36.2)

---
updated-dependencies:
- dependency-name: k8s.io/apimachinery
  dependency-version: 0.36.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-06 17:33:43 -03:00
dependabot[bot]andlnx01 387e787a40 Bump k8s.io/apimachinery from 0.35.4 to 0.36.1 (#1197)
Bumps [k8s.io/apimachinery](https://github.com/kubernetes/apimachinery) from 0.35.4 to 0.36.1.
- [Commits](https://github.com/kubernetes/apimachinery/compare/v0.35.4...v0.36.1)

---
updated-dependencies:
- dependency-name: k8s.io/apimachinery
  dependency-version: 0.36.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-01 08:46:46 -03:00
Terraform User 1fdfec73a1 Managed by Terraform 2026-04-27 18:39:00 +00:00
Vitor Rodrigo Vezani aee298e2e0 add notice to include registry change and immutable images notice (#1191)
* add notice to include registry change and immutable images notice

* try to fix tests

* Refactor goreleaser script to conditionally skip Docker tags based on release status

* remove if
2026-04-27 12:42:05 -03:00
Vitor Rodrigo Vezani 0444cf92bc unify release process along with others OSS projects (#1192)
* unify release process along with others OSS projects

* remove docker loging

* override polaris image repository for tests

* fix polaris

* adding snapshot back

* store snapshot version into circleci workspace and load with kind on k8s cluster

* add test back

* add test back

* fix polaris config
2026-04-27 12:24:11 -03:00
Terraform User 50e4c63079 Managed by Terraform 2026-04-24 20:23:41 +00:00
Terraform User 7d053a6cbd Managed by Terraform 2026-04-23 15:34:04 +00:00
Terraform User d50336387b Managed by Terraform 2026-04-23 14:42:23 +00:00
Vitor Rodrigo Vezani 1554c90ece Add GCR support (#1189) 2026-04-22 10:45:37 -03:00
jdesouza 9dfb4262e6 INS-2239: Fix polaris vulnerabilities (#1187)
* Reapply "Fix vulnerabilities"

This reverts commit 27878a4670.

* Fix

* Fix

* Fix

* Fix
2026-04-21 15:32:09 -03:00
jdesouza 27878a4670 Revert "Fix vulnerabilities"
This reverts commit a46eeb28b6.
2026-04-21 14:50:44 -03:00
jdesouza a46eeb28b6 Fix vulnerabilities 2026-04-21 14:49:25 -03:00
Vitor Rodrigo Vezani fd862afadc remove adhoc envsubst process on goreleaser (#1186)
* remove adhoc envsubst process

* use envOrDefault instead of sprig's default
2026-04-21 13:05:27 -03:00
Terraform User ee9f568ebd Managed by Terraform 2026-04-15 20:14:16 +00:00
jdesouza 47c7deddfd INS-2152: polaris libs bump (#1182)
* Upgrade libs

* INS-2152: polaris libs bump
2026-04-01 15:17:52 -03:00
jdesouza 2007a2c317 INS-2026: Bump libs and fix vulnerabilities for polaris (#1176)
* Bump

* Bump

* Bump

* Bump

* Bump

* Fix

* FIx

* FIx

* Fix
2026-03-09 14:16:16 -03:00
jdesouza a5af7bb34e INS-1950: Go 1.26 and bump libs for polaris (#1172)
* Go 1.26

* Go 1.26

* Go 1.26

* Go 1.26
2026-02-24 12:22:14 -03:00
jdesouza 893e5de589 Revert "Go 1.26"
This reverts commit f6eed6589d.
2026-02-23 08:23:23 -03:00
jdesouza f6eed6589d Go 1.26 2026-02-23 08:22:16 -03:00
jdesouza 517c8899d7 INS-1829: Bump libs and fix vulnerabilities (#1171)
* Fix vulnerability

* Bump
2026-01-30 16:21:23 -03:00
jdesouza e2df222ded INS-1683: polaris: Bump go to 1.25.5 for fixing vulnerability (#1162)
* INS-1683: polaris: Bump go to 1.25.5 for fixing vulnerability

* INS-1683: polaris: Bump go to 1.25.5 for fixing vulnerability
2025-12-08 10:50:39 -03:00
jdesouza d45d67cdd2 Revert "INS-1683: polaris: Bump go to 1.25.5 for fixing vulnerability"
This reverts commit e8a113388e.
2025-12-08 09:48:07 -03:00
jdesouza e8a113388e INS-1683: polaris: Bump go to 1.25.5 for fixing vulnerability 2025-12-08 09:44:58 -03:00
jdesouza 21ef1f495c INS-1562: Fix polaris vulnerabilities (#1156) 2025-10-31 11:01:10 -03:00
jdesouza ec1ba2f2db INS-1303: fix CVE-2025-47907 (#1141)
* Revert "Revert "INS-1303: fix CVE-2025-47907""

This reverts commit 39fb27c6dd.

* INS-1303: fix CVE-2025-47907
2025-08-18 13:01:19 -03:00
jdesouza 39fb27c6dd Revert "INS-1303: fix CVE-2025-47907"
This reverts commit e51b90ef79.
2025-08-18 10:48:27 -03:00
jdesouza e51b90ef79 INS-1303: fix CVE-2025-47907 2025-08-18 10:46:11 -03:00
47 changed files with 889 additions and 745 deletions
+141 -142
View File
@@ -1,183 +1,182 @@
## DO NOT EDIT - Managed by Terraform
version: 2.1
orbs:
rok8s: fairwinds/rok8s-scripts@12
rok8s: fairwinds/rok8s-scripts@16.0.0
oss-docs: fairwinds/oss-docs@0
executors:
vm:
machine:
enabled: true
commands:
install_goreleaser_dependencies:
description: Installs dependencies for CI scripts
steps:
- run: apk update
# gettext provides envsubst
- run: apk add gettext
# Register other docker platforms, to build arm64.
# This shouldn't be needed, why TBD.
- run: docker run --privileged --rm tonistiigi/binfmt --install all
references:
set_environment_variables: &set_environment_variables
run:
name: Set Environment Variables
command: |
echo 'export CI_SHA1=$CIRCLE_SHA1' >> ${BASH_ENV}
echo 'export CI_BRANCH=$CIRCLE_BRANCH' >> ${BASH_ENV}
echo 'export CI_BUILD_NUM=$CIRCLE_BUILD_NUM' >> ${BASH_ENV}
echo 'export CI_TAG=$CIRCLE_TAG' >> ${BASH_ENV}
echo 'export PUSH_ALL_VERSION_TAGS=true' >> ${BASH_ENV}
echo 'export GOPROXY=https://proxy.golang.org' >> ${BASH_ENV}
echo 'export GO111MODULE=on' >> ${BASH_ENV}
echo 'export GOFLAGS=-mod=mod' >> ${BASH_ENV}
echo 'export GORELEASER_CURRENT_TAG="${CIRCLE_TAG}"' >> $BASH_ENV
install_k8s: &install_k8s
run:
name: Install K8s
command: |
sudo apt-get update
echo "Installing git and jq"
sudo apt-get install -yqq jq git
echo "Installing KIND"
curl -sLO https://github.com/kubernetes-sigs/kind/releases/download/v0.19.0/kind-linux-amd64
chmod 0755 kind-linux-amd64
sudo mv kind-linux-amd64 /usr/local/bin/kind
kind version
echo "Installing Kubectl"
curl -sLO https://storage.googleapis.com/kubernetes-release/release/v1.25.10/bin/linux/amd64/kubectl
chmod 0755 kubectl
sudo mv kubectl /usr/local/bin/
kubectl version --client
echo "Creating Kubernetes Cluster with Kind"
kind create cluster --wait=90s --image kindest/node:v1.25.9@sha256:c08d6c52820aa42e533b70bce0c2901183326d86dcdcbedecc9343681db45161
docker ps -a
kubectl version
echo "Installing Helm"
curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3
chmod 700 get_helm.sh
./get_helm.sh
echo "Installing cert-manager"
helm repo add jetstack https://charts.jetstack.io
helm repo update
echo "Helm install"
helm install cert-manager jetstack/cert-manager --namespace cert-manager --version v1.12.1 --set "installCRDs=true" --wait --create-namespace
echo "Install cert-manager successful"
test_binary_dashboard: &test_binary_dashboard
run:
name: Test Dashboard
command: |
go run main.go dashboard --port 3000 --audit-path ./pkg/config/examples &
sleep 30
curl -f http://localhost:3000 > /dev/null
curl -f http://localhost:3000/health > /dev/null
curl -f http://localhost:3000/favicon.ico > /dev/null
curl -f http://localhost:3000/static/css/main.css > /dev/null
curl -f http://localhost:3000/results.json > /dev/null
curl -f http://localhost:3000/details/security > /dev/null
test_k8s: &test_k8s
run:
name: Test Kubernetes Deployments
command: |
if [[ -z $CIRCLE_PR_NUMBER ]]; then
./test/webhook_test.sh
./test/kube_dashboard_test.sh
else
echo "Skipping Kubernetes tests for forked PR"
fi
install_vault_alpine: &install_vault_alpine
install_vault_machine: &install_vault_machine
run:
name: install hashicorp vault
command: |
apk --update add curl yq
sudo apt-get update -y && sudo apt-get install -y curl unzip
cd /tmp
curl -LO https://releases.hashicorp.com/vault/1.13.2/vault_1.13.2_linux_amd64.zip
sha256sum vault_1.13.2_linux_amd64.zip | grep f7930279de8381de7c532164b4a4408895d9606c0d24e2e9d2f9acb5dfe99b3c
unzip vault_1.13.2_linux_amd64.zip
mv vault /usr/bin/vault
jobs:
test_k8s:
working_directory: ~/polaris
resource_class: medium
executor: vm
steps:
- checkout
- *set_environment_variables
- *install_k8s
- *test_k8s
curl -LO https://releases.hashicorp.com/vault/1.21.4/vault_1.21.4_linux_amd64.zip
echo '889b681990fe221b884b7932fa9c9dd0ee9811b9349554f1aa287ab63c9f3dae vault_1.21.4_linux_amd64.zip' | sha256sum -c
unzip -o vault_1.21.4_linux_amd64.zip
sudo mv vault /usr/bin/vault
setup_qemu_binfmt: &setup_qemu_binfmt
run:
name: Setup QEMU for multi-arch Docker builds
command: |
sudo apt-get update -y
sudo apt-get install -y qemu-user-static binfmt-support
docker buildx create --use || true
docker buildx inspect --bootstrap
e2e_configuration: &e2e_configuration
executor: golang-exec
pre_script: e2e/pre.sh
script: e2e/test.sh
command_runner_image: quay.io/reactiveops/ci-images:v14.1-bullseye
enable_docker_layer_caching: true
store-test-results: /tmp/test-results
attach-workspace: true
requires:
- test
- snapshot
filters:
branches:
only: /.*/
tags:
ignore: /.*/
executors:
golang-exec:
docker:
- image: cimg/go:1.26.7
jobs:
test:
docker:
- image: cimg/go:1.24.4
- image: cimg/go:1.26.7
steps:
- checkout
- *set_environment_variables
- run: go vet ./...
- run: go test ./... -coverprofile=coverage.txt -covermode=count
- *test_binary_dashboard
build_and_push:
working_directory: /go/src/github.com/fairwindsops/polaris/
- run:
name: Go Mod Download
command: go mod download && go mod verify
- run:
name: golangci-lint
command: |
curl -fsSL -o golangci-lint.tar.gz https://github.com/golangci/golangci-lint/releases/download/v2.12.2/golangci-lint-2.12.2-linux-amd64.tar.gz
echo '8df580d2670fed8fa984aac0507099af8df275e665215f5c7a2ae3943893a553 golangci-lint.tar.gz' | sha256sum -c
tar -xzf golangci-lint.tar.gz
mv golangci-lint-2.12.2-linux-amd64/golangci-lint "$(go env GOPATH)/bin/golangci-lint"
golangci-lint run --timeout 5m
- run:
name: test
command: |
go test -v -coverprofile=coverage.txt -covermode=atomic ./...
go vet ./...
- run:
name: Test Dashboard
command: ./test/dashboard_test.sh
snapshot:
machine:
image: ubuntu-2204:current
resource_class: large
steps:
- checkout
- *setup_qemu_binfmt
- run:
name: Run GoReleaser snapshot
command: |
docker run --rm \
-v /var/run/docker.sock:/var/run/docker.sock \
-v "$(pwd):/workspace" -w /workspace \
-e CIRCLE_SHA1 \
-e CIRCLE_BRANCH \
-e CIRCLE_TAG \
goreleaser/goreleaser:v2.17.1 release --snapshot --skip=sign
- run:
name: Save snapshot amd64 image for e2e
command: |
mkdir -p /tmp/workspace/docker_save
docker save us-docker.pkg.dev/fairwinds-ops/oss/polaris:${CIRCLE_SHA1}-amd64 > /tmp/workspace/docker_save/polaris_${CIRCLE_SHA1}-amd64.tar
- persist_to_workspace:
root: /tmp/workspace/
paths:
- docker_save
- store_artifacts:
path: dist
destination: snapshot
release:
machine:
image: ubuntu-2204:current
resource_class: large
shell: /bin/bash
docker:
# The goreleaser image tag determins the version of Go.
# Manually check goreleaser images for their version of Go.
# Ref: https://hub.docker.com/r/goreleaser/goreleaser/tags
- image: goreleaser/goreleaser:v2.10.2
steps:
- checkout
- setup_remote_docker
- *install_vault_alpine
- *install_vault_machine
- rok8s/get_vault_env:
vault_path: repo/global/env
- rok8s/get_vault_env:
vault_path: repo/polaris/env
- *set_environment_variables
- run: docker login quay.io -u="${FAIRWINDS_QUAY_USER}" -p="${FAIRWINDS_QUAY_TOKEN}"
- install_goreleaser_dependencies
- run: scripts/goreleaser.sh
- run:
name: docker login Google Artifact Registry
command: |
echo "$GCP_ARTIFACTREADWRITE_JSON_KEY" | base64 -d | docker login -u _json_key --password-stdin us-docker.pkg.dev
- *setup_qemu_binfmt
- run:
name: Run GoReleaser release
command: |
export GORELEASER_CURRENT_TAG="${CIRCLE_TAG}"
docker run --rm \
-v /var/run/docker.sock:/var/run/docker.sock \
-v "$(pwd):/workspace" -w /workspace \
-v "${HOME}/.docker:/root/.docker" \
-e GORELEASER_CURRENT_TAG \
-e CIRCLE_TAG \
-e CIRCLE_SHA1 \
-e GO111MODULE=on \
-e GITHUB_TOKEN \
-e VAULT_ADDR \
-e VAULT_TOKEN \
goreleaser/goreleaser:v2.17.1 release
workflows:
version: 2
release:
test_and_build:
jobs:
- test:
filters:
tags:
ignore: /.*/
- snapshot:
requires:
- test
filters:
branches:
only: /.*/
- build_and_push:
tags:
ignore: /.*/
- rok8s/kubernetes_e2e_tests:
name: "kubernetes e2e"
kind_node_image: "kindest/node:v1.34.0@sha256:7416a61b42b1662ca6ca89f02028ac133a309a2a30ba309614e8ec94d976dc5a"
<<: *e2e_configuration
release:
jobs:
- test:
filters:
branches:
ignore: /.*/
tags:
only: /v.*/
- release:
requires:
- test
context: org-global
filters:
branches:
ignore: /pull\/[0-9]+/
ignore: /.*/
tags:
ignore: /^testing-.*/
- test_k8s:
requires:
- build_and_push
filters:
branches:
ignore: /pull\/[0-9]+/
tags:
ignore: /^testing-.*/
only: /v.*/
- oss-docs/publish-docs:
requires:
- release
repository: polaris
filters:
branches:
ignore: /.*/
tags:
ignore: /^testing-.*/
only: /v.*/
+1 -1
View File
@@ -1,6 +1,6 @@
# The action uses an own Dockerfile on purpose because the root Dockerfile takes way too long to build for an action
FROM alpine:3.22
FROM alpine:3.24
RUN apk add --no-cache \
bash \
-20
View File
@@ -1,20 +0,0 @@
## DO NOT EDIT - Managed by Terraform
version: 2
updates:
- package-ecosystem: "docker"
directory: "/"
schedule:
interval: "weekly"
- package-ecosystem: "npm"
directory: "/docs"
schedule:
interval: "weekly"
open-pull-requests-limit: 0
ignore:
- dependency-name: "*"
- package-ecosystem: "gomod"
directory: "/"
schedule:
interval: "weekly"
+2 -2
View File
@@ -7,7 +7,7 @@ jobs:
build-int:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v6.0.2
- name: Setup polaris
uses: ./.github/actions/setup-polaris
with:
@@ -18,7 +18,7 @@ jobs:
build-ext:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v6.0.2
- name: Setup polaris
uses: fairwindsops/polaris/.github/actions/setup-polaris@master
with:
-2
View File
@@ -1,5 +1,3 @@
# goreleaser is run via a wrapper that creates .goreleaser.yml from .goreleaser.yml.envsubst
.goreleaser.yml
# dist
# # Binaries for programs and plugins
.go-version
+116
View File
@@ -0,0 +1,116 @@
## DO NOT EDIT - Managed by Terraform
# yaml-language-server: $$schema=https://goreleaser.com/static/schema.json
version: 2
project_name: polaris
before:
hooks:
- go mod download
builds:
- id: polaris
ldflags:
- -X main.Version={{.Version}} -X main.Commit={{.Commit}} -s -w
env:
- CGO_ENABLED=0
- GO111MODULE=on
goos:
- linux
- darwin
- windows
goarch:
- amd64
- arm
- arm64
goarm:
- "6"
- "7"
ignore:
- goos: windows
goarch: arm
- goos: windows
goarch: arm64
brews:
- name: polaris
repository:
owner: FairwindsOps
name: homebrew-tap
directory: Formula
description: Open Source Best Practices for Kubernetes
url_template: "https://github.com/FairwindsOps/polaris/releases/download/{{ .Tag }}/{{ .ArtifactName }}"
test: |
system "#{bin}/polaris version"
release:
disable: '{{ eq (envOrDefault "GORELEASER_SKIP_RELEASE" "false") "true" }}'
prerelease: auto
github:
owner: FairwindsOps
name: polaris
footer: |
You can verify the signatures of both the checksums.txt file and the published docker images using [cosign](https://github.com/sigstore/cosign).
```bash
cosign verify-blob checksums.txt --bundle=checksums.txt.sigstore.json --key https://artifacts.fairwinds.com/cosign-p256.pub
```
```bash
cosign verify us-docker.pkg.dev/fairwinds-ops/oss/polaris:{{ .Tag }} --key https://artifacts.fairwinds.com/cosign-p256.pub
```
checksum:
name_template: "checksums.txt"
changelog:
sort: asc
filters:
exclude:
- '^docs:'
- '^test:'
signs:
- cmd: cosign
signature: "${artifact}.sigstore.json"
args:
- "sign-blob"
- "--key=hashivault://cosign-p256"
- "--bundle=${signature}"
- "${artifact}"
- "--yes"
artifacts: all
docker_signs:
- artifacts: all
args: ["sign", "--key=hashivault://cosign-p256", "us-docker.pkg.dev/fairwinds-ops/oss/polaris@${digest}", "-r", "--yes"]
dockers:
- image_templates:
- "us-docker.pkg.dev/fairwinds-ops/oss/polaris:{{ .FullCommit }}-amd64"
- "us-docker.pkg.dev/fairwinds-ops/oss/polaris:{{ .Tag }}-amd64"
use: buildx
dockerfile: Dockerfile
build_flag_templates:
- "--platform=linux/amd64"
- image_templates:
- "us-docker.pkg.dev/fairwinds-ops/oss/polaris:{{ .FullCommit }}-arm64v8"
- "us-docker.pkg.dev/fairwinds-ops/oss/polaris:{{ .Tag }}-arm64v8"
use: buildx
goarch: arm64
goos: linux
dockerfile: Dockerfile
build_flag_templates:
- "--platform=linux/arm64/v8"
- image_templates:
- "us-docker.pkg.dev/fairwinds-ops/oss/polaris:{{ .FullCommit }}-armv7"
- "us-docker.pkg.dev/fairwinds-ops/oss/polaris:{{ .Tag }}-armv7"
use: buildx
goarch: arm
goarm: 7
goos: linux
dockerfile: Dockerfile
build_flag_templates:
- "--platform=linux/arm/v7"
docker_manifests:
- name_template: us-docker.pkg.dev/fairwinds-ops/oss/polaris:{{ .Tag }}
image_templates:
- "us-docker.pkg.dev/fairwinds-ops/oss/polaris:{{ .Tag }}-amd64"
- "us-docker.pkg.dev/fairwinds-ops/oss/polaris:{{ .Tag }}-arm64v8"
- "us-docker.pkg.dev/fairwinds-ops/oss/polaris:{{ .Tag }}-armv7"
- name_template: us-docker.pkg.dev/fairwinds-ops/oss/polaris:{{ .FullCommit }}
image_templates:
- "us-docker.pkg.dev/fairwinds-ops/oss/polaris:{{ .FullCommit }}-amd64"
- "us-docker.pkg.dev/fairwinds-ops/oss/polaris:{{ .FullCommit }}-arm64v8"
- "us-docker.pkg.dev/fairwinds-ops/oss/polaris:{{ .FullCommit }}-armv7"
-114
View File
@@ -1,114 +0,0 @@
version: 2
checksum:
name_template: 'checksums.txt'
changelog:
sort: asc
filters:
exclude:
- '^docs:'
- '^test:'
env:
- GOBIN={{ .Env.TMPDIR }}/go-bin
before:
hooks:
- go mod download
builds:
- id: polaris
ldflags:
- -X main.Version={{.Version}} -X main.Commit={{.Commit}} -s -w
env:
- CGO_ENABLED=0
- GO111MODULE=on
goos:
- linux
- darwin
- windows
goarch:
- amd64
- arm
- arm64
goarm:
- 6
- 7
archives:
- id: polaris
builds: ["polaris"]
name_template: "{{ .ProjectName }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}{{ if .Mips }}_{{ .Mips }}{{ end }}"
signs:
- cmd: cosign
args: ["sign-blob", "--key=hashivault://cosign", "-output-signature=${signature}", "${artifact}"]
artifacts: checksum
release:
# This is replaced using `envsubst`, depending on the git branch.
disable: ${skip_release}
prerelease: auto
footer: |
You can verify the signature of the checksums.txt file using [cosign](https://github.com/sigstore/cosign).
```
cosign verify-blob checksums.txt --signature=checksums.txt.sig --key https://artifacts.fairwinds.com/cosign.pub
```
brews:
- name: polaris
# This is replaced using `envsubst`, depending on the git branch.
skip_upload: ${skip_release}
repository:
owner: FairwindsOps
name: homebrew-tap
directory: Formula
description: Open Source Best Practices for Kubernetes
url_template: "https://github.com/FairwindsOps/polaris/releases/download/{{ .Tag }}/{{ .ArtifactName }}"
test: |
system "#{bin}/polaris version"
dockers:
# There are multiple images to match the `--platform` docker build flag with
# combinations of `GOOS`, `GOARCH`, and `GOARM`
- image_templates:
- "quay.io/fairwinds/polaris:{{ .FullCommit }}-amd64"
use: buildx
build_flag_templates:
- "--platform=linux/amd64"
- image_templates:
- "quay.io/fairwinds/polaris:{{ .FullCommit }}-arm64"
use: buildx
goarch: arm64
goos: linux
build_flag_templates:
- "--platform=linux/arm64"
docker_manifests:
# Create DOcker manifests that make multiple architectures available within a tag,
# and provide partial-version tags like 2, and 2.2.
- name_template: quay.io/fairwinds/polaris:{{ .FullCommit }}
image_templates:
- "quay.io/fairwinds/polaris:{{ .FullCommit }}-amd64"
- "quay.io/fairwinds/polaris:{{ .FullCommit }}-arm64"
- name_template: quay.io/fairwinds/polaris:{{ .Env.feature_docker_tag }}
# This is replaced using `envsubst`, depending on the git branch.
skip_push: ${skip_feature_docker_tags}
image_templates:
- "quay.io/fairwinds/polaris:{{ .FullCommit }}-amd64"
- "quay.io/fairwinds/polaris:{{ .FullCommit }}-arm64"
- name_template: quay.io/fairwinds/polaris:latest
# This is replaced using `envsubst`, depending on the git branch.
skip_push: ${skip_release}
image_templates:
- "quay.io/fairwinds/polaris:{{ .FullCommit }}-amd64"
- "quay.io/fairwinds/polaris:{{ .FullCommit }}-arm64"
- name_template: quay.io/fairwinds/polaris:{{ .Tag }}
# This is replaced using `envsubst`, depending on the git branch.
skip_push: ${skip_release}
image_templates:
- "quay.io/fairwinds/polaris:{{ .FullCommit }}-amd64"
- "quay.io/fairwinds/polaris:{{ .FullCommit }}-arm64"
- name_template: quay.io/fairwinds/polaris:{{ .Major }}
# This is replaced using `envsubst`, depending on the git branch.
skip_push: ${skip_release}
image_templates:
- "quay.io/fairwinds/polaris:{{ .FullCommit }}-amd64"
- "quay.io/fairwinds/polaris:{{ .FullCommit }}-arm64"
- name_template: quay.io/fairwinds/polaris:{{ .Major }}.{{ .Minor }}
# This is replaced using `envsubst`, depending on the git branch.
skip_push: ${skip_release}
image_templates:
- "quay.io/fairwinds/polaris:{{ .FullCommit }}-amd64"
- "quay.io/fairwinds/polaris:{{ .FullCommit }}-arm64"
+2 -2
View File
@@ -1,4 +1,4 @@
FROM alpine:3.22
FROM alpine:3.24.1
LABEL org.opencontainers.image.authors="FairwindsOps, Inc." \
org.opencontainers.image.vendor="FairwindsOps, Inc." \
@@ -10,7 +10,7 @@ LABEL org.opencontainers.image.authors="FairwindsOps, Inc." \
org.opencontainers.image.licenses="Apache License 2.0"
WORKDIR /usr/local/bin
RUN apk -U upgrade
# Install ca-certs
RUN apk --no-cache add ca-certificates
RUN addgroup -S polaris && adduser -u 1200 -S polaris -G polaris
+38
View File
@@ -29,6 +29,44 @@ Polaris can be run in three different modes:
## Documentation
Check out the [documentation at docs.fairwinds.com](https://polaris.docs.fairwinds.com)
## Notice: Registry Migration and Immutable Images (v10.1.8 → v10.2.0)
Starting with **v10.2.0**:
- Images moved to `us-docker.pkg.dev/fairwinds-ops/oss/polaris`
- `quay.io/fairwinds/polaris` is deprecated
### Required action
```diff
- quay.io/fairwinds/polaris:<tag>
+ us-docker.pkg.dev/fairwinds-ops/oss/polaris:<tag>
```
---
## Immutable and signed images
* Images are now **signed**
* Tags are **immutable**
* No more floating tags:
* `v10`
* `v10.1`
* `latest`
Use full version tags:
```
us-docker.pkg.dev/fairwinds-ops/oss/polaris:v<major>.<minor>.<patch>
```
Or pin by digest:
```
us-docker.pkg.dev/fairwinds-ops/oss/polaris@sha256:<digest>
```
<!-- Begin boilerplate -->
## Join the Fairwinds Open Source Community
+19 -10
View File
@@ -189,17 +189,18 @@ func outputAudit(auditData validator.AuditData, outputFile, outputURL, outputFor
var outputBytes []byte
var err error
if outputFormat == "score" {
outputBytes = []byte(fmt.Sprintf("%d\n", auditData.GetSummary().GetScore()))
} else if outputFormat == "yaml" {
switch outputFormat {
case "score":
outputBytes = fmt.Appendf(nil, "%d\n", auditData.GetSummary().GetScore())
case "yaml":
var jsonBytes []byte
jsonBytes, err = json.Marshal(auditData)
if err == nil {
outputBytes, err = yaml.JSONToYAML(jsonBytes)
}
} else if outputFormat == "pretty" {
case "pretty":
outputBytes = []byte(auditData.GetPrettyOutput(useColor))
} else {
default:
outputBytes, err = json.MarshalIndent(auditData, "", " ")
}
if err != nil {
@@ -207,7 +208,10 @@ func outputAudit(auditData validator.AuditData, outputFile, outputURL, outputFor
os.Exit(1)
}
if outputURL == "" && outputFile == "" {
os.Stdout.Write(outputBytes)
if _, err := os.Stdout.Write(outputBytes); err != nil {
logrus.Errorf("Error writing audit to stdout: %v", err)
os.Exit(1)
}
} else {
if outputURL != "" {
req, err := http.NewRequest("POST", outputURL, bytes.NewBuffer(outputBytes))
@@ -217,11 +221,12 @@ func outputAudit(auditData validator.AuditData, outputFile, outputURL, outputFor
os.Exit(1)
}
if outputFormat == "json" {
switch outputFormat {
case "json":
req.Header.Set("Content-Type", "application/json")
} else if outputFormat == "yaml" {
case "yaml":
req.Header.Set("Content-Type", "application/x-yaml")
} else {
default:
req.Header.Set("Content-Type", "text/plain")
}
@@ -236,7 +241,11 @@ func outputAudit(auditData validator.AuditData, outputFile, outputURL, outputFor
os.Exit(1)
}
defer resp.Body.Close()
defer func() {
if err := resp.Body.Close(); err != nil {
logrus.Errorf("Error closing response body: %v", err)
}
}()
body, err := io.ReadAll(resp.Body)
+3 -1
View File
@@ -61,7 +61,9 @@ var dashboardCmd = &cobra.Command{
}
router.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("OK"))
if _, err := w.Write([]byte("OK")); err != nil {
logrus.Errorf("Error writing health response: %v", err)
}
})
http.Handle("/", router)
+3 -1
View File
@@ -48,7 +48,9 @@ var fixCommand = &cobra.Command{
if err != nil {
if errors.Is(err, fix.ErrFilesPathRequired) {
logrus.Error("Please specify a files-path flag")
cmd.Help()
if helpErr := cmd.Help(); helpErr != nil {
logrus.Error(helpErr)
}
os.Exit(1)
}
logrus.Fatal(err)
+3 -2
View File
@@ -29,7 +29,6 @@ var (
disallowExemptions bool
disallowConfigExemptions bool
disallowAnnotationExemptions bool
fixChecks bool
logLevel string
auditPath string
displayName string
@@ -88,7 +87,9 @@ var rootCmd = &cobra.Command{
},
PersistentPostRun: func(cmd *cobra.Command, args []string) {
if !strings.HasPrefix(cmd.Use, "audit") {
os.Stderr.WriteString("\n\nWant more? Automate Polaris for free with Fairwinds Insights!\n🚀 https://fairwinds.com/insights-signup/polaris 🚀 \n")
if _, err := os.Stderr.WriteString("\n\nWant more? Automate Polaris for free with Fairwinds Insights!\n🚀 https://fairwinds.com/insights-signup/polaris 🚀 \n"); err != nil {
logrus.Error(err)
}
}
},
}
+3 -3
View File
@@ -46,11 +46,11 @@ go test ./pkg/... -v -coverprofile cover.out
### Webhook tests
```bash
kind create cluster --wait=90s --image kindest/node:v1.15.11 --name polaris-test
docker build -t quay.io/fairwinds/polaris:debug . # or use your own registry
docker push quay.io/fairwinds/polaris:debug
docker build -t us-docker.pkg.dev/fairwinds-ops/oss/polaris:debug . # or use your own registry
docker push us-docker.pkg.dev/fairwinds-ops/oss/polaris:debug
helm repo add jetstack https://charts.jetstack.io
helm install cert-manager jetstack/cert-manager --namespace cert-manager --version v1.12.1 --set "installCRDs=true" --wait
POLARIS_IMAGE=quay.io/fairwinds/polaris:debug ./test/webhook_test.sh
POLARIS_IMAGE=us-docker.pkg.dev/fairwinds-ops/oss/polaris:debug ./test/webhook_test.sh
```
to avoid the final cleanup for debugging purposes, you can run
```bash
+1 -1
View File
@@ -42,7 +42,7 @@ polaris dashboard --port 8080 --audit-path=./deploy/
### Local Docker container
```
docker run -d -p8080:8080 -v ~/.kube/config:/opt/app/config:ro quay.io/fairwinds/polaris:1.2 polaris dashboard --kubeconfig /opt/app/config
docker run -d -p8080:8080 -v ~/.kube/config:/opt/app/config:ro us-docker.pkg.dev/fairwinds-ops/oss/polaris:1.2 polaris dashboard --kubeconfig /opt/app/config
```
## Using the Dashboard
Executable
+35
View File
@@ -0,0 +1,35 @@
#!/bin/bash
set -euo pipefail
KIND_VERSION=v0.30.0
if [ -z "${CI_SHA1:-}" ]; then
echo "CI_SHA1 not set"
exit 1
fi
echo "CI_SHA1: ${CI_SHA1}"
tar="/tmp/workspace/docker_save/polaris_${CI_SHA1}-amd64.tar"
if [ ! -f "$tar" ]; then
echo "Missing snapshot image at ${tar}"
exit 1
fi
if ! command -v kind > /dev/null; then
echo "Installing kind ${KIND_VERSION}"
bindir="$(pwd)/bin-kind"
mkdir -p "$bindir"
curl -fsSLo "$bindir/kind" \
"https://github.com/kubernetes-sigs/kind/releases/download/${KIND_VERSION}/kind-linux-amd64"
chmod +x "$bindir/kind"
export PATH="$bindir:$PATH"
fi
kind version
docker load --input "$tar"
docker tag "us-docker.pkg.dev/fairwinds-ops/oss/polaris:${CI_SHA1}-amd64" \
"us-docker.pkg.dev/fairwinds-ops/oss/polaris:${CI_SHA1}"
kind load docker-image --name e2e "us-docker.pkg.dev/fairwinds-ops/oss/polaris:${CI_SHA1}"
docker cp . e2e-command-runner:/polaris
Executable
+23
View File
@@ -0,0 +1,23 @@
#!/bin/bash
set -euo pipefail
mkdir -p /tmp/test-results
if [[ -n "${CIRCLE_PR_NUMBER:-}" ]]; then
echo "Skipping Kubernetes tests for forked PR"
exit 0
fi
cd /polaris
helm repo add jetstack https://charts.jetstack.io
helm repo update
helm install cert-manager jetstack/cert-manager \
--namespace cert-manager \
--version v1.12.1 \
--set installCRDs=true \
--wait \
--create-namespace
./test/webhook_test.sh
./test/kube_dashboard_test.sh
+1 -1
View File
@@ -4,4 +4,4 @@ options:
images:
docker:
- quay.io/fairwinds/polaris:$CI_SHA1
- us-docker.pkg.dev/fairwinds-ops/oss/polaris:$CI_SHA1
+49 -40
View File
@@ -1,24 +1,24 @@
module github.com/fairwindsops/polaris
go 1.24.4
go 1.26.2
require (
github.com/fairwindsops/controller-utils v0.3.4
github.com/fatih/color v1.18.0
github.com/fatih/color v1.19.0
github.com/gorilla/mux v1.8.1
github.com/pkg/errors v0.9.1
github.com/qri-io/jsonpointer v0.1.1
github.com/qri-io/jsonschema v0.2.1
github.com/sirupsen/logrus v1.9.3
github.com/spf13/cobra v1.9.1
github.com/stretchr/testify v1.10.0
github.com/sirupsen/logrus v1.10.1
github.com/spf13/cobra v1.10.2
github.com/stretchr/testify v1.12.1
github.com/thoas/go-funk v0.9.3
gomodules.xyz/jsonpatch/v2 v2.5.0
gopkg.in/yaml.v3 v3.0.1
k8s.io/api v0.33.3
k8s.io/apimachinery v0.33.3
k8s.io/client-go v0.33.1
sigs.k8s.io/controller-runtime v0.21.0
k8s.io/api v0.36.4
k8s.io/apimachinery v0.36.4
k8s.io/client-go v0.36.4
sigs.k8s.io/controller-runtime v0.24.1
sigs.k8s.io/yaml v1.6.0
)
@@ -26,52 +26,61 @@ require (
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/emicklei/go-restful/v3 v3.12.2 // indirect
github.com/emicklei/go-restful/v3 v3.13.0 // indirect
github.com/evanphx/json-patch v5.9.0+incompatible // indirect
github.com/evanphx/json-patch/v5 v5.9.11 // indirect
github.com/fsnotify/fsnotify v1.9.0 // indirect
github.com/fxamacker/cbor/v2 v2.8.0 // indirect
github.com/fxamacker/cbor/v2 v2.9.0 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-openapi/jsonpointer v0.21.1 // indirect
github.com/go-openapi/jsonreference v0.21.0 // indirect
github.com/go-openapi/swag v0.23.1 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/google/gnostic-models v0.6.9 // indirect
github.com/google/go-cmp v0.7.0 // indirect
github.com/go-openapi/jsonpointer v0.22.5 // indirect
github.com/go-openapi/jsonreference v0.21.5 // indirect
github.com/go-openapi/swag v0.25.5 // indirect
github.com/go-openapi/swag/cmdutils v0.25.5 // indirect
github.com/go-openapi/swag/conv v0.25.5 // indirect
github.com/go-openapi/swag/fileutils v0.25.5 // indirect
github.com/go-openapi/swag/jsonname v0.25.5 // indirect
github.com/go-openapi/swag/jsonutils v0.25.5 // indirect
github.com/go-openapi/swag/loading v0.25.5 // indirect
github.com/go-openapi/swag/mangling v0.25.5 // indirect
github.com/go-openapi/swag/netutils v0.25.5 // indirect
github.com/go-openapi/swag/stringutils v0.25.5 // indirect
github.com/go-openapi/swag/typeutils v0.25.5 // indirect
github.com/go-openapi/swag/yamlutils v0.25.5 // indirect
github.com/google/gnostic-models v0.7.1 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/mailru/easyjson v0.9.0 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // 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/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/prometheus/client_golang v1.22.0 // indirect
github.com/prometheus/client_golang v1.23.2 // indirect
github.com/prometheus/client_model v0.6.2 // indirect
github.com/prometheus/common v0.64.0 // indirect
github.com/prometheus/procfs v0.16.1 // indirect
github.com/samber/lo v1.51.0 // indirect
github.com/spf13/pflag v1.0.6 // indirect
github.com/prometheus/common v0.67.5 // indirect
github.com/prometheus/procfs v0.20.1 // indirect
github.com/samber/lo v1.53.0 // indirect
github.com/spf13/pflag v1.0.10 // indirect
github.com/x448/float16 v0.8.4 // indirect
go.yaml.in/yaml/v2 v2.4.2 // indirect
golang.org/x/net v0.41.0 // indirect
golang.org/x/oauth2 v0.30.0 // indirect
golang.org/x/sys v0.34.0 // indirect
golang.org/x/term v0.33.0 // indirect
golang.org/x/text v0.26.0 // indirect
golang.org/x/time v0.12.0 // indirect
google.golang.org/protobuf v1.36.6 // indirect
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
go.yaml.in/yaml/v2 v2.4.4 // indirect
go.yaml.in/yaml/v3 v3.0.5 // indirect
golang.org/x/net v0.56.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/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
gopkg.in/inf.v0 v0.9.1 // indirect
k8s.io/apiextensions-apiserver v0.33.1 // indirect
k8s.io/klog/v2 v2.130.1 // indirect
k8s.io/kube-openapi v0.0.0-20250610211856-8b98d1ed966a // indirect
k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 // indirect
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect
k8s.io/apiextensions-apiserver v0.36.0 // indirect
k8s.io/klog/v2 v2.140.0 // indirect
k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a // indirect
k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2 // indirect
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect
sigs.k8s.io/randfill v1.0.0 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.7.0 // indirect
sigs.k8s.io/structured-merge-diff/v6 v6.3.3 // indirect
)
+123 -132
View File
@@ -1,28 +1,29 @@
github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=
github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
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/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
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/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/emicklei/go-restful/v3 v3.12.2 h1:DhwDP0vY3k8ZzE0RunuJy8GhNpPL6zqLkDf9B/a0/xU=
github.com/emicklei/go-restful/v3 v3.12.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
github.com/emicklei/go-restful/v3 v3.13.0 h1:C4Bl2xDndpU6nJ4bc1jXd+uTmYPVUwkD6bFY/oTyCes=
github.com/emicklei/go-restful/v3 v3.13.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
github.com/evanphx/json-patch v5.9.0+incompatible h1:fBXyNpNMuTTDdquAq/uisOr2lShz4oaXpDTX2bLe7ls=
github.com/evanphx/json-patch v5.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/evanphx/json-patch/v5 v5.9.11 h1:/8HVnzMq13/3x9TPvjG08wUGqBTmZBsCWzjTM0wiaDU=
github.com/evanphx/json-patch/v5 v5.9.11/go.mod h1:3j+LviiESTElxA4p3EMKAB9HXj3/XEtnUf6OZxqIQTM=
github.com/fairwindsops/controller-utils v0.3.4 h1:t1qulL2GVDVUJTIE4icpBy3KnsxFTavnNAbFnd60blc=
github.com/fairwindsops/controller-utils v0.3.4/go.mod h1:9/hOHX70/LG40RgtFAjtXFiMWEpItqm6Scf+obRFB2Y=
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
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/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/fxamacker/cbor/v2 v2.8.0 h1:fFtUGXUzXPHTIUdne5+zzMPTfffl3RD5qYnkY40vtxU=
github.com/fxamacker/cbor/v2 v2.8.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM=
github.com/fxamacker/cbor/v2 v2.9.0/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=
@@ -30,40 +31,59 @@ 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.21.1 h1:whnzv/pNXtK2FbX/W9yJfRmE2gsmkfahjMKB0fZvcic=
github.com/go-openapi/jsonpointer v0.21.1/go.mod h1:50I1STOfbY1ycR8jGz8DaMeLCdXiI6aDteEdRNNzpdk=
github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ=
github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4=
github.com/go-openapi/swag v0.23.1 h1:lpsStH0n2ittzTnbaSloVZLuB5+fvSY/+hnagBjSNZU=
github.com/go-openapi/swag v0.23.1/go.mod h1:STZs8TbRvEQQKUA+JZNAm3EWlgaOBGpyFDqQnDHMef0=
github.com/go-openapi/jsonpointer v0.22.5 h1:8on/0Yp4uTb9f4XvTrM2+1CPrV05QPZXu+rvu2o9jcA=
github.com/go-openapi/jsonpointer v0.22.5/go.mod h1:gyUR3sCvGSWchA2sUBJGluYMbe1zazrYWIkWPjjMUY0=
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.25.5 h1:pNkwbUEeGwMtcgxDr+2GBPAk4kT+kJ+AaB+TMKAg+TU=
github.com/go-openapi/swag v0.25.5/go.mod h1:B3RT6l8q7X803JRxa2e59tHOiZlX1t8viplOcs9CwTA=
github.com/go-openapi/swag/cmdutils v0.25.5 h1:yh5hHrpgsw4NwM9KAEtaDTXILYzdXh/I8Whhx9hKj7c=
github.com/go-openapi/swag/cmdutils v0.25.5/go.mod h1:pdae/AFo6WxLl5L0rq87eRzVPm/XRHM3MoYgRMvG4A0=
github.com/go-openapi/swag/conv v0.25.5 h1:wAXBYEXJjoKwE5+vc9YHhpQOFj2JYBMF2DUi+tGu97g=
github.com/go-openapi/swag/conv v0.25.5/go.mod h1:CuJ1eWvh1c4ORKx7unQnFGyvBbNlRKbnRyAvDvzWA4k=
github.com/go-openapi/swag/fileutils v0.25.5 h1:B6JTdOcs2c0dBIs9HnkyTW+5gC+8NIhVBUwERkFhMWk=
github.com/go-openapi/swag/fileutils v0.25.5/go.mod h1:V3cT9UdMQIaH4WiTrUc9EPtVA4txS0TOmRURmhGF4kc=
github.com/go-openapi/swag/jsonname v0.25.5 h1:8p150i44rv/Drip4vWI3kGi9+4W9TdI3US3uUYSFhSo=
github.com/go-openapi/swag/jsonname v0.25.5/go.mod h1:jNqqikyiAK56uS7n8sLkdaNY/uq6+D2m2LANat09pKU=
github.com/go-openapi/swag/jsonutils v0.25.5 h1:XUZF8awQr75MXeC+/iaw5usY/iM7nXPDwdG3Jbl9vYo=
github.com/go-openapi/swag/jsonutils v0.25.5/go.mod h1:48FXUaz8YsDAA9s5AnaUvAmry1UcLcNVWUjY42XkrN4=
github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.5 h1:SX6sE4FrGb4sEnnxbFL/25yZBb5Hcg1inLeErd86Y1U=
github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.5/go.mod h1:/2KvOTrKWjVA5Xli3DZWdMCZDzz3uV/T7bXwrKWPquo=
github.com/go-openapi/swag/loading v0.25.5 h1:odQ/umlIZ1ZVRteI6ckSrvP6e2w9UTF5qgNdemJHjuU=
github.com/go-openapi/swag/loading v0.25.5/go.mod h1:I8A8RaaQ4DApxhPSWLNYWh9NvmX2YKMoB9nwvv6oW6g=
github.com/go-openapi/swag/mangling v0.25.5 h1:hyrnvbQRS7vKePQPHHDso+k6CGn5ZBs5232UqWZmJZw=
github.com/go-openapi/swag/mangling v0.25.5/go.mod h1:6hadXM/o312N/h98RwByLg088U61TPGiltQn71Iw0NY=
github.com/go-openapi/swag/netutils v0.25.5 h1:LZq2Xc2QI8+7838elRAaPCeqJnHODfSyOa7ZGfxDKlU=
github.com/go-openapi/swag/netutils v0.25.5/go.mod h1:lHbtmj4m57APG/8H7ZcMMSWzNqIQcu0RFiXrPUara14=
github.com/go-openapi/swag/stringutils v0.25.5 h1:NVkoDOA8YBgtAR/zvCx5rhJKtZF3IzXcDdwOsYzrB6M=
github.com/go-openapi/swag/stringutils v0.25.5/go.mod h1:PKK8EZdu4QJq8iezt17HM8RXnLAzY7gW0O1KKarrZII=
github.com/go-openapi/swag/typeutils v0.25.5 h1:EFJ+PCga2HfHGdo8s8VJXEVbeXRCYwzzr9u4rJk7L7E=
github.com/go-openapi/swag/typeutils v0.25.5/go.mod h1:itmFmScAYE1bSD8C4rS0W+0InZUBrB2xSPbWt6DLGuc=
github.com/go-openapi/swag/yamlutils v0.25.5 h1:kASCIS+oIeoc55j28T4o8KwlV2S4ZLPT6G0iq2SSbVQ=
github.com/go-openapi/swag/yamlutils v0.25.5/go.mod h1:Gek1/SjjfbYvM+Iq4QGwa/2lEXde9n2j4a3wI3pNuOQ=
github.com/go-openapi/testify/enable/yaml/v2 v2.4.0 h1:7SgOMTvJkM8yWrQlU8Jm18VeDPuAvB/xWrdxFJkoFag=
github.com/go-openapi/testify/enable/yaml/v2 v2.4.0/go.mod h1:14iV8jyyQlinc9StD7w1xVPW3CO3q1Gj04Jy//Kw4VM=
github.com/go-openapi/testify/v2 v2.4.0 h1:8nsPrHVCWkQ4p8h1EsRVymA2XABB4OT40gcvAu+voFM=
github.com/go-openapi/testify/v2 v2.4.0/go.mod h1:HCPmvFFnheKK2BuwSA0TbbdxJ3I16pjwMkYkP4Ywn54=
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/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
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/gnostic-models v0.6.9 h1:MU/8wDLif2qCXZmzncUQ/BOfxWfthHi63KqpoNbWqVw=
github.com/google/gnostic-models v0.6.9/go.mod h1:CiWsm0s6BSQd1hRn8/QmxqB6BesYcbSZxsz9b0KuDBw=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
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.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-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgYQBbFN4U4JNXUNYpxael3UzMyo=
github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
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/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
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/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
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/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
@@ -72,8 +92,6 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4=
github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
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=
@@ -81,149 +99,122 @@ github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D
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=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=
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.22.0 h1:Yed107/8DjTr0lKCNt7Dn8yQ6ybuDRQoMGrNFKzMfHg=
github.com/onsi/ginkgo/v2 v2.22.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo=
github.com/onsi/gomega v1.36.1 h1:bJDPBO7ibjxcbHMgSCoo4Yj18UWbKDlLwX1x9sybDcw=
github.com/onsi/gomega v1.36.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog=
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.39.0 h1:y2ROC3hKFmQZJNFeGAMeHZKkjBL65mIZcvrLQBF9k6Q=
github.com/onsi/gomega v1.39.0/go.mod h1:ZCU1pkQcXDO5Sl9/VVEGlDyp+zm0m1cmeG5TOzLgdh4=
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=
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.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q=
github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0=
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_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.64.0 h1:pdZeA+g617P7oGv1CzdTzyeShxAGrTBsolKNOLQPGO4=
github.com/prometheus/common v0.64.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8=
github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=
github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=
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/qri-io/jsonpointer v0.1.1 h1:prVZBZLL6TW5vsSB9fFHFAMBLI4b0ri5vribQlTJiBA=
github.com/qri-io/jsonpointer v0.1.1/go.mod h1:DnJPaYgiKu56EuDp8TU5wFLdZIcAnb/uH9v37ZaMV64=
github.com/qri-io/jsonschema v0.2.1 h1:NNFoKms+kut6ABPf6xiKNM5214jzxAhDBrPHCJ97Wg0=
github.com/qri-io/jsonschema v0.2.1/go.mod h1:g7DPkiOsK1xv6T/Ao5scXRkd+yTFygcANPBaaqW+VrI=
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
github.com/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/samber/lo v1.51.0 h1:kysRYLbHy/MB7kQZf5DSN50JHmMsNEdeY24VzJFu7wI=
github.com/samber/lo v1.51.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRoM0=
github.com/samber/lo v1.53.0 h1:t975lj2py4kJPQ6haz1QMgtId2gtmfktACxIXArw3HM=
github.com/samber/lo v1.53.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRoM0=
github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
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/sirupsen/logrus v1.10.1 h1:xi4336Zh11WpU14fXR6I67V3yaTPQYwRx2WEtHbRg4Q=
github.com/sirupsen/logrus v1.10.1/go.mod h1:vsQHnG7xzNsxk3NrwboUiWPnIC3dmbjcGPykD7+tiHk=
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.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.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.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=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
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=
go.uber.org/multierr v1.11.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/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/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/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
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.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
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.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8=
golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
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.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
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/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/mod v0.37.0 h1:vF1DjpVEshcIqoEaauuHebaLk1O1forxjxBaVn884JQ=
golang.org/x/mod v0.37.0/go.mod h1:m8S8VeM9r4dzDwjrKO0a1sZP3YjeMamRRlD+fmR2Q/0=
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/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/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/term v0.33.0 h1:NuFncQrRcaRvVmgRkvM3j/F00gWIAlcmlB8ACEKmGIg=
golang.org/x/term v0.33.0/go.mod h1:s18+ql9tYWp1IfpV9DmCtQDDSRBUjKaw9M1eAv5UeF0=
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.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.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.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc=
golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI=
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=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
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.39.0 h1:UbZz4pLOvn600D6Oh6GGEI6VAmndrEBLv8/6BEXzyus=
golang.org/x/text v0.39.0/go.mod h1:3UwRclnC2g0TU9x8PZiyfOajCd1zaUNHF9cvqcQZ+ZM=
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.47.0 h1:7Kn5x/d1svx/PzryTsqeoZN4TZwqeH5pGWjefhLi/1Q=
golang.org/x/tools v0.47.0/go.mod h1:dFHnyTvFWY212G+h7ZY4Vsp/K3U4/7W9TyVaAul8uCA=
gomodules.xyz/jsonpatch/v2 v2.5.0 h1:JELs8RLM12qJGXU4u/TO3V25KW8GreMKl9pdkk14RM0=
gomodules.xyz/jsonpatch/v2 v2.5.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY=
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
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=
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.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4=
gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M=
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=
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.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/api v0.33.3 h1:SRd5t//hhkI1buzxb288fy2xvjubstenEKL9K51KBI8=
k8s.io/api v0.33.3/go.mod h1:01Y/iLUjNBM3TAvypct7DIj0M0NIZc+PzAHCIo0CYGE=
k8s.io/apiextensions-apiserver v0.33.1 h1:N7ccbSlRN6I2QBcXevB73PixX2dQNIW0ZRuguEE91zI=
k8s.io/apiextensions-apiserver v0.33.1/go.mod h1:uNQ52z1A1Gu75QSa+pFK5bcXc4hq7lpOXbweZgi4dqA=
k8s.io/apimachinery v0.33.3 h1:4ZSrmNa0c/ZpZJhAgRdcsFcZOw1PQU1bALVQ0B3I5LA=
k8s.io/apimachinery v0.33.3/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM=
k8s.io/client-go v0.33.1 h1:ZZV/Ks2g92cyxWkRRnfUDsnhNn28eFpt26aGc8KbXF4=
k8s.io/client-go v0.33.1/go.mod h1:JAsUrl1ArO7uRVFWfcj6kOomSlCv+JpvIsp6usAGefA=
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
k8s.io/kube-openapi v0.0.0-20250610211856-8b98d1ed966a h1:ZV3Zr+/7s7aVbjNGICQt+ppKWsF1tehxggNfbM7XnG8=
k8s.io/kube-openapi v0.0.0-20250610211856-8b98d1ed966a/go.mod h1:5jIi+8yX4RIb8wk3XwBo5Pq2ccx4FP10ohkbSKCZoK8=
k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 h1:hwvWFiBzdWw1FhfY1FooPn3kzWuJ8tmbZBHi4zVsl1Y=
k8s.io/utils v0.0.0-20250604170112-4c0f3b243397/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
sigs.k8s.io/controller-runtime v0.21.0 h1:CYfjpEuicjUecRk+KAeyYh+ouUBn4llGyDYytIGcJS8=
sigs.k8s.io/controller-runtime v0.21.0/go.mod h1:OSg14+F65eWqIu4DceX7k/+QRAbTTvxeQSNSOQpukWM=
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE=
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg=
sigs.k8s.io/randfill v0.0.0-20250304075658-069ef1bbf016/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
k8s.io/api v0.36.4 h1:RxrvqCL6vgH5/+UnTeu1IIFqYmGfy0hnyrod1rn35Oo=
k8s.io/api v0.36.4/go.mod h1:S2B3orCFBDhrgyWbLeuKcT2QdHIpQesBkCYSlWtwUOw=
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.4 h1:PT2UzkupGuAx/+xT5XjiMJ1WGpY3fn9/hdAvjweRet4=
k8s.io/apimachinery v0.36.4/go.mod h1:p2I2dipt7JHG+quVwQ1d02d28O4GdDi77RByQ13MTpk=
k8s.io/client-go v0.36.4 h1:MDvfDNvMSt0Br94SK8neviVlwL9qifw9B26hJCpD1K0=
k8s.io/client-go v0.36.4/go.mod h1:pNK4WKELbwlEDvtbE8l22lEZL5THYF61H5EealokZmA=
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-20260317180543-43fb72c5454a h1:xCeOEAOoGYl2jnJoHkC3hkbPJgdATINPMAxaynU2Ovg=
k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a/go.mod h1:uGBT7iTA6c6MvqUvSXIaYZo9ukscABYi2btjhvgKGZ0=
k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2 h1:AZYQSJemyQB5eRxqcPky+/7EdBj0xi3g0ZcxxJ7vbWU=
k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk=
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/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/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.7.0 h1:qPeWmscJcXP0snki5IYF79Z8xrl8ETFxgMd7wez1XkI=
sigs.k8s.io/structured-merge-diff/v4 v4.7.0/go.mod h1:dDy58f92j70zLsuZVuUX5Wp9vtxXpaZnkPGWeqDfCps=
sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=
sigs.k8s.io/structured-merge-diff/v6 v6.3.3 h1:u08YRbVUi59ri4YD6cg0UqNM4Dimn0sIl+wldcx5PYw=
sigs.k8s.io/structured-merge-diff/v6 v6.3.3/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=
+2 -2
View File
@@ -108,7 +108,7 @@ func Parse(rawBytes []byte) (Configuration, error) {
if err == io.EOF {
break
}
return conf, fmt.Errorf("Decoding config failed: %v", err)
return conf, fmt.Errorf("decoding config failed: %v", err)
}
}
for key, check := range conf.CustomChecks {
@@ -127,7 +127,7 @@ func Parse(rawBytes []byte) (Configuration, error) {
// Validate checks if a config is valid
func (conf Configuration) Validate() error {
if len(conf.Checks) == 0 {
return errors.New("No checks were enabled")
return errors.New("no checks were enabled")
}
return nil
}
+10 -7
View File
@@ -92,7 +92,7 @@ customChecks:
func TestParseError(t *testing.T) {
_, err := Parse([]byte(confInvalid))
expectedErr := "Decoding config failed: error unmarshaling JSON: while decoding JSON: json: cannot unmarshal string into Go value of type config.Configuration"
expectedErr := "decoding config failed: error unmarshaling JSON: while decoding JSON: json: cannot unmarshal string into Go value of type config.Configuration"
assert.EqualError(t, err, expectedErr)
}
@@ -115,7 +115,9 @@ func TestConfigFromURL(t *testing.T) {
var parsedConf Configuration
srv := &http.Server{Addr: ":8081"}
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
io.WriteString(w, confValidYAML)
if _, err := io.WriteString(w, confValidYAML); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
})
go func() {
@@ -142,19 +144,20 @@ func TestConfigNoServerError(t *testing.T) {
}
func TestConfigWithCustomChecks(t *testing.T) {
valid := map[string]interface{}{
"securityContext": map[string]interface{}{
valid := map[string]any{
"securityContext": map[string]any{
"foo": "bar",
},
}
invalid := map[string]interface{}{
"notSecurityContext": map[string]interface{}{},
invalid := map[string]any{
"notSecurityContext": map[string]any{},
}
parsedConf, err := Parse([]byte(confCustomChecks))
assert.NoError(t, err, "Expected no error when parsing YAML config")
assert.Equal(t, 1, len(parsedConf.CustomChecks))
check, err := parsedConf.CustomChecks["foo"].TemplateForResource(map[string]interface{}{})
check, err := parsedConf.CustomChecks["foo"].TemplateForResource(map[string]any{})
assert.NoError(t, err)
isValid, _, err := check.CheckObject(context.TODO(), valid)
assert.NoError(t, err)
assert.Equal(t, true, isValid)
+39 -42
View File
@@ -21,6 +21,8 @@ import (
"errors"
"fmt"
"io"
"maps"
"slices"
"strings"
"text/template"
@@ -58,34 +60,34 @@ var HandledTargets = []TargetKind{
type Mutation struct {
Path string
Op string
Value interface{}
Value any
Comment string
}
// SchemaCheck is a Polaris check that runs using JSON Schema
type SchemaCheck struct {
ID string `yaml:"id" json:"id"`
Category string `yaml:"category" json:"category"`
SuccessMessage string `yaml:"successMessage" json:"successMessage"`
FailureMessage string `yaml:"failureMessage" json:"failureMessage"`
Controllers includeExcludeList `yaml:"controllers" json:"controllers"`
Containers includeExcludeList `yaml:"containers" json:"containers"`
Target TargetKind `yaml:"target" json:"target"`
SchemaTarget TargetKind `yaml:"schemaTarget" json:"schemaTarget"`
Schema map[string]interface{} `yaml:"schema" json:"schema"`
SchemaString string `yaml:"schemaString" json:"schemaString"`
Validator jsonschema.Schema `yaml:"-" json:"-"`
AdditionalSchemas map[string]map[string]interface{} `yaml:"additionalSchemas" json:"additionalSchemas"`
AdditionalSchemaStrings map[string]string `yaml:"additionalSchemaStrings" json:"additionalSchemaStrings"`
AdditionalValidators map[string]jsonschema.Schema `yaml:"-" json:"-"`
Mutations []Mutation `yaml:"mutations" json:"mutations"`
ID string `yaml:"id" json:"id"`
Category string `yaml:"category" json:"category"`
SuccessMessage string `yaml:"successMessage" json:"successMessage"`
FailureMessage string `yaml:"failureMessage" json:"failureMessage"`
Controllers includeExcludeList `yaml:"controllers" json:"controllers"`
Containers includeExcludeList `yaml:"containers" json:"containers"`
Target TargetKind `yaml:"target" json:"target"`
SchemaTarget TargetKind `yaml:"schemaTarget" json:"schemaTarget"`
Schema map[string]any `yaml:"schema" json:"schema"`
SchemaString string `yaml:"schemaString" json:"schemaString"`
Validator jsonschema.Schema `yaml:"-" json:"-"`
AdditionalSchemas map[string]map[string]any `yaml:"additionalSchemas" json:"additionalSchemas"`
AdditionalSchemaStrings map[string]string `yaml:"additionalSchemaStrings" json:"additionalSchemaStrings"`
AdditionalValidators map[string]jsonschema.Schema `yaml:"-" json:"-"`
Mutations []Mutation `yaml:"mutations" json:"mutations"`
}
type resourceMinimum string
type resourceMaximum string
// UnmarshalYAMLOrJSON is a helper function to unmarshal data in an arbitrary format
func UnmarshalYAMLOrJSON(raw []byte, dest interface{}) error {
func UnmarshalYAMLOrJSON(raw []byte, dest any) error {
reader := bytes.NewReader(raw)
d := k8sYaml.NewYAMLOrJSONDecoder(reader, 4096)
for {
@@ -93,7 +95,7 @@ func UnmarshalYAMLOrJSON(raw []byte, dest interface{}) error {
if err == io.EOF {
break
}
return fmt.Errorf("Decoding schema check failed: %v", err)
return fmt.Errorf("decoding schema check failed: %v", err)
}
}
return nil
@@ -106,7 +108,9 @@ func ParseCheck(id string, rawBytes []byte) (SchemaCheck, error) {
if err != nil {
return check, err
}
check.Initialize(id)
if err := check.Initialize(id); err != nil {
return check, err
}
return check, nil
}
@@ -129,7 +133,7 @@ func newResourceMaximum() jsonschema.Keyword {
return new(resourceMaximum)
}
func (min resourceMinimum) ValidateKeyword(ctx context.Context, currentState *jsonschema.ValidationState, data interface{}) {
func (min resourceMinimum) ValidateKeyword(ctx context.Context, currentState *jsonschema.ValidationState, data any) {
err := validateRange(string(min), data, true)
if err != nil {
errs := currentState.Errs
@@ -137,7 +141,7 @@ func (min resourceMinimum) ValidateKeyword(ctx context.Context, currentState *js
currentState.Errs = errs
}
}
func (max resourceMaximum) ValidateKeyword(ctx context.Context, currentState *jsonschema.ValidationState, data interface{}) {
func (max resourceMaximum) ValidateKeyword(ctx context.Context, currentState *jsonschema.ValidationState, data any) {
err := validateRange(string(max), data, false)
if err != nil {
errs := currentState.Errs
@@ -163,7 +167,7 @@ func (max resourceMaximum) Register(uri string, registry *jsonschema.SchemaRegis
// Not implemented
}
func parseQuantity(i interface{}) (resource.Quantity, *[]jsonschema.KeyError) {
func parseQuantity(i any) (resource.Quantity, *[]jsonschema.KeyError) {
if resNum, ok := i.(float64); ok {
i = fmt.Sprintf("%f", resNum)
}
@@ -182,7 +186,7 @@ func parseQuantity(i interface{}) (resource.Quantity, *[]jsonschema.KeyError) {
return q, nil
}
func validateRange(limit interface{}, data interface{}, isMinimum bool) *[]jsonschema.KeyError {
func validateRange(limit any, data any, isMinimum bool) *[]jsonschema.KeyError {
limitQuantity, err := parseQuantity(limit)
if err != nil {
return err
@@ -228,21 +232,19 @@ func (check *SchemaCheck) Initialize(id string) error {
}
check.AdditionalSchemaStrings[kind] = string(jsonBytes)
}
check.Schema = map[string]interface{}{}
check.AdditionalSchemas = map[string]map[string]interface{}{}
check.Schema = map[string]any{}
check.AdditionalSchemas = map[string]map[string]any{}
return nil
}
// TemplateForResource fills out a check's templated fields given a particular resource
func (check SchemaCheck) TemplateForResource(res interface{}) (*SchemaCheck, error) {
func (check SchemaCheck) TemplateForResource(res any) (*SchemaCheck, error) {
newCheck := check // Make a copy of the check, since we're going to modify the schema
templateStrings := map[string]string{
"": newCheck.SchemaString,
}
for kind, schema := range newCheck.AdditionalSchemaStrings {
templateStrings[kind] = schema
}
maps.Copy(templateStrings, newCheck.AdditionalSchemaStrings)
newCheck.SchemaString = ""
newCheck.AdditionalSchemaStrings = map[string]string{}
@@ -294,7 +296,7 @@ func (check SchemaCheck) CheckPodSpec(ctx context.Context, pod *corev1.PodSpec)
}
// CheckPodTemplate checks a pod template against the schema
func (check SchemaCheck) CheckPodTemplate(ctx context.Context, podTemplate interface{}) (bool, []jsonschema.KeyError, error) {
func (check SchemaCheck) CheckPodTemplate(ctx context.Context, podTemplate any) (bool, []jsonschema.KeyError, error) {
return check.CheckObject(ctx, podTemplate)
}
@@ -310,7 +312,7 @@ func (check SchemaCheck) CheckContainer(ctx context.Context, container *corev1.C
}
// CheckObject checks arbitrary data against the schema
func (check SchemaCheck) CheckObject(ctx context.Context, obj interface{}) (bool, []jsonschema.KeyError, error) {
func (check SchemaCheck) CheckObject(ctx context.Context, obj any) (bool, []jsonschema.KeyError, error) {
bytes, err := json.Marshal(obj)
if err != nil {
return false, nil, err
@@ -320,10 +322,10 @@ func (check SchemaCheck) CheckObject(ctx context.Context, obj interface{}) (bool
}
// CheckAdditionalObjects looks for an object that passes the specified additional schema
func (check SchemaCheck) CheckAdditionalObjects(ctx context.Context, groupkind string, objects []interface{}) (bool, error) {
func (check SchemaCheck) CheckAdditionalObjects(ctx context.Context, groupkind string, objects []any) (bool, error) {
val, ok := check.AdditionalValidators[groupkind]
if !ok {
return false, errors.New("No validator found for " + groupkind)
return false, errors.New("no validator found for " + groupkind)
}
for _, obj := range objects {
bytes, err := json.Marshal(obj)
@@ -356,19 +358,14 @@ func (check SchemaCheck) IsActionable(target TargetKind, kind string, isInit boo
return false
}
isIncluded := len(check.Controllers.Include) == 0
for _, inclusion := range check.Controllers.Include {
if inclusion == kind {
isIncluded = true
break
}
if slices.Contains(check.Controllers.Include, kind) {
isIncluded = true
}
if !isIncluded {
return false
}
for _, exclusion := range check.Controllers.Exclude {
if exclusion == kind {
return false
}
if slices.Contains(check.Controllers.Exclude, kind) {
return false
}
if check.Target == TargetContainer {
isIncluded := len(check.Containers.Include) == 0
+13 -10
View File
@@ -24,7 +24,6 @@ import (
"net/http"
"net/url"
"path"
"strings"
"github.com/fairwindsops/polaris/pkg/config"
"github.com/fairwindsops/polaris/pkg/kube"
@@ -111,7 +110,9 @@ func writeTemplate(tmpl *template.Template, data *templateData, w http.ResponseW
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
buf.WriteTo(w)
if _, err := buf.WriteTo(w); err != nil {
logrus.Errorf("Error writing template: %v", err)
}
}
func getConfigForQuery(base config.Configuration, query url.Values) config.Configuration {
@@ -153,7 +154,9 @@ func GetRouter(ctx context.Context, c config.Configuration, auditPath string, po
router.PathPrefix("/static/").Handler(http.StripPrefix(path.Join(basePath, "/static/"), fileServer))
router.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("OK"))
if _, err := w.Write([]byte("OK")); err != nil {
logrus.Errorf("Error writing health response: %v", err)
}
})
router.HandleFunc("/favicon.ico", func(w http.ResponseWriter, r *http.Request) {
@@ -163,7 +166,9 @@ func GetRouter(ctx context.Context, c config.Configuration, auditPath string, po
http.Error(w, "Error getting favicon", http.StatusInternalServerError)
return
}
w.Write(favicon)
if _, err := w.Write(favicon); err != nil {
logrus.Errorf("Error writing favicon: %v", err)
}
})
router.HandleFunc("/results.json", func(w http.ResponseWriter, r *http.Request) {
@@ -188,11 +193,7 @@ func GetRouter(ctx context.Context, c config.Configuration, auditPath string, po
JSONHandler(w, r, auditData)
})
router.HandleFunc("/details/{category}", func(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
category := vars["category"]
category = strings.Replace(category, ".md", "", -1)
})
router.HandleFunc("/details/{category}", func(http.ResponseWriter, *http.Request) {})
router.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/" && r.URL.Path != basePath {
@@ -265,5 +266,7 @@ func MainHandler(w http.ResponseWriter, r *http.Request, c config.Configuration,
func JSONHandler(w http.ResponseWriter, r *http.Request, auditData *validator.AuditData) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(auditData)
if err := json.NewEncoder(w).Encode(auditData); err != nil {
logrus.Errorf("Error encoding audit JSON: %v", err)
}
}
+8 -13
View File
@@ -15,7 +15,7 @@
package dashboard
import (
"fmt"
"slices"
"strings"
"github.com/fairwindsops/polaris/pkg/config"
@@ -128,13 +128,13 @@ func getCategoryLink(category string) string {
func getCategoryInfo(category string) string {
switch category {
case "Reliability":
return fmt.Sprintf(`
return `
Kubernetes is built to reliabily run highly available applications.
Polaris includes a number of checks to ensure that you are maximizing
the reliability potential of Kubernetes.
`)
`
case "Efficiency":
return fmt.Sprintf(`
return `
Configuring resource requests and limits for workloads running in Kubernetes
helps ensure that every container will have access to all the resources it
needs. These are also a crucial part of cluster autoscaling logic, as new
@@ -142,25 +142,20 @@ func getCategoryInfo(category string) string {
infrastructure for new pod(s). By default, Polaris validates that resource
requests and limits are set, it also includes optional functionality to ensure
these requests and limits fall within specified ranges.
`)
`
case "Security":
return fmt.Sprintf(`
return `
Kubernetes provides a great deal of configurability when it comes to the
security of your workloads. A key principle here involves limiting the level
of access any individual workload has. Polaris has validations for a number of
best practices, mostly focused on ensuring that unnecessary access has not
been granted to an application workload.
`)
`
default:
return ""
}
}
func stringInSlice(a string, list []string) bool {
for _, b := range list {
if b == a {
return true
}
}
return false
return slices.Contains(list, a)
}
+13 -13
View File
@@ -39,13 +39,13 @@ type GenericResource struct {
ObjectMeta kubeAPIMetaV1.Object
Resource unstructured.Unstructured
PodSpec *kubeAPICoreV1.PodSpec
PodTemplate interface{}
PodTemplate any
OriginalObjectJSON []byte
OriginalObjectYAML []byte
}
// NewGenericResourceFromUnstructured creates a workload from an unstructured.Unstructured
func NewGenericResourceFromUnstructured(unst unstructured.Unstructured, podSpecMap interface{}) (GenericResource, error) {
func NewGenericResourceFromUnstructured(unst unstructured.Unstructured, podSpecMap any) (GenericResource, error) {
if unst.GetCreationTimestamp().Time.IsZero() {
unstructured.RemoveNestedField(unst.Object, "metadata", "creationTimestamp")
unstructured.RemoveNestedField(unst.Object, "status")
@@ -69,7 +69,7 @@ func NewGenericResourceFromUnstructured(unst unstructured.Unstructured, podSpecM
return workload, err
}
workload.OriginalObjectJSON = b
m := make(map[string]interface{})
m := make(map[string]any)
err = json.Unmarshal(b, &m)
if err != nil {
return workload, err
@@ -93,7 +93,7 @@ func NewGenericResourceFromUnstructured(unst unstructured.Unstructured, podSpecM
}
// NewGenericResourceFromPod builds a new workload for a given Pod without looking at parents
func NewGenericResourceFromPod(podResource kubeAPICoreV1.Pod, originalObject interface{}) (GenericResource, error) {
func NewGenericResourceFromPod(podResource kubeAPICoreV1.Pod, originalObject any) (GenericResource, error) {
podMap, err := SerializePod(&podResource)
if err != nil {
return GenericResource{}, err
@@ -102,7 +102,7 @@ func NewGenericResourceFromPod(podResource kubeAPICoreV1.Pod, originalObject int
Kind: "Pod",
PodSpec: &podResource.Spec,
PodTemplate: podMap,
ObjectMeta: podResource.ObjectMeta.GetObjectMeta(),
ObjectMeta: podResource.GetObjectMeta(),
}
if originalObject != nil {
bytes, err := json.Marshal(originalObject)
@@ -157,9 +157,9 @@ func resolveControllerFromPod(ctx context.Context, podResource kubeAPICoreV1.Pod
}
topKind := "Pod"
topMeta := podWorkload.ObjectMeta
var topPodSpec interface{}
var topPodSpec any
topPodSpec = podWorkload.Resource.Object
owners := podResource.ObjectMeta.GetOwnerReferences()
owners := podResource.GetOwnerReferences()
lastKey := ""
for len(owners) > 0 {
if len(owners) > 1 {
@@ -263,10 +263,10 @@ func GetObject(ctx context.Context, namespace, kind, version, name string, dynam
}
// GetPodSpec looks inside arbitrary YAML for a PodSpec
func GetPodSpec(yaml map[string]interface{}) interface{} {
func GetPodSpec(yaml map[string]any) any {
for _, child := range podSpecFields {
if childYaml, ok := yaml[child]; ok {
return GetPodSpec(childYaml.(map[string]interface{}))
return GetPodSpec(childYaml.(map[string]any))
}
}
if _, ok := yaml["containers"]; ok {
@@ -278,9 +278,9 @@ func GetPodSpec(yaml map[string]interface{}) interface{} {
// GetPodTemplate looks inside arbitrary YAML for a Pod template, containing
// fields `spec.containers`.
// For example, it returns the `spec.template` level of a Kubernetes Deployment yaml.
func GetPodTemplate(yaml map[string]interface{}) (podTemplate interface{}, err error) {
func GetPodTemplate(yaml map[string]any) (podTemplate any, err error) {
if yamlSpec, ok := yaml["spec"]; ok {
if yamlSpecMap, ok := yamlSpec.(map[string]interface{}); ok {
if yamlSpecMap, ok := yamlSpec.(map[string]any); ok {
if _, ok := yamlSpecMap["containers"]; ok {
// This is a hack around unstructured.SetNestedField using DeepCopy which does
// not support the type int, and panics.
@@ -289,7 +289,7 @@ func GetPodTemplate(yaml map[string]interface{}) (podTemplate interface{}, err e
if err != nil {
return nil, err
}
podTemplateMap := make(map[string]interface{})
podTemplateMap := make(map[string]any)
err = json.Unmarshal(podTemplateJSON, &podTemplateMap)
if err != nil {
return nil, err
@@ -300,7 +300,7 @@ func GetPodTemplate(yaml map[string]interface{}) (podTemplate interface{}, err e
}
for _, podSpecField := range podSpecFields {
if childYaml, ok := yaml[podSpecField]; ok {
return GetPodTemplate(childYaml.(map[string]interface{}))
return GetPodTemplate(childYaml.(map[string]any))
}
}
return nil, nil
+33 -18
View File
@@ -52,6 +52,7 @@ type ResourceProvider struct {
SourceType string
Nodes []corev1.Node
Namespaces []corev1.Namespace
Pods []corev1.Pod
Resources resourceKindMap
}
@@ -109,13 +110,13 @@ func maybeTransformKindIntoGroupKind(k string) string {
}
func parseGroupKind(gk string) schema.GroupKind {
i := strings.Index(gk, "/")
if i == -1 {
before, after, ok := strings.Cut(gk, "/")
if !ok {
return schema.GroupKind{Kind: gk}
}
group := gk[:i]
kind := gk[i+1:]
group := before
kind := after
return schema.GroupKind{Group: group, Kind: kind}
}
@@ -127,6 +128,7 @@ func newResourceProvider(version, sourceType, sourceName string) ResourceProvide
CreationTime: time.Now(),
Nodes: make([]corev1.Node, 0),
Namespaces: make([]corev1.Namespace, 0),
Pods: make([]corev1.Pod, 0),
Resources: make(map[string][]GenericResource),
}
}
@@ -156,13 +158,13 @@ func CreateResourceProviderFromResource(ctx context.Context, workload string) (*
}
serverVersion, err := clientSet.Discovery().ServerVersion()
if err != nil {
return nil, fmt.Errorf("Error fetching Cluster API version: %w", err)
return nil, fmt.Errorf("error fetching Cluster API version: %w", err)
}
resources := newResourceProvider(serverVersion.Major+"."+serverVersion.Minor, "Resource", workload)
parts := strings.Split(workload, "/")
if len(parts) != 4 {
return nil, fmt.Errorf("Invalid workload identifier %s. Should be in format namespace/kind/version/name, e.g. nginx-ingress/Deployment.apps/v1/default-backend", workload)
return nil, fmt.Errorf("invalid workload identifier %s. Should be in format namespace/kind/version/name, e.g. nginx-ingress/Deployment.apps/v1/default-backend", workload)
}
namespace := parts[0]
kind := parts[1]
@@ -171,11 +173,11 @@ func CreateResourceProviderFromResource(ctx context.Context, workload string) (*
obj, err := GetObject(ctx, namespace, kind, version, name, dynamicClient, restMapper)
if err != nil {
return nil, fmt.Errorf("Could not find workload %s: %w", workload, err)
return nil, fmt.Errorf("could not find workload %s: %w", workload, err)
}
workloadObj, err := NewGenericResourceFromUnstructured(*obj, nil)
if err != nil {
return nil, fmt.Errorf("Could not parse workload %s: %w", workload, err)
return nil, fmt.Errorf("could not parse workload %s: %w", workload, err)
}
resources.Resources.addResource(workloadObj)
return &resources, nil
@@ -196,6 +198,9 @@ func CreateResourceProviderFromPath(directory string) (*ResourceProvider, error)
}
visitFile := func(path string, f os.FileInfo, err error) error {
if err != nil {
return err
}
if !strings.HasSuffix(path, ".yml") && !strings.HasSuffix(path, ".yaml") {
return nil
}
@@ -246,19 +251,19 @@ func GetKubeClient(ctx context.Context, kubeContext string) (dynamic.Interface,
kubeConf, err = config.GetConfig()
}
if err != nil {
return nil, nil, nil, "", fmt.Errorf("Error fetching KubeConfig: %v", err)
return nil, nil, nil, "", fmt.Errorf("error fetching KubeConfig: %v", err)
}
clientSet, err := kubernetes.NewForConfig(kubeConf)
if err != nil {
return nil, nil, nil, "", fmt.Errorf("Error creating Kubernetes client: %v", err)
return nil, nil, nil, "", fmt.Errorf("error creating Kubernetes client: %v", err)
}
dynamicClient, err := dynamic.NewForConfig(kubeConf)
if err != nil {
return nil, nil, nil, "", fmt.Errorf("Error connecting to dynamic interface: %v", err)
return nil, nil, nil, "", fmt.Errorf("error connecting to dynamic interface: %v", err)
}
resources, err := restmapper.GetAPIGroupResources(clientSet.Discovery())
if err != nil {
return nil, nil, nil, "", fmt.Errorf("Error getting API Group resources: %v", err)
return nil, nil, nil, "", fmt.Errorf("error getting API Group resources: %v", err)
}
return dynamicClient, restmapper.NewDiscoveryRESTMapper(resources), clientSet, kubeConf.Host, nil
}
@@ -304,6 +309,14 @@ func CreateResourceProviderFromAPI(ctx context.Context, kube kubernetes.Interfac
}
namespaces = nsList
}
logrus.Info("Loading pods")
pods, err := kube.CoreV1().Pods(c.Namespace).List(ctx, listOpts)
if err != nil {
logrus.Errorf("Error fetching Pods: %v", err)
return nil, err
}
logrus.Info("Setting up restmapper")
resources, err := restmapper.GetAPIGroupResources(kube.Discovery())
if err != nil {
@@ -383,6 +396,7 @@ func CreateResourceProviderFromAPI(ctx context.Context, kube kubernetes.Interfac
provider.Nodes = nodes.Items
provider.Namespaces = namespaces.Items
provider.Pods = pods.Items
provider.Resources.addResources(kubernetesResources)
logrus.Info("Done loading Kubernetes resources")
return &provider, nil
@@ -442,6 +456,7 @@ func (resources *ResourceProvider) addResourceFromString(contents string) error
return err
}
workload.OriginalObjectYAML = contentBytes
resources.Pods = append(resources.Pods, pod)
resources.Resources.addResource(workload)
} else {
newResource, err := NewGenericResourceFromBytes(contentBytes)
@@ -454,12 +469,12 @@ func (resources *ResourceProvider) addResourceFromString(contents string) error
}
// SerializePodSpec converts a typed PodSpec into a map[string]interface{}
func SerializePodSpec(pod *corev1.PodSpec) (map[string]interface{}, error) {
func SerializePodSpec(pod *corev1.PodSpec) (map[string]any, error) {
podJSON, err := json.Marshal(pod)
if err != nil {
return nil, err
}
podMap := make(map[string]interface{})
podMap := make(map[string]any)
err = json.Unmarshal(podJSON, &podMap)
if err != nil {
return nil, err
@@ -468,12 +483,12 @@ func SerializePodSpec(pod *corev1.PodSpec) (map[string]interface{}, error) {
}
// SerializePod converts a typed Pod into a map[string]interface{}
func SerializePod(pod *corev1.Pod) (map[string]interface{}, error) {
func SerializePod(pod *corev1.Pod) (map[string]any, error) {
podJSON, err := json.Marshal(pod)
if err != nil {
return nil, err
}
podMap := make(map[string]interface{})
podMap := make(map[string]any)
err = json.Unmarshal(podJSON, &podMap)
if err != nil {
return nil, err
@@ -482,12 +497,12 @@ func SerializePod(pod *corev1.Pod) (map[string]interface{}, error) {
}
// SerializeContainer converts a typed Container into a map[string]interface{}
func SerializeContainer(container *corev1.Container) (map[string]interface{}, error) {
func SerializeContainer(container *corev1.Container) (map[string]any, error) {
containerJSON, err := json.Marshal(container)
if err != nil {
return nil, err
}
containerMap := make(map[string]interface{})
containerMap := make(map[string]any)
err = json.Unmarshal(containerJSON, &containerMap)
if err != nil {
return nil, err
+6 -5
View File
@@ -40,7 +40,7 @@ func TestGetResourcesFromPath(t *testing.T) {
assert.Equal(t, 0, len(provider.Nodes), "Should not have any nodes")
assert.Equal(t, 1, len(provider.Namespaces), "Should have a namespace")
assert.Equal(t, "two", provider.Namespaces[0].ObjectMeta.Name)
assert.Equal(t, "two", provider.Namespaces[0].Name)
namespaceCount := map[string]int{}
for _, resources := range provider.Resources {
@@ -69,8 +69,8 @@ func TestGetMultipleResourceFromSingleFile(t *testing.T) {
assert.Equal(t, "dashboard", resources.Resources["apps/Deployment"][0].PodSpec.Containers[0].Name)
assert.Equal(t, 2, len(resources.Namespaces), "Should have a namespace")
assert.Equal(t, "polaris", resources.Namespaces[0].ObjectMeta.Name)
assert.Equal(t, "polaris-2", resources.Namespaces[1].ObjectMeta.Name)
assert.Equal(t, "polaris", resources.Namespaces[0].Name)
assert.Equal(t, "polaris-2", resources.Namespaces[1].Name)
}
func TestGetMultipleResourceFromBadFile(t *testing.T) {
@@ -92,8 +92,8 @@ func TestAddResourcesFromReader(t *testing.T) {
assert.Equal(t, "dashboard", resources.Resources["apps/Deployment"][0].PodSpec.Containers[0].Name)
assert.Equal(t, 2, len(resources.Namespaces), "Should have a namespace")
assert.Equal(t, "polaris", resources.Namespaces[0].ObjectMeta.Name)
assert.Equal(t, "polaris-2", resources.Namespaces[1].ObjectMeta.Name)
assert.Equal(t, "polaris", resources.Namespaces[0].Name)
assert.Equal(t, "polaris-2", resources.Namespaces[1].Name)
}
func TestGetResourceFromAPI(t *testing.T) {
@@ -157,6 +157,7 @@ func TestGetResourceFromAPI(t *testing.T) {
assert.Equal(t, tt.want.SourceName, resources.SourceName)
assert.IsType(t, tt.want.CreationTime, resources.CreationTime)
assert.Equal(t, 0, len(resources.Nodes), "Should not have any nodes")
assert.Equal(t, 5, len(resources.Pods), "Should have 5 pods")
for k, v := range resources.Resources {
fmt.Println("cont", k, v)
}
+5 -4
View File
@@ -173,7 +173,7 @@ func createPathAndFindNodes(node *yaml.Node, selectors []string, create bool) ([
func addOrReplaceValue(node *yaml.Node, splits []string, value *yaml.Node) error {
if len(node.Content) == 0 {
return errors.New("No content in node")
return errors.New("no content in node")
}
nodes, err := createPathAndFindNodes(node.Content[0], splits, true)
if err != nil {
@@ -224,7 +224,7 @@ func addOrReplaceValue(node *yaml.Node, splits []string, value *yaml.Node) error
return nil
}
func getNodeFromValue(value interface{}, comment string) (*yaml.Node, error) {
func getNodeFromValue(value any, comment string) (*yaml.Node, error) {
bytes, err := yaml.Marshal(value)
if err != nil {
return nil, err
@@ -235,7 +235,7 @@ func getNodeFromValue(value interface{}, comment string) (*yaml.Node, error) {
return nil, err
}
if len(doc.Content) == 0 {
return nil, errors.New("Generated an empty YAML document")
return nil, errors.New("generated an empty YAML document")
}
if doc.Content[0].Kind == yaml.MappingNode {
doc.Content[0].Content[0].HeadComment = comment
@@ -293,7 +293,8 @@ func removeMatchingNode(node *yaml.Node, selectors []string) error {
for _, node := range visitArrayNodes {
lastSelector := len(selectors) == 1
if !lastSelector {
removeMatchingNode(node, selectors[1:])
// Missing nested keys are expected for * wildcards (e.g. owners/*/aliases).
_ = removeMatchingNode(node, selectors[1:])
}
}
}
+3 -3
View File
@@ -102,7 +102,7 @@ obj:
`,
patch: config.Mutation{
Op: "replace",
Value: map[string]interface{}{
Value: map[string]any{
"bar": []string{"c", "d"},
},
Path: "/obj/foo",
@@ -129,7 +129,7 @@ obj:
original: `foo: bar`,
patch: config.Mutation{
Op: "add",
Value: map[string]interface{}{
Value: map[string]any{
"baz": "quux",
},
Path: "/extra",
@@ -149,7 +149,7 @@ foo: bar
`,
patch: config.Mutation{
Op: "replace",
Value: map[string]interface{}{
Value: map[string]any{
"baz": "quux",
},
Path: "/extra",
+1
View File
@@ -35,6 +35,7 @@ func TestValidatePDB(t *testing.T) {
}
pdb := unstructured.Unstructured{}
res, err := kube.NewGenericResourceFromUnstructured(pdb, nil)
assert.NoError(t, err)
res.Kind = "PodDisruptionBudget"
actualResult, err := applyNonControllerSchemaChecks(context.Background(), &c, nil, res)
+3 -2
View File
@@ -99,10 +99,11 @@ func TestControllerLevelChecks(t *testing.T) {
assert.Equal(t, 10, res.Resources.GetLength())
testResources(res)
replicaSpec := map[string]interface{}{"replicas": 2}
replicaSpec := map[string]any{"replicas": 2}
b, err := json.Marshal(replicaSpec)
assert.NoError(t, err)
err = json.Unmarshal(b, &replicaSpec)
assert.NoError(t, err)
d1, p1 := test.MockDeploy("test", "test-deployment")
d2, p2 := test.MockDeploy("test", "test-deployment-2")
@@ -203,7 +204,7 @@ func TestControllerExemptions(t *testing.T) {
}
pod := test.MockPod()
pod.ObjectMeta.Namespace = "foo"
pod.Namespace = "foo"
workload, err := kube.NewGenericResourceFromPod(pod, nil)
assert.NoError(t, err)
workload.Kind = "Deployment"
+3 -3
View File
@@ -50,6 +50,7 @@ func RunAudit(ctx context.Context, config conf.Configuration, kubeResources *kub
ClusterInfo: ClusterInfo{
Version: kubeResources.ServerVersion,
Nodes: len(kubeResources.Nodes),
Pods: len(kubeResources.Pods),
Namespaces: len(kubeResources.Namespaces),
Controllers: kubeResources.Resources.GetNumberOfControllers(),
},
@@ -61,13 +62,12 @@ func RunAudit(ctx context.Context, config conf.Configuration, kubeResources *kub
// ReadAuditFromFile reads the data from a past audit stored in a JSON or YAML file.
func ReadAuditFromFile(fileName string) AuditData {
auditData := AuditData{}
oldFileBytes, err := os.ReadFile(fileName)
if err != nil {
logrus.Errorf("Unable to read contents of loaded file: %v", err)
os.Exit(1)
}
auditData, err = ParseAudit(oldFileBytes)
auditData, err := ParseAudit(oldFileBytes)
if err != nil {
logrus.Errorf("Error parsing file contents into auditData: %v", err)
os.Exit(1)
@@ -85,7 +85,7 @@ func ParseAudit(oldFileBytes []byte) (AuditData, error) {
if err == io.EOF {
return conf, nil
}
return conf, fmt.Errorf("Decoding config failed: %v", err)
return conf, fmt.Errorf("decoding config failed: %v", err)
}
}
}
+3
View File
@@ -51,6 +51,9 @@ func TestGetTemplateData(t *testing.T) {
assert.EqualValues(t, sum, actualAudit.GetSummary())
assert.Equal(t, actualAudit.SourceType, "Cluster", "should be from a cluster")
assert.Equal(t, actualAudit.SourceName, "test", "should be from a cluster")
assert.Equal(t, 5, actualAudit.ClusterInfo.Pods, "should report pod count from the cluster")
assert.Equal(t, 5, actualAudit.ClusterInfo.Controllers, "should report controller count from the cluster")
assert.Equal(t, 1, actualAudit.ClusterInfo.Namespaces, "should report namespace count from the cluster")
expectedResults := []struct {
kind string
+15 -13
View File
@@ -250,15 +250,16 @@ func fillString(id string, l int) string {
// GetPrettyOutput returns a human-readable string
func (res AuditData) GetPrettyOutput(useColor bool) string {
color.NoColor = !useColor
str := titleColor.Sprint(fmt.Sprintf("Polaris audited %s %s at %s\n", res.SourceType, res.SourceName, res.AuditTime))
str += color.CyanString(fmt.Sprintf(" Nodes: %d | Namespaces: %d | Controllers: %d\n", res.ClusterInfo.Nodes, res.ClusterInfo.Namespaces, res.ClusterInfo.Controllers))
str += color.GreenString(fmt.Sprintf(" Final score: %d\n", res.Score))
str += "\n"
var str strings.Builder
str.WriteString(titleColor.Sprint(fmt.Sprintf("Polaris audited %s %s at %s\n", res.SourceType, res.SourceName, res.AuditTime)))
str.WriteString(color.CyanString(fmt.Sprintf(" Nodes: %d | Namespaces: %d | Controllers: %d\n", res.ClusterInfo.Nodes, res.ClusterInfo.Namespaces, res.ClusterInfo.Controllers)))
str.WriteString(color.GreenString(fmt.Sprintf(" Final score: %d\n", res.Score)))
str.WriteString("\n")
for _, result := range res.Results {
str += result.GetPrettyOutput() + "\n"
str.WriteString(result.GetPrettyOutput() + "\n")
}
color.NoColor = false
return str
return str.String()
}
// GetPrettyOutput returns a human-readable string
@@ -277,11 +278,12 @@ func (res Result) GetPrettyOutput() string {
// GetPrettyOutput returns a human-readable string
func (res PodResult) GetPrettyOutput() string {
str := res.Results.GetPrettyOutput()
var str strings.Builder
str.WriteString(res.Results.GetPrettyOutput())
for _, cont := range res.ContainerResults {
str += cont.GetPrettyOutput()
str.WriteString(cont.GetPrettyOutput())
}
return str
return str.String()
}
// GetPrettyOutput returns a human-readable string
@@ -296,7 +298,7 @@ const minIDLength = 40
// GetPrettyOutput returns a human-readable string
func (res ResultSet) GetPrettyOutput() string {
indent := " "
str := ""
var str strings.Builder
for _, msg := range res {
status := color.GreenString(successMessage)
if !msg.Success {
@@ -309,8 +311,8 @@ func (res ResultSet) GetPrettyOutput() string {
if color.NoColor {
status = strings.Fields(status)[1] // remove emoji
}
str += fmt.Sprintf("%s%s %s\n", indent, checkColor.Sprint(fillString(msg.ID, minIDLength-len(indent))), status)
str += fmt.Sprintf("%s %s - %s\n", indent, msg.Category, msg.Message)
str.WriteString(indent + checkColor.Sprint(fillString(msg.ID, minIDLength-len(indent))) + " " + status + "\n")
str.WriteString(indent + " " + msg.Category + " - " + msg.Message + "\n")
}
return str
return str.String()
}
@@ -28,7 +28,7 @@ import (
func TestGetTemplateInputReturnsPolarisSubKeys(t *testing.T) {
pod := test.MockPod() // Includes a container, required by GetPodSpec
pod.Spec.NodeName = "testNodeName"
pod.ObjectMeta.Name = "testpod"
pod.Name = "testpod"
genRes, err := kube.NewGenericResourceFromPod(pod, pod)
require.NoError(t, err, "creating new generic resource from a pod")
schemaTest := schemaTestCase{
+20 -28
View File
@@ -48,15 +48,15 @@ func (s schemaTestCase) ShortString() string {
var msg strings.Builder
targetStr := s.Target
if targetStr != "" {
msg.WriteString(fmt.Sprintf("target=%s, ", targetStr))
msg.WriteString("target=" + string(targetStr) + ", ")
}
ns := s.Resource.ObjectMeta.GetNamespace()
if ns != "" {
msg.WriteString(fmt.Sprintf("namespace=%s, ", ns))
msg.WriteString("namespace=" + ns + ", ")
}
msg.WriteString(fmt.Sprintf("resource=%s/%s", s.Resource.Kind, s.Resource.ObjectMeta.GetName()))
msg.WriteString("resource=" + s.Resource.Kind + "/" + s.Resource.ObjectMeta.GetName())
if s.Target == config.TargetContainer {
msg.WriteString(fmt.Sprintf(", container=%s", s.Container.Name))
msg.WriteString(", container=" + s.Container.Name)
}
return msg.String()
}
@@ -72,7 +72,7 @@ func resolveCheck(conf *config.Configuration, checkID string, test schemaTestCas
check, ok = config.BuiltInChecks[checkID]
}
if !ok {
return nil, fmt.Errorf("Check %s not found", checkID)
return nil, fmt.Errorf("check %s not found", checkID)
}
containerName := ""
@@ -99,7 +99,7 @@ func resolveCheck(conf *config.Configuration, checkID string, test schemaTestCas
// getTemplateInput augments a schemaTestCase.Resource.Resource.Object with
// Polaris built-in variables. The result can be used as input for
// CheckSchema.TemplateForResource().
func getTemplateInput(test schemaTestCase) (map[string]interface{}, error) {
func getTemplateInput(test schemaTestCase) (map[string]any, error) {
templateInput := test.Resource.Resource.Object
if templateInput == nil {
return nil, nil
@@ -113,7 +113,7 @@ func getTemplateInput(test schemaTestCase) (map[string]interface{}, error) {
if err != nil {
return nil, err
}
podTemplateMap, ok := test.Resource.PodTemplate.(map[string]interface{})
podTemplateMap, ok := test.Resource.PodTemplate.(map[string]any)
if ok {
err := unstructured.SetNestedMap(templateInput, podTemplateMap, "Polaris", "PodTemplate")
if err != nil {
@@ -136,17 +136,12 @@ func getTemplateInput(test schemaTestCase) (map[string]interface{}, error) {
}
func makeResult(conf *config.Configuration, check *config.SchemaCheck, passes bool, issues []jsonschema.KeyError) ResultMessage {
details := []string{}
for _, issue := range issues {
details = append(details, issue.Message)
}
result := ResultMessage{
ID: check.ID,
Severity: conf.Checks[check.ID],
Category: check.Category,
Success: passes,
// FIXME: need to fix the tests before adding this back
//Details: details,
// FIXME: need to fix the tests before adding Details from issues
}
if passes {
result.Message = check.SuccessMessage
@@ -167,17 +162,14 @@ func hasExemptionAnnotation(objMeta metaV1.Object, checkID string) bool {
}
checkKey := fmt.Sprintf(exemptionAnnotationPattern, checkID)
val = annot[checkKey]
if strings.ToLower(val) == "true" {
return true
}
return false
return strings.ToLower(val) == "true"
}
// ApplyAllSchemaChecksToResourceProvider applies all available checks to a ResourceProvider
func ApplyAllSchemaChecksToResourceProvider(ctx context.Context, conf *config.Configuration, resourceProvider *kube.ResourceProvider) ([]Result, error) {
results := []Result{}
if resourceProvider == nil {
return nil, errors.New("No resource provider set, cannot apply schema checks")
return nil, errors.New("no resource provider set, cannot apply schema checks")
}
for _, resources := range resourceProvider.Resources {
kindResults, err := ApplyAllSchemaChecksToAllResources(ctx, conf, resourceProvider, resources)
@@ -241,7 +233,7 @@ func applyControllerSchemaChecks(ctx context.Context, conf *config.Configuration
}
for key, val := range nonControllerResults {
if _, ok := finalResult.Results[key]; ok {
return finalResult, errors.New("Duplicate finding for check " + key)
return finalResult, errors.New("duplicate finding for check " + key)
}
finalResult.Results[key] = val
}
@@ -351,7 +343,7 @@ func applySchemaCheck(ctx context.Context, conf *config.Configuration, checkID s
podCopy := *test.Resource.PodSpec
podCopy.InitContainers = []corev1.Container{}
podCopy.Containers = []corev1.Container{*test.Container}
containerIndex := -1
var containerIndex int
if !test.IsInitContainer {
containerIndex = funk.IndexOf(test.Resource.PodSpec.Containers, func(value corev1.Container) bool {
return value.Name == test.Container.Name
@@ -371,7 +363,7 @@ func applySchemaCheck(ctx context.Context, conf *config.Configuration, checkID s
}
passes, issues, err = check.CheckPodSpec(ctx, &podCopy)
} else {
return nil, fmt.Errorf("Unknown combination of target (%s) and schema target (%s)", check.Target, check.SchemaTarget)
return nil, fmt.Errorf("unknown combination of target (%s) and schema target (%s)", check.Target, check.SchemaTarget)
}
} else if check.Target == config.TargetPodSpec {
passes, issues, err = check.CheckPodSpec(ctx, test.Resource.PodSpec)
@@ -380,7 +372,7 @@ func applySchemaCheck(ctx context.Context, conf *config.Configuration, checkID s
passes, issues, err = check.CheckPodTemplate(ctx, test.Resource.PodTemplate)
prefix = getJSONSchemaPrefix(test.Resource.Kind)
} else if check.Target == config.TargetContainer {
containerIndex := -1
var containerIndex int
if !test.IsInitContainer {
containerIndex = funk.IndexOf(test.Resource.PodSpec.Containers, func(value corev1.Container) bool {
return value.Name == test.Container.Name
@@ -425,9 +417,9 @@ func applySchemaCheck(ctx context.Context, conf *config.Configuration, checkID s
resources = funk.Filter(resources, func(res kube.GenericResource) bool {
return res.ObjectMeta.GetNamespace() == "" || res.ObjectMeta.GetNamespace() == namespace
}).([]kube.GenericResource)
objects := funk.Map(resources, func(res kube.GenericResource) interface{} {
objects := funk.Map(resources, func(res kube.GenericResource) any {
return res.Resource.Object
}).([]interface{})
}).([]any)
passes, err = check.CheckAdditionalObjects(ctx, groupkind, objects)
if err != nil {
return nil, err
@@ -475,12 +467,12 @@ func deepCopyMutation(source config.Mutation) config.Mutation {
}
func getJSONSchemaPrefix(kind string) (prefix string) {
if kind == "CronJob" {
switch kind {
case "CronJob":
prefix = "/spec/jobTemplate/spec/template/spec"
} else if kind == "Pod" {
case "Pod":
prefix = "/spec"
} else if (kind == "Deployment") || (kind == "DaemonSet") ||
(kind == "StatefulSet") || (kind == "Job") || (kind == "ReplicationController") {
case "Deployment", "DaemonSet", "StatefulSet", "Job", "ReplicationController":
prefix = "/spec/template/spec"
}
return prefix
+3 -3
View File
@@ -50,7 +50,7 @@ func (cs *CountSummary) AddSummary(other CountSummary) {
// AddResult adds a single result to the summary
func (cs *CountSummary) AddResult(result ResultMessage) {
if result.Success == false {
if !result.Success {
if result.Severity == config.SeverityWarning {
cs.Warnings++
} else {
@@ -198,7 +198,7 @@ func (rs ResultSet) GetSuccesses() []ResultMessage {
func (rs ResultSet) GetWarnings() []ResultMessage {
warnings := []ResultMessage{}
for _, msg := range rs {
if msg.Success == false && msg.Severity == config.SeverityWarning {
if !msg.Success && msg.Severity == config.SeverityWarning {
warnings = append(warnings, msg)
}
}
@@ -209,7 +209,7 @@ func (rs ResultSet) GetWarnings() []ResultMessage {
func (rs ResultSet) GetDangers() []ResultMessage {
errors := []ResultMessage{}
for _, msg := range rs {
if msg.Success == false && msg.Severity == config.SeverityDanger {
if !msg.Success && msg.Severity == config.SeverityDanger {
errors = append(errors, msg)
}
}
+1 -1
View File
@@ -49,7 +49,7 @@ func NewMutateWebhook(ctx context.Context, mgr manager.Manager, c config.Configu
}
func (m *Mutator) mutate(ctx context.Context, req admission.Request) ([]jsonpatch.Operation, error) {
results, kubeResources, err := GetValidatedResults(ctx, req.AdmissionRequest.Kind.Kind, m.decoder, req, m.Config)
results, kubeResources, err := GetValidatedResults(ctx, req.Kind.Kind, m.decoder, req, m.Config)
if err != nil {
logrus.Errorf("Error while validating resource: %v", err)
return nil, err
+17 -27
View File
@@ -17,8 +17,8 @@ package webhook
import (
"context"
"encoding/json"
"fmt"
"net/http"
"strings"
"github.com/fairwindsops/controller-utils/pkg/controller"
"github.com/fairwindsops/polaris/pkg/config"
@@ -53,7 +53,7 @@ func NewValidateWebhook(mgr manager.Manager, c config.Configuration) {
}
func (v *Validator) handleInternal(ctx context.Context, req admission.Request) (*validator.Result, kube.GenericResource, error) {
return GetValidatedResults(ctx, req.AdmissionRequest.Kind.Kind, v.decoder, req, v.Config)
return GetValidatedResults(ctx, req.Kind.Kind, v.decoder, req, v.Config)
}
// GetValidatedResults returns the validated results.
@@ -71,31 +71,20 @@ func GetValidatedResults(ctx context.Context, kind string, decoder *admission.De
return nil, resource, err
}
if ownerReferences, ok := decoded["metadata"].(map[string]any)["ownerReferences"].([]any); ok && len(ownerReferences) > 0 {
allOwnersReferenceValid := true
dynamicClient, restMapper, _, _, err := kube.GetKubeClient(context.Background(), "")
if err != nil {
logrus.Errorf("getting the kubernetes client: %v", err)
return nil, resource, err
}
for _, ownerReference := range ownerReferences {
ownerReference := ownerReference.(map[string]any)
ctrl, err := kube.GetObject(context.Background(), req.Namespace, ownerReference["kind"].(string), ownerReference["apiVersion"].(string), ownerReference["name"].(string), dynamicClient, restMapper)
if err != nil {
logrus.Infof("error retrieving owner for object %s - running checks: %v", req.Name, err)
allOwnersReferenceValid = false
break
} else {
err = controller.ValidateIfControllerMatches(decoded, ctrl.Object)
if err != nil {
logrus.Infof("object %s has an owner but the owner is invalid - running checks: %v", req.Name, err)
allOwnersReferenceValid = false
break
}
}
if allOwnersReferenceValid {
logrus.Infof("object %s has owner(s) and the owner(s) are valid - skipping", req.Name)
return nil, resource, nil
}
ownerReference := ownerReferences[0].(map[string]any)
ctrl, err := kube.GetObject(context.Background(), req.Namespace, ownerReference["kind"].(string), ownerReference["apiVersion"].(string), ownerReference["name"].(string), dynamicClient, restMapper)
if err != nil {
logrus.Infof("error retrieving owner for object %s - running checks: %v", req.Name, err)
} else if err := controller.ValidateIfControllerMatches(decoded, ctrl.Object); err != nil {
logrus.Infof("object %s has an owner but the owner is invalid - running checks: %v", req.Name, err)
} else {
logrus.Infof("object %s has owner(s) and the owner(s) are valid - skipping", req.Name)
return nil, resource, nil
}
} else {
logrus.Infof("Object %s has no owner - running checks", req.Name)
@@ -135,11 +124,12 @@ func (v *Validator) Handle(ctx context.Context, req admission.Request) admission
}
func getFailureReason(result validator.Result) string {
reason := "\nPolaris prevented this deployment due to configuration problems:\n"
var reason strings.Builder
reason.WriteString("\nPolaris prevented this deployment due to configuration problems:\n")
for _, message := range result.Results {
if !message.Success && message.Severity == config.SeverityDanger {
reason += fmt.Sprintf("- %s: %s\n", result.Kind, message.Message)
reason.WriteString("- " + result.Kind + ": " + message.Message + "\n")
}
}
@@ -147,18 +137,18 @@ func getFailureReason(result validator.Result) string {
if podResult != nil {
for _, message := range podResult.Results {
if !message.Success && message.Severity == config.SeverityDanger {
reason += fmt.Sprintf("- Pod: %s\n", message.Message)
reason.WriteString("- Pod: " + message.Message + "\n")
}
}
for _, containerResult := range podResult.ContainerResults {
for _, message := range containerResult.Results {
if !message.Success && message.Severity == config.SeverityDanger {
reason += fmt.Sprintf("- Container %s: %s\n", containerResult.Name, message.Message)
reason.WriteString("- Container " + containerResult.Name + ": " + message.Message + "\n")
}
}
}
}
return reason
return reason.String()
}
+97
View File
@@ -0,0 +1,97 @@
// DO NOT EDIT - Managed by Terraform
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:recommended",
":dependencyDashboard"
],
"labels": ["dependencies"],
"prConcurrentLimit": 10,
"branchConcurrentLimit": 10,
"minimumReleaseAge": "7 days",
"enabledManagers": [
"gomod",
"dockerfile",
"circleci",
"custom.regex"
],
"postUpdateOptions": ["gomodTidy", "gomodUpdateImportPaths"],
"customManagers": [
{
"description": "HashiCorp Vault download URL in CircleCI",
"customType": "regex",
"managerFilePatterns": ["/(^|/)\\.circleci/config\\.yml$/"],
"matchStrings": [
"https://releases\\.hashicorp\\.com/vault/(?<currentValue>[\\d.]+)/vault_[\\d.]+_linux_amd64\\.zip"
],
"depNameTemplate": "hashicorp/vault",
"datasourceTemplate": "hashicorp-releases",
"autoReplaceStringTemplate": "https://releases.hashicorp.com/vault/{{{newValue}}}/vault_{{{newValue}}}_linux_amd64.zip"
},
{
"description": "HashiCorp Vault archive name in CircleCI (sha256 line must be updated manually)",
"customType": "regex",
"managerFilePatterns": ["/(^|/)\\.circleci/config\\.yml$/"],
"matchStrings": [
"vault_(?<currentValue>[\\d.]+)_linux_amd64\\.zip"
],
"depNameTemplate": "hashicorp/vault",
"datasourceTemplate": "hashicorp-releases",
"autoReplaceStringTemplate": "vault_{{{newValue}}}_linux_amd64.zip"
},
{
"description": "golangci-lint install.sh pin in CircleCI",
"customType": "regex",
"managerFilePatterns": ["/(^|/)\\.circleci/config\\.yml$/"],
"matchStrings": [
"golangci/golangci-lint/HEAD/install.sh \\| sh -s -- -b /usr/local/bin v(?<currentValue>[0-9.]+)"
],
"depNameTemplate": "golangci/golangci-lint",
"datasourceTemplate": "github-releases",
"extractVersionTemplate": "^v?(?<version>.*)$",
"autoReplaceStringTemplate": "golangci/golangci-lint/HEAD/install.sh | sh -s -- -b /usr/local/bin v{{{newValue}}}"
},
{
"description": "Kind node image for rok8s-scripts kubernetes_e2e_tests",
"customType": "regex",
"managerFilePatterns": ["/(^|/)\\.circleci/config\\.yml$/"],
"matchStrings": [
"kind_node_image:\\s*\"kindest/node:(?<currentValue>v[^\"\\s]+)\""
],
"depNameTemplate": "kindest/node",
"datasourceTemplate": "docker",
"autoReplaceStringTemplate": "kind_node_image: \"kindest/node:{{{newValue}}}\""
}
],
"packageRules": [
{
"description": "Group all non-major (minor + patch) updates into a single PR",
"matchUpdateTypes": ["minor", "patch", "digest", "lockFileMaintenance"],
"groupName": "all non-major dependencies",
"groupSlug": "all-non-major",
"minimumReleaseAge": "7 days"
},
{
"description": "Keep major updates separate and also enforce minimum age",
"matchUpdateTypes": ["major"],
"minimumReleaseAge": "7 days"
},
{
"description": "Disable replace updates",
"matchManagers": ["gomod"],
"matchDepTypes": ["replace"],
"enabled": false
},
{
"description": "Every upgrade: upstream release must be at least 7 days old",
"matchManagers": ["gomod", "dockerfile", "circleci", "custom.regex"],
"minimumReleaseAge": "7 days"
},
{
"description": "Group HashiCorp Vault version bumps (sha256 checksum still needs manual update)",
"groupName": "HashiCorp Vault",
"matchManagers": ["custom.regex"],
"matchDepNames": ["hashicorp/vault"]
}
]
}
-63
View File
@@ -1,63 +0,0 @@
#!/usr/bin/env sh
# Wrap goreleaser by using envsubst on .goreleaser.yml,
# and creating a temporary git tag.
function cleanup {
if [ "${CIRCLE_TAG}" == "" ] ; then
echo "${this_script} deleting git tag ${temporary_git_tag} for goreleaser"
unset GORELEASER_CURRENT_TAG
git tag -d ${temporary_git_tag}
fi
}
set -eE # errexit and errtrace
trap 'cleanup' ERR
this_script="$(basename $0)"
hash envsubst
hash goreleaser
if [ "${TMPDIR}" == "" ] ; then
export TMPDIR="/tmp"
echo "${this_script} temporarily set the TMPDIR environment variable to ${TMPDIR}, used for a temporary GOBIN environment variable"
fi
export skip_feature_docker_tags=false
export skip_release=true
if [ "${CIRCLE_TAG}" == "" ] ; then
# Create a temporary tag for goreleaser, incrementing the last tag.
last_git_tag="$(git describe --tags --abbrev=0 2>/dev/null)"
if [ "${last_git_tag}" == "" ] ; then
echo "${this_script} is unable to determine the last git tag so a temporary tag can be created, using: git describe --tags --abbrev=0"
exit 1
fi
if [ "$(git config user.email)" == "" ] ; then
# git will use this env var as its user.email.
# git tag -m is used in case tags are manually pushed by accident,
# however git tag -m requires an email.
export EMAIL='goreleaser_ci@fairwinds.com'
echo "${this_script} using ${EMAIL} temporarily as the git user.email"
fi
temporary_git_tag=$(echo "${last_git_tag}" | awk -F. '{$NF = $NF + 1;} 1' | sed 's/ /./g')-rc
echo "${this_script} creating temporary git tag ${temporary_git_tag} for goreleaser, the last real tag is ${last_git_tag}"
# The -f is included to overwrite existing tags, perhaps from previous CI jobs.
git tag -f -m "temporary local tag for goreleaser" ${temporary_git_tag}
export GORELEASER_CURRENT_TAG=${temporary_git_tag}
# Use an adjusted git feature branch name as a docker tag.
export feature_docker_tag=$(echo "${CIRCLE_BRANCH:0:26}" | sed 's/[^a-zA-Z0-9]/-/g' | sed 's/-\+$//')
echo "${this_script} also using docker tag ${feature_docker_tag} since ${CIRCLE_BRANCH} is a feature branch"
else
export GORELEASER_CURRENT_TAG=${CIRCLE_TAG}
echo "${this_script} setting skip_release to false, and skip_feature_docker_tags to true, because CIRCLE_TAG is set"
export skip_feature_docker_tags=true
export skip_release=false
fi
echo "${this_script} using git tag ${GORELEASER_CURRENT_TAG}"
# Only substitute specific variables, as goreleaser uses shell variable syntax
# for its `signs` section `signature` and `artifact` variables.
cat .goreleaser.yml.envsubst |envsubst '${skip_release} ${skip_feature_docker_tags} ${feature_docker_tag}' >.goreleaser.yml
goreleaser --skip=sign $@
if [ $? -eq 0 ] ; then
echo "${this_script} removing the temporary .goreleaser.yml since goreleaser was successful"
rm .goreleaser.yml # Keep git clean for additional goreleaser runs
fi
cleanup
+11
View File
@@ -0,0 +1,11 @@
#!/bin/bash
set -euo pipefail
go run main.go dashboard --port 3000 --audit-path ./pkg/config/examples &
sleep 30
curl -f http://localhost:3000 > /dev/null
curl -f http://localhost:3000/health > /dev/null
curl -f http://localhost:3000/favicon.ico > /dev/null
curl -f http://localhost:3000/static/css/main.css > /dev/null
curl -f http://localhost:3000/results.json > /dev/null
curl -f http://localhost:3000/details/security > /dev/null
+12 -10
View File
@@ -32,12 +32,12 @@ import (
"k8s.io/client-go/kubernetes/fake"
)
func newUnstructured(apiVersion, kind, namespace, name string, spec map[string]interface{}) unstructured.Unstructured {
func newUnstructured(apiVersion, kind, namespace, name string, spec map[string]any) unstructured.Unstructured {
return unstructured.Unstructured{
Object: map[string]interface{}{
Object: map[string]any{
"apiVersion": apiVersion,
"kind": kind,
"metadata": map[string]interface{}{
"metadata": map[string]any{
"namespace": namespace,
"name": name,
},
@@ -85,7 +85,7 @@ func MockIngress() networkingv1.Ingress {
}
// MockController creates a mock controller and pod
func MockController(apiVersion, kind, namespace, name string, spec map[string]interface{}, podSpec corev1.PodSpec, dest interface{}) corev1.Pod {
func MockController(apiVersion, kind, namespace, name string, spec map[string]any, podSpec corev1.PodSpec, dest any) corev1.Pod {
unst := newUnstructured(apiVersion, kind, namespace, name, spec)
pod := corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
@@ -111,19 +111,19 @@ func MockController(apiVersion, kind, namespace, name string, spec map[string]in
}
// MockControllerWithNormalSpec mocks a controller with podspec at spec.template.spec
func MockControllerWithNormalSpec(apiVersion, kind, namespace, name string, dest interface{}) corev1.Pod {
func MockControllerWithNormalSpec(apiVersion, kind, namespace, name string, dest any) corev1.Pod {
p := MockPod()
b, err := json.Marshal(p.Spec)
if err != nil {
panic(err)
}
pSpec := map[string]interface{}{}
pSpec := map[string]any{}
err = json.Unmarshal(b, &pSpec)
if err != nil {
panic(err)
}
spec := map[string]interface{}{
"template": map[string]interface{}{
spec := map[string]any{
"template": map[string]any{
"spec": pSpec,
},
}
@@ -162,7 +162,7 @@ func MockJob(namespace, name string) (batchv1.Job, corev1.Pod) {
func MockCronJob(namespace, name string) (batchv1.CronJob, corev1.Pod) {
cj := batchv1.CronJob{}
p := MockPod()
spec := map[string]interface{}{}
spec := map[string]any{}
pod := MockController("batch/v1", "CronJob", namespace, name, spec, p.Spec, &cj)
cj.Spec.JobTemplate.Spec.Template.Spec = pod.Spec
@@ -188,7 +188,9 @@ func MockNamespace(name string) corev1.Namespace {
// SetupTestAPI creates a test kube API struct.
func SetupTestAPI(objects ...runtime.Object) (kubernetes.Interface, dynamic.Interface) {
scheme := runtime.NewScheme()
fake.AddToScheme(scheme)
if err := fake.AddToScheme(scheme); err != nil {
panic(err)
}
dynamicClient := dynamicFake.NewSimpleDynamicClient(scheme, objects...)
k := fake.NewSimpleClientset(objects...)
k.Resources = []*metav1.APIResourceList{
+3 -1
View File
@@ -22,7 +22,9 @@ function check_timeout() {
helm repo add fairwinds-stable https://charts.fairwinds.com/stable
helm install polaris fairwinds-stable/polaris --namespace polaris --create-namespace \
--set image.tag=$CI_SHA1
--set image.repository=us-docker.pkg.dev/fairwinds-ops/oss/polaris \
--set image.tag=$CI_SHA1 \
--set image.pullPolicy=IfNotPresent
check_dashboard_is_ready
+1 -1
View File
@@ -89,7 +89,7 @@ func initTestCases() ([]testCase, map[string]string, map[string][]testCase) {
if err != nil {
panic(err)
}
resourceFilename := strings.Replace(tc.Name(), "mutated", "failure", -1)
resourceFilename := strings.ReplaceAll(tc.Name(), "mutated", "failure")
resources, err := kube.CreateResourceProviderFromPath(checkDir + "/" + resourceFilename)
if err != nil {
+3 -1
View File
@@ -88,7 +88,9 @@ helm install polaris fairwinds-stable/polaris --namespace polaris --create-names
--set dashboard.enable=false \
--set webhook.enable=true \
--set webhook.mutate=true \
--set image.tag=$CI_SHA1
--set image.repository=us-docker.pkg.dev/fairwinds-ops/oss/polaris \
--set image.tag=$CI_SHA1 \
--set image.pullPolicy=IfNotPresent
echo "Waiting for the webhook to come online"
check_webhook_is_ready