mirror of
https://github.com/FairwindsOps/polaris.git
synced 2026-08-23 22:26:34 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4bdf5315b6 | ||
|
|
278cb9008c | ||
|
|
d6858e0fe4 | ||
|
|
78fe4cb11c | ||
|
|
387e787a40 | ||
|
|
1fdfec73a1 | ||
|
|
aee298e2e0 | ||
|
|
0444cf92bc | ||
|
|
50e4c63079 | ||
|
|
7d053a6cbd | ||
|
|
d50336387b | ||
|
|
1554c90ece | ||
|
|
9dfb4262e6 | ||
|
|
27878a4670 | ||
|
|
a46eeb28b6 | ||
|
|
fd862afadc | ||
|
|
ee9f568ebd | ||
|
|
47c7deddfd | ||
|
|
2007a2c317 | ||
|
|
a5af7bb34e | ||
|
|
893e5de589 | ||
|
|
f6eed6589d | ||
|
|
517c8899d7 | ||
|
|
e2df222ded | ||
|
|
d45d67cdd2 | ||
|
|
e8a113388e | ||
|
|
21ef1f495c | ||
|
|
ec1ba2f2db | ||
|
|
39fb27c6dd | ||
|
|
e51b90ef79 | ||
|
|
4de082b443 | ||
|
|
264dc51f4c | ||
|
|
45b1f6a1a5 | ||
|
|
38e540e0cf | ||
|
|
2b17c31957 | ||
|
|
813d9c0a2a | ||
|
|
d7d30ac611 | ||
|
|
8fe7a94260 | ||
|
|
f29571e8bb | ||
|
|
a74714b5e5 | ||
|
|
ba04b7e330 | ||
|
|
d70591b1b8 | ||
|
|
80e6f7214e | ||
|
|
1d20a16cb3 | ||
|
|
37edbc7159 | ||
|
|
d451480acf | ||
|
|
705e6e4016 | ||
|
|
2d9aa54e68 | ||
|
|
4dd3a81bbd | ||
|
|
4b87bafbb9 | ||
|
|
62effa36b6 | ||
|
|
913988e44c | ||
|
|
f281cd1e33 |
+115
-17
@@ -1,7 +1,7 @@
|
||||
version: 2.1
|
||||
|
||||
orbs:
|
||||
rok8s: fairwinds/rok8s-scripts@12
|
||||
rok8s: fairwinds/rok8s-scripts@14
|
||||
oss-docs: fairwinds/oss-docs@0
|
||||
|
||||
executors:
|
||||
@@ -13,12 +13,10 @@ 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
|
||||
- run: apk update
|
||||
# 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
|
||||
@@ -107,6 +105,26 @@ references:
|
||||
sha256sum vault_1.13.2_linux_amd64.zip | grep f7930279de8381de7c532164b4a4408895d9606c0d24e2e9d2f9acb5dfe99b3c
|
||||
unzip vault_1.13.2_linux_amd64.zip
|
||||
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
|
||||
load_polaris_image_into_kind: &load_polaris_image_into_kind
|
||||
run:
|
||||
name: Load Polaris snapshot image into Kind
|
||||
command: |
|
||||
TAR="/tmp/workspace/docker_save/polaris_${CIRCLE_SHA1}.tar"
|
||||
if [ -f "$TAR" ]; then
|
||||
echo "Loading snapshot image from $TAR"
|
||||
docker load -i "$TAR"
|
||||
kind load docker-image "us-docker.pkg.dev/fairwinds-ops/oss/polaris:${CIRCLE_SHA1}"
|
||||
else
|
||||
echo "No snapshot tar (tag workflow); tests will pull the image from the registry."
|
||||
fi
|
||||
jobs:
|
||||
test_k8s:
|
||||
working_directory: ~/polaris
|
||||
@@ -115,12 +133,15 @@ jobs:
|
||||
steps:
|
||||
- checkout
|
||||
- *set_environment_variables
|
||||
- attach_workspace:
|
||||
at: /tmp/workspace
|
||||
- *install_k8s
|
||||
- *load_polaris_image_into_kind
|
||||
- *test_k8s
|
||||
|
||||
test:
|
||||
docker:
|
||||
- image: cimg/go:1.22
|
||||
- image: cimg/go:1.26.2
|
||||
steps:
|
||||
- checkout
|
||||
- *set_environment_variables
|
||||
@@ -128,7 +149,7 @@ jobs:
|
||||
- run: go test ./... -coverprofile=coverage.txt -covermode=count
|
||||
- *test_binary_dashboard
|
||||
|
||||
build_and_push:
|
||||
release:
|
||||
working_directory: /go/src/github.com/fairwindsops/polaris/
|
||||
resource_class: large
|
||||
shell: /bin/bash
|
||||
@@ -136,7 +157,7 @@ jobs:
|
||||
# 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.3.2
|
||||
- image: goreleaser/goreleaser:v2.15.4
|
||||
steps:
|
||||
- checkout
|
||||
- setup_remote_docker
|
||||
@@ -146,35 +167,112 @@ jobs:
|
||||
- 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}"
|
||||
- run:
|
||||
name: docker login Google Artifact Registry
|
||||
command: |
|
||||
docker login -u _json_key -p "$(echo $GCP_ARTIFACTREADWRITE_JSON_KEY | base64 -d)" us-docker.pkg.dev
|
||||
- install_goreleaser_dependencies
|
||||
- run: scripts/goreleaser.sh
|
||||
- run: echo 'export GORELEASER_CURRENT_TAG="${CIRCLE_TAG}"' >> $BASH_ENV
|
||||
- run: goreleaser release --clean
|
||||
- run: mkdir -p /tmp/workspace/docker_save && touch /tmp/workspace/docker_save/.release-workspace-marker
|
||||
- persist_to_workspace:
|
||||
root: /tmp/workspace
|
||||
paths:
|
||||
- docker_save
|
||||
|
||||
snapshot:
|
||||
working_directory: /home/circleci/project
|
||||
machine:
|
||||
image: ubuntu-2204:current
|
||||
resource_class: large
|
||||
shell: /bin/bash
|
||||
steps:
|
||||
- checkout
|
||||
- *setup_qemu_binfmt
|
||||
- run:
|
||||
name: goreleaser release (snapshot, local only)
|
||||
environment:
|
||||
GORELEASER_SKIP_RELEASE: "true"
|
||||
command: |
|
||||
set -euo pipefail
|
||||
REPO_ROOT="${HOME}/project"
|
||||
docker run --rm \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||
-v "${REPO_ROOT}:/workspace" -w /workspace \
|
||||
-e GORELEASER_SKIP_RELEASE=true \
|
||||
-e CIRCLE_SHA1 \
|
||||
-e CIRCLE_BRANCH \
|
||||
-e CIRCLE_TAG \
|
||||
--entrypoint sh \
|
||||
goreleaser/goreleaser:v2.15.4 \
|
||||
-c 'git config --global --add safe.directory /workspace && cd /workspace && goreleaser release --snapshot --clean --skip=sign,homebrew'
|
||||
- run: mkdir -p /tmp/workspace/docker_save/
|
||||
- run:
|
||||
name: Tag and save amd64 image for Kind
|
||||
command: |
|
||||
set -euo pipefail
|
||||
docker tag "us-docker.pkg.dev/fairwinds-ops/oss/polaris:${CIRCLE_SHA1}-amd64" "us-docker.pkg.dev/fairwinds-ops/oss/polaris:${CIRCLE_SHA1}"
|
||||
docker save "us-docker.pkg.dev/fairwinds-ops/oss/polaris:${CIRCLE_SHA1}" -o "/tmp/workspace/docker_save/polaris_${CIRCLE_SHA1}.tar"
|
||||
- persist_to_workspace:
|
||||
root: /tmp/workspace
|
||||
paths:
|
||||
- docker_save
|
||||
- store_artifacts:
|
||||
path: dist
|
||||
destination: snapshot
|
||||
|
||||
workflows:
|
||||
version: 2
|
||||
|
||||
release:
|
||||
branch:
|
||||
jobs:
|
||||
- test:
|
||||
filters:
|
||||
tags:
|
||||
ignore: /.*/
|
||||
- snapshot:
|
||||
filters:
|
||||
branches:
|
||||
ignore: /pull\/[0-9]+/
|
||||
tags:
|
||||
ignore: /.*/
|
||||
- test_k8s:
|
||||
requires:
|
||||
- snapshot
|
||||
filters:
|
||||
branches:
|
||||
ignore: /pull\/[0-9]+/
|
||||
tags:
|
||||
ignore: /.*/
|
||||
|
||||
tag_release:
|
||||
jobs:
|
||||
- test:
|
||||
filters:
|
||||
tags:
|
||||
only: /.*/
|
||||
- build_and_push:
|
||||
branches:
|
||||
ignore: /.*/
|
||||
- release:
|
||||
requires:
|
||||
- test
|
||||
context: org-global
|
||||
filters:
|
||||
branches:
|
||||
ignore: /pull\/[0-9]+/
|
||||
ignore: /.*/
|
||||
tags:
|
||||
ignore: /^testing-.*/
|
||||
- test_k8s:
|
||||
requires:
|
||||
- build_and_push
|
||||
- release
|
||||
filters:
|
||||
branches:
|
||||
ignore: /pull\/[0-9]+/
|
||||
ignore: /.*/
|
||||
tags:
|
||||
ignore: /^testing-.*/
|
||||
- oss-docs/publish-docs:
|
||||
requires:
|
||||
- test_k8s
|
||||
repository: polaris
|
||||
filters:
|
||||
branches:
|
||||
|
||||
@@ -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.17
|
||||
FROM alpine:3.23
|
||||
|
||||
RUN apk add --no-cache \
|
||||
bash \
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
@@ -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"
|
||||
@@ -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
@@ -1,4 +1,4 @@
|
||||
FROM alpine:3.20
|
||||
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
|
||||
|
||||
@@ -29,19 +29,50 @@ 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
|
||||
|
||||
The goal of the Fairwinds Community is to exchange ideas, influence the open source roadmap,
|
||||
and network with fellow Kubernetes users.
|
||||
[Chat with us on Slack](https://join.slack.com/t/fairwindscommunity/shared_invite/zt-2na8gtwb4-DGQ4qgmQbczQyB2NlFlYQQ)
|
||||
or
|
||||
[join the user group](https://www.fairwinds.com/open-source-software-user-group) to get involved!
|
||||
|
||||
<a href="https://insights.fairwinds.com/auth/register/">
|
||||
<img src="https://www.fairwinds.com/hubfs/Doc_Banners/Fairwinds_OSS_User_Group_740x125_v6.png"
|
||||
alt="Love Fairwinds Open Source? Automate Fairwinds Open Source for free with Fairwinds Insights. Click to learn more" />
|
||||
</a>
|
||||
|
||||
## Other Projects from Fairwinds
|
||||
|
||||
@@ -56,9 +87,5 @@ Or [check out the full list](https://www.fairwinds.com/open-source-software?utm_
|
||||
If you're interested in running Polaris in multiple clusters,
|
||||
tracking the results over time, integrating with Slack, Datadog, and Jira,
|
||||
or unlocking other functionality, check out
|
||||
[Fairwinds Insights](https://fairwinds.com/pricing),
|
||||
[Fairwinds Insights](https://fairwinds.com/insights),
|
||||
a platform for auditing and enforcing policy in Kubernetes clusters.
|
||||
|
||||
<a href="https://fairwinds.com/pricing">
|
||||
<img src="https://www.fairwinds.com/hubfs/Doc_Banners/Fairwinds_Polaris_Ad.png" alt="Fairwinds Insights" />
|
||||
</a>
|
||||
|
||||
+3
-68
@@ -24,14 +24,8 @@ import (
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
|
||||
workloads "github.com/fairwindsops/insights-plugins/plugins/workloads"
|
||||
workloadsPkg "github.com/fairwindsops/insights-plugins/plugins/workloads/pkg"
|
||||
|
||||
"github.com/fairwindsops/polaris/pkg/auth"
|
||||
cfg "github.com/fairwindsops/polaris/pkg/config"
|
||||
"github.com/fairwindsops/polaris/pkg/insights"
|
||||
"github.com/fairwindsops/polaris/pkg/kube"
|
||||
"github.com/fairwindsops/polaris/pkg/validator"
|
||||
"github.com/sirupsen/logrus"
|
||||
@@ -55,9 +49,6 @@ var (
|
||||
auditNamespace string
|
||||
severityLevel string
|
||||
skipSslValidation bool
|
||||
uploadInsights bool
|
||||
clusterName string
|
||||
quiet bool
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -79,9 +70,6 @@ func init() {
|
||||
auditCmd.PersistentFlags().StringVar(&auditNamespace, "namespace", "", "Namespace to audit. Only applies to in-cluster audits")
|
||||
auditCmd.PersistentFlags().StringVar(&severityLevel, "severity", "", "Severity level used to filter results. Behaves like log levels. 'danger' is the least verbose (warning, danger)")
|
||||
auditCmd.PersistentFlags().BoolVar(&skipSslValidation, "skip-ssl-validation", false, "Skip https certificate verification")
|
||||
auditCmd.PersistentFlags().BoolVar(&uploadInsights, "upload-insights", false, "Upload scan results to Fairwinds Insights")
|
||||
auditCmd.PersistentFlags().StringVar(&clusterName, "cluster-name", "", "Set --cluster-name to a descriptive name for the cluster you're auditing")
|
||||
auditCmd.PersistentFlags().BoolVar(&quiet, "quiet", false, "Suppress the 'upload to Insights' prompt.")
|
||||
}
|
||||
|
||||
var auditCmd = &cobra.Command{
|
||||
@@ -120,23 +108,6 @@ var auditCmd = &cobra.Command{
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
if uploadInsights && len(clusterName) == 0 {
|
||||
logrus.Error("cluster-name is required when using --upload-insights")
|
||||
os.Exit(1)
|
||||
}
|
||||
if uploadInsights {
|
||||
if auditPath != "" {
|
||||
logrus.Errorf("upload-insights and audit-path are not supported when used simultaneously")
|
||||
os.Exit(1)
|
||||
}
|
||||
if !auth.IsLoggedIn() {
|
||||
err := auth.HandleLogin(insightsHost)
|
||||
if err != nil {
|
||||
logrus.Errorf("error handling logging: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ctx := context.TODO()
|
||||
k, err := kube.CreateResourceProvider(ctx, auditPath, resourceToAudit, config)
|
||||
@@ -145,49 +116,13 @@ var auditCmd = &cobra.Command{
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
auditData, err := validator.RunAudit(config, k)
|
||||
auditData, err := validator.RunAudit(context.Background(), config, k)
|
||||
if err != nil {
|
||||
logrus.Errorf("Error while running audit on resources: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if uploadInsights {
|
||||
auth, err := auth.GetAuth(insightsHost)
|
||||
if err != nil {
|
||||
logrus.Errorf("getting auth: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
// fetch workloads using workload plugin... or should we adapt the workloads from above?
|
||||
dynamicClient, restMapper, clientSet, host, err := kube.GetKubeClient(ctx, "")
|
||||
if err != nil {
|
||||
logrus.Errorf("getting the kubernetes client: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
k8sResources, err := workloadsPkg.CreateResourceProviderFromAPI(ctx, dynamicClient, restMapper, clientSet, host)
|
||||
if err != nil {
|
||||
logrus.Errorf("creating resource provider: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
insightsClient := insights.NewHTTPClient(insightsHost, auth.Organization, auth.Token)
|
||||
insightsReporter := insights.NewInsightsReporter(insightsClient)
|
||||
wr := insights.WorkloadsReport{Version: workloads.Version, Payload: *k8sResources}
|
||||
pr := insights.PolarisReport{Version: version, Payload: auditData}
|
||||
logrus.Infof("Uploading to Fairwinds Insights organization '%s/%s'...", auth.Organization, clusterName)
|
||||
err = insightsReporter.ReportAuditToFairwindsInsights(clusterName, wr, pr)
|
||||
if err != nil {
|
||||
logrus.Errorf("reporting audit file to insights: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
os.Stderr.WriteString("\n\nSuccess! You can see your results at:")
|
||||
os.Stderr.WriteString(fmt.Sprintf("\n\n%s/orgs/%s/clusters/%s/action-items\n\n", insightsHost, auth.Organization, clusterName))
|
||||
} else {
|
||||
outputAudit(auditData, auditOutputFile, auditOutputURL, auditOutputFormat, useColor, onlyShowFailedTests, severityLevel)
|
||||
if !quiet {
|
||||
os.Stderr.WriteString("\n\n🚀 Upload your Polaris findings to Fairwinds Insights to see remediation advice, add teammates, integrate with Slack or Jira, and more:")
|
||||
os.Stderr.WriteString("\n\n❯ polaris " + strings.Join(os.Args[1:], " ") + " --upload-insights --cluster-name=my-cluster\n\n")
|
||||
}
|
||||
}
|
||||
outputAudit(auditData, auditOutputFile, auditOutputURL, auditOutputFormat, useColor, onlyShowFailedTests, severityLevel)
|
||||
|
||||
summary := auditData.GetSummary()
|
||||
score := summary.GetScore()
|
||||
@@ -255,7 +190,7 @@ 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()))
|
||||
outputBytes = fmt.Appendf(nil, "%d\n", auditData.GetSummary().GetScore())
|
||||
} else if outputFormat == "yaml" {
|
||||
var jsonBytes []byte
|
||||
jsonBytes, err = json.Marshal(auditData)
|
||||
|
||||
@@ -1,81 +0,0 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"github.com/fairwindsops/polaris/pkg/auth"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(authCmd)
|
||||
authCmd.AddCommand(loginCmd)
|
||||
authCmd.AddCommand(logoutCmd)
|
||||
authCmd.AddCommand(statusCmd)
|
||||
authCmd.AddCommand(tokenCmd)
|
||||
}
|
||||
|
||||
var authCmd = &cobra.Command{
|
||||
Use: "auth",
|
||||
Short: "Authenticate polaris with Fairwinds Insights",
|
||||
Long: `Authenticate polaris with Fairwinds Insights so better experience`,
|
||||
}
|
||||
|
||||
var loginCmd = &cobra.Command{
|
||||
Use: "login",
|
||||
Short: "Authenticate polaris with Fairwinds Insights.",
|
||||
Long: `Authenticate polaris with Fairwinds Insights.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
err := auth.HandleLogin(insightsHost)
|
||||
if err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
},
|
||||
PersistentPostRunE: func(cmd *cobra.Command, args []string) error {
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var logoutCmd = &cobra.Command{
|
||||
Use: "logout",
|
||||
Short: "Log out of a Fairwinds Insights.",
|
||||
Long: `Log out of a Fairwinds Insights.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
err := auth.HandleLogout()
|
||||
if err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
},
|
||||
PersistentPostRunE: func(cmd *cobra.Command, args []string) error {
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var statusCmd = &cobra.Command{
|
||||
Use: "status",
|
||||
Short: "View authentication status.",
|
||||
Long: `View authentication status.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
err := auth.PrintStatus(insightsHost)
|
||||
if err != nil {
|
||||
logrus.Fatalf("printing status: %v", err)
|
||||
}
|
||||
},
|
||||
PersistentPostRunE: func(cmd *cobra.Command, args []string) error {
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var tokenCmd = &cobra.Command{
|
||||
Use: "token",
|
||||
Short: "Print the auth token gh is configured to use.",
|
||||
Long: `Print the auth token gh is configured to use.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
err := auth.PrintToken()
|
||||
if err != nil {
|
||||
logrus.Fatalf("printing token: %v", err)
|
||||
}
|
||||
},
|
||||
PersistentPostRunE: func(cmd *cobra.Command, args []string) error {
|
||||
return nil
|
||||
},
|
||||
}
|
||||
@@ -15,6 +15,7 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
@@ -54,7 +55,7 @@ var dashboardCmd = &cobra.Command{
|
||||
auditData := validator.ReadAuditFromFile(loadAuditFile)
|
||||
auditDataPtr = &auditData
|
||||
}
|
||||
router, err := dashboard.GetRouter(config, auditPath, serverPort, basePath, auditDataPtr)
|
||||
router, err := dashboard.GetRouter(context.Background(), config, auditPath, serverPort, basePath, auditDataPtr)
|
||||
if err != nil {
|
||||
logrus.Fatalf("error creating router: %v", err)
|
||||
}
|
||||
|
||||
+2
-1
@@ -15,6 +15,7 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"os"
|
||||
|
||||
@@ -43,7 +44,7 @@ var fixCommand = &cobra.Command{
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
logrus.Debug("Setting up controller manager")
|
||||
|
||||
err := fix.Execute(config, filesPath, isTemplate, checksToFix...)
|
||||
err := fix.Execute(context.Background(), config, filesPath, isTemplate, checksToFix...)
|
||||
if err != nil {
|
||||
if errors.Is(err, fix.ErrFilesPathRequired) {
|
||||
logrus.Error("Please specify a files-path flag")
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
@@ -76,7 +77,7 @@ var webhookCmd = &cobra.Command{
|
||||
fwebhook.NewValidateWebhook(mgr, config)
|
||||
}
|
||||
if enableMutations {
|
||||
fwebhook.NewMutateWebhook(mgr, config)
|
||||
fwebhook.NewMutateWebhook(context.Background(), mgr, config)
|
||||
}
|
||||
logrus.Infof("Polaris webhook server listening on port %d", webhookPort)
|
||||
if err := mgr.Start(signals.SetupSignalHandler()); err != nil {
|
||||
|
||||
-16
@@ -9,8 +9,6 @@ meta:
|
||||
# top-level commands
|
||||
audit
|
||||
Runs a one-time audit.
|
||||
auth
|
||||
Authenticate polaris with Fairwinds Insights
|
||||
dashboard
|
||||
Runs the webserver for Polaris dashboard.
|
||||
fix
|
||||
@@ -44,7 +42,6 @@ webhook
|
||||
# audit flags
|
||||
--audit-path string If specified, audits one or more YAML files instead of a cluster.
|
||||
--checks strings Optional flag to specify specific checks to check
|
||||
--cluster-name string Set --cluster-name to a descriptive name for the cluster you're auditing
|
||||
--color Whether to use color in pretty format. (default true)
|
||||
--display-name string An optional identifier for the audit.
|
||||
-f, --format string Output format for results - json, yaml, pretty, or score. (default "json")
|
||||
@@ -56,13 +53,11 @@ webhook
|
||||
--only-show-failed-tests If specified, audit output will only show failed tests.
|
||||
--output-file string Destination file for audit results.
|
||||
--output-url string Destination URL to send audit results.
|
||||
--quiet Suppress the 'upload to Insights' prompt.
|
||||
--resource string Audit a specific resource, in the format namespace/kind/version/name, e.g. nginx-ingress/Deployment.apps/v1/default-backend.
|
||||
--set-exit-code-below-score int Set an exit code of 4 when the score is below this threshold (1-100).
|
||||
--set-exit-code-on-danger Set an exit code of 3 when the audit contains danger-level issues.
|
||||
--severity string Severity level used to filter results. Behaves like log levels. 'danger' is the least verbose (warning, danger)
|
||||
--skip-ssl-validation Skip https certificate verification
|
||||
--upload-insights Upload scan results to Fairwinds Insights
|
||||
|
||||
# fix flags
|
||||
--checks strings Optional flag to specify specific checks to fix eg. checks=hostIPCSet,hostPIDSet and checks=all applies fix to all defined checks mutations
|
||||
@@ -76,15 +71,4 @@ webhook
|
||||
-h, --help help for webhook
|
||||
-p, --port int Port for the dashboard webserver. (default 9876)
|
||||
|
||||
# auth sub-commands
|
||||
login Authenticate polaris with Fairwinds Insights.
|
||||
logout Log out of a Fairwinds Insights.
|
||||
status View authentication status.
|
||||
token Print the auth token gh is configured to use.
|
||||
```
|
||||
|
||||
#### Suppressing 'upload to Insights' output
|
||||
|
||||
When running the `polaris audit` subcommand, you can suppress the following output using the `--quiet flag`:
|
||||
|
||||
> 🚀 Upload your Polaris findings to Fairwinds Insights to see remediation advice, add teammates, integrate with Slack or Jira, and more:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -25,7 +25,7 @@ customChecks:
|
||||
category: Security
|
||||
target: Container
|
||||
schema:
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
'$schema': https://json-schema.org/draft/2019-09/schema
|
||||
type: object
|
||||
properties:
|
||||
image:
|
||||
@@ -73,7 +73,7 @@ customChecks:
|
||||
category: Resources
|
||||
target: Container
|
||||
schema:
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
'$schema': https://json-schema.org/draft/2019-09/schema
|
||||
type: object
|
||||
required:
|
||||
- resources
|
||||
@@ -120,7 +120,7 @@ successMessage: Label app.kubernetes.io/name matches metadata.name
|
||||
failureMessage: Label app.kubernetes.io/name must match metadata.name
|
||||
target: Controller
|
||||
schema:
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
'$schema': https://json-schema.org/draft/2019-09/schema
|
||||
type: object
|
||||
properties:
|
||||
metadata:
|
||||
@@ -193,7 +193,7 @@ controllers:
|
||||
include:
|
||||
- Deployment
|
||||
schema:
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
'$schema': https://json-schema.org/draft/2019-09/schema
|
||||
type: object
|
||||
properties:
|
||||
metadata:
|
||||
@@ -233,7 +233,7 @@ customChecks:
|
||||
foo:
|
||||
jsonSchema: |
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema",
|
||||
"$schema": "https://json-schema.org/draft/2019-09/schema",
|
||||
"type": "object"
|
||||
}
|
||||
```
|
||||
|
||||
+1
-1
@@ -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
|
||||
|
||||
@@ -4,4 +4,4 @@ options:
|
||||
|
||||
images:
|
||||
docker:
|
||||
- quay.io/fairwinds/polaris:$CI_SHA1
|
||||
- us-docker.pkg.dev/fairwinds-ops/oss/polaris:$CI_SHA1
|
||||
|
||||
@@ -1,83 +1,86 @@
|
||||
module github.com/fairwindsops/polaris
|
||||
|
||||
go 1.22.7
|
||||
go 1.26.2
|
||||
|
||||
require (
|
||||
github.com/AlecAivazis/survey/v2 v2.3.7
|
||||
github.com/fairwindsops/controller-utils v0.3.4
|
||||
github.com/fairwindsops/insights-plugins/plugins/workloads v0.0.0-20240917173116-506f92bdf9a0
|
||||
github.com/fatih/color v1.17.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/jsonschema v0.1.2
|
||||
github.com/sirupsen/logrus v1.9.3
|
||||
github.com/spf13/cobra v1.8.1
|
||||
github.com/stretchr/testify v1.9.0
|
||||
github.com/qri-io/jsonpointer v0.1.1
|
||||
github.com/qri-io/jsonschema v0.2.1
|
||||
github.com/sirupsen/logrus v1.9.4
|
||||
github.com/spf13/cobra v1.10.2
|
||||
github.com/stretchr/testify v1.11.1
|
||||
github.com/thoas/go-funk v0.9.3
|
||||
gomodules.xyz/jsonpatch/v2 v2.4.0
|
||||
gomodules.xyz/jsonpatch/v2 v2.5.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
k8s.io/api v0.31.1
|
||||
k8s.io/apimachinery v0.31.1
|
||||
k8s.io/client-go v0.31.1
|
||||
sigs.k8s.io/controller-runtime v0.19.0
|
||||
sigs.k8s.io/yaml v1.4.0
|
||||
k8s.io/api v0.35.4
|
||||
k8s.io/apimachinery v0.36.2
|
||||
k8s.io/client-go v0.35.4
|
||||
sigs.k8s.io/controller-runtime v0.23.3
|
||||
sigs.k8s.io/yaml v1.6.0
|
||||
)
|
||||
|
||||
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.11.3 // indirect
|
||||
github.com/evanphx/json-patch/v5 v5.9.0 // indirect
|
||||
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
||||
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
|
||||
github.com/go-logr/logr v1.4.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.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.20.3 // indirect
|
||||
github.com/go-openapi/jsonreference v0.20.5 // indirect
|
||||
github.com/go-openapi/swag v0.22.10 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
github.com/golang/protobuf v1.5.4 // indirect
|
||||
github.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49 // indirect
|
||||
github.com/google/go-cmp v0.6.0 // indirect
|
||||
github.com/google/gofuzz v1.2.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/imdario/mergo v0.3.16 // 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/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // 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/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // 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.19.1 // indirect
|
||||
github.com/prometheus/client_model v0.6.1 // indirect
|
||||
github.com/prometheus/common v0.55.0 // indirect
|
||||
github.com/prometheus/procfs v0.15.1 // indirect
|
||||
github.com/qri-io/jsonpointer v0.1.1 // indirect
|
||||
github.com/samber/lo v1.46.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/prometheus/client_golang v1.23.2 // indirect
|
||||
github.com/prometheus/client_model v0.6.2 // indirect
|
||||
github.com/prometheus/common v0.67.5 // indirect
|
||||
github.com/prometheus/procfs v0.20.1 // indirect
|
||||
github.com/samber/lo v1.53.0 // indirect
|
||||
github.com/spf13/pflag v1.0.10 // indirect
|
||||
github.com/x448/float16 v0.8.4 // indirect
|
||||
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 // indirect
|
||||
golang.org/x/net v0.26.0 // indirect
|
||||
golang.org/x/oauth2 v0.21.0 // indirect
|
||||
golang.org/x/sys v0.21.0 // indirect
|
||||
golang.org/x/term v0.21.0 // indirect
|
||||
golang.org/x/text v0.16.0 // indirect
|
||||
golang.org/x/time v0.5.0 // indirect
|
||||
google.golang.org/protobuf v1.34.2 // 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.4 // indirect
|
||||
golang.org/x/net v0.55.0 // indirect
|
||||
golang.org/x/oauth2 v0.36.0 // indirect
|
||||
golang.org/x/sys v0.45.0 // indirect
|
||||
golang.org/x/term v0.43.0 // indirect
|
||||
golang.org/x/text v0.37.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
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
k8s.io/klog/v2 v2.130.1 // indirect
|
||||
k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect
|
||||
k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 // indirect
|
||||
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect
|
||||
k8s.io/apiextensions-apiserver v0.35.4 // 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/v6 v6.3.2 // indirect
|
||||
)
|
||||
|
||||
@@ -1,256 +1,221 @@
|
||||
github.com/AlecAivazis/survey/v2 v2.3.7 h1:6I/u8FvytdGsgonrYsVn2t8t4QiRnh6QSTqkkhIiSjQ=
|
||||
github.com/AlecAivazis/survey/v2 v2.3.7/go.mod h1:xUTIdE4KCOIjsBAE1JYsUPoCqYdZ1reCfTwbto0Fduo=
|
||||
github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 h1:+vx7roKuyA63nhn5WAunQHLTznkw5W8b1Xc0dNjp83s=
|
||||
github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDeC1lPdgDeDbhX8XFpy1jqjK0IBG8W5K+xYqA0w=
|
||||
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/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.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/creack/pty v1.1.17 h1:QeVUsEDNrLBW4tMgZHvxy18sKtr6VI492kBhUfhDJNI=
|
||||
github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
|
||||
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.11.3 h1:yagOQz/38xJmcNeZJtrUcKjkHRltIaIFXKWeG1SkWGE=
|
||||
github.com/emicklei/go-restful/v3 v3.11.3/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.0 h1:kcBlZQbplgElYIlo/n1hJbls2z/1awpXxpRi0/FOJfg=
|
||||
github.com/evanphx/json-patch/v5 v5.9.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ=
|
||||
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/fairwindsops/insights-plugins/plugins/workloads v0.0.0-20240917173116-506f92bdf9a0 h1:7adPvardRgDkZlEfZJwNzfT25bZ9KY2odhmZeud19ds=
|
||||
github.com/fairwindsops/insights-plugins/plugins/workloads v0.0.0-20240917173116-506f92bdf9a0/go.mod h1:J+1LlUXLrbYbCzk/oxe9NLTToUWtCRzfM3akvOV+VxM=
|
||||
github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4=
|
||||
github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI=
|
||||
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
|
||||
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
|
||||
github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=
|
||||
github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
|
||||
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.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.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
||||
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/go-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.20.3 h1:jykzYWS/kyGtsHfRt6aV8JTB9pcQAXPIA7qlZ5aRlyk=
|
||||
github.com/go-openapi/jsonpointer v0.20.3/go.mod h1:c7l0rjoouAuIxCm8v/JWKRgMjDG/+/7UBWsXMrv6PsM=
|
||||
github.com/go-openapi/jsonreference v0.20.5 h1:hutI+cQI+HbSQaIGSfsBsYI0pHk+CATf8Fk5gCSj0yI=
|
||||
github.com/go-openapi/jsonreference v0.20.5/go.mod h1:thAqAp31UABtI+FQGKAQfmv7DbFpKNUlva2UPCxKu2Y=
|
||||
github.com/go-openapi/swag v0.22.10 h1:4y86NVn7Z2yYd6pfS4Z+Nyh3aAUL3Nul+LMbhFKy0gA=
|
||||
github.com/go-openapi/swag v0.22.10/go.mod h1:Cnn8BYtRlx6BNE3DPN86f/xkapGIcLWzh3CLEb4C1jI=
|
||||
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/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
github.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49 h1:0VpGH+cDhbDtdcweoyCVsF3fhN8kejK6rFe/2FFX2nU=
|
||||
github.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49/go.mod h1:BkkQ4L1KS1xMt2aWSPStnn55ChGC0DPOn2FQYj+f25M=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/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.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-20240525223248-4bfdf5a9a2af h1:kmjWCqn2qkEml422C2Rrd27c3VGxi6a/6HNq8QmHRKM=
|
||||
github.com/google/pprof v0.0.0-20240525223248-4bfdf5a9a2af/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo=
|
||||
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/hinshun/vt10x v0.0.0-20220119200601-820417d04eec h1:qv2VnGeEQHchGaZ/u7lxST/RaJw+cv273q79D81Xbog=
|
||||
github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec/go.mod h1:Q48J4R4DvxnHolD5P8pOtXigYlRuPLGl6moFx3ulM68=
|
||||
github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4=
|
||||
github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY=
|
||||
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/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
|
||||
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=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
||||
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4=
|
||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
|
||||
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.19.0 h1:9Cnnf7UHo57Hy3k6/m5k3dRfGTMXGvxhHFvkDTCTpvA=
|
||||
github.com/onsi/ginkgo/v2 v2.19.0/go.mod h1:rlwLi9PilAFJ8jCg9UE1QP6VBpd6/xj3SRC0d6TU0To=
|
||||
github.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk=
|
||||
github.com/onsi/gomega v1.33.1/go.mod h1:U4R44UsT+9eLIaYRB2a5qajjtQYn0hauxvRm16AVYg0=
|
||||
github.com/onsi/ginkgo/v2 v2.27.2 h1:LzwLj0b89qtIy6SSASkzlNvX6WktqurSHwkk2ipF/Ns=
|
||||
github.com/onsi/ginkgo/v2 v2.27.2/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo=
|
||||
github.com/onsi/gomega v1.38.2 h1:eZCjf2xjZAqe+LeWvKb5weQ+NcPwX84kqJ0cZNxok2A=
|
||||
github.com/onsi/gomega v1.38.2/go.mod h1:W2MJcYxRGV63b418Ai34Ud0hEdTVXq9NW9+Sx6uXf3k=
|
||||
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.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE=
|
||||
github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho=
|
||||
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
|
||||
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
|
||||
github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc=
|
||||
github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8=
|
||||
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
|
||||
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
|
||||
github.com/qri-io/jsonpointer v0.1.0/go.mod h1:DnJPaYgiKu56EuDp8TU5wFLdZIcAnb/uH9v37ZaMV64=
|
||||
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.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.1.2 h1:JlI7JAlxBbxh5Y641ctf+3kxcwYM6QbKDiqiQRkIBr0=
|
||||
github.com/qri-io/jsonschema v0.1.2/go.mod h1:SiF7DGMMKfw3cPrKZErviQKaUGserd2PYMOGm0vrELU=
|
||||
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
|
||||
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
|
||||
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.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.46.0 h1:w8G+oaCPgz1PoCJztqymCFaKwXt+5cCXn51uPxExFfQ=
|
||||
github.com/samber/lo v1.46.0/go.mod h1:RmDH9Ct32Qy3gduHQuKJ3gW1fMHAnE/fAzQuf6He5cU=
|
||||
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.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
|
||||
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w=
|
||||
github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g=
|
||||
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/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.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/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=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
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=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 h1:LfspQV/FYTatPTr/3HzIcmiUFH7PGP+OQ6mgDYo3yuQ=
|
||||
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc=
|
||||
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/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
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.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
|
||||
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
|
||||
golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs=
|
||||
golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
||||
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.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/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-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ=
|
||||
go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ=
|
||||
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
||||
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||
golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM=
|
||||
golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU=
|
||||
golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8=
|
||||
golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww=
|
||||
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.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
|
||||
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
|
||||
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA=
|
||||
golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0=
|
||||
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.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
|
||||
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
|
||||
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
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.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||
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=
|
||||
gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw=
|
||||
gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY=
|
||||
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
|
||||
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
|
||||
golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY=
|
||||
golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/term v0.43.0 h1:S4RLU2sB31O/NCl+zFN9Aru9A/Cq2aqKpTZJ6B+DwT4=
|
||||
golang.org/x/term v0.43.0/go.mod h1:lrhlHNdQJHO+1qVYiHfFKVuVioJIheAc3fBSMFYEIsk=
|
||||
golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc=
|
||||
golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38=
|
||||
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.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c=
|
||||
golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI=
|
||||
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.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.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
k8s.io/api v0.31.1 h1:Xe1hX/fPW3PXYYv8BlozYqw63ytA92snr96zMW9gWTU=
|
||||
k8s.io/api v0.31.1/go.mod h1:sbN1g6eY6XVLeqNsZGLnI5FwVseTrZX7Fv3O26rhAaI=
|
||||
k8s.io/apiextensions-apiserver v0.31.0 h1:fZgCVhGwsclj3qCw1buVXCV6khjRzKC5eCFt24kyLSk=
|
||||
k8s.io/apiextensions-apiserver v0.31.0/go.mod h1:b9aMDEYaEe5sdK+1T0KU78ApR/5ZVp4i56VacZYEHxk=
|
||||
k8s.io/apimachinery v0.31.1 h1:mhcUBbj7KUjaVhyXILglcVjuS4nYXiwC+KKFBgIVy7U=
|
||||
k8s.io/apimachinery v0.31.1/go.mod h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsMo=
|
||||
k8s.io/client-go v0.31.1 h1:f0ugtWSbWpxHR7sjVpQwuvw9a3ZKLXX0u0itkFXufb0=
|
||||
k8s.io/client-go v0.31.1/go.mod h1:sKI8871MJN2OyeqRlmA4W4KM9KBdBUpDLu/43eGemCg=
|
||||
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-20240228011516-70dd3763d340 h1:BZqlfIlq5YbRMFko6/PM7FjZpUb45WallggurYhKGag=
|
||||
k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340/go.mod h1:yD4MZYeKMBwQKVht279WycxKyM84kkAx2DPrTXaeb98=
|
||||
k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 h1:pUdcCO1Lk/tbT5ztQWOBi5HBgbBP1J8+AsQnQCKsi8A=
|
||||
k8s.io/utils v0.0.0-20240711033017-18e509b52bc8/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
|
||||
sigs.k8s.io/controller-runtime v0.19.0 h1:nWVM7aq+Il2ABxwiCizrVDSlmDcshi9llbaFbC0ji/Q=
|
||||
sigs.k8s.io/controller-runtime v0.19.0/go.mod h1:iRmWllt8IlaLjvTTDLhRBXIEtkCK6hwVBJJsYS9Ajf4=
|
||||
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo=
|
||||
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4=
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08=
|
||||
sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
|
||||
sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=
|
||||
k8s.io/api v0.35.4 h1:P7nFYKl5vo9AGUp1Z+Pmd3p2tA7bX2wbFWCvDeRv988=
|
||||
k8s.io/api v0.35.4/go.mod h1:yl4lqySWOgYJJf9RERXKUwE9g2y+CkuwG+xmcOK8wXU=
|
||||
k8s.io/apiextensions-apiserver v0.35.4 h1:HeP+Upp7ItdvnyGmub0yoix+2z5+ev4M5cE5TCgtOUU=
|
||||
k8s.io/apiextensions-apiserver v0.35.4/go.mod h1:ogQlk+stIE8mnoRthSYCwlOS12fVqgWFiErMwPaXA7c=
|
||||
k8s.io/apimachinery v0.36.2 h1:0PE/W/WNy1UX61NLbXY5TMbJ6UwLL6E6lAPkYrKFxbQ=
|
||||
k8s.io/apimachinery v0.36.2/go.mod h1:fvf/HOLXq9RId0rnDIbN1OEBvHXdQbLMM8nu0LcBUf4=
|
||||
k8s.io/client-go v0.35.4 h1:DN6fyaGuzK64UvnKO5fOA6ymSjvfGAnCAHAR0C66kD8=
|
||||
k8s.io/client-go v0.35.4/go.mod h1:2Pg9WpsS4NeOpoYTfHHfMxBG8zFMSAUi4O/qoiJC3nY=
|
||||
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.23.3 h1:VjB/vhoPoA9l1kEKZHBMnQF33tdCLQKJtydy4iqwZ80=
|
||||
sigs.k8s.io/controller-runtime v0.23.3/go.mod h1:B6COOxKptp+YaUT5q4l6LqUJTRpizbgf9KSRNdQGns0=
|
||||
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/v6 v6.3.2 h1:kwVWMx5yS1CrnFWA/2QHyRVJ8jM6dBA80uLmm0wJkk8=
|
||||
sigs.k8s.io/structured-merge-diff/v6 v6.3.2/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE=
|
||||
sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs=
|
||||
sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4=
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
var userHomeDir string
|
||||
var polarisHostsFilepath string
|
||||
|
||||
var ErrNotLoggedIn = errors.New("not logged in")
|
||||
|
||||
func init() {
|
||||
var err error
|
||||
userHomeDir, err = os.UserHomeDir()
|
||||
if err != nil {
|
||||
logrus.Fatalf("reading user home dir: %v", err)
|
||||
}
|
||||
polarisHostsFilepath = userHomeDir + "/.config/polaris/hosts.yaml"
|
||||
}
|
||||
|
||||
func readPolarisHostsFile() (map[string]Host, error) {
|
||||
f, err := os.Open(polarisHostsFilepath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
b, err := io.ReadAll(f)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
content := map[string]Host{}
|
||||
err = yaml.Unmarshal(b, &content)
|
||||
return content, err
|
||||
}
|
||||
|
||||
func GetAuth(insightsHost string) (*Host, error) {
|
||||
hosts, err := readPolarisHostsFile()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(hosts) == 0 {
|
||||
return nil, ErrNotLoggedIn
|
||||
}
|
||||
if h, ok := hosts[insightsHost]; ok {
|
||||
return &h, nil
|
||||
}
|
||||
return nil, ErrNotLoggedIn
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// openBrowser opens up the provided URL in a browser
|
||||
func openBrowser(url string) error {
|
||||
var cmd *exec.Cmd
|
||||
switch runtime.GOOS {
|
||||
case "openbsd":
|
||||
fallthrough
|
||||
case "linux":
|
||||
cmd = exec.Command("xdg-open", url)
|
||||
case "darwin":
|
||||
cmd = exec.Command("open", url)
|
||||
case "windows":
|
||||
r := strings.NewReplacer("&", "^&")
|
||||
cmd = exec.Command("cmd", "/c", "start", r.Replace(url))
|
||||
}
|
||||
if cmd != nil {
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
err := cmd.Start()
|
||||
if err != nil {
|
||||
logrus.Printf("Failed to open browser due to error %v", err)
|
||||
return fmt.Errorf("Failed to open browser: " + err.Error())
|
||||
}
|
||||
err = cmd.Wait()
|
||||
if err != nil {
|
||||
logrus.Printf("Failed to wait for open browser command to finish due to error %v", err)
|
||||
return fmt.Errorf("Failed to wait for open browser command to finish: " + err.Error())
|
||||
}
|
||||
return nil
|
||||
} else {
|
||||
return errors.New("unsupported platform")
|
||||
}
|
||||
}
|
||||
@@ -1,291 +0,0 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/fairwindsops/polaris/pkg/insights"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/sirupsen/logrus"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
const loginPath = "/auth/login"
|
||||
const registerPath = "/auth/register"
|
||||
|
||||
const (
|
||||
loginUsingBrowser = "Login with a web browser"
|
||||
pasteAnAuthenticationToken = "Paste an authentication token"
|
||||
)
|
||||
|
||||
type paramsOrError struct {
|
||||
token string
|
||||
user string
|
||||
organization string
|
||||
err error
|
||||
}
|
||||
|
||||
type Host struct {
|
||||
Token string `yaml:"token"`
|
||||
User string `yaml:"user"`
|
||||
Organization string `yaml:"organization"`
|
||||
}
|
||||
|
||||
var paramsOrErrorChan = make(chan paramsOrError)
|
||||
|
||||
func HandleLogin(insightsHost string) error {
|
||||
if _, err := os.Stat(polarisHostsFilepath); err == nil {
|
||||
content, err := readPolarisHostsFile()
|
||||
if err != nil {
|
||||
return fmt.Errorf("reading polaris hosts file: %w", err)
|
||||
}
|
||||
|
||||
if len(content) > 0 {
|
||||
if h, ok := content[insightsHost]; ok {
|
||||
c := insights.NewHTTPClient(insightsHost, h.Organization, h.Token)
|
||||
isValid, err := c.IsTokenValid()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if isValid {
|
||||
var reAuthenticate bool
|
||||
err = survey.AskOne(&survey.Confirm{Message: fmt.Sprintf("You're already logged into %s. Do you want to re-authenticate?", insightsHost)}, &reAuthenticate)
|
||||
if err != nil {
|
||||
return fmt.Errorf("prompting re-authenticate: %w", err)
|
||||
}
|
||||
if !reAuthenticate {
|
||||
// bail-out
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
selection := &survey.Select{
|
||||
Message: "How would you like to authenticate Polaris?",
|
||||
Options: []string{loginUsingBrowser, pasteAnAuthenticationToken},
|
||||
Default: loginUsingBrowser,
|
||||
}
|
||||
|
||||
var answer string
|
||||
err := survey.AskOne(selection, &answer)
|
||||
if err != nil {
|
||||
return fmt.Errorf("asking how to authenticate: %w", err)
|
||||
}
|
||||
|
||||
var user, token, organization string
|
||||
if answer == loginUsingBrowser {
|
||||
listener, err := net.Listen("tcp", "localhost:0")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
localServerPort := listener.Addr().(*net.TCPAddr).Port
|
||||
url := fmt.Sprintf(insightsHost + registerPath + "?source=polaris&callbackUrl=" + fmt.Sprintf("http://localhost:%d/auth/login/callback", localServerPort))
|
||||
err = openBrowser(url)
|
||||
if err != nil {
|
||||
logrus.Warnf("could not open browser: %v", err)
|
||||
logrus.Infoln("paste the link below into your browser:")
|
||||
os.Stdout.Write([]byte(url + "\n"))
|
||||
}
|
||||
|
||||
var router *mux.Router
|
||||
go func() {
|
||||
router = mux.NewRouter()
|
||||
router.HandleFunc("/auth/login/callback", callbackHandler(insightsHost, localServerPort))
|
||||
if err := http.Serve(listener, router); err != nil {
|
||||
paramsOrErrorChan <- paramsOrError{err: fmt.Errorf("starting the local http server: %w", err)}
|
||||
}
|
||||
}()
|
||||
|
||||
// wait the browser to callback the local server
|
||||
paramOrError := <-paramsOrErrorChan
|
||||
|
||||
if paramOrError.err != nil {
|
||||
return paramOrError.err
|
||||
}
|
||||
|
||||
user = paramOrError.user
|
||||
organization = paramOrError.organization
|
||||
token = paramOrError.token
|
||||
} else {
|
||||
var answer string
|
||||
var bot bot
|
||||
err := survey.AskOne(&survey.Password{Message: "Paste your authentication token:"}, &answer, survey.WithValidator(validateToken(insightsHost, &bot)))
|
||||
if err != nil {
|
||||
return fmt.Errorf("asking how to authenticate: %w", err)
|
||||
}
|
||||
token = answer
|
||||
user = bot.Name
|
||||
organization = bot.Organization
|
||||
}
|
||||
|
||||
polarisCfgDir := filepath.Join(userHomeDir, ".config", "polaris")
|
||||
err = os.MkdirAll(polarisCfgDir, os.ModePerm)
|
||||
if err != nil {
|
||||
return fmt.Errorf("creating polaris config dir: %w", err)
|
||||
}
|
||||
|
||||
content := map[string]Host{insightsHost: {Token: token, User: user, Organization: organization}}
|
||||
b, err := yaml.Marshal(content)
|
||||
if err != nil {
|
||||
return fmt.Errorf("marshalling yaml data: %w", err)
|
||||
}
|
||||
|
||||
err = os.WriteFile(polarisHostsFilepath, b, os.ModePerm)
|
||||
if err != nil {
|
||||
return fmt.Errorf("writing data to file: %w", err)
|
||||
}
|
||||
|
||||
logrus.Debugf("hosts file has been saved")
|
||||
|
||||
fmt.Println("✓ Authentication complete.")
|
||||
fmt.Printf("✓ Logged in organization %s as %s.\n", organization, user)
|
||||
return nil
|
||||
}
|
||||
|
||||
func fetchAuthToken(insightsHost, organization, code string) (string, error) {
|
||||
authTokenURL := fmt.Sprintf("%s/v0/organizations/%s/auth/token", insightsHost, organization)
|
||||
body := map[string]any{"grantType": "authorization_code", "code": code}
|
||||
b, err := json.Marshal(body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
r, err := http.NewRequest("POST", authTokenURL, bytes.NewBuffer(b))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
r.Header.Add("Content-Type", "application/json")
|
||||
|
||||
res, err := http.DefaultClient.Do(r)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
|
||||
if res.StatusCode < 200 || res.StatusCode >= 400 {
|
||||
return "", fmt.Errorf("expected 200 OK - received %s", res.Status)
|
||||
}
|
||||
|
||||
var rBody map[string]any
|
||||
err = json.NewDecoder(res.Body).Decode(&rBody)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
token, ok := rBody["accessToken"].(string)
|
||||
if !ok {
|
||||
return "", fmt.Errorf("unable to parse accessToken from response body: %v", rBody)
|
||||
}
|
||||
|
||||
return token, nil
|
||||
}
|
||||
|
||||
func callbackHandler(insightsHost string, localServerPort int) func(w http.ResponseWriter, r *http.Request) {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
// checks for error in the params
|
||||
errMsg := r.URL.Query().Get("error")
|
||||
if len(errMsg) > 0 {
|
||||
errDescriptionMsg := r.URL.Query().Get("error_description")
|
||||
fmt.Fprintf(w, "unable to perform integration: %s - %s", errMsg, errDescriptionMsg)
|
||||
paramsOrErrorChan <- paramsOrError{err: fmt.Errorf("%s - %s", errMsg, errDescriptionMsg)}
|
||||
return
|
||||
}
|
||||
|
||||
var err error
|
||||
code := r.URL.Query().Get("code")
|
||||
if len(code) == 0 {
|
||||
err = errors.New("code query param is required in callback")
|
||||
}
|
||||
user := r.URL.Query().Get("user")
|
||||
if len(user) == 0 {
|
||||
err = errors.New("user query param is required in callback")
|
||||
}
|
||||
organization := r.URL.Query().Get("organization")
|
||||
if len(organization) == 0 {
|
||||
err = errors.New("organization query param is required in callback")
|
||||
}
|
||||
token, err := fetchAuthToken(insightsHost, organization, code)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("fetching auth token: %w", err)
|
||||
}
|
||||
if err != nil {
|
||||
fmt.Fprintf(w, "unable to perform integration: %v", err)
|
||||
paramsOrErrorChan <- paramsOrError{err: err}
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Fprint(w, "Polaris and Fairwinds Insights integration has finished successfully, your credentials are set! You can safely close this tab now.")
|
||||
paramsOrErrorChan <- paramsOrError{token: token, user: user, organization: organization}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func validateToken(insightsHost string, bot *bot) func(args any) error {
|
||||
return func(args any) error {
|
||||
token, ok := args.(string)
|
||||
if !ok {
|
||||
return errors.New("casting token to string")
|
||||
}
|
||||
if len(strings.TrimSpace(token)) <= 0 {
|
||||
return errors.New("token is required")
|
||||
}
|
||||
return fetchOrganizationBot(insightsHost, token, bot)
|
||||
}
|
||||
}
|
||||
|
||||
type bot struct {
|
||||
ID int
|
||||
Organization string
|
||||
Name string
|
||||
Role string
|
||||
AuthToken string
|
||||
CreatedAt time.Time
|
||||
}
|
||||
|
||||
func fetchOrganizationBot(insightsHost, authToken string, bot *bot) error {
|
||||
authTokenURL := fmt.Sprintf("%s/v0/bots/from-request", insightsHost)
|
||||
r, err := http.NewRequest("GET", authTokenURL, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
r.Header.Add("Content-Type", "application/json")
|
||||
r.Header.Add("Authorization", "Bearer "+authToken)
|
||||
|
||||
res, err := http.DefaultClient.Do(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
|
||||
if res.StatusCode < 200 || res.StatusCode >= 400 {
|
||||
return fmt.Errorf("expected 200 (OK) - received %d", res.StatusCode)
|
||||
}
|
||||
|
||||
err = json.NewDecoder(res.Body).Decode(bot)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func IsLoggedIn() bool {
|
||||
if _, err := os.Stat(polarisHostsFilepath); err == nil {
|
||||
content, err := readPolarisHostsFile()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return len(content) > 0
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
func HandleLogout() error {
|
||||
if !IsLoggedIn() {
|
||||
fmt.Println("not logged in to Fairwinds Insights")
|
||||
return nil
|
||||
}
|
||||
err := performLogout()
|
||||
if err != nil {
|
||||
return fmt.Errorf("performing logout: %v", err)
|
||||
}
|
||||
fmt.Println("✓ Logged out of Fairwinds Insights")
|
||||
return nil
|
||||
}
|
||||
|
||||
func performLogout() error {
|
||||
content, err := readPolarisHostsFile()
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(content) > 0 {
|
||||
err = os.WriteFile(polarisHostsFilepath, []byte("{}"), os.ModePerm)
|
||||
if err != nil {
|
||||
return fmt.Errorf("writing data to file: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/fairwindsops/polaris/pkg/insights"
|
||||
)
|
||||
|
||||
func PrintStatus(insightsHost string) error {
|
||||
if content, err := readPolarisHostsFile(); err == nil {
|
||||
if len(content) > 0 {
|
||||
if h, ok := content[insightsHost]; ok {
|
||||
c := insights.NewHTTPClient(insightsHost, h.Organization, h.Token)
|
||||
isValid, err := c.IsTokenValid()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !isValid {
|
||||
fmt.Println("✕ Your token is no longer valid. Run polaris auth login to authenticate.")
|
||||
return nil
|
||||
}
|
||||
fmt.Printf("✓ Logged in to %s as %s\n", insightsHost, h.User)
|
||||
fmt.Printf("✓ Token: %s\n", hideToken(h.Token, 3))
|
||||
return nil
|
||||
}
|
||||
}
|
||||
fmt.Printf("✕ No authentication found for host %s. Run polaris auth login to authenticate.\n", insightsHost)
|
||||
return nil
|
||||
}
|
||||
fmt.Println("You are not logged into Fairwinds Insights. Run polaris auth login to authenticate.")
|
||||
return nil
|
||||
}
|
||||
|
||||
func hideToken(token string, hideAfter int) string {
|
||||
var i int
|
||||
return strings.Map(func(r rune) rune {
|
||||
defer func() {
|
||||
i++
|
||||
}()
|
||||
if i > hideAfter {
|
||||
return []rune("*")[0]
|
||||
}
|
||||
return r
|
||||
}, token)
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
package auth
|
||||
|
||||
import "fmt"
|
||||
|
||||
func PrintToken() error {
|
||||
if content, err := readPolarisHostsFile(); err == nil {
|
||||
if len(content) > 0 {
|
||||
for k, h := range content {
|
||||
if len(content) == 1 {
|
||||
fmt.Println(h.Token)
|
||||
} else {
|
||||
fmt.Printf("%s: %s\n", k, h.Token)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
fmt.Println("no oauth token")
|
||||
return nil
|
||||
}
|
||||
@@ -29,11 +29,14 @@ var (
|
||||
"deploymentMissingReplicas",
|
||||
// Pod checks
|
||||
"hostIPCSet",
|
||||
"hostPathSet",
|
||||
"hostProcess",
|
||||
"hostPIDSet",
|
||||
"hostNetworkSet",
|
||||
"automountServiceAccountToken",
|
||||
"topologySpreadConstraint",
|
||||
// Container checks
|
||||
"procMount",
|
||||
"memoryLimitsMissing",
|
||||
"memoryRequestsMissing",
|
||||
"cpuLimitsMissing",
|
||||
|
||||
@@ -3,7 +3,7 @@ failureMessage: The ServiceAccount will be automounted
|
||||
category: Security
|
||||
target: PodSpec
|
||||
schema:
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
'$schema': https://json-schema.org/draft/2019-09/schema
|
||||
type: object
|
||||
properties:
|
||||
serviceAccountName:
|
||||
|
||||
@@ -3,7 +3,7 @@ failureMessage: The ClusterRole allows Pods/exec or pods/attach
|
||||
category: Security
|
||||
target: rbac.authorization.k8s.io/ClusterRole
|
||||
schemaString: |
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
'$schema': https://json-schema.org/draft/2019-09/schema
|
||||
type: object
|
||||
required: ["metadata", "rules"]
|
||||
anyOf:
|
||||
|
||||
@@ -3,7 +3,7 @@ failureMessage: The ClusterRoleBinding references the default cluster-admin Clus
|
||||
category: Security
|
||||
target: rbac.authorization.k8s.io/ClusterRoleBinding
|
||||
schemaString: |
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
'$schema': https://json-schema.org/draft/2019-09/schema
|
||||
type: object
|
||||
anyOf:
|
||||
# Do not alert on default ClusterRoleBindings.
|
||||
|
||||
@@ -3,7 +3,7 @@ failureMessage: The ClusterRoleBinding references a ClusterRole that allows Pods
|
||||
category: Security
|
||||
target: rbac.authorization.k8s.io/ClusterRoleBinding
|
||||
schemaString: |
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
'$schema': https://json-schema.org/draft/2019-09/schema
|
||||
type: object
|
||||
anyOf:
|
||||
# Do not alert on default ClusterRoleBindings.
|
||||
|
||||
@@ -6,7 +6,7 @@ containers:
|
||||
exclude:
|
||||
- initContainer
|
||||
schema:
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
'$schema': https://json-schema.org/draft/2019-09/schema
|
||||
type: object
|
||||
required:
|
||||
- resources
|
||||
|
||||
@@ -6,7 +6,7 @@ containers:
|
||||
exclude:
|
||||
- initContainer
|
||||
schema:
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
'$schema': https://json-schema.org/draft/2019-09/schema
|
||||
type: object
|
||||
required:
|
||||
- resources
|
||||
|
||||
@@ -3,7 +3,7 @@ failureMessage: Container should not have dangerous capabilities
|
||||
category: Security
|
||||
target: Container
|
||||
schema:
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
'$schema': https://json-schema.org/draft/2019-09/schema
|
||||
type: object
|
||||
properties:
|
||||
securityContext:
|
||||
|
||||
@@ -6,7 +6,7 @@ controllers:
|
||||
include:
|
||||
- Deployment
|
||||
schema:
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
'$schema': https://json-schema.org/draft/2019-09/schema
|
||||
type: object
|
||||
required:
|
||||
- spec
|
||||
|
||||
@@ -3,7 +3,7 @@ failureMessage: Host IPC should not be configured
|
||||
category: Security
|
||||
target: PodSpec
|
||||
schema:
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
'$schema': https://json-schema.org/draft/2019-09/schema
|
||||
type: object
|
||||
properties:
|
||||
hostIPC:
|
||||
|
||||
@@ -3,7 +3,7 @@ failureMessage: Host network should not be configured
|
||||
category: Security
|
||||
target: PodSpec
|
||||
schema:
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
'$schema': https://json-schema.org/draft/2019-09/schema
|
||||
type: object
|
||||
properties:
|
||||
hostNetwork:
|
||||
|
||||
@@ -3,7 +3,7 @@ failureMessage: Host PID should not be configured
|
||||
category: Security
|
||||
target: PodSpec
|
||||
schema:
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
'$schema': https://json-schema.org/draft/2019-09/schema
|
||||
type: object
|
||||
properties:
|
||||
hostPID:
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
successMessage: HostPath volumes are not configured
|
||||
failureMessage: HostPath volumes must be forbidden
|
||||
category: Security
|
||||
target: PodSpec
|
||||
schema:
|
||||
'$schema': https://json-schema.org/draft/2019-09/schema
|
||||
type: object
|
||||
properties:
|
||||
volumes:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
hostPath:
|
||||
type: string
|
||||
const: ''
|
||||
@@ -3,7 +3,7 @@ failureMessage: Host port should not be configured
|
||||
category: Security
|
||||
target: Container
|
||||
schema:
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
'$schema': https://json-schema.org/draft/2019-09/schema
|
||||
type: object
|
||||
required:
|
||||
properties:
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
successMessage: Privileged access to the host check is valid
|
||||
failureMessage: Privileged access to the host is disallowed
|
||||
category: Security
|
||||
target: PodSpec
|
||||
schema:
|
||||
'$schema': https://json-schema.org/draft/2019-09/schema
|
||||
type: object
|
||||
properties:
|
||||
containers:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
securityContext:
|
||||
type: object
|
||||
properties:
|
||||
windowsOptions:
|
||||
type: object
|
||||
properties:
|
||||
hostProcess:
|
||||
type: boolean
|
||||
const: false
|
||||
securityContext:
|
||||
type: object
|
||||
properties:
|
||||
windowsOptions:
|
||||
type: object
|
||||
properties:
|
||||
hostProcess:
|
||||
type: boolean
|
||||
const: false
|
||||
@@ -3,7 +3,7 @@ failureMessage: HPA maxReplicas and minReplicas should be different
|
||||
category: Reliability
|
||||
target: autoscaling/HorizontalPodAutoscaler
|
||||
schemaString: |
|
||||
"$schema": http://json-schema.org/draft-07/schema#
|
||||
"$schema": https://json-schema.org/draft/2019-09/schema#
|
||||
type: object
|
||||
properties:
|
||||
spec:
|
||||
|
||||
@@ -3,7 +3,7 @@ failureMessage: HPA minReplicas should be 2 or more
|
||||
category: Reliability
|
||||
target: autoscaling/HorizontalPodAutoscaler
|
||||
schema:
|
||||
"$schema": http://json-schema.org/draft-07/schema#
|
||||
"$schema": https://json-schema.org/draft/2019-09/schema#
|
||||
type: object
|
||||
properties:
|
||||
spec:
|
||||
|
||||
@@ -3,7 +3,7 @@ failureMessage: Container should not have insecure capabilities
|
||||
category: Security
|
||||
target: Container
|
||||
schema:
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
'$schema': https://json-schema.org/draft/2019-09/schema
|
||||
type: object
|
||||
required:
|
||||
- securityContext
|
||||
|
||||
@@ -3,8 +3,8 @@ FailureMessage: Use one of AppArmor, Seccomp, SELinux, or dropping Linux Capabil
|
||||
category: Security
|
||||
target: Container
|
||||
schemaString: |
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
definitions:
|
||||
'$schema': https://json-schema.org/draft/2019-09/schema
|
||||
$defs:
|
||||
podOrContainerSeccompProfile:
|
||||
type: object
|
||||
{{ $podSeccompProfileType := .Polaris.PodSpec.securityContext.seccompProfile.type }}
|
||||
@@ -83,7 +83,7 @@ schemaString: |
|
||||
type: object
|
||||
{{ else }}
|
||||
anyOf:
|
||||
- $ref: "#/definitions/podOrContainerSeccompProfile"
|
||||
- $ref: "#/definitions/podOrContainerSELinuxOptions"
|
||||
- $ref: "#/definitions/containerDropCapabilities"
|
||||
- $ref: "#/$defs/podOrContainerSeccompProfile"
|
||||
- $ref: "#/$defs/podOrContainerSELinuxOptions"
|
||||
- $ref: "#/$defs/containerDropCapabilities"
|
||||
{{ end}}
|
||||
|
||||
@@ -10,7 +10,7 @@ containers:
|
||||
- initContainer
|
||||
target: Container
|
||||
schema:
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
'$schema': https://json-schema.org/draft/2019-09/schema
|
||||
type: object
|
||||
required:
|
||||
- livenessProbe
|
||||
|
||||
@@ -6,7 +6,7 @@ containers:
|
||||
exclude:
|
||||
- initContainer
|
||||
schema:
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
'$schema': https://json-schema.org/draft/2019-09/schema
|
||||
type: object
|
||||
required:
|
||||
- resources
|
||||
|
||||
@@ -6,7 +6,7 @@ containers:
|
||||
exclude:
|
||||
- initContainer
|
||||
schema:
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
'$schema': https://json-schema.org/draft/2019-09/schema
|
||||
type: object
|
||||
required:
|
||||
- resources
|
||||
|
||||
@@ -3,7 +3,7 @@ failureMessage: Label app.kubernetes.io/instance must match metadata.name
|
||||
category: Reliability
|
||||
target: Controller
|
||||
schema:
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
'$schema': https://json-schema.org/draft/2019-09/schema
|
||||
type: object
|
||||
properties:
|
||||
metadata:
|
||||
|
||||
@@ -3,7 +3,7 @@ failureMessage: A NetworkPolicy should match pod labels and contain applied egre
|
||||
category: Security
|
||||
target: PodTemplate
|
||||
schema:
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
'$schema': https://json-schema.org/draft/2019-09/schema
|
||||
type: object
|
||||
properties:
|
||||
metadata:
|
||||
|
||||
@@ -6,7 +6,7 @@ controllers:
|
||||
include:
|
||||
- Deployment
|
||||
schema:
|
||||
"$schema": http://json-schema.org/draft-07/schema#
|
||||
"$schema": https://json-schema.org/draft/2019-09/schema#
|
||||
type: object
|
||||
required: [spec]
|
||||
properties:
|
||||
|
||||
@@ -4,8 +4,8 @@ category: Security
|
||||
target: Container
|
||||
schemaTarget: PodSpec
|
||||
schema:
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
definitions:
|
||||
'$schema': https://json-schema.org/draft/2019-09/schema
|
||||
$defs:
|
||||
goodSecurityContext:
|
||||
type: object
|
||||
anyOf:
|
||||
@@ -25,13 +25,13 @@ schema:
|
||||
- securityContext
|
||||
properties:
|
||||
securityContext:
|
||||
$ref: "#/definitions/goodSecurityContext"
|
||||
$ref: "#/$defs/goodSecurityContext"
|
||||
containers:
|
||||
type: array
|
||||
items:
|
||||
properties:
|
||||
securityContext:
|
||||
$ref: "#/definitions/notBadSecurityContext"
|
||||
$ref: "#/$defs/notBadSecurityContext"
|
||||
- properties:
|
||||
containers:
|
||||
type: array
|
||||
@@ -40,7 +40,7 @@ schema:
|
||||
- securityContext
|
||||
properties:
|
||||
securityContext:
|
||||
$ref: "#/definitions/goodSecurityContext"
|
||||
$ref: "#/$defs/goodSecurityContext"
|
||||
mutations:
|
||||
- op: add
|
||||
path: /securityContext/readOnlyRootFilesystem
|
||||
|
||||
@@ -3,7 +3,7 @@ failureMessage: Voluntary evictions are not possible
|
||||
category: Reliability
|
||||
target: policy/PodDisruptionBudget
|
||||
schema:
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
'$schema': https://json-schema.org/draft/2019-09/schema
|
||||
type: object
|
||||
required:
|
||||
- spec
|
||||
|
||||
@@ -3,7 +3,7 @@ failureMessage: Priority class should be set
|
||||
category: Reliability
|
||||
target: PodSpec
|
||||
schema:
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
'$schema': https://json-schema.org/draft/2019-09/schema
|
||||
type: object
|
||||
required:
|
||||
- priorityClassName
|
||||
|
||||
@@ -4,8 +4,8 @@ category: Security
|
||||
target: Container
|
||||
schemaTarget: PodSpec
|
||||
schema:
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
definitions:
|
||||
'$schema': https://json-schema.org/draft/2019-09/schema
|
||||
$defs:
|
||||
goodSecurityContext:
|
||||
type: object
|
||||
anyOf:
|
||||
@@ -25,13 +25,13 @@ schema:
|
||||
- securityContext
|
||||
properties:
|
||||
securityContext:
|
||||
$ref: "#/definitions/goodSecurityContext"
|
||||
$ref: "#/$defs/goodSecurityContext"
|
||||
containers:
|
||||
type: array
|
||||
items:
|
||||
properties:
|
||||
securityContext:
|
||||
$ref: "#/definitions/notBadSecurityContext"
|
||||
$ref: "#/$defs/notBadSecurityContext"
|
||||
- properties:
|
||||
containers:
|
||||
type: array
|
||||
@@ -40,7 +40,7 @@ schema:
|
||||
- securityContext
|
||||
properties:
|
||||
securityContext:
|
||||
$ref: "#/definitions/goodSecurityContext"
|
||||
$ref: "#/$defs/goodSecurityContext"
|
||||
|
||||
mutations:
|
||||
- op: add
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
successMessage: The default /proc masks are set up to reduce attack surface, and should be required
|
||||
failureMessage: Proc mount must not be changed from the default
|
||||
category: Security
|
||||
target: PodSpec
|
||||
schema:
|
||||
'$schema': https://json-schema.org/draft/2019-09/schema
|
||||
type: object
|
||||
properties:
|
||||
containers:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
securityContext:
|
||||
type: object
|
||||
properties:
|
||||
procMount:
|
||||
type: string
|
||||
const: Default
|
||||
@@ -3,7 +3,7 @@ failureMessage: Image pull policy should be "Always"
|
||||
category: Reliability
|
||||
target: Container
|
||||
schema:
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
'$schema': https://json-schema.org/draft/2019-09/schema
|
||||
required:
|
||||
- imagePullPolicy
|
||||
properties:
|
||||
|
||||
@@ -10,7 +10,7 @@ containers:
|
||||
- initContainer
|
||||
target: Container
|
||||
schema:
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
'$schema': https://json-schema.org/draft/2019-09/schema
|
||||
type: object
|
||||
required:
|
||||
- readinessProbe
|
||||
|
||||
@@ -3,7 +3,7 @@ failureMessage: The Role allows Pods/exec or pods/attach
|
||||
category: Security
|
||||
target: rbac.authorization.k8s.io/Role
|
||||
schemaString: |
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
'$schema': https://json-schema.org/draft/2019-09/schema
|
||||
type: object
|
||||
required: ["metadata", "rules"]
|
||||
anyOf:
|
||||
|
||||
@@ -3,7 +3,7 @@ failureMessage: The RoleBinding references the default cluster-admin ClusterRole
|
||||
category: Security
|
||||
target: rbac.authorization.k8s.io/RoleBinding
|
||||
schemaString: |
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
'$schema': https://json-schema.org/draft/2019-09/schema
|
||||
type: object
|
||||
anyOf:
|
||||
# Pass RoleBindings that point to a Role.
|
||||
|
||||
@@ -3,7 +3,7 @@ failureMessage: The RoleBinding references a Role with wildcard permissions
|
||||
category: Security
|
||||
target: rbac.authorization.k8s.io/RoleBinding
|
||||
schemaString: |
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
'$schema': https://json-schema.org/draft/2019-09/schema
|
||||
type: object
|
||||
anyOf:
|
||||
# Pass RoleBindings that point to a ClusterRole.
|
||||
|
||||
@@ -3,7 +3,7 @@ failureMessage: The RoleBinding references a ClusterRole that allows Pods/exec,
|
||||
category: Security
|
||||
target: rbac.authorization.k8s.io/RoleBinding
|
||||
schemaString: |
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
'$schema': https://json-schema.org/draft/2019-09/schema
|
||||
type: object
|
||||
anyOf:
|
||||
# Pass RoleBindings that point to a Role.
|
||||
|
||||
@@ -3,7 +3,7 @@ failureMessage: The RoleBinding references a Role that allows Pods/exec, allows
|
||||
category: Security
|
||||
target: rbac.authorization.k8s.io/RoleBinding
|
||||
schemaString: |
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
'$schema': https://json-schema.org/draft/2019-09/schema
|
||||
type: object
|
||||
anyOf:
|
||||
# Pass RoleBindings that point to a ClusterRole.
|
||||
|
||||
@@ -4,8 +4,8 @@ category: Security
|
||||
target: Container
|
||||
schemaTarget: PodSpec
|
||||
schema:
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
definitions:
|
||||
'$schema': https://json-schema.org/draft/2019-09/schema
|
||||
$defs:
|
||||
notBadSecurityContext:
|
||||
type: object
|
||||
properties:
|
||||
@@ -15,13 +15,13 @@ schema:
|
||||
type: object
|
||||
properties:
|
||||
securityContext:
|
||||
$ref: "#/definitions/notBadSecurityContext"
|
||||
$ref: "#/$defs/notBadSecurityContext"
|
||||
containers:
|
||||
type: array
|
||||
items:
|
||||
properties:
|
||||
securityContext:
|
||||
$ref: "#/definitions/notBadSecurityContext"
|
||||
$ref: "#/$defs/notBadSecurityContext"
|
||||
mutations:
|
||||
- op: add
|
||||
path: /securityContext/privileged
|
||||
|
||||
@@ -4,8 +4,8 @@ category: Security
|
||||
target: Container
|
||||
schemaTarget: PodSpec
|
||||
schema:
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
definitions:
|
||||
'$schema': https://json-schema.org/draft/2019-09/schema
|
||||
$defs:
|
||||
goodSecurityContext:
|
||||
type: object
|
||||
anyOf:
|
||||
@@ -33,13 +33,13 @@ schema:
|
||||
- securityContext
|
||||
properties:
|
||||
securityContext:
|
||||
$ref: "#/definitions/goodSecurityContext"
|
||||
$ref: "#/$defs/goodSecurityContext"
|
||||
containers:
|
||||
type: array
|
||||
items:
|
||||
properties:
|
||||
securityContext:
|
||||
$ref: "#/definitions/notBadSecurityContext"
|
||||
$ref: "#/$defs/notBadSecurityContext"
|
||||
# non-root specified at container level
|
||||
- properties:
|
||||
containers:
|
||||
@@ -49,7 +49,7 @@ schema:
|
||||
- securityContext
|
||||
properties:
|
||||
securityContext:
|
||||
$ref: "#/definitions/goodSecurityContext"
|
||||
$ref: "#/$defs/goodSecurityContext"
|
||||
mutations:
|
||||
- op: add
|
||||
path: /securityContext/runAsNonRoot
|
||||
|
||||
@@ -3,7 +3,7 @@ failureMessage: Potentially sensitive content is detected in the ConfigMap keys
|
||||
category: Security
|
||||
target: /ConfigMap
|
||||
schemaString: |
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
'$schema': https://json-schema.org/draft/2019-09/schema
|
||||
type: object
|
||||
required: ["metadata"]
|
||||
properties:
|
||||
|
||||
@@ -3,7 +3,7 @@ failureMessage: The container sets potentially sensitive environment variables
|
||||
category: Security
|
||||
target: Container
|
||||
schemaString: |
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
'$schema': https://json-schema.org/draft/2019-09/schema
|
||||
type: object
|
||||
properties:
|
||||
env:
|
||||
|
||||
@@ -3,7 +3,7 @@ failureMessage: Image tag should be specified
|
||||
category: Reliability
|
||||
target: Container
|
||||
schema:
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
'$schema': https://json-schema.org/draft/2019-09/schema
|
||||
required:
|
||||
- image
|
||||
allOf:
|
||||
|
||||
@@ -3,7 +3,7 @@ failureMessage: Ingress does not have TLS configured
|
||||
category: Security
|
||||
target: networking.k8s.io/Ingress
|
||||
schema:
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
'$schema': https://json-schema.org/draft/2019-09/schema
|
||||
type: object
|
||||
required:
|
||||
- spec
|
||||
|
||||
@@ -7,7 +7,7 @@ controllers:
|
||||
- Job
|
||||
- CronJob
|
||||
schema:
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
'$schema': https://json-schema.org/draft/2019-09/schema
|
||||
type: object
|
||||
required:
|
||||
- topologySpreadConstraints
|
||||
|
||||
+12
-12
@@ -52,7 +52,7 @@ customChecks:
|
||||
category: Security
|
||||
target: Container
|
||||
schema:
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
'$schema': https://json-schema.org/draft/2019-09/schema
|
||||
type: object
|
||||
required:
|
||||
- securityContext
|
||||
@@ -69,7 +69,7 @@ customChecks:
|
||||
target: Container
|
||||
jsonSchema: >
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema",
|
||||
"$schema": "https://json-schema.org/draft/2019-09/schema",
|
||||
"type": "object",
|
||||
"required": ["securityContext"]
|
||||
}
|
||||
@@ -83,7 +83,7 @@ customChecks:
|
||||
category: Security
|
||||
target: Container
|
||||
schema:
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
'$schema': https://json-schema.org/draft/2019-09/schema
|
||||
type: object
|
||||
required:
|
||||
- securityContext
|
||||
@@ -142,35 +142,35 @@ 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{}{})
|
||||
isValid, _, err := check.CheckObject(valid)
|
||||
check, err := parsedConf.CustomChecks["foo"].TemplateForResource(map[string]any{})
|
||||
isValid, _, err := check.CheckObject(context.TODO(), valid)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, true, isValid)
|
||||
isValid, _, err = check.CheckObject(invalid)
|
||||
isValid, _, err = check.CheckObject(context.TODO(), invalid)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, false, isValid)
|
||||
|
||||
parsedConf, err = Parse([]byte(confCustomChecksWithJSONSchema))
|
||||
assert.NoError(t, err, "Expected no error when parsing YAML config")
|
||||
assert.Equal(t, 1, len(parsedConf.CustomChecks))
|
||||
isValid, problems, err := parsedConf.CustomChecks["foo"].CheckObject(valid)
|
||||
isValid, problems, err := parsedConf.CustomChecks["foo"].CheckObject(context.TODO(), valid)
|
||||
assert.NoError(t, err)
|
||||
if !assert.Equal(t, true, isValid) {
|
||||
fmt.Println(problems[0].PropertyPath, problems[0].InvalidValue, problems[0].Message)
|
||||
}
|
||||
isValid, _, err = check.CheckObject(invalid)
|
||||
isValid, _, err = check.CheckObject(context.TODO(), invalid)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, false, isValid)
|
||||
}
|
||||
|
||||
@@ -23,11 +23,14 @@ checks:
|
||||
# security
|
||||
automountServiceAccountToken: warning
|
||||
hostIPCSet: danger
|
||||
hostPathSet: warning
|
||||
hostProcess: warning
|
||||
hostPIDSet: danger
|
||||
linuxHardening: warning
|
||||
missingNetworkPolicy: warning
|
||||
notReadOnlyRootFilesystem: warning
|
||||
privilegeEscalationAllowed: danger
|
||||
procMount: warning
|
||||
runAsRootAllowed: danger
|
||||
runAsPrivileged: danger
|
||||
dangerousCapabilities: danger
|
||||
|
||||
@@ -23,11 +23,14 @@ checks:
|
||||
# security
|
||||
automountServiceAccountToken: warning
|
||||
hostIPCSet: danger
|
||||
hostPathSet: warning
|
||||
hostProcess: warning
|
||||
hostPIDSet: danger
|
||||
linuxHardening: danger
|
||||
missingNetworkPolicy: warning
|
||||
notReadOnlyRootFilesystem: warning
|
||||
privilegeEscalationAllowed: danger
|
||||
procMount: warning
|
||||
runAsRootAllowed: danger
|
||||
runAsPrivileged: danger
|
||||
dangerousCapabilities: danger
|
||||
@@ -72,7 +75,7 @@ customChecks:
|
||||
category: Resources
|
||||
target: Container
|
||||
schema:
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
'$schema': https://json-schema.org/draft/2019-09/schema
|
||||
type: object
|
||||
required:
|
||||
- resources
|
||||
@@ -102,7 +105,7 @@ customChecks:
|
||||
category: Images
|
||||
target: Container
|
||||
schema:
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
'$schema': https://json-schema.org/draft/2019-09/schema
|
||||
type: object
|
||||
properties:
|
||||
image:
|
||||
|
||||
+83
-67
@@ -16,13 +16,17 @@ package config
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"maps"
|
||||
"slices"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"github.com/qri-io/jsonpointer"
|
||||
"github.com/qri-io/jsonschema"
|
||||
"github.com/thoas/go-funk"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
@@ -56,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.RootSchema `yaml:"-" json:"-"`
|
||||
AdditionalSchemas map[string]map[string]interface{} `yaml:"additionalSchemas" json:"additionalSchemas"`
|
||||
AdditionalSchemaStrings map[string]string `yaml:"additionalSchemaStrings" json:"additionalSchemaStrings"`
|
||||
AdditionalValidators map[string]jsonschema.RootSchema `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 {
|
||||
@@ -109,8 +113,9 @@ func ParseCheck(id string, rawBytes []byte) (SchemaCheck, error) {
|
||||
}
|
||||
|
||||
func init() {
|
||||
jsonschema.RegisterValidator("resourceMinimum", newResourceMinimum)
|
||||
jsonschema.RegisterValidator("resourceMaximum", newResourceMaximum)
|
||||
jsonschema.RegisterKeyword("resourceMinimum", newResourceMinimum)
|
||||
jsonschema.RegisterKeyword("resourceMaximum", newResourceMaximum)
|
||||
jsonschema.LoadDraft2019_09()
|
||||
}
|
||||
|
||||
type includeExcludeList struct {
|
||||
@@ -118,50 +123,68 @@ type includeExcludeList struct {
|
||||
Exclude []string `yaml:"exclude"`
|
||||
}
|
||||
|
||||
func newResourceMinimum() jsonschema.Validator {
|
||||
func newResourceMinimum() jsonschema.Keyword {
|
||||
return new(resourceMinimum)
|
||||
}
|
||||
|
||||
func newResourceMaximum() jsonschema.Validator {
|
||||
func newResourceMaximum() jsonschema.Keyword {
|
||||
return new(resourceMaximum)
|
||||
}
|
||||
|
||||
// Validate checks that a specified quanitity is not less than the minimum
|
||||
func (min resourceMinimum) Validate(path string, data interface{}, errs *[]jsonschema.ValError) {
|
||||
err := validateRange(path, string(min), data, true)
|
||||
func (min resourceMinimum) ValidateKeyword(ctx context.Context, currentState *jsonschema.ValidationState, data any) {
|
||||
err := validateRange(string(min), data, true)
|
||||
if err != nil {
|
||||
errs := currentState.Errs
|
||||
*errs = append(*errs, *err...)
|
||||
currentState.Errs = errs
|
||||
}
|
||||
}
|
||||
func (max resourceMaximum) ValidateKeyword(ctx context.Context, currentState *jsonschema.ValidationState, data any) {
|
||||
err := validateRange(string(max), data, false)
|
||||
if err != nil {
|
||||
errs := currentState.Errs
|
||||
*errs = append(*errs, *err...)
|
||||
currentState.Errs = errs
|
||||
}
|
||||
}
|
||||
|
||||
// Validate checks that a specified quanitity is not greater than the maximum
|
||||
func (max resourceMaximum) Validate(path string, data interface{}, errs *[]jsonschema.ValError) {
|
||||
err := validateRange(path, string(max), data, false)
|
||||
if err != nil {
|
||||
*errs = append(*errs, *err...)
|
||||
}
|
||||
func (min resourceMinimum) Resolve(pointer jsonpointer.Pointer, uri string) *jsonschema.Schema {
|
||||
// Not implemented
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseQuantity(i interface{}) (resource.Quantity, *[]jsonschema.ValError) {
|
||||
func (min resourceMinimum) Register(uri string, registry *jsonschema.SchemaRegistry) {
|
||||
// Not implemented
|
||||
}
|
||||
|
||||
func (max resourceMaximum) Resolve(pointer jsonpointer.Pointer, uri string) *jsonschema.Schema {
|
||||
// Not implemented
|
||||
return nil
|
||||
}
|
||||
func (max resourceMaximum) Register(uri string, registry *jsonschema.SchemaRegistry) {
|
||||
// Not implemented
|
||||
}
|
||||
|
||||
func parseQuantity(i any) (resource.Quantity, *[]jsonschema.KeyError) {
|
||||
if resNum, ok := i.(float64); ok {
|
||||
i = fmt.Sprintf("%f", resNum)
|
||||
}
|
||||
resStr, ok := i.(string)
|
||||
if !ok {
|
||||
return resource.Quantity{}, &[]jsonschema.ValError{
|
||||
return resource.Quantity{}, &[]jsonschema.KeyError{
|
||||
{Message: fmt.Sprintf("Resource quantity %v is not a string", i)},
|
||||
}
|
||||
}
|
||||
q, err := resource.ParseQuantity(resStr)
|
||||
if err != nil {
|
||||
return resource.Quantity{}, &[]jsonschema.ValError{
|
||||
return resource.Quantity{}, &[]jsonschema.KeyError{
|
||||
{Message: fmt.Sprintf("Could not parse resource quantity: %s", resStr)},
|
||||
}
|
||||
}
|
||||
return q, nil
|
||||
}
|
||||
|
||||
func validateRange(path string, limit interface{}, data interface{}, isMinimum bool) *[]jsonschema.ValError {
|
||||
func validateRange(limit any, data any, isMinimum bool) *[]jsonschema.KeyError {
|
||||
limitQuantity, err := parseQuantity(limit)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -173,14 +196,14 @@ func validateRange(path string, limit interface{}, data interface{}, isMinimum b
|
||||
cmp := limitQuantity.Cmp(actualQuantity)
|
||||
if isMinimum {
|
||||
if cmp == 1 {
|
||||
return &[]jsonschema.ValError{
|
||||
{Message: fmt.Sprintf("%s quantity %v is > %v", path, actualQuantity, limitQuantity)},
|
||||
return &[]jsonschema.KeyError{
|
||||
{Message: fmt.Sprintf("quantity %v is > %v", actualQuantity, limitQuantity)},
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if cmp == -1 {
|
||||
return &[]jsonschema.ValError{
|
||||
{Message: fmt.Sprintf("%s quantity %v is < %v", path, actualQuantity, limitQuantity)},
|
||||
return &[]jsonschema.KeyError{
|
||||
{Message: fmt.Sprintf("quantity %v is < %v", actualQuantity, limitQuantity)},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -207,21 +230,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{}
|
||||
|
||||
@@ -251,9 +272,9 @@ func (check SchemaCheck) TemplateForResource(res interface{}) (*SchemaCheck, err
|
||||
}
|
||||
}
|
||||
|
||||
newCheck.AdditionalValidators = map[string]jsonschema.RootSchema{}
|
||||
newCheck.AdditionalValidators = map[string]jsonschema.Schema{}
|
||||
for kind, schemaStr := range newCheck.AdditionalSchemaStrings {
|
||||
val := jsonschema.RootSchema{}
|
||||
val := jsonschema.Schema{}
|
||||
err := UnmarshalYAMLOrJSON([]byte(schemaStr), &val)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -268,38 +289,38 @@ func (check SchemaCheck) TemplateForResource(res interface{}) (*SchemaCheck, err
|
||||
}
|
||||
|
||||
// CheckPodSpec checks a pod spec against the schema
|
||||
func (check SchemaCheck) CheckPodSpec(pod *corev1.PodSpec) (bool, []jsonschema.ValError, error) {
|
||||
return check.CheckObject(pod)
|
||||
func (check SchemaCheck) CheckPodSpec(ctx context.Context, pod *corev1.PodSpec) (bool, []jsonschema.KeyError, error) {
|
||||
return check.CheckObject(ctx, pod)
|
||||
}
|
||||
|
||||
// CheckPodTemplate checks a pod template against the schema
|
||||
func (check SchemaCheck) CheckPodTemplate(podTemplate interface{}) (bool, []jsonschema.ValError, error) {
|
||||
return check.CheckObject(podTemplate)
|
||||
func (check SchemaCheck) CheckPodTemplate(ctx context.Context, podTemplate any) (bool, []jsonschema.KeyError, error) {
|
||||
return check.CheckObject(ctx, podTemplate)
|
||||
}
|
||||
|
||||
// CheckController checks a controler's spec against the schema
|
||||
func (check SchemaCheck) CheckController(bytes []byte) (bool, []jsonschema.ValError, error) {
|
||||
errs, err := check.Validator.ValidateBytes(bytes)
|
||||
func (check SchemaCheck) CheckController(ctx context.Context, bytes []byte) (bool, []jsonschema.KeyError, error) {
|
||||
errs, err := check.Validator.ValidateBytes(ctx, bytes)
|
||||
return len(errs) == 0, errs, err
|
||||
}
|
||||
|
||||
// CheckContainer checks a container spec against the schema
|
||||
func (check SchemaCheck) CheckContainer(container *corev1.Container) (bool, []jsonschema.ValError, error) {
|
||||
return check.CheckObject(container)
|
||||
func (check SchemaCheck) CheckContainer(ctx context.Context, container *corev1.Container) (bool, []jsonschema.KeyError, error) {
|
||||
return check.CheckObject(ctx, container)
|
||||
}
|
||||
|
||||
// CheckObject checks arbitrary data against the schema
|
||||
func (check SchemaCheck) CheckObject(obj interface{}) (bool, []jsonschema.ValError, 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
|
||||
}
|
||||
errs, err := check.Validator.ValidateBytes(bytes)
|
||||
errs, err := check.Validator.ValidateBytes(ctx, bytes)
|
||||
return len(errs) == 0, errs, err
|
||||
}
|
||||
|
||||
// CheckAdditionalObjects looks for an object that passes the specified additional schema
|
||||
func (check SchemaCheck) CheckAdditionalObjects(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)
|
||||
@@ -309,7 +330,7 @@ func (check SchemaCheck) CheckAdditionalObjects(groupkind string, objects []inte
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
errs, err := val.ValidateBytes(bytes)
|
||||
errs, err := val.ValidateBytes(ctx, bytes)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
@@ -335,19 +356,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
|
||||
|
||||
@@ -16,6 +16,7 @@ package dashboard
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"embed"
|
||||
"encoding/json"
|
||||
"html/template"
|
||||
@@ -140,7 +141,7 @@ func stripUnselectedNamespaces(data *validator.AuditData, selectedNamespaces []s
|
||||
}
|
||||
|
||||
// GetRouter returns a mux router serving all routes necessary for the dashboard
|
||||
func GetRouter(c config.Configuration, auditPath string, port int, basePath string, auditData *validator.AuditData) (*mux.Router, error) {
|
||||
func GetRouter(ctx context.Context, c config.Configuration, auditPath string, port int, basePath string, auditData *validator.AuditData) (*mux.Router, error) {
|
||||
router := mux.NewRouter().PathPrefix(basePath).Subrouter()
|
||||
|
||||
assetsSubFS, err := fs.Sub(assetsFS, "assets")
|
||||
@@ -176,7 +177,7 @@ func GetRouter(c config.Configuration, auditPath string, port int, basePath stri
|
||||
}
|
||||
|
||||
var auditDataObj validator.AuditData
|
||||
auditDataObj, err = validator.RunAudit(adjustedConf, k)
|
||||
auditDataObj, err = validator.RunAudit(ctx, adjustedConf, k)
|
||||
if err != nil {
|
||||
http.Error(w, "Error Fetching Deployments", http.StatusInternalServerError)
|
||||
return
|
||||
@@ -212,7 +213,7 @@ func GetRouter(c config.Configuration, auditPath string, port int, basePath stri
|
||||
|
||||
logrus.Infof("Running audit")
|
||||
var auditData validator.AuditData
|
||||
auditData, err = validator.RunAudit(adjustedConf, k)
|
||||
auditData, err = validator.RunAudit(ctx, adjustedConf, k)
|
||||
if err != nil {
|
||||
logrus.Errorf("Error getting audit data: %v", err)
|
||||
http.Error(w, "Error running audit", 500)
|
||||
|
||||
@@ -16,6 +16,7 @@ package dashboard
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/fairwindsops/polaris/pkg/config"
|
||||
@@ -157,10 +158,5 @@ func getCategoryInfo(category string) string {
|
||||
}
|
||||
|
||||
func stringInSlice(a string, list []string) bool {
|
||||
for _, b := range list {
|
||||
if b == a {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
return slices.Contains(list, a)
|
||||
}
|
||||
|
||||
+3
-2
@@ -1,6 +1,7 @@
|
||||
package fix
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
@@ -19,7 +20,7 @@ const templateCloseMarker = "POLARIS_CLOSE_TMPL"
|
||||
|
||||
var ErrFilesPathRequired = errors.New("files-path flag is required")
|
||||
|
||||
func Execute(config config.Configuration, filesPath string, isTemplate bool, checksToFix ...string) error {
|
||||
func Execute(ctx context.Context, config config.Configuration, filesPath string, isTemplate bool, checksToFix ...string) error {
|
||||
if filesPath == "" {
|
||||
return ErrFilesPathRequired
|
||||
}
|
||||
@@ -69,7 +70,7 @@ func Execute(config config.Configuration, filesPath string, isTemplate bool, che
|
||||
if err != nil {
|
||||
return fmt.Errorf("error creating resource provider from yaml: %v", err)
|
||||
}
|
||||
results, err := validator.ApplyAllSchemaChecksToResourceProvider(&config, kubeResources)
|
||||
results, err := validator.ApplyAllSchemaChecksToResourceProvider(ctx, &config, kubeResources)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error applying schema check to the resources %s: %v", fullFilePath, err)
|
||||
}
|
||||
|
||||
@@ -1,200 +0,0 @@
|
||||
package insights
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type cluster struct {
|
||||
Name string `json:"Name"`
|
||||
AuthToken string `json:"AuthToken"`
|
||||
Organization string `json:"Organization"`
|
||||
Status string `json:"Status"`
|
||||
}
|
||||
|
||||
type reportJob struct {
|
||||
ID int `json:"id"`
|
||||
Status string `json:"status"`
|
||||
TimeTakenInMs int `json:"timeTaken"`
|
||||
}
|
||||
|
||||
type Client interface {
|
||||
UpsertCluster(clusterName string) (*cluster, error)
|
||||
SendReport(cluster cluster, reportType, reportVersion string, payload []byte) (*reportJob, error)
|
||||
GetReportJob(clusterName string, reportJobID int) (*reportJob, error)
|
||||
IsTokenValid() (bool, error)
|
||||
}
|
||||
|
||||
type HTTPClient struct {
|
||||
insightsHost string
|
||||
organization string
|
||||
token string
|
||||
}
|
||||
|
||||
func NewHTTPClient(host, organization, token string) Client {
|
||||
return HTTPClient{host, organization, token}
|
||||
}
|
||||
|
||||
func (ic HTTPClient) UpsertCluster(clusterName string) (*cluster, error) {
|
||||
clusterURL := fmt.Sprintf("%s/v0/organizations/%s/clusters/%s?showToken=true", ic.insightsHost, ic.organization, clusterName)
|
||||
req, err := http.NewRequest("GET", clusterURL, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("building request for fetching cluster: %w", err)
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("Authorization", "Bearer "+ic.token)
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("making request fetching cluster: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if isSuccessful2XX(resp.StatusCode) {
|
||||
// cluster already created
|
||||
logrus.Infof("cluster %q found...", clusterName)
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("reading response body: %w", err)
|
||||
}
|
||||
var c cluster
|
||||
err = json.Unmarshal(body, &c)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unmarshaling response body: %w", err)
|
||||
}
|
||||
return &c, nil
|
||||
} else {
|
||||
logrus.Warnf("not able to fetch cluster, expected 2xx - received %d, will try to create", resp.StatusCode)
|
||||
}
|
||||
|
||||
logrus.Infof("cluster %q not found... creating..", clusterName)
|
||||
|
||||
req, err = http.NewRequest("POST", clusterURL, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("building request for creating cluster: %w", err)
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("Authorization", "Bearer "+ic.token)
|
||||
resp, err = http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("making request for creating cluster: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("reading response body: %w", err)
|
||||
}
|
||||
|
||||
if !isSuccessful2XX(resp.StatusCode) {
|
||||
return nil, fmt.Errorf("creating cluster, expected 200 OK received %s: %v", resp.Status, string(body))
|
||||
}
|
||||
|
||||
var c cluster
|
||||
err = json.Unmarshal(body, &c)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unmarshaling response body: %w", err)
|
||||
}
|
||||
|
||||
logrus.Infof("cluster %q created...", clusterName)
|
||||
return &c, nil
|
||||
}
|
||||
|
||||
func (ic HTTPClient) SendReport(cluster cluster, reportType, reportVersion string, payload []byte) (*reportJob, error) {
|
||||
uploadReportURL := fmt.Sprintf("%s/v0/organizations/%s/clusters/%s/data/%s", ic.insightsHost, ic.organization, cluster.Name, reportType)
|
||||
req, err := http.NewRequest("POST", uploadReportURL, bytes.NewBuffer(payload))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("building request for output: %w", err)
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("Authorization", "Bearer "+cluster.AuthToken)
|
||||
req.Header.Set("X-Fairwinds-Report-Version", reportVersion)
|
||||
req.Header.Set("X-Fairwinds-Report-Priority", "4") // should have higher priority than the default 5
|
||||
req.Header.Set("X-Fairwinds-Agent-Version", "")
|
||||
req.Header.Set("X-Fairwinds-Agent-Chart-Version", "")
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("making request for output: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("reading response body: %w", err)
|
||||
}
|
||||
if !isSuccessful2XX(resp.StatusCode) {
|
||||
return nil, fmt.Errorf("sending %s report, expected 200 OK received %s: %v", reportType, resp.Status, string(body))
|
||||
}
|
||||
|
||||
var rj reportJob
|
||||
err = json.Unmarshal(body, &rj)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unmarshaling response body: %w", err)
|
||||
}
|
||||
logrus.Debugf("%s report sent to fairwinds insights", reportType)
|
||||
|
||||
return &rj, nil
|
||||
}
|
||||
|
||||
func isSuccessful2XX(statusCode int) bool {
|
||||
return statusCode >= 200 && statusCode < 300
|
||||
}
|
||||
|
||||
func (ic HTTPClient) GetReportJob(clusterName string, reportJobID int) (*reportJob, error) {
|
||||
reportJobsURL := fmt.Sprintf("%s/v0/organizations/%s/clusters/%s/report-jobs/%d", ic.insightsHost, ic.organization, clusterName, reportJobID)
|
||||
req, err := http.NewRequest("GET", reportJobsURL, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("building request for fetching report-job: %w", err)
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("Authorization", "Bearer "+ic.token)
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("making request fetching report-job: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if !isSuccessful2XX(resp.StatusCode) {
|
||||
return nil, fmt.Errorf("fetching report-job, expected 200 OK received %s", resp.Status)
|
||||
}
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("reading response body: %w", err)
|
||||
}
|
||||
var rj reportJob
|
||||
err = json.Unmarshal(body, &rj)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unmarshaling response body: %w", err)
|
||||
}
|
||||
return &rj, nil
|
||||
}
|
||||
|
||||
// IsTokenValid checks if the token is valid by fetching the organization
|
||||
func (ic HTTPClient) IsTokenValid() (bool, error) {
|
||||
organizationURL := fmt.Sprintf("%s/v0/organizations/%s", ic.insightsHost, ic.organization)
|
||||
req, err := http.NewRequest("GET", organizationURL, nil)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("building request for fetching organization: %w", err)
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("Authorization", "Bearer "+ic.token)
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("making request fetching organization: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if isSuccessful2XX(resp.StatusCode) {
|
||||
return true, nil // token is valid
|
||||
}
|
||||
|
||||
if resp.StatusCode == http.StatusNotFound || resp.StatusCode == http.StatusUnauthorized || resp.StatusCode == http.StatusForbidden {
|
||||
return false, nil // token is invalid
|
||||
}
|
||||
|
||||
// unexpected error
|
||||
return false, fmt.Errorf("fetching organization, expected 200 OK - received %s", resp.Status)
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
package insights
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
workloads "github.com/fairwindsops/insights-plugins/plugins/workloads/pkg"
|
||||
"github.com/fairwindsops/polaris/pkg/validator"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type insightsReporter struct {
|
||||
client Client
|
||||
}
|
||||
|
||||
func NewInsightsReporter(client Client) *insightsReporter {
|
||||
return &insightsReporter{
|
||||
client: client,
|
||||
}
|
||||
}
|
||||
|
||||
type WorkloadsReport struct {
|
||||
Version string
|
||||
Payload workloads.ClusterWorkloadReport
|
||||
}
|
||||
|
||||
type PolarisReport struct {
|
||||
Version string
|
||||
Payload validator.AuditData
|
||||
}
|
||||
|
||||
// ReportAuditToFairwindsInsights report audit to insights
|
||||
// 1 - check if cluster exists, otherwise create it
|
||||
// 2 - send workload report
|
||||
// 3 - send polaris report
|
||||
// 4 - checks if report job is completed for 3 minutes
|
||||
// 5 - display link to Fairwinds Insights
|
||||
func (ir insightsReporter) ReportAuditToFairwindsInsights(clusterName string, wr WorkloadsReport, pr PolarisReport) error {
|
||||
cluster, err := ir.client.UpsertCluster(clusterName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
workloadsPayload, err := json.MarshalIndent(wr.Payload, "", " ")
|
||||
if err != nil {
|
||||
return fmt.Errorf("marshaling data: %w", err)
|
||||
}
|
||||
_, err = ir.client.SendReport(*cluster, "workloads", wr.Version, workloadsPayload)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
polarisPayload, err := json.MarshalIndent(pr.Payload, "", " ")
|
||||
if err != nil {
|
||||
return fmt.Errorf("marshaling data: %w", err)
|
||||
}
|
||||
reportJob, err := ir.client.SendReport(*cluster, "polaris", pr.Version, polarisPayload)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
success, err := verifyReportJobCompletion(&ir, cluster.Name, reportJob.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !success {
|
||||
return fmt.Errorf("timed out waiting for report job to complete")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// verifyReportJobCompletion checks Insights for reportJob completion (timeout after 3 minutes)
|
||||
func verifyReportJobCompletion(ir *insightsReporter, clusterName string, reportJobID int) (bool, error) {
|
||||
defer func() { fmt.Println() }()
|
||||
logrus.Println("Processing (this usually takes 1-3 minutes)...")
|
||||
for i := 0; i < 60; i++ {
|
||||
reportJob, err := ir.client.GetReportJob(clusterName, reportJobID)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if reportJob.Status == "completed" {
|
||||
return true, nil
|
||||
}
|
||||
fmt.Print(".")
|
||||
time.Sleep(3 * time.Second)
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
+11
-11
@@ -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
|
||||
@@ -157,7 +157,7 @@ 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()
|
||||
lastKey := ""
|
||||
@@ -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
|
||||
|
||||
+22
-10
@@ -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),
|
||||
}
|
||||
}
|
||||
@@ -304,6 +306,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 +393,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 +453,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 +466,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 +480,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 +494,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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -161,7 +161,6 @@ func createPathAndFindNodes(node *yaml.Node, selectors []string, create bool) ([
|
||||
node.Value = ""
|
||||
case yaml.SequenceNode:
|
||||
return nil, errors.Errorf("parent node is array, use /*/ or /0/../%v/ instead of .%v to access its item(s) first", len(node.Content)-1, currentSelector)
|
||||
|
||||
default:
|
||||
return nil, errors.Errorf("parent node is of unknown kind %v", node.Kind)
|
||||
}
|
||||
@@ -225,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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
package validator
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
@@ -36,7 +37,7 @@ func TestValidatePDB(t *testing.T) {
|
||||
res, err := kube.NewGenericResourceFromUnstructured(pdb, nil)
|
||||
res.Kind = "PodDisruptionBudget"
|
||||
|
||||
actualResult, err := applyNonControllerSchemaChecks(&c, nil, res)
|
||||
actualResult, err := applyNonControllerSchemaChecks(context.Background(), &c, nil, res)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -76,7 +77,7 @@ func TestValidateIngress(t *testing.T) {
|
||||
}
|
||||
res.Kind = "Ingress"
|
||||
|
||||
actualResult, err := applyNonControllerSchemaChecks(&c, nil, res)
|
||||
actualResult, err := applyNonControllerSchemaChecks(context.Background(), &c, nil, res)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
package validator
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
@@ -69,7 +70,7 @@ func testValidateWithWorkload(t *testing.T, container *corev1.Container, resourc
|
||||
assert.NoError(t, err, "Expected no error when parsing config")
|
||||
|
||||
var results ResultSet
|
||||
results, err = applyContainerSchemaChecks(&parsedConf, nil, workload, container, false)
|
||||
results, err = applyContainerSchemaChecks(context.Background(), &parsedConf, nil, workload, container, false)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -93,7 +94,7 @@ func TestValidateResourcesEmptyConfig(t *testing.T) {
|
||||
Name: "Empty",
|
||||
}
|
||||
|
||||
results, err := applyContainerSchemaChecks(&conf.Configuration{}, nil, getEmptyWorkload(t, ""), container, false)
|
||||
results, err := applyContainerSchemaChecks(context.Background(), &conf.Configuration{}, nil, getEmptyWorkload(t, ""), container, false)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -190,7 +191,7 @@ func TestValidateHealthChecks(t *testing.T) {
|
||||
for idx, tt := range testCases {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
controller := getEmptyWorkload(t, "")
|
||||
results, err := applyContainerSchemaChecks(&conf.Configuration{Checks: tt.probes}, nil, controller, tt.container, tt.isInit)
|
||||
results, err := applyContainerSchemaChecks(context.Background(), &conf.Configuration{Checks: tt.probes}, nil, controller, tt.container, tt.isInit)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -304,7 +305,7 @@ func TestValidateImage(t *testing.T) {
|
||||
for _, tt := range testCases {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
controller := getEmptyWorkload(t, "")
|
||||
results, err := applyContainerSchemaChecks(&conf.Configuration{Checks: tt.image}, nil, controller, tt.container, false)
|
||||
results, err := applyContainerSchemaChecks(context.Background(), &conf.Configuration{Checks: tt.image}, nil, controller, tt.container, false)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -421,7 +422,7 @@ func TestValidateNetworking(t *testing.T) {
|
||||
for _, tt := range testCases {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
controller := getEmptyWorkload(t, "")
|
||||
results, err := applyContainerSchemaChecks(&conf.Configuration{Checks: tt.networkConf}, nil, controller, tt.container, false)
|
||||
results, err := applyContainerSchemaChecks(context.Background(), &conf.Configuration{Checks: tt.networkConf}, nil, controller, tt.container, false)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -926,7 +927,7 @@ func TestValidateSecurity(t *testing.T) {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
workload, err := kube.NewGenericResourceFromPod(corev1.Pod{Spec: *tt.pod}, nil)
|
||||
assert.NoError(t, err)
|
||||
results, err := applyContainerSchemaChecks(&conf.Configuration{Checks: tt.securityConf}, nil, workload, tt.container, false)
|
||||
results, err := applyContainerSchemaChecks(context.Background(), &conf.Configuration{Checks: tt.securityConf}, nil, workload, tt.container, false)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -1071,7 +1072,7 @@ func TestValidateRunAsRoot(t *testing.T) {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
workload, err := kube.NewGenericResourceFromPod(corev1.Pod{Spec: *tt.pod}, nil)
|
||||
assert.NoError(t, err)
|
||||
results, err := applyContainerSchemaChecks(&config, nil, workload, tt.container, false)
|
||||
results, err := applyContainerSchemaChecks(context.Background(), &config, nil, workload, tt.container, false)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ func TestValidateController(t *testing.T) {
|
||||
}
|
||||
|
||||
var actualResult Result
|
||||
actualResult, err = applyControllerSchemaChecks(&c, nil, deployment)
|
||||
actualResult, err = applyControllerSchemaChecks(context.Background(), &c, nil, deployment)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -73,7 +73,7 @@ func TestControllerLevelChecks(t *testing.T) {
|
||||
Category: "Reliability",
|
||||
}
|
||||
for _, controller := range res.Resources["Deployment"] {
|
||||
actualResult, err := applyControllerSchemaChecks(&c, nil, controller)
|
||||
actualResult, err := applyControllerSchemaChecks(context.Background(), &c, nil, controller)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -99,7 +99,7 @@ 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)
|
||||
@@ -137,7 +137,7 @@ func TestSkipHealthChecks(t *testing.T) {
|
||||
"livenessProbeMissing": {ID: "livenessProbeMissing", Message: "Liveness probe should be configured", Success: false, Severity: "warning", Category: "Reliability"},
|
||||
}
|
||||
var actualResult Result
|
||||
actualResult, err = applyControllerSchemaChecks(&c, nil, deployment)
|
||||
actualResult, err = applyControllerSchemaChecks(context.Background(), &c, nil, deployment)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -156,7 +156,7 @@ func TestSkipHealthChecks(t *testing.T) {
|
||||
Dangers: uint(0),
|
||||
}
|
||||
expectedResults = ResultSet{}
|
||||
actualResult, err = applyControllerSchemaChecks(&c, nil, job)
|
||||
actualResult, err = applyControllerSchemaChecks(context.Background(), &c, nil, job)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -174,7 +174,7 @@ func TestSkipHealthChecks(t *testing.T) {
|
||||
Dangers: uint(0),
|
||||
}
|
||||
expectedResults = ResultSet{}
|
||||
actualResult, err = applyControllerSchemaChecks(&c, nil, cronjob)
|
||||
actualResult, err = applyControllerSchemaChecks(context.Background(), &c, nil, cronjob)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -210,7 +210,7 @@ func TestControllerExemptions(t *testing.T) {
|
||||
resources := []kube.GenericResource{workload}
|
||||
|
||||
var actualResults []Result
|
||||
actualResults, err = ApplyAllSchemaChecksToAllResources(&c, nil, resources)
|
||||
actualResults, err = ApplyAllSchemaChecksToAllResources(context.Background(), &c, nil, resources)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -221,7 +221,7 @@ func TestControllerExemptions(t *testing.T) {
|
||||
c.Exemptions = []conf.Exemption{{
|
||||
Namespace: "foo",
|
||||
}}
|
||||
actualResults, err = ApplyAllSchemaChecksToAllResources(&c, nil, resources)
|
||||
actualResults, err = ApplyAllSchemaChecksToAllResources(context.Background(), &c, nil, resources)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -233,7 +233,7 @@ func TestControllerExemptions(t *testing.T) {
|
||||
resources[0].ObjectMeta.SetAnnotations(map[string]string{
|
||||
exemptionAnnotationKey: "true",
|
||||
})
|
||||
actualResults, err = ApplyAllSchemaChecksToAllResources(&c, nil, resources)
|
||||
actualResults, err = ApplyAllSchemaChecksToAllResources(context.Background(), &c, nil, resources)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -242,7 +242,7 @@ func TestControllerExemptions(t *testing.T) {
|
||||
assert.EqualValues(t, expectedExemptSum, actualResults[0].GetSummary())
|
||||
|
||||
c.DisallowExemptions = true
|
||||
actualResults, err = ApplyAllSchemaChecksToAllResources(&c, nil, resources)
|
||||
actualResults, err = ApplyAllSchemaChecksToAllResources(context.Background(), &c, nil, resources)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"github.com/qri-io/jsonschema"
|
||||
)
|
||||
|
||||
type validatorFunction func(test schemaTestCase) (bool, []jsonschema.ValError, error)
|
||||
type validatorFunction func(test schemaTestCase) (bool, []jsonschema.KeyError, error)
|
||||
|
||||
var validatorMapper = map[string]validatorFunction{}
|
||||
var lock = &sync.Mutex{}
|
||||
|
||||
@@ -16,6 +16,7 @@ package validator
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
@@ -29,13 +30,13 @@ import (
|
||||
)
|
||||
|
||||
// RunAudit runs a full Polaris audit and returns an AuditData object
|
||||
func RunAudit(config conf.Configuration, kubeResources *kube.ResourceProvider) (AuditData, error) {
|
||||
func RunAudit(ctx context.Context, config conf.Configuration, kubeResources *kube.ResourceProvider) (AuditData, error) {
|
||||
displayName := config.DisplayName
|
||||
if displayName == "" {
|
||||
displayName = kubeResources.SourceName
|
||||
}
|
||||
|
||||
results, err := ApplyAllSchemaChecksToResourceProvider(&config, kubeResources)
|
||||
results, err := ApplyAllSchemaChecksToResourceProvider(ctx, &config, kubeResources)
|
||||
if err != nil {
|
||||
return AuditData{}, err
|
||||
}
|
||||
@@ -49,6 +50,7 @@ func RunAudit(config conf.Configuration, kubeResources *kube.ResourceProvider) (
|
||||
ClusterInfo: ClusterInfo{
|
||||
Version: kubeResources.ServerVersion,
|
||||
Nodes: len(kubeResources.Nodes),
|
||||
Pods: len(kubeResources.Pods),
|
||||
Namespaces: len(kubeResources.Namespaces),
|
||||
Controllers: kubeResources.Resources.GetNumberOfControllers(),
|
||||
},
|
||||
|
||||
@@ -45,12 +45,15 @@ func TestGetTemplateData(t *testing.T) {
|
||||
score := uint(0)
|
||||
|
||||
var actualAudit AuditData
|
||||
actualAudit, err = RunAudit(c, resources)
|
||||
actualAudit, err = RunAudit(context.Background(), c, resources)
|
||||
assert.Equal(t, err, nil, "error should be nil")
|
||||
assert.Equal(t, score, actualAudit.Score, "")
|
||||
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
@@ -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(fmt.Sprintf("%s%s %s\n", indent, checkColor.Sprint(fillString(msg.ID, minIDLength-len(indent))), status))
|
||||
str.WriteString(fmt.Sprintf("%s %s - %s\n", indent, msg.Category, msg.Message))
|
||||
}
|
||||
return str
|
||||
return str.String()
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ func init() {
|
||||
registerCustomChecks("pdbMinAvailableGreaterThanHPAMinReplicas", pdbMinAvailableGreaterThanHPAMinReplicas)
|
||||
}
|
||||
|
||||
func pdbMinAvailableGreaterThanHPAMinReplicas(test schemaTestCase) (bool, []jsonschema.ValError, error) {
|
||||
func pdbMinAvailableGreaterThanHPAMinReplicas(test schemaTestCase) (bool, []jsonschema.KeyError, error) {
|
||||
if test.ResourceProvider == nil {
|
||||
return true, nil, nil
|
||||
}
|
||||
@@ -69,12 +69,12 @@ func pdbMinAvailableGreaterThanHPAMinReplicas(test schemaTestCase) (bool, []json
|
||||
}
|
||||
}
|
||||
|
||||
if attachedHPA.Spec.MinReplicas != nil && pdbMinAvailable >= int(*attachedHPA.Spec.MinReplicas) {
|
||||
return false, []jsonschema.ValError{
|
||||
if attachedHPA.Spec.MinReplicas != nil && pdbMinAvailable > int(*attachedHPA.Spec.MinReplicas) {
|
||||
return false, []jsonschema.KeyError{
|
||||
{
|
||||
PropertyPath: "spec.minAvailable",
|
||||
InvalidValue: pdbMinAvailable,
|
||||
Message: fmt.Sprintf("The minAvailable value in the PodDisruptionBudget(%s) is %d, which is greater or equal than the minReplicas value in the HorizontalPodAutoscaler(%s) (%d)", attachedPDB.Name, pdbMinAvailable, attachedHPA.Name, *attachedHPA.Spec.MinReplicas),
|
||||
Message: fmt.Sprintf("The minAvailable value in the PodDisruptionBudget(%s) is %d, which is greater than the minReplicas value in the HorizontalPodAutoscaler(%s) (%d)", attachedPDB.Name, pdbMinAvailable, attachedHPA.Name, *attachedHPA.Spec.MinReplicas),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -15,9 +15,11 @@
|
||||
package validator
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
conf "github.com/fairwindsops/polaris/pkg/config"
|
||||
@@ -32,6 +34,9 @@ func TestValidatePod(t *testing.T) {
|
||||
"hostPIDSet": conf.SeverityDanger,
|
||||
"hostNetworkSet": conf.SeverityWarning,
|
||||
"hostPortSet": conf.SeverityDanger,
|
||||
"hostPathSet": conf.SeverityWarning,
|
||||
"procMount": conf.SeverityWarning,
|
||||
"hostProcess": conf.SeverityWarning,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -39,7 +44,7 @@ func TestValidatePod(t *testing.T) {
|
||||
deployment, err := kube.NewGenericResourceFromPod(p, nil)
|
||||
assert.NoError(t, err)
|
||||
expectedSum := CountSummary{
|
||||
Successes: uint(4),
|
||||
Successes: uint(7),
|
||||
Warnings: uint(0),
|
||||
Dangers: uint(0),
|
||||
}
|
||||
@@ -48,9 +53,12 @@ func TestValidatePod(t *testing.T) {
|
||||
"hostIPCSet": {ID: "hostIPCSet", Message: "Host IPC is not configured", Success: true, Severity: "danger", Category: "Security"},
|
||||
"hostNetworkSet": {ID: "hostNetworkSet", Message: "Host network is not configured", Success: true, Severity: "warning", Category: "Security"},
|
||||
"hostPIDSet": {ID: "hostPIDSet", Message: "Host PID is not configured", Success: true, Severity: "danger", Category: "Security"},
|
||||
"hostPathSet": {ID: "hostPathSet", Message: "HostPath volumes are not configured", Success: true, Severity: "warning", Category: "Security"},
|
||||
"procMount": {ID: "procMount", Message: "The default /proc masks are set up to reduce attack surface, and should be required", Success: true, Severity: "warning", Category: "Security"},
|
||||
"hostProcess": {ID: "hostProcess", Message: "Privileged access to the host check is valid", Success: true, Severity: "warning", Category: "Security"},
|
||||
}
|
||||
|
||||
actualPodResult, err := applyControllerSchemaChecks(&c, nil, deployment)
|
||||
actualPodResult, err := applyControllerSchemaChecks(context.Background(), &c, nil, deployment)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -67,25 +75,48 @@ func TestInvalidIPCPod(t *testing.T) {
|
||||
"hostPIDSet": conf.SeverityDanger,
|
||||
"hostNetworkSet": conf.SeverityWarning,
|
||||
"hostPortSet": conf.SeverityDanger,
|
||||
"hostPathSet": conf.SeverityWarning,
|
||||
"procMount": conf.SeverityWarning,
|
||||
"hostProcess": conf.SeverityWarning,
|
||||
},
|
||||
}
|
||||
|
||||
p := test.MockPod()
|
||||
p.Spec.HostIPC = true
|
||||
p.Spec.Volumes = append(p.Spec.Volumes, v1.Volume{
|
||||
Name: "hostpath",
|
||||
VolumeSource: v1.VolumeSource{
|
||||
HostPath: &v1.HostPathVolumeSource{
|
||||
Path: "/var/run/docker.sock",
|
||||
},
|
||||
},
|
||||
})
|
||||
procMount := v1.UnmaskedProcMount
|
||||
p.Spec.Containers[0].SecurityContext = &v1.SecurityContext{
|
||||
ProcMount: &procMount,
|
||||
}
|
||||
hostProcess := true
|
||||
p.Spec.Containers[0].SecurityContext.WindowsOptions = &v1.WindowsSecurityContextOptions{
|
||||
HostProcess: &hostProcess,
|
||||
}
|
||||
|
||||
workload, err := kube.NewGenericResourceFromPod(p, nil)
|
||||
assert.NoError(t, err)
|
||||
expectedSum := CountSummary{
|
||||
Successes: uint(3),
|
||||
Warnings: uint(0),
|
||||
Warnings: uint(3),
|
||||
Dangers: uint(1),
|
||||
}
|
||||
expectedResults := ResultSet{
|
||||
"hostIPCSet": {ID: "hostIPCSet", Message: "Host IPC should not be configured", Success: false, Severity: "danger", Category: "Security"},
|
||||
"hostNetworkSet": {ID: "hostNetworkSet", Message: "Host network is not configured", Success: true, Severity: "warning", Category: "Security"},
|
||||
"hostPIDSet": {ID: "hostPIDSet", Message: "Host PID is not configured", Success: true, Severity: "danger", Category: "Security"},
|
||||
"hostPathSet": {ID: "hostPathSet", Message: "HostPath volumes must be forbidden", Success: false, Severity: "warning", Category: "Security"},
|
||||
"procMount": {ID: "procMount", Message: "Proc mount must not be changed from the default", Success: false, Severity: "warning", Category: "Security"},
|
||||
"hostProcess": {ID: "hostProcess", Message: "Privileged access to the host is disallowed", Success: false, Severity: "warning", Category: "Security"},
|
||||
}
|
||||
|
||||
actualPodResult, err := applyControllerSchemaChecks(&c, nil, workload)
|
||||
actualPodResult, err := applyControllerSchemaChecks(context.Background(), &c, nil, workload)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -121,7 +152,7 @@ func TestInvalidNetworkPod(t *testing.T) {
|
||||
"hostPIDSet": {ID: "hostPIDSet", Message: "Host PID is not configured", Success: true, Severity: "danger", Category: "Security"},
|
||||
}
|
||||
|
||||
actualPodResult, err := applyControllerSchemaChecks(&c, nil, workload)
|
||||
actualPodResult, err := applyControllerSchemaChecks(context.Background(), &c, nil, workload)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -157,7 +188,7 @@ func TestInvalidPIDPod(t *testing.T) {
|
||||
"hostNetworkSet": {ID: "hostNetworkSet", Message: "Host network is not configured", Success: true, Severity: "warning", Category: "Security"},
|
||||
}
|
||||
|
||||
actualPodResult, err := applyControllerSchemaChecks(&c, nil, workload)
|
||||
actualPodResult, err := applyControllerSchemaChecks(context.Background(), &c, nil, workload)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -200,7 +231,7 @@ func TestExemption(t *testing.T) {
|
||||
"hostPIDSet": {ID: "hostPIDSet", Message: "Host PID is not configured", Success: true, Severity: "danger", Category: "Security"},
|
||||
}
|
||||
|
||||
actualPodResult, err := applyControllerSchemaChecks(&c, nil, workload)
|
||||
actualPodResult, err := applyControllerSchemaChecks(context.Background(), &c, nil, workload)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
+78
-46
@@ -15,6 +15,8 @@
|
||||
package validator
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
@@ -97,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
|
||||
@@ -111,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 {
|
||||
@@ -133,7 +135,7 @@ func getTemplateInput(test schemaTestCase) (map[string]interface{}, error) {
|
||||
return templateInput, nil
|
||||
}
|
||||
|
||||
func makeResult(conf *config.Configuration, check *config.SchemaCheck, passes bool, issues []jsonschema.ValError) ResultMessage {
|
||||
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)
|
||||
@@ -172,13 +174,13 @@ func hasExemptionAnnotation(objMeta metaV1.Object, checkID string) bool {
|
||||
}
|
||||
|
||||
// ApplyAllSchemaChecksToResourceProvider applies all available checks to a ResourceProvider
|
||||
func ApplyAllSchemaChecksToResourceProvider(conf *config.Configuration, resourceProvider *kube.ResourceProvider) ([]Result, error) {
|
||||
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")
|
||||
}
|
||||
for _, resources := range resourceProvider.Resources {
|
||||
kindResults, err := ApplyAllSchemaChecksToAllResources(conf, resourceProvider, resources)
|
||||
kindResults, err := ApplyAllSchemaChecksToAllResources(ctx, conf, resourceProvider, resources)
|
||||
if err != nil {
|
||||
return results, err
|
||||
}
|
||||
@@ -188,10 +190,10 @@ func ApplyAllSchemaChecksToResourceProvider(conf *config.Configuration, resource
|
||||
}
|
||||
|
||||
// ApplyAllSchemaChecksToAllResources applies available checks to a list of resources
|
||||
func ApplyAllSchemaChecksToAllResources(conf *config.Configuration, resourceProvider *kube.ResourceProvider, resources []kube.GenericResource) ([]Result, error) {
|
||||
func ApplyAllSchemaChecksToAllResources(ctx context.Context, conf *config.Configuration, resourceProvider *kube.ResourceProvider, resources []kube.GenericResource) ([]Result, error) {
|
||||
results := []Result{}
|
||||
for _, resource := range resources {
|
||||
result, err := ApplyAllSchemaChecks(conf, resourceProvider, resource)
|
||||
result, err := ApplyAllSchemaChecks(ctx, conf, resourceProvider, resource)
|
||||
if err != nil {
|
||||
return results, err
|
||||
}
|
||||
@@ -203,37 +205,37 @@ func ApplyAllSchemaChecksToAllResources(conf *config.Configuration, resourceProv
|
||||
}
|
||||
|
||||
// ApplyAllSchemaChecks applies available checks to a single resource
|
||||
func ApplyAllSchemaChecks(conf *config.Configuration, resourceProvider *kube.ResourceProvider, resource kube.GenericResource) (Result, error) {
|
||||
func ApplyAllSchemaChecks(ctx context.Context, conf *config.Configuration, resourceProvider *kube.ResourceProvider, resource kube.GenericResource) (Result, error) {
|
||||
if resource.PodSpec == nil {
|
||||
return applyNonControllerSchemaChecks(conf, resourceProvider, resource)
|
||||
return applyNonControllerSchemaChecks(ctx, conf, resourceProvider, resource)
|
||||
}
|
||||
return applyControllerSchemaChecks(conf, resourceProvider, resource)
|
||||
return applyControllerSchemaChecks(ctx, conf, resourceProvider, resource)
|
||||
}
|
||||
|
||||
func applyNonControllerSchemaChecks(conf *config.Configuration, resourceProvider *kube.ResourceProvider, resource kube.GenericResource) (Result, error) {
|
||||
func applyNonControllerSchemaChecks(ctx context.Context, conf *config.Configuration, resourceProvider *kube.ResourceProvider, resource kube.GenericResource) (Result, error) {
|
||||
finalResult := Result{
|
||||
Kind: resource.Kind,
|
||||
Name: resource.ObjectMeta.GetName(),
|
||||
Namespace: resource.ObjectMeta.GetNamespace(),
|
||||
}
|
||||
resultSet, err := applyTopLevelSchemaChecks(conf, resourceProvider, resource, false)
|
||||
resultSet, err := applyTopLevelSchemaChecks(ctx, conf, resourceProvider, resource, false)
|
||||
finalResult.Results = resultSet
|
||||
return finalResult, err
|
||||
}
|
||||
|
||||
func applyControllerSchemaChecks(conf *config.Configuration, resourceProvider *kube.ResourceProvider, resource kube.GenericResource) (Result, error) {
|
||||
func applyControllerSchemaChecks(ctx context.Context, conf *config.Configuration, resourceProvider *kube.ResourceProvider, resource kube.GenericResource) (Result, error) {
|
||||
finalResult := Result{
|
||||
Kind: resource.Kind,
|
||||
Name: resource.ObjectMeta.GetName(),
|
||||
Namespace: resource.ObjectMeta.GetNamespace(),
|
||||
}
|
||||
resultSet, err := applyTopLevelSchemaChecks(conf, resourceProvider, resource, true)
|
||||
resultSet, err := applyTopLevelSchemaChecks(ctx, conf, resourceProvider, resource, true)
|
||||
if err != nil {
|
||||
return finalResult, err
|
||||
}
|
||||
finalResult.Results = resultSet
|
||||
|
||||
nonControllerResults, err := applyTopLevelSchemaChecks(conf, resourceProvider, resource, false)
|
||||
nonControllerResults, err := applyTopLevelSchemaChecks(ctx, conf, resourceProvider, resource, false)
|
||||
if err != nil {
|
||||
return finalResult, err
|
||||
}
|
||||
@@ -244,7 +246,7 @@ func applyControllerSchemaChecks(conf *config.Configuration, resourceProvider *k
|
||||
finalResult.Results[key] = val
|
||||
}
|
||||
|
||||
podRS, err := applyPodSchemaChecks(conf, resourceProvider, resource)
|
||||
podRS, err := applyPodSchemaChecks(ctx, conf, resourceProvider, resource)
|
||||
if err != nil {
|
||||
return finalResult, err
|
||||
}
|
||||
@@ -255,7 +257,7 @@ func applyControllerSchemaChecks(conf *config.Configuration, resourceProvider *k
|
||||
finalResult.PodResult = &podRes
|
||||
|
||||
for _, container := range resource.PodSpec.InitContainers {
|
||||
results, err := applyContainerSchemaChecks(conf, resourceProvider, resource, &container, true)
|
||||
results, err := applyContainerSchemaChecks(ctx, conf, resourceProvider, resource, &container, true)
|
||||
if err != nil {
|
||||
return finalResult, err
|
||||
}
|
||||
@@ -266,7 +268,7 @@ func applyControllerSchemaChecks(conf *config.Configuration, resourceProvider *k
|
||||
podRes.ContainerResults = append(podRes.ContainerResults, cRes)
|
||||
}
|
||||
for _, container := range resource.PodSpec.Containers {
|
||||
results, err := applyContainerSchemaChecks(conf, resourceProvider, resource, &container, false)
|
||||
results, err := applyContainerSchemaChecks(ctx, conf, resourceProvider, resource, &container, false)
|
||||
if err != nil {
|
||||
return finalResult, err
|
||||
}
|
||||
@@ -280,7 +282,7 @@ func applyControllerSchemaChecks(conf *config.Configuration, resourceProvider *k
|
||||
return finalResult, nil
|
||||
}
|
||||
|
||||
func applyTopLevelSchemaChecks(conf *config.Configuration, resources *kube.ResourceProvider, res kube.GenericResource, isController bool) (ResultSet, error) {
|
||||
func applyTopLevelSchemaChecks(ctx context.Context, conf *config.Configuration, resources *kube.ResourceProvider, res kube.GenericResource, isController bool) (ResultSet, error) {
|
||||
test := schemaTestCase{
|
||||
ResourceProvider: resources,
|
||||
Resource: res,
|
||||
@@ -288,19 +290,19 @@ func applyTopLevelSchemaChecks(conf *config.Configuration, resources *kube.Resou
|
||||
if isController {
|
||||
test.Target = config.TargetController
|
||||
}
|
||||
return applySchemaChecks(conf, test)
|
||||
return applySchemaChecks(ctx, conf, test)
|
||||
}
|
||||
|
||||
func applyPodSchemaChecks(conf *config.Configuration, resources *kube.ResourceProvider, controller kube.GenericResource) (ResultSet, error) {
|
||||
func applyPodSchemaChecks(ctx context.Context, conf *config.Configuration, resources *kube.ResourceProvider, controller kube.GenericResource) (ResultSet, error) {
|
||||
test := schemaTestCase{
|
||||
Target: config.TargetPodSpec,
|
||||
ResourceProvider: resources,
|
||||
Resource: controller,
|
||||
}
|
||||
return applySchemaChecks(conf, test)
|
||||
return applySchemaChecks(ctx, conf, test)
|
||||
}
|
||||
|
||||
func applyContainerSchemaChecks(conf *config.Configuration, resources *kube.ResourceProvider, controller kube.GenericResource, container *corev1.Container, isInit bool) (ResultSet, error) {
|
||||
func applyContainerSchemaChecks(ctx context.Context, conf *config.Configuration, resources *kube.ResourceProvider, controller kube.GenericResource, container *corev1.Container, isInit bool) (ResultSet, error) {
|
||||
test := schemaTestCase{
|
||||
Target: config.TargetContainer,
|
||||
ResourceProvider: resources,
|
||||
@@ -308,14 +310,14 @@ func applyContainerSchemaChecks(conf *config.Configuration, resources *kube.Reso
|
||||
Container: container,
|
||||
IsInitContainer: isInit,
|
||||
}
|
||||
return applySchemaChecks(conf, test)
|
||||
return applySchemaChecks(ctx, conf, test)
|
||||
}
|
||||
|
||||
func applySchemaChecks(conf *config.Configuration, test schemaTestCase) (ResultSet, error) {
|
||||
func applySchemaChecks(ctx context.Context, conf *config.Configuration, test schemaTestCase) (ResultSet, error) {
|
||||
results := ResultSet{}
|
||||
checkIDs := getSortedKeys(conf.Checks)
|
||||
for _, checkID := range checkIDs {
|
||||
result, err := applySchemaCheck(conf, checkID, test)
|
||||
result, err := applySchemaCheck(ctx, conf, checkID, test)
|
||||
if err != nil {
|
||||
return results, err
|
||||
}
|
||||
@@ -326,7 +328,7 @@ func applySchemaChecks(conf *config.Configuration, test schemaTestCase) (ResultS
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func applySchemaCheck(conf *config.Configuration, checkID string, test schemaTestCase) (*ResultMessage, error) {
|
||||
func applySchemaCheck(ctx context.Context, conf *config.Configuration, checkID string, test schemaTestCase) (*ResultMessage, error) {
|
||||
check, err := resolveCheck(conf, checkID, test)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -334,45 +336,75 @@ func applySchemaCheck(conf *config.Configuration, checkID string, test schemaTes
|
||||
return nil, nil
|
||||
}
|
||||
var passes bool
|
||||
var issues []jsonschema.ValError
|
||||
var issues []jsonschema.KeyError
|
||||
var prefix string
|
||||
emptyValidator := true
|
||||
validatorBytes, err := json.Marshal(check.Validator)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if string(validatorBytes) != "" && string(validatorBytes) != "null" && string(validatorBytes) != "{}" {
|
||||
emptyValidator = false
|
||||
}
|
||||
if check.SchemaTarget != "" {
|
||||
if check.SchemaTarget == config.TargetPodSpec && check.Target == config.TargetContainer {
|
||||
podCopy := *test.Resource.PodSpec
|
||||
podCopy.InitContainers = []corev1.Container{}
|
||||
podCopy.Containers = []corev1.Container{*test.Container}
|
||||
containerIndex := funk.IndexOf(test.Resource.PodSpec.Containers, func(value corev1.Container) bool {
|
||||
return value.Name == test.Container.Name
|
||||
})
|
||||
containerIndex := -1
|
||||
if !test.IsInitContainer {
|
||||
containerIndex = funk.IndexOf(test.Resource.PodSpec.Containers, func(value corev1.Container) bool {
|
||||
return value.Name == test.Container.Name
|
||||
})
|
||||
} else {
|
||||
containerIndex = funk.IndexOf(test.Resource.PodSpec.InitContainers, func(value corev1.Container) bool {
|
||||
return value.Name == test.Container.Name
|
||||
})
|
||||
}
|
||||
prefix = getJSONSchemaPrefix(test.Resource.Kind)
|
||||
if prefix != "" {
|
||||
prefix += "/containers/" + strconv.Itoa(containerIndex)
|
||||
if test.IsInitContainer {
|
||||
prefix += "/initContainers/" + strconv.Itoa(containerIndex)
|
||||
} else {
|
||||
prefix += "/containers/" + strconv.Itoa(containerIndex)
|
||||
}
|
||||
}
|
||||
passes, issues, err = check.CheckPodSpec(&podCopy)
|
||||
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)
|
||||
}
|
||||
} else if check.Target == config.TargetPodSpec {
|
||||
passes, issues, err = check.CheckPodSpec(test.Resource.PodSpec)
|
||||
passes, issues, err = check.CheckPodSpec(ctx, test.Resource.PodSpec)
|
||||
prefix = getJSONSchemaPrefix(test.Resource.Kind)
|
||||
} else if check.Target == config.TargetPodTemplate {
|
||||
passes, issues, err = check.CheckPodTemplate(test.Resource.PodTemplate)
|
||||
passes, issues, err = check.CheckPodTemplate(ctx, test.Resource.PodTemplate)
|
||||
prefix = getJSONSchemaPrefix(test.Resource.Kind)
|
||||
} else if check.Target == config.TargetContainer {
|
||||
containerIndex := funk.IndexOf(test.Resource.PodSpec.Containers, func(value corev1.Container) bool {
|
||||
return value.Name == test.Container.Name
|
||||
})
|
||||
containerIndex := -1
|
||||
if !test.IsInitContainer {
|
||||
containerIndex = funk.IndexOf(test.Resource.PodSpec.Containers, func(value corev1.Container) bool {
|
||||
return value.Name == test.Container.Name
|
||||
})
|
||||
} else {
|
||||
containerIndex = funk.IndexOf(test.Resource.PodSpec.InitContainers, func(value corev1.Container) bool {
|
||||
return value.Name == test.Container.Name
|
||||
})
|
||||
}
|
||||
prefix = getJSONSchemaPrefix(test.Resource.Kind)
|
||||
if prefix != "" {
|
||||
prefix += "/containers/" + strconv.Itoa(containerIndex)
|
||||
if test.IsInitContainer {
|
||||
prefix += "/initContainers/" + strconv.Itoa(containerIndex)
|
||||
} else {
|
||||
prefix += "/containers/" + strconv.Itoa(containerIndex)
|
||||
}
|
||||
}
|
||||
passes, issues, err = check.CheckContainer(test.Container)
|
||||
} else if check.Validator.SchemaURI != "" {
|
||||
passes, issues, err = check.CheckObject(test.Resource.Resource.Object)
|
||||
passes, issues, err = check.CheckContainer(ctx, test.Container)
|
||||
} else if !emptyValidator {
|
||||
passes, issues, err = check.CheckObject(ctx, test.Resource.Resource.Object)
|
||||
} else if validatorMapper[checkID] != nil {
|
||||
passes, issues, err = validatorMapper[checkID](test)
|
||||
} else {
|
||||
passes, issues, err = true, []jsonschema.ValError{}, nil
|
||||
passes, issues, err = true, []jsonschema.KeyError{}, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -393,10 +425,10 @@ func applySchemaCheck(conf *config.Configuration, checkID string, test schemaTes
|
||||
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{})
|
||||
passes, err = check.CheckAdditionalObjects(groupkind, objects)
|
||||
}).([]any)
|
||||
passes, err = check.CheckAdditionalObjects(ctx, groupkind, objects)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
package validator
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
conf "github.com/fairwindsops/polaris/pkg/config"
|
||||
@@ -58,7 +59,7 @@ customChecks:
|
||||
category: Efficiency
|
||||
target: Container
|
||||
schema:
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
'$schema': https://json-schema.org/draft/2019-09/schema
|
||||
type: object
|
||||
required:
|
||||
- resources
|
||||
@@ -86,7 +87,7 @@ customChecks:
|
||||
exclude:
|
||||
- initContainer
|
||||
schema:
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
'$schema': https://json-schema.org/draft/2019-09/schema
|
||||
type: object
|
||||
required:
|
||||
- resources
|
||||
@@ -158,14 +159,14 @@ func TestValidateResourcesInit(t *testing.T) {
|
||||
assert.NoError(t, err, "Expected no error when parsing config")
|
||||
|
||||
var results ResultSet
|
||||
results, err = applyContainerSchemaChecks(&parsedConf, nil, controller, emptyContainer, false)
|
||||
results, err = applyContainerSchemaChecks(context.Background(), &parsedConf, nil, controller, emptyContainer, false)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
assert.Equal(t, uint(1), results.GetSummary().Dangers)
|
||||
assert.Equal(t, uint(1), results.GetSummary().Warnings)
|
||||
|
||||
results, err = applyContainerSchemaChecks(&parsedConf, nil, controller, emptyContainer, true)
|
||||
results, err = applyContainerSchemaChecks(context.Background(), &parsedConf, nil, controller, emptyContainer, true)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ type Mutator struct {
|
||||
}
|
||||
|
||||
// NewMutateWebhook creates a mutating admission webhook for the apiType.
|
||||
func NewMutateWebhook(mgr manager.Manager, c config.Configuration) {
|
||||
func NewMutateWebhook(ctx context.Context, mgr manager.Manager, c config.Configuration) {
|
||||
path := "/mutate"
|
||||
decoder := admission.NewDecoder(runtime.NewScheme())
|
||||
mutator := Mutator{
|
||||
@@ -48,8 +48,8 @@ func NewMutateWebhook(mgr manager.Manager, c config.Configuration) {
|
||||
mgr.GetWebhookServer().Register(path, &webhook.Admission{Handler: &mutator})
|
||||
}
|
||||
|
||||
func (m *Mutator) mutate(req admission.Request) ([]jsonpatch.Operation, error) {
|
||||
results, kubeResources, err := GetValidatedResults(req.AdmissionRequest.Kind.Kind, m.decoder, req, m.Config)
|
||||
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)
|
||||
if err != nil {
|
||||
logrus.Errorf("Error while validating resource: %v", err)
|
||||
return nil, err
|
||||
@@ -87,7 +87,7 @@ func (m *Mutator) mutate(req admission.Request) ([]jsonpatch.Operation, error) {
|
||||
// Handle for Validator to run validation checks.
|
||||
func (m *Mutator) Handle(ctx context.Context, req admission.Request) admission.Response {
|
||||
logrus.Info("Starting mutation request")
|
||||
patches, err := m.mutate(req)
|
||||
patches, err := m.mutate(ctx, req)
|
||||
if err != nil {
|
||||
logrus.Errorf("Error while getting mutations: %v", err)
|
||||
return admission.Errored(403, err)
|
||||
|
||||
+12
-10
@@ -19,6 +19,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/fairwindsops/controller-utils/pkg/controller"
|
||||
"github.com/fairwindsops/polaris/pkg/config"
|
||||
@@ -52,12 +53,12 @@ func NewValidateWebhook(mgr manager.Manager, c config.Configuration) {
|
||||
mgr.GetWebhookServer().Register(path, &webhook.Admission{Handler: &validator})
|
||||
}
|
||||
|
||||
func (v *Validator) handleInternal(req admission.Request) (*validator.Result, kube.GenericResource, error) {
|
||||
return GetValidatedResults(req.AdmissionRequest.Kind.Kind, v.decoder, req, v.Config)
|
||||
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)
|
||||
}
|
||||
|
||||
// GetValidatedResults returns the validated results.
|
||||
func GetValidatedResults(kind string, decoder *admission.Decoder, req admission.Request, config config.Configuration) (*validator.Result, kube.GenericResource, error) {
|
||||
func GetValidatedResults(ctx context.Context, kind string, decoder *admission.Decoder, req admission.Request, config config.Configuration) (*validator.Result, kube.GenericResource, error) {
|
||||
var resource kube.GenericResource
|
||||
var err error
|
||||
rawBytes := req.Object.Raw
|
||||
@@ -106,7 +107,7 @@ func GetValidatedResults(kind string, decoder *admission.Decoder, req admission.
|
||||
logrus.Errorf("Failed to create resource: %v", err)
|
||||
return nil, resource, err
|
||||
}
|
||||
resourceResult, err := validator.ApplyAllSchemaChecks(&config, nil, resource)
|
||||
resourceResult, err := validator.ApplyAllSchemaChecks(ctx, &config, nil, resource)
|
||||
if err != nil {
|
||||
return nil, resource, err
|
||||
}
|
||||
@@ -116,7 +117,7 @@ func GetValidatedResults(kind string, decoder *admission.Decoder, req admission.
|
||||
// Handle for Validator to run validation checks.
|
||||
func (v *Validator) Handle(ctx context.Context, req admission.Request) admission.Response {
|
||||
logrus.Info("Starting admission request")
|
||||
result, _, err := v.handleInternal(req)
|
||||
result, _, err := v.handleInternal(ctx, req)
|
||||
if err != nil {
|
||||
logrus.Errorf("Error validating request: %v", err)
|
||||
return admission.Errored(http.StatusBadRequest, err)
|
||||
@@ -135,11 +136,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(fmt.Sprintf("- %s: %s\n", result.Kind, message.Message))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -147,18 +149,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(fmt.Sprintf("- Pod: %s\n", message.Message))
|
||||
}
|
||||
}
|
||||
|
||||
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(fmt.Sprintf("- Container %s: %s\n", containerResult.Name, message.Message))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return reason
|
||||
return reason.String()
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -0,0 +1,14 @@
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: nginx
|
||||
labels:
|
||||
app.kubernetes.io/name: nginx
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx
|
||||
volumes:
|
||||
- name: log-volume
|
||||
hostPath:
|
||||
path: /var/log
|
||||
@@ -0,0 +1,12 @@
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: nginx
|
||||
labels:
|
||||
app.kubernetes.io/name: nginx
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx
|
||||
volumes:
|
||||
- name: log-volume
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user