File kube-score-1.20.0.obscpio of Package kube-score
07070100000000000081A4000000000000000000000001680FB1F800000199000000000000000000000000000000000000002000000000kube-score-1.20.0/.golangci.yml
linters:
disable-all: true
enable:
- deadcode
- errcheck
- gosimple
- govet
- ineffassign
- staticcheck
- structcheck
- typecheck
- unused
- varcheck
- bodyclose
- contextcheck
- cyclop
- durationcheck
- errname
- errorlint
- exportloopref
- goimports
- gosec
- gocritic
linters-settings:
cyclop:
max-complexity: 35
07070100000001000081A4000000000000000000000001680FB1F800000321000000000000000000000000000000000000002200000000kube-score-1.20.0/.goreleaser.ymlproject_name: kube-score
version: 2
builds:
- env:
- CGO_ENABLED=0
goos:
- linux
- darwin
- windows
goarch:
- amd64
- arm
- arm64
dir: cmd/kube-score
archives:
- id: binary
format: binary
files:
- LICENSE
# A release in archive format is needed for the homebrew release
- id: default
files:
- LICENSE
checksum:
name_template: 'checksums.txt'
snapshot:
version_template: "{{ .Tag }}-next"
changelog:
sort: asc
brews:
- name: kube-score
ids:
- default
repository:
owner: kube-score
name: homebrew-tap
directory: Formula
url_template: "https://github.com/zegl/kube-score/releases/download/{{ .Tag }}/{{ .ArtifactName }}"
homepage: "https://kube-score.com/"
install: |
bin.install "kube-score"
07070100000002000081A4000000000000000000000001680FB1F800000A13000000000000000000000000000000000000001D00000000kube-score-1.20.0/Dockerfile# syntax=docker/dockerfile:1
ARG ALPINE_IMAGE=alpine:3.17.0
FROM ${ALPINE_IMAGE} as args
ARG HELM_VERSION=v3.10.2
ARG KUSTOMIZE_VERSION=v4.5.7
FROM args as args-amd64
ARG HELM_SHA256SUM="2315941a13291c277dac9f65e75ead56386440d3907e0540bf157ae70f188347"
ARG KUSTOMIZE_SHA256SUM="701e3c4bfa14e4c520d481fdf7131f902531bfc002cb5062dcf31263a09c70c9"
FROM args as args-arm64
ARG HELM_SHA256SUM="57fa17b6bb040a3788116557a72579f2180ea9620b4ee8a9b7244e5901df02e4"
ARG KUSTOMIZE_SHA256SUM="65665b39297cc73c13918f05bbe8450d17556f0acd16242a339271e14861df67"
# FROM args as args-arm
# ARG HELM_SHA256SUM="25af344f46348958baa1c758cdf3b204ede3ddc483be1171ed3738d47efd0aae"
FROM --platform=$BUILDPLATFORM args-${TARGETARCH} as downloader
ARG TARGETPLATFORM
ARG BUILDPLATFORM
ARG TARGETARCH
RUN apk update && apk add curl
RUN curl --location "https://get.helm.sh/helm-${HELM_VERSION}-linux-${TARGETARCH}.tar.gz" > helm.tar.gz && \
echo "${HELM_SHA256SUM} helm.tar.gz" | sha256sum && \
tar xzvf helm.tar.gz && \
chmod +x /linux-${TARGETARCH}/helm && \
mv /linux-${TARGETARCH}/helm /usr/bin/helm
RUN curl --location "https://github.com/kubernetes-sigs/kustomize/releases/download/kustomize%2F${KUSTOMIZE_VERSION}/kustomize_${KUSTOMIZE_VERSION}_linux_${TARGETARCH}.tar.gz" > kustomize.tar.gz && \
echo "${KUSTOMIZE_SHA256SUM} kustomize.tar.gz" | sha256sum && \
tar xzvf kustomize.tar.gz && \
chmod +x kustomize && \
mv kustomize /usr/bin/kustomize
FROM golang:1.23-alpine as builder
ARG TARGETARCH
ARG TARGETPLATFORM
WORKDIR /kube-score
COPY go.mod go.sum ./
RUN go mod download
COPY . .
ARG KUBE_SCORE_VERSION
ARG KUBE_SCORE_COMMIT
ARG KUBE_SCORE_DATE
RUN --mount=type=cache,target=/root/.cache/go-build \
CGO_ENABLED=0 \
go build \
-ldflags="-X 'main.version=${KUBE_SCORE_VERSION}-docker-${TARGETPLATFORM}' -X 'main.commit=${KUBE_SCORE_COMMIT}' -X 'main.date=${KUBE_SCORE_DATE}'" \
-o /usr/bin/kube-score \
./cmd/kube-score
FROM ${ALPINE_IMAGE} as runner
RUN apk update && \
apk upgrade && \
apk add bash ca-certificates git
COPY --from=downloader /usr/bin/helm /usr/bin/helm
COPY --from=downloader /usr/bin/kustomize /usr/bin/kustomize
COPY --from=builder /usr/bin/kube-score /usr/bin/kube-score
# Symlink to /kube-score for backwards compatibility (with kube-score v1.15.0 and earlier)
RUN ln -s /usr/bin/kube-score /kube-score
# Dry runs
RUN /kube-score version && \
/usr/bin/kube-score version && \
kube-score version && \
helm version && kustomize version
WORKDIR /project
ENTRYPOINT ["/kube-score"]
07070100000003000081A4000000000000000000000001680FB1F800000430000000000000000000000000000000000000001A00000000kube-score-1.20.0/LICENSEMIT License
Copyright (c) 2018 Gustav Westling
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
07070100000004000081A4000000000000000000000001680FB1F80000224A000000000000000000000000000000000000001C00000000kube-score-1.20.0/README.md# kube-score
<p align="center"><img src="https://user-images.githubusercontent.com/47952/56085330-6c0a2480-5e41-11e9-89ba-0cfddd7714a8.png" height="200"></p>
[](https://goreportcard.com/report/github.com/zegl/kube-score)
[](https://github.com/zegl/kube-score/actions/workflows/test.yaml)
[](https://github.com/zegl/kube-score/releases)


[](https://github.com/zegl/kube-score/blob/master/LICENSE)
---
`kube-score` is a tool that performs static code analysis of your Kubernetes object definitions.
The output is a list of recommendations of what you can improve to make your application more secure and resilient.
You can test kube-score out in the browser with the [online demo](https://kube-score.com) ([source](https://github.com/kube-score/web)).
## Installation
kube-score is easy to install, and is available from the following sources:
| Distribution | Command / Link |
|-----------------------------------------------------|-----------------------------------------------------------------------------------------|
| Pre-built binaries for macOS, Linux, and Windows | [GitHub releases](https://github.com/zegl/kube-score/releases) |
| Docker | `docker pull zegl/kube-score` ([Docker Hub)](https://hub.docker.com/r/zegl/kube-score/) |
| Homebrew (macOS and Linux) | `brew install kube-score` |
| [Krew](https://krew.sigs.k8s.io/) (macOS and Linux) | `kubectl krew install score` |
## Sponsors
[_Your company here?_ ](https://polar.sh/zegl/subscriptions)
## Checks
For a full list of checks, see [README_CHECKS.md](README_CHECKS.md).
* Container limits (should be set)
* Pod is targeted by a `NetworkPolicy`, both egress and ingress rules are recommended
* Deployments and StatefulSets should have a `PodDisruptionPolicy`
* Deployments and StatefulSets should have host PodAntiAffinity configured
* Container probes, a readiness should be configured, and should not be identical to the liveness probe. Read more in [README_PROBES.md](README_PROBES.md).
* Container securityContext, run as high number user/group, do not run as root or with privileged root fs. Read more in [README_SECURITYCONTEXT.md](README_SECURITYCONTEXT.md).
* Stable APIs, use a stable API if available (supported: Deployments, StatefulSets, DaemonSet)
## Example output

## Usage in CI
`kube-score` can run in your CI/CD environment and will exit with exit code 1 if a critical error has been found.
The trigger level can be changed to warning with the `--exit-one-on-warning` argument.
The input to `kube-score` should be all applications that you deploy to the same namespace for the best result.
### Example with Helm
```bash
helm template my-app | kube-score score -
```
### Example with Kustomize
```bash
kustomize build . | kube-score score -
```
### Example with static YAMLs
```bash
kube-score score my-app/*.yaml
```
```bash
kube-score score my-app/deployment.yaml my-app/service.yaml
```
### Example with an existing cluster
```bash
kubectl api-resources --verbs=list --namespaced -o name \
| xargs -n1 -I{} bash -c "kubectl get {} --all-namespaces -oyaml && echo ---" \
| kube-score score -
```
### Example with Docker
```bash
docker run -v $(pwd):/project zegl/kube-score:latest score my-app/*.yaml
```
## Configuration
```
Usage of kube-score:
kube-score [action] --flags
Actions:
score Checks all files in the input, and gives them a score and recommendations
list Prints a CSV list of all available score checks
version Print the version of kube-score
help Print this message
Flags for score:
--disable-ignore-checks-annotations Set to true to disable the effect of the 'kube-score/ignore' annotations
--disable-optional-checks-annotations Set to true to disable the effect of the 'kube-score/enable' annotations
--enable-optional-test strings Enable an optional test, can be set multiple times
--exit-one-on-warning Exit with code 1 in case of warnings
--help Print help
--ignore-container-cpu-limit Disables the requirement of setting a container CPU limit
--ignore-container-memory-limit Disables the requirement of setting a container memory limit
--ignore-test strings Disable a test, can be set multiple times
--kubernetes-version string Setting the kubernetes-version will affect the checks ran against the manifests. Set this to the version of Kubernetes that you're using in production for the best results. (default "v1.18")
-o, --output-format string Set to 'human', 'json', 'ci' or 'sarif'. If set to ci, kube-score will output the program in a format that is easier to parse by other programs. Sarif output allows for easier integration with CI platforms. (default "human")
--output-version string Changes the version of the --output-format. The 'json' format has version 'v2' (default) and 'v1' (deprecated, will be removed in v1.7.0). The 'human' and 'ci' formats has only version 'v1' (default). If not explicitly set, the default version for that particular output format will be used.
-v, --verbose count Enable verbose output, can be set multiple times for increased verbosity.
```
### Ignoring a test
Tests can be ignored in the whole run of the program, with the `--ignore-test` flag.
A test can also be ignored on a per-object basis, by adding the annotation `kube-score/ignore` to the object.
The value should be a comma-separated string of the [test IDs](README_CHECKS.md).
Example:
Testing this object will temporarily disable the `service-type` test, which warns against using services of type NodePort.
```yaml
apiVersion: v1
kind: Service
metadata:
name: node-port-service-with-ignore
namespace: foospace
annotations:
kube-score/ignore: service-type
spec:
selector:
app: my-app
ports:
- protocol: TCP
port: 80
targetPort: 8080
type: NodePort
```
### Enabling an optional test
Optional tests can be enabled in the whole run of the program, with the `--enable-optional-test` flag.
A test can also be enabled on a per-object basis, by adding the annotation `kube-score/enable` to the object.
The value should be a comma-separated string of the [test IDs](README_CHECKS.md).
Example:
Testing this object will enable the `container-seccomp-profile` test.
Also, multiple tests defined by `kube-score/ignore` are also ignored at the same.
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: optional-test-manifest-deployment
labels:
app: optional-test-manifest
annotations:
kube-score/ignore: pod-networkpolicy,container-resources,container-image-pull-policy,container-security-context-privileged,container-security-context-user-group-id,container-security-context-readonlyrootfilesystem,container-ephemeral-storage-request-and-limit
kube-score/enable: container-seccomp-profile
spec:
replicas: 1
selector:
matchLabels:
app: optional-test-manifest
template:
metadata:
labels:
app: optional-test-manifest
spec:
containers:
- name: optional-test-manifest
image: busybox:1.34
command:
- /bin/sh
- -c
- date; env; tail -f /dev/null
```
## Building from source
`kube-score` requires [Go](https://golang.org/) `1.21` or later to build. Clone this repository, and then:
```bash
# Build the project
go build ./cmd/kube-score
# Run all tests
go test -v ./...
```
## Contributing?
Do you want to help out? Take a look at the [Contributing Guidelines](./.github/CONTRIBUTING.md) for more info. 🤩
## Dependencies
| Project | Version |
|---------------------|---------|
| go.dev | ^1.21 |
## Made by
<a href="https://github.com/zegl/kube-score/graphs/contributors">
<img src="https://contrib.rocks/image?repo=zegl/kube-score" />
</a>
07070100000005000081A4000000000000000000000001680FB1F8000014C4000000000000000000000000000000000000002300000000kube-score-1.20.0/README_CHECKS.md<!-- This file was generated by hack/generate-list-docs.py -->
| ID | Target | Description | Enabled |
|----|--------|-------------|---------|
| deployment-strategy | Deployment | Makes sure that all Deployments targeted by service use RollingUpdate strategy | default |
| deployment-replicas | Deployment | Makes sure that Deployment has multiple replicas. The --min-replicas-deployment flag can be used to specify the required minimum. Default is 2. | default |
| ingress-targets-service | Ingress | Makes sure that the Ingress targets a Service | default |
| cronjob-has-deadline | CronJob | Makes sure that all CronJobs has a configured deadline | default |
| cronjob-restartpolicy | CronJob | Makes sure CronJobs have a valid RestartPolicy | default |
| container-resources | Pod | Makes sure that all pods have resource limits and requests set. The --ignore-container-cpu-limit flag can be used to disable the requirement of having a CPU limit | default |
| container-resource-requests-equal-limits | Pod | Makes sure that all pods have the same requests as limits on resources set. | optional |
| container-cpu-requests-equal-limits | Pod | Makes sure that all pods have the same CPU requests as limits set. | optional |
| container-memory-requests-equal-limits | Pod | Makes sure that all pods have the same memory requests as limits set. | optional |
| container-image-tag | Pod | Makes sure that a explicit non-latest tag is used | default |
| container-image-pull-policy | Pod | Makes sure that the pullPolicy is set to Always. This makes sure that imagePullSecrets are always validated. | default |
| container-ephemeral-storage-request-and-limit | Pod | Makes sure all pods have ephemeral-storage requests and limits set | default |
| container-ephemeral-storage-request-equals-limit | Pod | Make sure all pods have matching ephemeral-storage requests and limits | optional |
| container-ports-check | Pod | Container Ports Checks | optional |
| environment-variable-key-duplication | Pod | Makes sure that duplicated environment variable keys are not duplicated | default |
| statefulset-has-poddisruptionbudget | StatefulSet | Makes sure that all StatefulSets are targeted by a PDB | default |
| deployment-has-poddisruptionbudget | Deployment | Makes sure that all Deployments are targeted by a PDB | default |
| poddisruptionbudget-has-policy | PodDisruptionBudget | Makes sure that PodDisruptionBudgets specify minAvailable or maxUnavailable | default |
| pod-networkpolicy | Pod | Makes sure that all Pods are targeted by a NetworkPolicy | default |
| networkpolicy-targets-pod | NetworkPolicy | Makes sure that all NetworkPolicies targets at least one Pod | default |
| pod-probes | Pod | Makes sure that all Pods have safe probe configurations | default |
| pod-probes-identical | Pod | Makes sure that readiness and liveness probes are not identical | default |
| container-security-context-user-group-id | Pod | Makes sure that all pods have a security context with valid UID and GID set | default |
| container-security-context-privileged | Pod | Makes sure that all pods have a unprivileged security context set | default |
| container-security-context-readonlyrootfilesystem | Pod | Makes sure that all pods have a security context with read only filesystem set | default |
| container-seccomp-profile | Pod | Makes sure that all pods have at a seccomp policy configured. | optional |
| service-targets-pod | Service | Makes sure that all Services targets a Pod | default |
| service-type | Service | Makes sure that the Service type is not NodePort | default |
| stable-version | all | Checks if the object is using a deprecated apiVersion | default |
| deployment-has-host-podantiaffinity | Deployment | Makes sure that a podAntiAffinity has been set that prevents multiple pods from being scheduled on the same node. https://kubernetes.io/docs/concepts/configuration/assign-pod-node/ | default |
| statefulset-has-host-podantiaffinity | StatefulSet | Makes sure that a podAntiAffinity has been set that prevents multiple pods from being scheduled on the same node. https://kubernetes.io/docs/concepts/configuration/assign-pod-node/ | default |
| deployment-targeted-by-hpa-does-not-have-replicas-configured | Deployment | Makes sure that Deployments using a HorizontalPodAutoscaler doesn't have a statically configured replica count set | default |
| statefulset-has-servicename | StatefulSet | Makes sure that StatefulSets have an existing headless serviceName. | default |
| deployment-pod-selector-labels-match-template-metadata-labels | Deployment | Ensure the StatefulSet selector labels match the template metadata labels. | default |
| statefulset-pod-selector-labels-match-template-metadata-labels | StatefulSet | Ensure the StatefulSet selector labels match the template metadata labels. | default |
| label-values | all | Validates label values | default |
| horizontalpodautoscaler-has-target | HorizontalPodAutoscaler | Makes sure that the HPA targets a valid object | default |
| horizontalpodautoscaler-replicas | HorizontalPodAutoscaler | Makes sure that the HPA has multiple replicas. The --min-replicas-hpa flag can be used to specify the required minimum. Default is 2. | default |
| pod-topology-spread-constraints | Pod | Pod Topology Spread Constraints | default |
07070100000006000081A4000000000000000000000001680FB1F800000DE4000000000000000000000000000000000000002300000000kube-score-1.20.0/README_PROBES.md# Readiness and Liveness Probes
Kubernetes supports three types of probes, that all serve different purposes, and have different pros and cons.
This article is here to describe what recommendations kube-score will make in what situations.
## Different probe types
### `readinessProbe`
This probe roughly answers the question
> Is it a good idea to send traffic to this Pod right now?
A common *misunderstanding* is that, since Kubernetes manages the Pods, you don't need to do graceful draining of Pods during shutdown.
Without a readinessProbe you're risking that:
* Traffic is sent to the Pod before the server has started.
* Traffic is still sent to the Pod _after_ the Pod has stopped.
**kube-score recommends**:
Every application that is targeted by a Service to:
* Setup a readinessProbe that responds with a healthy status when: The application is fully booted, and ready to serve traffic.
* Set `interval` (default: 10s), `timeout` (default: 1s), `successThreshold` (default: 1), `failureThreshold` (default: 3) to your needs. In the default configuration, your application will fail for 30s (+ the time it takes for the network to react), for clients to stop sending traffic to your application.
* Don't depend on downstream dependencies, such as other services or databases in your probe. If the dependency has a hickup, or for example, a database is restarted, removing your Pods from the Load Balancers rotation will likely only make the downtime worse.
* Handle shutdowns gracefully: Applications should start to fail the readinessProbe after receiving `SIGTERM`, and _wait_ until the service is unregistered from all Load Balancers, before shutting down. As an alternative to this, a [`preStop`](https://kubernetes.io/docs/tasks/configure-pod-container/attach-handler-lifecycle-event/#define-poststart-and-prestop-handlers) hook with a sleep can be used.
### `livenessProbe`
This probe roughly answers the question
> Is the _container_ healthy right now, or do we need to restart it?
It can be used to let Kubernetes know if your application is deadlocked, and needs to be restarted. Only the container with the failing probe will be restarted, other containers in the same Pod will be unaffected.
**kube-score recommends**:
* If you don't know why you need a livenessProbe, don't configure it.
* It should _never_, be the same as your `readinessProbe`.
* The livenessProbe should *never* depend on downstream dependencies, such as databases or other services.
### `startupProbe` (alpha since v1.16, beta since v1.17)
This probe roughly answers the question
> Should we start running the livenessProbe now?
For applications that take a longer time to boot than the livenessProbes `initialDelaySeconds` + `periodSeconds` * `failureThreshold`, a `startupProbe` can be configured.
The startupProbe allows you to decrease the liveness probes `initialDelaySeconds`, and catch application deadlocks earlier.
As soon as the startupProbe has succeeded once, the livenessProbe will start to be executed.
**kube-score recommends**:
* Configure a startupProbe if you have a livenessProbe configured.
## Further reading
* [Pod Lifecycle, kubernetes.io](https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#container-probes)
* [Configure Liveness, Readiness and Startup Probes, kubernetes.io](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/)
* [Liveness probes are dangerous, srcco.de](https://srcco.de/posts/kubernetes-liveness-probes-are-dangerous.html)
07070100000007000081A4000000000000000000000001680FB1F8000006E5000000000000000000000000000000000000002C00000000kube-score-1.20.0/README_SECURITYCONTEXT.md# Security Context
The default `container-security-context` check checks the `SecurityContext`
for
* Containers with writeable root filesystems
* Containers that run with user ID or group ID < 10000
* Privileged containers
If you do not want all of this checks you can disable `container-security-context`
and enable one or more of the following optional checks:
* `container-security-context-user-group-id`
* `container-security-context-privileged`
* `container-security-context-readonlyrootfilesystem`
## Removal timeline of `container-security-context`
`container-security-context` has been deprecated (see [#204](https://github.com/zegl/kube-score/pull/204), [#325](https://github.com/zegl/kube-score/pull/325), [#326](https://github.com/zegl/kube-score/pull/326)).
The checks that has container-security-context preformed has been split into three different checks, which where all introduced in v1.10.
* v1.10: Introduce the three new checks (opt-in), and officially deprecate `container-security-context`.
* v1.12: Make `container-security-context` optional (opt-in), and make the three new checks run by default.
* v1.13: Remove `container-security-context`.
In v1.10, run kube-score with the following flags to ensure compatability with v1.12 and later:
```bash
kube-score score \
--enable-optional-test container-security-context-user-group-id \
--enable-optional-test container-security-context-privileged \
--enable-optional-test container-security-context-readonlyrootfilesystem \
--ignore-test container-security-context
```
----
_Note:_ The "flip" and the deletion of the tests where originally scheduled to happen in v1.11 and v1.12. This did not happend, and the migration is now scheduled for v1.12 and v1.13 instead.
07070100000008000041ED000000000000000000000002680FB1F800000000000000000000000000000000000000000000001600000000kube-score-1.20.0/cmd07070100000009000041ED000000000000000000000002680FB1F800000000000000000000000000000000000000000000002100000000kube-score-1.20.0/cmd/kube-score0707010000000A000081A4000000000000000000000001680FB1F80000004B000000000000000000000000000000000000002C00000000kube-score-1.20.0/cmd/kube-score/DockerfileFROM scratch
COPY kube-score /
WORKDIR /project
ENTRYPOINT ["/kube-score"]
0707010000000B000081A4000000000000000000000001680FB1F8000004E2000000000000000000000000000000000000002800000000kube-score-1.20.0/cmd/kube-score/cmd.gopackage main
import (
"fmt"
"path"
"strings"
)
type cmdFunc func(string, []string)
func parse(args []string, cmds map[string]cmdFunc) (command string, cmdArgsOffset int, err error) {
helpName := execName(args[0])
// When executing kube-score as a kubectl plugin, default to the "score" sub-command to avoid stuttering
// "kubectl score" is equivalent to "kubectl score score"
if isKubectlPlugin(helpName) {
command = "score"
cmdArgsOffset = 1
}
// No command, flag, or file has been specified
if len(args) <= cmdArgsOffset {
err = fmt.Errorf("No command, flag or file")
return
}
// If arg 1 is set and is a valid command, always use it as the command to execute, instead of the default
if len(args) > 1 {
if _, ok := cmds[args[1]]; ok {
command = args[1]
cmdArgsOffset = 2
}
}
return
}
func execName(args0 string) string {
// Detect name of the binary
binName := path.Base(args0)
// If executed as a kubectl plugin, replace dash with a space
// "kubectl-score" -> "kubectl score"
if strings.HasPrefix(binName, "kubectl-") {
binName = strings.Replace(binName, "kubectl-", "kubectl ", 1)
}
return binName
}
func isKubectlPlugin(helpName string) bool {
return execName(helpName) == "kubectl score"
}
0707010000000C000081A4000000000000000000000001680FB1F8000003DD000000000000000000000000000000000000002D00000000kube-score-1.20.0/cmd/kube-score/cmd_test.gopackage main
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestParseCli(t *testing.T) {
cmds := map[string]cmdFunc{
"a": func(string, []string) {},
"b": func(string, []string) {},
"score": func(string, []string) {},
}
cmd, offset, err := parse([]string{"kube-score", "a"}, cmds)
assert.Equal(t, "a", cmd)
assert.Equal(t, 2, offset)
assert.Nil(t, err)
cmd, offset, err = parse([]string{"kube-score", "unknown"}, cmds)
assert.Equal(t, "", cmd)
assert.Equal(t, 0, offset)
assert.Nil(t, err)
cmd, offset, err = parse([]string{"kubectl-score", "a"}, cmds)
assert.Equal(t, "a", cmd)
assert.Equal(t, 2, offset)
assert.Nil(t, err)
cmd, offset, err = parse([]string{"kubectl-score", "xyz.yaml"}, cmds)
assert.Equal(t, "score", cmd)
assert.Equal(t, 1, offset)
assert.Nil(t, err)
cmd, offset, err = parse([]string{"kubectl-score", "score", "xyz.yaml"}, cmds)
assert.Equal(t, "score", cmd)
assert.Equal(t, 2, offset)
assert.Nil(t, err)
}
0707010000000D000081A4000000000000000000000001680FB1F8000000FF000000000000000000000000000000000000003000000000kube-score-1.20.0/cmd/kube-score/cmd_version.gopackage main
import (
"fmt"
)
// These variables are overwritten by goreleaser
var version = "development"
var commit = "N/A"
var date = "N/A"
func cmdVersion() {
fmt.Printf("kube-score version: %s, commit: %s, built: %s\n", version, commit, date)
}
0707010000000E000081A4000000000000000000000001680FB1F80000026A000000000000000000000000000000000000003100000000kube-score-1.20.0/cmd/kube-score/helm.DockerfileFROM debian:stretch as downloader
ARG HELM_VERSION=v2.17.0
ARG HELM_SHA256SUM="f3bec3c7c55f6a9eb9e6586b8c503f370af92fe987fcbf741f37707606d70296"
RUN apt-get update && \
apt-get install -y curl && \
curl --location "https://get.helm.sh/helm-${HELM_VERSION}-linux-amd64.tar.gz" > helm.tar.gz && \
echo "${HELM_SHA256SUM} helm.tar.gz" | sha256sum --check && \
tar xzvf helm.tar.gz && \
chmod +x /linux-amd64/helm
FROM alpine:3.14.2
RUN apk update && \
apk upgrade && \
apk add bash ca-certificates
COPY --from=downloader /linux-amd64/helm /usr/bin/helm
COPY kube-score /usr/bin/kube-score
0707010000000F000081A4000000000000000000000001680FB1F800000269000000000000000000000000000000000000003200000000kube-score-1.20.0/cmd/kube-score/helm3.DockerfileFROM debian:stretch as downloader
ARG HELM_VERSION=v3.7.0
ARG HELM_SHA256SUM="096e30f54c3ccdabe30a8093f8e128dba76bb67af697b85db6ed0453a2701bf9"
RUN apt-get update && \
apt-get install -y curl && \
curl --location "https://get.helm.sh/helm-${HELM_VERSION}-linux-amd64.tar.gz" > helm.tar.gz && \
echo "${HELM_SHA256SUM} helm.tar.gz" | sha256sum --check && \
tar xzvf helm.tar.gz && \
chmod +x /linux-amd64/helm
FROM alpine:3.14.2
RUN apk update && \
apk upgrade && \
apk add bash ca-certificates
COPY --from=downloader /linux-amd64/helm /usr/bin/helm
COPY kube-score /usr/bin/kube-score
07070100000010000081A4000000000000000000000001680FB1F80000010B000000000000000000000000000000000000002E00000000kube-score-1.20.0/cmd/kube-score/help_test.gopackage main
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestExecName(t *testing.T) {
assert.Equal(t, "kube-score", execName("kube-score"))
assert.Equal(t, "ks", execName("ks"))
assert.Equal(t, "kubectl score", execName("kubectl-score"))
}
07070100000011000081A4000000000000000000000001680FB1F8000002D6000000000000000000000000000000000000003600000000kube-score-1.20.0/cmd/kube-score/kustomize.DockerfileFROM debian:stretch as downloader
ARG KUSTOMIZE_VERSION=v4.5.4
ARG KUSTOMIZE_SHA256SUM="1159c5c17c964257123b10e7d8864e9fe7f9a580d4124a388e746e4003added3"
RUN apt-get update && \
apt-get install -y curl && \
curl --location "https://github.com/kubernetes-sigs/kustomize/releases/download/kustomize%2F${KUSTOMIZE_VERSION}/kustomize_${KUSTOMIZE_VERSION}_linux_amd64.tar.gz" > kustomize.tar.gz && \
echo "${KUSTOMIZE_SHA256SUM} kustomize.tar.gz" | sha256sum --check && \
tar xzvf kustomize.tar.gz && \
chmod +x kustomize
FROM alpine:3.10.1
RUN apk update && \
apk upgrade && \
apk add bash ca-certificates git
COPY --from=downloader kustomize /usr/bin/kustomize
COPY kube-score /usr/bin/kube-score
07070100000012000081A4000000000000000000000001680FB1F800002BCE000000000000000000000000000000000000002900000000kube-score-1.20.0/cmd/kube-score/main.gopackage main
import (
"bytes"
"encoding/csv"
"encoding/json"
"errors"
"fmt"
"io"
"os"
"path/filepath"
"github.com/mattn/go-isatty"
flag "github.com/spf13/pflag"
"github.com/zegl/kube-score/config"
ks "github.com/zegl/kube-score/domain"
"github.com/zegl/kube-score/parser"
"github.com/zegl/kube-score/renderer/ci"
"github.com/zegl/kube-score/renderer/human"
"github.com/zegl/kube-score/renderer/json_v2"
"github.com/zegl/kube-score/renderer/junit"
"github.com/zegl/kube-score/renderer/sarif"
"github.com/zegl/kube-score/score"
"github.com/zegl/kube-score/score/checks"
"github.com/zegl/kube-score/scorecard"
"golang.org/x/term"
)
func main() {
helpName := execName(os.Args[0])
fs := flag.NewFlagSet(helpName, flag.ExitOnError)
setDefault(fs, helpName, "", true)
cmds := map[string]cmdFunc{
"score": func(helpName string, args []string) {
if err := scoreFiles(helpName, args); err != nil {
_, _ = fmt.Fprintf(os.Stderr, "Failed to score files: %v\n", err)
os.Exit(1)
}
},
"list": func(helpName string, args []string) {
if err := listChecks(helpName, args); err != nil {
_, _ = fmt.Fprintf(os.Stderr, "Failed to list checks: %v\n", err)
os.Exit(1)
}
},
"version": func(helpName string, args []string) {
cmdVersion()
},
"help": func(helpName string, args []string) {
fs.Usage()
os.Exit(1)
},
}
command, cmdArgsOffset, err := parse(os.Args, cmds)
if err != nil {
fs.Usage()
os.Exit(1)
}
// Execute the command, or the help command if no matching command is found
if ex, ok := cmds[command]; ok {
ex(helpName, os.Args[cmdArgsOffset:])
} else {
cmds["help"](helpName, os.Args[cmdArgsOffset:])
}
}
func setDefault(fs *flag.FlagSet, binName, actionName string, displayForMoreInfo bool) {
fs.Usage = func() {
usage := fmt.Sprintf(`Usage of %s:
%s [action] --flags
Actions:
score Checks all files in the input, and gives them a score and recommendations
list Prints a CSV list of all available score checks
version Print the version of kube-score
help Print this message`+"\n\n", binName, binName)
if displayForMoreInfo {
usage += fmt.Sprintf(`Run "%s [action] --help" for more information about a particular command`, binName)
}
if len(actionName) > 0 {
usage += "Flags for " + actionName + ":"
}
fmt.Println(usage)
if len(actionName) > 0 {
fs.PrintDefaults()
}
}
}
func scoreFiles(binName string, args []string) error {
fs := flag.NewFlagSet(binName, flag.ExitOnError)
exitOneOnWarning := fs.Bool("exit-one-on-warning", false, "Exit with code 1 in case of warnings")
ignoreContainerCpuLimit := fs.Bool("ignore-container-cpu-limit", false, "Disables the requirement of setting a container CPU limit")
ignoreContainerMemoryLimit := fs.Bool("ignore-container-memory-limit", false, "Disables the requirement of setting a container memory limit")
verboseOutput := fs.CountP("verbose", "v", "Enable verbose output, can be set multiple times for increased verbosity.")
printHelp := fs.Bool("help", false, "Print help")
outputFormat := fs.StringP("output-format", "o", "human", "Set to 'human', 'json', 'ci' or 'sarif'. If set to ci, kube-score will output the program in a format that is easier to parse by other programs. Sarif output allows for easier integration with CI platforms.")
outputVersion := fs.String("output-version", "", "Changes the version of the --output-format. The 'json' format has version 'v2' (default) and 'v1' (deprecated, will be removed in v1.7.0). The 'human' and 'ci' formats has only version 'v1' (default). If not explicitly set, the default version for that particular output format will be used.")
color := fs.String("color", "auto", "If the output should be colored. Set to 'always', 'never' or 'auto'. If set to 'auto', kube-score will try to detect if the current terminal / platform supports colors. If set to 'never', kube-score will not output any colors. If set to 'always', kube-score will output colors even if the current terminal / platform does not support colors.")
optionalTests := fs.StringSlice("enable-optional-test", []string{}, "Enable an optional test, can be set multiple times")
ignoreTests := fs.StringSlice("ignore-test", []string{}, "Disable a test, can be set multiple times")
disableIgnoreChecksAnnotation := fs.Bool("disable-ignore-checks-annotations", false, "Set to true to disable the effect of the 'kube-score/ignore' annotations")
disableOptionalChecksAnnotation := fs.Bool("disable-optional-checks-annotations", false, "Set to true to disable the effect of the 'kube-score/enable' annotations")
allDefaultOptional := fs.Bool("all-default-optional", false, "Set to true to enable all tests")
kubernetesVersion := fs.String("kubernetes-version", "v1.18", "Setting the kubernetes-version will affect the checks ran against the manifests. Set this to the version of Kubernetes that you're using in production for the best results.")
minReplicasDeployment := fs.Int("min-replicas-deployment", 2, "Minimum required number of replicas for a deployment")
minReplicasHPA := fs.Int("min-replicas-hpa", 2, "Minimum required number of replicas for a horizontal pod autoscaler")
setDefault(fs, binName, "score", false)
err := fs.Parse(args)
if err != nil {
return fmt.Errorf("failed to parse files: %w", err)
}
if *printHelp {
fs.Usage()
return nil
}
if *outputFormat != "human" && *outputFormat != "ci" && *outputFormat != "json" && *outputFormat != "sarif" && *outputFormat != "junit" {
fs.Usage()
return fmt.Errorf("Error: --output-format must be set to: 'human', 'json', 'sarif', 'junit' or 'ci'")
}
acceptedColors := map[string]bool{
"auto": true,
"always": true,
"never": true,
}
if !acceptedColors[*color] {
fs.Usage()
return fmt.Errorf("Error: --color must be set to: 'auto', 'always' or 'never'")
}
filesToRead := fs.Args()
if len(filesToRead) == 0 {
return fmt.Errorf(`Error: No files given as arguments.
Usage: %s score [--flag1 --flag2] file1 file2 ...
Use "-" as filename to read from STDIN.`, execName(binName))
}
var allFilePointers []ks.NamedReader
for _, file := range filesToRead {
var fp io.Reader
var filename string
if file == "-" {
fp = os.Stdin
filename = "STDIN"
} else {
var err error
fp, err = os.Open(file)
if err != nil {
return err
}
filename, _ = filepath.Abs(file)
}
allFilePointers = append(allFilePointers, namedReader{Reader: fp, name: filename})
}
if len(*ignoreTests) > 0 && *allDefaultOptional {
return errors.New("Invalid argument combination. --all-default-optional and --ignore-tests cannot be used together")
}
if *allDefaultOptional {
var addOptionalChecks []string
for _, c := range score.RegisterAllChecks(parser.Empty(), nil, nil).All() {
if c.Optional {
addOptionalChecks = append(addOptionalChecks, c.ID)
}
}
optionalTests = &addOptionalChecks
}
ignoredTests := listToStructMap(ignoreTests)
enabledOptionalTests := listToStructMap(optionalTests)
kubeVer, err := config.ParseSemver(*kubernetesVersion)
if err != nil {
return errors.New("Invalid --kubernetes-version. Use on format \"vN.NN\"")
}
runConfig := &config.RunConfiguration{
IgnoreContainerCpuLimitRequirement: *ignoreContainerCpuLimit,
IgnoreContainerMemoryLimitRequirement: *ignoreContainerMemoryLimit,
EnabledOptionalTests: enabledOptionalTests,
UseIgnoreChecksAnnotation: !*disableIgnoreChecksAnnotation,
UseOptionalChecksAnnotation: !*disableOptionalChecksAnnotation,
KubernetesVersion: kubeVer,
MinReplicasDeployment: *minReplicasDeployment,
MinReplicasHPA: *minReplicasHPA,
}
p, err := parser.New(&parser.Config{
VerboseOutput: *verboseOutput,
})
if err != nil {
return fmt.Errorf("failed to initializer parser: %w", err)
}
parsedFiles, err := p.ParseFiles(allFilePointers)
if err != nil {
return fmt.Errorf("failed to parse files: %w", err)
}
checks := score.RegisterAllChecks(parsedFiles, &checks.Config{IgnoredTests: ignoredTests}, runConfig)
scoreCard, err := score.Score(parsedFiles, checks, runConfig)
if err != nil {
return err
}
var exitCode int
switch {
case scoreCard.AnyBelowOrEqualToGrade(scorecard.GradeCritical):
exitCode = 1
case *exitOneOnWarning && scoreCard.AnyBelowOrEqualToGrade(scorecard.GradeWarning):
exitCode = 1
default:
exitCode = 0
}
var r io.Reader
version := getOutputVersion(*outputVersion, *outputFormat)
switch {
case *outputFormat == "json" && version == "v1":
d, _ := json.MarshalIndent(scoreCard, "", " ")
w := bytes.NewBufferString("")
w.WriteString(string(d))
r = w
case *outputFormat == "json" && version == "v2":
r = json_v2.Output(scoreCard)
case *outputFormat == "human" && version == "v1":
termWidth, _, err := term.GetSize(int(os.Stdin.Fd()))
// Assume a width of 80 if it can't be detected
if err != nil {
termWidth = 80
}
r, err = human.Human(scoreCard, *verboseOutput, termWidth, useColor(*color))
if err != nil {
return err
}
case *outputFormat == "ci" && version == "v1":
r = ci.CI(scoreCard)
case *outputFormat == "sarif":
r = sarif.Output(scoreCard)
case *outputFormat == "junit":
r = junit.JUnit(scoreCard)
default:
return fmt.Errorf("error: Unknown --output-format or --output-version")
}
output, _ := io.ReadAll(r)
fmt.Print(string(output))
os.Exit(exitCode)
return nil
}
func getOutputVersion(flagValue, format string) string {
if len(flagValue) > 0 {
return flagValue
}
switch format {
case "json":
return "v2"
default:
return "v1"
}
}
func listChecks(binName string, args []string) error {
fs := flag.NewFlagSet(binName, flag.ExitOnError)
printHelp := fs.Bool("help", false, "Print help")
setDefault(fs, binName, "list", false)
err := fs.Parse(args)
if err != nil {
return nil
}
if *printHelp {
fs.Usage()
return nil
}
allChecks := score.RegisterAllChecks(parser.Empty(), nil, nil)
output := csv.NewWriter(os.Stdout)
for _, c := range allChecks.All() {
optionalString := "default"
if c.Optional {
optionalString = "optional"
}
err := output.Write([]string{c.ID, c.TargetType, c.Comment, optionalString})
if err != nil {
return nil
}
}
output.Flush()
return nil
}
func listToStructMap(items *[]string) map[string]struct{} {
structMap := make(map[string]struct{})
for _, testID := range *items {
structMap[testID] = struct{}{}
}
return structMap
}
type namedReader struct {
io.Reader
name string
}
func (n namedReader) Name() string {
return n.name
}
func useColor(colorArg string) bool {
// Respect user preference
switch colorArg {
case "always":
return true
case "never":
return false
}
// If running on Github Actions, use colors
if _, ok := os.LookupEnv("GITHUB_ACTIONS"); ok {
return true
}
// If NO_COLOR is set, don't use color
if _, ok := os.LookupEnv("NO_COLOR"); ok {
return false
}
// Dont use color if not a terminal
if os.Getenv("TERM") == "dumb" {
return false
}
if !isatty.IsTerminal(os.Stdout.Fd()) && !isatty.IsCygwinTerminal(os.Stdout.Fd()) {
return false
}
// Use colors
return true
}
07070100000013000041ED000000000000000000000002680FB1F800000000000000000000000000000000000000000000001B00000000kube-score-1.20.0/cmd/wasm07070100000014000081A4000000000000000000000001680FB1F8000009D1000000000000000000000000000000000000002300000000kube-score-1.20.0/cmd/wasm/main.go//go:build wasm
// +build wasm
package main
import (
"bytes"
"fmt"
"io"
"os"
"strings"
"syscall/js"
t2html "github.com/buildkite/terminal-to-html"
"github.com/zegl/kube-score/config"
"github.com/zegl/kube-score/domain"
"github.com/zegl/kube-score/parser"
"github.com/zegl/kube-score/renderer/ci"
"github.com/zegl/kube-score/renderer/human"
"github.com/zegl/kube-score/renderer/json_v2"
"github.com/zegl/kube-score/renderer/junit"
"github.com/zegl/kube-score/renderer/sarif"
"github.com/zegl/kube-score/score"
"github.com/zegl/kube-score/score/checks"
"golang.org/x/term"
)
func main() {
js.Global().Set("handleScore", js.FuncOf(handleScore))
select {}
}
type inputReader struct {
*strings.Reader
}
func (inputReader) Name() string {
return "input"
}
func handleScore(this js.Value, inputs []js.Value) interface{} {
if len(inputs) != 2 {
fmt.Println("Unexpected number of arguments")
return "Unexpected number of arguments"
}
inputYaml := inputs[0].String()
format := inputs[1].String()
reader := &inputReader{
Reader: strings.NewReader(inputYaml),
}
files := []domain.NamedReader{reader}
p, err := parser.New(&parser.Config{})
if err != nil {
fmt.Println(err)
return string(err.Error())
}
allObjs, err := p.ParseFiles(files)
if err != nil {
fmt.Println(err)
return string(err.Error())
}
checksConfig := &checks.Config{}
runConfig := &config.RunConfiguration{}
allChecks := score.RegisterAllChecks(allObjs, checksConfig, runConfig)
scoreCard, err := score.Score(allObjs, allChecks, runConfig)
if err != nil {
fmt.Println(err)
return string(err.Error())
}
var r io.Reader
switch format {
case "json":
r = json_v2.Output(scoreCard)
case "human":
termWidth, _, err := term.GetSize(int(os.Stdin.Fd()))
// Assume a width of 80 if it can't be detected
if err != nil {
termWidth = 80
}
body, err := human.Human(scoreCard, 0, termWidth, true)
if err != nil {
fmt.Println(err)
return string(err.Error())
}
bodyBytes, err := io.ReadAll(body)
if err != nil {
fmt.Println(err)
return string(err.Error())
}
htmlBody := t2html.Render(bodyBytes)
r = bytes.NewReader(htmlBody)
case "ci":
r = ci.CI(scoreCard)
case "sarif":
r = sarif.Output(scoreCard)
case "junit":
r = junit.JUnit(scoreCard)
default:
return fmt.Errorf("error: unknown format")
}
body, err := io.ReadAll(r)
fmt.Println("body", body)
if err != nil {
fmt.Println(err)
return string(err.Error())
}
return string(body)
}
07070100000015000041ED000000000000000000000002680FB1F800000000000000000000000000000000000000000000001900000000kube-score-1.20.0/config07070100000016000081A4000000000000000000000001680FB1F800000543000000000000000000000000000000000000002300000000kube-score-1.20.0/config/config.gopackage config
import (
"errors"
"fmt"
"strconv"
"strings"
)
type RunConfiguration struct {
IgnoreContainerCpuLimitRequirement bool
IgnoreContainerMemoryLimitRequirement bool
EnabledOptionalTests map[string]struct{}
UseIgnoreChecksAnnotation bool
UseOptionalChecksAnnotation bool
KubernetesVersion Semver
MinReplicasDeployment int
MinReplicasHPA int
}
type Semver struct {
Major int
Minor int
}
var errInvalidSemver = errors.New("invalid semver")
func ParseSemver(s string) (Semver, error) {
if len(s) == 0 {
return Semver{}, errInvalidSemver
}
start := 0
if s[0] == 'v' {
start = 1
}
// Separate by .
parts := strings.Split(s[start:], ".")
if len(parts) != 2 {
return Semver{}, errInvalidSemver
}
major, err := strconv.Atoi(parts[0])
if err != nil {
return Semver{}, errInvalidSemver
}
minor, err := strconv.Atoi(parts[1])
if err != nil {
return Semver{}, errInvalidSemver
}
return Semver{
Major: major,
Minor: minor,
}, nil
}
func (s Semver) LessThan(other Semver) bool {
if s.Major < other.Major {
return true
}
if s.Major == other.Major && s.Minor < other.Minor {
return true
}
return false
}
func (s Semver) String() string {
return fmt.Sprintf("v%d.%d", s.Major, s.Minor)
}
07070100000017000081A4000000000000000000000001680FB1F80000045A000000000000000000000000000000000000002800000000kube-score-1.20.0/config/semver_test.gopackage config
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestSemver(t *testing.T) {
tc := []struct {
input string
expected Semver
expectedErr error
}{
{"v1.0", Semver{1, 0}, nil},
{"v1.999", Semver{1, 999}, nil},
{"1.0", Semver{1, 0}, nil},
{"1.999", Semver{1, 999}, nil},
{"foo", Semver{}, errInvalidSemver},
{"v1.2.3", Semver{}, errInvalidSemver},
{"v1.foo", Semver{}, errInvalidSemver},
{"x1.0", Semver{}, errInvalidSemver},
{"v0x00.123", Semver{}, errInvalidSemver},
{"v1b.5nn3", Semver{}, errInvalidSemver},
}
for d, tc := range tc {
s, e := ParseSemver(tc.input)
assert.Equal(t, tc.expected, s, "Case: %d", d)
assert.Equal(t, tc.expectedErr, e, "Case: %d", d)
}
}
func TestSemverLessThan(t *testing.T) {
tc := []struct {
a Semver
b Semver
expected bool
}{
{Semver{1, 0}, Semver{1, 0}, false},
{Semver{1, 0}, Semver{1, 2}, true},
{Semver{1, 6}, Semver{2, 0}, true},
{Semver{2, 6}, Semver{1, 18}, false},
}
for d, tc := range tc {
assert.Equal(t, tc.expected, tc.a.LessThan(tc.b), "Case: %d", d)
}
}
07070100000018000041ED000000000000000000000002680FB1F800000000000000000000000000000000000000000000001900000000kube-score-1.20.0/domain07070100000019000081A4000000000000000000000001680FB1F800000AEB000000000000000000000000000000000000002700000000kube-score-1.20.0/domain/kube-score.gopackage domain
import (
"io"
autoscalingv1 "k8s.io/api/autoscaling/v1"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
networkingv1 "k8s.io/api/networking/v1"
policyv1 "k8s.io/api/policy/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
type Check struct {
Name string
ID string
TargetType string
Comment string
Optional bool
}
type NamedReader interface {
io.Reader
Name() string
}
type FileLocation struct {
Name string
Line int
}
type BothMeta struct {
TypeMeta metav1.TypeMeta
ObjectMeta metav1.ObjectMeta
FileLocationer
}
type PodSpecer interface {
FileLocationer
GetTypeMeta() metav1.TypeMeta
GetObjectMeta() metav1.ObjectMeta
GetPodTemplateSpec() corev1.PodTemplateSpec
}
type FileLocationer interface {
FileLocation() FileLocation
}
type HpaTargeter interface {
GetTypeMeta() metav1.TypeMeta
GetObjectMeta() metav1.ObjectMeta
MinReplicas() *int32
HpaTarget() autoscalingv1.CrossVersionObjectReference
FileLocationer
}
type Ingress interface {
GetTypeMeta() metav1.TypeMeta
GetObjectMeta() metav1.ObjectMeta
Rules() []networkingv1.IngressRule
FileLocationer
}
type Metas interface {
Metas() []BothMeta
}
type Pod interface {
Pod() corev1.Pod
FileLocationer
}
type Pods interface {
Pods() []Pod
}
type PodSpeccers interface {
PodSpeccers() []PodSpecer
}
type Service interface {
Service() corev1.Service
FileLocationer
}
type Services interface {
Services() []Service
}
type StatefulSet interface {
StatefulSet() appsv1.StatefulSet
FileLocationer
}
type StatefulSets interface {
StatefulSets() []StatefulSet
}
type Deployment interface {
Deployment() appsv1.Deployment
FileLocationer
}
type Deployments interface {
Deployments() []Deployment
}
type NetworkPolicy interface {
NetworkPolicy() networkingv1.NetworkPolicy
FileLocationer
}
type NetworkPolicies interface {
NetworkPolicies() []NetworkPolicy
}
type Ingresses interface {
Ingresses() []Ingress
}
type CronJob interface {
GetTypeMeta() metav1.TypeMeta
GetObjectMeta() metav1.ObjectMeta
StartingDeadlineSeconds() *int64
GetPodTemplateSpec() corev1.PodTemplateSpec
FileLocationer
}
type CronJobs interface {
CronJobs() []CronJob
}
type PodDisruptionBudget interface {
GetTypeMeta() metav1.TypeMeta
GetObjectMeta() metav1.ObjectMeta
Namespace() string
Spec() policyv1.PodDisruptionBudgetSpec
PodDisruptionBudgetSelector() *metav1.LabelSelector
FileLocationer
}
type PodDisruptionBudgets interface {
PodDisruptionBudgets() []PodDisruptionBudget
}
type HorizontalPodAutoscalers interface {
HorizontalPodAutoscalers() []HpaTargeter
}
type AllTypes interface {
Metas
Pods
PodSpeccers
Services
StatefulSets
Deployments
NetworkPolicies
Ingresses
CronJobs
PodDisruptionBudgets
HorizontalPodAutoscalers
}
0707010000001A000041ED000000000000000000000002680FB1F800000000000000000000000000000000000000000000001B00000000kube-score-1.20.0/examples0707010000001B000081A4000000000000000000000001680FB1F800000618000000000000000000000000000000000000002B00000000kube-score-1.20.0/examples/custom_check.gopackage examples
import (
"bytes"
"io"
"strings"
"github.com/zegl/kube-score/config"
"github.com/zegl/kube-score/domain"
"github.com/zegl/kube-score/parser"
"github.com/zegl/kube-score/score"
"github.com/zegl/kube-score/score/checks"
"github.com/zegl/kube-score/scorecard"
v1 "k8s.io/api/apps/v1"
)
type namedReader struct {
io.Reader
name string
}
func (n namedReader) Name() string {
return n.name
}
// ExampleCheckObject shows how kube-score can be extended with a custom check function
//
// In this example, raw is a YAML encoded Kubernetes object
func ExampleCheckObject(raw []byte) (*scorecard.Scorecard, error) {
parser, err := parser.New(nil)
if err != nil {
return nil, err
}
reader := bytes.NewReader(raw)
// Parse all objects to read
allObjects, err := parser.ParseFiles(
[]domain.NamedReader{
namedReader{
Reader: reader,
name: "input",
},
},
)
if err != nil {
return nil, err
}
// Register check functions to run
checks := checks.New(nil)
checks.RegisterDeploymentCheck("custom-deployment-check", "A custom kube-score check function", customDeploymentCheck)
return score.Score(allObjects, checks, &config.RunConfiguration{})
}
func customDeploymentCheck(d v1.Deployment) (scorecard.TestScore, error) {
if strings.Contains(d.Name, "foo") {
return scorecard.TestScore{
Grade: scorecard.GradeCritical,
Comments: []scorecard.TestScoreComment{{
Summary: "Deployments names can not contian 'foo'",
}}}, nil
}
return scorecard.TestScore{Grade: scorecard.GradeAllOK}, nil
}
0707010000001C000081A4000000000000000000000001680FB1F8000004F7000000000000000000000000000000000000003000000000kube-score-1.20.0/examples/custom_check_test.gopackage examples
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/zegl/kube-score/scorecard"
)
func TestExampleCheckObjectAllOK(t *testing.T) {
card, err := ExampleCheckObject([]byte(`
apiVersion: apps/v1
kind: Deployment
metadata:
name: example
spec:
replicas: 10
template:
metadata:
labels:
app: foo
spec:
containers:
- name: foobar
image: foo:bar`))
assert.NoError(t, err)
assert.Len(t, *card, 1)
for _, v := range *card {
assert.Len(t, v.Checks, 1)
assert.Equal(t, "custom-deployment-check", v.Checks[0].Check.ID)
assert.Equal(t, scorecard.GradeAllOK, v.Checks[0].Grade)
}
}
func TestExampleCheckObjectErrorNameContainsFoo(t *testing.T) {
card, err := ExampleCheckObject([]byte(`
apiVersion: apps/v1
kind: Deployment
metadata:
name: example-foo
spec:
replicas: 10
template:
metadata:
labels:
app: foo
spec:
containers:
- name: foobar
image: foo:bar`))
assert.NoError(t, err)
assert.Len(t, *card, 1)
for _, v := range *card {
assert.Len(t, v.Checks, 1)
assert.Equal(t, "custom-deployment-check", v.Checks[0].Check.ID)
assert.Equal(t, scorecard.GradeCritical, v.Checks[0].Grade)
}
}
0707010000001D000081A4000000000000000000000001680FB1F80000062C000000000000000000000000000000000000001900000000kube-score-1.20.0/go.modmodule github.com/zegl/kube-score
require (
github.com/buildkite/terminal-to-html v3.2.0+incompatible
github.com/eidolon/wordwrap v0.0.0-20161011182207-e0f54129b8bb
github.com/fatih/color v1.18.0
github.com/google/go-cmp v0.7.0
github.com/jstemmer/go-junit-report/v2 v2.1.0
github.com/mattn/go-isatty v0.0.20
github.com/spf13/pflag v1.0.6
github.com/stretchr/testify v1.10.0
golang.org/x/term v0.31.0
gopkg.in/yaml.v3 v3.0.1
k8s.io/api v0.32.3
k8s.io/apimachinery v0.32.3
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738
)
require (
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/x448/float16 v0.8.4 // indirect
golang.org/x/net v0.36.0 // indirect
golang.org/x/sys v0.32.0 // indirect
golang.org/x/text v0.22.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
k8s.io/klog/v2 v2.130.1 // indirect
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.4.2 // indirect
sigs.k8s.io/yaml v1.4.0 // indirect
)
go 1.23.0
toolchain go1.23.1
0707010000001E000081A4000000000000000000000001680FB1F80000298A000000000000000000000000000000000000001900000000kube-score-1.20.0/go.sumgithub.com/buildkite/terminal-to-html v3.2.0+incompatible h1:WdXzl7ZmYzCAz4pElZosPaUlRTW+qwVx/SkQSCa1jXs=
github.com/buildkite/terminal-to-html v3.2.0+incompatible/go.mod h1:BFFdFecOxCgjdcarqI+8izs6v85CU/1RA/4Bqh4GR7E=
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/eidolon/wordwrap v0.0.0-20161011182207-e0f54129b8bb h1:ioQwBmKdOCpMVS/bDaESqNWXIE/aw4+gsVtysCGMWZ4=
github.com/eidolon/wordwrap v0.0.0-20161011182207-e0f54129b8bb/go.mod h1:ZAPs+OyRzeVJFGvXVDVffgCzQfjg3qU9Ig8G/MU3zZ4=
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=
github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
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/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/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/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/jstemmer/go-junit-report/v2 v2.1.0 h1:X3+hPYlSczH9IMIpSC9CQSZA0L+BipYafciZUWHEmsc=
github.com/jstemmer/go-junit-report/v2 v2.1.0/go.mod h1:mgHVr7VUo5Tn8OLVr1cKnLuEy0M92wdRntM99h7RkgQ=
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/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/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.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
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/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/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/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/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/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=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.36.0 h1:vWF2fRbw4qslQsQzgFqZff+BItCvGFQqKzKIzx1rmoA=
golang.org/x/net v0.36.0/go.mod h1:bFmbeoIPfrw4sMHNhb4J9f6+tPziuGjq7Jk/38fxi1I=
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/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o=
golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw=
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.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
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/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=
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/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
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.32.3 h1:Hw7KqxRusq+6QSplE3NYG4MBxZw1BZnq4aP4cJVINls=
k8s.io/api v0.32.3/go.mod h1:2wEDTXADtm/HA7CCMD8D8bK4yuBUptzaRhYcYEEYA3k=
k8s.io/apimachinery v0.32.3 h1:JmDuDarhDmA/Li7j3aPrwhpNBA94Nvk5zLeOge9HH1U=
k8s.io/apimachinery v0.32.3/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE=
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 h1:M3sRQVHv7vB20Xc2ybTt7ODCeFj6JSWYFzOFnYeS6Ro=
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 h1:/Rv+M11QRah1itp8VhT6HoVx1Ray9eB4DBr+K+/sCJ8=
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3/go.mod h1:18nIHnGi6636UCz6m8i4DhaJ65T6EruyzmoQqI2BVDo=
sigs.k8s.io/structured-merge-diff/v4 v4.4.2 h1:MdmvkGuXi/8io6ixD5wud3vOLwc1rj0aNqRlpuvjmwA=
sigs.k8s.io/structured-merge-diff/v4 v4.4.2/go.mod h1:N8f93tFZh9U6vpxwRArLiikrE5/2tiu1w1AGfACIGE4=
sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=
0707010000001F000041ED000000000000000000000002680FB1F800000000000000000000000000000000000000000000001700000000kube-score-1.20.0/hack07070100000020000081A4000000000000000000000001680FB1F8000000F4000000000000000000000000000000000000002200000000kube-score-1.20.0/hack/RELEASE.md# How to make a release
1. Build a release `GITHUB_TOKEN=ghp_XXXX goreleaser`
2. Build and push to Docker Hub: `PUSH_LATEST=1 ./hack/release-docker.sh`
3. Update the krew index: `./hack/krew-index.sh`
4. Update the relnotes
5. Update homebrew
07070100000021000081A4000000000000000000000001680FB1F80000016C000000000000000000000000000000000000002D00000000kube-score-1.20.0/hack/generate-list-docs.pyimport subprocess
import csv
from io import StringIO
print("<!-- This file was generated by hack/generate-list-docs.py -->")
print("| ID | Target | Description | Enabled |")
print("|----|--------|-------------|---------|")
listAsCSV = subprocess.getoutput("kube-score list")
for row in csv.reader(StringIO(listAsCSV)):
print("| " + " | ".join(row) + " |")
07070100000022000081ED000000000000000000000001680FB1F8000005D3000000000000000000000000000000000000002500000000kube-score-1.20.0/hack/krew-index.sh#!/bin/bash
set -euo pipefail
set -x
VERSION=$(git describe --tags --abbrev=0 | cut -c2-)
KREW_INDEX_PATH="$(mktemp -d)"
FILE="${KREW_INDEX_PATH}/plugins/score.yaml"
KUBE_SCORE_REPO_ROOT="$( cd "$( dirname "${BASH_SOURCE[0]}" )/.." >/dev/null 2>&1 && pwd )"
checksum() {
grep -E "kube-score_${VERSION}_${1}_amd64.tar.gz" ${KUBE_SCORE_REPO_ROOT}/dist/checksums.txt | awk '{print $1}'
}
gg() {
git -C "$KREW_INDEX_PATH" "$@"
}
git clone git@github.com:kubernetes-sigs/krew-index.git "$KREW_INDEX_PATH"
git -C "$KREW_INDEX_PATH" remote add zegl git@github.com:zegl/krew-index.git
gg checkout master
gg fetch origin
gg reset --hard origin/master
gg branch -D "kube-score-${VERSION}" || true
gg checkout -b "kube-score-${VERSION}"
yq --inplace ".spec.version = \"v${VERSION}\"" "${FILE}"
yq --inplace ".spec.platforms[0].uri = \"https://github.com/zegl/kube-score/releases/download/v${VERSION}/kube-score_${VERSION}_darwin_amd64.tar.gz\"" "$FILE"
yq --inplace ".spec.platforms[0].sha256 = \"$(checksum darwin)\"" "$FILE"
yq --inplace ".spec.platforms[1].uri = \"https://github.com/zegl/kube-score/releases/download/v${VERSION}/kube-score_${VERSION}_linux_amd64.tar.gz\"" "$FILE"
yq --inplace ".spec.platforms[1].sha256 = \"$(checksum linux)\"" "$FILE"
gg add plugins/score.yaml
gg commit -m "Update score to v${VERSION}"
gg push --force -u zegl "kube-score-${VERSION}"
open "https://github.com/kubernetes-sigs/krew-index/compare/master...zegl:kube-score-${VERSION}?expand=1"
07070100000023000081ED000000000000000000000001680FB1F800000382000000000000000000000000000000000000002900000000kube-score-1.20.0/hack/release-docker.sh#!/bin/bash
set -euo pipefail
function echoinfo() {
LIGHT_GREEN='\033[1;32m'
NC='\033[0m' # No Color
printf "${LIGHT_GREEN}%s${NC}\n" "$1"
}
VERSION="$(git describe --tags --abbrev=0)"
TAG_ARGS="-t zegl/kube-score:${VERSION}"
PLATFORM_ARGS=""
PUSH_OR_LOAD_ARG="--load"
if [ -z ${PUSH_LATEST+x} ]; then
echoinfo "[x] Dry run. (Set PUSH_LATEST if you want to push and tag latest)"
else
echoinfo "[x] Making production build. Will push to Docker Hub!"
TAG_ARGS="${TAG_ARGS} -t zegl/kube-score:latest"
PLATFORM_ARGS="--platform linux/arm64 --platform linux/amd64"
PUSH_OR_LOAD_ARG="--push"
fi
docker buildx build \
--build-arg KUBE_SCORE_VERSION=${VERSION} \
--build-arg "KUBE_SCORE_COMMIT=$(git rev-parse HEAD)" \
--build-arg "KUBE_SCORE_DATE=$(date -Iseconds)" \
${PLATFORM_ARGS} \
${TAG_ARGS} \
${PUSH_OR_LOAD_ARG} \
--target runner \
.
07070100000024000081ED000000000000000000000001680FB1F8000005DC000000000000000000000000000000000000002300000000kube-score-1.20.0/hack/relnotes.sh#!/usr/bin/env bash
set -euo pipefail
# Dependencies: rg (ripgrep), jq
if [ "$#" -ne 1 ]; then
echo "Unexpected number of arguments"
echo
echo "Usage:"
echo " $0 previous-release-tag"
exit 1;
fi
PREV_RELEASE=$1
CURRENT_TAG=$(git tag -l --points-at HEAD);
if [ "$(echo "$CURRENT_TAG" | wc -l)" -ne 1 ]; then
echo "Could not detect version. HEAD should have exactly one tag.";
exit 1;
fi
#
# Generate list of changes based on RELNOTES in commits
#
echo "# Changes";
RELNOTE_MERGES=$(git log ${PREV_RELEASE}...${CURRENT_TAG} --grep RELNOTE --oneline --merges)
while read -r line; do
if [ -z "$line" ]; then
continue;
fi
COMMIT=$(echo "$line" | awk '{print $1}')
git show "$COMMIT" | rg -o '^\s+([0-9]+):(.*?)\s+RELNOTE:(.*?)\s+```' --multiline-dotall --multiline --replace "* #\$1 \$3" || true;
done <<< "$RELNOTE_MERGES"
#
# Authors secrion
#
echo
echo -n "This release contains contributions from: "
git log ${PREV_RELEASE}...HEAD | rg -o "Co-authored-by: (.*?) <" --replace "\$1" | sort | uniq | grep -v "\[bot\]" | awk 'ORS=", "' | sed 's/, $//'
#
# Download instructions
#
echo
echo "# Download"
echo "* Download the binaries from the GitHub release page"
echo "* Download the image from Docker Hub: \`zegl/kube-score:${CURRENT_TAG}\` (Built for arm64 and amd64. Includes both Helm3 and Kustomize.)"
echo "* Download from homebrew: \`brew install kube-score/tap/kube-score\`"
echo "* Download with krew: \`kubectl krew install score\`"
07070100000025000081A4000000000000000000000001680FB1F8000000D0000000000000000000000000000000000000001F00000000kube-score-1.20.0/package.json{
"name": "kube-score",
"private": true,
"version": "0.0.0",
"type": "module",
"packageManager": "pnpm@9.5.0+sha256.dbdf5961c32909fb030595a9daa1dae720162e658609a8f92f2fa99835510ca5"
}
07070100000026000041ED000000000000000000000002680FB1F800000000000000000000000000000000000000000000001900000000kube-score-1.20.0/parser07070100000027000081A4000000000000000000000001680FB1F80000015E000000000000000000000000000000000000002200000000kube-score-1.20.0/parser/error.gopackage parser
import (
"strings"
)
type parseErrors []error
func (p parseErrors) Error() string {
var s []string
for _, e := range p {
s = append(s, e.Error())
}
return strings.Join(s, "\n")
}
func (p *parseErrors) AddIfErr(err error) {
if err != nil {
*p = append(*p, err)
}
}
func (p parseErrors) Any() bool {
return len(p) > 0
}
07070100000028000041ED000000000000000000000002680FB1F800000000000000000000000000000000000000000000002200000000kube-score-1.20.0/parser/internal07070100000029000041ED000000000000000000000002680FB1F800000000000000000000000000000000000000000000002A00000000kube-score-1.20.0/parser/internal/cronjob0707010000002A000081A4000000000000000000000001680FB1F8000002F0000000000000000000000000000000000000003000000000kube-score-1.20.0/parser/internal/cronjob/v1.gopackage cronjob
import (
ks "github.com/zegl/kube-score/domain"
v1 "k8s.io/api/batch/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
type CronJobV1 struct {
Obj v1.CronJob
Location ks.FileLocation
}
func (c CronJobV1) StartingDeadlineSeconds() *int64 {
return c.Obj.Spec.StartingDeadlineSeconds
}
func (c CronJobV1) FileLocation() ks.FileLocation {
return c.Location
}
func (c CronJobV1) GetTypeMeta() metav1.TypeMeta {
return c.Obj.TypeMeta
}
func (c CronJobV1) GetObjectMeta() metav1.ObjectMeta {
return c.Obj.ObjectMeta
}
func (c CronJobV1) GetPodTemplateSpec() corev1.PodTemplateSpec {
t := c.Obj.Spec.JobTemplate.Spec.Template
t.ObjectMeta.Namespace = c.Obj.ObjectMeta.Namespace
return t
}
0707010000002B000081A4000000000000000000000001680FB1F800000315000000000000000000000000000000000000003500000000kube-score-1.20.0/parser/internal/cronjob/v1beta1.gopackage cronjob
import (
ks "github.com/zegl/kube-score/domain"
"k8s.io/api/batch/v1beta1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
type CronJobV1beta1 struct {
Obj v1beta1.CronJob
Location ks.FileLocation
}
func (c CronJobV1beta1) StartingDeadlineSeconds() *int64 {
return c.Obj.Spec.StartingDeadlineSeconds
}
func (c CronJobV1beta1) FileLocation() ks.FileLocation {
return c.Location
}
func (c CronJobV1beta1) GetTypeMeta() metav1.TypeMeta {
return c.Obj.TypeMeta
}
func (c CronJobV1beta1) GetObjectMeta() metav1.ObjectMeta {
return c.Obj.ObjectMeta
}
func (c CronJobV1beta1) GetPodTemplateSpec() corev1.PodTemplateSpec {
t := c.Obj.Spec.JobTemplate.Spec.Template
t.ObjectMeta.Namespace = c.Obj.ObjectMeta.Namespace
return t
}
0707010000002C000081A4000000000000000000000001680FB1F80000070F000000000000000000000000000000000000002F00000000kube-score-1.20.0/parser/internal/deamonset.gopackage internal
import (
appsv1 "k8s.io/api/apps/v1"
appsv1beta2 "k8s.io/api/apps/v1beta2"
corev1 "k8s.io/api/core/v1"
extensionsv1beta1 "k8s.io/api/extensions/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
ks "github.com/zegl/kube-score/domain"
)
type Appsv1DaemonSet struct {
appsv1.DaemonSet
Location ks.FileLocation
}
func (d Appsv1DaemonSet) FileLocation() ks.FileLocation {
return d.Location
}
func (d Appsv1DaemonSet) GetTypeMeta() metav1.TypeMeta {
return d.TypeMeta
}
func (d Appsv1DaemonSet) GetObjectMeta() metav1.ObjectMeta {
return d.ObjectMeta
}
func (d Appsv1DaemonSet) GetPodTemplateSpec() corev1.PodTemplateSpec {
d.Spec.Template.ObjectMeta.Namespace = d.ObjectMeta.Namespace
return d.Spec.Template
}
type Appsv1beta2DaemonSet struct {
appsv1beta2.DaemonSet
Location ks.FileLocation
}
func (d Appsv1beta2DaemonSet) FileLocation() ks.FileLocation {
return d.Location
}
func (d Appsv1beta2DaemonSet) GetTypeMeta() metav1.TypeMeta {
return d.TypeMeta
}
func (d Appsv1beta2DaemonSet) GetObjectMeta() metav1.ObjectMeta {
return d.ObjectMeta
}
func (d Appsv1beta2DaemonSet) GetPodTemplateSpec() corev1.PodTemplateSpec {
d.Spec.Template.ObjectMeta.Namespace = d.ObjectMeta.Namespace
return d.Spec.Template
}
type Extensionsv1beta1DaemonSet struct {
extensionsv1beta1.DaemonSet
Location ks.FileLocation
}
func (d Extensionsv1beta1DaemonSet) FileLocation() ks.FileLocation {
return d.Location
}
func (d Extensionsv1beta1DaemonSet) GetTypeMeta() metav1.TypeMeta {
return d.TypeMeta
}
func (d Extensionsv1beta1DaemonSet) GetObjectMeta() metav1.ObjectMeta {
return d.ObjectMeta
}
func (d Extensionsv1beta1DaemonSet) GetPodTemplateSpec() corev1.PodTemplateSpec {
d.Spec.Template.ObjectMeta.Namespace = d.ObjectMeta.Namespace
return d.Spec.Template
}
0707010000002D000081A4000000000000000000000001680FB1F8000009B7000000000000000000000000000000000000003000000000kube-score-1.20.0/parser/internal/deployment.gopackage internal
import (
appsv1 "k8s.io/api/apps/v1"
appsv1beta1 "k8s.io/api/apps/v1beta1"
appsv1beta2 "k8s.io/api/apps/v1beta2"
corev1 "k8s.io/api/core/v1"
extensionsv1beta1 "k8s.io/api/extensions/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
ks "github.com/zegl/kube-score/domain"
)
type Appsv1Deployment struct {
Obj appsv1.Deployment
Location ks.FileLocation
}
func (d Appsv1Deployment) FileLocation() ks.FileLocation {
return d.Location
}
func (d Appsv1Deployment) GetTypeMeta() metav1.TypeMeta {
return d.Obj.TypeMeta
}
func (d Appsv1Deployment) GetObjectMeta() metav1.ObjectMeta {
return d.Obj.ObjectMeta
}
func (d Appsv1Deployment) GetPodTemplateSpec() corev1.PodTemplateSpec {
d.Obj.Spec.Template.ObjectMeta.Namespace = d.Obj.ObjectMeta.Namespace
return d.Obj.Spec.Template
}
func (d Appsv1Deployment) Deployment() appsv1.Deployment {
return d.Obj
}
type Appsv1beta1Deployment struct {
appsv1beta1.Deployment
Location ks.FileLocation
}
func (d Appsv1beta1Deployment) FileLocation() ks.FileLocation {
return d.Location
}
func (d Appsv1beta1Deployment) GetTypeMeta() metav1.TypeMeta {
return d.TypeMeta
}
func (d Appsv1beta1Deployment) GetObjectMeta() metav1.ObjectMeta {
return d.ObjectMeta
}
func (d Appsv1beta1Deployment) GetPodTemplateSpec() corev1.PodTemplateSpec {
d.Spec.Template.ObjectMeta.Namespace = d.ObjectMeta.Namespace
return d.Spec.Template
}
type Appsv1beta2Deployment struct {
appsv1beta2.Deployment
Location ks.FileLocation
}
func (d Appsv1beta2Deployment) FileLocation() ks.FileLocation {
return d.Location
}
func (d Appsv1beta2Deployment) GetTypeMeta() metav1.TypeMeta {
return d.TypeMeta
}
func (d Appsv1beta2Deployment) GetObjectMeta() metav1.ObjectMeta {
return d.ObjectMeta
}
func (d Appsv1beta2Deployment) GetPodTemplateSpec() corev1.PodTemplateSpec {
d.Spec.Template.ObjectMeta.Namespace = d.ObjectMeta.Namespace
return d.Spec.Template
}
type Extensionsv1beta1Deployment struct {
extensionsv1beta1.Deployment
Location ks.FileLocation
}
func (d Extensionsv1beta1Deployment) FileLocation() ks.FileLocation {
return d.Location
}
func (d Extensionsv1beta1Deployment) GetTypeMeta() metav1.TypeMeta {
return d.TypeMeta
}
func (d Extensionsv1beta1Deployment) GetObjectMeta() metav1.ObjectMeta {
return d.ObjectMeta
}
func (d Extensionsv1beta1Deployment) GetPodTemplateSpec() corev1.PodTemplateSpec {
d.Spec.Template.ObjectMeta.Namespace = d.ObjectMeta.Namespace
return d.Spec.Template
}
0707010000002E000081A4000000000000000000000001680FB1F800000963000000000000000000000000000000000000002900000000kube-score-1.20.0/parser/internal/hpa.gopackage internal
import (
autoscalingv1 "k8s.io/api/autoscaling/v1"
autoscalingv2 "k8s.io/api/autoscaling/v2"
autoscalingv2beta1 "k8s.io/api/autoscaling/v2beta1"
autoscalingv2beta2 "k8s.io/api/autoscaling/v2beta2"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
ks "github.com/zegl/kube-score/domain"
)
type HPAv1 struct {
autoscalingv1.HorizontalPodAutoscaler
Location ks.FileLocation
}
func (d HPAv1) FileLocation() ks.FileLocation {
return d.Location
}
func (d HPAv1) GetTypeMeta() metav1.TypeMeta {
return d.TypeMeta
}
func (d HPAv1) GetObjectMeta() metav1.ObjectMeta {
return d.ObjectMeta
}
func (d HPAv1) MinReplicas() *int32 {
return d.Spec.MinReplicas
}
func (d HPAv1) HpaTarget() autoscalingv1.CrossVersionObjectReference {
return d.Spec.ScaleTargetRef
}
type HPAv2beta1 struct {
autoscalingv2beta1.HorizontalPodAutoscaler
Location ks.FileLocation
}
func (d HPAv2beta1) FileLocation() ks.FileLocation {
return d.Location
}
func (d HPAv2beta1) GetTypeMeta() metav1.TypeMeta {
return d.TypeMeta
}
func (d HPAv2beta1) GetObjectMeta() metav1.ObjectMeta {
return d.ObjectMeta
}
func (d HPAv2beta1) MinReplicas() *int32 {
return d.Spec.MinReplicas
}
func (d HPAv2beta1) HpaTarget() autoscalingv1.CrossVersionObjectReference {
return autoscalingv1.CrossVersionObjectReference(d.Spec.ScaleTargetRef)
}
type HPAv2beta2 struct {
autoscalingv2beta2.HorizontalPodAutoscaler
Location ks.FileLocation
}
func (d HPAv2beta2) FileLocation() ks.FileLocation {
return d.Location
}
func (d HPAv2beta2) GetTypeMeta() metav1.TypeMeta {
return d.TypeMeta
}
func (d HPAv2beta2) GetObjectMeta() metav1.ObjectMeta {
return d.ObjectMeta
}
func (d HPAv2beta2) MinReplicas() *int32 {
return d.Spec.MinReplicas
}
func (d HPAv2beta2) HpaTarget() autoscalingv1.CrossVersionObjectReference {
return autoscalingv1.CrossVersionObjectReference(d.Spec.ScaleTargetRef)
}
type HPAv2 struct {
autoscalingv2.HorizontalPodAutoscaler
Location ks.FileLocation
}
func (d HPAv2) FileLocation() ks.FileLocation {
return d.Location
}
func (d HPAv2) GetTypeMeta() metav1.TypeMeta {
return d.TypeMeta
}
func (d HPAv2) GetObjectMeta() metav1.ObjectMeta {
return d.ObjectMeta
}
func (d HPAv2) MinReplicas() *int32 {
return d.Spec.MinReplicas
}
func (d HPAv2) HpaTarget() autoscalingv1.CrossVersionObjectReference {
return autoscalingv1.CrossVersionObjectReference(d.Spec.ScaleTargetRef)
}
0707010000002F000041ED000000000000000000000002680FB1F800000000000000000000000000000000000000000000002A00000000kube-score-1.20.0/parser/internal/ingress07070100000030000081A4000000000000000000000001680FB1F800000D41000000000000000000000000000000000000002D00000000kube-score-1.20.0/parser/internal/ingress.gopackage internal
import (
extensionsv1beta1 "k8s.io/api/extensions/v1beta1"
networkingv1 "k8s.io/api/networking/v1"
networkingv1beta1 "k8s.io/api/networking/v1beta1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
ks "github.com/zegl/kube-score/domain"
)
var _ ks.Ingress = (*IngressV1)(nil)
var _ ks.Ingress = (*IngressV1beta1)(nil)
var _ ks.Ingress = (*ExtensionsIngressV1beta1)(nil)
type IngressV1 struct {
networkingv1.Ingress
Location ks.FileLocation
}
func (i IngressV1) FileLocation() ks.FileLocation {
return i.Location
}
func (i IngressV1) GetObjectMeta() v1.ObjectMeta {
return i.ObjectMeta
}
func (i IngressV1) GetTypeMeta() v1.TypeMeta {
return i.TypeMeta
}
func (i IngressV1) Rules() []networkingv1.IngressRule {
return i.Spec.Rules
}
type IngressV1beta1 struct {
networkingv1beta1.Ingress
Location ks.FileLocation
}
func (i IngressV1beta1) FileLocation() ks.FileLocation {
return i.Location
}
func (i IngressV1beta1) GetObjectMeta() v1.ObjectMeta {
return i.ObjectMeta
}
func (i IngressV1beta1) GetTypeMeta() v1.TypeMeta {
return i.TypeMeta
}
func (i IngressV1beta1) Rules() []networkingv1.IngressRule {
var res []networkingv1.IngressRule
paths := func(in []networkingv1beta1.HTTPIngressPath) (out []networkingv1.HTTPIngressPath) {
for _, path := range in {
out = append(out, networkingv1.HTTPIngressPath{
Path: path.Path,
Backend: networkingv1.IngressBackend{
Service: &networkingv1.IngressServiceBackend{
Name: path.Backend.ServiceName,
Port: networkingv1.ServiceBackendPort{
Name: path.Backend.ServicePort.StrVal,
Number: path.Backend.ServicePort.IntVal,
},
},
},
})
}
return
}
for _, rule := range i.Spec.Rules {
var http *networkingv1.HTTPIngressRuleValue
if rule.HTTP != nil {
http = &networkingv1.HTTPIngressRuleValue{
Paths: paths(rule.HTTP.Paths),
}
}
res = append(res, networkingv1.IngressRule{
Host: rule.Host,
IngressRuleValue: networkingv1.IngressRuleValue{
HTTP: http,
},
})
}
return res
}
type ExtensionsIngressV1beta1 struct {
extensionsv1beta1.Ingress
Location ks.FileLocation
}
func (i ExtensionsIngressV1beta1) GetObjectMeta() v1.ObjectMeta {
return i.ObjectMeta
}
func (i ExtensionsIngressV1beta1) GetTypeMeta() v1.TypeMeta {
return i.TypeMeta
}
func (i ExtensionsIngressV1beta1) Rules() []networkingv1.IngressRule {
var res []networkingv1.IngressRule
paths := func(in []extensionsv1beta1.HTTPIngressPath) (out []networkingv1.HTTPIngressPath) {
for _, path := range in {
out = append(out, networkingv1.HTTPIngressPath{
Path: path.Path,
Backend: networkingv1.IngressBackend{
Service: &networkingv1.IngressServiceBackend{
Name: path.Backend.ServiceName,
Port: networkingv1.ServiceBackendPort{
Name: path.Backend.ServicePort.StrVal,
Number: path.Backend.ServicePort.IntVal,
},
},
},
})
}
return
}
for _, rule := range i.Spec.Rules {
var http *networkingv1.HTTPIngressRuleValue
if rule.HTTP != nil {
http = &networkingv1.HTTPIngressRuleValue{
Paths: paths(rule.HTTP.Paths),
}
}
res = append(res, networkingv1.IngressRule{
Host: rule.Host,
IngressRuleValue: networkingv1.IngressRuleValue{
HTTP: http,
},
})
}
return res
}
func (i ExtensionsIngressV1beta1) FileLocation() ks.FileLocation {
return i.Location
}
07070100000031000081A4000000000000000000000001680FB1F800000139000000000000000000000000000000000000003500000000kube-score-1.20.0/parser/internal/ingress/ingress.gopackage ingress
import (
"k8s.io/api/extensions/v1beta1"
ks "github.com/zegl/kube-score/domain"
)
type Ingress struct {
Obj v1beta1.Ingress
Location ks.FileLocation
}
func (i Ingress) Ingress() v1beta1.Ingress {
return i.Obj
}
func (i Ingress) FileLocation() ks.FileLocation {
return i.Location
}
07070100000032000081A4000000000000000000000001680FB1F800000275000000000000000000000000000000000000002A00000000kube-score-1.20.0/parser/internal/jobs.gopackage internal
import (
batchv1 "k8s.io/api/batch/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
ks "github.com/zegl/kube-score/domain"
)
type Batchv1Job struct {
batchv1.Job
Location ks.FileLocation
}
func (d Batchv1Job) FileLocation() ks.FileLocation {
return d.Location
}
func (d Batchv1Job) GetTypeMeta() metav1.TypeMeta {
return d.TypeMeta
}
func (d Batchv1Job) GetObjectMeta() metav1.ObjectMeta {
return d.ObjectMeta
}
func (d Batchv1Job) GetPodTemplateSpec() corev1.PodTemplateSpec {
d.Spec.Template.ObjectMeta.Namespace = d.ObjectMeta.Namespace
return d.Spec.Template
}
07070100000033000041ED000000000000000000000002680FB1F800000000000000000000000000000000000000000000003000000000kube-score-1.20.0/parser/internal/networkpolicy07070100000034000081A4000000000000000000000001680FB1F80000016B000000000000000000000000000000000000003700000000kube-score-1.20.0/parser/internal/networkpolicy/pod.gopackage pod
import (
networkingv1 "k8s.io/api/networking/v1"
ks "github.com/zegl/kube-score/domain"
)
type NetworkPolicy struct {
Obj networkingv1.NetworkPolicy
Location ks.FileLocation
}
func (p NetworkPolicy) NetworkPolicy() networkingv1.NetworkPolicy {
return p.Obj
}
func (p NetworkPolicy) FileLocation() ks.FileLocation {
return p.Location
}
07070100000035000041ED000000000000000000000002680FB1F800000000000000000000000000000000000000000000002600000000kube-score-1.20.0/parser/internal/pdb07070100000036000081A4000000000000000000000001680FB1F8000007CB000000000000000000000000000000000000002D00000000kube-score-1.20.0/parser/internal/pdb/pod.gopackage pod
import (
policyv1 "k8s.io/api/policy/v1"
policyv1beta1 "k8s.io/api/policy/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
ks "github.com/zegl/kube-score/domain"
)
type PodDisruptionBudgetV1beta1 struct {
Obj policyv1beta1.PodDisruptionBudget
Location ks.FileLocation
}
func (p PodDisruptionBudgetV1beta1) GetObjectMeta() metav1.ObjectMeta {
return p.Obj.ObjectMeta
}
func (p PodDisruptionBudgetV1beta1) GetTypeMeta() metav1.TypeMeta {
return p.Obj.TypeMeta
}
func (p PodDisruptionBudgetV1beta1) PodDisruptionBudgetSelector() *metav1.LabelSelector {
return p.Obj.Spec.Selector
}
func (p PodDisruptionBudgetV1beta1) Namespace() string {
return p.Obj.Namespace
}
func (p PodDisruptionBudgetV1beta1) FileLocation() ks.FileLocation {
return p.Location
}
func (p PodDisruptionBudgetV1beta1) Spec() policyv1.PodDisruptionBudgetSpec {
var polType *policyv1.UnhealthyPodEvictionPolicyType
if p.Obj.Spec.UnhealthyPodEvictionPolicy != nil {
t := policyv1.UnhealthyPodEvictionPolicyType(*p.Obj.Spec.UnhealthyPodEvictionPolicy)
polType = &t
}
return policyv1.PodDisruptionBudgetSpec{
MinAvailable: p.Obj.Spec.MinAvailable,
Selector: p.Obj.Spec.Selector,
MaxUnavailable: p.Obj.Spec.MaxUnavailable,
UnhealthyPodEvictionPolicy: polType,
}
}
type PodDisruptionBudgetV1 struct {
Obj policyv1.PodDisruptionBudget
Location ks.FileLocation
}
func (p PodDisruptionBudgetV1) GetObjectMeta() metav1.ObjectMeta {
return p.Obj.ObjectMeta
}
func (p PodDisruptionBudgetV1) GetTypeMeta() metav1.TypeMeta {
return p.Obj.TypeMeta
}
func (p PodDisruptionBudgetV1) PodDisruptionBudgetSelector() *metav1.LabelSelector {
return p.Obj.Spec.Selector
}
func (p PodDisruptionBudgetV1) FileLocation() ks.FileLocation {
return p.Location
}
func (p PodDisruptionBudgetV1) Namespace() string {
return p.Obj.Namespace
}
func (p PodDisruptionBudgetV1) Spec() policyv1.PodDisruptionBudgetSpec {
return p.Obj.Spec
}
07070100000037000041ED000000000000000000000002680FB1F800000000000000000000000000000000000000000000002600000000kube-score-1.20.0/parser/internal/pod07070100000038000081A4000000000000000000000001680FB1F800000117000000000000000000000000000000000000002D00000000kube-score-1.20.0/parser/internal/pod/pod.gopackage pod
import (
corev1 "k8s.io/api/core/v1"
ks "github.com/zegl/kube-score/domain"
)
type Pod struct {
Obj corev1.Pod
Location ks.FileLocation
}
func (p Pod) Pod() corev1.Pod {
return p.Obj
}
func (p Pod) FileLocation() ks.FileLocation {
return p.Location
}
07070100000039000041ED000000000000000000000002680FB1F800000000000000000000000000000000000000000000002A00000000kube-score-1.20.0/parser/internal/service0707010000003A000081A4000000000000000000000001680FB1F800000123000000000000000000000000000000000000003100000000kube-score-1.20.0/parser/internal/service/pod.gopackage pod
import (
v1 "k8s.io/api/core/v1"
ks "github.com/zegl/kube-score/domain"
)
type Service struct {
Obj v1.Service
Location ks.FileLocation
}
func (p Service) Service() v1.Service {
return p.Obj
}
func (p Service) FileLocation() ks.FileLocation {
return p.Location
}
0707010000003B000081A4000000000000000000000001680FB1F80000076F000000000000000000000000000000000000003100000000kube-score-1.20.0/parser/internal/statefulset.gopackage internal
import (
appsv1 "k8s.io/api/apps/v1"
appsv1beta1 "k8s.io/api/apps/v1beta1"
appsv1beta2 "k8s.io/api/apps/v1beta2"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
ks "github.com/zegl/kube-score/domain"
)
type Appsv1StatefulSet struct {
Obj appsv1.StatefulSet
Location ks.FileLocation
}
func (d Appsv1StatefulSet) FileLocation() ks.FileLocation {
return d.Location
}
func (s Appsv1StatefulSet) GetTypeMeta() metav1.TypeMeta {
return s.Obj.TypeMeta
}
func (s Appsv1StatefulSet) GetObjectMeta() metav1.ObjectMeta {
return s.Obj.ObjectMeta
}
func (s Appsv1StatefulSet) GetPodTemplateSpec() corev1.PodTemplateSpec {
s.Obj.Spec.Template.ObjectMeta.Namespace = s.Obj.ObjectMeta.Namespace
return s.Obj.Spec.Template
}
func (s Appsv1StatefulSet) StatefulSet() appsv1.StatefulSet {
return s.Obj
}
type Appsv1beta1StatefulSet struct {
appsv1beta1.StatefulSet
Location ks.FileLocation
}
func (d Appsv1beta1StatefulSet) FileLocation() ks.FileLocation {
return d.Location
}
func (s Appsv1beta1StatefulSet) GetTypeMeta() metav1.TypeMeta {
return s.TypeMeta
}
func (s Appsv1beta1StatefulSet) GetObjectMeta() metav1.ObjectMeta {
return s.ObjectMeta
}
func (s Appsv1beta1StatefulSet) GetPodTemplateSpec() corev1.PodTemplateSpec {
s.Spec.Template.ObjectMeta.Namespace = s.ObjectMeta.Namespace
return s.Spec.Template
}
type Appsv1beta2StatefulSet struct {
appsv1beta2.StatefulSet
Location ks.FileLocation
}
func (d Appsv1beta2StatefulSet) FileLocation() ks.FileLocation {
return d.Location
}
func (s Appsv1beta2StatefulSet) GetTypeMeta() metav1.TypeMeta {
return s.TypeMeta
}
func (s Appsv1beta2StatefulSet) GetObjectMeta() metav1.ObjectMeta {
return s.ObjectMeta
}
func (s Appsv1beta2StatefulSet) GetPodTemplateSpec() corev1.PodTemplateSpec {
s.Spec.Template.ObjectMeta.Namespace = s.ObjectMeta.Namespace
return s.Spec.Template
}
0707010000003C000081A4000000000000000000000001680FB1F800003C71000000000000000000000000000000000000002200000000kube-score-1.20.0/parser/parse.gopackage parser
import (
"bytes"
"fmt"
"io"
"log"
"strings"
"gopkg.in/yaml.v3"
appsv1 "k8s.io/api/apps/v1"
appsv1beta1 "k8s.io/api/apps/v1beta1"
appsv1beta2 "k8s.io/api/apps/v1beta2"
autoscalingv1 "k8s.io/api/autoscaling/v1"
autoscalingv2 "k8s.io/api/autoscaling/v2"
autoscalingv2beta1 "k8s.io/api/autoscaling/v2beta1"
autoscalingv2beta2 "k8s.io/api/autoscaling/v2beta2"
batchv1 "k8s.io/api/batch/v1"
batchv1beta1 "k8s.io/api/batch/v1beta1"
corev1 "k8s.io/api/core/v1"
extensionsv1beta1 "k8s.io/api/extensions/v1beta1"
networkingv1 "k8s.io/api/networking/v1"
networkingv1beta1 "k8s.io/api/networking/v1beta1"
policyv1 "k8s.io/api/policy/v1"
policyv1beta1 "k8s.io/api/policy/v1beta1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/runtime/serializer"
ks "github.com/zegl/kube-score/domain"
"github.com/zegl/kube-score/parser/internal"
internalcronjob "github.com/zegl/kube-score/parser/internal/cronjob"
internalnetpol "github.com/zegl/kube-score/parser/internal/networkpolicy"
internalpdb "github.com/zegl/kube-score/parser/internal/pdb"
internalpod "github.com/zegl/kube-score/parser/internal/pod"
internalservice "github.com/zegl/kube-score/parser/internal/service"
)
type Parser struct {
scheme *runtime.Scheme
codecs serializer.CodecFactory
config *Config
}
type Config struct {
VerboseOutput int
}
type schemaAdderFunc func(scheme *runtime.Scheme) error
func New(config *Config) (*Parser, error) {
if config == nil {
config = &Config{}
}
scheme := runtime.NewScheme()
p := &Parser{
scheme: scheme,
codecs: serializer.NewCodecFactory(scheme),
config: config,
}
if err := p.addToScheme(); err != nil {
return nil, fmt.Errorf("failed to init: %w", err)
}
return p, nil
}
func (p *Parser) addToScheme() error {
var adders = []schemaAdderFunc{
corev1.AddToScheme,
appsv1.AddToScheme,
networkingv1.AddToScheme,
extensionsv1beta1.AddToScheme,
appsv1beta1.AddToScheme,
appsv1beta2.AddToScheme,
batchv1.AddToScheme,
batchv1beta1.AddToScheme,
policyv1beta1.AddToScheme,
policyv1.AddToScheme,
}
for _, adder := range adders {
if err := adder(p.scheme); err != nil {
return fmt.Errorf("failed to add type to schema: %w", err)
}
}
return nil
}
type detectKind struct {
ApiVersion string `yaml:"apiVersion"`
Kind string `yaml:"kind"`
}
type parsedObjects struct {
bothMetas []ks.BothMeta
pods []ks.Pod
podspecers []ks.PodSpecer
networkPolicies []ks.NetworkPolicy
services []ks.Service
podDisruptionBudgets []ks.PodDisruptionBudget
deployments []ks.Deployment
statefulsets []ks.StatefulSet
ingresses []ks.Ingress // supports multiple versions of ingress
cronjobs []ks.CronJob
hpaTargeters []ks.HpaTargeter // all versions of HPAs
}
func (p *parsedObjects) Services() []ks.Service {
return p.services
}
func (p *parsedObjects) Pods() []ks.Pod {
return p.pods
}
func (p *parsedObjects) PodSpeccers() []ks.PodSpecer {
return p.podspecers
}
func (p *parsedObjects) Ingresses() []ks.Ingress {
return p.ingresses
}
func (p *parsedObjects) PodDisruptionBudgets() []ks.PodDisruptionBudget {
return p.podDisruptionBudgets
}
func (p *parsedObjects) CronJobs() []ks.CronJob {
return p.cronjobs
}
func (p *parsedObjects) Deployments() []ks.Deployment {
return p.deployments
}
func (p *parsedObjects) StatefulSets() []ks.StatefulSet {
return p.statefulsets
}
func (p *parsedObjects) Metas() []ks.BothMeta {
return p.bothMetas
}
func (p *parsedObjects) NetworkPolicies() []ks.NetworkPolicy {
return p.networkPolicies
}
func (p *parsedObjects) HorizontalPodAutoscalers() []ks.HpaTargeter {
return p.hpaTargeters
}
func Empty() ks.AllTypes {
return &parsedObjects{}
}
func (p *Parser) ParseFiles(files []ks.NamedReader) (ks.AllTypes, error) {
s := &parsedObjects{}
for _, namedReader := range files {
fullFile, err := io.ReadAll(namedReader)
if err != nil {
return nil, err
}
// Convert to unix style newlines
fullFile = bytes.ReplaceAll(fullFile, []byte("\r\n"), []byte("\n"))
offset := 1 // Line numbers are 1 indexed
// Remove initial "---\n" if present
if bytes.HasPrefix(fullFile, []byte("---\n")) {
fullFile = fullFile[4:]
offset = 2
}
for _, fileContents := range bytes.Split(fullFile, []byte("\n---\n")) {
if len(bytes.TrimSpace(fileContents)) > 0 {
if err := p.detectAndDecode(s, namedReader.Name(), offset, fileContents); err != nil {
return nil, err
}
}
offset += 2 + bytes.Count(fileContents, []byte("\n"))
}
}
return s, nil
}
func (p *Parser) detectAndDecode(s *parsedObjects, fileName string, fileOffset int, raw []byte) error {
var detect detectKind
err := yaml.Unmarshal(raw, &detect)
if err != nil {
return err
}
detectedVersion := schema.FromAPIVersionAndKind(detect.ApiVersion, detect.Kind)
// Parse lists and their items recursively
if detectedVersion == corev1.SchemeGroupVersion.WithKind("List") {
var list corev1.List
err := p.decode(raw, &list)
if err != nil {
return err
}
for _, listItem := range list.Items {
err := p.detectAndDecode(s, fileName, fileOffset, listItem.Raw)
if err != nil {
return err
}
}
return nil
}
err = p.decodeItem(s, detectedVersion, fileName, fileOffset, raw)
if err != nil {
return err
}
return nil
}
func (p *Parser) decode(data []byte, object runtime.Object) error {
deserializer := p.codecs.UniversalDeserializer()
if _, _, err := deserializer.Decode(data, nil, object); err != nil {
gvk := object.GetObjectKind().GroupVersionKind()
return fmt.Errorf("Failed to parse %s: err=%w", gvk, err)
}
return nil
}
func detectFileLocation(fileName string, fileOffset int, fileContents []byte) ks.FileLocation {
// If the object YAML begins with a Helm style "# Source: " comment
// Use the information in there as the file name
firstRow := string(bytes.Split(fileContents, []byte("\n"))[0])
helmTemplatePrefix := "# Source: "
if strings.HasPrefix(firstRow, helmTemplatePrefix) {
return ks.FileLocation{
Name: firstRow[len(helmTemplatePrefix):],
Line: 1, // Set line to 1 as the line definition gets lost in Helm
}
}
return ks.FileLocation{
Name: fileName,
Line: fileOffset,
}
}
func (p *Parser) decodeItem(s *parsedObjects, detectedVersion schema.GroupVersionKind, fileName string, fileOffset int, fileContents []byte) error {
addPodSpeccer := func(ps ks.PodSpecer) {
s.podspecers = append(s.podspecers, ps)
s.bothMetas = append(s.bothMetas, ks.BothMeta{
TypeMeta: ps.GetTypeMeta(),
ObjectMeta: ps.GetObjectMeta(),
FileLocationer: ps,
})
}
fileLocation := detectFileLocation(fileName, fileOffset, fileContents)
var errs parseErrors
switch detectedVersion {
case corev1.SchemeGroupVersion.WithKind("Pod"):
var pod corev1.Pod
errs.AddIfErr(p.decode(fileContents, &pod))
p := internalpod.Pod{Obj: pod, Location: fileLocation}
s.pods = append(s.pods, p)
s.bothMetas = append(s.bothMetas, ks.BothMeta{TypeMeta: pod.TypeMeta, ObjectMeta: pod.ObjectMeta, FileLocationer: p})
case batchv1.SchemeGroupVersion.WithKind("Job"):
var job batchv1.Job
errs.AddIfErr(p.decode(fileContents, &job))
addPodSpeccer(internal.Batchv1Job{Job: job, Location: fileLocation})
case batchv1beta1.SchemeGroupVersion.WithKind("CronJob"):
var cronjob batchv1beta1.CronJob
errs.AddIfErr(p.decode(fileContents, &cronjob))
cjob := internalcronjob.CronJobV1beta1{Obj: cronjob, Location: fileLocation}
addPodSpeccer(cjob)
s.cronjobs = append(s.cronjobs, cjob)
case batchv1.SchemeGroupVersion.WithKind("CronJob"):
var cronjob batchv1.CronJob
errs.AddIfErr(p.decode(fileContents, &cronjob))
cjob := internalcronjob.CronJobV1{Obj: cronjob, Location: fileLocation}
addPodSpeccer(cjob)
s.cronjobs = append(s.cronjobs, cjob)
case appsv1.SchemeGroupVersion.WithKind("Deployment"):
var deployment appsv1.Deployment
errs.AddIfErr(p.decode(fileContents, &deployment))
deploy := internal.Appsv1Deployment{Obj: deployment, Location: fileLocation}
addPodSpeccer(deploy)
// TODO: Support older versions of Deployment as well?
s.deployments = append(s.deployments, deploy)
case appsv1beta1.SchemeGroupVersion.WithKind("Deployment"):
var deployment appsv1beta1.Deployment
errs.AddIfErr(p.decode(fileContents, &deployment))
addPodSpeccer(internal.Appsv1beta1Deployment{Deployment: deployment, Location: fileLocation})
case appsv1beta2.SchemeGroupVersion.WithKind("Deployment"):
var deployment appsv1beta2.Deployment
errs.AddIfErr(p.decode(fileContents, &deployment))
addPodSpeccer(internal.Appsv1beta2Deployment{Deployment: deployment, Location: fileLocation})
case extensionsv1beta1.SchemeGroupVersion.WithKind("Deployment"):
var deployment extensionsv1beta1.Deployment
errs.AddIfErr(p.decode(fileContents, &deployment))
addPodSpeccer(internal.Extensionsv1beta1Deployment{Deployment: deployment, Location: fileLocation})
case appsv1.SchemeGroupVersion.WithKind("StatefulSet"):
var statefulSet appsv1.StatefulSet
errs.AddIfErr(p.decode(fileContents, &statefulSet))
sset := internal.Appsv1StatefulSet{Obj: statefulSet, Location: fileLocation}
addPodSpeccer(sset)
// TODO: Support older versions of StatefulSet as well?
s.statefulsets = append(s.statefulsets, sset)
case appsv1beta1.SchemeGroupVersion.WithKind("StatefulSet"):
var statefulSet appsv1beta1.StatefulSet
errs.AddIfErr(p.decode(fileContents, &statefulSet))
addPodSpeccer(internal.Appsv1beta1StatefulSet{StatefulSet: statefulSet, Location: fileLocation})
case appsv1beta2.SchemeGroupVersion.WithKind("StatefulSet"):
var statefulSet appsv1beta2.StatefulSet
errs.AddIfErr(p.decode(fileContents, &statefulSet))
addPodSpeccer(internal.Appsv1beta2StatefulSet{StatefulSet: statefulSet, Location: fileLocation})
case appsv1.SchemeGroupVersion.WithKind("DaemonSet"):
var daemonset appsv1.DaemonSet
errs.AddIfErr(p.decode(fileContents, &daemonset))
addPodSpeccer(internal.Appsv1DaemonSet{DaemonSet: daemonset, Location: fileLocation})
case appsv1beta2.SchemeGroupVersion.WithKind("DaemonSet"):
var daemonset appsv1beta2.DaemonSet
errs.AddIfErr(p.decode(fileContents, &daemonset))
addPodSpeccer(internal.Appsv1beta2DaemonSet{DaemonSet: daemonset, Location: fileLocation})
case extensionsv1beta1.SchemeGroupVersion.WithKind("DaemonSet"):
var daemonset extensionsv1beta1.DaemonSet
errs.AddIfErr(p.decode(fileContents, &daemonset))
addPodSpeccer(internal.Extensionsv1beta1DaemonSet{DaemonSet: daemonset, Location: fileLocation})
case networkingv1.SchemeGroupVersion.WithKind("NetworkPolicy"):
var netpol networkingv1.NetworkPolicy
errs.AddIfErr(p.decode(fileContents, &netpol))
np := internalnetpol.NetworkPolicy{Obj: netpol, Location: fileLocation}
s.networkPolicies = append(s.networkPolicies, np)
s.bothMetas = append(s.bothMetas, ks.BothMeta{TypeMeta: netpol.TypeMeta, ObjectMeta: netpol.ObjectMeta, FileLocationer: np})
case corev1.SchemeGroupVersion.WithKind("Service"):
var service corev1.Service
errs.AddIfErr(p.decode(fileContents, &service))
serv := internalservice.Service{Obj: service, Location: fileLocation}
s.services = append(s.services, serv)
s.bothMetas = append(s.bothMetas, ks.BothMeta{TypeMeta: service.TypeMeta, ObjectMeta: service.ObjectMeta, FileLocationer: serv})
case policyv1beta1.SchemeGroupVersion.WithKind("PodDisruptionBudget"):
var disruptBudget policyv1beta1.PodDisruptionBudget
errs.AddIfErr(p.decode(fileContents, &disruptBudget))
dbug := internalpdb.PodDisruptionBudgetV1beta1{Obj: disruptBudget, Location: fileLocation}
s.podDisruptionBudgets = append(s.podDisruptionBudgets, dbug)
s.bothMetas = append(s.bothMetas, ks.BothMeta{TypeMeta: disruptBudget.TypeMeta, ObjectMeta: disruptBudget.ObjectMeta, FileLocationer: dbug})
case policyv1.SchemeGroupVersion.WithKind("PodDisruptionBudget"):
var disruptBudget policyv1.PodDisruptionBudget
errs.AddIfErr(p.decode(fileContents, &disruptBudget))
dbug := internalpdb.PodDisruptionBudgetV1{Obj: disruptBudget, Location: fileLocation}
s.podDisruptionBudgets = append(s.podDisruptionBudgets, dbug)
s.bothMetas = append(s.bothMetas, ks.BothMeta{
TypeMeta: disruptBudget.TypeMeta,
ObjectMeta: disruptBudget.ObjectMeta,
FileLocationer: dbug,
})
case extensionsv1beta1.SchemeGroupVersion.WithKind("Ingress"):
var ingress extensionsv1beta1.Ingress
errs.AddIfErr(p.decode(fileContents, &ingress))
ing := internal.ExtensionsIngressV1beta1{Ingress: ingress, Location: fileLocation}
s.ingresses = append(s.ingresses, ing)
s.bothMetas = append(s.bothMetas, ks.BothMeta{TypeMeta: ingress.TypeMeta, ObjectMeta: ingress.ObjectMeta, FileLocationer: ing})
case networkingv1beta1.SchemeGroupVersion.WithKind("Ingress"):
var ingress networkingv1beta1.Ingress
errs.AddIfErr(p.decode(fileContents, &ingress))
ing := internal.IngressV1beta1{Ingress: ingress, Location: fileLocation}
s.ingresses = append(s.ingresses, ing)
s.bothMetas = append(s.bothMetas, ks.BothMeta{TypeMeta: ingress.TypeMeta, ObjectMeta: ingress.ObjectMeta, FileLocationer: ing})
case networkingv1.SchemeGroupVersion.WithKind("Ingress"):
var ingress networkingv1.Ingress
errs.AddIfErr(p.decode(fileContents, &ingress))
ing := internal.IngressV1{Ingress: ingress, Location: fileLocation}
s.ingresses = append(s.ingresses, ing)
s.bothMetas = append(s.bothMetas, ks.BothMeta{TypeMeta: ingress.TypeMeta, ObjectMeta: ingress.ObjectMeta, FileLocationer: ing})
case autoscalingv1.SchemeGroupVersion.WithKind("HorizontalPodAutoscaler"):
var hpa autoscalingv1.HorizontalPodAutoscaler
errs.AddIfErr(p.decode(fileContents, &hpa))
h := internal.HPAv1{HorizontalPodAutoscaler: hpa, Location: fileLocation}
s.hpaTargeters = append(s.hpaTargeters, h)
s.bothMetas = append(s.bothMetas, ks.BothMeta{TypeMeta: hpa.TypeMeta, ObjectMeta: hpa.ObjectMeta, FileLocationer: h})
case autoscalingv2beta1.SchemeGroupVersion.WithKind("HorizontalPodAutoscaler"):
var hpa autoscalingv2beta1.HorizontalPodAutoscaler
errs.AddIfErr(p.decode(fileContents, &hpa))
h := internal.HPAv2beta1{HorizontalPodAutoscaler: hpa, Location: fileLocation}
s.hpaTargeters = append(s.hpaTargeters, h)
s.bothMetas = append(s.bothMetas, ks.BothMeta{TypeMeta: hpa.TypeMeta, ObjectMeta: hpa.ObjectMeta, FileLocationer: h})
case autoscalingv2beta2.SchemeGroupVersion.WithKind("HorizontalPodAutoscaler"):
var hpa autoscalingv2beta2.HorizontalPodAutoscaler
errs.AddIfErr(p.decode(fileContents, &hpa))
h := internal.HPAv2beta2{HorizontalPodAutoscaler: hpa, Location: fileLocation}
s.hpaTargeters = append(s.hpaTargeters, h)
s.bothMetas = append(s.bothMetas, ks.BothMeta{
TypeMeta: hpa.TypeMeta,
ObjectMeta: hpa.ObjectMeta,
FileLocationer: h,
})
case autoscalingv2.SchemeGroupVersion.WithKind("HorizontalPodAutoscaler"):
var hpa autoscalingv2.HorizontalPodAutoscaler
errs.AddIfErr(p.decode(fileContents, &hpa))
h := internal.HPAv2{HorizontalPodAutoscaler: hpa, Location: fileLocation}
s.hpaTargeters = append(s.hpaTargeters, h)
s.bothMetas = append(s.bothMetas, ks.BothMeta{TypeMeta: hpa.TypeMeta, ObjectMeta: hpa.ObjectMeta, FileLocationer: h})
default:
if p.config.VerboseOutput > 1 {
log.Printf("Unknown datatype: %s", detectedVersion.String())
}
}
if errs.Any() {
return errs
}
return nil
}
0707010000003D000081A4000000000000000000000001680FB1F8000005A0000000000000000000000000000000000000002700000000kube-score-1.20.0/parser/parse_test.gopackage parser
import (
"fmt"
"os"
"testing"
ks "github.com/zegl/kube-score/domain"
"github.com/stretchr/testify/assert"
)
func TestParse(t *testing.T) {
cases := []struct {
fname string
expected error
}{
{
"testdata/invalid-yaml.yaml",
fmt.Errorf("Failed to parse /v1, Kind=Service: err=json: cannot unmarshal string into Go struct field ServicePort.spec.ports.nodePort of type int32"),
}, {
"testdata/valid-yaml.yaml",
nil,
},
}
parser, err := New(nil)
assert.NoError(t, err)
for _, tc := range cases {
fp, err := os.Open(tc.fname)
assert.Nil(t, err)
_, err = parser.ParseFiles(
[]ks.NamedReader{fp},
)
if tc.expected == nil {
assert.Nil(t, err)
} else {
assert.Equal(t, tc.expected.Error(), err.Error())
}
}
}
func TestFileLocationHelm(t *testing.T) {
doc := `# Source: app1/templates/deployment.yaml
kind: Deployment
apiVersion: apps/v1
metadata:
name: foo
spec:
template:
metadata:
labels:
foo: bar`
fl := detectFileLocation("someName", 1, []byte(doc))
assert.Equal(t, "app1/templates/deployment.yaml", fl.Name)
assert.Equal(t, 1, fl.Line)
}
func TestFileLocation(t *testing.T) {
doc := `kind: Deployment
apiVersion: apps/v1
metadata:
name: foo
spec:
template:
metadata:
labels:
foo: bar`
fl := detectFileLocation("someName", 123, []byte(doc))
assert.Equal(t, "someName", fl.Name)
assert.Equal(t, 123, fl.Line)
}
0707010000003E000041ED000000000000000000000002680FB1F800000000000000000000000000000000000000000000002200000000kube-score-1.20.0/parser/testdata0707010000003F000081A4000000000000000000000001680FB1F8000000DA000000000000000000000000000000000000003400000000kube-score-1.20.0/parser/testdata/invalid-yaml.yamlapiVersion: v1
kind: Service
metadata:
name: ${APP}-service
namespace: test
spec:
type: NodePort
ports:
- port: ${PORT_EXPOSE}
targetPort: ${PORT_EXPOSE}
nodePort: ${PORT}
selector:
app: ${APP}
07070100000040000081A4000000000000000000000001680FB1F8000000BD000000000000000000000000000000000000003200000000kube-score-1.20.0/parser/testdata/valid-yaml.yamlapiVersion: v1
kind: Service
metadata:
name: xyz-service
namespace: test
spec:
type: NodePort
ports:
- port: 123
targetPort: 4444
nodePort: 32444
selector:
app: xyz
07070100000041000081A4000000000000000000000001680FB1F80000E181000000000000000000000000000000000000002100000000kube-score-1.20.0/pnpm-lock.yamllockfileVersion: '9.0'
settings:
autoInstallPeers: true
excludeLinksFromLockfile: false
importers:
.: {}
web:
dependencies:
react:
specifier: ^18.3.1
version: 18.3.1
react-dom:
specifier: ^18.3.1
version: 18.3.1(react@18.3.1)
styled-components:
specifier: ^6.1.11
version: 6.1.11(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
devDependencies:
'@types/react':
specifier: ^18.3.3
version: 18.3.3
'@types/react-dom':
specifier: ^18.3.0
version: 18.3.0
'@typescript-eslint/eslint-plugin':
specifier: ^8.8.1
version: 8.8.1(@typescript-eslint/parser@8.8.1(eslint@9.12.0)(typescript@5.6.3))(eslint@9.12.0)(typescript@5.6.3)
'@typescript-eslint/parser':
specifier: ^8.8.1
version: 8.8.1(eslint@9.12.0)(typescript@5.6.3)
'@vitejs/plugin-react-swc':
specifier: ^3.7.1
version: 3.7.1(vite@5.4.8)
eslint:
specifier: ^9.12.0
version: 9.12.0
eslint-plugin-react-hooks:
specifier: ^5.0.0
version: 5.0.0(eslint@9.12.0)
eslint-plugin-react-refresh:
specifier: ^0.4.12
version: 0.4.12(eslint@9.12.0)
typescript:
specifier: ^5.6.3
version: 5.6.3
vite:
specifier: ^5.4.8
version: 5.4.8
packages:
'@emotion/is-prop-valid@1.2.2':
resolution: {integrity: sha512-uNsoYd37AFmaCdXlg6EYD1KaPOaRWRByMCYzbKUX4+hhMfrxdVSelShywL4JVaAeM/eHUOSprYBQls+/neX3pw==}
'@emotion/memoize@0.8.1':
resolution: {integrity: sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==}
'@emotion/unitless@0.8.1':
resolution: {integrity: sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==}
'@esbuild/aix-ppc64@0.21.5':
resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==}
engines: {node: '>=12'}
cpu: [ppc64]
os: [aix]
'@esbuild/android-arm64@0.21.5':
resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==}
engines: {node: '>=12'}
cpu: [arm64]
os: [android]
'@esbuild/android-arm@0.21.5':
resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==}
engines: {node: '>=12'}
cpu: [arm]
os: [android]
'@esbuild/android-x64@0.21.5':
resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==}
engines: {node: '>=12'}
cpu: [x64]
os: [android]
'@esbuild/darwin-arm64@0.21.5':
resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==}
engines: {node: '>=12'}
cpu: [arm64]
os: [darwin]
'@esbuild/darwin-x64@0.21.5':
resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==}
engines: {node: '>=12'}
cpu: [x64]
os: [darwin]
'@esbuild/freebsd-arm64@0.21.5':
resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==}
engines: {node: '>=12'}
cpu: [arm64]
os: [freebsd]
'@esbuild/freebsd-x64@0.21.5':
resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==}
engines: {node: '>=12'}
cpu: [x64]
os: [freebsd]
'@esbuild/linux-arm64@0.21.5':
resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==}
engines: {node: '>=12'}
cpu: [arm64]
os: [linux]
'@esbuild/linux-arm@0.21.5':
resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==}
engines: {node: '>=12'}
cpu: [arm]
os: [linux]
'@esbuild/linux-ia32@0.21.5':
resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==}
engines: {node: '>=12'}
cpu: [ia32]
os: [linux]
'@esbuild/linux-loong64@0.21.5':
resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==}
engines: {node: '>=12'}
cpu: [loong64]
os: [linux]
'@esbuild/linux-mips64el@0.21.5':
resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==}
engines: {node: '>=12'}
cpu: [mips64el]
os: [linux]
'@esbuild/linux-ppc64@0.21.5':
resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==}
engines: {node: '>=12'}
cpu: [ppc64]
os: [linux]
'@esbuild/linux-riscv64@0.21.5':
resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==}
engines: {node: '>=12'}
cpu: [riscv64]
os: [linux]
'@esbuild/linux-s390x@0.21.5':
resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==}
engines: {node: '>=12'}
cpu: [s390x]
os: [linux]
'@esbuild/linux-x64@0.21.5':
resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==}
engines: {node: '>=12'}
cpu: [x64]
os: [linux]
'@esbuild/netbsd-x64@0.21.5':
resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==}
engines: {node: '>=12'}
cpu: [x64]
os: [netbsd]
'@esbuild/openbsd-x64@0.21.5':
resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==}
engines: {node: '>=12'}
cpu: [x64]
os: [openbsd]
'@esbuild/sunos-x64@0.21.5':
resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==}
engines: {node: '>=12'}
cpu: [x64]
os: [sunos]
'@esbuild/win32-arm64@0.21.5':
resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==}
engines: {node: '>=12'}
cpu: [arm64]
os: [win32]
'@esbuild/win32-ia32@0.21.5':
resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==}
engines: {node: '>=12'}
cpu: [ia32]
os: [win32]
'@esbuild/win32-x64@0.21.5':
resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==}
engines: {node: '>=12'}
cpu: [x64]
os: [win32]
'@eslint-community/eslint-utils@4.4.0':
resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
peerDependencies:
eslint: ^6.0.0 || ^7.0.0 || >=8.0.0
'@eslint-community/regexpp@4.11.0':
resolution: {integrity: sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A==}
engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0}
'@eslint/config-array@0.18.0':
resolution: {integrity: sha512-fTxvnS1sRMu3+JjXwJG0j/i4RT9u4qJ+lqS/yCGap4lH4zZGzQ7tu+xZqQmcMZq5OBZDL4QRxQzRjkWcGt8IVw==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@eslint/core@0.6.0':
resolution: {integrity: sha512-8I2Q8ykA4J0x0o7cg67FPVnehcqWTBehu/lmY+bolPFHGjh49YzGBMXTvpqVgEbBdvNCSxj6iFgiIyHzf03lzg==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@eslint/eslintrc@3.1.0':
resolution: {integrity: sha512-4Bfj15dVJdoy3RfZmmo86RK1Fwzn6SstsvK9JS+BaVKqC6QQQQyXekNaC+g+LKNgkQ+2VhGAzm6hO40AhMR3zQ==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@eslint/js@9.12.0':
resolution: {integrity: sha512-eohesHH8WFRUprDNyEREgqP6beG6htMeUYeCpkEgBCieCMme5r9zFWjzAJp//9S+Kub4rqE+jXe9Cp1a7IYIIA==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@eslint/object-schema@2.1.4':
resolution: {integrity: sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@eslint/plugin-kit@0.2.0':
resolution: {integrity: sha512-vH9PiIMMwvhCx31Af3HiGzsVNULDbyVkHXwlemn/B0TFj/00ho3y55efXrUZTfQipxoHC5u4xq6zblww1zm1Ig==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@humanfs/core@0.19.0':
resolution: {integrity: sha512-2cbWIHbZVEweE853g8jymffCA+NCMiuqeECeBBLm8dg2oFdjuGJhgN4UAbI+6v0CKbbhvtXA4qV8YR5Ji86nmw==}
engines: {node: '>=18.18.0'}
'@humanfs/node@0.16.5':
resolution: {integrity: sha512-KSPA4umqSG4LHYRodq31VDwKAvaTF4xmVlzM8Aeh4PlU1JQ3IG0wiA8C25d3RQ9nJyM3mBHyI53K06VVL/oFFg==}
engines: {node: '>=18.18.0'}
'@humanwhocodes/module-importer@1.0.1':
resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==}
engines: {node: '>=12.22'}
'@humanwhocodes/retry@0.3.1':
resolution: {integrity: sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==}
engines: {node: '>=18.18'}
'@nodelib/fs.scandir@2.1.5':
resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
engines: {node: '>= 8'}
'@nodelib/fs.stat@2.0.5':
resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==}
engines: {node: '>= 8'}
'@nodelib/fs.walk@1.2.8':
resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==}
engines: {node: '>= 8'}
'@rollup/rollup-android-arm-eabi@4.24.0':
resolution: {integrity: sha512-Q6HJd7Y6xdB48x8ZNVDOqsbh2uByBhgK8PiQgPhwkIw/HC/YX5Ghq2mQY5sRMZWHb3VsFkWooUVOZHKr7DmDIA==}
cpu: [arm]
os: [android]
'@rollup/rollup-android-arm64@4.24.0':
resolution: {integrity: sha512-ijLnS1qFId8xhKjT81uBHuuJp2lU4x2yxa4ctFPtG+MqEE6+C5f/+X/bStmxapgmwLwiL3ih122xv8kVARNAZA==}
cpu: [arm64]
os: [android]
'@rollup/rollup-darwin-arm64@4.24.0':
resolution: {integrity: sha512-bIv+X9xeSs1XCk6DVvkO+S/z8/2AMt/2lMqdQbMrmVpgFvXlmde9mLcbQpztXm1tajC3raFDqegsH18HQPMYtA==}
cpu: [arm64]
os: [darwin]
'@rollup/rollup-darwin-x64@4.24.0':
resolution: {integrity: sha512-X6/nOwoFN7RT2svEQWUsW/5C/fYMBe4fnLK9DQk4SX4mgVBiTA9h64kjUYPvGQ0F/9xwJ5U5UfTbl6BEjaQdBQ==}
cpu: [x64]
os: [darwin]
'@rollup/rollup-linux-arm-gnueabihf@4.24.0':
resolution: {integrity: sha512-0KXvIJQMOImLCVCz9uvvdPgfyWo93aHHp8ui3FrtOP57svqrF/roSSR5pjqL2hcMp0ljeGlU4q9o/rQaAQ3AYA==}
cpu: [arm]
os: [linux]
'@rollup/rollup-linux-arm-musleabihf@4.24.0':
resolution: {integrity: sha512-it2BW6kKFVh8xk/BnHfakEeoLPv8STIISekpoF+nBgWM4d55CZKc7T4Dx1pEbTnYm/xEKMgy1MNtYuoA8RFIWw==}
cpu: [arm]
os: [linux]
'@rollup/rollup-linux-arm64-gnu@4.24.0':
resolution: {integrity: sha512-i0xTLXjqap2eRfulFVlSnM5dEbTVque/3Pi4g2y7cxrs7+a9De42z4XxKLYJ7+OhE3IgxvfQM7vQc43bwTgPwA==}
cpu: [arm64]
os: [linux]
'@rollup/rollup-linux-arm64-musl@4.24.0':
resolution: {integrity: sha512-9E6MKUJhDuDh604Qco5yP/3qn3y7SLXYuiC0Rpr89aMScS2UAmK1wHP2b7KAa1nSjWJc/f/Lc0Wl1L47qjiyQw==}
cpu: [arm64]
os: [linux]
'@rollup/rollup-linux-powerpc64le-gnu@4.24.0':
resolution: {integrity: sha512-2XFFPJ2XMEiF5Zi2EBf4h73oR1V/lycirxZxHZNc93SqDN/IWhYYSYj8I9381ikUFXZrz2v7r2tOVk2NBwxrWw==}
cpu: [ppc64]
os: [linux]
'@rollup/rollup-linux-riscv64-gnu@4.24.0':
resolution: {integrity: sha512-M3Dg4hlwuntUCdzU7KjYqbbd+BLq3JMAOhCKdBE3TcMGMZbKkDdJ5ivNdehOssMCIokNHFOsv7DO4rlEOfyKpg==}
cpu: [riscv64]
os: [linux]
'@rollup/rollup-linux-s390x-gnu@4.24.0':
resolution: {integrity: sha512-mjBaoo4ocxJppTorZVKWFpy1bfFj9FeCMJqzlMQGjpNPY9JwQi7OuS1axzNIk0nMX6jSgy6ZURDZ2w0QW6D56g==}
cpu: [s390x]
os: [linux]
'@rollup/rollup-linux-x64-gnu@4.24.0':
resolution: {integrity: sha512-ZXFk7M72R0YYFN5q13niV0B7G8/5dcQ9JDp8keJSfr3GoZeXEoMHP/HlvqROA3OMbMdfr19IjCeNAnPUG93b6A==}
cpu: [x64]
os: [linux]
'@rollup/rollup-linux-x64-musl@4.24.0':
resolution: {integrity: sha512-w1i+L7kAXZNdYl+vFvzSZy8Y1arS7vMgIy8wusXJzRrPyof5LAb02KGr1PD2EkRcl73kHulIID0M501lN+vobQ==}
cpu: [x64]
os: [linux]
'@rollup/rollup-win32-arm64-msvc@4.24.0':
resolution: {integrity: sha512-VXBrnPWgBpVDCVY6XF3LEW0pOU51KbaHhccHw6AS6vBWIC60eqsH19DAeeObl+g8nKAz04QFdl/Cefta0xQtUQ==}
cpu: [arm64]
os: [win32]
'@rollup/rollup-win32-ia32-msvc@4.24.0':
resolution: {integrity: sha512-xrNcGDU0OxVcPTH/8n/ShH4UevZxKIO6HJFK0e15XItZP2UcaiLFd5kiX7hJnqCbSztUF8Qot+JWBC/QXRPYWQ==}
cpu: [ia32]
os: [win32]
'@rollup/rollup-win32-x64-msvc@4.24.0':
resolution: {integrity: sha512-fbMkAF7fufku0N2dE5TBXcNlg0pt0cJue4xBRE2Qc5Vqikxr4VCgKj/ht6SMdFcOacVA9rqF70APJ8RN/4vMJw==}
cpu: [x64]
os: [win32]
'@swc/core-darwin-arm64@1.7.35':
resolution: {integrity: sha512-BQSSozVxjxS+SVQz6e3GC/+OBWGIK3jfe52pWdANmycdjF3ch7lrCKTHTU7eHwyoJ96mofszPf5AsiVJF34Fwg==}
engines: {node: '>=10'}
cpu: [arm64]
os: [darwin]
'@swc/core-darwin-x64@1.7.35':
resolution: {integrity: sha512-44TYdKN/EWtkU88foXR7IGki9JzhEJzaFOoPevfi9Xe7hjAD/x2+AJOWWqQNzDPMz9+QewLdUVLyR6s5okRgtg==}
engines: {node: '>=10'}
cpu: [x64]
os: [darwin]
'@swc/core-linux-arm-gnueabihf@1.7.35':
resolution: {integrity: sha512-ccfA5h3zxwioD+/z/AmYtkwtKz9m4rWTV7RoHq6Jfsb0cXHrd6tbcvgqRWXra1kASlE+cDWsMtEZygs9dJRtUQ==}
engines: {node: '>=10'}
cpu: [arm]
os: [linux]
'@swc/core-linux-arm64-gnu@1.7.35':
resolution: {integrity: sha512-hx65Qz+G4iG/IVtxJKewC5SJdki8PAPFGl6gC/57Jb0+jA4BIoGLD/J3Q3rCPeoHfdqpkCYpahtyUq8CKx41Jg==}
engines: {node: '>=10'}
cpu: [arm64]
os: [linux]
'@swc/core-linux-arm64-musl@1.7.35':
resolution: {integrity: sha512-kL6tQL9No7UEoEvDRuPxzPTpxrvbwYteNRbdChSSP74j13/55G2/2hLmult5yFFaWuyoyU/2lvzjRL/i8OLZxg==}
engines: {node: '>=10'}
cpu: [arm64]
os: [linux]
'@swc/core-linux-x64-gnu@1.7.35':
resolution: {integrity: sha512-Ke4rcLQSwCQ2LHdJX1FtnqmYNQ3IX6BddKlUtS7mcK13IHkQzZWp0Dcu6MgNA3twzb/dBpKX5GLy07XdGgfmyw==}
engines: {node: '>=10'}
cpu: [x64]
os: [linux]
'@swc/core-linux-x64-musl@1.7.35':
resolution: {integrity: sha512-T30tlLnz0kYyDFyO5RQF5EQ4ENjW9+b56hEGgFUYmfhFhGA4E4V67iEx7KIG4u0whdPG7oy3qjyyIeTb7nElEw==}
engines: {node: '>=10'}
cpu: [x64]
os: [linux]
'@swc/core-win32-arm64-msvc@1.7.35':
resolution: {integrity: sha512-CfM/k8mvtuMyX+okRhemfLt784PLS0KF7Q9djA8/Dtavk0L5Ghnq+XsGltO3d8B8+XZ7YOITsB14CrjehzeHsg==}
engines: {node: '>=10'}
cpu: [arm64]
os: [win32]
'@swc/core-win32-ia32-msvc@1.7.35':
resolution: {integrity: sha512-ATB3uuH8j/RmS64EXQZJSbo2WXfRNpTnQszHME/sGaexsuxeijrp3DTYSFAA3R2Bu6HbIIX6jempe1Au8I3j+A==}
engines: {node: '>=10'}
cpu: [ia32]
os: [win32]
'@swc/core-win32-x64-msvc@1.7.35':
resolution: {integrity: sha512-iDGfQO1571NqWUXtLYDhwIELA/wadH42ioGn+J9R336nWx40YICzy9UQyslWRhqzhQ5kT+QXAW/MoCWc058N6Q==}
engines: {node: '>=10'}
cpu: [x64]
os: [win32]
'@swc/core@1.7.35':
resolution: {integrity: sha512-3cUteCTbr2r5jqfgx0r091sfq5Mgh6F1SQh8XAOnSvtKzwv2bC31mvBHVAieD1uPa2kHJhLav20DQgXOhpEitw==}
engines: {node: '>=10'}
peerDependencies:
'@swc/helpers': '*'
peerDependenciesMeta:
'@swc/helpers':
optional: true
'@swc/counter@0.1.3':
resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==}
'@swc/types@0.1.13':
resolution: {integrity: sha512-JL7eeCk6zWCbiYQg2xQSdLXQJl8Qoc9rXmG2cEKvHe3CKwMHwHGpfOb8frzNLmbycOo6I51qxnLnn9ESf4I20Q==}
'@types/estree@1.0.6':
resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==}
'@types/json-schema@7.0.15':
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
'@types/prop-types@15.7.12':
resolution: {integrity: sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==}
'@types/react-dom@18.3.0':
resolution: {integrity: sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg==}
'@types/react@18.3.3':
resolution: {integrity: sha512-hti/R0pS0q1/xx+TsI73XIqk26eBsISZ2R0wUijXIngRK9R/e7Xw/cXVxQK7R5JjW+SV4zGcn5hXjudkN/pLIw==}
'@types/stylis@4.2.5':
resolution: {integrity: sha512-1Xve+NMN7FWjY14vLoY5tL3BVEQ/n42YLwaqJIPYhotZ9uBHt87VceMwWQpzmdEt2TNXIorIFG+YeCUUW7RInw==}
'@typescript-eslint/eslint-plugin@8.8.1':
resolution: {integrity: sha512-xfvdgA8AP/vxHgtgU310+WBnLB4uJQ9XdyP17RebG26rLtDrQJV3ZYrcopX91GrHmMoH8bdSwMRh2a//TiJ1jQ==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
'@typescript-eslint/parser': ^8.0.0 || ^8.0.0-alpha.0
eslint: ^8.57.0 || ^9.0.0
typescript: '*'
peerDependenciesMeta:
typescript:
optional: true
'@typescript-eslint/parser@8.8.1':
resolution: {integrity: sha512-hQUVn2Lij2NAxVFEdvIGxT9gP1tq2yM83m+by3whWFsWC+1y8pxxxHUFE1UqDu2VsGi2i6RLcv4QvouM84U+ow==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
eslint: ^8.57.0 || ^9.0.0
typescript: '*'
peerDependenciesMeta:
typescript:
optional: true
'@typescript-eslint/scope-manager@8.8.1':
resolution: {integrity: sha512-X4JdU+66Mazev/J0gfXlcC/dV6JI37h+93W9BRYXrSn0hrE64IoWgVkO9MSJgEzoWkxONgaQpICWg8vAN74wlA==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@typescript-eslint/type-utils@8.8.1':
resolution: {integrity: sha512-qSVnpcbLP8CALORf0za+vjLYj1Wp8HSoiI8zYU5tHxRVj30702Z1Yw4cLwfNKhTPWp5+P+k1pjmD5Zd1nhxiZA==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
typescript: '*'
peerDependenciesMeta:
typescript:
optional: true
'@typescript-eslint/types@8.8.1':
resolution: {integrity: sha512-WCcTP4SDXzMd23N27u66zTKMuEevH4uzU8C9jf0RO4E04yVHgQgW+r+TeVTNnO1KIfrL8ebgVVYYMMO3+jC55Q==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@typescript-eslint/typescript-estree@8.8.1':
resolution: {integrity: sha512-A5d1R9p+X+1js4JogdNilDuuq+EHZdsH9MjTVxXOdVFfTJXunKJR/v+fNNyO4TnoOn5HqobzfRlc70NC6HTcdg==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
typescript: '*'
peerDependenciesMeta:
typescript:
optional: true
'@typescript-eslint/utils@8.8.1':
resolution: {integrity: sha512-/QkNJDbV0bdL7H7d0/y0qBbV2HTtf0TIyjSDTvvmQEzeVx8jEImEbLuOA4EsvE8gIgqMitns0ifb5uQhMj8d9w==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
eslint: ^8.57.0 || ^9.0.0
'@typescript-eslint/visitor-keys@8.8.1':
resolution: {integrity: sha512-0/TdC3aeRAsW7MDvYRwEc1Uwm0TIBfzjPFgg60UU2Haj5qsCs9cc3zNgY71edqE3LbWfF/WoZQd3lJoDXFQpag==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@vitejs/plugin-react-swc@3.7.1':
resolution: {integrity: sha512-vgWOY0i1EROUK0Ctg1hwhtC3SdcDjZcdit4Ups4aPkDcB1jYhmo+RMYWY87cmXMhvtD5uf8lV89j2w16vkdSVg==}
peerDependencies:
vite: ^4 || ^5
acorn-jsx@5.3.2:
resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
peerDependencies:
acorn: ^6.0.0 || ^7.0.0 || ^8.0.0
acorn@8.12.1:
resolution: {integrity: sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==}
engines: {node: '>=0.4.0'}
hasBin: true
ajv@6.12.6:
resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==}
ansi-styles@4.3.0:
resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
engines: {node: '>=8'}
argparse@2.0.1:
resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
balanced-match@1.0.2:
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
brace-expansion@1.1.11:
resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==}
brace-expansion@2.0.1:
resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==}
braces@3.0.3:
resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==}
engines: {node: '>=8'}
callsites@3.1.0:
resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
engines: {node: '>=6'}
camelize@1.0.1:
resolution: {integrity: sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==}
chalk@4.1.2:
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
engines: {node: '>=10'}
color-convert@2.0.1:
resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
engines: {node: '>=7.0.0'}
color-name@1.1.4:
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
concat-map@0.0.1:
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
cross-spawn@7.0.3:
resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==}
engines: {node: '>= 8'}
css-color-keywords@1.0.0:
resolution: {integrity: sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==}
engines: {node: '>=4'}
css-to-react-native@3.2.0:
resolution: {integrity: sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==}
csstype@3.1.3:
resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==}
debug@4.3.5:
resolution: {integrity: sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==}
engines: {node: '>=6.0'}
peerDependencies:
supports-color: '*'
peerDependenciesMeta:
supports-color:
optional: true
deep-is@0.1.4:
resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
esbuild@0.21.5:
resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==}
engines: {node: '>=12'}
hasBin: true
escape-string-regexp@4.0.0:
resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==}
engines: {node: '>=10'}
eslint-plugin-react-hooks@5.0.0:
resolution: {integrity: sha512-hIOwI+5hYGpJEc4uPRmz2ulCjAGD/N13Lukkh8cLV0i2IRk/bdZDYjgLVHj+U9Z704kLIdIO6iueGvxNur0sgw==}
engines: {node: '>=10'}
peerDependencies:
eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0
eslint-plugin-react-refresh@0.4.12:
resolution: {integrity: sha512-9neVjoGv20FwYtCP6CB1dzR1vr57ZDNOXst21wd2xJ/cTlM2xLq0GWVlSNTdMn/4BtP6cHYBMCSp1wFBJ9jBsg==}
peerDependencies:
eslint: '>=7'
eslint-scope@8.1.0:
resolution: {integrity: sha512-14dSvlhaVhKKsa9Fx1l8A17s7ah7Ef7wCakJ10LYk6+GYmP9yDti2oq2SEwcyndt6knfcZyhyxwY3i9yL78EQw==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
eslint-visitor-keys@3.4.3:
resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
eslint-visitor-keys@4.1.0:
resolution: {integrity: sha512-Q7lok0mqMUSf5a/AdAZkA5a/gHcO6snwQClVNNvFKCAVlxXucdU8pKydU5ZVZjBx5xr37vGbFFWtLQYreLzrZg==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
eslint@9.12.0:
resolution: {integrity: sha512-UVIOlTEWxwIopRL1wgSQYdnVDcEvs2wyaO6DGo5mXqe3r16IoCNWkR29iHhyaP4cICWjbgbmFUGAhh0GJRuGZw==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
hasBin: true
peerDependencies:
jiti: '*'
peerDependenciesMeta:
jiti:
optional: true
espree@10.2.0:
resolution: {integrity: sha512-upbkBJbckcCNBDBDXEbuhjbP68n+scUd3k/U2EkyM9nw+I/jPiL4cLF/Al06CF96wRltFda16sxDFrxsI1v0/g==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
esquery@1.6.0:
resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==}
engines: {node: '>=0.10'}
esrecurse@4.3.0:
resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==}
engines: {node: '>=4.0'}
estraverse@5.3.0:
resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==}
engines: {node: '>=4.0'}
esutils@2.0.3:
resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==}
engines: {node: '>=0.10.0'}
fast-deep-equal@3.1.3:
resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
fast-glob@3.3.2:
resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==}
engines: {node: '>=8.6.0'}
fast-json-stable-stringify@2.1.0:
resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==}
fast-levenshtein@2.0.6:
resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==}
fastq@1.17.1:
resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==}
file-entry-cache@8.0.0:
resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==}
engines: {node: '>=16.0.0'}
fill-range@7.1.1:
resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
engines: {node: '>=8'}
find-up@5.0.0:
resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==}
engines: {node: '>=10'}
flat-cache@4.0.1:
resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==}
engines: {node: '>=16'}
flatted@3.3.1:
resolution: {integrity: sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==}
fsevents@2.3.3:
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
os: [darwin]
glob-parent@5.1.2:
resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
engines: {node: '>= 6'}
glob-parent@6.0.2:
resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==}
engines: {node: '>=10.13.0'}
globals@14.0.0:
resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==}
engines: {node: '>=18'}
graphemer@1.4.0:
resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==}
has-flag@4.0.0:
resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
engines: {node: '>=8'}
ignore@5.3.1:
resolution: {integrity: sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==}
engines: {node: '>= 4'}
import-fresh@3.3.0:
resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==}
engines: {node: '>=6'}
imurmurhash@0.1.4:
resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==}
engines: {node: '>=0.8.19'}
is-extglob@2.1.1:
resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
engines: {node: '>=0.10.0'}
is-glob@4.0.3:
resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
engines: {node: '>=0.10.0'}
is-number@7.0.0:
resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
engines: {node: '>=0.12.0'}
isexe@2.0.0:
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
js-tokens@4.0.0:
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
js-yaml@4.1.0:
resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==}
hasBin: true
json-buffer@3.0.1:
resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==}
json-schema-traverse@0.4.1:
resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==}
json-stable-stringify-without-jsonify@1.0.1:
resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==}
keyv@4.5.4:
resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==}
levn@0.4.1:
resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
engines: {node: '>= 0.8.0'}
locate-path@6.0.0:
resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}
engines: {node: '>=10'}
lodash.merge@4.6.2:
resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
loose-envify@1.4.0:
resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
hasBin: true
merge2@1.4.1:
resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
engines: {node: '>= 8'}
micromatch@4.0.7:
resolution: {integrity: sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==}
engines: {node: '>=8.6'}
minimatch@3.1.2:
resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
minimatch@9.0.5:
resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==}
engines: {node: '>=16 || 14 >=14.17'}
ms@2.1.2:
resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==}
nanoid@3.3.7:
resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==}
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
hasBin: true
natural-compare@1.4.0:
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
optionator@0.9.4:
resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==}
engines: {node: '>= 0.8.0'}
p-limit@3.1.0:
resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==}
engines: {node: '>=10'}
p-locate@5.0.0:
resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==}
engines: {node: '>=10'}
parent-module@1.0.1:
resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
engines: {node: '>=6'}
path-exists@4.0.0:
resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
engines: {node: '>=8'}
path-key@3.1.1:
resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
engines: {node: '>=8'}
picocolors@1.0.1:
resolution: {integrity: sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==}
picocolors@1.1.0:
resolution: {integrity: sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==}
picomatch@2.3.1:
resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
engines: {node: '>=8.6'}
postcss-value-parser@4.2.0:
resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==}
postcss@8.4.38:
resolution: {integrity: sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==}
engines: {node: ^10 || ^12 || >=14}
postcss@8.4.47:
resolution: {integrity: sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==}
engines: {node: ^10 || ^12 || >=14}
prelude-ls@1.2.1:
resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
engines: {node: '>= 0.8.0'}
punycode@2.3.1:
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
engines: {node: '>=6'}
queue-microtask@1.2.3:
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
react-dom@18.3.1:
resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==}
peerDependencies:
react: ^18.3.1
react@18.3.1:
resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==}
engines: {node: '>=0.10.0'}
resolve-from@4.0.0:
resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
engines: {node: '>=4'}
reusify@1.0.4:
resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==}
engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
rollup@4.24.0:
resolution: {integrity: sha512-DOmrlGSXNk1DM0ljiQA+i+o0rSLhtii1je5wgk60j49d1jHT5YYttBv1iWOnYSTG+fZZESUOSNiAl89SIet+Cg==}
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
hasBin: true
run-parallel@1.2.0:
resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
scheduler@0.23.2:
resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==}
semver@7.6.2:
resolution: {integrity: sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==}
engines: {node: '>=10'}
hasBin: true
shallowequal@1.1.0:
resolution: {integrity: sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==}
shebang-command@2.0.0:
resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
engines: {node: '>=8'}
shebang-regex@3.0.0:
resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}
engines: {node: '>=8'}
source-map-js@1.2.0:
resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==}
engines: {node: '>=0.10.0'}
source-map-js@1.2.1:
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
engines: {node: '>=0.10.0'}
strip-json-comments@3.1.1:
resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
engines: {node: '>=8'}
styled-components@6.1.11:
resolution: {integrity: sha512-Ui0jXPzbp1phYij90h12ksljKGqF8ncGx+pjrNPsSPhbUUjWT2tD1FwGo2LF6USCnbrsIhNngDfodhxbegfEOA==}
engines: {node: '>= 16'}
peerDependencies:
react: '>= 16.8.0'
react-dom: '>= 16.8.0'
stylis@4.3.2:
resolution: {integrity: sha512-bhtUjWd/z6ltJiQwg0dUfxEJ+W+jdqQd8TbWLWyeIJHlnsqmGLRFFd8e5mA0AZi/zx90smXRlN66YMTcaSFifg==}
supports-color@7.2.0:
resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
engines: {node: '>=8'}
text-table@0.2.0:
resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==}
to-regex-range@5.0.1:
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
engines: {node: '>=8.0'}
ts-api-utils@1.3.0:
resolution: {integrity: sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==}
engines: {node: '>=16'}
peerDependencies:
typescript: '>=4.2.0'
tslib@2.6.2:
resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==}
type-check@0.4.0:
resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}
engines: {node: '>= 0.8.0'}
typescript@5.6.3:
resolution: {integrity: sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==}
engines: {node: '>=14.17'}
hasBin: true
uri-js@4.4.1:
resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
vite@5.4.8:
resolution: {integrity: sha512-FqrItQ4DT1NC4zCUqMB4c4AZORMKIa0m8/URVCZ77OZ/QSNeJ54bU1vrFADbDsuwfIPcgknRkmqakQcgnL4GiQ==}
engines: {node: ^18.0.0 || >=20.0.0}
hasBin: true
peerDependencies:
'@types/node': ^18.0.0 || >=20.0.0
less: '*'
lightningcss: ^1.21.0
sass: '*'
sass-embedded: '*'
stylus: '*'
sugarss: '*'
terser: ^5.4.0
peerDependenciesMeta:
'@types/node':
optional: true
less:
optional: true
lightningcss:
optional: true
sass:
optional: true
sass-embedded:
optional: true
stylus:
optional: true
sugarss:
optional: true
terser:
optional: true
which@2.0.2:
resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
engines: {node: '>= 8'}
hasBin: true
word-wrap@1.2.5:
resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==}
engines: {node: '>=0.10.0'}
yocto-queue@0.1.0:
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
engines: {node: '>=10'}
snapshots:
'@emotion/is-prop-valid@1.2.2':
dependencies:
'@emotion/memoize': 0.8.1
'@emotion/memoize@0.8.1': {}
'@emotion/unitless@0.8.1': {}
'@esbuild/aix-ppc64@0.21.5':
optional: true
'@esbuild/android-arm64@0.21.5':
optional: true
'@esbuild/android-arm@0.21.5':
optional: true
'@esbuild/android-x64@0.21.5':
optional: true
'@esbuild/darwin-arm64@0.21.5':
optional: true
'@esbuild/darwin-x64@0.21.5':
optional: true
'@esbuild/freebsd-arm64@0.21.5':
optional: true
'@esbuild/freebsd-x64@0.21.5':
optional: true
'@esbuild/linux-arm64@0.21.5':
optional: true
'@esbuild/linux-arm@0.21.5':
optional: true
'@esbuild/linux-ia32@0.21.5':
optional: true
'@esbuild/linux-loong64@0.21.5':
optional: true
'@esbuild/linux-mips64el@0.21.5':
optional: true
'@esbuild/linux-ppc64@0.21.5':
optional: true
'@esbuild/linux-riscv64@0.21.5':
optional: true
'@esbuild/linux-s390x@0.21.5':
optional: true
'@esbuild/linux-x64@0.21.5':
optional: true
'@esbuild/netbsd-x64@0.21.5':
optional: true
'@esbuild/openbsd-x64@0.21.5':
optional: true
'@esbuild/sunos-x64@0.21.5':
optional: true
'@esbuild/win32-arm64@0.21.5':
optional: true
'@esbuild/win32-ia32@0.21.5':
optional: true
'@esbuild/win32-x64@0.21.5':
optional: true
'@eslint-community/eslint-utils@4.4.0(eslint@9.12.0)':
dependencies:
eslint: 9.12.0
eslint-visitor-keys: 3.4.3
'@eslint-community/regexpp@4.11.0': {}
'@eslint/config-array@0.18.0':
dependencies:
'@eslint/object-schema': 2.1.4
debug: 4.3.5
minimatch: 3.1.2
transitivePeerDependencies:
- supports-color
'@eslint/core@0.6.0': {}
'@eslint/eslintrc@3.1.0':
dependencies:
ajv: 6.12.6
debug: 4.3.5
espree: 10.2.0
globals: 14.0.0
ignore: 5.3.1
import-fresh: 3.3.0
js-yaml: 4.1.0
minimatch: 3.1.2
strip-json-comments: 3.1.1
transitivePeerDependencies:
- supports-color
'@eslint/js@9.12.0': {}
'@eslint/object-schema@2.1.4': {}
'@eslint/plugin-kit@0.2.0':
dependencies:
levn: 0.4.1
'@humanfs/core@0.19.0': {}
'@humanfs/node@0.16.5':
dependencies:
'@humanfs/core': 0.19.0
'@humanwhocodes/retry': 0.3.1
'@humanwhocodes/module-importer@1.0.1': {}
'@humanwhocodes/retry@0.3.1': {}
'@nodelib/fs.scandir@2.1.5':
dependencies:
'@nodelib/fs.stat': 2.0.5
run-parallel: 1.2.0
'@nodelib/fs.stat@2.0.5': {}
'@nodelib/fs.walk@1.2.8':
dependencies:
'@nodelib/fs.scandir': 2.1.5
fastq: 1.17.1
'@rollup/rollup-android-arm-eabi@4.24.0':
optional: true
'@rollup/rollup-android-arm64@4.24.0':
optional: true
'@rollup/rollup-darwin-arm64@4.24.0':
optional: true
'@rollup/rollup-darwin-x64@4.24.0':
optional: true
'@rollup/rollup-linux-arm-gnueabihf@4.24.0':
optional: true
'@rollup/rollup-linux-arm-musleabihf@4.24.0':
optional: true
'@rollup/rollup-linux-arm64-gnu@4.24.0':
optional: true
'@rollup/rollup-linux-arm64-musl@4.24.0':
optional: true
'@rollup/rollup-linux-powerpc64le-gnu@4.24.0':
optional: true
'@rollup/rollup-linux-riscv64-gnu@4.24.0':
optional: true
'@rollup/rollup-linux-s390x-gnu@4.24.0':
optional: true
'@rollup/rollup-linux-x64-gnu@4.24.0':
optional: true
'@rollup/rollup-linux-x64-musl@4.24.0':
optional: true
'@rollup/rollup-win32-arm64-msvc@4.24.0':
optional: true
'@rollup/rollup-win32-ia32-msvc@4.24.0':
optional: true
'@rollup/rollup-win32-x64-msvc@4.24.0':
optional: true
'@swc/core-darwin-arm64@1.7.35':
optional: true
'@swc/core-darwin-x64@1.7.35':
optional: true
'@swc/core-linux-arm-gnueabihf@1.7.35':
optional: true
'@swc/core-linux-arm64-gnu@1.7.35':
optional: true
'@swc/core-linux-arm64-musl@1.7.35':
optional: true
'@swc/core-linux-x64-gnu@1.7.35':
optional: true
'@swc/core-linux-x64-musl@1.7.35':
optional: true
'@swc/core-win32-arm64-msvc@1.7.35':
optional: true
'@swc/core-win32-ia32-msvc@1.7.35':
optional: true
'@swc/core-win32-x64-msvc@1.7.35':
optional: true
'@swc/core@1.7.35':
dependencies:
'@swc/counter': 0.1.3
'@swc/types': 0.1.13
optionalDependencies:
'@swc/core-darwin-arm64': 1.7.35
'@swc/core-darwin-x64': 1.7.35
'@swc/core-linux-arm-gnueabihf': 1.7.35
'@swc/core-linux-arm64-gnu': 1.7.35
'@swc/core-linux-arm64-musl': 1.7.35
'@swc/core-linux-x64-gnu': 1.7.35
'@swc/core-linux-x64-musl': 1.7.35
'@swc/core-win32-arm64-msvc': 1.7.35
'@swc/core-win32-ia32-msvc': 1.7.35
'@swc/core-win32-x64-msvc': 1.7.35
'@swc/counter@0.1.3': {}
'@swc/types@0.1.13':
dependencies:
'@swc/counter': 0.1.3
'@types/estree@1.0.6': {}
'@types/json-schema@7.0.15': {}
'@types/prop-types@15.7.12': {}
'@types/react-dom@18.3.0':
dependencies:
'@types/react': 18.3.3
'@types/react@18.3.3':
dependencies:
'@types/prop-types': 15.7.12
csstype: 3.1.3
'@types/stylis@4.2.5': {}
'@typescript-eslint/eslint-plugin@8.8.1(@typescript-eslint/parser@8.8.1(eslint@9.12.0)(typescript@5.6.3))(eslint@9.12.0)(typescript@5.6.3)':
dependencies:
'@eslint-community/regexpp': 4.11.0
'@typescript-eslint/parser': 8.8.1(eslint@9.12.0)(typescript@5.6.3)
'@typescript-eslint/scope-manager': 8.8.1
'@typescript-eslint/type-utils': 8.8.1(eslint@9.12.0)(typescript@5.6.3)
'@typescript-eslint/utils': 8.8.1(eslint@9.12.0)(typescript@5.6.3)
'@typescript-eslint/visitor-keys': 8.8.1
eslint: 9.12.0
graphemer: 1.4.0
ignore: 5.3.1
natural-compare: 1.4.0
ts-api-utils: 1.3.0(typescript@5.6.3)
optionalDependencies:
typescript: 5.6.3
transitivePeerDependencies:
- supports-color
'@typescript-eslint/parser@8.8.1(eslint@9.12.0)(typescript@5.6.3)':
dependencies:
'@typescript-eslint/scope-manager': 8.8.1
'@typescript-eslint/types': 8.8.1
'@typescript-eslint/typescript-estree': 8.8.1(typescript@5.6.3)
'@typescript-eslint/visitor-keys': 8.8.1
debug: 4.3.5
eslint: 9.12.0
optionalDependencies:
typescript: 5.6.3
transitivePeerDependencies:
- supports-color
'@typescript-eslint/scope-manager@8.8.1':
dependencies:
'@typescript-eslint/types': 8.8.1
'@typescript-eslint/visitor-keys': 8.8.1
'@typescript-eslint/type-utils@8.8.1(eslint@9.12.0)(typescript@5.6.3)':
dependencies:
'@typescript-eslint/typescript-estree': 8.8.1(typescript@5.6.3)
'@typescript-eslint/utils': 8.8.1(eslint@9.12.0)(typescript@5.6.3)
debug: 4.3.5
ts-api-utils: 1.3.0(typescript@5.6.3)
optionalDependencies:
typescript: 5.6.3
transitivePeerDependencies:
- eslint
- supports-color
'@typescript-eslint/types@8.8.1': {}
'@typescript-eslint/typescript-estree@8.8.1(typescript@5.6.3)':
dependencies:
'@typescript-eslint/types': 8.8.1
'@typescript-eslint/visitor-keys': 8.8.1
debug: 4.3.5
fast-glob: 3.3.2
is-glob: 4.0.3
minimatch: 9.0.5
semver: 7.6.2
ts-api-utils: 1.3.0(typescript@5.6.3)
optionalDependencies:
typescript: 5.6.3
transitivePeerDependencies:
- supports-color
'@typescript-eslint/utils@8.8.1(eslint@9.12.0)(typescript@5.6.3)':
dependencies:
'@eslint-community/eslint-utils': 4.4.0(eslint@9.12.0)
'@typescript-eslint/scope-manager': 8.8.1
'@typescript-eslint/types': 8.8.1
'@typescript-eslint/typescript-estree': 8.8.1(typescript@5.6.3)
eslint: 9.12.0
transitivePeerDependencies:
- supports-color
- typescript
'@typescript-eslint/visitor-keys@8.8.1':
dependencies:
'@typescript-eslint/types': 8.8.1
eslint-visitor-keys: 3.4.3
'@vitejs/plugin-react-swc@3.7.1(vite@5.4.8)':
dependencies:
'@swc/core': 1.7.35
vite: 5.4.8
transitivePeerDependencies:
- '@swc/helpers'
acorn-jsx@5.3.2(acorn@8.12.1):
dependencies:
acorn: 8.12.1
acorn@8.12.1: {}
ajv@6.12.6:
dependencies:
fast-deep-equal: 3.1.3
fast-json-stable-stringify: 2.1.0
json-schema-traverse: 0.4.1
uri-js: 4.4.1
ansi-styles@4.3.0:
dependencies:
color-convert: 2.0.1
argparse@2.0.1: {}
balanced-match@1.0.2: {}
brace-expansion@1.1.11:
dependencies:
balanced-match: 1.0.2
concat-map: 0.0.1
brace-expansion@2.0.1:
dependencies:
balanced-match: 1.0.2
braces@3.0.3:
dependencies:
fill-range: 7.1.1
callsites@3.1.0: {}
camelize@1.0.1: {}
chalk@4.1.2:
dependencies:
ansi-styles: 4.3.0
supports-color: 7.2.0
color-convert@2.0.1:
dependencies:
color-name: 1.1.4
color-name@1.1.4: {}
concat-map@0.0.1: {}
cross-spawn@7.0.3:
dependencies:
path-key: 3.1.1
shebang-command: 2.0.0
which: 2.0.2
css-color-keywords@1.0.0: {}
css-to-react-native@3.2.0:
dependencies:
camelize: 1.0.1
css-color-keywords: 1.0.0
postcss-value-parser: 4.2.0
csstype@3.1.3: {}
debug@4.3.5:
dependencies:
ms: 2.1.2
deep-is@0.1.4: {}
esbuild@0.21.5:
optionalDependencies:
'@esbuild/aix-ppc64': 0.21.5
'@esbuild/android-arm': 0.21.5
'@esbuild/android-arm64': 0.21.5
'@esbuild/android-x64': 0.21.5
'@esbuild/darwin-arm64': 0.21.5
'@esbuild/darwin-x64': 0.21.5
'@esbuild/freebsd-arm64': 0.21.5
'@esbuild/freebsd-x64': 0.21.5
'@esbuild/linux-arm': 0.21.5
'@esbuild/linux-arm64': 0.21.5
'@esbuild/linux-ia32': 0.21.5
'@esbuild/linux-loong64': 0.21.5
'@esbuild/linux-mips64el': 0.21.5
'@esbuild/linux-ppc64': 0.21.5
'@esbuild/linux-riscv64': 0.21.5
'@esbuild/linux-s390x': 0.21.5
'@esbuild/linux-x64': 0.21.5
'@esbuild/netbsd-x64': 0.21.5
'@esbuild/openbsd-x64': 0.21.5
'@esbuild/sunos-x64': 0.21.5
'@esbuild/win32-arm64': 0.21.5
'@esbuild/win32-ia32': 0.21.5
'@esbuild/win32-x64': 0.21.5
escape-string-regexp@4.0.0: {}
eslint-plugin-react-hooks@5.0.0(eslint@9.12.0):
dependencies:
eslint: 9.12.0
eslint-plugin-react-refresh@0.4.12(eslint@9.12.0):
dependencies:
eslint: 9.12.0
eslint-scope@8.1.0:
dependencies:
esrecurse: 4.3.0
estraverse: 5.3.0
eslint-visitor-keys@3.4.3: {}
eslint-visitor-keys@4.1.0: {}
eslint@9.12.0:
dependencies:
'@eslint-community/eslint-utils': 4.4.0(eslint@9.12.0)
'@eslint-community/regexpp': 4.11.0
'@eslint/config-array': 0.18.0
'@eslint/core': 0.6.0
'@eslint/eslintrc': 3.1.0
'@eslint/js': 9.12.0
'@eslint/plugin-kit': 0.2.0
'@humanfs/node': 0.16.5
'@humanwhocodes/module-importer': 1.0.1
'@humanwhocodes/retry': 0.3.1
'@types/estree': 1.0.6
'@types/json-schema': 7.0.15
ajv: 6.12.6
chalk: 4.1.2
cross-spawn: 7.0.3
debug: 4.3.5
escape-string-regexp: 4.0.0
eslint-scope: 8.1.0
eslint-visitor-keys: 4.1.0
espree: 10.2.0
esquery: 1.6.0
esutils: 2.0.3
fast-deep-equal: 3.1.3
file-entry-cache: 8.0.0
find-up: 5.0.0
glob-parent: 6.0.2
ignore: 5.3.1
imurmurhash: 0.1.4
is-glob: 4.0.3
json-stable-stringify-without-jsonify: 1.0.1
lodash.merge: 4.6.2
minimatch: 3.1.2
natural-compare: 1.4.0
optionator: 0.9.4
text-table: 0.2.0
transitivePeerDependencies:
- supports-color
espree@10.2.0:
dependencies:
acorn: 8.12.1
acorn-jsx: 5.3.2(acorn@8.12.1)
eslint-visitor-keys: 4.1.0
esquery@1.6.0:
dependencies:
estraverse: 5.3.0
esrecurse@4.3.0:
dependencies:
estraverse: 5.3.0
estraverse@5.3.0: {}
esutils@2.0.3: {}
fast-deep-equal@3.1.3: {}
fast-glob@3.3.2:
dependencies:
'@nodelib/fs.stat': 2.0.5
'@nodelib/fs.walk': 1.2.8
glob-parent: 5.1.2
merge2: 1.4.1
micromatch: 4.0.7
fast-json-stable-stringify@2.1.0: {}
fast-levenshtein@2.0.6: {}
fastq@1.17.1:
dependencies:
reusify: 1.0.4
file-entry-cache@8.0.0:
dependencies:
flat-cache: 4.0.1
fill-range@7.1.1:
dependencies:
to-regex-range: 5.0.1
find-up@5.0.0:
dependencies:
locate-path: 6.0.0
path-exists: 4.0.0
flat-cache@4.0.1:
dependencies:
flatted: 3.3.1
keyv: 4.5.4
flatted@3.3.1: {}
fsevents@2.3.3:
optional: true
glob-parent@5.1.2:
dependencies:
is-glob: 4.0.3
glob-parent@6.0.2:
dependencies:
is-glob: 4.0.3
globals@14.0.0: {}
graphemer@1.4.0: {}
has-flag@4.0.0: {}
ignore@5.3.1: {}
import-fresh@3.3.0:
dependencies:
parent-module: 1.0.1
resolve-from: 4.0.0
imurmurhash@0.1.4: {}
is-extglob@2.1.1: {}
is-glob@4.0.3:
dependencies:
is-extglob: 2.1.1
is-number@7.0.0: {}
isexe@2.0.0: {}
js-tokens@4.0.0: {}
js-yaml@4.1.0:
dependencies:
argparse: 2.0.1
json-buffer@3.0.1: {}
json-schema-traverse@0.4.1: {}
json-stable-stringify-without-jsonify@1.0.1: {}
keyv@4.5.4:
dependencies:
json-buffer: 3.0.1
levn@0.4.1:
dependencies:
prelude-ls: 1.2.1
type-check: 0.4.0
locate-path@6.0.0:
dependencies:
p-locate: 5.0.0
lodash.merge@4.6.2: {}
loose-envify@1.4.0:
dependencies:
js-tokens: 4.0.0
merge2@1.4.1: {}
micromatch@4.0.7:
dependencies:
braces: 3.0.3
picomatch: 2.3.1
minimatch@3.1.2:
dependencies:
brace-expansion: 1.1.11
minimatch@9.0.5:
dependencies:
brace-expansion: 2.0.1
ms@2.1.2: {}
nanoid@3.3.7: {}
natural-compare@1.4.0: {}
optionator@0.9.4:
dependencies:
deep-is: 0.1.4
fast-levenshtein: 2.0.6
levn: 0.4.1
prelude-ls: 1.2.1
type-check: 0.4.0
word-wrap: 1.2.5
p-limit@3.1.0:
dependencies:
yocto-queue: 0.1.0
p-locate@5.0.0:
dependencies:
p-limit: 3.1.0
parent-module@1.0.1:
dependencies:
callsites: 3.1.0
path-exists@4.0.0: {}
path-key@3.1.1: {}
picocolors@1.0.1: {}
picocolors@1.1.0: {}
picomatch@2.3.1: {}
postcss-value-parser@4.2.0: {}
postcss@8.4.38:
dependencies:
nanoid: 3.3.7
picocolors: 1.0.1
source-map-js: 1.2.0
postcss@8.4.47:
dependencies:
nanoid: 3.3.7
picocolors: 1.1.0
source-map-js: 1.2.1
prelude-ls@1.2.1: {}
punycode@2.3.1: {}
queue-microtask@1.2.3: {}
react-dom@18.3.1(react@18.3.1):
dependencies:
loose-envify: 1.4.0
react: 18.3.1
scheduler: 0.23.2
react@18.3.1:
dependencies:
loose-envify: 1.4.0
resolve-from@4.0.0: {}
reusify@1.0.4: {}
rollup@4.24.0:
dependencies:
'@types/estree': 1.0.6
optionalDependencies:
'@rollup/rollup-android-arm-eabi': 4.24.0
'@rollup/rollup-android-arm64': 4.24.0
'@rollup/rollup-darwin-arm64': 4.24.0
'@rollup/rollup-darwin-x64': 4.24.0
'@rollup/rollup-linux-arm-gnueabihf': 4.24.0
'@rollup/rollup-linux-arm-musleabihf': 4.24.0
'@rollup/rollup-linux-arm64-gnu': 4.24.0
'@rollup/rollup-linux-arm64-musl': 4.24.0
'@rollup/rollup-linux-powerpc64le-gnu': 4.24.0
'@rollup/rollup-linux-riscv64-gnu': 4.24.0
'@rollup/rollup-linux-s390x-gnu': 4.24.0
'@rollup/rollup-linux-x64-gnu': 4.24.0
'@rollup/rollup-linux-x64-musl': 4.24.0
'@rollup/rollup-win32-arm64-msvc': 4.24.0
'@rollup/rollup-win32-ia32-msvc': 4.24.0
'@rollup/rollup-win32-x64-msvc': 4.24.0
fsevents: 2.3.3
run-parallel@1.2.0:
dependencies:
queue-microtask: 1.2.3
scheduler@0.23.2:
dependencies:
loose-envify: 1.4.0
semver@7.6.2: {}
shallowequal@1.1.0: {}
shebang-command@2.0.0:
dependencies:
shebang-regex: 3.0.0
shebang-regex@3.0.0: {}
source-map-js@1.2.0: {}
source-map-js@1.2.1: {}
strip-json-comments@3.1.1: {}
styled-components@6.1.11(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
dependencies:
'@emotion/is-prop-valid': 1.2.2
'@emotion/unitless': 0.8.1
'@types/stylis': 4.2.5
css-to-react-native: 3.2.0
csstype: 3.1.3
postcss: 8.4.38
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
shallowequal: 1.1.0
stylis: 4.3.2
tslib: 2.6.2
stylis@4.3.2: {}
supports-color@7.2.0:
dependencies:
has-flag: 4.0.0
text-table@0.2.0: {}
to-regex-range@5.0.1:
dependencies:
is-number: 7.0.0
ts-api-utils@1.3.0(typescript@5.6.3):
dependencies:
typescript: 5.6.3
tslib@2.6.2: {}
type-check@0.4.0:
dependencies:
prelude-ls: 1.2.1
typescript@5.6.3: {}
uri-js@4.4.1:
dependencies:
punycode: 2.3.1
vite@5.4.8:
dependencies:
esbuild: 0.21.5
postcss: 8.4.47
rollup: 4.24.0
optionalDependencies:
fsevents: 2.3.3
which@2.0.2:
dependencies:
isexe: 2.0.0
word-wrap@1.2.5: {}
yocto-queue@0.1.0: {}
07070100000042000081A4000000000000000000000001680FB1F800000000000000000000000000000000000000000000002600000000kube-score-1.20.0/pnpm-workspace.yaml07070100000043000041ED000000000000000000000002680FB1F800000000000000000000000000000000000000000000001B00000000kube-score-1.20.0/renderer07070100000044000041ED000000000000000000000002680FB1F800000000000000000000000000000000000000000000001E00000000kube-score-1.20.0/renderer/ci07070100000045000081A4000000000000000000000001680FB1F80000051F000000000000000000000000000000000000002400000000kube-score-1.20.0/renderer/ci/ci.go// Package ci is currently considered to be in alpha status, and is not covered
// by the API stability guarantees
package ci
import (
"bytes"
"fmt"
"io"
"sort"
"github.com/zegl/kube-score/scorecard"
)
// "Machine" / CI friendly output
func CI(scoreCard *scorecard.Scorecard) io.Reader {
w := bytes.NewBufferString("")
// Print the items sorted by scorecard key
var keys []string
for k := range *scoreCard {
keys = append(keys, k)
}
sort.Strings(keys)
for _, key := range keys {
scoredObject := (*scoreCard)[key]
for _, card := range scoredObject.Checks {
if len(card.Comments) == 0 {
if card.Skipped {
fmt.Fprintf(w, "[SKIPPED] %s\n",
scoredObject.HumanFriendlyRef(),
)
} else {
fmt.Fprintf(w, "[%s] %s\n",
card.Grade.String(),
scoredObject.HumanFriendlyRef(),
)
}
}
for _, comment := range card.Comments {
message := comment.Summary
if comment.Path != "" {
message = "(" + comment.Path + ") " + comment.Summary
}
if card.Skipped {
fmt.Fprintf(w, "[SKIPPED] %s: %s\n",
scoredObject.HumanFriendlyRef(),
message,
)
} else {
fmt.Fprintf(w, "[%s] %s: %s\n",
card.Grade.String(),
scoredObject.HumanFriendlyRef(),
message,
)
}
}
}
}
return w
}
07070100000046000081A4000000000000000000000001680FB1F800000907000000000000000000000000000000000000002900000000kube-score-1.20.0/renderer/ci/ci_test.gopackage ci
import (
"io"
"testing"
"github.com/stretchr/testify/assert"
"github.com/zegl/kube-score/domain"
"github.com/zegl/kube-score/scorecard"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func getTestCard() *scorecard.Scorecard {
checks := []scorecard.TestScore{
{
Check: domain.Check{
Name: "test-warning-two-comments",
},
Grade: scorecard.GradeWarning,
Comments: []scorecard.TestScoreComment{
{
Path: "a",
Summary: "summary",
Description: "description",
},
{
// No path
Summary: "summary",
Description: "description",
},
},
},
{
Check: domain.Check{
Name: "test-ok-comment",
},
Grade: scorecard.GradeAllOK,
Comments: []scorecard.TestScoreComment{
{
Path: "a",
Summary: "summary",
Description: "description",
},
},
},
{
Check: domain.Check{
Name: "test-skipped-comment",
},
Skipped: true,
Comments: []scorecard.TestScoreComment{
{
Path: "a",
Summary: "skipped sum",
Description: "skipped description",
},
},
},
{
Check: domain.Check{
Name: "test-skipped-no-comment",
},
Skipped: true,
},
}
return &scorecard.Scorecard{
"a": &scorecard.ScoredObject{
TypeMeta: v1.TypeMeta{
Kind: "Testing",
APIVersion: "v1",
},
ObjectMeta: v1.ObjectMeta{
Name: "foo",
Namespace: "foofoo",
},
Checks: checks,
},
// No namespace
"b": &scorecard.ScoredObject{
TypeMeta: v1.TypeMeta{
Kind: "Testing",
APIVersion: "v1",
},
ObjectMeta: v1.ObjectMeta{
Name: "bar-no-namespace",
},
Checks: checks,
},
}
}
func TestCiOutput(t *testing.T) {
t.Parallel()
// Defaults
r := CI(getTestCard())
all, err := io.ReadAll(r)
assert.Nil(t, err)
assert.Equal(t, `[WARNING] foo/foofoo v1/Testing: (a) summary
[WARNING] foo/foofoo v1/Testing: summary
[OK] foo/foofoo v1/Testing: (a) summary
[SKIPPED] foo/foofoo v1/Testing: (a) skipped sum
[SKIPPED] foo/foofoo v1/Testing
[WARNING] bar-no-namespace v1/Testing: (a) summary
[WARNING] bar-no-namespace v1/Testing: summary
[OK] bar-no-namespace v1/Testing: (a) summary
[SKIPPED] bar-no-namespace v1/Testing: (a) skipped sum
[SKIPPED] bar-no-namespace v1/Testing
`, string(all))
}
07070100000047000041ED000000000000000000000002680FB1F800000000000000000000000000000000000000000000002100000000kube-score-1.20.0/renderer/human07070100000048000081A4000000000000000000000001680FB1F800000F8E000000000000000000000000000000000000002A00000000kube-score-1.20.0/renderer/human/human.go// Package human is currently considered to be in alpha status, and is not covered
// by the API stability guarantees
package human
import (
"bytes"
"fmt"
"io"
"sort"
"strings"
"github.com/eidolon/wordwrap"
"github.com/fatih/color"
"github.com/zegl/kube-score/scorecard"
)
func Human(scoreCard *scorecard.Scorecard, verboseOutput int, termWidth int, useColors bool) (io.Reader, error) {
// Print the items sorted by scorecard key
var keys []string
for k := range *scoreCard {
keys = append(keys, k)
}
sort.Strings(keys)
// Override usage of colors to our own preference
color.NoColor = !useColors
w := bytes.NewBufferString("")
for _, key := range keys {
scoredObject := (*scoreCard)[key]
// Headers for each object
var writtenHeaderChars int
writtenHeaderChars, _ = color.New(color.FgMagenta).Fprintf(w, "%s/%s %s", scoredObject.TypeMeta.APIVersion, scoredObject.TypeMeta.Kind, scoredObject.ObjectMeta.Name)
if scoredObject.ObjectMeta.Namespace != "" {
written2, _ := color.New(color.FgMagenta).Fprintf(w, " in %s", scoredObject.ObjectMeta.Namespace)
writtenHeaderChars += written2
}
// Adjust to termsize
_, err := fmt.Fprint(w, safeRepeat(" ", min(80, termWidth)-writtenHeaderChars-2))
if err != nil {
return nil, fmt.Errorf("failed to write terminal padding: %w", err)
}
switch {
case scoredObject.AnyBelowOrEqualToGrade(scorecard.GradeCritical):
_, err = fmt.Fprintf(w, "💥\n")
case scoredObject.AnyBelowOrEqualToGrade(scorecard.GradeWarning):
_, err = fmt.Fprintf(w, "🤔\n")
default:
_, err = fmt.Fprintf(w, "✅\n")
}
if err != nil {
return nil, fmt.Errorf("failed to write: %w", err)
}
// Display file name if the object has any warnings or criticals
if scoredObject.AnyBelowOrEqualToGrade(scorecard.GradeWarning) {
if scoredObject.FileLocation.Name != "" {
_, _ = color.New(color.FgHiBlack).Fprintf(w, " path=%s\n", scoredObject.FileLocation.Name)
}
}
for _, card := range scoredObject.Checks {
r := outputHumanStep(card, verboseOutput, termWidth)
if _, err := io.Copy(w, r); err != nil {
return nil, fmt.Errorf("failed to copy output: %w", err)
}
}
}
return w, nil
}
func outputHumanStep(card scorecard.TestScore, verboseOutput int, termWidth int) io.Reader {
w := bytes.NewBufferString("")
// Only print skipped items if verbosity is at least 2
if card.Skipped && verboseOutput < 2 {
return w
}
var col color.Attribute
switch {
case card.Skipped || card.Grade >= scorecard.GradeAllOK:
// Higher than or equal to --threshold-ok
col = color.FgGreen
// If verbose output is disabled, skip OK items in the output
if verboseOutput == 0 {
return w
}
case card.Grade >= scorecard.GradeWarning:
// Higher than or equal to --threshold-warning
col = color.FgYellow
default:
// All lower than both --threshold-ok and --threshold-warning are critical
col = color.FgRed
}
if card.Skipped {
color.New(col).Fprintf(w, " [SKIPPED] %s\n", card.Check.Name)
} else {
color.New(col).Fprintf(w, " [%s] %s\n", card.Grade.String(), card.Check.Name)
}
for _, comment := range card.Comments {
fmt.Fprintf(w, " · ")
if len(comment.Path) > 0 {
fmt.Fprintf(w, "%s -> ", comment.Path)
}
fmt.Fprint(w, comment.Summary)
if len(comment.Description) > 0 {
wrapWidth := termWidth - 12
if wrapWidth < 40 {
wrapWidth = 40
}
wrapper := wordwrap.Wrapper(wrapWidth, false)
wrapped := wrapper(comment.Description)
fmt.Fprintln(w)
fmt.Fprint(w, wordwrap.Indent(wrapped, strings.Repeat(" ", 12), false))
}
if len(comment.DocumentationURL) > 0 {
fmt.Fprintln(w)
fmt.Fprintf(w, "%sMore information: %s", strings.Repeat(" ", 12), comment.DocumentationURL)
}
fmt.Fprintln(w)
}
return w
}
func safeRepeat(s string, count int) string {
if count < 0 {
return ""
}
return strings.Repeat(s, count)
}
func min(a, b int) int {
if a < b {
return a
}
return b
}
07070100000049000081A4000000000000000000000001680FB1F800003214000000000000000000000000000000000000003000000000kube-score-1.20.0/renderer/human/output_test.gopackage human
import (
"io"
"testing"
"github.com/stretchr/testify/assert"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/zegl/kube-score/domain"
"github.com/zegl/kube-score/scorecard"
)
func getTestCard() *scorecard.Scorecard {
checks := []scorecard.TestScore{
{
Check: domain.Check{
Name: "test-warning-two-comments",
},
Grade: scorecard.GradeWarning,
Comments: []scorecard.TestScoreComment{
{
Path: "a",
Summary: "summary",
Description: "description",
},
{
// No path
Summary: "summary",
Description: "description",
DocumentationURL: "https://kube-score.com/whatever",
},
},
},
{
Check: domain.Check{
Name: "test-ok-comment",
},
Grade: scorecard.GradeAllOK,
Comments: []scorecard.TestScoreComment{
{
Path: "a",
Summary: "summary",
Description: "description",
},
},
},
{
Check: domain.Check{
Name: "test-skipped-comment",
},
Skipped: true,
Comments: []scorecard.TestScoreComment{
{
Path: "a",
Summary: "skipped sum",
Description: "skipped description",
},
},
},
{
Check: domain.Check{
Name: "test-skipped-no-comment",
},
Skipped: true,
},
}
return &scorecard.Scorecard{
"a": &scorecard.ScoredObject{
TypeMeta: v1.TypeMeta{
Kind: "Testing",
APIVersion: "v1",
},
ObjectMeta: v1.ObjectMeta{
Name: "foo",
Namespace: "foofoo",
},
Checks: checks,
},
// No namespace
"b": &scorecard.ScoredObject{
TypeMeta: v1.TypeMeta{
Kind: "Testing",
APIVersion: "v1",
},
ObjectMeta: v1.ObjectMeta{
Name: "bar-no-namespace",
},
Checks: checks,
},
}
}
func TestHumanOutputDefault(t *testing.T) {
t.Parallel()
r, err := Human(getTestCard(), 0, 100, false)
assert.Nil(t, err)
all, err := io.ReadAll(r)
assert.Nil(t, err)
assert.Equal(t, `v1/Testing foo in foofoo 🤔
[WARNING] test-warning-two-comments
· a -> summary
description
· summary
description
More information: https://kube-score.com/whatever
v1/Testing bar-no-namespace 🤔
[WARNING] test-warning-two-comments
· a -> summary
description
· summary
description
More information: https://kube-score.com/whatever
`, string(all))
}
func TestHumanOutputVerbose1(t *testing.T) {
t.Parallel()
r, err := Human(getTestCard(), 1, 100, false)
assert.Nil(t, err)
all, err := io.ReadAll(r)
assert.Nil(t, err)
assert.Equal(t, `v1/Testing foo in foofoo 🤔
[WARNING] test-warning-two-comments
· a -> summary
description
· summary
description
More information: https://kube-score.com/whatever
[OK] test-ok-comment
· a -> summary
description
v1/Testing bar-no-namespace 🤔
[WARNING] test-warning-two-comments
· a -> summary
description
· summary
description
More information: https://kube-score.com/whatever
[OK] test-ok-comment
· a -> summary
description
`, string(all))
}
func TestHumanOutputVerbose2(t *testing.T) {
t.Parallel()
r, err := Human(getTestCard(), 2, 100, false)
assert.Nil(t, err)
all, err := io.ReadAll(r)
assert.Nil(t, err)
assert.Equal(t, `v1/Testing foo in foofoo 🤔
[WARNING] test-warning-two-comments
· a -> summary
description
· summary
description
More information: https://kube-score.com/whatever
[OK] test-ok-comment
· a -> summary
description
[SKIPPED] test-skipped-comment
· a -> skipped sum
skipped description
[SKIPPED] test-skipped-no-comment
v1/Testing bar-no-namespace 🤔
[WARNING] test-warning-two-comments
· a -> summary
description
· summary
description
More information: https://kube-score.com/whatever
[OK] test-ok-comment
· a -> summary
description
[SKIPPED] test-skipped-comment
· a -> skipped sum
skipped description
[SKIPPED] test-skipped-no-comment
`, string(all))
}
func getTestCardAllOK() *scorecard.Scorecard {
checks := []scorecard.TestScore{
{
Check: domain.Check{
Name: "test-warning-two-comments",
},
Grade: scorecard.GradeAllOK,
Comments: []scorecard.TestScoreComment{
{
Path: "a",
Summary: "summary",
Description: "description",
},
{
// No path
Summary: "summary",
Description: "description",
},
},
},
{
Check: domain.Check{
Name: "test-ok-comment",
},
Grade: scorecard.GradeAllOK,
Comments: []scorecard.TestScoreComment{
{
Path: "a",
Summary: "summary",
Description: "description",
},
},
},
{
Check: domain.Check{
Name: "test-skipped-comment",
},
Skipped: true,
Comments: []scorecard.TestScoreComment{
{
Path: "a",
Summary: "skipped sum",
Description: "skipped description",
},
},
},
{
Check: domain.Check{
Name: "test-skipped-no-comment",
},
Skipped: true,
},
}
return &scorecard.Scorecard{
"a": &scorecard.ScoredObject{
TypeMeta: v1.TypeMeta{
Kind: "Testing",
APIVersion: "v1",
},
ObjectMeta: v1.ObjectMeta{
Name: "foo",
Namespace: "foofoo",
},
Checks: checks,
},
// No namespace
"b": &scorecard.ScoredObject{
TypeMeta: v1.TypeMeta{
Kind: "Testing",
APIVersion: "v1",
},
ObjectMeta: v1.ObjectMeta{
Name: "bar-no-namespace",
},
Checks: checks,
},
}
}
func TestHumanOutputAllOKDefault(t *testing.T) {
t.Parallel()
r, err := Human(getTestCardAllOK(), 0, 100, false)
assert.Nil(t, err)
all, err := io.ReadAll(r)
assert.Nil(t, err)
assert.Equal(t, `v1/Testing foo in foofoo ✅
v1/Testing bar-no-namespace ✅
`, string(all))
}
func getTestCardLongDescription() *scorecard.Scorecard {
checks := []scorecard.TestScore{
{
Check: domain.Check{
Name: "test-warning-two-comments",
},
Grade: scorecard.GradeWarning,
Comments: []scorecard.TestScoreComment{
{
Path: "a",
Summary: "summary",
Description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras elementum sagittis lacus, a dictum tortor lobortis vel. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nulla eu neque erat. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Maecenas et nisl venenatis, elementum augue a, porttitor libero.",
},
},
},
}
return &scorecard.Scorecard{
"a": &scorecard.ScoredObject{
TypeMeta: v1.TypeMeta{
Kind: "Testing",
APIVersion: "v1",
},
ObjectMeta: v1.ObjectMeta{
Name: "foo",
Namespace: "foofoo",
},
Checks: checks,
},
}
}
func TestHumanOutputLogDescription120Width(t *testing.T) {
t.Parallel()
r, err := Human(getTestCardLongDescription(), 0, 120, false)
assert.Nil(t, err)
all, err := io.ReadAll(r)
assert.Nil(t, err)
assert.Equal(t, `v1/Testing foo in foofoo 🤔
[WARNING] test-warning-two-comments
· a -> summary
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras elementum sagittis lacus, a dictum tortor
lobortis vel. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas.
Nulla eu neque erat. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae;
Maecenas et nisl venenatis, elementum augue a, porttitor libero.
`, string(all))
}
func TestHumanOutputLogDescription100Width(t *testing.T) {
t.Parallel()
r, err := Human(getTestCardLongDescription(), 0, 100, false)
assert.Nil(t, err)
all, err := io.ReadAll(r)
assert.Nil(t, err)
assert.Equal(t, `v1/Testing foo in foofoo 🤔
[WARNING] test-warning-two-comments
· a -> summary
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras elementum sagittis lacus,
a dictum tortor lobortis vel. Pellentesque habitant morbi tristique senectus et netus et
malesuada fames ac turpis egestas. Nulla eu neque erat. Vestibulum ante ipsum primis in
faucibus orci luctus et ultrices posuere cubilia Curae; Maecenas et nisl venenatis,
elementum augue a, porttitor libero.
`, string(all))
}
func TestHumanOutputLogDescription80Width(t *testing.T) {
t.Parallel()
r, err := Human(getTestCardLongDescription(), 0, 80, false)
assert.Nil(t, err)
all, err := io.ReadAll(r)
assert.Nil(t, err)
assert.Equal(t, `v1/Testing foo in foofoo 🤔
[WARNING] test-warning-two-comments
· a -> summary
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras
elementum sagittis lacus, a dictum tortor lobortis vel. Pellentesque
habitant morbi tristique senectus et netus et malesuada fames ac
turpis egestas. Nulla eu neque erat. Vestibulum ante ipsum primis in
faucibus orci luctus et ultrices posuere cubilia Curae; Maecenas et
nisl venenatis, elementum augue a, porttitor libero.
`, string(all))
}
func TestHumanOutputLogDescription0Width(t *testing.T) {
t.Parallel()
r, err := Human(getTestCardLongDescription(), 0, 0, false)
assert.Nil(t, err)
all, err := io.ReadAll(r)
assert.Nil(t, err)
assert.Equal(t, `v1/Testing foo in foofoo🤔
[WARNING] test-warning-two-comments
· a -> summary
Lorem ipsum dolor sit amet, consectetur
adipiscing elit. Cras elementum sagittis
lacus, a dictum tortor lobortis vel.
Pellentesque habitant morbi tristique
senectus et netus et malesuada fames ac
turpis egestas. Nulla eu neque erat.
Vestibulum ante ipsum primis in faucibus
orci luctus et ultrices posuere cubilia
Curae; Maecenas et nisl venenatis,
elementum augue a, porttitor libero.
`, string(all))
}
func getTestCardLongTitle() *scorecard.Scorecard {
checks := []scorecard.TestScore{
{
Check: domain.Check{
Name: "test-warning-two-comments",
},
Grade: scorecard.GradeWarning,
Comments: []scorecard.TestScoreComment{
{
Path: "a",
Summary: "summary",
Description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras elementum sagittis lacus, a dictum tortor lobortis vel. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nulla eu neque erat. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Maecenas et nisl venenatis, elementum augue a, porttitor libero.",
},
},
},
}
return &scorecard.Scorecard{
"a": &scorecard.ScoredObject{
TypeMeta: v1.TypeMeta{
Kind: "Testing",
APIVersion: "v1",
},
ObjectMeta: v1.ObjectMeta{
Name: "this-is-a-very-long-title-this-is-a-very-long-title-this-is-a-very-long-title-this-is-a-very-long-title-this-is-a-very-long-title",
Namespace: "foofoo",
},
Checks: checks,
},
}
}
func TestHumanOutputWithLongObjectNames(t *testing.T) {
t.Parallel()
r, err := Human(getTestCardLongTitle(), 0, 80, false)
assert.Nil(t, err)
all, err := io.ReadAll(r)
assert.Nil(t, err)
assert.Equal(t, `v1/Testing this-is-a-very-long-title-this-is-a-very-long-title-this-is-a-very-long-title-this-is-a-very-long-title-this-is-a-very-long-title in foofoo🤔
[WARNING] test-warning-two-comments
· a -> summary
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras
elementum sagittis lacus, a dictum tortor lobortis vel. Pellentesque
habitant morbi tristique senectus et netus et malesuada fames ac
turpis egestas. Nulla eu neque erat. Vestibulum ante ipsum primis in
faucibus orci luctus et ultrices posuere cubilia Curae; Maecenas et
nisl venenatis, elementum augue a, porttitor libero.
`, string(all))
}
0707010000004A000041ED000000000000000000000002680FB1F800000000000000000000000000000000000000000000002300000000kube-score-1.20.0/renderer/json_v20707010000004B000081A4000000000000000000000001680FB1F8000008A0000000000000000000000000000000000000002A00000000kube-score-1.20.0/renderer/json_v2/api.gopackage json_v2
import (
"bytes"
"encoding/json"
"io"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
ks "github.com/zegl/kube-score/domain"
"github.com/zegl/kube-score/scorecard"
)
type Check struct {
Name string `json:"name"`
ID string `json:"id"`
TargetType string `json:"target_type"`
Comment string `json:"comment"`
Optional bool `json:"optional"`
}
type ScoredObject struct {
ObjectName string `json:"object_name"`
TypeMeta metav1.TypeMeta `json:"type_meta"`
ObjectMeta metav1.ObjectMeta `json:"object_meta"`
Checks []TestScore `json:"checks"`
FileName string `json:"file_name"`
FileRow int `json:"file_row"`
}
type TestScore struct {
Check Check `json:"check"`
Grade scorecard.Grade `json:"grade"`
Skipped bool `json:"skipped"`
Comments []TestScoreComment `json:"comments"`
}
type TestScoreComment struct {
Path string `json:"path"`
Summary string `json:"summary"`
Description string `json:"description"`
}
func Output(input *scorecard.Scorecard) io.Reader {
var objs []ScoredObject
for k, v := range *input {
objs = append(objs, ScoredObject{
ObjectName: k,
TypeMeta: v.TypeMeta,
ObjectMeta: v.ObjectMeta,
Checks: convertTestScore(v.Checks),
FileName: v.FileLocation.Name,
FileRow: v.FileLocation.Line,
})
}
j, err := json.MarshalIndent(objs, "", " ")
if err != nil {
panic(err)
}
return bytes.NewBuffer(j)
}
func convertTestScore(in []scorecard.TestScore) (res []TestScore) {
for _, v := range in {
res = append(res, TestScore{
Check: convertCheck(v.Check),
Grade: v.Grade,
Skipped: v.Skipped,
Comments: convertComments(v.Comments),
})
}
return
}
func convertComments(in []scorecard.TestScoreComment) (res []TestScoreComment) {
for _, v := range in {
res = append(res, TestScoreComment{
Path: v.Path,
Summary: v.Summary,
Description: v.Description,
})
}
return
}
func convertCheck(v ks.Check) Check {
return Check{
Name: v.Name,
ID: v.ID,
TargetType: v.TargetType,
Comment: v.Comment,
Optional: v.Optional,
}
}
0707010000004C000041ED000000000000000000000002680FB1F800000000000000000000000000000000000000000000002100000000kube-score-1.20.0/renderer/junit0707010000004D000081A4000000000000000000000001680FB1F8000008F6000000000000000000000000000000000000002A00000000kube-score-1.20.0/renderer/junit/junit.gopackage junit
import (
"bytes"
"io"
"github.com/jstemmer/go-junit-report/v2/junit"
"github.com/zegl/kube-score/scorecard"
)
// JUnit XML output
func JUnit(scoreCard *scorecard.Scorecard) io.Reader {
testSuites := junit.Testsuites{
Name: "kube-score",
}
for _, scoredObject := range *scoreCard {
testsuite := junit.Testsuite{
Name: scoredObject.HumanFriendlyRef(),
}
for _, testScore := range scoredObject.Checks {
if len(testScore.Comments) == 0 {
if testScore.Skipped {
testsuite.AddTestcase(junit.Testcase{
Name: testScore.Check.Name,
Classname: scoredObject.HumanFriendlyRef(),
Skipped: &junit.Result{},
})
} else {
if testScore.Grade == scorecard.GradeAlmostOK || testScore.Grade == scorecard.GradeAllOK {
testsuite.AddTestcase(junit.Testcase{
Name: testScore.Check.Name,
Classname: scoredObject.HumanFriendlyRef(),
})
} else {
testsuite.AddTestcase(junit.Testcase{
Name: testScore.Check.Name,
Classname: scoredObject.HumanFriendlyRef(),
Failure: &junit.Result{},
})
}
}
} else {
for _, comment := range testScore.Comments {
message := comment.Summary + ": " + comment.Description
if comment.Path != "" {
message = "(" + comment.Path + ") " + comment.Summary + ": " + comment.Description
}
if testScore.Skipped {
testsuite.AddTestcase(junit.Testcase{
Name: testScore.Check.Name,
Classname: scoredObject.HumanFriendlyRef(),
Skipped: &junit.Result{
Message: message,
},
})
} else {
if testScore.Grade == scorecard.GradeAlmostOK || testScore.Grade == scorecard.GradeAllOK {
testsuite.AddTestcase(junit.Testcase{
Name: testScore.Check.Name,
Classname: scoredObject.HumanFriendlyRef(),
})
} else {
testsuite.AddTestcase(junit.Testcase{
Name: testScore.Check.Name,
Classname: scoredObject.HumanFriendlyRef(),
Failure: &junit.Result{
Message: message,
},
})
}
}
}
}
}
testSuites.AddSuite(testsuite)
}
buffer := &bytes.Buffer{}
err := testSuites.WriteXML(buffer)
if err != nil {
panic(err)
}
return buffer
}
0707010000004E000081A4000000000000000000000001680FB1F800000DDA000000000000000000000000000000000000002F00000000kube-score-1.20.0/renderer/junit/junit_test.gopackage junit
import (
"io"
"testing"
"github.com/stretchr/testify/assert"
"github.com/zegl/kube-score/domain"
"github.com/zegl/kube-score/scorecard"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func getTestCard() *scorecard.Scorecard {
checks := []scorecard.TestScore{
{
Check: domain.Check{
Name: "test-warning-two-comments",
},
Grade: scorecard.GradeWarning,
Comments: []scorecard.TestScoreComment{
{
Path: "a",
Summary: "summary",
Description: "description",
},
{
// No path
Summary: "summary",
Description: "description",
},
},
},
{
Check: domain.Check{
Name: "test-ok-comment",
},
Grade: scorecard.GradeAllOK,
Comments: []scorecard.TestScoreComment{
{
Path: "a",
Summary: "summary",
Description: "description",
},
},
},
{
Check: domain.Check{
Name: "test-skipped-comment",
},
Skipped: true,
Comments: []scorecard.TestScoreComment{
{
Path: "a",
Summary: "skipped sum",
Description: "skipped description",
},
},
},
{
Check: domain.Check{
Name: "test-skipped-no-comment",
},
Skipped: true,
},
}
return &scorecard.Scorecard{
"a": &scorecard.ScoredObject{
TypeMeta: v1.TypeMeta{
Kind: "Testing",
APIVersion: "v1",
},
ObjectMeta: v1.ObjectMeta{
Name: "foo",
Namespace: "foofoo",
},
Checks: checks,
},
// No namespace
"b": &scorecard.ScoredObject{
TypeMeta: v1.TypeMeta{
Kind: "Testing",
APIVersion: "v1",
},
ObjectMeta: v1.ObjectMeta{
Name: "bar-no-namespace",
},
Checks: checks,
},
}
}
func TestJUnitOutput(t *testing.T) {
t.Parallel()
r := JUnit(getTestCard())
all, err := io.ReadAll(r)
assert.Nil(t, err)
assert.Equal(t, `<testsuites name="kube-score" tests="10" failures="4" skipped="4">
<testsuite name="foo/foofoo v1/Testing" tests="5" failures="2" errors="0" id="0" skipped="2" time="">
<testcase name="test-warning-two-comments" classname="foo/foofoo v1/Testing">
<failure message="(a) summary: description"></failure>
</testcase>
<testcase name="test-warning-two-comments" classname="foo/foofoo v1/Testing">
<failure message="summary: description"></failure>
</testcase>
<testcase name="test-ok-comment" classname="foo/foofoo v1/Testing"></testcase>
<testcase name="test-skipped-comment" classname="foo/foofoo v1/Testing">
<skipped message="(a) skipped sum: skipped description"></skipped>
</testcase>
<testcase name="test-skipped-no-comment" classname="foo/foofoo v1/Testing">
<skipped message=""></skipped>
</testcase>
</testsuite>
<testsuite name="bar-no-namespace v1/Testing" tests="5" failures="2" errors="0" id="0" skipped="2" time="">
<testcase name="test-warning-two-comments" classname="bar-no-namespace v1/Testing">
<failure message="(a) summary: description"></failure>
</testcase>
<testcase name="test-warning-two-comments" classname="bar-no-namespace v1/Testing">
<failure message="summary: description"></failure>
</testcase>
<testcase name="test-ok-comment" classname="bar-no-namespace v1/Testing"></testcase>
<testcase name="test-skipped-comment" classname="bar-no-namespace v1/Testing">
<skipped message="(a) skipped sum: skipped description"></skipped>
</testcase>
<testcase name="test-skipped-no-comment" classname="bar-no-namespace v1/Testing">
<skipped message=""></skipped>
</testcase>
</testsuite>
</testsuites>
`, string(all))
}
0707010000004F000041ED000000000000000000000002680FB1F800000000000000000000000000000000000000000000002100000000kube-score-1.20.0/renderer/sarif07070100000050000081A4000000000000000000000001680FB1F8000007CE000000000000000000000000000000000000002A00000000kube-score-1.20.0/renderer/sarif/sarif.gopackage sarif
import (
"bytes"
"encoding/json"
"io"
"github.com/zegl/kube-score/domain"
"github.com/zegl/kube-score/sarif"
"github.com/zegl/kube-score/scorecard"
)
func Output(input *scorecard.Scorecard) io.Reader {
var results []sarif.Results
var rules []sarif.Rules
addRule := func(check domain.Check) {
for _, r := range rules {
if r.ID == check.ID {
return
}
}
rules = append(rules, sarif.Rules{
ID: check.ID,
Name: check.Name,
})
}
for _, v := range *input {
for _, check := range v.Checks {
if check.Skipped {
continue
}
var level string
switch check.Grade {
case scorecard.GradeCritical:
level = "error"
case scorecard.GradeWarning:
level = "warning"
default:
continue
}
addRule(check.Check)
for _, comment := range check.Comments {
results = append(results, sarif.Results{
Message: sarif.Message{
Text: comment.Summary,
},
RuleID: check.Check.ID,
Level: level,
Properties: sarif.ResultsProperties{
IssueConfidence: "HIGH",
IssueSeverity: "HIGH",
},
Locations: []sarif.Locations{
{
PhysicalLocation: sarif.PhysicalLocation{
ArtifactLocation: sarif.ArtifactLocation{
URI: "file://" + v.FileLocation.Name,
},
ContextRegion: sarif.ContextRegion{
StartLine: v.FileLocation.Line,
},
},
},
},
})
}
}
}
run := sarif.Run{
Tool: sarif.Tool{
Driver: sarif.Driver{
Name: "kube-score",
Rules: rules,
},
},
Conversion: sarif.Conversion{
Tool: sarif.Tool{
Driver: sarif.Driver{
Name: "kube-score"},
},
},
Results: results,
}
res := sarif.Sarif{
Runs: []sarif.Run{run},
Version: "2.1.0",
Schema: "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json",
}
j, err := json.MarshalIndent(res, "", " ")
if err != nil {
panic(err)
}
return bytes.NewBuffer(j)
}
07070100000051000041ED000000000000000000000002680FB1F800000000000000000000000000000000000000000000001800000000kube-score-1.20.0/sarif07070100000052000081A4000000000000000000000001680FB1F800000B9F000000000000000000000000000000000000002100000000kube-score-1.20.0/sarif/sarif.gopackage sarif
import (
"time"
)
type Sarif struct {
Runs []Run `json:"runs,omitempty"`
Version string `json:"version,omitempty"`
Schema string `json:"$schema,omitempty"`
}
type Rules struct {
ID string `json:"id,omitempty"`
Name string `json:"name,omitempty"`
HelpURI string `json:"helpUri,omitempty"`
}
type Driver struct {
Name string `json:"name,omitempty"`
Rules []Rules `json:"rules,omitempty"`
}
type Tool struct {
Driver Driver `json:"driver,omitempty"`
}
type WorkingDirectory struct {
URI string `json:"uri,omitempty"`
}
type Invocation struct {
Arguments []string `json:"arguments,omitempty"`
ExecutionSuccessful bool `json:"executionSuccessful"`
CommandLine string `json:"commandLine,omitempty"`
EndTimeUtc time.Time `json:"endTimeUtc,omitempty"`
WorkingDirectory WorkingDirectory `json:"workingDirectory,omitempty"`
}
type Conversion struct {
Tool Tool `json:"tool,omitempty"`
Invocation Invocation `json:"invocation,omitempty"`
}
type Invocations struct {
ExecutionSuccessful bool `json:"executionSuccessful,omitempty"`
EndTimeUtc time.Time `json:"endTimeUtc,omitempty"`
WorkingDirectory WorkingDirectory `json:"workingDirectory,omitempty"`
}
type Properties struct {
}
type Message struct {
Text string `json:"text,omitempty"`
}
type Snippet struct {
Text string `json:"text,omitempty"`
}
type Region struct {
Snippet Snippet `json:"snippet,omitempty"`
StartLine int `json:"startLine,omitempty"`
}
type ArtifactLocation struct {
URI string `json:"uri,omitempty"`
}
type ContextRegion struct {
Snippet Snippet `json:"snippet,omitempty"`
EndLine int `json:"endLine,omitempty"`
StartLine int `json:"startLine,omitempty"`
}
type PhysicalLocation struct {
Region Region `json:"region,omitempty"`
ArtifactLocation ArtifactLocation `json:"artifactLocation,omitempty"`
ContextRegion ContextRegion `json:"contextRegion,omitempty"`
}
type Locations struct {
PhysicalLocation PhysicalLocation `json:"physicalLocation,omitempty"`
}
type ResultsProperties struct {
IssueConfidence string `json:"issue_confidence,omitempty"`
IssueSeverity string `json:"issue_severity,omitempty"`
}
type Results struct {
Message Message `json:"message,omitempty"`
Level string `json:"level,omitempty"`
Locations []Locations `json:"locations,omitempty"`
Properties ResultsProperties `json:"properties,omitempty"`
RuleID string `json:"ruleId,omitempty"`
RuleIndex int `json:"ruleIndex,omitempty"`
}
type Run struct {
Tool Tool `json:"tool,omitempty"`
Conversion Conversion `json:"conversion,omitempty"`
Invocations []Invocations `json:"invocations,omitempty"`
Properties Properties `json:"properties,omitempty"`
Results []Results `json:"results,omitempty"`
}
07070100000053000041ED000000000000000000000002680FB1F800000000000000000000000000000000000000000000001800000000kube-score-1.20.0/score07070100000054000041ED000000000000000000000002680FB1F800000000000000000000000000000000000000000000001D00000000kube-score-1.20.0/score/apps07070100000055000081A4000000000000000000000001680FB1F80000237A000000000000000000000000000000000000002500000000kube-score-1.20.0/score/apps/apps.gopackage apps
import (
"fmt"
"strings"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
ks "github.com/zegl/kube-score/domain"
"github.com/zegl/kube-score/score/checks"
"github.com/zegl/kube-score/score/internal"
"github.com/zegl/kube-score/scorecard"
)
func Register(allChecks *checks.Checks, allHPAs []ks.HpaTargeter, allServices []ks.Service) {
allChecks.RegisterDeploymentCheck("Deployment has host PodAntiAffinity", "Makes sure that a podAntiAffinity has been set that prevents multiple pods from being scheduled on the same node. https://kubernetes.io/docs/concepts/configuration/assign-pod-node/", deploymentHasAntiAffinity)
allChecks.RegisterStatefulSetCheck("StatefulSet has host PodAntiAffinity", "Makes sure that a podAntiAffinity has been set that prevents multiple pods from being scheduled on the same node. https://kubernetes.io/docs/concepts/configuration/assign-pod-node/", statefulsetHasAntiAffinity)
allChecks.RegisterDeploymentCheck("Deployment targeted by HPA does not have replicas configured", "Makes sure that Deployments using a HorizontalPodAutoscaler doesn't have a statically configured replica count set", hpaDeploymentNoReplicas(allHPAs))
allChecks.RegisterStatefulSetCheck("StatefulSet has ServiceName", "Makes sure that StatefulSets have an existing headless serviceName.", statefulsetHasServiceName(allServices))
allChecks.RegisterDeploymentCheck("Deployment Pod Selector labels match template metadata labels", "Ensure the StatefulSet selector labels match the template metadata labels.", deploymentSelectorLabelsMatching)
allChecks.RegisterStatefulSetCheck("StatefulSet Pod Selector labels match template metadata labels", "Ensure the StatefulSet selector labels match the template metadata labels.", statefulSetSelectorLabelsMatching)
}
func hpaDeploymentNoReplicas(allHPAs []ks.HpaTargeter) func(deployment appsv1.Deployment) (scorecard.TestScore, error) {
return func(deployment appsv1.Deployment) (score scorecard.TestScore, err error) {
// If is targeted by a HPA
for _, hpa := range allHPAs {
target := hpa.HpaTarget()
if hpa.GetObjectMeta().Namespace == deployment.Namespace &&
strings.EqualFold(target.Kind, deployment.Kind) &&
target.Name == deployment.Name {
if deployment.Spec.Replicas == nil {
score.Grade = scorecard.GradeAllOK
return
}
score.Grade = scorecard.GradeCritical
score.AddComment("", "The deployment is targeted by a HPA, but a static replica count is configured in the DeploymentSpec", "When replicas are both statically set and managed by the HPA, the replicas will be changed to the statically configured count when the spec is applied, even if the HPA wants the replica count to be higher.")
return
}
}
score.Grade = scorecard.GradeAllOK
score.Skipped = true
score.AddComment("", "Skipped because the deployment is not targeted by a HorizontalPodAutoscaler", "")
return
}
}
func deploymentHasAntiAffinity(deployment appsv1.Deployment) (score scorecard.TestScore, err error) {
// Ignore if the deployment only has a single replica
// If replicas is not explicitly set, we'll still warn if the anti affinity is missing
// as that might indicate use of a Horizontal Pod Autoscaler
if deployment.Spec.Replicas != nil && *deployment.Spec.Replicas < 2 {
score.Skipped = true
score.AddComment("", "Skipped because the deployment has less than 2 replicas", "")
return
}
warn := func() {
score.Grade = scorecard.GradeWarning
score.AddComment("", "Deployment does not have a host podAntiAffinity set", "It's recommended to set a podAntiAffinity that stops multiple pods from a deployment from being scheduled on the same node. This increases availability in case the node becomes unavailable.")
}
affinity := deployment.Spec.Template.Spec.Affinity
if affinity == nil || affinity.PodAntiAffinity == nil {
warn()
return
}
labels := internal.MapLabels(deployment.Spec.Template.GetObjectMeta().GetLabels())
if hasPodAntiAffinity(labels, affinity) {
score.Grade = scorecard.GradeAllOK
return
}
warn()
return
}
func statefulsetHasAntiAffinity(statefulset appsv1.StatefulSet) (score scorecard.TestScore, err error) {
// Ignore if the statefulset only has a single replica
// If replicas is not explicitly set, we'll still warn if the anti affinity is missing
// as that might indicate use of a Horizontal Pod Autoscaler
if statefulset.Spec.Replicas != nil && *statefulset.Spec.Replicas < 2 {
score.Skipped = true
score.AddComment("", "Skipped because the statefulset has less than 2 replicas", "")
return
}
warn := func() {
score.Grade = scorecard.GradeWarning
score.AddComment("", "StatefulSet does not have a host podAntiAffinity set", "It's recommended to set a podAntiAffinity that stops multiple pods from a statefulset from being scheduled on the same node. This increases availability in case the node becomes unavailable.")
}
affinity := statefulset.Spec.Template.Spec.Affinity
if affinity == nil || affinity.PodAntiAffinity == nil {
warn()
return
}
labels := internal.MapLabels(statefulset.Spec.Template.GetObjectMeta().GetLabels())
if hasPodAntiAffinity(labels, affinity) {
score.Grade = scorecard.GradeAllOK
return
}
warn()
return
}
func hasPodAntiAffinity(selfLabels internal.MapLabels, affinity *corev1.Affinity) bool {
approvedTopologyKeys := map[string]struct{}{
"kubernetes.io/hostname": {},
"topology.kubernetes.io/region": {},
"topology.kubernetes.io/zone": {},
// Deprecated in Kubernetes v1.17
"failure-domain.beta.kubernetes.io/region": {},
"failure-domain.beta.kubernetes.io/zone": {},
}
for _, pref := range affinity.PodAntiAffinity.PreferredDuringSchedulingIgnoredDuringExecution {
if _, ok := approvedTopologyKeys[pref.PodAffinityTerm.TopologyKey]; ok {
if selector, err := metav1.LabelSelectorAsSelector(pref.PodAffinityTerm.LabelSelector); err == nil {
if selector.Matches(selfLabels) {
return true
}
}
}
}
for _, req := range affinity.PodAntiAffinity.RequiredDuringSchedulingIgnoredDuringExecution {
if _, ok := approvedTopologyKeys[req.TopologyKey]; ok {
if selector, err := metav1.LabelSelectorAsSelector(req.LabelSelector); err == nil {
if selector.Matches(selfLabels) {
return true
}
}
}
}
return false
}
func statefulsetHasServiceName(allServices []ks.Service) func(statefulset appsv1.StatefulSet) (scorecard.TestScore, error) {
return func(statefulset appsv1.StatefulSet) (score scorecard.TestScore, err error) {
for _, service := range allServices {
if service.Service().Namespace != statefulset.Namespace ||
service.Service().Name != statefulset.Spec.ServiceName ||
service.Service().Spec.ClusterIP != "None" {
continue
}
if internal.LabelSelectorMatchesLabels(
service.Service().Spec.Selector,
statefulset.Spec.Template.GetObjectMeta().GetLabels(),
) {
score.Grade = scorecard.GradeAllOK
return
}
}
score.Grade = scorecard.GradeCritical
score.AddComment("", "StatefulSet does not have a valid serviceName", "StatefulSets currently require a Headless Service to be responsible for the network identity of the Pods. You are responsible for creating this Service. https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/#limitations")
return
}
}
func statefulSetSelectorLabelsMatching(statefulset appsv1.StatefulSet) (score scorecard.TestScore, err error) {
selector, err := metav1.LabelSelectorAsSelector(statefulset.Spec.Selector)
if err != nil {
score.Grade = scorecard.GradeCritical
score.AddComment("", "StatefulSet selector labels are not matching template metadata labels", fmt.Sprintf("Invalid selector: %s", err))
return
}
if selector.Matches(internal.MapLabels(statefulset.Spec.Template.GetObjectMeta().GetLabels())) {
score.Grade = scorecard.GradeAllOK
return
}
score.Grade = scorecard.GradeCritical
score.AddComment("", "StatefulSet selector labels not matching template metadata labels", "StatefulSets require `.spec.selector` to match `.spec.template.metadata.labels`. https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/#pod-selector")
return
}
func deploymentSelectorLabelsMatching(deployment appsv1.Deployment) (score scorecard.TestScore, err error) {
selector, err := metav1.LabelSelectorAsSelector(deployment.Spec.Selector)
if err != nil {
score.Grade = scorecard.GradeCritical
score.AddComment("", "Deployment selector labels are not matching template metadata labels", fmt.Sprintf("Invalid selector: %s", err))
return
}
if selector.Matches(internal.MapLabels(deployment.Spec.Template.GetObjectMeta().GetLabels())) {
score.Grade = scorecard.GradeAllOK
return
}
score.Grade = scorecard.GradeCritical
score.AddComment("", "Deployment selector labels not matching template metadata labels", "Deployment require `.spec.selector` to match `.spec.template.metadata.labels`. https://kubernetes.io/docs/concepts/workloads/controllers/deployment/")
return
}
07070100000056000081A4000000000000000000000001680FB1F80000595F000000000000000000000000000000000000002A00000000kube-score-1.20.0/score/apps/apps_test.gopackage apps
import (
"testing"
"github.com/stretchr/testify/assert"
appsv1 "k8s.io/api/apps/v1"
autoscalingv1 "k8s.io/api/autoscaling/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
ks "github.com/zegl/kube-score/domain"
"github.com/zegl/kube-score/scorecard"
)
type testcase struct {
replicas *int32
affinity *corev1.Affinity
expectedGrade scorecard.Grade
expectedSkipped bool
}
func i(i int32) *int32 {
return &i
}
func antiAffinityTestCases() []testcase {
return []testcase{
{
// No affinity configured
expectedGrade: scorecard.GradeWarning,
replicas: i(5),
expectedSkipped: false,
},
{
// OK! (required)
expectedGrade: scorecard.GradeAllOK,
replicas: i(5),
affinity: &corev1.Affinity{
PodAntiAffinity: &corev1.PodAntiAffinity{
RequiredDuringSchedulingIgnoredDuringExecution: []corev1.PodAffinityTerm{
{
TopologyKey: "kubernetes.io/hostname",
LabelSelector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"app": "foo",
},
},
},
},
},
},
expectedSkipped: false,
},
{
// OK! (required) ( topology.kubernetes.io/zone )
expectedGrade: scorecard.GradeAllOK,
replicas: i(5),
affinity: &corev1.Affinity{
PodAntiAffinity: &corev1.PodAntiAffinity{
RequiredDuringSchedulingIgnoredDuringExecution: []corev1.PodAffinityTerm{
{
TopologyKey: "topology.kubernetes.io/zone",
LabelSelector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"app": "foo",
},
},
},
},
},
},
expectedSkipped: false,
},
{
// OK! (required) ( topology.kubernetes.io/region )
expectedGrade: scorecard.GradeAllOK,
replicas: i(5),
affinity: &corev1.Affinity{
PodAntiAffinity: &corev1.PodAntiAffinity{
RequiredDuringSchedulingIgnoredDuringExecution: []corev1.PodAffinityTerm{
{
TopologyKey: "topology.kubernetes.io/region",
LabelSelector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"app": "foo",
},
},
},
},
},
},
expectedSkipped: false,
},
{
// Not OK! (required) ( some other topology key )
expectedGrade: scorecard.GradeWarning,
replicas: i(5),
affinity: &corev1.Affinity{
PodAntiAffinity: &corev1.PodAntiAffinity{
RequiredDuringSchedulingIgnoredDuringExecution: []corev1.PodAffinityTerm{
{
TopologyKey: "topology.kubernetes.io/what-is-this-key",
LabelSelector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"app": "foo",
},
},
},
},
},
},
expectedSkipped: false,
},
{
// OK (preferred)
expectedGrade: scorecard.GradeAllOK,
replicas: i(5),
affinity: &corev1.Affinity{
PodAntiAffinity: &corev1.PodAntiAffinity{
PreferredDuringSchedulingIgnoredDuringExecution: []corev1.WeightedPodAffinityTerm{
{
Weight: 100,
PodAffinityTerm: corev1.PodAffinityTerm{
TopologyKey: "kubernetes.io/hostname",
LabelSelector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"app": "foo",
},
},
},
},
},
},
},
expectedSkipped: false,
},
{
// Not matching app label
expectedGrade: scorecard.GradeWarning,
replicas: i(5),
affinity: &corev1.Affinity{
PodAntiAffinity: &corev1.PodAntiAffinity{
RequiredDuringSchedulingIgnoredDuringExecution: []corev1.PodAffinityTerm{
{
TopologyKey: "kubernetes.io/hostname",
LabelSelector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"app": "not-foo",
},
},
},
},
},
},
expectedSkipped: false,
},
{
// Less than two replicas
expectedGrade: 0,
replicas: i(1),
expectedSkipped: true,
},
}
}
func TestStatefulsetHasAntiAffinity(t *testing.T) {
t.Parallel()
for caseID, tc := range antiAffinityTestCases() {
s := appsv1.StatefulSet{
Spec: appsv1.StatefulSetSpec{
Replicas: tc.replicas,
Template: corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{
"app": "foo",
},
},
Spec: corev1.PodSpec{
Affinity: tc.affinity,
},
},
},
}
score, err := statefulsetHasAntiAffinity(s)
assert.Nil(t, err)
assert.Equal(t, tc.expectedGrade, score.Grade, "caseID=%d", caseID)
}
}
func TestDeploymentHasAntiAffinity(t *testing.T) {
t.Parallel()
for caseID, tc := range antiAffinityTestCases() {
s := appsv1.Deployment{
Spec: appsv1.DeploymentSpec{
Replicas: tc.replicas,
Template: corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{
"app": "foo",
},
},
Spec: corev1.PodSpec{
Affinity: tc.affinity,
},
},
},
}
score, err := deploymentHasAntiAffinity(s)
assert.Nil(t, err)
assert.Equal(t, tc.expectedGrade, score.Grade, "unexpected grade caseID=%d", caseID)
assert.Equal(t, tc.expectedSkipped, score.Skipped, "unexpected skipped, caseID=%d", caseID)
}
}
func TestDeploymentTargetedByHpaHasNoReplicasAllOK(t *testing.T) {
t.Parallel()
deployment := appsv1.Deployment{
TypeMeta: metav1.TypeMeta{Kind: "Deployment"},
ObjectMeta: metav1.ObjectMeta{Name: "foo"},
Spec: appsv1.DeploymentSpec{
Replicas: nil,
},
}
hpas := []ks.HpaTargeter{
hpav1{
autoscalingv1.HorizontalPodAutoscaler{
Spec: autoscalingv1.HorizontalPodAutoscalerSpec{
ScaleTargetRef: autoscalingv1.CrossVersionObjectReference{
Kind: "Deployment",
Name: "foo",
APIVersion: "apps/v1",
},
},
},
},
}
f := hpaDeploymentNoReplicas(hpas)
score, err := f(deployment)
assert.Nil(t, err)
assert.Equal(t, scorecard.GradeAllOK, score.Grade)
assert.False(t, score.Skipped)
}
func TestDeploymentTargetedByHpaHasSetReplicasCritical(t *testing.T) {
t.Parallel()
deployment := appsv1.Deployment{
TypeMeta: metav1.TypeMeta{Kind: "Deployment"},
ObjectMeta: metav1.ObjectMeta{Name: "foo"},
Spec: appsv1.DeploymentSpec{
Replicas: i(30),
},
}
hpas := []ks.HpaTargeter{
hpav1{
autoscalingv1.HorizontalPodAutoscaler{
Spec: autoscalingv1.HorizontalPodAutoscalerSpec{
ScaleTargetRef: autoscalingv1.CrossVersionObjectReference{
Kind: "Deployment",
Name: "foo",
APIVersion: "apps/v1",
},
},
},
},
}
f := hpaDeploymentNoReplicas(hpas)
score, err := f(deployment)
assert.Nil(t, err)
assert.Equal(t, scorecard.GradeCritical, score.Grade)
assert.False(t, score.Skipped)
}
func TestDeploymentNotTargetedByHpaIsSkippedAllOKK(t *testing.T) {
t.Parallel()
deployment := appsv1.Deployment{
TypeMeta: metav1.TypeMeta{Kind: "Deployment"},
ObjectMeta: metav1.ObjectMeta{Name: "foo"},
Spec: appsv1.DeploymentSpec{
Replicas: i(30),
},
}
hpas := []ks.HpaTargeter{
hpav1{
autoscalingv1.HorizontalPodAutoscaler{
Spec: autoscalingv1.HorizontalPodAutoscalerSpec{
ScaleTargetRef: autoscalingv1.CrossVersionObjectReference{
Kind: "Deployment",
Name: "some-other-foo",
APIVersion: "apps/v1",
},
},
},
},
}
f := hpaDeploymentNoReplicas(hpas)
score, err := f(deployment)
assert.Nil(t, err)
assert.Equal(t, scorecard.GradeAllOK, score.Grade)
assert.True(t, score.Skipped)
}
func TestDeploymentTargetedByHpaHasNoReplicasAllOKCaseInsensitiveKind(t *testing.T) {
t.Parallel()
deployment := appsv1.Deployment{
TypeMeta: metav1.TypeMeta{Kind: "Deployment"},
ObjectMeta: metav1.ObjectMeta{Name: "foo"},
Spec: appsv1.DeploymentSpec{
Replicas: nil,
},
}
hpas := []ks.HpaTargeter{
hpav1{
autoscalingv1.HorizontalPodAutoscaler{
Spec: autoscalingv1.HorizontalPodAutoscalerSpec{
ScaleTargetRef: autoscalingv1.CrossVersionObjectReference{
Kind: "deployment",
Name: "foo",
APIVersion: "apps/v1",
},
},
},
},
}
f := hpaDeploymentNoReplicas(hpas)
score, err := f(deployment)
assert.Nil(t, err)
assert.Equal(t, scorecard.GradeAllOK, score.Grade)
assert.False(t, score.Skipped)
}
type hpav1 struct {
autoscalingv1.HorizontalPodAutoscaler
}
func (d hpav1) GetTypeMeta() metav1.TypeMeta {
return d.TypeMeta
}
func (d hpav1) GetObjectMeta() metav1.ObjectMeta {
return d.ObjectMeta
}
func (d hpav1) MinReplicas() *int32 {
return d.Spec.MinReplicas
}
func (d hpav1) HpaTarget() autoscalingv1.CrossVersionObjectReference {
return d.Spec.ScaleTargetRef
}
func (hpav1) FileLocation() ks.FileLocation {
return ks.FileLocation{}
}
func TestStatefulSetHasServiceName(t *testing.T) {
t.Parallel()
testcases := []struct {
statefulset appsv1.StatefulSet
services []ks.Service
expectedErr error
expectedGrade scorecard.Grade
expectedSkipped bool
}{
// Match (no namespace)
{
statefulset: appsv1.StatefulSet{
TypeMeta: metav1.TypeMeta{Kind: "StatefulSet"},
ObjectMeta: metav1.ObjectMeta{Name: "foo"},
Spec: appsv1.StatefulSetSpec{
ServiceName: "foo-svc",
Template: corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{
"app": "foo",
},
},
},
},
},
services: []ks.Service{
service{
corev1.Service{
ObjectMeta: metav1.ObjectMeta{Name: "foo-svc"},
Spec: corev1.ServiceSpec{
ClusterIP: "None",
Selector: map[string]string{
"app": "foo",
},
},
},
},
},
expectedErr: nil,
expectedGrade: scorecard.GradeAllOK,
expectedSkipped: false,
},
// No match (different service name)
{
statefulset: appsv1.StatefulSet{
TypeMeta: metav1.TypeMeta{Kind: "StatefulSet"},
ObjectMeta: metav1.ObjectMeta{Name: "foo"},
Spec: appsv1.StatefulSetSpec{
ServiceName: "bar-svc",
Template: corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{
"app": "foo",
},
},
},
},
},
services: []ks.Service{
service{
corev1.Service{
ObjectMeta: metav1.ObjectMeta{Name: "foo-svc"},
Spec: corev1.ServiceSpec{
ClusterIP: "None",
Selector: map[string]string{
"app": "foo",
},
},
},
},
},
expectedErr: nil,
expectedGrade: scorecard.GradeCritical,
expectedSkipped: false,
},
// No match (missing service name)
{
statefulset: appsv1.StatefulSet{
TypeMeta: metav1.TypeMeta{Kind: "StatefulSet"},
ObjectMeta: metav1.ObjectMeta{Name: "foo"},
Spec: appsv1.StatefulSetSpec{
Template: corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{
"app": "foo",
},
},
},
},
},
services: []ks.Service{
service{
corev1.Service{
ObjectMeta: metav1.ObjectMeta{Name: "foo-svc"},
Spec: corev1.ServiceSpec{
ClusterIP: "None",
Selector: map[string]string{
"app": "foo",
},
},
},
},
},
expectedErr: nil,
expectedGrade: scorecard.GradeCritical,
expectedSkipped: false,
},
// Match (same namespace)
{
statefulset: appsv1.StatefulSet{
TypeMeta: metav1.TypeMeta{Kind: "StatefulSet"},
ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "foo-ns"},
Spec: appsv1.StatefulSetSpec{
ServiceName: "foo-svc",
Template: corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{
"app": "foo",
},
},
},
},
},
services: []ks.Service{
service{
corev1.Service{
ObjectMeta: metav1.ObjectMeta{Name: "foo-svc", Namespace: "foo-ns"},
Spec: corev1.ServiceSpec{
ClusterIP: "None",
Selector: map[string]string{
"app": "foo",
},
},
},
},
},
expectedErr: nil,
expectedGrade: scorecard.GradeAllOK,
expectedSkipped: false,
},
// No match (different namespace)
{
statefulset: appsv1.StatefulSet{
TypeMeta: metav1.TypeMeta{Kind: "StatefulSet"},
ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "foo-ns"},
Spec: appsv1.StatefulSetSpec{
ServiceName: "foo-svc",
Template: corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{
"app": "foo",
},
},
},
},
},
services: []ks.Service{
service{
corev1.Service{
ObjectMeta: metav1.ObjectMeta{Name: "foo-svc", Namespace: "bar-ns"},
Spec: corev1.ServiceSpec{
ClusterIP: "None",
Selector: map[string]string{
"app": "foo",
},
},
},
},
},
expectedErr: nil,
expectedGrade: scorecard.GradeCritical,
expectedSkipped: false,
},
// Match (multiple namespaces)
{
statefulset: appsv1.StatefulSet{
TypeMeta: metav1.TypeMeta{Kind: "StatefulSet"},
ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "foo-ns"},
Spec: appsv1.StatefulSetSpec{
ServiceName: "foo-svc",
Template: corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{
"app": "foo",
},
},
},
},
},
services: []ks.Service{
service{
corev1.Service{
ObjectMeta: metav1.ObjectMeta{Name: "foo-svc", Namespace: "bar-ns"},
Spec: corev1.ServiceSpec{
ClusterIP: "None",
Selector: map[string]string{
"app": "foo",
},
},
},
},
service{
corev1.Service{
ObjectMeta: metav1.ObjectMeta{Name: "foo-svc", Namespace: "foo-ns"},
Spec: corev1.ServiceSpec{
ClusterIP: "None",
Selector: map[string]string{
"app": "foo",
},
},
},
},
},
expectedErr: nil,
expectedGrade: scorecard.GradeAllOK,
expectedSkipped: false,
},
// Match (multiple namespaces, reversed)
{
statefulset: appsv1.StatefulSet{
TypeMeta: metav1.TypeMeta{Kind: "StatefulSet"},
ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "foo-ns"},
Spec: appsv1.StatefulSetSpec{
ServiceName: "foo-svc",
Template: corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{
"app": "foo",
},
},
},
},
},
services: []ks.Service{
service{
corev1.Service{
ObjectMeta: metav1.ObjectMeta{Name: "foo-svc", Namespace: "foo-ns"},
Spec: corev1.ServiceSpec{
ClusterIP: "None",
Selector: map[string]string{
"app": "foo",
},
},
},
},
service{
corev1.Service{
ObjectMeta: metav1.ObjectMeta{Name: "foo-svc", Namespace: "bar-ns"},
Spec: corev1.ServiceSpec{
ClusterIP: "None",
Selector: map[string]string{
"app": "foo",
},
},
},
},
},
expectedErr: nil,
expectedGrade: scorecard.GradeAllOK,
expectedSkipped: false,
},
// No match (not headless service)
{
statefulset: appsv1.StatefulSet{
TypeMeta: metav1.TypeMeta{Kind: "StatefulSet"},
ObjectMeta: metav1.ObjectMeta{Name: "foo"},
Spec: appsv1.StatefulSetSpec{
ServiceName: "foo-svc",
Template: corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{
"app": "foo",
},
},
},
},
},
services: []ks.Service{
service{
corev1.Service{
ObjectMeta: metav1.ObjectMeta{Name: "foo-svc"},
Spec: corev1.ServiceSpec{
ClusterIP: "",
Selector: map[string]string{
"app": "foo",
},
},
},
},
},
expectedErr: nil,
expectedGrade: scorecard.GradeCritical,
expectedSkipped: false,
},
// No match (selector)
{
statefulset: appsv1.StatefulSet{
TypeMeta: metav1.TypeMeta{Kind: "StatefulSet"},
ObjectMeta: metav1.ObjectMeta{Name: "foo"},
Spec: appsv1.StatefulSetSpec{
ServiceName: "foo-svc",
Template: corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{
"app": "foo",
},
},
},
},
},
services: []ks.Service{
service{
corev1.Service{
ObjectMeta: metav1.ObjectMeta{Name: "foo-svc"},
Spec: corev1.ServiceSpec{
ClusterIP: "None",
Selector: map[string]string{
"app": "bar",
},
},
},
},
},
expectedErr: nil,
expectedGrade: scorecard.GradeCritical,
expectedSkipped: false,
},
}
for _, tc := range testcases {
fn := statefulsetHasServiceName(tc.services)
score, err := fn(tc.statefulset)
assert.Equal(t, tc.expectedErr, err)
assert.Equal(t, tc.expectedGrade, score.Grade)
assert.Equal(t, tc.expectedSkipped, score.Skipped)
}
}
func TestStatefulSetSelectorLabels(t *testing.T) {
t.Parallel()
testcases := []struct {
statefulset appsv1.StatefulSet
expectedErr error
expectedGrade scorecard.Grade
}{
// Match
{
statefulset: appsv1.StatefulSet{
TypeMeta: metav1.TypeMeta{Kind: "StatefulSet"},
ObjectMeta: metav1.ObjectMeta{Name: "foo"},
Spec: appsv1.StatefulSetSpec{
Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"app": "foo",
},
},
Template: corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{
"app": "foo",
},
},
},
},
},
expectedErr: nil,
expectedGrade: scorecard.GradeAllOK,
},
// No match (labels differ)
{
statefulset: appsv1.StatefulSet{
TypeMeta: metav1.TypeMeta{Kind: "StatefulSet"},
ObjectMeta: metav1.ObjectMeta{Name: "foo"},
Spec: appsv1.StatefulSetSpec{
Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"app": "foo",
},
},
Template: corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{
"app": "bar",
},
},
},
},
},
expectedErr: nil,
expectedGrade: scorecard.GradeCritical,
},
// Match (expression)
{
statefulset: appsv1.StatefulSet{
TypeMeta: metav1.TypeMeta{Kind: "StatefulSet"},
ObjectMeta: metav1.ObjectMeta{Name: "foo"},
Spec: appsv1.StatefulSetSpec{
Selector: &metav1.LabelSelector{
MatchExpressions: []metav1.LabelSelectorRequirement{
{
Key: "app",
Operator: metav1.LabelSelectorOpIn,
Values: []string{"aaa", "bbb", "bar"},
},
},
},
Template: corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{
"app": "bar",
},
},
},
},
},
expectedErr: nil,
expectedGrade: scorecard.GradeAllOK,
},
// No match (expression)
{
statefulset: appsv1.StatefulSet{
TypeMeta: metav1.TypeMeta{Kind: "StatefulSet"},
ObjectMeta: metav1.ObjectMeta{Name: "foo"},
Spec: appsv1.StatefulSetSpec{
Selector: &metav1.LabelSelector{
MatchExpressions: []metav1.LabelSelectorRequirement{
{
Key: "app",
Operator: metav1.LabelSelectorOpNotIn,
Values: []string{"aaa", "bbb", "bar"},
},
},
},
Template: corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{
"app": "bar",
},
},
},
},
},
expectedErr: nil,
expectedGrade: scorecard.GradeCritical,
},
}
for _, tc := range testcases {
score, err := statefulSetSelectorLabelsMatching(tc.statefulset)
assert.Equal(t, tc.expectedErr, err)
assert.Equal(t, tc.expectedGrade, score.Grade)
}
}
func TestDeploymentSelectorLabels(t *testing.T) {
t.Parallel()
testcases := []struct {
statefulset appsv1.Deployment
expectedErr error
expectedGrade scorecard.Grade
}{
// Match
{
statefulset: appsv1.Deployment{
TypeMeta: metav1.TypeMeta{Kind: "Deployment"},
ObjectMeta: metav1.ObjectMeta{Name: "foo"},
Spec: appsv1.DeploymentSpec{
Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"app": "foo",
},
},
Template: corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{
"app": "foo",
},
},
},
},
},
expectedErr: nil,
expectedGrade: scorecard.GradeAllOK,
},
// No match (labels differ)
{
statefulset: appsv1.Deployment{
TypeMeta: metav1.TypeMeta{Kind: "StatefulSet"},
ObjectMeta: metav1.ObjectMeta{Name: "foo"},
Spec: appsv1.DeploymentSpec{
Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"app": "foo",
},
},
Template: corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{
"app": "bar",
},
},
},
},
},
expectedErr: nil,
expectedGrade: scorecard.GradeCritical,
},
// Match (expression)
{
statefulset: appsv1.Deployment{
TypeMeta: metav1.TypeMeta{Kind: "StatefulSet"},
ObjectMeta: metav1.ObjectMeta{Name: "foo"},
Spec: appsv1.DeploymentSpec{
Selector: &metav1.LabelSelector{
MatchExpressions: []metav1.LabelSelectorRequirement{
{
Key: "app",
Operator: metav1.LabelSelectorOpIn,
Values: []string{"aaa", "bbb", "bar"},
},
},
},
Template: corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{
"app": "bar",
},
},
},
},
},
expectedErr: nil,
expectedGrade: scorecard.GradeAllOK,
},
// No match (expression)
{
statefulset: appsv1.Deployment{
TypeMeta: metav1.TypeMeta{Kind: "StatefulSet"},
ObjectMeta: metav1.ObjectMeta{Name: "foo"},
Spec: appsv1.DeploymentSpec{
Selector: &metav1.LabelSelector{
MatchExpressions: []metav1.LabelSelectorRequirement{
{
Key: "app",
Operator: metav1.LabelSelectorOpNotIn,
Values: []string{"aaa", "bbb", "bar"},
},
},
},
Template: corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{
"app": "bar",
},
},
},
},
},
expectedErr: nil,
expectedGrade: scorecard.GradeCritical,
},
}
for _, tc := range testcases {
score, err := deploymentSelectorLabelsMatching(tc.statefulset)
assert.Equal(t, tc.expectedErr, err)
assert.Equal(t, tc.expectedGrade, score.Grade)
}
}
type service struct {
svc corev1.Service
}
func (d service) Service() corev1.Service {
return d.svc
}
func (d service) FileLocation() ks.FileLocation {
return ks.FileLocation{}
}
07070100000057000081A4000000000000000000000001680FB1F800001337000000000000000000000000000000000000002500000000kube-score-1.20.0/score/apps_test.gopackage score
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/zegl/kube-score/config"
ks "github.com/zegl/kube-score/domain"
"github.com/zegl/kube-score/scorecard"
)
func TestDeploymentHasPodAntiAffinityPreffered(t *testing.T) {
t.Parallel()
testExpectedScore(t, "deployment-host-antiaffinity-preffered.yaml", "Deployment has host PodAntiAffinity", scorecard.GradeAllOK)
}
func TestDeploymentHasPodAntiAffinityPrefferedNoSelectorMatch(t *testing.T) {
t.Parallel()
testExpectedScore(t, "deployment-host-antiaffinity-preffered-selector-no-match.yaml", "Deployment has host PodAntiAffinity", scorecard.GradeWarning)
}
func TestDeploymentHasPodAntiAffinityPrefferedSelectorExpression(t *testing.T) {
t.Parallel()
testExpectedScore(t, "deployment-host-antiaffinity-preffered-selector-expression.yaml", "Deployment has host PodAntiAffinity", scorecard.GradeAllOK)
}
func TestDeploymentHasPodAntiAffinityRequired(t *testing.T) {
t.Parallel()
testExpectedScore(t, "deployment-host-antiaffinity-required.yaml", "Deployment has host PodAntiAffinity", scorecard.GradeAllOK)
}
func TestDeploymentHasPodAntiAffinityNotSet(t *testing.T) {
t.Parallel()
testExpectedScore(t, "deployment-host-antiaffinity-not-set.yaml", "Deployment has host PodAntiAffinity", scorecard.GradeWarning)
}
func TestDeploymentHasPodAntiAffinityOneReplica(t *testing.T) {
t.Parallel()
// skipped
testExpectedScore(t, "deployment-host-antiaffinity-1-replica.yaml", "Deployment has host PodAntiAffinity", 0)
}
func TestStatefulSetHasPodAntiAffinityPreffered(t *testing.T) {
t.Parallel()
testExpectedScore(t, "statefulset-host-antiaffinity-preffered.yaml", "StatefulSet has host PodAntiAffinity", scorecard.GradeAllOK)
}
func TestStatefulSetHasPodAntiAffinityRequired(t *testing.T) {
t.Parallel()
testExpectedScore(t, "statefulset-host-antiaffinity-required.yaml", "StatefulSet has host PodAntiAffinity", scorecard.GradeAllOK)
}
func TestStatefulSetHasPodAntiAffinityNotSet(t *testing.T) {
t.Parallel()
testExpectedScore(t, "statefulset-host-antiaffinity-not-set.yaml", "StatefulSet has host PodAntiAffinity", scorecard.GradeWarning)
}
func TestStatefulSetHasPodAntiAffinityOneReplica(t *testing.T) {
t.Parallel()
// skipped
testExpectedScore(t, "statefulset-host-antiaffinity-1-replica.yaml", "StatefulSet has host PodAntiAffinity", 0)
}
func TestStatefulSetHasPodAntiAffinityUndefinedReplicas(t *testing.T) {
t.Parallel()
testExpectedScore(t, "statefulset-host-antiaffinity-undefined-replicas.yaml", "StatefulSet has host PodAntiAffinity", scorecard.GradeWarning)
}
func TestDeploymentWithHPAHasReplicas(t *testing.T) {
t.Parallel()
testExpectedScore(t, "deployment-with-hpa-has-replicas.yaml", "Deployment targeted by HPA does not have replicas configured", scorecard.GradeCritical)
}
func TestDeploymentWithHPANotHasReplicas(t *testing.T) {
t.Parallel()
testExpectedScore(t, "deployment-with-hpa-not-has-replicas.yaml", "Deployment targeted by HPA does not have replicas configured", scorecard.GradeAllOK)
}
func TestStatefulsetHasServiceName(t *testing.T) {
t.Parallel()
testExpectedScore(t, "statefulset-service-name.yaml", "StatefulSet has ServiceName", scorecard.GradeAllOK)
}
func TestStatefulsetHasServiceNameDifferentName(t *testing.T) {
t.Parallel()
testExpectedScore(t, "statefulset-service-name-different-name.yaml", "StatefulSet has ServiceName", scorecard.GradeCritical)
}
func TestStatefulsetHasServiceNameDifferentNamespace(t *testing.T) {
t.Parallel()
testExpectedScore(t, "statefulset-service-name-not-headless.yaml", "StatefulSet has ServiceName", scorecard.GradeCritical)
}
func TestStatefulsetHasServiceNameNotHeadless(t *testing.T) {
t.Parallel()
testExpectedScore(t, "statefulset-service-name-not-headless.yaml", "StatefulSet has ServiceName", scorecard.GradeCritical)
}
func TestStatefulsetHasServiceNameDifferentLabel(t *testing.T) {
t.Parallel()
testExpectedScore(t, "statefulset-service-name-different-label.yaml", "StatefulSet has ServiceName", scorecard.GradeCritical)
}
func TestStatefulsetSelectorLabels(t *testing.T) {
t.Parallel()
testExpectedScore(t, "statefulset-different-labels.yaml", "StatefulSet Pod Selector labels match template metadata labels", scorecard.GradeCritical)
}
func TestStatefulsetTemplateIgnores(t *testing.T) {
t.Parallel()
skipped := wasSkipped(t, []ks.NamedReader{testFile("statefulset-nested-ignores.yaml")}, nil, &config.RunConfiguration{
UseIgnoreChecksAnnotation: true,
UseOptionalChecksAnnotation: true,
}, "Container Image Tag")
assert.True(t, skipped)
}
func TestStatefulsetTemplateIgnoresNotIgnoredWhenFlagDisabled(t *testing.T) {
t.Parallel()
skipped := wasSkipped(t, []ks.NamedReader{testFile("statefulset-nested-ignores.yaml")}, nil, &config.RunConfiguration{
UseIgnoreChecksAnnotation: false,
UseOptionalChecksAnnotation: true,
}, "Container Image Tag")
assert.False(t, skipped)
}
07070100000058000041ED000000000000000000000002680FB1F800000000000000000000000000000000000000000000001F00000000kube-score-1.20.0/score/checks07070100000059000081A4000000000000000000000001680FB1F800001B8E000000000000000000000000000000000000002900000000kube-score-1.20.0/score/checks/checks.gopackage checks
import (
"strings"
ks "github.com/zegl/kube-score/domain"
"github.com/zegl/kube-score/scorecard"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
networkingv1 "k8s.io/api/networking/v1"
)
type Config struct {
IgnoredTests map[string]struct{}
}
func New(cnf *Config) *Checks {
if cnf == nil {
cnf = &Config{}
}
return &Checks{
cnf: cnf,
all: make([]ks.Check, 0),
metas: make(map[string]GenCheck[ks.BothMeta]),
pods: make(map[string]GenCheck[ks.PodSpecer]),
services: make(map[string]GenCheck[corev1.Service]),
statefulsets: make(map[string]GenCheck[appsv1.StatefulSet]),
deployments: make(map[string]GenCheck[appsv1.Deployment]),
networkpolicies: make(map[string]GenCheck[networkingv1.NetworkPolicy]),
ingresses: make(map[string]GenCheck[ks.Ingress]),
cronjobs: make(map[string]GenCheck[ks.CronJob]),
horizontalPodAutoscalers: make(map[string]GenCheck[ks.HpaTargeter]),
poddisruptionbudgets: make(map[string]GenCheck[ks.PodDisruptionBudget]),
}
}
func NewCheck(name, targetType, comment string, optional bool) ks.Check {
return ks.Check{
Name: name,
ID: machineFriendlyName(name),
TargetType: targetType,
Comment: comment,
Optional: optional,
}
}
func machineFriendlyName(in string) string {
in = strings.ToLower(in)
in = strings.ReplaceAll(in, " ", "-")
return in
}
type MetaCheckFn = func(ks.BothMeta) scorecard.TestScore
type MetaCheck struct {
ks.Check
Fn MetaCheckFn
}
type CheckFunc[T any] func(T) (scorecard.TestScore, error)
type GenCheck[T any] struct {
ks.Check
Fn CheckFunc[T]
}
type Checks struct {
all []ks.Check
metas map[string]GenCheck[ks.BothMeta]
pods map[string]GenCheck[ks.PodSpecer]
services map[string]GenCheck[corev1.Service]
statefulsets map[string]GenCheck[appsv1.StatefulSet]
deployments map[string]GenCheck[appsv1.Deployment]
networkpolicies map[string]GenCheck[networkingv1.NetworkPolicy]
ingresses map[string]GenCheck[ks.Ingress]
cronjobs map[string]GenCheck[ks.CronJob]
horizontalPodAutoscalers map[string]GenCheck[ks.HpaTargeter]
poddisruptionbudgets map[string]GenCheck[ks.PodDisruptionBudget]
cnf *Config
}
func (c Checks) isEnabled(check ks.Check) bool {
_, ok := c.cnf.IgnoredTests[check.ID]
return !ok
}
func (c *Checks) RegisterMetaCheck(name, comment string, fn CheckFunc[ks.BothMeta]) {
reg(c, "all", name, comment, false, fn, c.metas)
}
func (c *Checks) RegisterOptionalMetaCheck(name, comment string, fn CheckFunc[ks.BothMeta]) {
reg(c, "all", name, comment, true, fn, c.metas)
}
func (c *Checks) Metas() map[string]GenCheck[ks.BothMeta] {
return c.metas
}
func reg[T any](c *Checks, targetType, name, comment string, optional bool, fn CheckFunc[T], mp map[string]GenCheck[T]) {
ch := NewCheck(name, targetType, comment, optional)
check := GenCheck[T]{Check: ch, Fn: fn}
c.all = append(c.all, check.Check)
if !c.isEnabled(check.Check) {
return
}
mp[machineFriendlyName(ch.Name)] = check
}
func (c *Checks) RegisterPodCheck(name, comment string, fn CheckFunc[ks.PodSpecer]) {
reg(c, "Pod", name, comment, false, fn, c.pods)
}
func (c *Checks) RegisterOptionalPodCheck(name, comment string, fn CheckFunc[ks.PodSpecer]) {
reg(c, "Pod", name, comment, true, fn, c.pods)
}
func (c *Checks) Pods() map[string]GenCheck[ks.PodSpecer] {
return c.pods
}
func (c *Checks) RegisterHorizontalPodAutoscalerCheck(name, comment string, fn CheckFunc[ks.HpaTargeter]) {
reg(c, "HorizontalPodAutoscaler", name, comment, false, fn, c.horizontalPodAutoscalers)
}
func (c *Checks) RegisterOptionalHorizontalPodAutoscalerCheck(name, comment string, fn CheckFunc[ks.HpaTargeter]) {
reg(c, "HorizontalPodAutoscaler", name, comment, true, fn, c.horizontalPodAutoscalers)
}
func (c *Checks) HorizontalPodAutoscalers() map[string]GenCheck[ks.HpaTargeter] {
return c.horizontalPodAutoscalers
}
func (c *Checks) RegisterCronJobCheck(name, comment string, fn CheckFunc[ks.CronJob]) {
reg(c, "CronJob", name, comment, false, fn, c.cronjobs)
}
func (c *Checks) RegisterOptionalCronJobCheck(name, comment string, fn CheckFunc[ks.CronJob]) {
reg(c, "CronJob", name, comment, true, fn, c.cronjobs)
}
func (c *Checks) CronJobs() map[string]GenCheck[ks.CronJob] {
return c.cronjobs
}
func (c *Checks) RegisterStatefulSetCheck(name, comment string, fn CheckFunc[appsv1.StatefulSet]) {
reg(c, "StatefulSet", name, comment, false, fn, c.statefulsets)
}
func (c *Checks) RegisterOptionalStatefulSetCheck(name, comment string, fn CheckFunc[appsv1.StatefulSet]) {
reg(c, "StatefulSet", name, comment, true, fn, c.statefulsets)
}
func (c *Checks) StatefulSets() map[string]GenCheck[appsv1.StatefulSet] {
return c.statefulsets
}
func (c *Checks) RegisterDeploymentCheck(name, comment string, fn CheckFunc[appsv1.Deployment]) {
reg(c, "Deployment", name, comment, false, fn, c.deployments)
}
func (c *Checks) RegisterOptionalDeploymentCheck(name, comment string, fn CheckFunc[appsv1.Deployment]) {
reg(c, "Deployment", name, comment, true, fn, c.deployments)
}
func (c *Checks) Deployments() map[string]GenCheck[appsv1.Deployment] {
return c.deployments
}
func (c *Checks) RegisterIngressCheck(name, comment string, fn CheckFunc[ks.Ingress]) {
reg(c, "Ingress", name, comment, false, fn, c.ingresses)
}
func (c *Checks) RegisterOptionalIngressCheck(name, comment string, fn CheckFunc[ks.Ingress]) {
reg(c, "Ingress", name, comment, true, fn, c.ingresses)
}
func (c *Checks) Ingresses() map[string]GenCheck[ks.Ingress] {
return c.ingresses
}
func (c *Checks) RegisterNetworkPolicyCheck(name, comment string, fn CheckFunc[networkingv1.NetworkPolicy]) {
reg(c, "NetworkPolicy", name, comment, false, fn, c.networkpolicies)
}
func (c *Checks) RegisterOptionalNetworkPolicyCheck(name, comment string, fn CheckFunc[networkingv1.NetworkPolicy]) {
reg(c, "NetworkPolicy", name, comment, true, fn, c.networkpolicies)
}
func (c *Checks) NetworkPolicies() map[string]GenCheck[networkingv1.NetworkPolicy] {
return c.networkpolicies
}
func (c *Checks) RegisterPodDisruptionBudgetCheck(name, comment string, fn CheckFunc[ks.PodDisruptionBudget]) {
reg(c, "PodDisruptionBudget", name, comment, false, fn, c.poddisruptionbudgets)
}
func (c *Checks) PodDisruptionBudgets() map[string]GenCheck[ks.PodDisruptionBudget] {
return c.poddisruptionbudgets
}
func (c *Checks) RegisterServiceCheck(name, comment string, fn CheckFunc[corev1.Service]) {
reg(c, "Service", name, comment, false, fn, c.services)
}
func (c *Checks) RegisterOptionalServiceCheck(name, comment string, fn CheckFunc[corev1.Service]) {
reg(c, "Service", name, comment, true, fn, c.services)
}
func (c *Checks) Services() map[string]GenCheck[corev1.Service] {
return c.services
}
func (c *Checks) All() []ks.Check {
return c.all
}
0707010000005A000041ED000000000000000000000002680FB1F800000000000000000000000000000000000000000000002200000000kube-score-1.20.0/score/container0707010000005B000081A4000000000000000000000001680FB1F8000033E8000000000000000000000000000000000000002F00000000kube-score-1.20.0/score/container/container.gopackage container
import (
"fmt"
"strings"
ks "github.com/zegl/kube-score/domain"
"github.com/zegl/kube-score/score/checks"
"github.com/zegl/kube-score/scorecard"
corev1 "k8s.io/api/core/v1"
)
func Register(allChecks *checks.Checks, ignoreContainerCpuLimitRequirement, ignoreContainerMemoryLimitRequirement bool) {
allChecks.RegisterPodCheck("Container Resources", `Makes sure that all pods have resource limits and requests set. The --ignore-container-cpu-limit flag can be used to disable the requirement of having a CPU limit`, containerResources(!ignoreContainerCpuLimitRequirement, !ignoreContainerMemoryLimitRequirement))
allChecks.RegisterOptionalPodCheck("Container Resource Requests Equal Limits", `Makes sure that all pods have the same requests as limits on resources set.`, containerResourceRequestsEqualLimits)
allChecks.RegisterOptionalPodCheck("Container CPU Requests Equal Limits", `Makes sure that all pods have the same CPU requests as limits set.`, containerCPURequestsEqualLimits)
allChecks.RegisterOptionalPodCheck("Container Memory Requests Equal Limits", `Makes sure that all pods have the same memory requests as limits set.`, containerMemoryRequestsEqualLimits)
allChecks.RegisterPodCheck("Container Image Tag", `Makes sure that a explicit non-latest tag is used`, containerImageTag)
allChecks.RegisterPodCheck("Container Image Pull Policy", `Makes sure that the pullPolicy is set to Always. This makes sure that imagePullSecrets are always validated.`, containerImagePullPolicy)
allChecks.RegisterPodCheck("Container Ephemeral Storage Request and Limit", "Makes sure all pods have ephemeral-storage requests and limits set", containerStorageEphemeralRequestAndLimit)
allChecks.RegisterOptionalPodCheck("Container Ephemeral Storage Request Equals Limit", "Make sure all pods have matching ephemeral-storage requests and limits", containerStorageEphemeralRequestEqualsLimit)
allChecks.RegisterOptionalPodCheck("Container Ports Check", "Container Ports Checks", containerPortsCheck)
allChecks.RegisterPodCheck("Environment Variable Key Duplication", "Makes sure that duplicated environment variable keys are not duplicated", environmentVariableKeyDuplication)
}
// containerResources makes sure that the container has resource requests and limits set
// The check for a CPU limit requirement can be enabled via the requireCPULimit flag parameter
func containerResources(requireCPULimit bool, requireMemoryLimit bool) func(ks.PodSpecer) (scorecard.TestScore, error) {
return func(ps ks.PodSpecer) (score scorecard.TestScore, err error) {
pod := ps.GetPodTemplateSpec().Spec
allContainers := pod.InitContainers
allContainers = append(allContainers, pod.Containers...)
hasMissingLimit := false
hasMissingRequest := false
for _, container := range allContainers {
if container.Resources.Limits.Cpu().IsZero() && requireCPULimit {
score.AddComment(container.Name, "CPU limit is not set", "Resource limits are recommended to avoid resource DDOS. Set resources.limits.cpu")
hasMissingLimit = true
}
if container.Resources.Limits.Memory().IsZero() && requireMemoryLimit {
score.AddComment(container.Name, "Memory limit is not set", "Resource limits are recommended to avoid resource DDOS. Set resources.limits.memory")
hasMissingLimit = true
}
if container.Resources.Requests.Cpu().IsZero() {
score.AddComment(container.Name, "CPU request is not set", "Resource requests are recommended to make sure that the application can start and run without crashing. Set resources.requests.cpu")
hasMissingRequest = true
}
if container.Resources.Requests.Memory().IsZero() {
score.AddComment(container.Name, "Memory request is not set", "Resource requests are recommended to make sure that the application can start and run without crashing. Set resources.requests.memory")
hasMissingRequest = true
}
}
switch {
case len(allContainers) == 0:
score.Grade = scorecard.GradeCritical
score.AddComment("", "No containers defined", "")
case hasMissingLimit:
score.Grade = scorecard.GradeCritical
case hasMissingRequest:
score.Grade = scorecard.GradeWarning
default:
score.Grade = scorecard.GradeAllOK
}
return
}
}
// containerResourceRequestsEqualLimits checks that all containers have equal requests and limits for CPU and memory resources
func containerResourceRequestsEqualLimits(ps ks.PodSpecer) (score scorecard.TestScore, err error) {
cpuScore, _ := containerCPURequestsEqualLimits(ps)
memoryScore, _ := containerMemoryRequestsEqualLimits(ps)
score.Grade = scorecard.GradeAllOK
if cpuScore.Grade == scorecard.GradeCritical {
score.Grade = scorecard.GradeCritical
score.Comments = append(score.Comments, cpuScore.Comments...)
}
if memoryScore.Grade == scorecard.GradeCritical {
score.Grade = scorecard.GradeCritical
score.Comments = append(score.Comments, memoryScore.Comments...)
}
return
}
// containerCPURequestsEqualLimits checks that all containers have equal requests and limits for CPU resources
func containerCPURequestsEqualLimits(ps ks.PodSpecer) (score scorecard.TestScore, err error) {
pod := ps.GetPodTemplateSpec().Spec
allContainers := pod.InitContainers
allContainers = append(allContainers, pod.Containers...)
resourcesDoNotMatch := false
for _, container := range allContainers {
requests := &container.Resources.Requests
limits := &container.Resources.Limits
if !requests.Cpu().Equal(*limits.Cpu()) {
score.AddComment(container.Name, "CPU requests does not match limits", "Having equal requests and limits is recommended to avoid resource DDOS of the node during spikes. Set resources.requests.cpu == resources.limits.cpu")
resourcesDoNotMatch = true
}
}
if resourcesDoNotMatch {
score.Grade = scorecard.GradeCritical
} else {
score.Grade = scorecard.GradeAllOK
}
return
}
// containerMemoryRequestsEqualLimits checks that all containers have equal requests and limits for memory resources
func containerMemoryRequestsEqualLimits(ps ks.PodSpecer) (score scorecard.TestScore, err error) {
pod := ps.GetPodTemplateSpec().Spec
allContainers := pod.InitContainers
allContainers = append(allContainers, pod.Containers...)
resourcesDoNotMatch := false
for _, container := range allContainers {
requests := &container.Resources.Requests
limits := &container.Resources.Limits
if !requests.Memory().Equal(*limits.Memory()) {
score.AddComment(container.Name, "Memory requests does not match limits", "Having equal requests and limits is recommended to avoid resource DDOS of the node during spikes. Set resources.requests.memory == resources.limits.memory")
resourcesDoNotMatch = true
}
}
if resourcesDoNotMatch {
score.Grade = scorecard.GradeCritical
} else {
score.Grade = scorecard.GradeAllOK
}
return
}
// containerImageTag checks that no container is using the ":latest" tag
func containerImageTag(ps ks.PodSpecer) (score scorecard.TestScore, err error) {
pod := ps.GetPodTemplateSpec().Spec
allContainers := pod.InitContainers
allContainers = append(allContainers, pod.Containers...)
hasTagLatest := false
for _, container := range allContainers {
tag := containerTag(container.Image)
if tag == "" || tag == "latest" {
score.AddComment(container.Name, "Image with latest tag", "Using a fixed tag is recommended to avoid accidental upgrades")
hasTagLatest = true
}
}
if hasTagLatest {
score.Grade = scorecard.GradeCritical
} else {
score.Grade = scorecard.GradeAllOK
}
return
}
// containerImagePullPolicy checks if the containers ImagePullPolicy is set to PullAlways
func containerImagePullPolicy(ps ks.PodSpecer) (score scorecard.TestScore, err error) {
pod := ps.GetPodTemplateSpec().Spec
allContainers := pod.InitContainers
allContainers = append(allContainers, pod.Containers...)
// Default to AllOK
score.Grade = scorecard.GradeAllOK
for _, container := range allContainers {
tag := containerTag(container.Image)
// If the pull policy is not set, and the tag is either empty or latest
// kubernetes will default to always pull the image
if container.ImagePullPolicy == corev1.PullPolicy("") && (tag == "" || tag == "latest") {
continue
}
// No defined pull policy
if container.ImagePullPolicy != corev1.PullAlways || container.ImagePullPolicy == corev1.PullPolicy("") {
score.AddComment(container.Name, "ImagePullPolicy is not set to Always", "It's recommended to always set the ImagePullPolicy to Always, to make sure that the imagePullSecrets are always correct, and to always get the image you want.")
score.Grade = scorecard.GradeCritical
}
}
return
}
// containerTag returns the image tag
// An empty string is returned if the image has no tag
func containerTag(image string) string {
imageParts := strings.Split(image, ":")
if len(imageParts) > 1 {
imageVersion := imageParts[len(imageParts)-1]
return imageVersion
}
return ""
}
func containerStorageEphemeralRequestAndLimit(ps ks.PodSpecer) (score scorecard.TestScore, err error) {
allContainers := ps.GetPodTemplateSpec().Spec.InitContainers
allContainers = append(allContainers, ps.GetPodTemplateSpec().Spec.Containers...)
score.Grade = scorecard.GradeAllOK
hasMissingLimit := false
hasMissingRequest := false
for _, container := range allContainers {
if container.Resources.Limits.StorageEphemeral().IsZero() {
score.AddComment(container.Name, "Ephemeral Storage limit is not set",
"Resource limits are recommended to avoid resource DDOS. Set resources.limits.ephemeral-storage")
hasMissingLimit = true
}
if container.Resources.Requests.StorageEphemeral().IsZero() {
score.AddComment(container.Name, "Ephemeral Storage request is not set",
"Resource requests are recommended to make sure the application can start and run without crashing. Set resource.requests.ephemeral-storage")
hasMissingRequest = true
}
}
switch {
case len(allContainers) == 0:
score.Grade = scorecard.GradeCritical
score.AddComment("", "No containers defined", "")
case hasMissingLimit:
score.Grade = scorecard.GradeCritical
case hasMissingRequest:
score.Grade = scorecard.GradeWarning
default:
score.Grade = scorecard.GradeAllOK
}
return
}
func containerStorageEphemeralRequestEqualsLimit(ps ks.PodSpecer) (score scorecard.TestScore, err error) {
allContainers := ps.GetPodTemplateSpec().Spec.InitContainers
allContainers = append(allContainers, ps.GetPodTemplateSpec().Spec.Containers...)
score.Grade = scorecard.GradeAllOK
for _, container := range allContainers {
if !container.Resources.Limits.StorageEphemeral().IsZero() && !container.Resources.Requests.StorageEphemeral().IsZero() {
requests := &container.Resources.Requests
limits := &container.Resources.Limits
if !requests.StorageEphemeral().Equal(*limits.StorageEphemeral()) {
score.AddComment(container.Name, "Ephemeral Storage request does not match limit", "Having equal requests and limits is recommended to avoid node resource DDOS during spikes")
score.Grade = scorecard.GradeCritical
}
}
}
return
}
// List of ports to expose from the container. This is primarily informational. Not specifying a port here
// does not prevent it from being exposed. Specifying it does not expose the port outside the cluster; that
// requires a Service object.
func containerPortsCheck(ps ks.PodSpecer) (score scorecard.TestScore, err error) {
const maxPortNameLength = 15
allContainers := ps.GetPodTemplateSpec().Spec.InitContainers
allContainers = append(allContainers, ps.GetPodTemplateSpec().Spec.Containers...)
score.Grade = scorecard.GradeAllOK
for _, container := range allContainers {
names := make(map[string]bool)
for _, port := range container.Ports {
if len(port.Name) > 0 {
if _, ok := names[port.Name]; !ok {
names[port.Name] = true
} else {
score.AddComment(container.Name, "Container Port Check", "Container ports.containerPort named ports must be unique")
score.Grade = scorecard.GradeCritical
}
}
if len(port.Name) > maxPortNameLength {
score.AddComment(container.Name, "Container Port Check", "Container port.Name length exceeds maximum permitted characters")
score.Grade = scorecard.GradeCritical
}
if port.ContainerPort == 0 {
score.AddComment(container.Name, "Container Port Check", "Container ports.containerPort cannot be empty")
score.Grade = scorecard.GradeCritical
}
}
}
return
}
// environmentVariableKeyDuplication checks that no duplicated environment variable keys.
func environmentVariableKeyDuplication(ps ks.PodSpecer) (score scorecard.TestScore, err error) {
pod := ps.GetPodTemplateSpec().Spec
allContainers := pod.InitContainers
allContainers = append(allContainers, pod.Containers...)
score.Grade = scorecard.GradeAllOK
for _, container := range allContainers {
envs := make(map[string]struct{})
for _, env := range container.Env {
if _, duplicated := envs[env.Name]; duplicated {
msg := fmt.Sprintf("Container environment variable key '%s' is duplicated", env.Name)
score.AddComment(container.Name, "Environment Variable Key Duplication", msg)
score.Grade = scorecard.GradeCritical
continue
}
envs[env.Name] = struct{}{}
}
}
return
}
0707010000005C000081A4000000000000000000000001680FB1F800004469000000000000000000000000000000000000003400000000kube-score-1.20.0/score/container/container_test.gopackage container
import (
"testing"
ks "github.com/zegl/kube-score/domain"
"k8s.io/apimachinery/pkg/api/resource"
"github.com/stretchr/testify/assert"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/zegl/kube-score/scorecard"
)
type podSpeccer struct {
typeMeta metav1.TypeMeta
objectMeta metav1.ObjectMeta
spec corev1.PodTemplateSpec
}
func (p *podSpeccer) GetTypeMeta() metav1.TypeMeta {
return p.typeMeta
}
func (p *podSpeccer) GetObjectMeta() metav1.ObjectMeta {
return p.objectMeta
}
func (p *podSpeccer) GetPodTemplateSpec() corev1.PodTemplateSpec {
return p.spec
}
func (p *podSpeccer) FileLocation() ks.FileLocation {
return ks.FileLocation{}
}
func TestOkAllTheSameContainerResourceRequestsEqualLimits(t *testing.T) {
t.Parallel()
s, _ := containerResourceRequestsEqualLimits(
&podSpeccer{
spec: corev1.PodTemplateSpec{
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "foo",
Resources: corev1.ResourceRequirements{
Requests: map[corev1.ResourceName]resource.Quantity{
"cpu": resource.MustParse("1"),
"memory": resource.MustParse("256Mi"),
},
Limits: map[corev1.ResourceName]resource.Quantity{
"cpu": resource.MustParse("1"),
"memory": resource.MustParse("256Mi"),
},
},
},
},
},
},
},
)
assert.Equal(t, scorecard.GradeAllOK, s.Grade)
assert.Len(t, s.Comments, 0)
}
func TestOkMultipleContainersContainerResourceRequestsEqualLimits(t *testing.T) {
t.Parallel()
s, _ := containerResourceRequestsEqualLimits(
&podSpeccer{
spec: corev1.PodTemplateSpec{
Spec: corev1.PodSpec{
InitContainers: []corev1.Container{
{
Name: "foo",
Resources: corev1.ResourceRequirements{
Requests: map[corev1.ResourceName]resource.Quantity{
"cpu": resource.MustParse("1"),
"memory": resource.MustParse("256Mi"),
},
Limits: map[corev1.ResourceName]resource.Quantity{
"cpu": resource.MustParse("1"),
"memory": resource.MustParse("256Mi"),
},
},
},
},
Containers: []corev1.Container{
{
Name: "foo",
Resources: corev1.ResourceRequirements{
Requests: map[corev1.ResourceName]resource.Quantity{
"cpu": resource.MustParse("1"),
"memory": resource.MustParse("256Mi"),
},
Limits: map[corev1.ResourceName]resource.Quantity{
"cpu": resource.MustParse("1"),
"memory": resource.MustParse("256Mi"),
},
},
},
{
Name: "foo2",
Resources: corev1.ResourceRequirements{
Requests: map[corev1.ResourceName]resource.Quantity{
"cpu": resource.MustParse("1"),
"memory": resource.MustParse("256Mi"),
},
Limits: map[corev1.ResourceName]resource.Quantity{
"cpu": resource.MustParse("1"),
"memory": resource.MustParse("256Mi"),
},
},
},
},
},
},
},
)
assert.Equal(t, scorecard.GradeAllOK, s.Grade)
assert.Len(t, s.Comments, 0)
}
func TestOkSameQuantityContainerResourceRequestsEqualLimits(t *testing.T) {
t.Parallel()
s, _ := containerResourceRequestsEqualLimits(
&podSpeccer{
spec: corev1.PodTemplateSpec{
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "foo",
Resources: corev1.ResourceRequirements{
Requests: map[corev1.ResourceName]resource.Quantity{
"cpu": resource.MustParse("1"),
"memory": resource.MustParse("256Mi"),
},
Limits: map[corev1.ResourceName]resource.Quantity{
"cpu": resource.MustParse("1000m"),
"memory": resource.MustParse("0.25Gi"),
},
},
},
},
},
},
},
)
assert.Equal(t, scorecard.GradeAllOK, s.Grade)
assert.Len(t, s.Comments, 0)
}
func TestFailBothContainerResourceRequestsEqualLimits(t *testing.T) {
t.Parallel()
s, _ := containerResourceRequestsEqualLimits(
&podSpeccer{
spec: corev1.PodTemplateSpec{
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "foo",
Resources: corev1.ResourceRequirements{
Requests: map[corev1.ResourceName]resource.Quantity{
"cpu": resource.MustParse("1"),
"memory": resource.MustParse("256Mi"),
},
Limits: map[corev1.ResourceName]resource.Quantity{
"cpu": resource.MustParse("2"),
"memory": resource.MustParse("512Mi"),
},
},
},
},
},
},
})
assert.Equal(t, scorecard.GradeCritical, s.Grade)
assert.Len(t, s.Comments, 2)
assert.Equal(t, "foo", s.Comments[0].Path)
assert.Equal(t, "CPU requests does not match limits", s.Comments[0].Summary)
assert.Equal(t, "Having equal requests and limits is recommended to avoid resource DDOS of the node during spikes. Set resources.requests.cpu == resources.limits.cpu", s.Comments[0].Description)
assert.Equal(t, "foo", s.Comments[1].Path)
assert.Equal(t, "Memory requests does not match limits", s.Comments[1].Summary)
assert.Equal(t, "Having equal requests and limits is recommended to avoid resource DDOS of the node during spikes. Set resources.requests.memory == resources.limits.memory", s.Comments[1].Description)
}
func TestFailCpuInitContainerResourceRequestsEqualLimits(t *testing.T) {
t.Parallel()
s, _ := containerResourceRequestsEqualLimits(
&podSpeccer{
spec: corev1.PodTemplateSpec{
Spec: corev1.PodSpec{
InitContainers: []corev1.Container{
{
Name: "init",
Resources: corev1.ResourceRequirements{
Requests: map[corev1.ResourceName]resource.Quantity{
"cpu": resource.MustParse("1"),
"memory": resource.MustParse("256Mi"),
},
Limits: map[corev1.ResourceName]resource.Quantity{
"cpu": resource.MustParse("2"),
"memory": resource.MustParse("256Mi"),
},
},
},
},
Containers: []corev1.Container{
{
Name: "foo",
Resources: corev1.ResourceRequirements{
Requests: map[corev1.ResourceName]resource.Quantity{
"cpu": resource.MustParse("1"),
"memory": resource.MustParse("256Mi"),
},
Limits: map[corev1.ResourceName]resource.Quantity{
"cpu": resource.MustParse("1"),
"memory": resource.MustParse("256Mi"),
},
},
},
},
},
},
},
)
assert.Equal(t, scorecard.GradeCritical, s.Grade)
assert.Len(t, s.Comments, 1)
assert.Equal(t, "init", s.Comments[0].Path)
assert.Equal(t, "CPU requests does not match limits", s.Comments[0].Summary)
assert.Equal(t, "Having equal requests and limits is recommended to avoid resource DDOS of the node during spikes. Set resources.requests.cpu == resources.limits.cpu", s.Comments[0].Description)
}
func TestOkAllCPURequestsEqualLimits(t *testing.T) {
t.Parallel()
s, _ := containerCPURequestsEqualLimits(
&podSpeccer{
spec: corev1.PodTemplateSpec{
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "foo",
Resources: corev1.ResourceRequirements{
Requests: map[corev1.ResourceName]resource.Quantity{
"cpu": resource.MustParse("1"),
},
Limits: map[corev1.ResourceName]resource.Quantity{
"cpu": resource.MustParse("1"),
},
},
},
},
},
},
},
)
assert.Equal(t, scorecard.GradeAllOK, s.Grade)
assert.Len(t, s.Comments, 0)
}
func TestOkMultipleContainersContainerCPURequestsEqualLimits(t *testing.T) {
t.Parallel()
s, _ := containerCPURequestsEqualLimits(
&podSpeccer{
spec: corev1.PodTemplateSpec{
Spec: corev1.PodSpec{
InitContainers: []corev1.Container{
{
Name: "foo",
Resources: corev1.ResourceRequirements{
Requests: map[corev1.ResourceName]resource.Quantity{
"cpu": resource.MustParse("1"),
},
Limits: map[corev1.ResourceName]resource.Quantity{
"cpu": resource.MustParse("1"),
},
},
},
},
Containers: []corev1.Container{
{
Name: "foo",
Resources: corev1.ResourceRequirements{
Requests: map[corev1.ResourceName]resource.Quantity{
"cpu": resource.MustParse("1"),
},
Limits: map[corev1.ResourceName]resource.Quantity{
"cpu": resource.MustParse("1"),
},
},
},
{
Name: "foo2",
Resources: corev1.ResourceRequirements{
Requests: map[corev1.ResourceName]resource.Quantity{
"cpu": resource.MustParse("1"),
},
Limits: map[corev1.ResourceName]resource.Quantity{
"cpu": resource.MustParse("1"),
},
},
},
},
},
},
},
)
assert.Equal(t, scorecard.GradeAllOK, s.Grade)
assert.Len(t, s.Comments, 0)
}
func TestOkSameQuantityContainerCPURequestsEqualLimits(t *testing.T) {
t.Parallel()
s, _ := containerCPURequestsEqualLimits(
&podSpeccer{
spec: corev1.PodTemplateSpec{
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "foo",
Resources: corev1.ResourceRequirements{
Requests: map[corev1.ResourceName]resource.Quantity{
"cpu": resource.MustParse("1"),
},
Limits: map[corev1.ResourceName]resource.Quantity{
"cpu": resource.MustParse("1000m"),
},
},
},
},
},
},
},
)
assert.Equal(t, scorecard.GradeAllOK, s.Grade)
assert.Len(t, s.Comments, 0)
}
func TestFailContainerCPURequestsEqualLimits(t *testing.T) {
t.Parallel()
s, _ := containerCPURequestsEqualLimits(
&podSpeccer{
spec: corev1.PodTemplateSpec{
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "foo",
Resources: corev1.ResourceRequirements{
Requests: map[corev1.ResourceName]resource.Quantity{
"cpu": resource.MustParse("1"),
"memory": resource.MustParse("256Mi"),
},
Limits: map[corev1.ResourceName]resource.Quantity{
"cpu": resource.MustParse("2"),
"memory": resource.MustParse("512Mi"),
},
},
},
},
},
},
},
)
assert.Equal(t, scorecard.GradeCritical, s.Grade)
assert.Len(t, s.Comments, 1)
assert.Equal(t, "foo", s.Comments[0].Path)
assert.Equal(t, "CPU requests does not match limits", s.Comments[0].Summary)
assert.Equal(t, "Having equal requests and limits is recommended to avoid resource DDOS of the node during spikes. Set resources.requests.cpu == resources.limits.cpu", s.Comments[0].Description)
}
func TestFailInitContainerCPURequestsEqualLimits(t *testing.T) {
t.Parallel()
s, _ := containerCPURequestsEqualLimits(
&podSpeccer{
spec: corev1.PodTemplateSpec{
Spec: corev1.PodSpec{
InitContainers: []corev1.Container{
{
Name: "init",
Resources: corev1.ResourceRequirements{
Requests: map[corev1.ResourceName]resource.Quantity{
"cpu": resource.MustParse("1"),
},
Limits: map[corev1.ResourceName]resource.Quantity{
"cpu": resource.MustParse("2"),
},
},
},
},
Containers: []corev1.Container{
{
Name: "foo",
Resources: corev1.ResourceRequirements{
Requests: map[corev1.ResourceName]resource.Quantity{
"cpu": resource.MustParse("1"),
"memory": resource.MustParse("256Mi"),
},
Limits: map[corev1.ResourceName]resource.Quantity{
"cpu": resource.MustParse("1"),
"memory": resource.MustParse("256Mi"),
},
},
},
},
},
},
},
)
assert.Equal(t, scorecard.GradeCritical, s.Grade)
assert.Len(t, s.Comments, 1)
assert.Equal(t, "init", s.Comments[0].Path)
assert.Equal(t, "CPU requests does not match limits", s.Comments[0].Summary)
assert.Equal(t, "Having equal requests and limits is recommended to avoid resource DDOS of the node during spikes. Set resources.requests.cpu == resources.limits.cpu", s.Comments[0].Description)
}
func TestOkContainerMemoryResourceRequestsEqualLimits(t *testing.T) {
t.Parallel()
s, _ := containerMemoryRequestsEqualLimits(
&podSpeccer{
spec: corev1.PodTemplateSpec{
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "foo",
Resources: corev1.ResourceRequirements{
Requests: map[corev1.ResourceName]resource.Quantity{
"memory": resource.MustParse("256Mi"),
},
Limits: map[corev1.ResourceName]resource.Quantity{
"memory": resource.MustParse("256Mi"),
},
},
},
},
},
},
},
)
assert.Equal(t, scorecard.GradeAllOK, s.Grade)
assert.Len(t, s.Comments, 0)
}
func TestOkMultipleContainersContainerMemoryRequestsEqualLimits(t *testing.T) {
t.Parallel()
s, _ := containerMemoryRequestsEqualLimits(
&podSpeccer{
spec: corev1.PodTemplateSpec{
Spec: corev1.PodSpec{
InitContainers: []corev1.Container{
{
Name: "foo",
Resources: corev1.ResourceRequirements{
Requests: map[corev1.ResourceName]resource.Quantity{
"memory": resource.MustParse("256Mi"),
},
Limits: map[corev1.ResourceName]resource.Quantity{
"memory": resource.MustParse("256Mi"),
},
},
},
},
Containers: []corev1.Container{
{
Name: "foo",
Resources: corev1.ResourceRequirements{
Requests: map[corev1.ResourceName]resource.Quantity{
"memory": resource.MustParse("256Mi"),
},
Limits: map[corev1.ResourceName]resource.Quantity{
"memory": resource.MustParse("256Mi"),
},
},
},
{
Name: "foo2",
Resources: corev1.ResourceRequirements{
Requests: map[corev1.ResourceName]resource.Quantity{
"memory": resource.MustParse("256Mi"),
},
Limits: map[corev1.ResourceName]resource.Quantity{
"memory": resource.MustParse("256Mi"),
},
},
},
},
},
},
},
)
assert.Equal(t, scorecard.GradeAllOK, s.Grade)
assert.Len(t, s.Comments, 0)
}
func TestOkSameQuantityContainerMemoryRequestsEqualLimits(t *testing.T) {
t.Parallel()
s, _ := containerMemoryRequestsEqualLimits(
&podSpeccer{
spec: corev1.PodTemplateSpec{
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "foo",
Resources: corev1.ResourceRequirements{
Requests: map[corev1.ResourceName]resource.Quantity{
"memory": resource.MustParse("256Mi"),
},
Limits: map[corev1.ResourceName]resource.Quantity{
"memory": resource.MustParse("0.25Gi"),
},
},
},
},
},
},
},
)
assert.Equal(t, scorecard.GradeAllOK, s.Grade)
assert.Len(t, s.Comments, 0)
}
func TestFailContainerMemoryRequestsEqualLimits(t *testing.T) {
t.Parallel()
s, _ := containerMemoryRequestsEqualLimits(
&podSpeccer{
spec: corev1.PodTemplateSpec{
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "foo",
Resources: corev1.ResourceRequirements{
Requests: map[corev1.ResourceName]resource.Quantity{
"cpu": resource.MustParse("1"),
"memory": resource.MustParse("256Mi"),
},
Limits: map[corev1.ResourceName]resource.Quantity{
"cpu": resource.MustParse("2"),
"memory": resource.MustParse("512Mi"),
},
},
},
},
},
},
},
)
assert.Equal(t, scorecard.GradeCritical, s.Grade)
assert.Len(t, s.Comments, 1)
assert.Equal(t, "foo", s.Comments[0].Path)
assert.Equal(t, "Memory requests does not match limits", s.Comments[0].Summary)
assert.Equal(t, "Having equal requests and limits is recommended to avoid resource DDOS of the node during spikes. Set resources.requests.memory == resources.limits.memory", s.Comments[0].Description)
}
func TestFailInitContainerMemoryRequestsEqualLimits(t *testing.T) {
t.Parallel()
s, _ := containerMemoryRequestsEqualLimits(
&podSpeccer{
spec: corev1.PodTemplateSpec{
Spec: corev1.PodSpec{
InitContainers: []corev1.Container{
{
Name: "init",
Resources: corev1.ResourceRequirements{
Requests: map[corev1.ResourceName]resource.Quantity{
"memory": resource.MustParse("256Mi"),
},
Limits: map[corev1.ResourceName]resource.Quantity{
"memory": resource.MustParse("512Mi"),
},
},
},
},
Containers: []corev1.Container{
{
Name: "foo",
Resources: corev1.ResourceRequirements{
Requests: map[corev1.ResourceName]resource.Quantity{
"cpu": resource.MustParse("1"),
"memory": resource.MustParse("256Mi"),
},
Limits: map[corev1.ResourceName]resource.Quantity{
"cpu": resource.MustParse("1"),
"memory": resource.MustParse("256Mi"),
},
},
},
},
},
},
},
)
assert.Equal(t, scorecard.GradeCritical, s.Grade)
assert.Len(t, s.Comments, 1)
assert.Equal(t, "init", s.Comments[0].Path)
assert.Equal(t, "Memory requests does not match limits", s.Comments[0].Summary)
assert.Equal(t, "Having equal requests and limits is recommended to avoid resource DDOS of the node during spikes. Set resources.requests.memory == resources.limits.memory", s.Comments[0].Description)
}
0707010000005D000041ED000000000000000000000002680FB1F800000000000000000000000000000000000000000000002000000000kube-score-1.20.0/score/cronjob0707010000005E000081A4000000000000000000000001680FB1F800000679000000000000000000000000000000000000002B00000000kube-score-1.20.0/score/cronjob/cronjob.gopackage cronjob
import (
ks "github.com/zegl/kube-score/domain"
"github.com/zegl/kube-score/score/checks"
"github.com/zegl/kube-score/scorecard"
)
func Register(allChecks *checks.Checks) {
allChecks.RegisterCronJobCheck("CronJob has deadline", `Makes sure that all CronJobs has a configured deadline`, cronJobHasDeadline)
allChecks.RegisterCronJobCheck("CronJob RestartPolicy", `Makes sure CronJobs have a valid RestartPolicy`, cronJobHasRestartPolicy)
}
func cronJobHasDeadline(job ks.CronJob) (score scorecard.TestScore, err error) {
if job.StartingDeadlineSeconds() == nil {
score.Grade = scorecard.GradeCritical
score.AddComment("", "The CronJob should have startingDeadlineSeconds configured",
"This makes sure that jobs are automatically cancelled if they can not be scheduled")
return
}
score.Grade = scorecard.GradeAllOK
return
}
// CronJob restartPolicy must be "OnFailure" or "Never". It cannot be empty (unspecified)
func cronJobHasRestartPolicy(job ks.CronJob) (score scorecard.TestScore, err error) {
podTmpl := job.GetPodTemplateSpec()
restartPolicy := podTmpl.Spec.RestartPolicy
if len(restartPolicy) > 0 {
if restartPolicy == "Never" || restartPolicy == "OnFailure" {
score.Grade = scorecard.GradeAllOK
} else {
score.Grade = scorecard.GradeCritical
score.AddComment("", "The CronJob must have a valid RestartPolicy configured",
"Valid CronJob RestartPolicy settings are Never or OnFailure")
}
} else {
score.Grade = scorecard.GradeCritical
score.AddComment("", "The CronJob is missing a valid RestartPolicy",
"Valid CronJob RestartPolicy settings are Never or OnFailure")
}
return
}
0707010000005F000081A4000000000000000000000001680FB1F8000006CD000000000000000000000000000000000000002800000000kube-score-1.20.0/score/cronjob_test.gopackage score
import (
"testing"
"github.com/zegl/kube-score/scorecard"
)
func TestCronJobHasDeadline(t *testing.T) {
t.Parallel()
for _, v := range []string{"batchv1beta1", "batchv1"} {
t.Run(v, func(t *testing.T) {
testExpectedScore(t, "cronjob-"+v+"-deadline-set.yaml", "CronJob has deadline", scorecard.GradeAllOK)
})
}
}
func TestCronJobNotHasDeadline(t *testing.T) {
t.Parallel()
for _, v := range []string{"batchv1beta1", "batchv1"} {
t.Run(v, func(t *testing.T) {
testExpectedScore(t, "cronjob-"+v+"-deadline-not-set.yaml", "CronJob has deadline", scorecard.GradeCritical)
})
}
}
func TestProbesPodCronMissingReady(t *testing.T) {
t.Parallel()
for _, v := range []string{"batchv1beta1", "batchv1"} {
t.Run(v, func(t *testing.T) {
testExpectedScore(t, "cronjob-"+v+"-deadline-not-set.yaml", "Pod Probes", scorecard.GradeAllOK)
})
}
}
func TestCronJobHasRestartPolicyMissing(t *testing.T) {
t.Parallel()
for _, v := range []string{"batchv1beta1", "batchv1"} {
t.Run(v, func(t *testing.T) {
testExpectedScore(t, "cronjob-"+v+"-restartpolicy-not-set.yaml", "CronJob RestartPolicy", scorecard.GradeCritical)
})
}
}
func TestCronJobHasRestartPolicyInvalid(t *testing.T) {
t.Parallel()
for _, v := range []string{"batchv1beta1", "batchv1"} {
t.Run(v, func(t *testing.T) {
testExpectedScore(t, "cronjob-"+v+"-restartpolicy-invalid.yaml", "CronJob RestartPolicy", scorecard.GradeCritical)
})
}
}
func TestCronJobHasRestartPolicyValid(t *testing.T) {
t.Parallel()
for _, v := range []string{"batchv1beta1", "batchv1"} {
t.Run(v, func(t *testing.T) {
testExpectedScore(t, "cronjob-"+v+"-restartpolicy-valid.yaml", "CronJob RestartPolicy", scorecard.GradeAllOK)
})
}
}
07070100000060000041ED000000000000000000000002680FB1F800000000000000000000000000000000000000000000002300000000kube-score-1.20.0/score/deployment07070100000061000081A4000000000000000000000001680FB1F8000011DC000000000000000000000000000000000000003100000000kube-score-1.20.0/score/deployment/deployment.gopackage deployment
import (
"fmt"
ks "github.com/zegl/kube-score/domain"
"github.com/zegl/kube-score/score/checks"
"github.com/zegl/kube-score/score/internal"
"github.com/zegl/kube-score/scorecard"
v1 "k8s.io/api/apps/v1"
autoscalingv1 "k8s.io/api/autoscaling/v1"
"k8s.io/utils/ptr"
)
func Register(allChecks *checks.Checks, all ks.AllTypes, minReplicas int) {
allChecks.RegisterDeploymentCheck("Deployment Strategy", `Makes sure that all Deployments targeted by service use RollingUpdate strategy`, deploymentRolloutStrategy(all.Services()))
allChecks.RegisterDeploymentCheck("Deployment Replicas", `Makes sure that Deployment has multiple replicas`, deploymentReplicas(all.Services(), all.HorizontalPodAutoscalers(), minReplicas))
}
// deploymentRolloutStrategy checks if a Deployment has the update strategy on RollingUpdate if targeted by a service
func deploymentRolloutStrategy(svcs []ks.Service) func(deployment v1.Deployment) (scorecard.TestScore, error) {
svcsInNamespace := make(map[string][]map[string]string)
for _, s := range svcs {
svc := s.Service()
if _, ok := svcsInNamespace[svc.Namespace]; !ok {
svcsInNamespace[svc.Namespace] = []map[string]string{}
}
svcsInNamespace[svc.Namespace] = append(svcsInNamespace[svc.Namespace], svc.Spec.Selector)
}
return func(deployment v1.Deployment) (score scorecard.TestScore, err error) {
referencedByService := false
for _, svcSelector := range svcsInNamespace[deployment.Namespace] {
if internal.LabelSelectorMatchesLabels(svcSelector, deployment.Spec.Template.Labels) {
referencedByService = true
break
}
}
if referencedByService {
if deployment.Spec.Strategy.Type == v1.RollingUpdateDeploymentStrategyType || deployment.Spec.Strategy.Type == "" {
score.Grade = scorecard.GradeAllOK
} else {
score.Grade = scorecard.GradeWarning
score.AddCommentWithURL("", "Deployment update strategy", "The deployment is used by a service but not using the RollingUpdate strategy which can cause interruptions. Set .spec.strategy.type to RollingUpdate.", "https://kubernetes.io/docs/concepts/workloads/controllers/deployment/#strategy")
}
} else {
score.Skipped = true
score.AddComment("", "Skipped as the Deployment is not targeted by a service", "")
}
return
}
}
// deploymentReplicas checks if a Deployment has >= 2 replicas if not (targeted by service || has HorizontalPodAutoscaler)
func deploymentReplicas(svcs []ks.Service, hpas []ks.HpaTargeter, minReplicas int) func(deployment v1.Deployment) (scorecard.TestScore, error) {
svcsInNamespace := make(map[string][]map[string]string)
for _, s := range svcs {
svc := s.Service()
if _, ok := svcsInNamespace[svc.Namespace]; !ok {
svcsInNamespace[svc.Namespace] = []map[string]string{}
}
svcsInNamespace[svc.Namespace] = append(svcsInNamespace[svc.Namespace], svc.Spec.Selector)
}
hpasInNamespace := make(map[string][]autoscalingv1.CrossVersionObjectReference)
for _, hpa := range hpas {
hpaTarget := hpa.HpaTarget()
hpaMeta := hpa.GetObjectMeta()
if _, ok := hpasInNamespace[hpaMeta.Namespace]; !ok {
hpasInNamespace[hpaMeta.Namespace] = []autoscalingv1.CrossVersionObjectReference{}
}
hpasInNamespace[hpaMeta.Namespace] = append(hpasInNamespace[hpaMeta.Namespace], hpaTarget)
}
return func(deployment v1.Deployment) (score scorecard.TestScore, err error) {
referencedByService := false
hasHPA := false
for _, svcSelector := range svcsInNamespace[deployment.Namespace] {
if internal.LabelSelectorMatchesLabels(svcSelector, deployment.Spec.Template.Labels) {
referencedByService = true
break
}
}
for _, hpaTarget := range hpasInNamespace[deployment.Namespace] {
if deployment.TypeMeta.APIVersion == hpaTarget.APIVersion &&
deployment.TypeMeta.Kind == hpaTarget.Kind &&
deployment.ObjectMeta.Name == hpaTarget.Name {
hasHPA = true
break
}
}
if !referencedByService {
score.Skipped = true
score.AddComment("", "Skipped as the Deployment is not targeted by service", "")
} else if hasHPA {
score.Skipped = true
score.AddComment("", "Skipped as the Deployment is controlled by a HorizontalPodAutoscaler", "")
} else {
if ptr.Deref(deployment.Spec.Replicas, 1) >= int32(minReplicas) {
score.Grade = scorecard.GradeAllOK
} else {
score.Grade = scorecard.GradeWarning
score.AddComment("", "Deployment few replicas", fmt.Sprintf("Deployments targeted by Services are recommended to have at least %d replicas to prevent unwanted downtime.", minReplicas))
}
}
return
}
}
07070100000062000081A4000000000000000000000001680FB1F800000B73000000000000000000000000000000000000002B00000000kube-score-1.20.0/score/deployment_test.gopackage score
import (
"testing"
"github.com/zegl/kube-score/config"
"github.com/stretchr/testify/assert"
ks "github.com/zegl/kube-score/domain"
"github.com/zegl/kube-score/scorecard"
)
func TestServiceTargetsDeploymentStrategyRolling(t *testing.T) {
t.Parallel()
testExpectedScore(t, "service-target-deployment.yaml", "Deployment Strategy", scorecard.GradeAllOK)
}
func TestServiceNotTargetsDeploymentStrategyNotRelevant(t *testing.T) {
t.Parallel()
skipped := wasSkipped(t,
[]ks.NamedReader{testFile("service-not-target-deployment.yaml")}, nil, nil,
"Deployment Strategy")
assert.True(t, skipped)
}
func TestServiceTargetsDeploymentStrategyNotRolling(t *testing.T) {
t.Parallel()
testExpectedScore(t, "service-target-deployment-not-rolling.yaml", "Deployment Strategy", scorecard.GradeWarning)
}
func TestServiceTargetsDeploymentStrategyNotSet(t *testing.T) {
t.Parallel()
testExpectedScore(t, "service-target-deployment-strategy-not-set.yaml", "Deployment Strategy", scorecard.GradeAllOK)
}
func TestServiceTargetsDeploymentReplicasOk(t *testing.T) {
t.Parallel()
testExpectedScore(t, "service-target-deployment.yaml", "Deployment Replicas", scorecard.GradeAllOK)
}
func TestServiceNotTargetsDeploymentReplicasNotRelevant(t *testing.T) {
t.Parallel()
assert.True(t, wasSkipped(t,
[]ks.NamedReader{testFile("service-not-target-deployment.yaml")}, nil, nil,
"Deployment Replicas"))
summaries := getSummaries(t, []ks.NamedReader{testFile("service-not-target-deployment.yaml")}, nil, nil,
"Deployment Replicas")
assert.Contains(t, summaries, "Skipped as the Deployment is not targeted by service")
}
func TestServiceTargetsDeploymentReplicasNok(t *testing.T) {
t.Parallel()
testExpectedScoreWithConfig(t, []ks.NamedReader{testFile("service-target-deployment-replica-1.yaml")}, nil, &config.RunConfiguration{
MinReplicasDeployment: 2,
}, "Deployment Replicas", scorecard.GradeWarning)
}
func TestServiceTargetsDeploymentReplicasNok1(t *testing.T) {
t.Parallel()
testExpectedScoreWithConfig(t, []ks.NamedReader{testFile("service-target-deployment-replica-1.yaml")}, nil, &config.RunConfiguration{
MinReplicasDeployment: 1,
}, "Deployment Replicas", scorecard.GradeAllOK)
}
func TestServiceTargetsDeploymentReplicasNok5(t *testing.T) {
t.Parallel()
testExpectedScoreWithConfig(t, []ks.NamedReader{testFile("service-target-deployment-replica-1.yaml")}, nil, &config.RunConfiguration{
MinReplicasDeployment: 5,
}, "Deployment Replicas", scorecard.GradeWarning)
}
func TestHPATargetsDeployment(t *testing.T) {
t.Parallel()
assert.True(t, wasSkipped(t,
[]ks.NamedReader{testFile("hpa-target-deployment.yaml")}, nil, nil,
"Deployment Replicas"))
summaries := getSummaries(t, []ks.NamedReader{testFile("hpa-target-deployment.yaml")}, nil, nil,
"Deployment Replicas")
assert.Contains(t, summaries, "Skipped as the Deployment is controlled by a HorizontalPodAutoscaler")
}
07070100000063000041ED000000000000000000000002680FB1F800000000000000000000000000000000000000000000002900000000kube-score-1.20.0/score/disruptionbudget07070100000064000081A4000000000000000000000001680FB1F800000F82000000000000000000000000000000000000003D00000000kube-score-1.20.0/score/disruptionbudget/disruptionbudget.gopackage disruptionbudget
import (
"fmt"
ks "github.com/zegl/kube-score/domain"
"github.com/zegl/kube-score/score/checks"
"github.com/zegl/kube-score/score/internal"
"github.com/zegl/kube-score/scorecard"
appsv1 "k8s.io/api/apps/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func Register(allChecks *checks.Checks, budgets ks.PodDisruptionBudgets) {
allChecks.RegisterStatefulSetCheck("StatefulSet has PodDisruptionBudget", `Makes sure that all StatefulSets are targeted by a PDB`, statefulSetHas(budgets.PodDisruptionBudgets()))
allChecks.RegisterDeploymentCheck("Deployment has PodDisruptionBudget", `Makes sure that all Deployments are targeted by a PDB`, deploymentHas(budgets.PodDisruptionBudgets()))
allChecks.RegisterPodDisruptionBudgetCheck("PodDisruptionBudget has policy", `Makes sure that PodDisruptionBudgets specify minAvailable or maxUnavailable`, hasPolicy)
}
func hasMatching(budgets []ks.PodDisruptionBudget, namespace string, labels map[string]string) (bool, string, error) {
var hasNamespaceMismatch []string
for _, budget := range budgets {
selector, err := metav1.LabelSelectorAsSelector(budget.PodDisruptionBudgetSelector())
if err != nil {
return false, "", fmt.Errorf("failed to create selector: %w", err)
}
if !selector.Matches(internal.MapLabels(labels)) {
continue
}
// matches, but in different namespace
if budget.Namespace() != namespace {
hasNamespaceMismatch = append(hasNamespaceMismatch, budget.Namespace())
continue
}
return true, "", nil
}
if len(hasNamespaceMismatch) > 0 {
return false, fmt.Sprintf("A matching budget was found, but in a different namespace. expected='%s' got='%+v'", namespace, hasNamespaceMismatch), nil
}
return false, "", nil
}
func statefulSetHas(budgets []ks.PodDisruptionBudget) func(appsv1.StatefulSet) (scorecard.TestScore, error) {
return func(statefulset appsv1.StatefulSet) (score scorecard.TestScore, err error) {
if statefulset.Spec.Replicas != nil && *statefulset.Spec.Replicas < 2 {
score.Skipped = true
score.AddComment("", "Skipped because the statefulset has less than 2 replicas", "")
return
}
match, comment, matchErr := hasMatching(budgets, statefulset.Namespace, statefulset.Spec.Template.Labels)
if matchErr != nil {
err = matchErr
return
}
if match {
score.Grade = scorecard.GradeAllOK
} else {
score.Grade = scorecard.GradeCritical
score.AddComment("", "No matching PodDisruptionBudget was found", "It's recommended to define a PodDisruptionBudget to avoid unexpected downtime during Kubernetes maintenance operations, such as when draining a node. "+comment)
}
return
}
}
func deploymentHas(budgets []ks.PodDisruptionBudget) func(appsv1.Deployment) (scorecard.TestScore, error) {
return func(deployment appsv1.Deployment) (score scorecard.TestScore, err error) {
if deployment.Spec.Replicas != nil && *deployment.Spec.Replicas < 2 {
score.Skipped = true
score.AddComment("", "Skipped because the deployment has less than 2 replicas", "")
return
}
match, comment, matchErr := hasMatching(budgets, deployment.Namespace, deployment.Spec.Template.Labels)
if matchErr != nil {
err = matchErr
return
}
if match {
score.Grade = scorecard.GradeAllOK
} else {
score.Grade = scorecard.GradeCritical
score.AddComment("", "No matching PodDisruptionBudget was found", "It's recommended to define a PodDisruptionBudget to avoid unexpected downtime during Kubernetes maintenance operations, such as when draining a node. "+comment)
}
return
}
}
func hasPolicy(pdb ks.PodDisruptionBudget) (score scorecard.TestScore, err error) {
spec := pdb.Spec()
if spec.MinAvailable == nil && spec.MaxUnavailable == nil {
score.AddComment("", "PodDisruptionBudget missing policy", "PodDisruptionBudget should specify minAvailable or maxUnavailable.")
score.Grade = scorecard.GradeCritical
} else {
score.Grade = scorecard.GradeAllOK
}
return
}
07070100000065000081A4000000000000000000000001680FB1F800000665000000000000000000000000000000000000004200000000kube-score-1.20.0/score/disruptionbudget/disruptionbudget_test.gopackage disruptionbudget
import (
"testing"
"github.com/stretchr/testify/assert"
appsv1 "k8s.io/api/apps/v1"
"github.com/zegl/kube-score/scorecard"
)
func TestStatefulSetReplicas(t *testing.T) {
t.Parallel()
cases := map[*int32]struct {
grade scorecard.Grade
skipped bool
}{
nil: {scorecard.GradeCritical, false}, // failed
intptr(1): {0, true}, // skipped
intptr(10): {scorecard.GradeCritical, false}, // failed
}
fn := statefulSetHas(nil)
for replicas, expected := range cases {
res, err := fn(appsv1.StatefulSet{Spec: appsv1.StatefulSetSpec{Replicas: replicas}})
assert.Nil(t, err)
assert.Equal(t, expected.skipped, res.Skipped)
if replicas == nil {
assert.Equal(t, expected.grade, res.Grade, "replicas=nil")
} else {
assert.Equal(t, expected.grade, res.Grade, "replicas=%+v", *replicas)
}
}
}
func TestDeploymentReplicas(t *testing.T) {
t.Parallel()
cases := map[*int32]struct {
grade scorecard.Grade
skipped bool
}{
nil: {scorecard.GradeCritical, false}, // failed
intptr(1): {0, true}, // skipped
intptr(10): {scorecard.GradeCritical, false}, // failed
}
fn := deploymentHas(nil)
for replicas, expected := range cases {
res, err := fn(appsv1.Deployment{Spec: appsv1.DeploymentSpec{Replicas: replicas}})
assert.Nil(t, err)
assert.Equal(t, expected.skipped, res.Skipped)
if replicas == nil {
assert.Equal(t, expected.grade, res.Grade, "replicas=nil")
} else {
assert.Equal(t, expected.grade, res.Grade, "replicas=%+v", *replicas)
}
}
}
func intptr(a int32) *int32 {
return &a
}
07070100000066000081A4000000000000000000000001680FB1F80000043B000000000000000000000000000000000000002D00000000kube-score-1.20.0/score/filelocation_test.gopackage score
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/zegl/kube-score/config"
ks "github.com/zegl/kube-score/domain"
)
func TestFileLocationHelm(t *testing.T) {
sc, err := testScore([]ks.NamedReader{testFile("linenumbers-helm.yaml")}, nil, &config.RunConfiguration{
KubernetesVersion: config.Semver{Major: 1, Minor: 18},
})
assert.Nil(t, err)
for _, c := range sc {
assert.Equal(t, "app1/templates/deployment.yaml", c.FileLocation.Name)
}
assert.Equal(t, 1, sc["Deployment/apps/v1//foo"].FileLocation.Line)
assert.Equal(t, 1, sc["Deployment/apps/v1//foo2"].FileLocation.Line)
}
func TestFileLocation(t *testing.T) {
sc, err := testScore([]ks.NamedReader{testFile("linenumbers.yaml")}, nil, &config.RunConfiguration{
KubernetesVersion: config.Semver{Major: 1, Minor: 18},
})
assert.Nil(t, err)
for _, c := range sc {
assert.Equal(t, "testdata/linenumbers.yaml", c.FileLocation.Name)
}
assert.Equal(t, 2, sc["Deployment/apps/v1//foo"].FileLocation.Line)
assert.Equal(t, 12, sc["Deployment/apps/v1//foo2"].FileLocation.Line)
}
07070100000067000041ED000000000000000000000002680FB1F800000000000000000000000000000000000000000000001C00000000kube-score-1.20.0/score/hpa07070100000068000081A4000000000000000000000001680FB1F800000741000000000000000000000000000000000000002300000000kube-score-1.20.0/score/hpa/hpa.gopackage hpa
import (
"fmt"
"github.com/zegl/kube-score/domain"
"github.com/zegl/kube-score/score/checks"
"github.com/zegl/kube-score/scorecard"
"k8s.io/utils/ptr"
)
func Register(allChecks *checks.Checks, allTargetableObjs []domain.BothMeta, minReplicas int) {
allChecks.RegisterHorizontalPodAutoscalerCheck("HorizontalPodAutoscaler has target", `Makes sure that the HPA targets a valid object`, hpaHasTarget(allTargetableObjs))
allChecks.RegisterHorizontalPodAutoscalerCheck("HorizontalPodAutoscaler Replicas", `Makes sure that the HPA has multiple replicas`, hpaHasMultipleReplicas(minReplicas))
}
func hpaHasTarget(allTargetableObjs []domain.BothMeta) func(hpa domain.HpaTargeter) (scorecard.TestScore, error) {
return func(hpa domain.HpaTargeter) (score scorecard.TestScore, err error) {
targetRef := hpa.HpaTarget()
var hasTarget bool
for _, t := range allTargetableObjs {
if t.TypeMeta.APIVersion == targetRef.APIVersion &&
t.TypeMeta.Kind == targetRef.Kind &&
t.ObjectMeta.Name == targetRef.Name &&
t.ObjectMeta.Namespace == hpa.GetObjectMeta().Namespace {
hasTarget = true
break
}
}
if hasTarget {
score.Grade = scorecard.GradeAllOK
} else {
score.Grade = scorecard.GradeCritical
score.AddComment("", "The HPA target does not match anything", "")
}
return
}
}
func hpaHasMultipleReplicas(minReplicas int) func(hpa domain.HpaTargeter) (scorecard.TestScore, error) {
return func(hpa domain.HpaTargeter) (score scorecard.TestScore, err error) {
if ptr.Deref(hpa.MinReplicas(), 1) >= int32(minReplicas) {
score.Grade = scorecard.GradeAllOK
} else {
score.Grade = scorecard.GradeWarning
score.AddComment("", "HPA few replicas", fmt.Sprintf("HorizontalPodAutoscalers are recommended to have at least %d replicas to prevent unwanted downtime.", minReplicas))
}
return
}
}
07070100000069000081A4000000000000000000000001680FB1F800001257000000000000000000000000000000000000002800000000kube-score-1.20.0/score/hpa/hpa_test.gopackage hpa
import (
"testing"
"github.com/stretchr/testify/assert"
v1 "k8s.io/api/autoscaling/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/zegl/kube-score/domain"
"github.com/zegl/kube-score/scorecard"
)
func TestHpaHasTarget(t *testing.T) {
t.Parallel()
testcases := []struct {
hpa v1.HorizontalPodAutoscaler
allTargets []domain.BothMeta
expectedGrade scorecard.Grade
}{
// No match
{
hpa: v1.HorizontalPodAutoscaler{
Spec: v1.HorizontalPodAutoscalerSpec{
ScaleTargetRef: v1.CrossVersionObjectReference{
Kind: "Deployment",
Name: "foo",
APIVersion: "apps/v1",
},
},
},
expectedGrade: scorecard.GradeCritical,
},
// Match (no namespace)
{
hpa: v1.HorizontalPodAutoscaler{
Spec: v1.HorizontalPodAutoscalerSpec{
ScaleTargetRef: v1.CrossVersionObjectReference{
Kind: "Deployment",
Name: "foo",
APIVersion: "apps/v1",
},
},
},
allTargets: []domain.BothMeta{
{
TypeMeta: metav1.TypeMeta{Kind: "Deployment", APIVersion: "apps/v1"},
ObjectMeta: metav1.ObjectMeta{Name: "foo"},
},
},
expectedGrade: scorecard.GradeAllOK,
},
// Match (namespace)
{
hpa: v1.HorizontalPodAutoscaler{
ObjectMeta: metav1.ObjectMeta{Namespace: "foospace"},
Spec: v1.HorizontalPodAutoscalerSpec{
ScaleTargetRef: v1.CrossVersionObjectReference{
Kind: "Deployment",
Name: "foo",
APIVersion: "apps/v1",
},
},
},
allTargets: []domain.BothMeta{
{
TypeMeta: metav1.TypeMeta{Kind: "Deployment", APIVersion: "apps/v1"},
ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "foospace"},
},
},
expectedGrade: scorecard.GradeAllOK,
},
// No match (namespace)
{
hpa: v1.HorizontalPodAutoscaler{
ObjectMeta: metav1.ObjectMeta{Namespace: "foospace2"},
Spec: v1.HorizontalPodAutoscalerSpec{
ScaleTargetRef: v1.CrossVersionObjectReference{
Kind: "Deployment",
Name: "foo",
APIVersion: "apps/v1",
},
},
},
allTargets: []domain.BothMeta{
{
TypeMeta: metav1.TypeMeta{Kind: "Deployment", APIVersion: "apps/v1"},
ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "foospace"},
},
},
expectedGrade: scorecard.GradeCritical,
},
// No match (name)
{
hpa: v1.HorizontalPodAutoscaler{
ObjectMeta: metav1.ObjectMeta{Namespace: "foospace"},
Spec: v1.HorizontalPodAutoscalerSpec{
ScaleTargetRef: v1.CrossVersionObjectReference{
Kind: "Deployment",
Name: "not-foo",
APIVersion: "apps/v1",
},
},
},
allTargets: []domain.BothMeta{
{
TypeMeta: metav1.TypeMeta{Kind: "Deployment", APIVersion: "apps/v1"},
ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "foospace"},
},
},
expectedGrade: scorecard.GradeCritical,
},
// No match (kind)
{
hpa: v1.HorizontalPodAutoscaler{
ObjectMeta: metav1.ObjectMeta{Namespace: "foospace"},
Spec: v1.HorizontalPodAutoscalerSpec{
ScaleTargetRef: v1.CrossVersionObjectReference{
Kind: "ReplicaSet",
Name: "foo",
APIVersion: "apps/v1",
},
},
},
allTargets: []domain.BothMeta{
{
TypeMeta: metav1.TypeMeta{Kind: "Deployment", APIVersion: "apps/v1"},
ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "foospace"},
},
},
expectedGrade: scorecard.GradeCritical,
},
// No match (version)
{
hpa: v1.HorizontalPodAutoscaler{
ObjectMeta: metav1.ObjectMeta{Namespace: "foospace"},
Spec: v1.HorizontalPodAutoscalerSpec{
ScaleTargetRef: v1.CrossVersionObjectReference{
Kind: "Deployment",
Name: "foo",
APIVersion: "apps/v1beta1",
},
},
},
allTargets: []domain.BothMeta{
{
TypeMeta: metav1.TypeMeta{Kind: "Deployment", APIVersion: "apps/v1"},
ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "foospace"},
},
},
expectedGrade: scorecard.GradeCritical,
},
}
for _, tc := range testcases {
fn := hpaHasTarget(tc.allTargets)
score, _ := fn(hpav1{tc.hpa})
assert.Equal(t, tc.expectedGrade, score.Grade)
}
}
type hpav1 struct {
v1.HorizontalPodAutoscaler
}
func (d hpav1) GetTypeMeta() metav1.TypeMeta {
return d.TypeMeta
}
func (d hpav1) GetObjectMeta() metav1.ObjectMeta {
return d.ObjectMeta
}
func (d hpav1) MinReplicas() *int32 {
return d.Spec.MinReplicas
}
func (d hpav1) HpaTarget() v1.CrossVersionObjectReference {
return d.Spec.ScaleTargetRef
}
func (d hpav1) FileLocation() domain.FileLocation {
return domain.FileLocation{}
}
0707010000006A000081A4000000000000000000000001680FB1F800000728000000000000000000000000000000000000002400000000kube-score-1.20.0/score/hpa_test.gopackage score
import (
"testing"
"github.com/zegl/kube-score/config"
ks "github.com/zegl/kube-score/domain"
"github.com/zegl/kube-score/scorecard"
)
func TestHorizontalPodAutoscalerV1TargetsDeployment(t *testing.T) {
t.Parallel()
testExpectedScore(t, "hpa-autoscalingv1-targets-deployment.yaml", "HorizontalPodAutoscaler has target", scorecard.GradeAllOK)
}
func TestHorizontalPodAutoscalerV2TargetsDeployment(t *testing.T) {
t.Parallel()
testExpectedScore(t, "hpa-autoscalingv2-targets-deployment.yaml", "HorizontalPodAutoscaler has target", scorecard.GradeAllOK)
}
func TestHorizontalPodAutoscalerHasNoTarget(t *testing.T) {
t.Parallel()
testExpectedScore(t, "hpa-has-no-target.yaml", "HorizontalPodAutoscaler has target", scorecard.GradeCritical)
}
func TestHorizontalPodAutoscalerMinReplicasOk(t *testing.T) {
t.Parallel()
testExpectedScore(t, "hpa-min-replicas-ok.yaml", "HorizontalPodAutoscaler Replicas", scorecard.GradeAllOK)
}
func TestHorizontalPodAutoscalerMinReplicasNok(t *testing.T) {
t.Parallel()
testExpectedScoreWithConfig(t, []ks.NamedReader{testFile("hpa-min-replicas-nok.yaml")}, nil, &config.RunConfiguration{
MinReplicasHPA: 2,
}, "HorizontalPodAutoscaler Replicas", scorecard.GradeWarning)
}
func TestHorizontalPodAutoscalerMinReplicasMin5ReplicasNotOk(t *testing.T) {
t.Parallel()
testExpectedScoreWithConfig(t, []ks.NamedReader{testFile("hpa-min-replicas-nok.yaml")}, nil, &config.RunConfiguration{
MinReplicasHPA: 5,
}, "HorizontalPodAutoscaler Replicas", scorecard.GradeWarning)
}
func TestHorizontalPodAutoscalerMinReplicasMin1ReplicaOk(t *testing.T) {
t.Parallel()
testExpectedScoreWithConfig(t, []ks.NamedReader{testFile("hpa-min-replicas-nok.yaml")}, nil, &config.RunConfiguration{
MinReplicasHPA: 1,
}, "HorizontalPodAutoscaler Replicas", scorecard.GradeAllOK)
}
0707010000006B000041ED000000000000000000000002680FB1F800000000000000000000000000000000000000000000002000000000kube-score-1.20.0/score/ingress0707010000006C000081A4000000000000000000000001680FB1F8000008E8000000000000000000000000000000000000002B00000000kube-score-1.20.0/score/ingress/ingress.gopackage ingress
import (
"fmt"
ks "github.com/zegl/kube-score/domain"
"github.com/zegl/kube-score/score/checks"
"github.com/zegl/kube-score/scorecard"
)
func Register(allChecks *checks.Checks, services ks.Services) {
allChecks.RegisterIngressCheck("Ingress targets Service", `Makes sure that the Ingress targets a Service`, ingressTargetsService(services.Services()))
}
func ingressTargetsService(allServices []ks.Service) func(ks.Ingress) (scorecard.TestScore, error) {
return func(ingress ks.Ingress) (scorecard.TestScore, error) {
return ingressTargetsServiceCommon(ingress, allServices)
}
}
func ingressTargetsServiceCommon(ingress ks.Ingress, allServices []ks.Service) (score scorecard.TestScore, err error) {
allRulesHaveMatches := true
for _, rule := range ingress.Rules() {
if rule.IngressRuleValue.HTTP == nil {
continue
}
for _, path := range rule.IngressRuleValue.HTTP.Paths {
pathHasMatch := false
for _, srv := range allServices {
service := srv.Service()
if service.Namespace != ingress.GetObjectMeta().Namespace {
continue
}
if path.Backend.Service == nil {
continue
}
if service.Name == path.Backend.Service.Name {
for _, servicePort := range service.Spec.Ports {
if path.Backend.Service.Port.Number > 0 && servicePort.Port == path.Backend.Service.Port.Number {
pathHasMatch = true
} else if servicePort.Name == path.Backend.Service.Port.Name {
pathHasMatch = true
}
}
}
}
if !pathHasMatch {
allRulesHaveMatches = false
if path.Backend.Service != nil {
if path.Backend.Service.Port.Number > 0 {
score.AddComment(path.Path, "No service match was found", fmt.Sprintf("No service with name %s and port number %d was found", path.Backend.Service.Name, path.Backend.Service.Port.Number))
} else {
score.AddComment(path.Path, "No service match was found", fmt.Sprintf("No service with name %s and port named %s was found", path.Backend.Service.Name, path.Backend.Service.Port.Name))
}
} else {
score.AddComment(path.Path, "No service match was found", "")
}
}
}
}
if allRulesHaveMatches {
score.Grade = scorecard.GradeAllOK
} else {
score.Grade = scorecard.GradeCritical
}
return
}
0707010000006D000081A4000000000000000000000001680FB1F8000006E3000000000000000000000000000000000000002800000000kube-score-1.20.0/score/ingress_test.gopackage score
import (
"testing"
"github.com/zegl/kube-score/scorecard"
)
func TestIngressTargetsService(t *testing.T) {
t.Parallel()
testExpectedScore(t, "ingress-targets-service.yaml", "Ingress targets Service", scorecard.GradeAllOK)
}
func TestIngressTargetsServiceNoMatch(t *testing.T) {
t.Parallel()
testExpectedScore(t, "ingress-targets-service-no-match.yaml", "Ingress targets Service", scorecard.GradeCritical)
}
func TestNetworkingIngressV1beta1TargetsService(t *testing.T) {
t.Parallel()
testExpectedScore(t, "ingress-networkingv1beta1-targets-service.yaml", "Ingress targets Service", scorecard.GradeAllOK)
}
func TestNetworkingIngressV1beta1TargetsServiceNoMatch(t *testing.T) {
t.Parallel()
testExpectedScore(t, "ingress-networkingv1beta1-targets-service-no-match.yaml", "Ingress targets Service", scorecard.GradeCritical)
}
func TestNetworkingIngressV1TargetsService(t *testing.T) {
t.Parallel()
testExpectedScore(t, "ingress-networkingv1-targets-service.yaml", "Ingress targets Service", scorecard.GradeAllOK)
}
func TestNetworkingIngressV1TargetsServiceNoMatch(t *testing.T) {
t.Parallel()
testExpectedScore(t, "ingress-networkingv1-targets-service-no-match.yaml", "Ingress targets Service", scorecard.GradeCritical)
}
func TestNetworkPolicyV1InvalidBackend(t *testing.T) {
t.Parallel()
testExpectedScore(t, "ingress-v1-invalid-backend.yaml", "Ingress targets Service", scorecard.GradeCritical)
}
func TestIngressNoPanicIssue363(t *testing.T) {
t.Parallel()
testExpectedScore(t, "ingress_issue363.yaml", "Ingress targets Service", scorecard.GradeCritical)
}
func TestIngressNoPanicIssue388(t *testing.T) {
t.Parallel()
testExpectedScore(t, "ingress_issue388.yaml", "Ingress targets Service", scorecard.GradeAllOK)
}
0707010000006E000041ED000000000000000000000002680FB1F800000000000000000000000000000000000000000000002100000000kube-score-1.20.0/score/internal0707010000006F000081A4000000000000000000000001680FB1F80000026A000000000000000000000000000000000000003200000000kube-score-1.20.0/score/internal/labelselector.gopackage internal
import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
// MapLabels implements the Kubernetes Labels interface
type MapLabels map[string]string
func (l MapLabels) Has(key string) bool {
_, ok := l[key]
return ok
}
func (l MapLabels) Get(key string) string {
return l[key]
}
func LabelSelectorMatchesLabels(selectorLabels map[string]string, labels map[string]string) bool {
labelSelector := &metav1.LabelSelector{
MatchLabels: selectorLabels,
}
selector, err := metav1.LabelSelectorAsSelector(labelSelector)
if err != nil {
return false
}
return selector.Matches(MapLabels(labels))
}
07070100000070000041ED000000000000000000000002680FB1F800000000000000000000000000000000000000000000001D00000000kube-score-1.20.0/score/meta07070100000071000081A4000000000000000000000001680FB1F8000002EB000000000000000000000000000000000000002700000000kube-score-1.20.0/score/meta/labels.gopackage meta
import (
"regexp"
"github.com/zegl/kube-score/domain"
"github.com/zegl/kube-score/score/checks"
"github.com/zegl/kube-score/scorecard"
)
func Register(allChecks *checks.Checks) {
allChecks.RegisterMetaCheck("Label values", "Validates label values", validateLabelValues)
}
func validateLabelValues(meta domain.BothMeta) (score scorecard.TestScore, err error) {
score.Grade = scorecard.GradeAllOK
r := regexp.MustCompile("^(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])?$")
for key, value := range meta.ObjectMeta.Labels {
if !r.MatchString(value) {
score.Grade = scorecard.GradeCritical
score.AddComment(key, "Invalid label value", "The label value is invalid, and will not be accepted by Kubernetes")
}
}
return
}
07070100000072000081A4000000000000000000000001680FB1F8000003FD000000000000000000000000000000000000002C00000000kube-score-1.20.0/score/meta/labels_test.gopackage meta
import (
"testing"
"github.com/stretchr/testify/assert"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/zegl/kube-score/domain"
"github.com/zegl/kube-score/scorecard"
)
func TestInvalidLabel(t *testing.T) {
t.Parallel()
s, _ := validateLabelValues(domain.BothMeta{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{
"foo": "engineering/kustomize", // label values can't contain slashes
"bar": "baribar",
},
},
})
assert.Equal(t, scorecard.GradeCritical, s.Grade)
assert.Len(t, s.Comments, 1)
assert.Equal(t, "foo", s.Comments[0].Path)
assert.Equal(t, "Invalid label value", s.Comments[0].Summary)
assert.Equal(t, "The label value is invalid, and will not be accepted by Kubernetes", s.Comments[0].Description)
}
func TestOKLabel(t *testing.T) {
t.Parallel()
s, _ := validateLabelValues(domain.BothMeta{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{
"foo": "foo-bar",
},
},
})
assert.Equal(t, scorecard.GradeAllOK, s.Grade)
}
07070100000073000041ED000000000000000000000002680FB1F800000000000000000000000000000000000000000000002600000000kube-score-1.20.0/score/networkpolicy07070100000074000081A4000000000000000000000001680FB1F80000137E000000000000000000000000000000000000003700000000kube-score-1.20.0/score/networkpolicy/networkpolicy.gopackage networkpolicy
import (
networkingv1 "k8s.io/api/networking/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
ks "github.com/zegl/kube-score/domain"
"github.com/zegl/kube-score/score/checks"
"github.com/zegl/kube-score/score/internal"
"github.com/zegl/kube-score/scorecard"
)
func Register(allChecks *checks.Checks, netpols ks.NetworkPolicies, pods ks.Pods, podspecers ks.PodSpeccers) {
allChecks.RegisterPodCheck("Pod NetworkPolicy", `Makes sure that all Pods are targeted by a NetworkPolicy`, podHasNetworkPolicy(netpols.NetworkPolicies()))
allChecks.RegisterNetworkPolicyCheck("NetworkPolicy targets Pod", `Makes sure that all NetworkPolicies targets at least one Pod`, networkPolicyTargetsPod(pods.Pods(), podspecers.PodSpeccers()))
}
// podHasNetworkPolicy returns a function that tests that all pods have matching NetworkPolicies
// podHasNetworkPolicy takes a list of all defined NetworkPolicies as input
func podHasNetworkPolicy(allNetpols []ks.NetworkPolicy) func(ks.PodSpecer) (scorecard.TestScore, error) {
return func(ps ks.PodSpecer) (score scorecard.TestScore, err error) {
hasMatchingEgressNetpol := false
hasMatchingIngressNetpol := false
for _, n := range allNetpols {
netPol := n.NetworkPolicy()
// Make sure that the pod and networkpolicy is in the same namespace
if ps.GetPodTemplateSpec().Namespace != netPol.Namespace {
continue
}
if selector, err := metav1.LabelSelectorAsSelector(&netPol.Spec.PodSelector); err == nil {
if selector.Matches(internal.MapLabels(ps.GetPodTemplateSpec().Labels)) {
// Documentation of PolicyTypes
//
// List of rule types that the NetworkPolicy relates to.
// Valid options are "Ingress", "Egress", or "Ingress,Egress".
// If this field is not specified, it will default based on the existence of Ingress or Egress rules;
// policies that contain an Egress section are assumed to affect Egress, and all policies
// (whether or not they contain an Ingress section) are assumed to affect Ingress.
// If you want to write an egress-only policy, you must explicitly specify policyTypes [ "Egress" ].
// Likewise, if you want to write a policy that specifies that no egress is allowed,
// you must specify a policyTypes value that include "Egress" (since such a policy would not include
// an Egress section and would otherwise default to just [ "Ingress" ]).
if netPol.Spec.PolicyTypes == nil || len(netPol.Spec.PolicyTypes) == 0 {
hasMatchingIngressNetpol = true
if len(netPol.Spec.Egress) > 0 {
hasMatchingEgressNetpol = true
}
} else {
for _, policyType := range netPol.Spec.PolicyTypes {
if policyType == networkingv1.PolicyTypeIngress {
hasMatchingIngressNetpol = true
}
if policyType == networkingv1.PolicyTypeEgress {
hasMatchingEgressNetpol = true
}
}
}
}
}
}
switch {
case hasMatchingEgressNetpol && hasMatchingIngressNetpol:
score.Grade = scorecard.GradeAllOK
case hasMatchingEgressNetpol && !hasMatchingIngressNetpol:
score.Grade = scorecard.GradeWarning
score.AddComment("", "The pod does not have a matching ingress NetworkPolicy", "Add a ingress policy to the pods NetworkPolicy")
case hasMatchingIngressNetpol && !hasMatchingEgressNetpol:
score.Grade = scorecard.GradeWarning
score.AddComment("", "The pod does not have a matching egress NetworkPolicy", "Add a egress policy to the pods NetworkPolicy")
default:
score.Grade = scorecard.GradeCritical
score.AddComment("", "The pod does not have a matching NetworkPolicy", "Create a NetworkPolicy that targets this pod to control who/what can communicate with this pod. Note, this feature needs to be supported by the CNI implementation used in the Kubernetes cluster to have an effect.")
}
return
}
}
func networkPolicyTargetsPod(pods []ks.Pod, podspecers []ks.PodSpecer) func(networkingv1.NetworkPolicy) (scorecard.TestScore, error) {
return func(netpol networkingv1.NetworkPolicy) (score scorecard.TestScore, err error) {
hasMatch := false
for _, p := range pods {
pod := p.Pod()
if pod.Namespace != netpol.Namespace {
continue
}
if selector, err := metav1.LabelSelectorAsSelector(&netpol.Spec.PodSelector); err == nil {
if selector.Matches(internal.MapLabels(pod.Labels)) {
hasMatch = true
break
}
}
}
if !hasMatch {
for _, pod := range podspecers {
if pod.GetObjectMeta().Namespace != netpol.Namespace {
continue
}
if selector, err := metav1.LabelSelectorAsSelector(&netpol.Spec.PodSelector); err == nil {
if selector.Matches(internal.MapLabels(pod.GetPodTemplateSpec().Labels)) {
hasMatch = true
break
}
}
}
}
if hasMatch {
score.Grade = scorecard.GradeAllOK
} else {
score.Grade = scorecard.GradeCritical
score.AddComment("", "The NetworkPolicys selector doesn't match any pods", "")
}
return
}
}
07070100000075000081A4000000000000000000000001680FB1F800000DDA000000000000000000000000000000000000003C00000000kube-score-1.20.0/score/networkpolicy/networkpolicy_test.gopackage networkpolicy
import (
"testing"
"github.com/stretchr/testify/assert"
corev1 "k8s.io/api/core/v1"
v1 "k8s.io/api/networking/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/zegl/kube-score/domain"
"github.com/zegl/kube-score/scorecard"
)
func TestPodHasNetworkPolicy(t *testing.T) {
t.Parallel()
cases := []struct {
polTypes []v1.PolicyType
expected scorecard.Grade
ingress []v1.NetworkPolicyIngressRule
egress []v1.NetworkPolicyEgressRule
selectorVal string
}{
{
polTypes: []v1.PolicyType{v1.PolicyTypeIngress},
expected: scorecard.GradeWarning, // has no egress
selectorVal: "test-a",
},
{
polTypes: []v1.PolicyType{v1.PolicyTypeEgress},
expected: scorecard.GradeWarning, // has no ingress
selectorVal: "test-a",
},
{
polTypes: []v1.PolicyType{v1.PolicyTypeEgress, v1.PolicyTypeIngress},
expected: scorecard.GradeAllOK,
selectorVal: "test-a",
},
{
polTypes: []v1.PolicyType{},
expected: scorecard.GradeWarning, // has no egress
selectorVal: "test-a",
},
{
polTypes: nil,
expected: scorecard.GradeWarning, // has no egress
selectorVal: "test-a",
},
{
polTypes: []v1.PolicyType{},
egress: []v1.NetworkPolicyEgressRule{{}, {}},
expected: scorecard.GradeAllOK,
selectorVal: "test-a",
},
{
polTypes: []v1.PolicyType{},
ingress: []v1.NetworkPolicyIngressRule{{}, {}},
expected: scorecard.GradeWarning, // has no ingress
selectorVal: "test-a",
},
{
polTypes: []v1.PolicyType{},
ingress: []v1.NetworkPolicyIngressRule{{}, {}},
egress: []v1.NetworkPolicyEgressRule{{}, {}},
expected: scorecard.GradeAllOK,
selectorVal: "test-a",
},
{
polTypes: nil,
ingress: []v1.NetworkPolicyIngressRule{{}, {}},
egress: []v1.NetworkPolicyEgressRule{{}, {}},
expected: scorecard.GradeAllOK,
selectorVal: "test-a",
},
{
polTypes: []v1.PolicyType{v1.PolicyTypeEgress, v1.PolicyTypeIngress},
expected: scorecard.GradeCritical, // pod has no ingress matching
selectorVal: "test-not-matching",
},
}
for caseID, tc := range cases {
pol := v1.NetworkPolicy{
Spec: v1.NetworkPolicySpec{
PodSelector: metav1.LabelSelector{
MatchLabels: map[string]string{"test": tc.selectorVal},
},
Ingress: tc.ingress,
Egress: tc.egress,
PolicyTypes: tc.polTypes,
},
}
pod := corev1.Pod{
TypeMeta: metav1.TypeMeta{},
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{
"test": "test-a",
},
},
Spec: corev1.PodSpec{},
}
fn := podHasNetworkPolicy([]domain.NetworkPolicy{np{Obj: pol}})
spec := corev1.PodTemplateSpec{ObjectMeta: pod.ObjectMeta, Spec: pod.Spec}
score, _ := fn(&podSpeccer{spec: spec})
assert.Equal(t, tc.expected, score.Grade, "caseID = %d", caseID)
}
}
type np struct {
Obj v1.NetworkPolicy
}
func (n np) NetworkPolicy() v1.NetworkPolicy {
return n.Obj
}
func (np) FileLocation() domain.FileLocation {
return domain.FileLocation{}
}
type podSpeccer struct {
typeMeta metav1.TypeMeta
objectMeta metav1.ObjectMeta
spec corev1.PodTemplateSpec
}
func (p *podSpeccer) GetTypeMeta() metav1.TypeMeta {
return p.typeMeta
}
func (p *podSpeccer) GetObjectMeta() metav1.ObjectMeta {
return p.objectMeta
}
func (p *podSpeccer) GetPodTemplateSpec() corev1.PodTemplateSpec {
return p.spec
}
func (p *podSpeccer) FileLocation() domain.FileLocation {
return domain.FileLocation{}
}
07070100000076000081A4000000000000000000000001680FB1F800000E0D000000000000000000000000000000000000002E00000000kube-score-1.20.0/score/networkpolicy_test.gopackage score
import (
"testing"
"github.com/zegl/kube-score/scorecard"
)
func TestPodHasNoMatchingNetworkPolicy(t *testing.T) {
t.Parallel()
testExpectedScore(t, "networkpolicy-not-matching.yaml", "Pod NetworkPolicy", scorecard.GradeCritical)
}
func TestPodHasMatchingNetworkPolicy(t *testing.T) {
t.Parallel()
testExpectedScore(t, "networkpolicy-matching.yaml", "Pod NetworkPolicy", scorecard.GradeAllOK)
}
func TestPodHasMatchingIngressNetworkPolicy(t *testing.T) {
t.Parallel()
testExpectedScore(t, "networkpolicy-matching-only-ingress.yaml", "Pod NetworkPolicy", scorecard.GradeWarning)
}
func TestPodHasMatchingEgressNetworkPolicy(t *testing.T) {
t.Parallel()
testExpectedScore(t, "networkpolicy-matching-only-egress.yaml", "Pod NetworkPolicy", scorecard.GradeWarning)
}
func TestNetworkPolicyTargetsPod(t *testing.T) {
t.Parallel()
testExpectedScore(t, "networkpolicy-targets-pod.yaml", "NetworkPolicy targets Pod", scorecard.GradeAllOK)
}
func TestNetworkPolicyTargetsPodInDeployment(t *testing.T) {
t.Parallel()
testExpectedScore(t, "networkpolicy-targets-pod-deployment.yaml", "NetworkPolicy targets Pod", scorecard.GradeAllOK)
}
func TestNetworkPolicyTargetsPodNotMatching(t *testing.T) {
t.Parallel()
testExpectedScore(t, "networkpolicy-targets-pod-not-matching.yaml", "NetworkPolicy targets Pod", scorecard.GradeCritical)
}
func TestNetworkPolicyDeploymentNamespaceMatching(t *testing.T) {
t.Parallel()
testExpectedScore(t, "networkpolicy-deployment-matching.yaml", "NetworkPolicy targets Pod", scorecard.GradeAllOK)
testExpectedScore(t, "networkpolicy-deployment-matching.yaml", "Pod NetworkPolicy", scorecard.GradeAllOK)
}
func TestNetworkPolicyStatefulSetNamespaceMatching(t *testing.T) {
t.Parallel()
testExpectedScore(t, "networkpolicy-statefulset-matching.yaml", "NetworkPolicy targets Pod", scorecard.GradeAllOK)
testExpectedScore(t, "networkpolicy-statefulset-matching.yaml", "Pod NetworkPolicy", scorecard.GradeAllOK)
}
func TestNetworkPolicyCronJobNamespaceMatching(t *testing.T) {
t.Parallel()
testExpectedScore(t, "networkpolicy-cronjob-matching.yaml", "NetworkPolicy targets Pod", scorecard.GradeAllOK)
testExpectedScore(t, "networkpolicy-cronjob-matching.yaml", "Pod NetworkPolicy", scorecard.GradeAllOK)
}
func TestNetworkPolicyDeploymentNamespaceNotMatchingSelector(t *testing.T) {
t.Parallel()
testExpectedScore(t, "networkpolicy-deployment-not-matching-selector.yaml", "NetworkPolicy targets Pod", scorecard.GradeCritical)
testExpectedScore(t, "networkpolicy-deployment-not-matching-selector.yaml", "Pod NetworkPolicy", scorecard.GradeCritical)
}
func TestNetworkPolicyStatefulSetNamespaceNotMatchingSelector(t *testing.T) {
t.Parallel()
testExpectedScore(t, "networkpolicy-statefulset-not-matching-selector.yaml", "NetworkPolicy targets Pod", scorecard.GradeCritical)
testExpectedScore(t, "networkpolicy-statefulset-not-matching-selector.yaml", "Pod NetworkPolicy", scorecard.GradeCritical)
}
func TestNetworkPolicyCronJobNamespaceNotMatchingSelector(t *testing.T) {
t.Parallel()
testExpectedScore(t, "networkpolicy-cronjob-not-matching-selector.yaml", "NetworkPolicy targets Pod", scorecard.GradeCritical)
testExpectedScore(t, "networkpolicy-cronjob-not-matching-selector.yaml", "Pod NetworkPolicy", scorecard.GradeCritical)
}
func TestNetworkPolicyEmptyPodSelector(t *testing.T) {
t.Parallel()
testExpectedScore(t, "networkpolicy-targets-all-pods.yaml", "NetworkPolicy targets Pod", scorecard.GradeAllOK)
testExpectedScore(t, "networkpolicy-targets-all-pods.yaml", "Pod NetworkPolicy", scorecard.GradeAllOK)
}
07070100000077000081A4000000000000000000000001680FB1F80000078F000000000000000000000000000000000000002900000000kube-score-1.20.0/score/optional_test.gopackage score
import (
"testing"
"github.com/zegl/kube-score/config"
ks "github.com/zegl/kube-score/domain"
"github.com/zegl/kube-score/score/checks"
"github.com/zegl/kube-score/scorecard"
)
func TestOptionalSkippedByDefault(t *testing.T) {
t.Parallel()
enabledOptionalTests := make(map[string]struct{})
wasSkipped(t, []ks.NamedReader{testFile("pod-container-memory-requests.yaml")}, nil, &config.RunConfiguration{
EnabledOptionalTests: enabledOptionalTests,
}, "Container Memory Requests Equal Limits")
}
func TestOptionalIgnoredAndEnabled(t *testing.T) {
t.Parallel()
enabledOptionalTests := make(map[string]struct{})
enabledOptionalTests["container-resource-requests-equal-limits"] = struct{}{}
ignoredTests := make(map[string]struct{})
ignoredTests["container-resource-requests-equal-limits"] = struct{}{}
wasSkipped(t, []ks.NamedReader{testFile("pod-container-memory-requests.yaml")}, &checks.Config{
IgnoredTests: ignoredTests,
}, &config.RunConfiguration{
EnabledOptionalTests: enabledOptionalTests,
}, "Container Memory Requests Equal Limits")
}
func TestOptionalRunCliFlagEnabledDefault(t *testing.T) {
t.Parallel()
enabledOptionalTests := make(map[string]struct{})
enabledOptionalTests["container-resource-requests-equal-limits"] = struct{}{}
testExpectedScoreWithConfig(t,
[]ks.NamedReader{testFile("pod-container-memory-requests.yaml")}, nil, &config.RunConfiguration{
EnabledOptionalTests: enabledOptionalTests,
}, "Container Memory Requests Equal Limits", scorecard.GradeCritical)
}
func TestOptionalRunAnnotationEnabled(t *testing.T) {
t.Parallel()
enabledOptionalTests := make(map[string]struct{})
testExpectedScoreWithConfig(t, []ks.NamedReader{testFile("pod-container-memory-requests-annotation-optional.yaml")}, nil,
&config.RunConfiguration{
EnabledOptionalTests: enabledOptionalTests,
}, "Container Memory Requests Equal Limits", scorecard.GradeCritical)
}
07070100000078000081A4000000000000000000000001680FB1F800000F6B000000000000000000000000000000000000003400000000kube-score-1.20.0/score/poddisruptionbudget_test.gopackage score
import (
"testing"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/assert"
"github.com/zegl/kube-score/scorecard"
)
func TestStatefulSetPodDisruptionBudgetMatches(t *testing.T) {
t.Parallel()
testExpectedScore(t, "statefulset-poddisruptionbudget-v1beta1-matches.yaml", "StatefulSet has PodDisruptionBudget", scorecard.GradeAllOK)
}
func TestStatefulSetPodDisruptionBudgetExpressionMatches(t *testing.T) {
t.Parallel()
testExpectedScore(t, "statefulset-poddisruptionbudget-v1beta1-expression-matches.yaml", "StatefulSet has PodDisruptionBudget", scorecard.GradeAllOK)
}
func TestStatefulSetPodDisruptionBudgetExpressionNoMatch(t *testing.T) {
t.Parallel()
testExpectedScore(t, "statefulset-poddisruptionbudget-v1beta1-expression-no-match.yaml", "StatefulSet has PodDisruptionBudget", scorecard.GradeCritical)
}
func TestStatefulSetPodDisruptionBudgetNoMatch(t *testing.T) {
t.Parallel()
testExpectedScore(t, "statefulset-poddisruptionbudget-v1beta1-no-match.yaml", "StatefulSet has PodDisruptionBudget", scorecard.GradeCritical)
}
func TestDeploymentPodDisruptionBudgetMatches(t *testing.T) {
t.Parallel()
testExpectedScore(t, "deployment-poddisruptionbudget-v1beta1-matches.yaml", "Deployment has PodDisruptionBudget", scorecard.GradeAllOK)
}
func TestDeploymentPodDisruptionBudgetExpressionMatches(t *testing.T) {
t.Parallel()
testExpectedScore(t, "deployment-poddisruptionbudget-v1beta1-expression-matches.yaml", "Deployment has PodDisruptionBudget", scorecard.GradeAllOK)
}
func TestDeploymentPodDisruptionBudgetExpressionNoMatch(t *testing.T) {
t.Parallel()
testExpectedScore(t, "deployment-poddisruptionbudget-v1beta1-expression-no-match.yaml", "Deployment has PodDisruptionBudget", scorecard.GradeCritical)
}
func TestDeploymentPodDisruptionBudgetNoMatch(t *testing.T) {
t.Parallel()
testExpectedScore(t, "deployment-poddisruptionbudget-v1beta1-no-match.yaml", "Deployment has PodDisruptionBudget", scorecard.GradeCritical)
}
func TestDeploymentPodDisruptionBudgetNoPolicy(t *testing.T) {
t.Parallel()
testExpectedScore(t, "deployment-poddisruptionbudget-v1beta1-no-policy.yaml", "PodDisruptionBudget has policy", scorecard.GradeCritical)
}
func TestDeploymentPodDisruptionBudgetV1NoPolicy(t *testing.T) {
t.Parallel()
testExpectedScore(t, "deployment-poddisruptionbudget-v1-no-policy.yaml", "PodDisruptionBudget has policy", scorecard.GradeCritical)
}
func TestDeploymentPodDisruptionBudgetV1Matches(t *testing.T) {
t.Parallel()
testExpectedScore(t, "deployment-poddisruptionbudget-v1-matches.yaml", "Deployment has PodDisruptionBudget", scorecard.GradeAllOK)
}
func TestDeploymentPodDisruptionBudgetV1NoMatch(t *testing.T) {
t.Parallel()
actual := testExpectedScore(t, "deployment-poddisruptionbudget-v1-no-match.yaml", "Deployment has PodDisruptionBudget", scorecard.GradeCritical)
expected := []scorecard.TestScoreComment{
{
Path: "",
Summary: "No matching PodDisruptionBudget was found",
Description: "It's recommended to define a PodDisruptionBudget to avoid unexpected downtime during Kubernetes maintenance operations, such as when draining a node. ",
},
}
diff := cmp.Diff(expected, actual)
assert.Empty(t, diff)
}
func TestDeploymentPodDisruptionBudgetV1NoMatchMatchInOtherNamespace(t *testing.T) {
t.Parallel()
actual := testExpectedScore(t, "deployment-poddisruptionbudget-v1-different-namespace.yaml", "Deployment has PodDisruptionBudget", scorecard.GradeCritical)
expected := []scorecard.TestScoreComment{
{
Path: "",
Summary: "No matching PodDisruptionBudget was found",
Description: "It's recommended to define a PodDisruptionBudget to avoid unexpected downtime during Kubernetes maintenance operations, such as when draining a node. A matching budget was found, but in a different namespace. expected='foo' got='[not-foo bar]'",
},
}
diff := cmp.Diff(expected, actual)
assert.Empty(t, diff)
}
07070100000079000041ED000000000000000000000002680FB1F800000000000000000000000000000000000000000000003500000000kube-score-1.20.0/score/podtopologyspreadconstraints0707010000007A000081A4000000000000000000000001680FB1F80000083B000000000000000000000000000000000000005500000000kube-score-1.20.0/score/podtopologyspreadconstraints/podtopologyspreadconstraints.gopackage podtopologyspreadconstraints
import (
ks "github.com/zegl/kube-score/domain"
"github.com/zegl/kube-score/score/checks"
"github.com/zegl/kube-score/scorecard"
)
func Register(allChecks *checks.Checks) {
allChecks.RegisterPodCheck("Pod Topology Spread Constraints", "Pod Topology Spread Constraints", podTopologySpreadConstraints)
}
func podTopologySpreadConstraints(pod ks.PodSpecer) (score scorecard.TestScore, err error) {
spreads := pod.GetPodTemplateSpec().Spec.TopologySpreadConstraints
if spreads == nil {
score.Grade = scorecard.GradeAllOK
score.AddComment("", "Pod Topology Spread Constraints", "No Pod Topology Spread Constraints set, kube-scheduler defaults assumed")
return
}
for _, spread := range spreads {
if spread.LabelSelector == nil {
score.Grade = scorecard.GradeCritical
score.AddComment("", "Pod Topology Spread Constraints", "No labelSelector detected. A label selector is needed determine the number of pods in a topology domain")
return
}
if spread.MaxSkew == 0 {
score.Grade = scorecard.GradeCritical
score.AddComment("", "Pod Topology Spread Constraints", "MaxSkew is set to zero. This is not allowed.")
return
}
if spread.MinDomains != nil && *spread.MinDomains == 0 {
score.Grade = scorecard.GradeCritical
score.AddComment("", "Pod Topology Spread Constraints", "MaxDomain set to zero. This is not allowed. Constraint behaves if minDomains is set to 1 if nil")
return
}
if spread.TopologyKey == "" {
score.Grade = scorecard.GradeCritical
score.AddComment("", "Pod Topology Spread Constraints", "TopologyKey is not set. This is the key of node labels used to bucket nodes into a domain")
return
}
if spread.WhenUnsatisfiable != "DoNotSchedule" && spread.WhenUnsatisfiable != "ScheduleAnyway" {
score.Grade = scorecard.GradeCritical
score.AddComment("", "Pod Topology Spread Constraints", "Invalid WhenUnsatisfiable setting detected")
return
}
}
score.Grade = scorecard.GradeAllOK
score.AddComment("", "Pod Topology Spread Constraints", "Pod Topology Spread Constraints")
return
}
0707010000007B000081A4000000000000000000000001680FB1F800000672000000000000000000000000000000000000003D00000000kube-score-1.20.0/score/podtopologyspreadconstraints_test.gopackage score
import (
"testing"
"github.com/zegl/kube-score/scorecard"
)
func TestPodTopologySpreadContraintsWithOneConstraint(t *testing.T) {
t.Parallel()
testExpectedScore(t, "pod-topology-spread-constraints-one-constraint.yaml", "Pod Topology Spread Constraints", scorecard.GradeAllOK)
}
func TestPodTopologySpreadContraintsWithTwoConstraints(t *testing.T) {
t.Parallel()
testExpectedScore(t, "pod-topology-spread-constraints-two-constraints.yaml", "Pod Topology Spread Constraints", scorecard.GradeAllOK)
}
func TestPodTopologySpreadContraintsNoLabelSelector(t *testing.T) {
t.Parallel()
testExpectedScore(t, "pod-topology-spread-constraints-no-labelselector.yaml", "Pod Topology Spread Constraints", scorecard.GradeCritical)
}
func TestPodTopologySpreadContraintsInvalidMaxSkew(t *testing.T) {
t.Parallel()
testExpectedScore(t, "pod-topology-spread-constraints-invalid-maxskew.yaml", "Pod Topology Spread Constraints", scorecard.GradeCritical)
}
func TestPodTopologySpreadContraintsInvalidMinDomains(t *testing.T) {
t.Parallel()
testExpectedScore(t, "pod-topology-spread-constraints-invalid-mindomains.yaml", "Pod Topology Spread Constraints", scorecard.GradeCritical)
}
func TestPodTopologySpreadContraintsNoTopologyKey(t *testing.T) {
t.Parallel()
testExpectedScore(t, "pod-topology-spread-constraints-no-topologykey.yaml", "Pod Topology Spread Constraints", scorecard.GradeCritical)
}
func TestPodTopologySpreadContraintsInvalidDirective(t *testing.T) {
t.Parallel()
testExpectedScore(t, "pod-topology-spread-constraints-invalid-whenunsatisfiable.yaml", "Pod Topology Spread Constraints", scorecard.GradeCritical)
}
0707010000007C000081A4000000000000000000000001680FB1F800000F65000000000000000000000000000000000000002600000000kube-score-1.20.0/score/probe_test.gopackage score
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/zegl/kube-score/scorecard"
)
func TestProbesPodAllMissing(t *testing.T) {
t.Parallel()
comments := testExpectedScore(t, "pod-probes-all-missing.yaml", "Pod Probes", scorecard.GradeCritical)
assert.Len(t, comments, 1)
assert.Equal(t, "Container is missing a readinessProbe", comments[0].Summary)
}
func TestProbesPodMissingReady(t *testing.T) {
t.Parallel()
comments := testExpectedScore(t, "pod-probes-missing-ready.yaml", "Pod Probes", scorecard.GradeCritical)
assert.Len(t, comments, 1)
assert.Equal(t, "Container is missing a readinessProbe", comments[0].Summary)
}
func TestProbesPodIdenticalHTTP(t *testing.T) {
t.Parallel()
comments := testExpectedScore(t, "pod-probes-identical-http.yaml", "Pod Probes Identical", scorecard.GradeCritical)
assert.Len(t, comments, 1)
assert.Equal(t, "Container has the same readiness and liveness probe", comments[0].Summary)
}
func TestProbesPodIdenticalTCP(t *testing.T) {
t.Parallel()
comments := testExpectedScore(t, "pod-probes-identical-tcp.yaml", "Pod Probes Identical", scorecard.GradeCritical)
assert.Len(t, comments, 1)
assert.Equal(t, "Container has the same readiness and liveness probe", comments[0].Summary)
}
func TestProbesPodIdenticalExec(t *testing.T) {
t.Parallel()
comments := testExpectedScore(t, "pod-probes-identical-exec.yaml", "Pod Probes Identical", scorecard.GradeCritical)
assert.Len(t, comments, 1)
assert.Equal(t, "Container has the same readiness and liveness probe", comments[0].Summary)
}
func TestProbesTargetedByService(t *testing.T) {
t.Parallel()
comments := testExpectedScore(t, "pod-probes-targeted-by-service.yaml", "Pod Probes", scorecard.GradeCritical)
assert.Len(t, comments, 1)
assert.Equal(t, "Container is missing a readinessProbe", comments[0].Summary)
}
func TestProbesTargetedByServiceSameNamespace(t *testing.T) {
t.Parallel()
comments := testExpectedScore(t, "pod-probes-targeted-by-service-same-namespace.yaml", "Pod Probes", scorecard.GradeCritical)
assert.Len(t, comments, 1)
assert.Equal(t, "Container is missing a readinessProbe", comments[0].Summary)
}
func TestProbesTargetedByServiceSameNamespaceMultiLabels(t *testing.T) {
t.Parallel()
comments := testExpectedScore(t, "pod-probes-targeted-by-service-same-namespace-multi-labels.yaml", "Pod Probes", scorecard.GradeCritical)
assert.Len(t, comments, 1)
assert.Equal(t, "Container is missing a readinessProbe", comments[0].Summary)
}
func TestProbesTargetedByServiceDifferentNamespace(t *testing.T) {
t.Parallel()
comments := testExpectedScore(t, "pod-probes-targeted-by-service-different-namespace.yaml", "Pod Probes", scorecard.GradeAllOK)
assert.Len(t, comments, 1)
assert.Equal(t, "The pod is not targeted by a service, skipping probe checks.", comments[0].Summary)
}
func TestProbesTargetedByServiceNotTargeted(t *testing.T) {
t.Parallel()
comments := testExpectedScore(t, "pod-probes-not-targeted-by-service.yaml", "Pod Probes", scorecard.GradeAllOK)
assert.Len(t, comments, 1)
assert.Equal(t, "The pod is not targeted by a service, skipping probe checks.", comments[0].Summary)
}
func TestProbesTargetedByServiceNotTargetedMultiLabels(t *testing.T) {
t.Parallel()
comments := testExpectedScore(t, "pod-probes-not-targeted-by-service-multi-labels.yaml", "Pod Probes", scorecard.GradeAllOK)
assert.Len(t, comments, 1)
assert.Equal(t, "The pod is not targeted by a service, skipping probe checks.", comments[0].Summary)
}
func TestProbesMultipleContainers(t *testing.T) {
t.Parallel()
comments := testExpectedScore(t, "pod-probes-on-different-containers.yaml", "Pod Probes", scorecard.GradeAllOK)
assert.Len(t, comments, 0)
}
func TestProbesMultipleContainersInit(t *testing.T) {
t.Parallel()
comments := testExpectedScore(t, "pod-probes-on-different-containers-init.yaml", "Pod Probes", scorecard.GradeAllOK)
assert.Len(t, comments, 0)
}
0707010000007D000041ED000000000000000000000002680FB1F800000000000000000000000000000000000000000000001F00000000kube-score-1.20.0/score/probes0707010000007E000081A4000000000000000000000001680FB1F800001875000000000000000000000000000000000000002900000000kube-score-1.20.0/score/probes/probes.gopackage probes
import (
ks "github.com/zegl/kube-score/domain"
"github.com/zegl/kube-score/score/checks"
"github.com/zegl/kube-score/score/internal"
"github.com/zegl/kube-score/scorecard"
corev1 "k8s.io/api/core/v1"
)
// Register registers the pod checks, including the new one for identical probes.
func Register(allChecks *checks.Checks, services ks.Services) {
allChecks.RegisterPodCheck("Pod Probes", `Makes sure that all Pods have safe probe configurations`, containerProbes(services.Services()))
allChecks.RegisterPodCheck("Pod Probes Identical", `Container has the same readiness and liveness probe`, containerProbesIdentical(services.Services()))
}
// containerProbes returns a function that checks if all probes are defined correctly in the Pod.
// Only one probe of each type is required on the entire pod.
// ReadinessProbes are not required if the pod is not targeted by a Service.
//
// containerProbes takes a slice of all defined Services as input.
func containerProbes(allServices []ks.Service) func(ks.PodSpecer) (scorecard.TestScore, error) {
return func(ps ks.PodSpecer) (score scorecard.TestScore, err error) {
typeMeta := ps.GetTypeMeta()
if typeMeta.Kind == "CronJob" && typeMeta.GroupVersionKind().Group == "batch" || typeMeta.Kind == "Job" && typeMeta.GroupVersionKind().Group == "batch" {
score.Grade = scorecard.GradeAllOK
return score, nil
}
podTemplate := ps.GetPodTemplateSpec()
allContainers := podTemplate.Spec.InitContainers
allContainers = append(allContainers, podTemplate.Spec.Containers...)
hasReadinessProbe := false
hasLivenessProbe := false
isTargetedByService := isTargetedByService(allServices, podTemplate)
// Check probes for each container
for _, container := range allContainers {
hasReadinessProbe, hasLivenessProbe = checkBasicProbes(container, hasReadinessProbe, hasLivenessProbe)
}
// If pod isn't targeted by a service, skip probe checks
if !isTargetedByService {
score.Grade = scorecard.GradeAllOK
score.Skipped = true
score.AddComment("", "The pod is not targeted by a service, skipping probe checks.", "")
return score, nil
}
// Evaluate probe checks
if !hasReadinessProbe {
score.Grade = scorecard.GradeCritical
score.AddCommentWithURL("", "Container is missing a readinessProbe",
"A readinessProbe should be used to indicate when the service is ready to receive traffic. "+
"Without it, the Pod is risking to receive traffic before it has booted. "+
"It's also used during rollouts, and can prevent downtime if a new version of the application is failing.",
"https://github.com/zegl/kube-score/blob/master/README_PROBES.md",
)
return score, nil
}
if !hasLivenessProbe {
score.Grade = scorecard.GradeAlmostOK
score.AddCommentWithURL("", "Container is missing a livenessProbe",
"A livenessProbe can be used to restart the container if it's deadlocked or has crashed without exiting. "+
"It's only recommended to setup a livenessProbe if you really need one.",
"https://github.com/zegl/kube-score/blob/master/README_PROBES.md",
)
return score, nil
}
score.Grade = scorecard.GradeAllOK
return score, nil
}
}
// containerProbesIdentical checks if the container's readiness and liveness probes are identical.
func containerProbesIdentical(allServices []ks.Service) func(ks.PodSpecer) (scorecard.TestScore, error) {
return func(ps ks.PodSpecer) (score scorecard.TestScore, err error) {
typeMeta := ps.GetTypeMeta()
if typeMeta.Kind == "CronJob" && typeMeta.GroupVersionKind().Group == "batch" || typeMeta.Kind == "Job" && typeMeta.GroupVersionKind().Group == "batch" {
score.Grade = scorecard.GradeAllOK
return score, nil
}
podTemplate := ps.GetPodTemplateSpec()
allContainers := podTemplate.Spec.InitContainers
allContainers = append(allContainers, podTemplate.Spec.Containers...)
probesAreIdentical := false
for _, container := range allContainers {
if container.ReadinessProbe != nil && container.LivenessProbe != nil {
if areProbesIdentical(container.ReadinessProbe, container.LivenessProbe) {
probesAreIdentical = true
break
}
}
}
// If probes are identical, mark it as a critical issue
if probesAreIdentical {
score.Grade = scorecard.GradeCritical
score.AddCommentWithURL(
"", "Container has the same readiness and liveness probe",
"Using the same probe for liveness and readiness is very likely dangerous. It's generally better to avoid re-using the same probe.",
"https://github.com/zegl/kube-score/blob/master/README_PROBES.md",
)
return score, nil
}
// No identical probes found, return OK grade
score.Grade = scorecard.GradeAllOK
return score, nil
}
}
// areProbesIdentical checks if readiness and liveness probes are identical.
func areProbesIdentical(r, l *corev1.Probe) bool {
if r.HTTPGet != nil && l.HTTPGet != nil {
return r.HTTPGet.Path == l.HTTPGet.Path && r.HTTPGet.Port.IntValue() == l.HTTPGet.Port.IntValue()
}
if r.TCPSocket != nil && l.TCPSocket != nil {
return r.TCPSocket.Port == l.TCPSocket.Port
}
if r.Exec != nil && l.Exec != nil {
if len(r.Exec.Command) == len(l.Exec.Command) {
for i, v := range r.Exec.Command {
if l.Exec.Command[i] != v {
return false
}
}
return true
}
}
return false
}
// checkBasicProbes checks for the presence of readiness and liveness probes.
func checkBasicProbes(container corev1.Container, hasReadinessProbe, hasLivenessProbe bool) (bool, bool) {
if container.ReadinessProbe != nil {
hasReadinessProbe = true
}
if container.LivenessProbe != nil {
hasLivenessProbe = true
}
return hasReadinessProbe, hasLivenessProbe
}
// isTargetedByService checks if the pod is targeted by any of the services.
func isTargetedByService(allServices []ks.Service, podTemplate corev1.PodTemplateSpec) bool {
for _, s := range allServices {
if podIsTargetedByService(podTemplate, s.Service()) {
return true
}
}
return false
}
func podIsTargetedByService(pod corev1.PodTemplateSpec, service corev1.Service) bool {
if pod.Namespace != service.Namespace {
return false
}
return internal.LabelSelectorMatchesLabels(
service.Spec.Selector,
pod.GetObjectMeta().GetLabels(),
)
}
0707010000007F000081A4000000000000000000000001680FB1F800000A73000000000000000000000000000000000000002E00000000kube-score-1.20.0/score/probes/probes_test.gopackage probes
import (
"testing"
"github.com/stretchr/testify/assert"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func TestPodIsTargetedByService(t *testing.T) {
t.Run("single label match", func(t *testing.T) {
res := podIsTargetedByService(v1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{"foo": "bar"},
},
},
v1.Service{
Spec: v1.ServiceSpec{
Selector: map[string]string{"foo": "bar"},
},
},
)
assert.True(t, res)
})
t.Run("single label mismatch", func(t *testing.T) {
res := podIsTargetedByService(v1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{"foo": "bar"},
},
},
v1.Service{
Spec: v1.ServiceSpec{
Selector: map[string]string{"foo": "baz"},
},
},
)
assert.False(t, res)
})
t.Run("multi label match", func(t *testing.T) {
res := podIsTargetedByService(v1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{
"foo1": "bar1",
"foo2": "bar2",
},
},
},
v1.Service{
Spec: v1.ServiceSpec{
Selector: map[string]string{
"foo1": "bar1",
"foo2": "bar2",
},
},
},
)
assert.True(t, res)
})
t.Run("multi non full match", func(t *testing.T) {
res := podIsTargetedByService(v1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{
"foo1": "bar1",
"foo2": "bar2",
},
},
},
v1.Service{
Spec: v1.ServiceSpec{
Selector: map[string]string{
"foo1": "bar1",
"foo2": "bar-whatever",
},
},
},
)
assert.False(t, res)
})
t.Run("multi label match same namespace", func(t *testing.T) {
res := podIsTargetedByService(v1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Namespace: "foospace",
Labels: map[string]string{
"foo1": "bar1",
"foo2": "bar2",
},
},
},
v1.Service{
ObjectMeta: metav1.ObjectMeta{Namespace: "foospace"},
Spec: v1.ServiceSpec{
Selector: map[string]string{
"foo1": "bar1",
"foo2": "bar2",
},
},
},
)
assert.True(t, res)
})
t.Run("multi label match different namespace", func(t *testing.T) {
res := podIsTargetedByService(v1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Namespace: "foospace",
Labels: map[string]string{
"foo1": "bar1",
"foo2": "bar2",
},
},
},
v1.Service{
ObjectMeta: metav1.ObjectMeta{Namespace: "someOtherNamespace"},
Spec: v1.ServiceSpec{
Selector: map[string]string{
"foo1": "bar1",
"foo2": "bar2",
},
},
},
)
assert.False(t, res)
})
}
07070100000080000081A4000000000000000000000001680FB1F800001B0B000000000000000000000000000000000000002100000000kube-score-1.20.0/score/score.gopackage score
import (
"errors"
"github.com/zegl/kube-score/config"
ks "github.com/zegl/kube-score/domain"
"github.com/zegl/kube-score/score/apps"
"github.com/zegl/kube-score/score/checks"
"github.com/zegl/kube-score/score/container"
"github.com/zegl/kube-score/score/cronjob"
"github.com/zegl/kube-score/score/deployment"
"github.com/zegl/kube-score/score/disruptionbudget"
"github.com/zegl/kube-score/score/hpa"
"github.com/zegl/kube-score/score/ingress"
"github.com/zegl/kube-score/score/meta"
"github.com/zegl/kube-score/score/networkpolicy"
"github.com/zegl/kube-score/score/podtopologyspreadconstraints"
"github.com/zegl/kube-score/score/probes"
"github.com/zegl/kube-score/score/security"
"github.com/zegl/kube-score/score/service"
"github.com/zegl/kube-score/score/stable"
"github.com/zegl/kube-score/scorecard"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func RegisterAllChecks(allObjects ks.AllTypes, checksConfig *checks.Config, runConfig *config.RunConfiguration) *checks.Checks {
allChecks := checks.New(checksConfig)
if runConfig == nil {
runConfig = &config.RunConfiguration{}
}
deployment.Register(allChecks, allObjects, runConfig.MinReplicasDeployment)
ingress.Register(allChecks, allObjects)
cronjob.Register(allChecks)
container.Register(allChecks, runConfig.IgnoreContainerCpuLimitRequirement, runConfig.IgnoreContainerMemoryLimitRequirement)
disruptionbudget.Register(allChecks, allObjects)
networkpolicy.Register(allChecks, allObjects, allObjects, allObjects)
probes.Register(allChecks, allObjects)
security.Register(allChecks)
service.Register(allChecks, allObjects, allObjects)
stable.Register(runConfig.KubernetesVersion, allChecks)
apps.Register(allChecks, allObjects.HorizontalPodAutoscalers(), allObjects.Services())
meta.Register(allChecks)
hpa.Register(allChecks, allObjects.Metas(), runConfig.MinReplicasHPA)
podtopologyspreadconstraints.Register(allChecks)
return allChecks
}
type podSpeccer struct {
typeMeta metav1.TypeMeta
objectMeta metav1.ObjectMeta
spec corev1.PodTemplateSpec
}
func (p *podSpeccer) GetTypeMeta() metav1.TypeMeta {
return p.typeMeta
}
func (p *podSpeccer) GetObjectMeta() metav1.ObjectMeta {
return p.objectMeta
}
func (p *podSpeccer) GetPodTemplateSpec() corev1.PodTemplateSpec {
return p.spec
}
func (p *podSpeccer) FileLocation() ks.FileLocation {
return ks.FileLocation{}
}
// Score runs a pre-configured list of tests against the files defined in the configuration, and returns a scorecard.
// Additional configuration and tuning parameters can be provided via the config.
func Score(allObjects ks.AllTypes, allChecks *checks.Checks, cnf *config.RunConfiguration) (*scorecard.Scorecard, error) {
if cnf == nil {
cnf = &config.RunConfiguration{}
}
if allChecks == nil {
return nil, errors.New("no checks registered")
}
scoreCard := scorecard.New()
newObject := func(typeMeta metav1.TypeMeta, objectMeta metav1.ObjectMeta) *scorecard.ScoredObject {
return scoreCard.NewObject(typeMeta, objectMeta, cnf)
}
for _, ingress := range allObjects.Ingresses() {
o := newObject(ingress.GetTypeMeta(), ingress.GetObjectMeta())
for _, test := range allChecks.Ingresses() {
fn, err := test.Fn(ingress)
if err != nil {
return nil, err
}
o.Add(fn, test.Check, ingress, ingress.GetObjectMeta().Annotations)
}
}
for _, meta := range allObjects.Metas() {
o := newObject(meta.TypeMeta, meta.ObjectMeta)
for _, test := range allChecks.Metas() {
fn, err := test.Fn(meta)
if err != nil {
return nil, err
}
o.Add(fn, test.Check, meta, meta.ObjectMeta.Annotations)
}
}
for _, pod := range allObjects.Pods() {
o := newObject(pod.Pod().TypeMeta, pod.Pod().ObjectMeta)
for _, test := range allChecks.Pods() {
podTemplateSpec := corev1.PodTemplateSpec{
ObjectMeta: pod.Pod().ObjectMeta,
Spec: pod.Pod().Spec,
}
score, _ := test.Fn(&podSpeccer{
typeMeta: pod.Pod().TypeMeta,
objectMeta: pod.Pod().ObjectMeta,
spec: podTemplateSpec,
})
o.Add(score, test.Check, pod, pod.Pod().ObjectMeta.Annotations)
}
}
for _, podspecer := range allObjects.PodSpeccers() {
o := newObject(podspecer.GetTypeMeta(), podspecer.GetObjectMeta())
for _, test := range allChecks.Pods() {
score, _ := test.Fn(podspecer)
o.Add(score, test.Check, podspecer,
podspecer.GetObjectMeta().Annotations,
podspecer.GetPodTemplateSpec().Annotations,
)
}
}
for _, service := range allObjects.Services() {
o := newObject(service.Service().TypeMeta, service.Service().ObjectMeta)
for _, test := range allChecks.Services() {
fn, err := test.Fn(service.Service())
if err != nil {
return nil, err
}
o.Add(fn, test.Check, service, service.Service().Annotations)
}
}
for _, statefulset := range allObjects.StatefulSets() {
o := newObject(statefulset.StatefulSet().TypeMeta, statefulset.StatefulSet().ObjectMeta)
for _, test := range allChecks.StatefulSets() {
fn, err := test.Fn(statefulset.StatefulSet())
if err != nil {
return nil, err
}
o.Add(fn, test.Check, statefulset, statefulset.StatefulSet().ObjectMeta.Annotations)
}
}
for _, deployment := range allObjects.Deployments() {
o := newObject(deployment.Deployment().TypeMeta, deployment.Deployment().ObjectMeta)
for _, test := range allChecks.Deployments() {
res, err := test.Fn(deployment.Deployment())
if err != nil {
return nil, err
}
o.Add(res, test.Check, deployment, deployment.Deployment().ObjectMeta.Annotations)
}
}
for _, netpol := range allObjects.NetworkPolicies() {
o := newObject(netpol.NetworkPolicy().TypeMeta, netpol.NetworkPolicy().ObjectMeta)
for _, test := range allChecks.NetworkPolicies() {
fn, err := test.Fn(netpol.NetworkPolicy())
if err != nil {
return nil, err
}
o.Add(fn, test.Check, netpol, netpol.NetworkPolicy().ObjectMeta.Annotations)
}
}
for _, cjob := range allObjects.CronJobs() {
o := newObject(cjob.GetTypeMeta(), cjob.GetObjectMeta())
for _, test := range allChecks.CronJobs() {
fn, err := test.Fn(cjob)
if err != nil {
return nil, err
}
o.Add(fn, test.Check, cjob, cjob.GetObjectMeta().Annotations)
}
}
for _, hpa := range allObjects.HorizontalPodAutoscalers() {
o := newObject(hpa.GetTypeMeta(), hpa.GetObjectMeta())
for _, test := range allChecks.HorizontalPodAutoscalers() {
fn, err := test.Fn(hpa)
if err != nil {
return nil, err
}
o.Add(fn, test.Check, hpa, hpa.GetObjectMeta().Annotations)
}
}
for _, pdb := range allObjects.PodDisruptionBudgets() {
o := newObject(pdb.GetTypeMeta(), pdb.GetObjectMeta())
for _, test := range allChecks.PodDisruptionBudgets() {
fn, err := test.Fn(pdb)
if err != nil {
return nil, err
}
o.Add(fn, test.Check, pdb, pdb.GetObjectMeta().Annotations)
}
}
return &scoreCard, nil
}
07070100000081000081A4000000000000000000000001680FB1F800004766000000000000000000000000000000000000002600000000kube-score-1.20.0/score/score_test.gopackage score
import (
"os"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/assert"
"github.com/zegl/kube-score/config"
ks "github.com/zegl/kube-score/domain"
"github.com/zegl/kube-score/parser"
"github.com/zegl/kube-score/score/checks"
"github.com/zegl/kube-score/scorecard"
)
func testFile(name string) *os.File {
fp, err := os.Open("testdata/" + name)
if err != nil {
panic(err)
}
return fp
}
// testExpectedScoreWithConfig runs all tests, but makes sure that the test for "testcase" was executed, and that
// the grade is set to expectedScore. The function returns the comments of "testcase".
func testExpectedScoreWithConfig(t *testing.T, files []ks.NamedReader, checksConfig *checks.Config, runConfig *config.RunConfiguration, testcase string, expectedScore scorecard.Grade) []scorecard.TestScoreComment {
sc, err := testScore(files, checksConfig, runConfig)
assert.NoError(t, err)
for _, objectScore := range sc {
for _, s := range objectScore.Checks {
if s.Check.Name == testcase {
assert.Equal(t, expectedScore, s.Grade)
return s.Comments
}
}
}
t.Error("Was not tested")
return nil
}
func wasSkipped(t *testing.T, files []ks.NamedReader, checksConfig *checks.Config, runConfig *config.RunConfiguration, testcase string) bool {
sc, err := testScore(files, checksConfig, runConfig)
assert.NoError(t, err)
for _, objectScore := range sc {
for _, s := range objectScore.Checks {
if s.Check.Name == testcase {
return s.Skipped
}
}
}
assert.Fail(t, "test was not run")
return false
}
func getSummaries(t *testing.T, files []ks.NamedReader, checksConfig *checks.Config, runConfig *config.RunConfiguration, testcase string) []string {
sc, err := testScore(files, checksConfig, runConfig)
assert.NoError(t, err)
var summaries []string
for _, objectScore := range sc {
for _, s := range objectScore.Checks {
if s.Check.Name == testcase {
for _, c := range s.Comments {
summaries = append(summaries, c.Summary)
}
return summaries
}
}
}
assert.Fail(t, "test was not run")
return summaries
}
func testScore(files []ks.NamedReader, checksConfig *checks.Config, runConfig *config.RunConfiguration) (scorecard.Scorecard, error) {
p, err := parser.New(nil)
if err != nil {
return nil, err
}
parsed, err := p.ParseFiles(files)
if err != nil {
return nil, err
}
if checksConfig == nil {
checksConfig = &checks.Config{}
}
if runConfig == nil {
runConfig = &config.RunConfiguration{}
}
allChecks := RegisterAllChecks(parsed, checksConfig, runConfig)
card, err := Score(parsed, allChecks, runConfig)
if err != nil {
return nil, err
}
return *card, err
}
func testExpectedScore(t *testing.T, filename string, testcase string, expectedScore scorecard.Grade) []scorecard.TestScoreComment {
return testExpectedScoreWithConfig(t, []ks.NamedReader{testFile(filename)}, nil, &config.RunConfiguration{
KubernetesVersion: config.Semver{Major: 1, Minor: 18},
}, testcase, expectedScore)
}
func TestPodContainerNoResources(t *testing.T) {
t.Parallel()
testExpectedScore(t, "pod-test-resources-none.yaml", "Container Resources", scorecard.GradeCritical)
}
func TestPodContainerResourceLimits(t *testing.T) {
t.Parallel()
testExpectedScore(t, "pod-test-resources-only-limits.yaml", "Container Resources", scorecard.GradeWarning)
}
func TestPodContainerResourceLimitsAndRequests(t *testing.T) {
t.Parallel()
testExpectedScore(t, "pod-test-resources-limits-and-requests.yaml", "Container Resources", scorecard.GradeAllOK)
}
func TestPodContainerResourceLimitCpuNotRequired(t *testing.T) {
t.Parallel()
testExpectedScoreWithConfig(t, []ks.NamedReader{testFile("pod-test-resources-limits-and-requests-no-cpu-limit.yaml")}, nil, &config.RunConfiguration{
IgnoreContainerCpuLimitRequirement: true,
}, "Container Resources", scorecard.GradeAllOK)
}
func TestPodContainerResourceLimitCpuRequired(t *testing.T) {
t.Parallel()
testExpectedScoreWithConfig(t, []ks.NamedReader{testFile("pod-test-resources-limits-and-requests-no-cpu-limit.yaml")}, nil, &config.RunConfiguration{
IgnoreContainerCpuLimitRequirement: false,
}, "Container Resources", scorecard.GradeCritical)
}
func TestPodContainerResourceNoLimitRequired(t *testing.T) {
t.Parallel()
testExpectedScoreWithConfig(t, []ks.NamedReader{testFile("pod-test-resources-no-limits.yaml")}, nil, &config.RunConfiguration{
IgnoreContainerCpuLimitRequirement: true,
IgnoreContainerMemoryLimitRequirement: true,
}, "Container Resources", scorecard.GradeAllOK)
}
func TestPodContainerResourceRequestsEqualLimits(t *testing.T) {
t.Parallel()
structMap := make(map[string]struct{})
structMap["container-resource-requests-equal-limits"] = struct{}{}
testExpectedScoreWithConfig(t, []ks.NamedReader{testFile("pod-test-resources-limits-and-requests.yaml")}, nil, &config.RunConfiguration{
EnabledOptionalTests: structMap,
}, "Container Resource Requests Equal Limits", scorecard.GradeAllOK)
}
func TestPodContainerMemoryRequestsEqualLimits(t *testing.T) {
t.Parallel()
structMap := make(map[string]struct{})
structMap["container-memory-requests-equal-limits"] = struct{}{}
testExpectedScoreWithConfig(t, []ks.NamedReader{testFile("pod-test-resources-limits-and-requests.yaml")}, nil, &config.RunConfiguration{
EnabledOptionalTests: structMap,
}, "Container Memory Requests Equal Limits", scorecard.GradeAllOK)
}
func TestPodContainerCPURequestsEqualLimits(t *testing.T) {
t.Parallel()
structMap := make(map[string]struct{})
structMap["container-cpu-requests-equal-limits"] = struct{}{}
testExpectedScoreWithConfig(t, []ks.NamedReader{testFile("pod-test-resources-limits-and-requests.yaml")}, nil, &config.RunConfiguration{
EnabledOptionalTests: structMap,
}, "Container CPU Requests Equal Limits", scorecard.GradeAllOK)
}
func TestPodContainerResourceRequestsEqualLimitsNoLimits(t *testing.T) {
t.Parallel()
structMap := make(map[string]struct{})
structMap["container-resource-requests-equal-limits"] = struct{}{}
testExpectedScoreWithConfig(t, []ks.NamedReader{testFile("pod-test-resources-no-limits.yaml")}, nil, &config.RunConfiguration{
EnabledOptionalTests: structMap,
}, "Container Resource Requests Equal Limits", scorecard.GradeCritical)
}
func TestPodContainerResourceRequestsEqualLimitsNoLimitsAnnotation(t *testing.T) {
t.Parallel()
testExpectedScoreWithConfig(t, []ks.NamedReader{testFile("pod-test-resources-no-limits-annotation-optional.yaml")}, nil, &config.RunConfiguration{
UseOptionalChecksAnnotation: true,
}, "Container Resource Requests Equal Limits", scorecard.GradeCritical)
}
func TestPodContainerMemoryRequestsEqualLimitsNoLimits(t *testing.T) {
t.Parallel()
structMap := make(map[string]struct{})
structMap["container-memory-requests-equal-limits"] = struct{}{}
testExpectedScoreWithConfig(t, []ks.NamedReader{testFile("pod-test-resources-no-limits.yaml")}, nil, &config.RunConfiguration{
EnabledOptionalTests: structMap,
}, "Container Memory Requests Equal Limits", scorecard.GradeCritical)
}
func TestPodContainerMemoryRequestsEqualLimitsNoLimitsAnnotation(t *testing.T) {
t.Parallel()
testExpectedScoreWithConfig(t, []ks.NamedReader{testFile("pod-test-resources-no-limits-annotation-optional.yaml")}, nil, &config.RunConfiguration{
UseOptionalChecksAnnotation: true,
}, "Container Memory Requests Equal Limits", scorecard.GradeCritical)
}
func TestPodContainerCPURequestsEqualLimitsNoLimits(t *testing.T) {
t.Parallel()
structMap := make(map[string]struct{})
structMap["container-cpu-requests-equal-limits"] = struct{}{}
testExpectedScoreWithConfig(t, []ks.NamedReader{testFile("pod-test-resources-no-limits.yaml")}, nil, &config.RunConfiguration{
EnabledOptionalTests: structMap,
}, "Container CPU Requests Equal Limits", scorecard.GradeCritical)
}
func TestPodContainerCPURequestsEqualLimitsNoLimitsAnnotation(t *testing.T) {
t.Parallel()
testExpectedScoreWithConfig(t, []ks.NamedReader{testFile("pod-test-resources-no-limits-annotation-optional.yaml")}, nil, &config.RunConfiguration{
UseOptionalChecksAnnotation: true,
}, "Container CPU Requests Equal Limits", scorecard.GradeCritical)
}
func TestDeploymentResources(t *testing.T) {
t.Parallel()
testExpectedScore(t, "deployment-test-resources.yaml", "Container Resources", scorecard.GradeWarning)
}
func TestStatefulSetResources(t *testing.T) {
t.Parallel()
testExpectedScore(t, "statefulset-test-resources.yaml", "Container Resources", scorecard.GradeWarning)
}
func TestPodContainerTagLatest(t *testing.T) {
t.Parallel()
testExpectedScore(t, "pod-image-tag-latest.yaml", "Container Image Tag", scorecard.GradeCritical)
}
func TestPodContainerTagFixed(t *testing.T) {
t.Parallel()
testExpectedScore(t, "pod-image-tag-fixed.yaml", "Container Image Tag", scorecard.GradeAllOK)
}
func TestPodContainerPullPolicyUndefined(t *testing.T) {
t.Parallel()
testExpectedScore(t, "pod-image-pullpolicy-undefined.yaml", "Container Image Pull Policy", scorecard.GradeCritical)
}
func TestPodContainerPullPolicyUndefinedLatestTag(t *testing.T) {
t.Parallel()
testExpectedScore(t, "pod-image-pullpolicy-undefined-latest-tag.yaml", "Container Image Pull Policy", scorecard.GradeAllOK)
}
func TestPodContainerPullPolicyUndefinedNoTag(t *testing.T) {
t.Parallel()
testExpectedScore(t, "pod-image-pullpolicy-undefined-no-tag.yaml", "Container Image Pull Policy", scorecard.GradeAllOK)
}
func TestPodContainerPullPolicyNever(t *testing.T) {
t.Parallel()
testExpectedScore(t, "pod-image-pullpolicy-never.yaml", "Container Image Pull Policy", scorecard.GradeCritical)
}
func TestPodContainerPullPolicyAlways(t *testing.T) {
t.Parallel()
testExpectedScore(t, "pod-image-pullpolicy-always.yaml", "Container Image Pull Policy", scorecard.GradeAllOK)
}
func TestConfigMapMultiDash(t *testing.T) {
t.Parallel()
_, err := testScore([]ks.NamedReader{testFile("configmap-multi-dash.yaml")}, nil, nil)
assert.Nil(t, err)
}
func TestAnnotationIgnore(t *testing.T) {
t.Parallel()
s, err := testScore([]ks.NamedReader{testFile("ignore-annotation-service.yaml")}, nil, &config.RunConfiguration{
UseIgnoreChecksAnnotation: true,
})
assert.Nil(t, err)
assert.Len(t, s, 1)
tested := false
for _, o := range s {
for _, c := range o.Checks {
if c.Check.ID == "service-type" {
assert.True(t, c.Skipped)
tested = true
}
}
assert.Equal(t, "node-port-service-with-ignore", o.ObjectMeta.Name)
}
assert.True(t, tested)
}
func TestAnnotationIgnoreDisabled(t *testing.T) {
t.Parallel()
s, err := testScore([]ks.NamedReader{testFile("ignore-annotation-service.yaml")}, nil, &config.RunConfiguration{
UseIgnoreChecksAnnotation: false,
})
assert.Nil(t, err)
assert.Len(t, s, 1)
tested := false
for _, o := range s {
for _, c := range o.Checks {
if c.Check.ID == "service-type" {
assert.False(t, c.Skipped)
assert.Equal(t, scorecard.GradeWarning, c.Grade)
tested = true
}
}
assert.Equal(t, "node-port-service-with-ignore", o.ObjectMeta.Name)
}
assert.True(t, tested)
}
func TestList(t *testing.T) {
t.Parallel()
s, err := testScore([]ks.NamedReader{testFile("list.yaml")}, nil, nil)
assert.Nil(t, err)
assert.Len(t, s, 2)
hasService := false
hasDeployment := false
for _, obj := range s {
if obj.ObjectMeta.Name == "list-service-test" {
hasService = true
}
if obj.ObjectMeta.Name == "list-deployment-test" {
hasDeployment = true
}
assert.Condition(t, func() bool { return len(obj.Checks) > 2 })
}
assert.True(t, hasService)
assert.True(t, hasDeployment)
}
// Note the input file specifies a condition that would fail the optional matching request and limit test, but returns GradeAllOK
// when only the default case is evaluated
func TestPodContainerStorageEphemeralRequestAndLimitOK(t *testing.T) {
t.Parallel()
testExpectedScore(t, "pod-ephemeral-storage-request-nomatch-limit.yaml", "Container Ephemeral Storage Request and Limit", scorecard.GradeAllOK)
}
func TestPodContainerStorageEphemeralNoLimit(t *testing.T) {
t.Parallel()
testExpectedScore(t, "pod-ephemeral-storage-missing-limit.yaml", "Container Ephemeral Storage Request and Limit", scorecard.GradeCritical)
}
func TestPodContainerStorageEphemeralNoRequest(t *testing.T) {
t.Parallel()
testExpectedScore(t, "pod-ephemeral-storage-missing-request.yaml", "Container Ephemeral Storage Request and Limit", scorecard.GradeWarning)
}
func TestPodContainerStorageEphemeralRequestEqualsLimit(t *testing.T) {
t.Parallel()
structMap := make(map[string]struct{})
structMap["container-ephemeral-storage-request-equals-limit"] = struct{}{}
testExpectedScoreWithConfig(t, []ks.NamedReader{testFile("pod-ephemeral-storage-request-matches-limit.yaml")}, nil, &config.RunConfiguration{
EnabledOptionalTests: structMap,
}, "Container Ephemeral Storage Request Equals Limit", scorecard.GradeAllOK)
}
func TestPodContainerStorageEphemeralRequestNoMatchLimit(t *testing.T) {
t.Parallel()
structMap := make(map[string]struct{})
structMap["container-ephemeral-storage-request-equals-limit"] = struct{}{}
testExpectedScoreWithConfig(t, []ks.NamedReader{testFile("pod-ephemeral-storage-request-nomatch-limit.yaml")}, nil, &config.RunConfiguration{
EnabledOptionalTests: structMap,
}, "Container Ephemeral Storage Request Equals Limit", scorecard.GradeCritical)
}
func TestPodContainerStorageEphemeralRequestNoMatchLimitAnnotation(t *testing.T) {
t.Parallel()
testExpectedScoreWithConfig(t, []ks.NamedReader{testFile("pod-ephemeral-storage-request-nomatch-limit-annotation-optional.yaml")}, nil, &config.RunConfiguration{
UseOptionalChecksAnnotation: true,
}, "Container Ephemeral Storage Request Equals Limit", scorecard.GradeCritical)
}
func TestPodContainerStorageEphemeralIgnoreAnnotation(t *testing.T) {
t.Parallel()
s, err := testScore([]ks.NamedReader{testFile("pod-ephemeral-storage-annotation-ignore.yaml")}, nil, &config.RunConfiguration{
UseIgnoreChecksAnnotation: true,
})
assert.Nil(t, err)
assert.Len(t, s, 1)
tested := false
for _, o := range s {
for _, c := range o.Checks {
if c.Check.ID == "container-resources" {
assert.True(t, c.Skipped)
tested = true
}
}
assert.Equal(t, "pod-ephemeral-storage-annotation-ignore", o.ObjectMeta.Name)
}
assert.True(t, tested)
}
func TestPodContainerPortsContainerPortMissing(t *testing.T) {
t.Parallel()
structMap := make(map[string]struct{})
structMap["container-ports-check"] = struct{}{}
testExpectedScoreWithConfig(t, []ks.NamedReader{testFile("pod-container-ports-missing-containerport.yaml")}, nil, &config.RunConfiguration{
EnabledOptionalTests: structMap,
}, "Container Ports Check", scorecard.GradeCritical)
}
func TestPodContainerPortsContainerPortMissingAnnotation(t *testing.T) {
t.Parallel()
testExpectedScoreWithConfig(t, []ks.NamedReader{testFile("pod-container-ports-missing-containerport-annotation-optional.yaml")}, nil, &config.RunConfiguration{
UseOptionalChecksAnnotation: true,
}, "Container Ports Check", scorecard.GradeCritical)
}
func TestPodContainerPortsDuplicateNames(t *testing.T) {
t.Parallel()
structMap := make(map[string]struct{})
structMap["container-ports-check"] = struct{}{}
testExpectedScoreWithConfig(t, []ks.NamedReader{testFile("pod-container-ports-duplicate-names.yaml")}, nil, &config.RunConfiguration{
EnabledOptionalTests: structMap,
}, "Container Ports Check", scorecard.GradeCritical)
}
func TestPodContainerPortsNameLength(t *testing.T) {
t.Parallel()
structMap := make(map[string]struct{})
structMap["container-ports-check"] = struct{}{}
testExpectedScoreWithConfig(t, []ks.NamedReader{testFile("pod-container-ports-name-too-long.yaml")}, nil, &config.RunConfiguration{
EnabledOptionalTests: structMap,
}, "Container Ports Check", scorecard.GradeCritical)
}
func TestPodContainerPortsOK(t *testing.T) {
t.Parallel()
structMap := make(map[string]struct{})
structMap["container-ports-check"] = struct{}{}
testExpectedScoreWithConfig(t, []ks.NamedReader{testFile("pod-container-ports-ok.yaml")}, nil, &config.RunConfiguration{
EnabledOptionalTests: structMap,
}, "Container Ports Check", scorecard.GradeAllOK)
}
func TestPodEnvOK(t *testing.T) {
t.Parallel()
structMap := make(map[string]struct{})
structMap["environment-variable-key-duplication"] = struct{}{}
testExpectedScoreWithConfig(t, []ks.NamedReader{testFile("pod-env-ok.yaml")}, nil, &config.RunConfiguration{
EnabledOptionalTests: structMap,
}, "Environment Variable Key Duplication", scorecard.GradeAllOK)
}
func TestPodEnvDuplicated(t *testing.T) {
t.Parallel()
structMap := make(map[string]struct{})
structMap["environment-variable-key-duplication"] = struct{}{}
actual := testExpectedScoreWithConfig(t, []ks.NamedReader{testFile("pod-env-duplicated.yaml")}, nil, &config.RunConfiguration{
EnabledOptionalTests: structMap,
}, "Environment Variable Key Duplication", scorecard.GradeCritical)
expected := []scorecard.TestScoreComment{
{
Path: "foobar",
Summary: "Environment Variable Key Duplication",
Description: "Container environment variable key 'bar' is duplicated",
},
{
Path: "foobar",
Summary: "Environment Variable Key Duplication",
Description: "Container environment variable key 'baz' is duplicated",
},
}
diff := cmp.Diff(expected, actual)
assert.Empty(t, diff)
}
func TestMultipleIgnoreAnnotations(t *testing.T) {
t.Parallel()
s, err := testScore([]ks.NamedReader{testFile("kube-score-ignore-annotations.yaml")}, nil, &config.RunConfiguration{
UseIgnoreChecksAnnotation: true,
})
assert.Nil(t, err)
assert.Len(t, s, 1)
tested := false
skipped := false
for _, o := range s {
for _, c := range o.Checks {
// implied by the ignore container-resources annotation
if c.Check.ID == "container-ephemeral-storage-request-and-limit" {
assert.True(t, c.Skipped)
skipped = true
}
// a default check
if c.Check.ID == "container-image-pull-policy" {
assert.False(t, c.Skipped)
assert.Equal(t, scorecard.GradeCritical, c.Grade)
tested = true
}
assert.Equal(t, "kube-score-ignore-annotations", o.ObjectMeta.Name)
}
}
assert.True(t, tested)
assert.True(t, skipped)
}
07070100000082000041ED000000000000000000000002680FB1F800000000000000000000000000000000000000000000002100000000kube-score-1.20.0/score/security07070100000083000081A4000000000000000000000001680FB1F800001A0F000000000000000000000000000000000000002D00000000kube-score-1.20.0/score/security/security.gopackage security
import (
ks "github.com/zegl/kube-score/domain"
"github.com/zegl/kube-score/score/checks"
"github.com/zegl/kube-score/scorecard"
corev1 "k8s.io/api/core/v1"
)
func Register(allChecks *checks.Checks) {
allChecks.RegisterPodCheck("Container Security Context User Group ID", `Makes sure that all pods have a security context with valid UID and GID set `, containerSecurityContextUserGroupID)
allChecks.RegisterPodCheck("Container Security Context Privileged", "Makes sure that all pods have a unprivileged security context set", containerSecurityContextPrivileged)
allChecks.RegisterPodCheck("Container Security Context ReadOnlyRootFilesystem", "Makes sure that all pods have a security context with read only filesystem set", containerSecurityContextReadOnlyRootFilesystem)
allChecks.RegisterOptionalPodCheck("Container Seccomp Profile", `Makes sure that all pods have at a seccomp policy configured.`, podSeccompProfile)
}
// containerSecurityContextReadOnlyRootFilesystem checks for pods using writeable root filesystems
func containerSecurityContextReadOnlyRootFilesystem(ps ks.PodSpecer) (score scorecard.TestScore, err error) {
allContainers := ps.GetPodTemplateSpec().Spec.InitContainers
allContainers = append(allContainers, ps.GetPodTemplateSpec().Spec.Containers...)
noContextSet := false
hasWritableRootFS := false
for _, container := range allContainers {
if container.SecurityContext == nil {
noContextSet = true
score.AddComment(container.Name, "Container has no configured security context", "Set securityContext to run the container in a more secure context.")
continue
}
sec := container.SecurityContext
if sec.ReadOnlyRootFilesystem == nil || !*sec.ReadOnlyRootFilesystem {
hasWritableRootFS = true
score.AddComment(container.Name, "The pod has a container with a writable root filesystem", "Set securityContext.readOnlyRootFilesystem to true")
}
}
if noContextSet || hasWritableRootFS {
score.Grade = scorecard.GradeCritical
} else {
score.Grade = scorecard.GradeAllOK
}
return
}
// containerSecurityContextPrivileged checks for privileged containers
func containerSecurityContextPrivileged(ps ks.PodSpecer) (score scorecard.TestScore, err error) {
allContainers := ps.GetPodTemplateSpec().Spec.InitContainers
allContainers = append(allContainers, ps.GetPodTemplateSpec().Spec.Containers...)
hasPrivileged := false
for _, container := range allContainers {
if container.SecurityContext != nil && container.SecurityContext.Privileged != nil && *container.SecurityContext.Privileged {
hasPrivileged = true
score.AddComment(container.Name, "The container is privileged", "Set securityContext.privileged to false. Privileged containers can access all devices on the host, and grants almost the same access as non-containerized processes on the host.")
}
}
if hasPrivileged {
score.Grade = scorecard.GradeCritical
} else {
score.Grade = scorecard.GradeAllOK
}
return
}
// containerSecurityContextUserGroupID checks that the user and group are valid ( > 10000) in the security context
func containerSecurityContextUserGroupID(ps ks.PodSpecer) (score scorecard.TestScore, err error) {
allContainers := ps.GetPodTemplateSpec().Spec.InitContainers
allContainers = append(allContainers, ps.GetPodTemplateSpec().Spec.Containers...)
podSecurityContext := ps.GetPodTemplateSpec().Spec.SecurityContext
noContextSet := false
hasLowUserID := false
hasLowGroupID := false
for _, container := range allContainers {
if container.SecurityContext == nil && podSecurityContext == nil {
noContextSet = true
score.AddComment(container.Name, "Container has no configured security context", "Set securityContext to run the container in a more secure context.")
continue
}
sec := container.SecurityContext
if sec == nil {
sec = &corev1.SecurityContext{}
}
// Forward values from PodSecurityContext to the (container level) SecurityContext if not set
if podSecurityContext != nil {
if sec.RunAsGroup == nil {
sec.RunAsGroup = podSecurityContext.RunAsGroup
}
if sec.RunAsUser == nil {
sec.RunAsUser = podSecurityContext.RunAsUser
}
}
if sec.RunAsUser == nil || *sec.RunAsUser < 10000 {
hasLowUserID = true
score.AddComment(container.Name, "The container is running with a low user ID", "A userid above 10 000 is recommended to avoid conflicts with the host. Set securityContext.runAsUser to a value > 10000")
}
if sec.RunAsGroup == nil || *sec.RunAsGroup < 10000 {
hasLowGroupID = true
score.AddComment(container.Name, "The container running with a low group ID", "A groupid above 10 000 is recommended to avoid conflicts with the host. Set securityContext.runAsGroup to a value > 10000")
}
}
if noContextSet || hasLowUserID || hasLowGroupID {
score.Grade = scorecard.GradeCritical
} else {
score.Grade = scorecard.GradeAllOK
}
return
}
// podSeccompProfile checks that a Seccommp profile is configured. The
// seccompProfile can be specified either through annotation or securityContext.
// There are two ways to specify the seccomp profile via securityContext --
// at the pod level or container level.
// Pod level seccomp profile is preferred since it is applied to all containers.
func podSeccompProfile(ps ks.PodSpecer) (score scorecard.TestScore, err error) {
metadata := ps.GetPodTemplateSpec().ObjectMeta
secured := false
// Check if the seccomp profile is set via annotation
if metadata.Annotations != nil {
if _, ok := metadata.Annotations["seccomp.security.alpha.kubernetes.io/defaultProfileName"]; ok {
secured = true
}
}
//Check if seccomp is set via securityContext at Pod or Container Level
if !secured {
elements := make(map[string]bool)
if ps.GetPodTemplateSpec().Spec.SecurityContext != nil && ps.GetPodTemplateSpec().Spec.SecurityContext.SeccompProfile != nil {
secured = true
} else {
// This does not check initContainers, only Containers
for _, container := range ps.GetPodTemplateSpec().Spec.Containers {
if container.SecurityContext != nil && container.SecurityContext.SeccompProfile != nil {
elements[container.Name] = true
secured = true
} else {
score.AddComment(container.Name, "The container has not configured Seccomp", "Running containers with Seccomp is recommended to reduce the kernel attack surface")
elements[container.Name] = false
}
}
}
// one unsecured container is enough to fail the test
for _, value := range elements {
if !value {
secured = false
}
}
}
if !secured {
score.Grade = scorecard.GradeWarning
} else {
score.Grade = scorecard.GradeAllOK
}
return
}
07070100000084000081A4000000000000000000000001680FB1F8000022D8000000000000000000000000000000000000002900000000kube-score-1.20.0/score/security_test.gopackage score
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/zegl/kube-score/config"
ks "github.com/zegl/kube-score/domain"
"github.com/zegl/kube-score/scorecard"
)
func TestContainerSeccompMissing(t *testing.T) {
t.Parallel()
structMap := make(map[string]struct{})
structMap["container-seccomp-profile"] = struct{}{}
testExpectedScoreWithConfig(t, []ks.NamedReader{testFile("pod-seccomp-no-annotation.yaml")}, nil, &config.RunConfiguration{
EnabledOptionalTests: structMap,
}, "Container Seccomp Profile", scorecard.GradeWarning)
}
func TestPodSecurityContextPresent(t *testing.T) {
t.Parallel()
structMap := make(map[string]struct{})
structMap["container-seccomp-profile"] = struct{}{}
testExpectedScoreWithConfig(t, []ks.NamedReader{testFile("pod-seccomp-securecontext-ok.yaml")}, nil, &config.RunConfiguration{
EnabledOptionalTests: structMap,
}, "Container Seccomp Profile", scorecard.GradeAllOK)
}
func TestContainerSecurityContextSeccompPresent(t *testing.T) {
t.Parallel()
structMap := make(map[string]struct{})
structMap["container-seccomp-profile"] = struct{}{}
testExpectedScoreWithConfig(t, []ks.NamedReader{testFile("pod-seccomp-container-securecontext-ok.yaml")}, nil, &config.RunConfiguration{
EnabledOptionalTests: structMap,
}, "Container Seccomp Profile", scorecard.GradeAllOK)
}
func TestPodSecurityContextSeccompAbsent(t *testing.T) {
t.Parallel()
structMap := make(map[string]struct{})
structMap["container-seccomp-profile"] = struct{}{}
testExpectedScoreWithConfig(t, []ks.NamedReader{testFile("pod-seccomp-securecontext-warning.yaml")}, nil, &config.RunConfiguration{
EnabledOptionalTests: structMap,
}, "Container Seccomp Profile", scorecard.GradeWarning)
}
func TestContainerSecurityContextSeccompAbsent(t *testing.T) {
t.Parallel()
structMap := make(map[string]struct{})
structMap["container-seccomp-profile"] = struct{}{}
testExpectedScoreWithConfig(t, []ks.NamedReader{testFile("pod-seccomp-container-securecontext-warning.yaml")}, nil, &config.RunConfiguration{
EnabledOptionalTests: structMap,
}, "Container Seccomp Profile", scorecard.GradeWarning)
}
func TestContainerSeccompMissingNotRunByDefault(t *testing.T) {
t.Parallel()
skipped := wasSkipped(t, []ks.NamedReader{testFile("pod-seccomp-no-annotation.yaml")}, nil, nil, "Container Seccomp Profile")
assert.True(t, skipped)
}
func TestContainerSeccompAllGood(t *testing.T) {
t.Parallel()
structMap := make(map[string]struct{})
structMap["container-seccomp-profile"] = struct{}{}
testExpectedScoreWithConfig(t, []ks.NamedReader{testFile("pod-seccomp-annotated.yaml")}, nil, &config.RunConfiguration{
EnabledOptionalTests: structMap,
}, "Container Seccomp Profile", scorecard.GradeAllOK)
}
func TestContainerSeccompAllGoodAnnotation(t *testing.T) {
t.Parallel()
testExpectedScoreWithConfig(t, []ks.NamedReader{testFile("pod-seccomp-annotated-annotation-optional.yaml")}, nil, &config.RunConfiguration{
UseOptionalChecksAnnotation: true,
}, "Container Seccomp Profile", scorecard.GradeAllOK)
}
func TestContainerSecurityContextUserGroupIDAllGood(t *testing.T) {
t.Parallel()
structMap := make(map[string]struct{})
structMap["container-security-context-user-group-id"] = struct{}{}
c := testExpectedScoreWithConfig(t, []ks.NamedReader{testFile("pod-security-context-all-good.yaml")}, nil, &config.RunConfiguration{
EnabledOptionalTests: structMap,
}, "Container Security Context User Group ID", scorecard.GradeAllOK)
assert.Empty(t, c)
}
func TestContainerSecurityContextUserGroupIDLowGroup(t *testing.T) {
t.Parallel()
optionalChecks := make(map[string]struct{})
optionalChecks["container-security-context-user-group-id"] = struct{}{}
comments := testExpectedScoreWithConfig(t, []ks.NamedReader{testFile("pod-security-context-low-group-id.yaml")}, nil, &config.RunConfiguration{
EnabledOptionalTests: optionalChecks,
}, "Container Security Context User Group ID", scorecard.GradeCritical)
assert.Contains(t, comments, scorecard.TestScoreComment{
Path: "foobar",
Summary: "The container running with a low group ID",
Description: "A groupid above 10 000 is recommended to avoid conflicts with the host. Set securityContext.runAsGroup to a value > 10000",
})
}
func TestContainerSecurityContextUserGroupIDLowUser(t *testing.T) {
t.Parallel()
optionalChecks := make(map[string]struct{})
optionalChecks["container-security-context-user-group-id"] = struct{}{}
comments := testExpectedScoreWithConfig(t, []ks.NamedReader{testFile("pod-security-context-low-user-id.yaml")}, nil, &config.RunConfiguration{
EnabledOptionalTests: optionalChecks,
}, "Container Security Context User Group ID", scorecard.GradeCritical)
assert.Contains(t, comments, scorecard.TestScoreComment{
Path: "foobar",
Summary: "The container is running with a low user ID",
Description: "A userid above 10 000 is recommended to avoid conflicts with the host. Set securityContext.runAsUser to a value > 10000",
})
}
func TestContainerSecurityContextUserGroupIDNoSecurityContext(t *testing.T) {
t.Parallel()
optionalChecks := make(map[string]struct{})
optionalChecks["container-security-context-user-group-id"] = struct{}{}
comments := testExpectedScoreWithConfig(t, []ks.NamedReader{testFile("pod-security-context-nosecuritycontext.yaml")}, nil, &config.RunConfiguration{
EnabledOptionalTests: optionalChecks,
}, "Container Security Context User Group ID", scorecard.GradeCritical)
assert.Contains(t, comments, scorecard.TestScoreComment{
Path: "foobar",
Summary: "Container has no configured security context",
Description: "Set securityContext to run the container in a more secure context.",
})
}
func TestContainerSecurityContextPrivilegedAllGood(t *testing.T) {
t.Parallel()
structMap := make(map[string]struct{})
structMap["container-security-context-privileged"] = struct{}{}
c := testExpectedScoreWithConfig(t, []ks.NamedReader{testFile("pod-security-context-all-good.yaml")}, nil, &config.RunConfiguration{
EnabledOptionalTests: structMap,
}, "Container Security Context Privileged", scorecard.GradeAllOK)
assert.Empty(t, c)
}
func TestContainerSecurityContextPrivilegedPrivileged(t *testing.T) {
t.Parallel()
optionalChecks := make(map[string]struct{})
optionalChecks["container-security-context-privileged"] = struct{}{}
comments := testExpectedScoreWithConfig(t, []ks.NamedReader{testFile("pod-security-context-privileged.yaml")}, nil, &config.RunConfiguration{
EnabledOptionalTests: optionalChecks,
}, "Container Security Context Privileged", scorecard.GradeCritical)
assert.Contains(t, comments, scorecard.TestScoreComment{
Path: "foobar",
Summary: "The container is privileged",
Description: "Set securityContext.privileged to false. Privileged containers can access all devices on the host, and grants almost the same access as non-containerized processes on the host.",
})
}
func TestContainerSecurityContextReadOnlyRootFilesystemAllGood(t *testing.T) {
t.Parallel()
structMap := make(map[string]struct{})
structMap["container-security-context-readonlyrootfilesystem"] = struct{}{}
c := testExpectedScoreWithConfig(t, []ks.NamedReader{testFile("pod-security-context-all-good.yaml")}, nil, &config.RunConfiguration{
EnabledOptionalTests: structMap,
}, "Container Security Context ReadOnlyRootFilesystem", scorecard.GradeAllOK)
assert.Empty(t, c)
}
func TestContainerSecurityContextReadOnlyRootFilesystemWriteable(t *testing.T) {
t.Parallel()
optionalChecks := make(map[string]struct{})
optionalChecks["container-security-context-readonlyrootfilesystem"] = struct{}{}
comments := testExpectedScoreWithConfig(t, []ks.NamedReader{testFile("pod-security-context-writeablerootfilesystem.yaml")}, nil, &config.RunConfiguration{
EnabledOptionalTests: optionalChecks,
}, "Container Security Context ReadOnlyRootFilesystem", scorecard.GradeCritical)
assert.Contains(t, comments, scorecard.TestScoreComment{
Path: "foobar",
Summary: "The pod has a container with a writable root filesystem",
Description: "Set securityContext.readOnlyRootFilesystem to true",
})
}
func TestContainerSecurityContextReadOnlyRootFilesystemNoSecurityContext(t *testing.T) {
t.Parallel()
optionalChecks := make(map[string]struct{})
optionalChecks["container-security-context-readonlyrootfilesystem"] = struct{}{}
comments := testExpectedScoreWithConfig(t, []ks.NamedReader{testFile("pod-security-context-nosecuritycontext.yaml")}, nil, &config.RunConfiguration{
EnabledOptionalTests: optionalChecks,
}, "Container Security Context ReadOnlyRootFilesystem", scorecard.GradeCritical)
assert.Contains(t, comments, scorecard.TestScoreComment{
Path: "foobar",
Summary: "Container has no configured security context",
Description: "Set securityContext to run the container in a more secure context.",
})
}
07070100000085000041ED000000000000000000000002680FB1F800000000000000000000000000000000000000000000002000000000kube-score-1.20.0/score/service07070100000086000081A4000000000000000000000001680FB1F800000A2B000000000000000000000000000000000000002B00000000kube-score-1.20.0/score/service/service.gopackage service
import (
corev1 "k8s.io/api/core/v1"
ks "github.com/zegl/kube-score/domain"
"github.com/zegl/kube-score/score/checks"
"github.com/zegl/kube-score/score/internal"
"github.com/zegl/kube-score/scorecard"
)
func Register(allChecks *checks.Checks, pods ks.Pods, podspeccers ks.PodSpeccers) {
allChecks.RegisterServiceCheck("Service Targets Pod", `Makes sure that all Services targets a Pod`, serviceTargetsPod(pods.Pods(), podspeccers.PodSpeccers()))
allChecks.RegisterServiceCheck("Service Type", `Makes sure that the Service type is not NodePort`, serviceType)
}
// serviceTargetsPod checks if a Service targets a pod and issues a critical warning if no matching pod
// could be found
func serviceTargetsPod(pods []ks.Pod, podspecers []ks.PodSpecer) func(corev1.Service) (scorecard.TestScore, error) {
podsInNamespace := make(map[string][]map[string]string)
for _, p := range pods {
pod := p.Pod()
if _, ok := podsInNamespace[pod.Namespace]; !ok {
podsInNamespace[pod.Namespace] = []map[string]string{}
}
podsInNamespace[pod.Namespace] = append(podsInNamespace[pod.Namespace], pod.Labels)
}
for _, podSpec := range podspecers {
if _, ok := podsInNamespace[podSpec.GetObjectMeta().Namespace]; !ok {
podsInNamespace[podSpec.GetObjectMeta().Namespace] = []map[string]string{}
}
podsInNamespace[podSpec.GetObjectMeta().Namespace] = append(podsInNamespace[podSpec.GetObjectMeta().Namespace], podSpec.GetPodTemplateSpec().Labels)
}
return func(service corev1.Service) (score scorecard.TestScore, err error) {
// Services of type ExternalName does not have a selector
if service.Spec.Type == corev1.ServiceTypeExternalName {
score.Grade = scorecard.GradeAllOK
return
}
hasMatch := false
for _, podLables := range podsInNamespace[service.Namespace] {
if internal.LabelSelectorMatchesLabels(service.Spec.Selector, podLables) {
hasMatch = true
break
}
}
if hasMatch {
score.Grade = scorecard.GradeAllOK
} else {
score.Grade = scorecard.GradeCritical
score.AddComment("", "The services selector does not match any pods", "")
}
return
}
}
func serviceType(service corev1.Service) (score scorecard.TestScore, err error) {
if service.Spec.Type == corev1.ServiceTypeNodePort {
score.Grade = scorecard.GradeWarning
score.AddComment("", "The service is of type NodePort", "NodePort services should be avoided as they are insecure, and can not be used together with NetworkPolicies. LoadBalancers or use of an Ingress is recommended over NodePorts.")
return
}
score.Grade = scorecard.GradeAllOK
return
}
07070100000087000081A4000000000000000000000001680FB1F8000009E4000000000000000000000000000000000000002800000000kube-score-1.20.0/score/service_test.gopackage score
import (
"testing"
"github.com/zegl/kube-score/scorecard"
)
func TestServiceTargetsPodDeployment(t *testing.T) {
t.Parallel()
testExpectedScore(t, "service-target-deployment.yaml", "Service Targets Pod", scorecard.GradeAllOK)
}
func TestServiceNotTargetsPodDeployment(t *testing.T) {
t.Parallel()
testExpectedScore(t, "service-not-target-deployment.yaml", "Service Targets Pod", scorecard.GradeCritical)
}
func TestServiceTargetsPodRaw(t *testing.T) {
t.Parallel()
testExpectedScore(t, "service-target-pod.yaml", "Service Targets Pod", scorecard.GradeAllOK)
}
func TestServiceNotTargetsPodRaw(t *testing.T) {
t.Parallel()
testExpectedScore(t, "service-not-target-pod.yaml", "Service Targets Pod", scorecard.GradeCritical)
}
func TestServiceTargetsPodRawMultiLabel(t *testing.T) {
t.Parallel()
testExpectedScore(t, "service-target-pod-multi-label.yaml", "Service Targets Pod", scorecard.GradeAllOK)
}
func TestServiceNotTargetsPodRawMultiLabel(t *testing.T) {
t.Parallel()
testExpectedScore(t, "service-not-target-pod-multi-label.yaml", "Service Targets Pod", scorecard.GradeCritical)
}
func TestServiceTargetsPodRawSameNamespace(t *testing.T) {
t.Parallel()
testExpectedScore(t, "service-target-pod-same-namespace.yaml", "Service Targets Pod", scorecard.GradeAllOK)
}
func TestServiceTargetsPodRawDifferentNamespace(t *testing.T) {
t.Parallel()
testExpectedScore(t, "service-target-pod-different-namespace.yaml", "Service Targets Pod", scorecard.GradeCritical)
}
func TestServiceTargetsPodDeploymentSameNamespace(t *testing.T) {
t.Parallel()
testExpectedScore(t, "service-target-deployment-same-namespace.yaml", "Service Targets Pod", scorecard.GradeAllOK)
}
func TestServiceTargetsPodDeploymentDifferentNamespace(t *testing.T) {
t.Parallel()
testExpectedScore(t, "service-target-deployment-different-namespace.yaml", "Service Targets Pod", scorecard.GradeCritical)
}
func TestServiceExternalName(t *testing.T) {
t.Parallel()
testExpectedScore(t, "service-externalname.yaml", "Service Targets Pod", scorecard.GradeAllOK)
}
func TestServiceTypeNodePort(t *testing.T) {
t.Parallel()
testExpectedScore(t, "service-type-nodeport.yaml", "Service Type", scorecard.GradeWarning)
}
func TestServiceTypeClusterIP(t *testing.T) {
t.Parallel()
testExpectedScore(t, "service-type-clusterip.yaml", "Service Type", scorecard.GradeAllOK)
}
func TestServiceTypeDefault(t *testing.T) {
t.Parallel()
testExpectedScore(t, "service-type-default.yaml", "Service Type", scorecard.GradeAllOK)
}
07070100000088000041ED000000000000000000000002680FB1F800000000000000000000000000000000000000000000001F00000000kube-score-1.20.0/score/stable07070100000089000081A4000000000000000000000001680FB1F800000B55000000000000000000000000000000000000003100000000kube-score-1.20.0/score/stable/stable_version.gopackage stable
import (
"fmt"
"github.com/zegl/kube-score/config"
"github.com/zegl/kube-score/domain"
"github.com/zegl/kube-score/score/checks"
"github.com/zegl/kube-score/scorecard"
)
func Register(kubernetesVersion config.Semver, allChecks *checks.Checks) {
allChecks.RegisterMetaCheck("Stable version", `Checks if the object is using a deprecated apiVersion`, metaStableAvailable(kubernetesVersion))
}
// ScoreMetaStableAvailable checks if the supplied TypeMeta is an unstable object type, that has a stable(r) replacement
func metaStableAvailable(kubernetsVersion config.Semver) func(meta domain.BothMeta) (scorecard.TestScore, error) {
return func(meta domain.BothMeta) (score scorecard.TestScore, err error) {
type recommendedApi struct {
newAPI string
availableSince config.Semver
}
withStable := map[string]map[string]recommendedApi{
"extensions/v1beta1": {
"Deployment": recommendedApi{"apps/v1", config.Semver{Major: 1, Minor: 9}},
"DaemonSet": recommendedApi{"apps/v1", config.Semver{Major: 1, Minor: 9}},
"Ingress": recommendedApi{"networking.k8s.io/v1", config.Semver{Major: 1, Minor: 19}},
"IngressClass": recommendedApi{"networking.k8s.io/v1", config.Semver{Major: 1, Minor: 19}},
},
"apps/v1beta1": {
"Deployment": recommendedApi{"apps/v1", config.Semver{Major: 1, Minor: 9}},
"StatefulSet": recommendedApi{"apps/v1", config.Semver{Major: 1, Minor: 9}},
},
"apps/v1beta2": {
"Deployment": recommendedApi{"apps/v1", config.Semver{Major: 1, Minor: 9}},
"StatefulSet": recommendedApi{"apps/v1", config.Semver{Major: 1, Minor: 9}},
"DaemonSet": recommendedApi{"apps/v1", config.Semver{Major: 1, Minor: 9}},
},
"batch/v1beta1": {
"CronJob": recommendedApi{"batch/v1", config.Semver{Major: 1, Minor: 21}},
},
"policy/v1beta1": {
"PodDisruptionBudget": recommendedApi{"policy/v1", config.Semver{Major: 1, Minor: 21}},
},
"networking.k8s.io/v1beta1": {
"Ingress": recommendedApi{"networking.k8s.io/v1", config.Semver{Major: 1, Minor: 19}},
"IngressClass": recommendedApi{"networking.k8s.io/v1", config.Semver{Major: 1, Minor: 19}},
},
}
score.Grade = scorecard.GradeAllOK
if inVersion, ok := withStable[meta.TypeMeta.APIVersion]; ok {
if recAPI, ok := inVersion[meta.TypeMeta.Kind]; ok {
// The recommended replacement is not available in the version of Kubernetes
// that the user is using
if kubernetsVersion.LessThan(recAPI.availableSince) {
return
}
score.Grade = scorecard.GradeWarning
score.AddComment("",
fmt.Sprintf("The apiVersion and kind %s/%s is deprecated", meta.TypeMeta.APIVersion, meta.TypeMeta.Kind),
fmt.Sprintf("It's recommended to use %s instead which has been available since Kubernetes %s", recAPI.newAPI, recAPI.availableSince.String()),
)
return
}
}
return
}
}
0707010000008A000081A4000000000000000000000001680FB1F800000BCE000000000000000000000000000000000000003600000000kube-score-1.20.0/score/stable/stable_version_test.gopackage stable
import (
"testing"
"github.com/stretchr/testify/assert"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/zegl/kube-score/config"
ks "github.com/zegl/kube-score/domain"
"github.com/zegl/kube-score/scorecard"
)
func TestStableVersionOldKubernetesVersion(t *testing.T) {
oldKubernetes := metaStableAvailable(config.Semver{Major: 1, Minor: 4})
scoreOld, _ := oldKubernetes(ks.BothMeta{TypeMeta: v1.TypeMeta{Kind: "Deployment", APIVersion: "extensions/v1beta1"}})
assert.Equal(t, scorecard.GradeAllOK, scoreOld.Grade)
assert.Equal(t, []scorecard.TestScoreComment(nil), scoreOld.Comments)
}
func TestStableVersionNewKubernetesVersion(t *testing.T) {
newKubernetes := metaStableAvailable(config.Semver{Major: 1, Minor: 18})
scoreNew, _ := newKubernetes(ks.BothMeta{TypeMeta: v1.TypeMeta{Kind: "Deployment", APIVersion: "extensions/v1beta1"}})
assert.Equal(t, scorecard.GradeWarning, scoreNew.Grade)
assert.Equal(t, []scorecard.TestScoreComment{{Path: "", Summary: "The apiVersion and kind extensions/v1beta1/Deployment is deprecated", Description: "It's recommended to use apps/v1 instead which has been available since Kubernetes v1.9", DocumentationURL: ""}}, scoreNew.Comments)
}
func TestStableVersionIngress(t *testing.T) {
newKubernetes := metaStableAvailable(config.Semver{Major: 1, Minor: 20})
scoreNew, _ := newKubernetes(ks.BothMeta{TypeMeta: v1.TypeMeta{Kind: "Ingress", APIVersion: "extensions/v1beta1"}})
assert.Equal(t, scorecard.GradeWarning, scoreNew.Grade)
assert.Equal(t, []scorecard.TestScoreComment{{Path: "", Summary: "The apiVersion and kind extensions/v1beta1/Ingress is deprecated", Description: "It's recommended to use networking.k8s.io/v1 instead which has been available since Kubernetes v1.19", DocumentationURL: ""}}, scoreNew.Comments)
}
func TestStableVersionPodDisruptionBudget(t *testing.T) {
newKubernetes := metaStableAvailable(config.Semver{Major: 1, Minor: 21})
scoreNew, _ := newKubernetes(ks.BothMeta{TypeMeta: v1.TypeMeta{Kind: "PodDisruptionBudget", APIVersion: "policy/v1beta1"}})
assert.Equal(t, scorecard.GradeWarning, scoreNew.Grade)
assert.Equal(t, []scorecard.TestScoreComment{{Path: "", Summary: "The apiVersion and kind policy/v1beta1/PodDisruptionBudget is deprecated", Description: "It's recommended to use policy/v1 instead which has been available since Kubernetes v1.21", DocumentationURL: ""}}, scoreNew.Comments)
}
func TestStableNetworkingIngress(t *testing.T) {
newKubernetes := metaStableAvailable(config.Semver{Major: 1, Minor: 21})
scoreNew, _ := newKubernetes(ks.BothMeta{TypeMeta: v1.TypeMeta{Kind: "Ingress", APIVersion: "networking.k8s.io/v1beta1"}})
assert.Equal(t, scorecard.GradeWarning, scoreNew.Grade)
assert.Equal(t, []scorecard.TestScoreComment{{Path: "", Summary: "The apiVersion and kind networking.k8s.io/v1beta1/Ingress is deprecated", Description: "It's recommended to use networking.k8s.io/v1 instead which has been available since Kubernetes v1.19", DocumentationURL: ""}}, scoreNew.Comments)
}
0707010000008B000081A4000000000000000000000001680FB1F800000ADC000000000000000000000000000000000000002F00000000kube-score-1.20.0/score/stable_version_test.gopackage score
import (
"testing"
"github.com/zegl/kube-score/config"
ks "github.com/zegl/kube-score/domain"
"github.com/zegl/kube-score/scorecard"
)
func TestStatefulSetAppsv1beta1(t *testing.T) {
t.Parallel()
testExpectedScore(t, "statefulset-appsv1beta1.yaml", "Stable version", scorecard.GradeWarning)
}
func TestStatefulSetAppsv1beta1Kubernetes1dot4(t *testing.T) {
t.Parallel()
testExpectedScoreWithConfig(t, []ks.NamedReader{testFile("statefulset-appsv1beta1.yaml")}, nil, &config.RunConfiguration{
KubernetesVersion: config.Semver{Major: 1, Minor: 4},
}, "Stable version", scorecard.GradeAllOK)
}
func TestStatefulSetAppsv1beta1Kubernetes1dot18(t *testing.T) {
t.Parallel()
testExpectedScoreWithConfig(t, []ks.NamedReader{testFile("statefulset-appsv1beta1.yaml")}, nil, &config.RunConfiguration{
KubernetesVersion: config.Semver{Major: 1, Minor: 18},
}, "Stable version", scorecard.GradeWarning)
}
func TestStatefulSetAppsv1beta2(t *testing.T) {
t.Parallel()
testExpectedScore(t, "statefulset-appsv1beta2.yaml", "Stable version", scorecard.GradeWarning)
}
func TestDeploymentExtensionsv1beta1(t *testing.T) {
t.Parallel()
testExpectedScore(t, "deployment-extensions-v1beta1.yaml", "Stable version", scorecard.GradeWarning)
}
func TestDeploymentAppsv1beta1(t *testing.T) {
t.Parallel()
testExpectedScore(t, "deployment-appsv1beta1.yaml", "Stable version", scorecard.GradeWarning)
}
func TestDeploymentAppsv1beta2(t *testing.T) {
t.Parallel()
testExpectedScore(t, "deployment-appsv1beta2.yaml", "Stable version", scorecard.GradeWarning)
}
func TestDaemonSetAppsv1(t *testing.T) {
t.Parallel()
testExpectedScore(t, "daemonset-appsv1.yaml", "Stable version", scorecard.GradeAllOK)
}
func TestDaemonSetAppsv1beta2(t *testing.T) {
t.Parallel()
testExpectedScore(t, "daemonset-appsv1beta2.yaml", "Stable version", scorecard.GradeWarning)
}
func TestDaemonSetExtensionsv1beta1(t *testing.T) {
t.Parallel()
testExpectedScore(t, "daemonset-extensionsv1beta1.yaml", "Stable version", scorecard.GradeWarning)
}
func TestCronJobBatchv1beta1(t *testing.T) {
t.Parallel()
testExpectedScore(t, "cronjob-batchv1beta1.yaml", "Stable version", scorecard.GradeAllOK)
}
func TestCronJobBatchv1beta1Kubernetes1dot21(t *testing.T) {
t.Parallel()
testExpectedScoreWithConfig(t, []ks.NamedReader{testFile("cronjob-batchv1beta1.yaml")}, nil, &config.RunConfiguration{
KubernetesVersion: config.Semver{Major: 1, Minor: 21},
}, "Stable version", scorecard.GradeWarning)
}
func TestCronJobBatchv1(t *testing.T) {
t.Parallel()
testExpectedScore(t, "cronjob-batchv1.yaml", "Stable version", scorecard.GradeAllOK)
}
func TestJobBatchv1(t *testing.T) {
t.Parallel()
testExpectedScore(t, "job-batchv1.yaml", "Stable version", scorecard.GradeAllOK)
}
0707010000008C000041ED000000000000000000000002680FB1F800000000000000000000000000000000000000000000002100000000kube-score-1.20.0/score/testdata0707010000008D000081A4000000000000000000000001680FB1F80000034E000000000000000000000000000000000000002D00000000kube-score-1.20.0/score/testdata/all-ok.yamlapiVersion: v1
kind: Pod
metadata:
name: pod-test-1
namespace: testspace
labels:
app: foo-all-ok
spec:
containers:
- name: foobar
image: foo/bar:123
imagePullPolicy: Always
resources:
requests:
cpu: 1
memory: 1
limits:
cpu: 1
memory: 1
readinessProbe:
httpGet:
path: /ready
port: 8080
livenessProbe:
httpGet:
path: /live
port: 8080
securityContext:
privileged: False
runAsUser: 30000
runAsGroup: 30000
readOnlyRootFilesystem: True
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: foo-all-ok-netpol
namespace: testspace
spec:
podSelector:
matchLabels:
app: foo-all-ok
policyTypes:
- Egress
- Ingress
0707010000008E000081A4000000000000000000000001680FB1F8000000A4000000000000000000000000000000000000003B00000000kube-score-1.20.0/score/testdata/configmap-multi-dash.yaml---
apiVersion: v1
kind: ConfigMap
metadata:
name: awesome
data:
awesome.crt: |
-----BEGIN CERTIFICATE-----
TEST_DATA
-----END CERTIFICATE-----
---
0707010000008F000081A4000000000000000000000001680FB1F800000183000000000000000000000000000000000000004700000000kube-score-1.20.0/score/testdata/cronjob-batchv1-deadline-not-set.yamlapiVersion: batch/v1
kind: CronJob
metadata:
name: hello
spec:
schedule: "*/1 * * * *"
jobTemplate:
spec:
template:
spec:
containers:
- name: hello
image: busybox
args:
- /bin/sh
- -c
- date; echo Hello from the Kubernetes cluster
restartPolicy: OnFailure07070100000090000081A4000000000000000000000001680FB1F8000001A2000000000000000000000000000000000000004300000000kube-score-1.20.0/score/testdata/cronjob-batchv1-deadline-set.yamlapiVersion: batch/v1
kind: CronJob
metadata:
name: hello
spec:
schedule: "*/1 * * * *"
startingDeadlineSeconds: 100
jobTemplate:
spec:
template:
spec:
containers:
- name: hello
image: busybox
args:
- /bin/sh
- -c
- date; echo Hello from the Kubernetes cluster
restartPolicy: OnFailure07070100000091000081A4000000000000000000000001680FB1F8000002AB000000000000000000000000000000000000004C00000000kube-score-1.20.0/score/testdata/cronjob-batchv1-restartpolicy-invalid.yamlkind: CronJob
apiVersion: batch/v1
metadata:
namespace: cronjobs
name: pwsh-test
spec:
jobTemplate:
spec:
template:
spec:
containers:
- name: pwsh
imagePullPolicy: Always
image: mcr.microsoft.com/powershell:7
command:
- pwsh
- -Command
- Start-Sleep -Seconds 5
securityContext:
readOnlyRootFilesystem: true
resources:
limits:
ephemeral-storage: 50Mi
requests:
ephemeral-storage: 50Mi
RestartPolicy: Once
schedule: '0/1 * * * *'
startingDeadlineSeconds: 5
07070100000092000081A4000000000000000000000001680FB1F80000017F000000000000000000000000000000000000004C00000000kube-score-1.20.0/score/testdata/cronjob-batchv1-restartpolicy-not-set.yamlapiVersion: batch/v1
kind: CronJob
metadata:
name: hello
spec:
schedule: "* * * * *"
jobTemplate:
spec:
template:
spec:
containers:
- name: hello
image: busybox:1.28
imagePullPolicy: IfNotPresent
command:
- /bin/sh
- -c
- date; echo Hello from the Kubernetes cluster
07070100000093000081A4000000000000000000000001680FB1F8000001A2000000000000000000000000000000000000004A00000000kube-score-1.20.0/score/testdata/cronjob-batchv1-restartpolicy-valid.yamlapiVersion: batch/v1
kind: CronJob
metadata:
name: hello
spec:
schedule: "* * * * *"
jobTemplate:
spec:
template:
spec:
containers:
- name: hello
image: busybox:1.28
imagePullPolicy: IfNotPresent
command:
- /bin/sh
- -c
- date; echo Hello from the Kubernetes cluster
restartPolicy: OnFailure
07070100000094000081A4000000000000000000000001680FB1F8000000E0000000000000000000000000000000000000003600000000kube-score-1.20.0/score/testdata/cronjob-batchv1.yamlapiVersion: batch/v1
kind: CronJob
metadata:
name: cronjob-test
spec:
schedule: "1 3 * * *"
jobTemplate:
spec:
template:
spec:
containers:
- name: foo
image: bar:latest07070100000095000081A4000000000000000000000001680FB1F800000188000000000000000000000000000000000000004C00000000kube-score-1.20.0/score/testdata/cronjob-batchv1beta1-deadline-not-set.yamlapiVersion: batch/v1beta1
kind: CronJob
metadata:
name: hello
spec:
schedule: "*/1 * * * *"
jobTemplate:
spec:
template:
spec:
containers:
- name: hello
image: busybox
args:
- /bin/sh
- -c
- date; echo Hello from the Kubernetes cluster
restartPolicy: OnFailure07070100000096000081A4000000000000000000000001680FB1F8000001A7000000000000000000000000000000000000004800000000kube-score-1.20.0/score/testdata/cronjob-batchv1beta1-deadline-set.yamlapiVersion: batch/v1beta1
kind: CronJob
metadata:
name: hello
spec:
schedule: "*/1 * * * *"
startingDeadlineSeconds: 100
jobTemplate:
spec:
template:
spec:
containers:
- name: hello
image: busybox
args:
- /bin/sh
- -c
- date; echo Hello from the Kubernetes cluster
restartPolicy: OnFailure07070100000097000081A4000000000000000000000001680FB1F8000002B2000000000000000000000000000000000000005100000000kube-score-1.20.0/score/testdata/cronjob-batchv1beta1-restartpolicy-invalid.yamlkind: CronJob
apiVersion: batch/v1beta1
metadata:
namespace: cronjobs
name: pwsh-test
spec:
jobTemplate:
spec:
template:
spec:
containers:
- name: pwsh
imagePullPolicy: Always
image: mcr.microsoft.com/powershell:7
command:
- pwsh
- -Command
- Start-Sleep -Seconds 5
securityContext:
readOnlyRootFilesystem: true
resources:
limits:
ephemeral-storage: 50Mi
requests:
ephemeral-storage: 50Mi
RestartPolicy: OnFailure
schedule: '0/1 * * * *'
startingDeadlineSeconds: 5
07070100000098000081A4000000000000000000000001680FB1F800000184000000000000000000000000000000000000005100000000kube-score-1.20.0/score/testdata/cronjob-batchv1beta1-restartpolicy-not-set.yamlapiVersion: batch/v1beta1
kind: CronJob
metadata:
name: hello
spec:
schedule: "* * * * *"
jobTemplate:
spec:
template:
spec:
containers:
- name: hello
image: busybox:1.28
imagePullPolicy: IfNotPresent
command:
- /bin/sh
- -c
- date; echo Hello from the Kubernetes cluster
07070100000099000081A4000000000000000000000001680FB1F8000001A7000000000000000000000000000000000000004F00000000kube-score-1.20.0/score/testdata/cronjob-batchv1beta1-restartpolicy-valid.yamlapiVersion: batch/v1beta1
kind: CronJob
metadata:
name: hello
spec:
schedule: "* * * * *"
jobTemplate:
spec:
template:
spec:
containers:
- name: hello
image: busybox:1.28
imagePullPolicy: IfNotPresent
command:
- /bin/sh
- -c
- date; echo Hello from the Kubernetes cluster
restartPolicy: OnFailure
0707010000009A000081A4000000000000000000000001680FB1F8000000E5000000000000000000000000000000000000003B00000000kube-score-1.20.0/score/testdata/cronjob-batchv1beta1.yamlapiVersion: batch/v1beta1
kind: CronJob
metadata:
name: cronjob-test
spec:
schedule: "1 3 * * *"
jobTemplate:
spec:
template:
spec:
containers:
- name: foo
image: bar:latest0707010000009B000081A4000000000000000000000001680FB1F80000009E000000000000000000000000000000000000003700000000kube-score-1.20.0/score/testdata/daemonset-appsv1.yamlapiVersion: apps/v1
kind: DaemonSet
metadata:
name: daemonset-test
spec:
template:
spec:
containers:
- name: foo
image: foo:latest0707010000009C000081A4000000000000000000000001680FB1F8000000A3000000000000000000000000000000000000003C00000000kube-score-1.20.0/score/testdata/daemonset-appsv1beta2.yamlapiVersion: apps/v1beta2
kind: DaemonSet
metadata:
name: daemonset-test
spec:
template:
spec:
containers:
- name: foo
image: foo:latest0707010000009D000081A4000000000000000000000001680FB1F8000000A9000000000000000000000000000000000000004200000000kube-score-1.20.0/score/testdata/daemonset-extensionsv1beta1.yamlapiVersion: extensions/v1beta1
kind: DaemonSet
metadata:
name: daemonset-test
spec:
template:
spec:
containers:
- name: foo
image: foo:latest0707010000009E000081A4000000000000000000000001680FB1F8000000FF000000000000000000000000000000000000003D00000000kube-score-1.20.0/score/testdata/deployment-appsv1beta1.yamlapiVersion: apps/v1beta1
kind: Deployment
metadata:
name: deployment-test-1
spec:
template:
spec:
containers:
- name: foobar
image: foo/bar:123
resources:
limits:
cpu: 200m
memory: 1Gi
0707010000009F000081A4000000000000000000000001680FB1F8000000FF000000000000000000000000000000000000003D00000000kube-score-1.20.0/score/testdata/deployment-appsv1beta2.yamlapiVersion: apps/v1beta2
kind: Deployment
metadata:
name: deployment-test-1
spec:
template:
spec:
containers:
- name: foobar
image: foo/bar:123
resources:
limits:
cpu: 200m
memory: 1Gi
070701000000A0000081A4000000000000000000000001680FB1F800000105000000000000000000000000000000000000004400000000kube-score-1.20.0/score/testdata/deployment-extensions-v1beta1.yamlapiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: deployment-test-1
spec:
template:
spec:
containers:
- name: foobar
image: foo/bar:123
resources:
limits:
cpu: 200m
memory: 1Gi
070701000000A1000081A4000000000000000000000001680FB1F8000000F3000000000000000000000000000000000000004D00000000kube-score-1.20.0/score/testdata/deployment-host-antiaffinity-1-replica.yamlapiVersion: apps/v1
kind: Deployment
metadata:
name: deployment-host-antiaffinity-1-replica
spec:
replicas: 1
template:
metadata:
labels:
app: foo
spec:
containers:
- name: foobar
image: foo:bar
070701000000A2000081A4000000000000000000000001680FB1F8000000EE000000000000000000000000000000000000004B00000000kube-score-1.20.0/score/testdata/deployment-host-antiaffinity-not-set.yamlapiVersion: apps/v1
kind: Deployment
metadata:
name: deployment-anti-affinity-not-set
spec:
replicas: 10
template:
metadata:
labels:
app: foo
spec:
containers:
- name: foobar
image: foo:bar
070701000000A3000081A4000000000000000000000001680FB1F800000286000000000000000000000000000000000000006100000000kube-score-1.20.0/score/testdata/deployment-host-antiaffinity-preffered-selector-expression.yamlapiVersion: apps/v1
kind: Deployment
metadata:
name: deployment-preffered-anti-affinity
spec:
replicas: 10
template:
metadata:
labels:
app: foo
spec:
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchExpressions:
- key: app
operator: In
values:
- foo
- another-foo
topologyKey: kubernetes.io/hostname
containers:
- name: foobar
image: foo:bar
070701000000A4000081A4000000000000000000000001680FB1F800000214000000000000000000000000000000000000005F00000000kube-score-1.20.0/score/testdata/deployment-host-antiaffinity-preffered-selector-no-match.yamlapiVersion: apps/v1
kind: Deployment
metadata:
name: deployment-preffered-anti-affinity
spec:
replicas: 10
template:
metadata:
labels:
app: foo
spec:
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchLabels:
app: not-foo
topologyKey: kubernetes.io/hostname
containers:
- name: foobar
image: foo:bar
070701000000A5000081A4000000000000000000000001680FB1F800000210000000000000000000000000000000000000004D00000000kube-score-1.20.0/score/testdata/deployment-host-antiaffinity-preffered.yamlapiVersion: apps/v1
kind: Deployment
metadata:
name: deployment-preffered-anti-affinity
spec:
replicas: 10
template:
metadata:
labels:
app: foo
spec:
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchLabels:
app: foo
topologyKey: kubernetes.io/hostname
containers:
- name: foobar
image: foo:bar
070701000000A6000081A4000000000000000000000001680FB1F8000001E9000000000000000000000000000000000000004C00000000kube-score-1.20.0/score/testdata/deployment-host-antiaffinity-required.yamlapiVersion: apps/v1
kind: Deployment
metadata:
name: deployment-required-anti-affinity
spec:
replicas: 10
template:
metadata:
labels:
app: foo
spec:
replicas: 10
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- topologyKey: "kubernetes.io/hostname"
labelSelector:
matchLabels:
app: foo
containers:
- name: foobar
image: foo:bar
070701000000A7000081A4000000000000000000000001680FB1F8000002E8000000000000000000000000000000000000005C00000000kube-score-1.20.0/score/testdata/deployment-poddisruptionbudget-v1-different-namespace.yamlapiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: app-budget
namespace: not-foo
spec:
minAvailable: 2
selector:
matchLabels:
app: foo
---
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: app-budget
namespace: bar
spec:
minAvailable: 2
selector:
matchLabels:
app: foo
---
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: app-budget-not-matching
namespace: baz
spec:
minAvailable: 2
selector:
matchLabels:
app: not-foo
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: statefulset-test-1
namespace: foo
spec:
template:
metadata:
labels:
app: foo
spec:
containers:
- name: foobar
image: foo:bar070701000000A8000081A4000000000000000000000001680FB1F800000165000000000000000000000000000000000000005000000000kube-score-1.20.0/score/testdata/deployment-poddisruptionbudget-v1-matches.yamlapiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: app-budget
spec:
minAvailable: 2
selector:
matchLabels:
app: foo
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: statefulset-test-1
spec:
template:
metadata:
labels:
app: foo
spec:
containers:
- name: foobar
image: foo:bar070701000000A9000081A4000000000000000000000001680FB1F800000169000000000000000000000000000000000000005100000000kube-score-1.20.0/score/testdata/deployment-poddisruptionbudget-v1-no-match.yamlapiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: app-budget
spec:
minAvailable: 2
selector:
matchLabels:
app: not-foo
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: statefulset-test-1
spec:
template:
metadata:
labels:
app: foo
spec:
containers:
- name: foobar
image: foo:bar070701000000AA000081A4000000000000000000000001680FB1F800000153000000000000000000000000000000000000005200000000kube-score-1.20.0/score/testdata/deployment-poddisruptionbudget-v1-no-policy.yamlapiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: app-budget
spec:
selector:
matchLabels:
app: foo
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: statefulset-test-1
spec:
template:
metadata:
labels:
app: foo
spec:
containers:
- name: foobar
image: foo:bar070701000000AB000081A4000000000000000000000001680FB1F8000001A2000000000000000000000000000000000000006000000000kube-score-1.20.0/score/testdata/deployment-poddisruptionbudget-v1beta1-expression-matches.yamlapiVersion: policy/v1beta1
kind: PodDisruptionBudget
metadata:
name: app-budget
spec:
minAvailable: 2
selector:
matchExpressions:
- key: app
operator: In
values: [foo, foo1, foo2]
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: statefulset-test-1
spec:
template:
metadata:
labels:
app: foo
spec:
containers:
- name: foobar
image: foo:bar070701000000AC000081A4000000000000000000000001680FB1F8000001A8000000000000000000000000000000000000006100000000kube-score-1.20.0/score/testdata/deployment-poddisruptionbudget-v1beta1-expression-no-match.yamlapiVersion: policy/v1beta1
kind: PodDisruptionBudget
metadata:
name: app-budget
spec:
minAvailable: 2
selector:
matchExpressions:
- key: app
operator: In
values: ["not-foo", "not-foo2"]
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: statefulset-test-1
spec:
template:
metadata:
labels:
app: foo
spec:
containers:
- name: foobar
image: foo:bar070701000000AD000081A4000000000000000000000001680FB1F80000016A000000000000000000000000000000000000005500000000kube-score-1.20.0/score/testdata/deployment-poddisruptionbudget-v1beta1-matches.yamlapiVersion: policy/v1beta1
kind: PodDisruptionBudget
metadata:
name: app-budget
spec:
minAvailable: 2
selector:
matchLabels:
app: foo
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: statefulset-test-1
spec:
template:
metadata:
labels:
app: foo
spec:
containers:
- name: foobar
image: foo:bar070701000000AE000081A4000000000000000000000001680FB1F80000016E000000000000000000000000000000000000005600000000kube-score-1.20.0/score/testdata/deployment-poddisruptionbudget-v1beta1-no-match.yamlapiVersion: policy/v1beta1
kind: PodDisruptionBudget
metadata:
name: app-budget
spec:
minAvailable: 2
selector:
matchLabels:
app: not-foo
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: statefulset-test-1
spec:
template:
metadata:
labels:
app: foo
spec:
containers:
- name: foobar
image: foo:bar070701000000AF000081A4000000000000000000000001680FB1F800000158000000000000000000000000000000000000005700000000kube-score-1.20.0/score/testdata/deployment-poddisruptionbudget-v1beta1-no-policy.yamlapiVersion: policy/v1beta1
kind: PodDisruptionBudget
metadata:
name: app-budget
spec:
selector:
matchLabels:
app: foo
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: statefulset-test-1
spec:
template:
metadata:
labels:
app: foo
spec:
containers:
- name: foobar
image: foo:bar070701000000B0000081A4000000000000000000000001680FB1F8000000FC000000000000000000000000000000000000004000000000kube-score-1.20.0/score/testdata/deployment-test-resources.yamlapiVersion: apps/v1
kind: Deployment
metadata:
name: deployment-test-1
spec:
template:
spec:
containers:
- name: foobar
image: foo/bar:123
resources:
limits:
cpu: 200m
memory: 1Gi
070701000000B1000081A4000000000000000000000001680FB1F80000023F000000000000000000000000000000000000004700000000kube-score-1.20.0/score/testdata/deployment-with-hpa-has-replicas.yamlapiVersion: autoscaling/v1
kind: HorizontalPodAutoscaler
metadata:
name: php-apache
namespace: default
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: php-apache
minReplicas: 1
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 50
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: php-apache
namespace: default
spec:
replicas: 123
template:
spec:
containers:
- name: foo
image: foo:latest070701000000B2000081A4000000000000000000000001680FB1F80000022F000000000000000000000000000000000000004B00000000kube-score-1.20.0/score/testdata/deployment-with-hpa-not-has-replicas.yamlapiVersion: autoscaling/v1
kind: HorizontalPodAutoscaler
metadata:
name: php-apache
namespace: default
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: php-apache
minReplicas: 1
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 50
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: php-apache
namespace: default
spec:
template:
spec:
containers:
- name: foo
image: foo:latest070701000000B3000041ED000000000000000000000002680FB1F800000000000000000000000000000000000000000000002600000000kube-score-1.20.0/score/testdata/helm070701000000B4000041ED000000000000000000000002680FB1F800000000000000000000000000000000000000000000002B00000000kube-score-1.20.0/score/testdata/helm/app1070701000000B5000081A4000000000000000000000001680FB1F80000001B000000000000000000000000000000000000003600000000kube-score-1.20.0/score/testdata/helm/app1/Chart.yamlname: app1
version: v1.0.0
070701000000B6000041ED000000000000000000000002680FB1F800000000000000000000000000000000000000000000003500000000kube-score-1.20.0/score/testdata/helm/app1/templates070701000000B7000081A4000000000000000000000001680FB1F8000000FA000000000000000000000000000000000000004500000000kube-score-1.20.0/score/testdata/helm/app1/templates/deployment.yamlkind: Deployment
apiVersion: apps/v1
metadata:
name: foo
spec:
template:
metadata:
labels:
foo: bar
---
kind: Deployment
apiVersion: apps/v1
metadata:
name: foo2
spec:
template:
metadata:
labels:
foo: bar2
070701000000B8000081A4000000000000000000000001680FB1F80000022F000000000000000000000000000000000000004B00000000kube-score-1.20.0/score/testdata/hpa-autoscalingv1-targets-deployment.yamlapiVersion: autoscaling/v1
kind: HorizontalPodAutoscaler
metadata:
name: php-apache
namespace: default
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: php-apache
minReplicas: 1
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 50
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: php-apache
namespace: default
spec:
template:
spec:
containers:
- name: foo
image: foo:latest070701000000B9000081A4000000000000000000000001680FB1F80000022F000000000000000000000000000000000000004B00000000kube-score-1.20.0/score/testdata/hpa-autoscalingv2-targets-deployment.yamlapiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: php-apache
namespace: default
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: php-apache
minReplicas: 1
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 50
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: php-apache
namespace: default
spec:
template:
spec:
containers:
- name: foo
image: foo:latest070701000000BA000081A4000000000000000000000001680FB1F800000233000000000000000000000000000000000000003800000000kube-score-1.20.0/score/testdata/hpa-has-no-target.yamlapiVersion: autoscaling/v1
kind: HorizontalPodAutoscaler
metadata:
name: php-apache
namespace: default
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: php-apache
minReplicas: 1
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 50
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: php-not-apache
namespace: default
spec:
template:
spec:
containers:
- name: foo
image: foo:latest070701000000BB000081A4000000000000000000000001680FB1F80000022F000000000000000000000000000000000000003B00000000kube-score-1.20.0/score/testdata/hpa-min-replicas-nok.yamlapiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: php-apache
namespace: default
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: php-apache
minReplicas: 1
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 50
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: php-apache
namespace: default
spec:
template:
spec:
containers:
- name: foo
image: foo:latest070701000000BC000081A4000000000000000000000001680FB1F80000022F000000000000000000000000000000000000003A00000000kube-score-1.20.0/score/testdata/hpa-min-replicas-ok.yamlapiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: php-apache
namespace: default
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: php-apache
minReplicas: 2
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 50
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: php-apache
namespace: default
spec:
template:
spec:
containers:
- name: foo
image: foo:latest070701000000BD000081A4000000000000000000000001680FB1F800000325000000000000000000000000000000000000003C00000000kube-score-1.20.0/score/testdata/hpa-target-deployment.yamlapiVersion: autoscaling/v1
kind: HorizontalPodAutoscaler
metadata:
name: php-apache
namespace: default
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: php-apache
minReplicas: 1
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 50
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: php-apache
namespace: default
spec:
replicas: 1
template:
metadata:
labels:
app: my-app
spec:
containers:
- name: foo
image: foo:latest
---
kind: Service
apiVersion: v1
metadata:
name: my-service
namespace: default
spec:
selector:
app: my-app
ports:
- protocol: TCP
port: 80
targetPort: 8080070701000000BE000081A4000000000000000000000001680FB1F800000106000000000000000000000000000000000000004000000000kube-score-1.20.0/score/testdata/ignore-annotation-service.yamlapiVersion: v1
kind: Service
metadata:
name: node-port-service-with-ignore
namespace: foospace
annotations:
kube-score/ignore: service-type
spec:
selector:
app: my-app
ports:
- protocol: TCP
port: 80
targetPort: 8080
type: NodePort
070701000000BF000081A4000000000000000000000001680FB1F80000024D000000000000000000000000000000000000005400000000kube-score-1.20.0/score/testdata/ingress-networkingv1-targets-service-no-match.yamlapiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: app-ingress
namespace: testspace
spec:
rules:
- http:
paths:
- path: /app
backend:
service:
name: app-service
port:
number: 5601
- path: /no-match
backend:
service:
name: app-service-123123
port:
number: 5601
---
kind: Service
apiVersion: v1
metadata:
name: app-service
namespace: testspace
spec:
selector:
app: kibana
ports:
- name: http
protocol: TCP
port: 5601
070701000000C0000081A4000000000000000000000001680FB1F8000001BD000000000000000000000000000000000000004B00000000kube-score-1.20.0/score/testdata/ingress-networkingv1-targets-service.yamlapiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: app-ingress
namespace: testspace
spec:
rules:
- http:
paths:
- path: /app
backend:
service:
name: app-service
port:
number: 5601
---
kind: Service
apiVersion: v1
metadata:
name: app-service
namespace: testspace
spec:
selector:
app: kibana
ports:
- name: http
protocol: TCP
port: 5601
070701000000C1000081A4000000000000000000000001680FB1F800000214000000000000000000000000000000000000005900000000kube-score-1.20.0/score/testdata/ingress-networkingv1beta1-targets-service-no-match.yamlapiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
name: app-ingress
namespace: testspace
spec:
rules:
- http:
paths:
- path: /app
backend:
serviceName: app-service
servicePort: 5601
- path: /no-match
backend:
serviceName: app-service-123123
servicePort: 5601
---
kind: Service
apiVersion: v1
metadata:
name: app-service
namespace: testspace
spec:
selector:
app: kibana
ports:
- name: http
protocol: TCP
port: 5601
070701000000C2000081A4000000000000000000000001680FB1F8000001A3000000000000000000000000000000000000005000000000kube-score-1.20.0/score/testdata/ingress-networkingv1beta1-targets-service.yamlapiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
name: app-ingress
namespace: testspace
spec:
rules:
- http:
paths:
- path: /app
backend:
serviceName: app-service
servicePort: 5601
---
kind: Service
apiVersion: v1
metadata:
name: app-service
namespace: testspace
spec:
selector:
app: kibana
ports:
- name: http
protocol: TCP
port: 5601
070701000000C3000081A4000000000000000000000001680FB1F80000020D000000000000000000000000000000000000004700000000kube-score-1.20.0/score/testdata/ingress-targets-service-no-match.yamlapiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: app-ingress
namespace: testspace
spec:
rules:
- http:
paths:
- path: /app
backend:
serviceName: app-service
servicePort: 5601
- path: /no-match
backend:
serviceName: app-service-123123
servicePort: 5601
---
kind: Service
apiVersion: v1
metadata:
name: app-service
namespace: testspace
spec:
selector:
app: kibana
ports:
- name: http
protocol: TCP
port: 5601
070701000000C4000081A4000000000000000000000001680FB1F80000019C000000000000000000000000000000000000003E00000000kube-score-1.20.0/score/testdata/ingress-targets-service.yamlapiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: app-ingress
namespace: testspace
spec:
rules:
- http:
paths:
- path: /app
backend:
serviceName: app-service
servicePort: 5601
---
kind: Service
apiVersion: v1
metadata:
name: app-service
namespace: testspace
spec:
selector:
app: kibana
ports:
- name: http
protocol: TCP
port: 5601
070701000000C5000081A4000000000000000000000001680FB1F8000000F7000000000000000000000000000000000000004100000000kube-score-1.20.0/score/testdata/ingress-v1-invalid-backend.yamlapiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: whatever
spec:
rules:
- host: abc.example.test
http:
paths:
- path: /
backend:
serviceName: abc
servicePort: def070701000000C6000081A4000000000000000000000001680FB1F800000276000000000000000000000000000000000000003700000000kube-score-1.20.0/score/testdata/ingress_issue363.yamlapiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: app-ingress
namespace: testspace
spec:
rules:
- host: foo.bar.network
http:
# Note: This input does _NOT_ match the spec.
# This test is here to make sure that there are no panics on invalid inputs.
paths:
- backend:
serviceName: app-service
servicePort: http
tls:
- hosts:
- foo.bar.network
---
kind: Service
apiVersion: v1
metadata:
name: app-service
namespace: testspace
spec:
selector:
app: kibana
ports:
- name: http
protocol: TCP
port: 5601
070701000000C7000081A4000000000000000000000001680FB1F800000111000000000000000000000000000000000000003700000000kube-score-1.20.0/score/testdata/ingress_issue388.yamlapiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: app-ingress
namespace: testspace
spec:
rules:
- host: foo.bar.network
# No http block — such ingresses can be used for redirects in nginx-ingress
tls:
- hosts:
- foo.bar.network
070701000000C8000081A4000000000000000000000001680FB1F8000000FB000000000000000000000000000000000000003200000000kube-score-1.20.0/score/testdata/job-batchv1.yamlapiVersion: batch/v1
kind: Job
metadata:
name: pi
spec:
template:
spec:
containers:
- name: pi
image: perl
command: ["perl", "-Mbignum=bpi", "-wle", "print bpi(2000)"]
restartPolicy: Never
backoffLimit: 4
070701000000C9000081A4000000000000000000000001680FB1F8000001CE000000000000000000000000000000000000004400000000kube-score-1.20.0/score/testdata/kube-score-ignore-annotations.yamlapiVersion: v1
kind: Pod
metadata:
name: kube-score-ignore-annotations
annotations:
"kube-score/ignore": container-security-context-readonlyrootfilesystem,pod-networkpolicy,container-security-context-user-group-id,pod-probes,container-resources
spec:
containers:
- name: foobar
image: foo/bar:123
resources:
limits:
cpu: 200m
memory: 1Gi
ephemeral-storage: 2Gi
requests:
cpu: 200m
070701000000CA000081A4000000000000000000000001680FB1F80000014F000000000000000000000000000000000000003700000000kube-score-1.20.0/score/testdata/linenumbers-helm.yaml---
# Source: app1/templates/deployment.yaml
kind: Deployment
apiVersion: apps/v1
metadata:
name: foo
spec:
template:
metadata:
labels:
foo: bar
---
# Source: app1/templates/deployment.yaml
kind: Deployment
apiVersion: apps/v1
metadata:
name: foo2
spec:
template:
metadata:
labels:
foo: bar2070701000000CB000081A4000000000000000000000001680FB1F8000000FD000000000000000000000000000000000000003200000000kube-score-1.20.0/score/testdata/linenumbers.yaml---
kind: Deployment
apiVersion: apps/v1
metadata:
name: foo
spec:
template:
metadata:
labels:
foo: bar
---
kind: Deployment
apiVersion: apps/v1
metadata:
name: foo2
spec:
template:
metadata:
labels:
foo: bar2070701000000CC000081A4000000000000000000000001680FB1F8000002AA000000000000000000000000000000000000002B00000000kube-score-1.20.0/score/testdata/list.yamlapiVersion: v1
kind: List
items:
- apiVersion: v1
kind: Service
metadata:
name: list-service-test
spec:
ports:
- protocol: TCP
port: 80
selector:
app: list-deployment-test
- apiVersion: apps/v1
kind: Deployment
metadata:
name: list-deployment-test
labels:
app: list-deployment-test
spec:
replicas: 1
selector:
matchLabels:
app: list-deployment-test
template:
metadata:
labels:
app: list-deployment-test
spec:
containers:
- name: nginx
image: nginx
070701000000CD000081A4000000000000000000000001680FB1F8000001F0000000000000000000000000000000000000004500000000kube-score-1.20.0/score/testdata/networkpolicy-cronjob-matching.yamlapiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: testapp-netpol
namespace: testspace
spec:
podSelector:
matchLabels:
app: foo
policyTypes:
- Egress
- Ingress
---
apiVersion: batch/v1beta1
kind: CronJob
metadata:
name: cronjob-test-1
namespace: testspace
spec:
jobTemplate:
spec:
template:
metadata:
labels:
app: foo
spec:
containers:
- name: foobar
image: foo:bar
070701000000CE000081A4000000000000000000000001680FB1F8000001F4000000000000000000000000000000000000005200000000kube-score-1.20.0/score/testdata/networkpolicy-cronjob-not-matching-selector.yamlapiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: testapp-netpol
namespace: testspace
spec:
podSelector:
matchLabels:
app: foo
policyTypes:
- Egress
- Ingress
---
apiVersion: batch/v1beta1
kind: CronJob
metadata:
name: cronjob-test-1
namespace: testspace
spec:
jobTemplate:
spec:
template:
metadata:
labels:
app: not-foo
spec:
containers:
- name: foobar
image: foo:bar
070701000000CF000081A4000000000000000000000001680FB1F8000001BB000000000000000000000000000000000000004800000000kube-score-1.20.0/score/testdata/networkpolicy-deployment-matching.yamlapiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: testapp-netpol
namespace: testspace
spec:
podSelector:
matchLabels:
app: foo
policyTypes:
- Egress
- Ingress
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: deployment-test-1
namespace: testspace
spec:
template:
metadata:
labels:
app: foo
spec:
containers:
- name: foobar
image: foo:bar
070701000000D0000081A4000000000000000000000001680FB1F8000001BF000000000000000000000000000000000000005500000000kube-score-1.20.0/score/testdata/networkpolicy-deployment-not-matching-selector.yamlapiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: testapp-netpol
namespace: testspace
spec:
podSelector:
matchLabels:
app: foo
policyTypes:
- Egress
- Ingress
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: deployment-test-1
namespace: testspace
spec:
template:
metadata:
labels:
app: not-foo
spec:
containers:
- name: foobar
image: foo:bar
070701000000D1000081A4000000000000000000000001680FB1F8000001C6000000000000000000000000000000000000004900000000kube-score-1.20.0/score/testdata/networkpolicy-matching-only-egress.yamlapiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: testapp-netpol
namespace: testspace
spec:
podSelector:
matchLabels:
app: testapp
egress:
- ports:
- port: 53
protocol: TCP
- port: 53
protocol: UDP
policyTypes:
- Egress
---
apiVersion: v1
kind: Pod
metadata:
name: pod-test-1
namespace: testspace
labels:
app: testapp
spec:
containers:
- name: foobar
image: foo/bar:latest
070701000000D2000081A4000000000000000000000001680FB1F8000001C8000000000000000000000000000000000000004A00000000kube-score-1.20.0/score/testdata/networkpolicy-matching-only-ingress.yamlapiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: testapp-netpol
namespace: testspace
spec:
podSelector:
matchLabels:
app: testapp
ingress:
- ports:
- port: 53
protocol: TCP
- port: 53
protocol: UDP
policyTypes:
- Ingress
---
apiVersion: v1
kind: Pod
metadata:
name: pod-test-1
namespace: testspace
labels:
app: testapp
spec:
containers:
- name: foobar
image: foo/bar:latest
070701000000D3000081A4000000000000000000000001680FB1F800000249000000000000000000000000000000000000003D00000000kube-score-1.20.0/score/testdata/networkpolicy-matching.yamlapiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: testapp-netpol
namespace: testspace
spec:
podSelector:
matchLabels:
app: testapp
egress:
- ports:
- port: 53
protocol: TCP
- port: 53
protocol: UDP
to:
- namespaceSelector:
matchLabels:
service: kube-system
policyTypes:
- Ingress
- Egress
---
apiVersion: v1
kind: Pod
metadata:
name: pod-test-1
namespace: testspace
labels:
app: testapp
spec:
containers:
- name: foobar
image: foo/bar:latest
070701000000D4000081A4000000000000000000000001680FB1F80000012B000000000000000000000000000000000000004100000000kube-score-1.20.0/score/testdata/networkpolicy-not-matching.yamlapiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: testapp-netpol
namespace: testspace
spec:
podSelector:
app: testapp
---
apiVersion: v1
kind: Pod
metadata:
name: pod-test-1
labels:
app: not-testapp
spec:
containers:
- name: foobar
image: foo/bar:latest
070701000000D5000081A4000000000000000000000001680FB1F8000001E9000000000000000000000000000000000000004900000000kube-score-1.20.0/score/testdata/networkpolicy-statefulset-matching.yamlapiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: testapp-netpol
namespace: testspace
spec:
podSelector:
matchLabels:
app: foo
policyTypes:
- Egress
- Ingress
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: statefulset-test-1
namespace: testspace
spec:
selector:
matchLabels:
app: foo
template:
metadata:
labels:
app: foo
spec:
containers:
- name: foobar
image: foo:bar
070701000000D6000081A4000000000000000000000001680FB1F8000001ED000000000000000000000000000000000000005600000000kube-score-1.20.0/score/testdata/networkpolicy-statefulset-not-matching-selector.yamlapiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: testapp-netpol
namespace: testspace
spec:
podSelector:
matchLabels:
app: foo
policyTypes:
- Egress
- Ingress
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: statefulset-test-1
namespace: testspace
spec:
selector:
matchLabels:
app: foo
template:
metadata:
labels:
app: not-foo
spec:
containers:
- name: foobar
image: foo:bar
070701000000D7000081A4000000000000000000000001680FB1F800000181000000000000000000000000000000000000004500000000kube-score-1.20.0/score/testdata/networkpolicy-targets-all-pods.yamlapiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: testapp-netpol
namespace: testspace
spec:
podSelector: {}
ingress:
- {}
egress:
- {}
policyTypes:
- Ingress
- Egress
---
apiVersion: v1
kind: Pod
metadata:
name: pod-test-1
namespace: testspace
labels:
app: testapp
spec:
containers:
- name: foobar
image: foo/bar:latest
070701000000D8000081A4000000000000000000000001680FB1F800000202000000000000000000000000000000000000004B00000000kube-score-1.20.0/score/testdata/networkpolicy-targets-pod-deployment.yamlapiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: testapp-netpol
namespace: testspace
spec:
podSelector:
matchLabels:
app: foo
egress:
- ports:
- port: 53
protocol: TCP
- port: 53
protocol: UDP
policyTypes:
- Egress
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: statefulset-test-1
namespace: testspace
spec:
template:
metadata:
labels:
app: foo
spec:
containers:
- name: foobar
image: foo:bar070701000000D9000081A4000000000000000000000001680FB1F8000001CA000000000000000000000000000000000000004D00000000kube-score-1.20.0/score/testdata/networkpolicy-targets-pod-not-matching.yamlapiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: testapp-netpol
namespace: testspace
spec:
podSelector:
matchLabels:
app: not-testapp
egress:
- ports:
- port: 53
protocol: TCP
- port: 53
protocol: UDP
policyTypes:
- Egress
---
apiVersion: v1
kind: Pod
metadata:
name: pod-test-1
namespace: testspace
labels:
app: testapp
spec:
containers:
- name: foobar
image: foo/bar:latest
070701000000DA000081A4000000000000000000000001680FB1F8000001C6000000000000000000000000000000000000004000000000kube-score-1.20.0/score/testdata/networkpolicy-targets-pod.yamlapiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: testapp-netpol
namespace: testspace
spec:
podSelector:
matchLabels:
app: testapp
egress:
- ports:
- port: 53
protocol: TCP
- port: 53
protocol: UDP
policyTypes:
- Egress
---
apiVersion: v1
kind: Pod
metadata:
name: pod-test-1
namespace: testspace
labels:
app: testapp
spec:
containers:
- name: foobar
image: foo/bar:latest
070701000000DB000081A4000000000000000000000001680FB1F8000003E5000000000000000000000000000000000000005800000000kube-score-1.20.0/score/testdata/pod-container-memory-requests-annotation-optional.yamlapiVersion: v1
kind: Pod
metadata:
name: pod-test-1
namespace: testspace
labels:
app: foo-all-ok
annotations:
kube-score/enable: container-resource-requests-equal-limits
spec:
containers:
- name: foobar
image: foo/bar:123
imagePullPolicy: Always
resources:
requests:
cpu: 1
memory: 1Gi
ephemeral-storage: 500Mi
limits:
cpu: 1
memory: 2Gi
ephemeral-storage: 500Mi
readinessProbe:
httpGet:
path: /ready
port: 8080
livenessProbe:
httpGet:
path: /live
port: 8080
securityContext:
privileged: False
runAsUser: 30000
runAsGroup: 30000
readOnlyRootFilesystem: True
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: foo-all-ok-netpol
namespace: testspace
spec:
podSelector:
matchLabels:
app: foo-all-ok
policyTypes:
- Egress
- Ingress070701000000DC000081A4000000000000000000000001680FB1F800000396000000000000000000000000000000000000004400000000kube-score-1.20.0/score/testdata/pod-container-memory-requests.yamlapiVersion: v1
kind: Pod
metadata:
name: pod-test-1
namespace: testspace
labels:
app: foo-all-ok
spec:
containers:
- name: foobar
image: foo/bar:123
imagePullPolicy: Always
resources:
requests:
cpu: 1
memory: 1Gi
ephemeral-storage: 500Mi
limits:
cpu: 1
memory: 2Gi
ephemeral-storage: 500Mi
readinessProbe:
httpGet:
path: /ready
port: 8080
livenessProbe:
httpGet:
path: /live
port: 8080
securityContext:
privileged: False
runAsUser: 30000
runAsGroup: 30000
readOnlyRootFilesystem: True
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: foo-all-ok-netpol
namespace: testspace
spec:
podSelector:
matchLabels:
app: foo-all-ok
policyTypes:
- Egress
- Ingress070701000000DD000081A4000000000000000000000001680FB1F8000001FF000000000000000000000000000000000000004A00000000kube-score-1.20.0/score/testdata/pod-container-ports-duplicate-names.yamlapiVersion: apps/v1
kind: Deployment
metadata:
name: app
labels:
app: app
spec:
selector:
matchLabels:
app: app
template:
metadata:
labels:
app: app
spec:
nodeSelector:
beta.kubernetes.io/os: linux
containers:
- name: app
image: app:dev
imagePullPolicy: "Always"
ports:
- name: app
containerPort: 80
protocol: TCP
- name: app
containerPort: 8080
protocol: TCP
070701000000DE000081A4000000000000000000000001680FB1F800000237000000000000000000000000000000000000006400000000kube-score-1.20.0/score/testdata/pod-container-ports-missing-containerport-annotation-optional.yamlapiVersion: apps/v1
kind: Deployment
metadata:
name: app
labels:
app: app
annotations:
kube-score/enable: container-ports-check
spec:
selector:
matchLabels:
app: app
template:
metadata:
labels:
app: app
spec:
nodeSelector:
beta.kubernetes.io/os: linux
containers:
- name: app
image: app:dev
imagePullPolicy: "Always"
ports:
- name: app
containerPort: 80
protocol: TCP
- name: smtp
containerPort:
protocol: TCP
070701000000DF000081A4000000000000000000000001680FB1F8000001FB000000000000000000000000000000000000005000000000kube-score-1.20.0/score/testdata/pod-container-ports-missing-containerport.yamlapiVersion: apps/v1
kind: Deployment
metadata:
name: app
labels:
app: app
spec:
selector:
matchLabels:
app: app
template:
metadata:
labels:
app: app
spec:
nodeSelector:
beta.kubernetes.io/os: linux
containers:
- name: app
image: app:dev
imagePullPolicy: "Always"
ports:
- name: app
containerPort: 80
protocol: TCP
- name: smtp
containerPort:
protocol: TCP
070701000000E0000081A4000000000000000000000001680FB1F80000022D000000000000000000000000000000000000004800000000kube-score-1.20.0/score/testdata/pod-container-ports-name-too-long.yamlapiVersion: apps/v1
kind: Deployment
metadata:
name: app
labels:
app: app
spec:
selector:
matchLabels:
app: app
template:
metadata:
labels:
app: app
spec:
nodeSelector:
beta.kubernetes.io/os: linux
containers:
- name: app
image: app:dev
imagePullPolicy: "Always"
ports:
- name: my-very-long-port-name-exceeds-fifteen-characters
containerPort: 80
protocol: TCP
- name: app
containerPort: 8080
protocol: TCP
070701000000E1000081A4000000000000000000000001680FB1F80000022E000000000000000000000000000000000000003D00000000kube-score-1.20.0/score/testdata/pod-container-ports-ok.yamlapiVersion: apps/v1
kind: Deployment
metadata:
name: app
labels:
app: app
spec:
selector:
matchLabels:
app: app
template:
metadata:
labels:
app: app
spec:
nodeSelector:
beta.kubernetes.io/os: linux
containers:
- name: app
image: app:dev
imagePullPolicy: "Always"
ports:
- name: app
containerPort: 80
hostPort: 8080
protocol: TCP
- name: smtp
containerPort: 25
hostPort: 465
protocol: TCP
070701000000E2000081A4000000000000000000000001680FB1F800000123000000000000000000000000000000000000003900000000kube-score-1.20.0/score/testdata/pod-env-duplicated.yamlapiVersion: v1
kind: Pod
metadata:
name: pod-test-1
spec:
containers:
- name: foobar
image: foo/bar:latest
env:
- name: foo
value: foo
- name: bar
value: bar
- name: bar
value: bar
- name: baz
value: baz
- name: baz
value: baz
070701000000E3000081A4000000000000000000000001680FB1F8000000C0000000000000000000000000000000000000003100000000kube-score-1.20.0/score/testdata/pod-env-ok.yamlapiVersion: v1
kind: Pod
metadata:
name: pod-test-1
spec:
containers:
- name: foobar
image: foo/bar:latest
env:
- name: foo
value: foo
- name: bar
value: bar
070701000000E4000081A4000000000000000000000001680FB1F800000151000000000000000000000000000000000000004E00000000kube-score-1.20.0/score/testdata/pod-ephemeral-storage-annotation-ignore.yamlapiVersion: v1
kind: Pod
metadata:
name: pod-ephemeral-storage-annotation-ignore
annotations:
"kube-score/ignore": 'container-resources'
spec:
containers:
- name: foobar
image: foo/bar:123
resources:
limits:
cpu: 200m
memory: 1Gi
ephemeral-storage: 2Gi
requests:
cpu: 200m
070701000000E5000081A4000000000000000000000001680FB1F80000010A000000000000000000000000000000000000004A00000000kube-score-1.20.0/score/testdata/pod-ephemeral-storage-missing-limit.yamlapiVersion: v1
kind: Pod
metadata:
name: pod-test-1
spec:
containers:
- name: foobar
image: foo/bar:123
resources:
limits:
cpu: 200m
memory: 1Gi
requests:
cpu: 200m
memory: 1Gi
ephemeral-storage: 2Gi
070701000000E6000081A4000000000000000000000001680FB1F80000010A000000000000000000000000000000000000004C00000000kube-score-1.20.0/score/testdata/pod-ephemeral-storage-missing-request.yamlapiVersion: v1
kind: Pod
metadata:
name: pod-test-1
spec:
containers:
- name: foobar
image: foo/bar:123
resources:
limits:
cpu: 200m
memory: 1Gi
ephemeral-storage: 2Gi
requests:
cpu: 200m
memory: 1Gi
070701000000E7000081A4000000000000000000000001680FB1F800000129000000000000000000000000000000000000005200000000kube-score-1.20.0/score/testdata/pod-ephemeral-storage-request-matches-limit.yamlapiVersion: v1
kind: Pod
metadata:
name: pod-test-1
spec:
containers:
- name: foobar
image: foo/bar:123
resources:
limits:
cpu: 200m
memory: 1Gi
ephemeral-storage: 2Gi
requests:
cpu: 200m
memory: 1Gi
ephemeral-storage: 2Gi
070701000000E8000081A4000000000000000000000001680FB1F800000180000000000000000000000000000000000000006600000000kube-score-1.20.0/score/testdata/pod-ephemeral-storage-request-nomatch-limit-annotation-optional.yamlapiVersion: v1
kind: Pod
metadata:
name: pod-test-1
annotations:
kube-score/enable: container-ephemeral-storage-request-equals-limit
spec:
containers:
- name: foobar
image: foo/bar:123
resources:
limits:
cpu: 200m
memory: 1Gi
ephemeral-storage: 2Gi
requests:
cpu: 200m
memory: 1Gi
ephemeral-storage: 1Gi
070701000000E9000081A4000000000000000000000001680FB1F800000129000000000000000000000000000000000000005200000000kube-score-1.20.0/score/testdata/pod-ephemeral-storage-request-nomatch-limit.yamlapiVersion: v1
kind: Pod
metadata:
name: pod-test-1
spec:
containers:
- name: foobar
image: foo/bar:123
resources:
limits:
cpu: 200m
memory: 1Gi
ephemeral-storage: 2Gi
requests:
cpu: 200m
memory: 1Gi
ephemeral-storage: 1Gi
070701000000EA000081A4000000000000000000000001680FB1F80000008E000000000000000000000000000000000000004200000000kube-score-1.20.0/score/testdata/pod-image-pullpolicy-always.yamlapiVersion: v1
kind: Pod
metadata:
name: pod-test-1
spec:
containers:
- name: foobar
image: foo/bar:123
imagePullPolicy: Always
070701000000EB000081A4000000000000000000000001680FB1F80000008D000000000000000000000000000000000000004100000000kube-score-1.20.0/score/testdata/pod-image-pullpolicy-never.yamlapiVersion: v1
kind: Pod
metadata:
name: pod-test-1
spec:
containers:
- name: foobar
image: foo/bar:123
imagePullPolicy: Never
070701000000EC000081A4000000000000000000000001680FB1F800000075000000000000000000000000000000000000005000000000kube-score-1.20.0/score/testdata/pod-image-pullpolicy-undefined-latest-tag.yamlapiVersion: v1
kind: Pod
metadata:
name: pod-test-1
spec:
containers:
- name: foobar
image: foo/bar:latest
070701000000ED000081A4000000000000000000000001680FB1F80000006E000000000000000000000000000000000000004C00000000kube-score-1.20.0/score/testdata/pod-image-pullpolicy-undefined-no-tag.yamlapiVersion: v1
kind: Pod
metadata:
name: pod-test-1
spec:
containers:
- name: foobar
image: foo/bar
070701000000EE000081A4000000000000000000000001680FB1F800000072000000000000000000000000000000000000004500000000kube-score-1.20.0/score/testdata/pod-image-pullpolicy-undefined.yamlapiVersion: v1
kind: Pod
metadata:
name: pod-test-1
spec:
containers:
- name: foobar
image: foo/bar:123
070701000000EF000081A4000000000000000000000001680FB1F800000072000000000000000000000000000000000000003A00000000kube-score-1.20.0/score/testdata/pod-image-tag-fixed.yamlapiVersion: v1
kind: Pod
metadata:
name: pod-test-1
spec:
containers:
- name: foobar
image: foo/bar:123
070701000000F0000081A4000000000000000000000001680FB1F800000075000000000000000000000000000000000000003B00000000kube-score-1.20.0/score/testdata/pod-image-tag-latest.yamlapiVersion: v1
kind: Pod
metadata:
name: pod-test-1
spec:
containers:
- name: foobar
image: foo/bar:latest
070701000000F1000081A4000000000000000000000001680FB1F80000012D000000000000000000000000000000000000003D00000000kube-score-1.20.0/score/testdata/pod-probes-all-missing.yamlapiVersion: v1
kind: Pod
metadata:
name: pod-test-1
labels:
app: test
spec:
containers:
- name: foobar
image: foo/bar:latest
---
kind: Service
apiVersion: v1
metadata:
name: my-service
spec:
selector:
app: test
ports:
- protocol: TCP
port: 80
targetPort: 8080070701000000F2000081A4000000000000000000000001680FB1F8000001C1000000000000000000000000000000000000003600000000kube-score-1.20.0/score/testdata/pod-probes-both.yamlapiVersion: v1
kind: Pod
metadata:
name: pod-test-1
labels:
app: test
spec:
containers:
- name: foobar
image: foo/bar:latest
readinessProbe:
httpGet:
path: /ready
port: 8080
livenessProbe:
httpGet:
path: /live
port: 8080
---
kind: Service
apiVersion: v1
metadata:
name: my-service
spec:
selector:
app: test
ports:
- protocol: TCP
port: 80
targetPort: 8080070701000000F3000081A4000000000000000000000001680FB1F8000001CE000000000000000000000000000000000000004000000000kube-score-1.20.0/score/testdata/pod-probes-identical-exec.yamlapiVersion: v1
kind: Pod
metadata:
name: pod-test-1
labels:
app: test
spec:
containers:
- name: foobar
image: foo/bar:latest
readinessProbe:
exec:
command:
- "abc"
- "123"
livenessProbe:
exec:
command:
- "abc"
- "123"
---
kind: Service
apiVersion: v1
metadata:
name: my-service
spec:
selector:
app: test
ports:
- protocol: TCP
port: 80
targetPort: 8080070701000000F4000081A4000000000000000000000001680FB1F8000001C4000000000000000000000000000000000000004000000000kube-score-1.20.0/score/testdata/pod-probes-identical-http.yamlapiVersion: v1
kind: Pod
metadata:
name: pod-test-1
labels:
app: test
spec:
containers:
- name: foobar
image: foo/bar:latest
readinessProbe:
httpGet:
path: /ready
port: 8080
livenessProbe:
httpGet:
path: /ready
port: 8080
---
kind: Service
apiVersion: v1
metadata:
name: my-service
spec:
selector:
app: test
ports:
- protocol: TCP
port: 80
targetPort: 8080
070701000000F5000081A4000000000000000000000001680FB1F80000019C000000000000000000000000000000000000003F00000000kube-score-1.20.0/score/testdata/pod-probes-identical-tcp.yamlapiVersion: v1
kind: Pod
metadata:
name: pod-test-1
labels:
app: test
spec:
containers:
- name: foobar
image: foo/bar:latest
readinessProbe:
tcpSocket:
port: 8080
livenessProbe:
tcpSocket:
port: 8080
---
kind: Service
apiVersion: v1
metadata:
name: my-service
spec:
selector:
app: test
ports:
- protocol: TCP
port: 80
targetPort: 8080070701000000F6000081A4000000000000000000000001680FB1F800000179000000000000000000000000000000000000003E00000000kube-score-1.20.0/score/testdata/pod-probes-missing-live.yamlapiVersion: v1
kind: Pod
metadata:
name: pod-test-1
labels:
app: test
spec:
containers:
- name: foobar
image: foo/bar:latest
readinessProbe:
httpGet:
path: /ready
port: 8080
---
kind: Service
apiVersion: v1
metadata:
name: my-service
spec:
selector:
app: test
ports:
- protocol: TCP
port: 80
targetPort: 8080070701000000F7000081A4000000000000000000000001680FB1F800000177000000000000000000000000000000000000003F00000000kube-score-1.20.0/score/testdata/pod-probes-missing-ready.yamlapiVersion: v1
kind: Pod
metadata:
name: pod-test-1
labels:
app: test
spec:
containers:
- name: foobar
image: foo/bar:latest
livenessProbe:
httpGet:
path: /live
port: 8080
---
kind: Service
apiVersion: v1
metadata:
name: my-service
spec:
selector:
app: test
ports:
- protocol: TCP
port: 80
targetPort: 8080070701000000F8000081A4000000000000000000000001680FB1F8000001AB000000000000000000000000000000000000005600000000kube-score-1.20.0/score/testdata/pod-probes-not-targeted-by-service-multi-labels.yamlapiVersion: v1
kind: Pod
metadata:
name: pod-test-1
labels:
app: my-app
otherLabel: foo
spec:
containers:
- name: foobar
image: foo/bar:latest
livenessProbe:
httpGet:
path: /ready
port: 8080
---
kind: Service
apiVersion: v1
metadata:
name: my-service
spec:
selector:
app: my-app-not-matching
otherLabel: foo
ports:
- protocol: TCP
port: 80
targetPort: 8080
070701000000F9000081A4000000000000000000000001680FB1F800000183000000000000000000000000000000000000004900000000kube-score-1.20.0/score/testdata/pod-probes-not-targeted-by-service.yamlapiVersion: v1
kind: Pod
metadata:
name: pod-test-1
labels:
app: my-app
spec:
containers:
- name: foobar
image: foo/bar:latest
livenessProbe:
httpGet:
path: /ready
port: 8080
---
kind: Service
apiVersion: v1
metadata:
name: my-service
spec:
selector:
app: my-app-not-matching
ports:
- protocol: TCP
port: 80
targetPort: 8080
070701000000FA000081A4000000000000000000000001680FB1F800000221000000000000000000000000000000000000004E00000000kube-score-1.20.0/score/testdata/pod-probes-on-different-containers-init.yamlapiVersion: v1
kind: Pod
metadata:
name: pod-test-1
labels:
app: test
spec:
initContainers:
- name: foobar2
image: foo/bar:latest
containers:
- name: foobar
image: foo/bar:latest
readinessProbe:
httpGet:
path: /ready
port: 8080
livenessProbe:
httpGet:
path: /live
port: 8080
---
kind: Service
apiVersion: v1
metadata:
name: my-service
spec:
selector:
app: test
ports:
- protocol: TCP
port: 80
targetPort: 8080070701000000FB000081A4000000000000000000000001680FB1F80000020E000000000000000000000000000000000000004900000000kube-score-1.20.0/score/testdata/pod-probes-on-different-containers.yamlapiVersion: v1
kind: Pod
metadata:
name: pod-test-1
labels:
app: test
spec:
containers:
- name: foobar
image: foo/bar:latest
readinessProbe:
httpGet:
path: /ready
port: 8080
livenessProbe:
httpGet:
path: /live
port: 8080
- name: foobar2
image: foo/bar:latest
---
kind: Service
apiVersion: v1
metadata:
name: my-service
spec:
selector:
app: test
ports:
- protocol: TCP
port: 80
targetPort: 8080070701000000FC000081A4000000000000000000000001680FB1F8000001A6000000000000000000000000000000000000005900000000kube-score-1.20.0/score/testdata/pod-probes-targeted-by-service-different-namespace.yamlapiVersion: v1
kind: Pod
metadata:
name: pod-test-1
namespace: foospace
labels:
app: my-app
spec:
containers:
- name: foobar
image: foo/bar:latest
livenessProbe:
httpGet:
path: /ready
port: 8080
---
kind: Service
apiVersion: v1
metadata:
name: my-service
namespace: not-foospace
spec:
selector:
app: my-app
ports:
- protocol: TCP
port: 80
targetPort: 8080
070701000000FD000081A4000000000000000000000001680FB1F8000001BC000000000000000000000000000000000000006100000000kube-score-1.20.0/score/testdata/pod-probes-targeted-by-service-same-namespace-multi-labels.yamlapiVersion: v1
kind: Pod
metadata:
name: pod-test-1
namespace: foospace
labels:
app: my-app
foo: bar
spec:
containers:
- name: foobar
image: foo/bar:latest
livenessProbe:
httpGet:
path: /ready
port: 8080
---
kind: Service
apiVersion: v1
metadata:
name: my-service
namespace: foospace
spec:
selector:
app: my-app
foo: bar
ports:
- protocol: TCP
port: 80
targetPort: 8080
070701000000FE000081A4000000000000000000000001680FB1F8000001A2000000000000000000000000000000000000005400000000kube-score-1.20.0/score/testdata/pod-probes-targeted-by-service-same-namespace.yamlapiVersion: v1
kind: Pod
metadata:
name: pod-test-1
namespace: foospace
labels:
app: my-app
spec:
containers:
- name: foobar
image: foo/bar:latest
livenessProbe:
httpGet:
path: /ready
port: 8080
---
kind: Service
apiVersion: v1
metadata:
name: my-service
namespace: foospace
spec:
selector:
app: my-app
ports:
- protocol: TCP
port: 80
targetPort: 8080
070701000000FF000081A4000000000000000000000001680FB1F800000176000000000000000000000000000000000000004500000000kube-score-1.20.0/score/testdata/pod-probes-targeted-by-service.yamlapiVersion: v1
kind: Pod
metadata:
name: pod-test-1
labels:
app: my-app
spec:
containers:
- name: foobar
image: foo/bar:latest
livenessProbe:
httpGet:
path: /ready
port: 8080
---
kind: Service
apiVersion: v1
metadata:
name: my-service
spec:
selector:
app: my-app
ports:
- protocol: TCP
port: 80
targetPort: 8080
07070100000100000081A4000000000000000000000001680FB1F800000109000000000000000000000000000000000000005000000000kube-score-1.20.0/score/testdata/pod-seccomp-annotated-annotation-optional.yamlapiVersion: v1
kind: Pod
metadata:
name: pod-test-1
annotations:
seccomp.security.alpha.kubernetes.io/defaultProfileName: 'runtime/default'
kube-score/enable: container-seccomp-profile
spec:
containers:
- name: foobar
image: foo/bar:latest
07070100000101000081A4000000000000000000000001680FB1F8000000D8000000000000000000000000000000000000003C00000000kube-score-1.20.0/score/testdata/pod-seccomp-annotated.yamlapiVersion: v1
kind: Pod
metadata:
name: pod-test-1
annotations:
seccomp.security.alpha.kubernetes.io/defaultProfileName: 'runtime/default'
spec:
containers:
- name: foobar
image: foo/bar:latest
07070100000102000081A4000000000000000000000001680FB1F800000129000000000000000000000000000000000000004D00000000kube-score-1.20.0/score/testdata/pod-seccomp-container-securecontext-ok.yamlapiVersion: v1
kind: Pod
metadata:
name: pod-test-1
spec:
containers:
- name: foobar
image: foo/bar:latest
securityContext:
privileged: False
runAsUser: 30000
runAsGroup: 30000
readOnlyRootFilesystem: True
seccompProfile:
type: RuntimeDefault
07070100000103000081A4000000000000000000000001680FB1F8000002AF000000000000000000000000000000000000005200000000kube-score-1.20.0/score/testdata/pod-seccomp-container-securecontext-warning.yamlapiVersion: v1
kind: Pod
metadata:
name: pod-test-1
spec:
containers:
- name: foobar
image: foo/bar:latest
securityContext:
privileged: False
runAsUser: 30000
runAsGroup: 30000
readOnlyRootFilesystem: True
seccompProfile:
type: RuntimeDefault
- name: foobaz
image: foo/baz:latest
securityContext:
privileged: False
runAsUser: 30000
runAsGroup: 30000
readOnlyRootFilesystem: True
- name: foozed
image: foo/zed:latest
securityContext:
privileged: False
runAsUser: 30000
runAsGroup: 30000
readOnlyRootFilesystem: True
seccompProfile:
type: RuntimeDefault 07070100000104000081A4000000000000000000000001680FB1F800000079000000000000000000000000000000000000004000000000kube-score-1.20.0/score/testdata/pod-seccomp-no-annotation.yamlapiVersion: v1
kind: Pod
metadata:
name: pod-test-1
spec:
containers:
- name: foobar
image: foo/bar:latest
07070100000105000081A4000000000000000000000001680FB1F800000136000000000000000000000000000000000000004300000000kube-score-1.20.0/score/testdata/pod-seccomp-securecontext-ok.yamlapiVersion: v1
kind: Pod
metadata:
name: pod-test-1
spec:
securityContext:
seccompProfile:
type: RuntimeDefault
containers:
- name: foobar
image: foo/bar:latest
securityContext:
privileged: False
runAsUser: 30000
runAsGroup: 30000
readOnlyRootFilesystem: True
07070100000106000081A4000000000000000000000001680FB1F80000011E000000000000000000000000000000000000004800000000kube-score-1.20.0/score/testdata/pod-seccomp-securecontext-warning.yamlapiVersion: v1
kind: Pod
metadata:
name: pod-test-1
spec:
securityContext:
privileged: False
runAsUser: 30000
runAsGroup: 30000
readOnlyRootFilesystem: True
containers:
- name: foobar
image: foo/bar:latest
- name: foobaz
image: foo/baz:latest
07070100000107000081A4000000000000000000000001680FB1F8000000F4000000000000000000000000000000000000004400000000kube-score-1.20.0/score/testdata/pod-security-context-all-good.yamlapiVersion: v1
kind: Pod
metadata:
name: pod-test-1
spec:
containers:
- name: foobar
image: foo/bar:latest
securityContext:
privileged: False
runAsUser: 30000
runAsGroup: 30000
readOnlyRootFilesystem: True
07070100000108000081A4000000000000000000000001680FB1F8000000B8000000000000000000000000000000000000004400000000kube-score-1.20.0/score/testdata/pod-security-context-high-ids.yamlapiVersion: v1
kind: Pod
metadata:
name: pod-test-1
spec:
containers:
- name: foobar
image: foo/bar:latest
securityContext:
runAsUser: 12000
runAsGroup: 1300007070100000109000081A4000000000000000000000001680FB1F8000000B4000000000000000000000000000000000000004800000000kube-score-1.20.0/score/testdata/pod-security-context-low-group-id.yamlapiVersion: v1
kind: Pod
metadata:
name: pod-test-1
spec:
containers:
- name: foobar
image: foo/bar:latest
securityContext:
runAsUser: 15000
runAsGroup: 00707010000010A000081A4000000000000000000000001680FB1F8000000B4000000000000000000000000000000000000004700000000kube-score-1.20.0/score/testdata/pod-security-context-low-user-id.yamlapiVersion: v1
kind: Pod
metadata:
name: pod-test-1
spec:
containers:
- name: foobar
image: foo/bar:latest
securityContext:
runAsUser: 0
runAsGroup: 200000707010000010B000081A4000000000000000000000001680FB1F800000074000000000000000000000000000000000000004D00000000kube-score-1.20.0/score/testdata/pod-security-context-nosecuritycontext.yamlapiVersion: v1
kind: Pod
metadata:
name: pod-test-1
spec:
containers:
- name: foobar
image: foo/bar:latest0707010000010C000081A4000000000000000000000001680FB1F8000000A0000000000000000000000000000000000000004600000000kube-score-1.20.0/score/testdata/pod-security-context-privileged.yamlapiVersion: v1
kind: Pod
metadata:
name: pod-test-1
spec:
containers:
- name: foobar
image: foo/bar:latest
securityContext:
privileged: True0707010000010D000081A4000000000000000000000001680FB1F8000000A0000000000000000000000000000000000000005300000000kube-score-1.20.0/score/testdata/pod-security-context-writeablerootfilesystem.yamlapiVersion: v1
kind: Pod
metadata:
name: pod-test-1
spec:
containers:
- name: foobar
image: foo/bar:latest
securityContext:
privileged: True0707010000010E000081A4000000000000000000000001680FB1F8000000D9000000000000000000000000000000000000005A00000000kube-score-1.20.0/score/testdata/pod-test-resources-limits-and-requests-no-cpu-limit.yamlapiVersion: v1
kind: Pod
metadata:
name: pod-test-1
spec:
containers:
- name: foobar
image: foo/bar:123
resources:
limits:
memory: 1Gi
requests:
cpu: 200m
memory: 1Gi
0707010000010F000081A4000000000000000000000001680FB1F8000000EB000000000000000000000000000000000000004D00000000kube-score-1.20.0/score/testdata/pod-test-resources-limits-and-requests.yamlapiVersion: v1
kind: Pod
metadata:
name: pod-test-1
spec:
containers:
- name: foobar
image: foo/bar:123
resources:
limits:
cpu: 200m
memory: 1Gi
requests:
cpu: 200m
memory: 1Gi
07070100000110000081A4000000000000000000000001680FB1F80000015A000000000000000000000000000000000000005700000000kube-score-1.20.0/score/testdata/pod-test-resources-no-limits-annotation-optional.yamlapiVersion: v1
kind: Pod
metadata:
name: pod-test-1
annotations:
kube-score/enable: container-memory-requests-equal-limits, container-cpu-requests-equal-limits , container-resource-requests-equal-limits
spec:
containers:
- name: foobar
image: foo/bar:123
resources:
requests:
cpu: 200m
memory: 1Gi
07070100000111000081A4000000000000000000000001680FB1F8000000B7000000000000000000000000000000000000004300000000kube-score-1.20.0/score/testdata/pod-test-resources-no-limits.yamlapiVersion: v1
kind: Pod
metadata:
name: pod-test-1
spec:
containers:
- name: foobar
image: foo/bar:123
resources:
requests:
cpu: 200m
memory: 1Gi
07070100000112000081A4000000000000000000000001680FB1F800000072000000000000000000000000000000000000003E00000000kube-score-1.20.0/score/testdata/pod-test-resources-none.yamlapiVersion: v1
kind: Pod
metadata:
name: pod-test-1
spec:
containers:
- name: foobar
image: foo/bar:123
07070100000113000081A4000000000000000000000001680FB1F8000000B5000000000000000000000000000000000000004500000000kube-score-1.20.0/score/testdata/pod-test-resources-only-limits.yamlapiVersion: v1
kind: Pod
metadata:
name: pod-test-1
spec:
containers:
- name: foobar
image: foo/bar:123
resources:
limits:
cpu: 200m
memory: 1Gi
07070100000114000081A4000000000000000000000001680FB1F800000133000000000000000000000000000000000000005600000000kube-score-1.20.0/score/testdata/pod-topology-spread-constraints-invalid-maxskew.yamlkind: Pod
apiVersion: v1
metadata:
name: mypod
labels:
foo: bar
spec:
topologySpreadConstraints:
- maxSkew: 0
topologyKey: zone
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
foo: bar
containers:
- name: pause
image: registry.k8s.io/pause:3.1
07070100000115000081A4000000000000000000000001680FB1F800000145000000000000000000000000000000000000005900000000kube-score-1.20.0/score/testdata/pod-topology-spread-constraints-invalid-mindomains.yamlkind: Pod
apiVersion: v1
metadata:
name: mypod
labels:
foo: bar
spec:
topologySpreadConstraints:
- maxSkew: 1
minDomains: 0
topologyKey: zone
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
foo: bar
containers:
- name: pause
image: registry.k8s.io/pause:3.1
07070100000116000081A4000000000000000000000001680FB1F800000134000000000000000000000000000000000000006000000000kube-score-1.20.0/score/testdata/pod-topology-spread-constraints-invalid-whenunsatisfiable.yamlkind: Pod
apiVersion: v1
metadata:
name: mypod
labels:
foo: bar
spec:
topologySpreadConstraints:
- maxSkew: 1
topologyKey: zone
whenUnsatisfiable: DoNotSCHEDULE
labelSelector:
matchLabels:
foo: bar
containers:
- name: pause
image: registry.k8s.io/pause:3.1
07070100000117000081A4000000000000000000000001680FB1F8000000FC000000000000000000000000000000000000005700000000kube-score-1.20.0/score/testdata/pod-topology-spread-constraints-no-labelselector.yamlkind: Pod
apiVersion: v1
metadata:
name: mypod
labels:
foo: bar
spec:
topologySpreadConstraints:
- maxSkew: 1
topologyKey: zone
whenUnsatisfiable: DoNotSchedule
containers:
- name: pause
image: registry.k8s.io/pause:3.1
07070100000118000081A4000000000000000000000001680FB1F80000011D000000000000000000000000000000000000005500000000kube-score-1.20.0/score/testdata/pod-topology-spread-constraints-no-topologykey.yamlkind: Pod
apiVersion: v1
metadata:
name: mypod
labels:
foo: bar
spec:
topologySpreadConstraints:
- maxSkew: 1
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
foo: bar
containers:
- name: pause
image: registry.k8s.io/pause:3.1
07070100000119000081A4000000000000000000000001680FB1F800000133000000000000000000000000000000000000005500000000kube-score-1.20.0/score/testdata/pod-topology-spread-constraints-one-constraint.yamlkind: Pod
apiVersion: v1
metadata:
name: mypod
labels:
foo: bar
spec:
topologySpreadConstraints:
- maxSkew: 1
topologyKey: zone
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
foo: bar
containers:
- name: pause
image: registry.k8s.io/pause:3.1
0707010000011A000081A4000000000000000000000001680FB1F8000001AD000000000000000000000000000000000000005600000000kube-score-1.20.0/score/testdata/pod-topology-spread-constraints-two-constraints.yamlkind: Pod
apiVersion: v1
metadata:
name: mypod
labels:
foo: bar
spec:
topologySpreadConstraints:
- maxSkew: 1
topologyKey: zone
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
foo: bar
- maxSkew: 1
topologyKey: node
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
foo: bar
containers:
- name: pause
image: registry.k8s.io/pause:3.10707010000011B000081A4000000000000000000000001680FB1F80000019D000000000000000000000000000000000000004C00000000kube-score-1.20.0/score/testdata/security-inherit-pod-security-context.yamlapiVersion: apps/v1
kind: Deployment
metadata:
name: test
labels:
app: test
spec:
selector:
matchLabels:
app: test
template:
metadata:
labels:
app: test
spec:
securityContext:
runAsGroup: 20000
runAsUser: 20000
containers:
- name: test
securityContext:
privileged: false
readOnlyRootFilesystem: true0707010000011C000081A4000000000000000000000001680FB1F8000000CD000000000000000000000000000000000000003B00000000kube-score-1.20.0/score/testdata/service-externalname.yamlapiVersion: v1
kind: Service
metadata:
name: wp-site
namespace: site
spec:
externalName: wp.example.com
ports:
- name: http
port: 80
protocol: TCP
targetPort: 80
type: ExternalName
0707010000011D000081A4000000000000000000000001680FB1F800000186000000000000000000000000000000000000004400000000kube-score-1.20.0/score/testdata/service-not-target-deployment.yamlapiVersion: apps/v1
kind: Deployment
metadata:
name: deployment-test-1
spec:
template:
metadata:
labels:
app: my-app
spec:
containers:
- name: foobar
image: foo/bar:123
---
kind: Service
apiVersion: v1
metadata:
name: my-service-no-match
spec:
selector:
app: my-app-no-match
ports:
- protocol: TCP
port: 80
targetPort: 8080
0707010000011E000081A4000000000000000000000001680FB1F800000153000000000000000000000000000000000000004900000000kube-score-1.20.0/score/testdata/service-not-target-pod-multi-label.yamlapiVersion: v1
kind: Pod
metadata:
name: pod-test-1
labels:
app: my-app
foo: bar
spec:
containers:
- name: foobar
image: foo/bar:latest
---
kind: Service
apiVersion: v1
metadata:
name: my-service
spec:
selector:
app: my-app
foo: bar-not-matching
ports:
- protocol: TCP
port: 80
targetPort: 8080
0707010000011F000081A4000000000000000000000001680FB1F800000139000000000000000000000000000000000000003D00000000kube-score-1.20.0/score/testdata/service-not-target-pod.yamlapiVersion: v1
kind: Pod
metadata:
name: pod-test-1
labels:
app: my-app
spec:
containers:
- name: foobar
image: foo/bar:latest
---
kind: Service
apiVersion: v1
metadata:
name: my-service
spec:
selector:
app: my-app-not-matching
ports:
- protocol: TCP
port: 80
targetPort: 8080
07070100000120000081A4000000000000000000000001680FB1F8000001A4000000000000000000000000000000000000005400000000kube-score-1.20.0/score/testdata/service-target-deployment-different-namespace.yamlapiVersion: apps/v1
kind: Deployment
metadata:
name: deployment-test-1
namespace: foospace
spec:
template:
metadata:
labels:
app: my-app
spec:
containers:
- name: foobar
image: foo/bar:123
---
kind: Service
apiVersion: v1
metadata:
name: my-service
namespace: not-foospace
spec:
selector:
app: my-app
ports:
- protocol: TCP
port: 80
targetPort: 8080
07070100000121000081A4000000000000000000000001680FB1F800000193000000000000000000000000000000000000004C00000000kube-score-1.20.0/score/testdata/service-target-deployment-not-rolling.yamlapiVersion: apps/v1
kind: Deployment
metadata:
name: deployment-test-1
spec:
template:
metadata:
labels:
app: my-app
spec:
containers:
- name: foobar
image: foo/bar:123
strategy:
type: Recreate
---
kind: Service
apiVersion: v1
metadata:
name: my-service
spec:
selector:
app: my-app
ports:
- protocol: TCP
port: 80
targetPort: 8080
07070100000122000081A4000000000000000000000001680FB1F8000001A6000000000000000000000000000000000000004A00000000kube-score-1.20.0/score/testdata/service-target-deployment-replica-1.yamlapiVersion: apps/v1
kind: Deployment
metadata:
name: deployment-test-1
spec:
template:
metadata:
labels:
app: my-app
spec:
containers:
- name: foobar
image: foo/bar:123
replicas: 1
strategy:
type: RollingUpdate
---
kind: Service
apiVersion: v1
metadata:
name: my-service
spec:
selector:
app: my-app
ports:
- protocol: TCP
port: 80
targetPort: 8080
07070100000123000081A4000000000000000000000001680FB1F8000001A0000000000000000000000000000000000000004F00000000kube-score-1.20.0/score/testdata/service-target-deployment-same-namespace.yamlapiVersion: apps/v1
kind: Deployment
metadata:
name: deployment-test-1
namespace: foospace
spec:
template:
metadata:
labels:
app: my-app
spec:
containers:
- name: foobar
image: foo/bar:123
---
kind: Service
apiVersion: v1
metadata:
name: my-service
namespace: foospace
spec:
selector:
app: my-app
ports:
- protocol: TCP
port: 80
targetPort: 8080
07070100000124000081A4000000000000000000000001680FB1F800000174000000000000000000000000000000000000005100000000kube-score-1.20.0/score/testdata/service-target-deployment-strategy-not-set.yamlapiVersion: apps/v1
kind: Deployment
metadata:
name: deployment-test-1
spec:
template:
metadata:
labels:
app: my-app
spec:
containers:
- name: foobar
image: foo/bar:123
---
kind: Service
apiVersion: v1
metadata:
name: my-service
spec:
selector:
app: my-app
ports:
- protocol: TCP
port: 80
targetPort: 8080
07070100000125000081A4000000000000000000000001680FB1F8000001A6000000000000000000000000000000000000004000000000kube-score-1.20.0/score/testdata/service-target-deployment.yamlapiVersion: apps/v1
kind: Deployment
metadata:
name: deployment-test-1
spec:
template:
metadata:
labels:
app: my-app
spec:
containers:
- name: foobar
image: foo/bar:123
replicas: 2
strategy:
type: RollingUpdate
---
kind: Service
apiVersion: v1
metadata:
name: my-service
spec:
selector:
app: my-app
ports:
- protocol: TCP
port: 80
targetPort: 8080
07070100000126000081A4000000000000000000000001680FB1F80000015C000000000000000000000000000000000000004D00000000kube-score-1.20.0/score/testdata/service-target-pod-different-namespace.yamlapiVersion: v1
kind: Pod
metadata:
name: pod-test-1
namespace: foospace
labels:
app: my-app
spec:
containers:
- name: foobar
image: foo/bar:latest
---
kind: Service
apiVersion: v1
metadata:
name: my-service
namespace: not-foospace
spec:
selector:
app: my-app
ports:
- protocol: TCP
port: 80
targetPort: 8080
07070100000127000081A4000000000000000000000001680FB1F800000146000000000000000000000000000000000000004500000000kube-score-1.20.0/score/testdata/service-target-pod-multi-label.yamlapiVersion: v1
kind: Pod
metadata:
name: pod-test-1
labels:
app: my-app
foo: bar
spec:
containers:
- name: foobar
image: foo/bar:latest
---
kind: Service
apiVersion: v1
metadata:
name: my-service
spec:
selector:
app: my-app
foo: bar
ports:
- protocol: TCP
port: 80
targetPort: 8080
07070100000128000081A4000000000000000000000001680FB1F800000158000000000000000000000000000000000000004800000000kube-score-1.20.0/score/testdata/service-target-pod-same-namespace.yamlapiVersion: v1
kind: Pod
metadata:
name: pod-test-1
namespace: foospace
labels:
app: my-app
spec:
containers:
- name: foobar
image: foo/bar:latest
---
kind: Service
apiVersion: v1
metadata:
name: my-service
namespace: foospace
spec:
selector:
app: my-app
ports:
- protocol: TCP
port: 80
targetPort: 8080
07070100000129000081A4000000000000000000000001680FB1F80000012C000000000000000000000000000000000000003900000000kube-score-1.20.0/score/testdata/service-target-pod.yamlapiVersion: v1
kind: Pod
metadata:
name: pod-test-1
labels:
app: my-app
spec:
containers:
- name: foobar
image: foo/bar:latest
---
kind: Service
apiVersion: v1
metadata:
name: my-service
spec:
selector:
app: my-app
ports:
- protocol: TCP
port: 80
targetPort: 8080
0707010000012A000081A4000000000000000000000001680FB1F8000000C8000000000000000000000000000000000000003D00000000kube-score-1.20.0/score/testdata/service-type-clusterip.yamlapiVersion: v1
kind: Service
metadata:
name: node-port-service
namespace: foospace
spec:
selector:
app: my-app
ports:
- protocol: TCP
port: 80
targetPort: 8080
type: ClusterIP
0707010000012B000081A4000000000000000000000001680FB1F8000000B6000000000000000000000000000000000000003B00000000kube-score-1.20.0/score/testdata/service-type-default.yamlapiVersion: v1
kind: Service
metadata:
name: node-port-service
namespace: foospace
spec:
selector:
app: my-app
ports:
- protocol: TCP
port: 80
targetPort: 8080
0707010000012C000081A4000000000000000000000001680FB1F8000000C7000000000000000000000000000000000000003C00000000kube-score-1.20.0/score/testdata/service-type-nodeport.yamlapiVersion: v1
kind: Service
metadata:
name: node-port-service
namespace: foospace
spec:
selector:
app: my-app
ports:
- protocol: TCP
port: 80
targetPort: 8080
type: NodePort
0707010000012D000081A4000000000000000000000001680FB1F80000012D000000000000000000000000000000000000003E00000000kube-score-1.20.0/score/testdata/statefulset-appsv1beta1.yamlapiVersion: apps/v1beta1
kind: StatefulSet
metadata:
name: statefulset-test-1
spec:
selector:
matchLabels:
app: foo
template:
spec:
containers:
- name: foobar
image: foo/bar:123
resources:
limits:
cpu: 200m
memory: 1Gi
0707010000012E000081A4000000000000000000000001680FB1F80000012D000000000000000000000000000000000000003E00000000kube-score-1.20.0/score/testdata/statefulset-appsv1beta2.yamlapiVersion: apps/v1beta2
kind: StatefulSet
metadata:
name: statefulset-test-1
spec:
selector:
matchLabels:
app: foo
template:
spec:
containers:
- name: foobar
image: foo/bar:123
resources:
limits:
cpu: 200m
memory: 1Gi
0707010000012F000081A4000000000000000000000001680FB1F80000011C000000000000000000000000000000000000004300000000kube-score-1.20.0/score/testdata/statefulset-different-labels.yamlapiVersion: apps/v1
kind: StatefulSet
metadata:
name: statefulset-test-1
spec:
selector:
matchLabels:
app: foo
serviceName: svc-test-1
template:
metadata:
labels:
app: bar
spec:
containers:
- name: foobar
image: foo/bar:123
07070100000130000081A4000000000000000000000001680FB1F800000121000000000000000000000000000000000000004E00000000kube-score-1.20.0/score/testdata/statefulset-host-antiaffinity-1-replica.yamlapiVersion: apps/v1
kind: StatefulSet
metadata:
name: statefulset-host-antiaffinity-1-replica
spec:
selector:
matchLabels:
app: foo
replicas: 1
template:
metadata:
labels:
app: foo
spec:
containers:
- name: foobar
image: foo:bar
07070100000131000081A4000000000000000000000001680FB1F80000011C000000000000000000000000000000000000004C00000000kube-score-1.20.0/score/testdata/statefulset-host-antiaffinity-not-set.yamlapiVersion: apps/v1
kind: StatefulSet
metadata:
name: statefulset-anti-affinity-not-set
spec:
selector:
matchLabels:
app: foo
replicas: 10
template:
metadata:
labels:
app: foo
spec:
containers:
- name: foobar
image: foo:bar
07070100000132000081A4000000000000000000000001680FB1F800000294000000000000000000000000000000000000004E00000000kube-score-1.20.0/score/testdata/statefulset-host-antiaffinity-preffered.yamlapiVersion: apps/v1
kind: StatefulSet
metadata:
name: statefulset-preffered-anti-affinity
spec:
selector:
matchLabels:
app: foo
replicas: 10
template:
metadata:
labels:
app: foo
spec:
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchExpressions:
- key: app
operator: In
values:
- foo
topologyKey: kubernetes.io/hostname
containers:
- name: foobar
image: foo:bar
07070100000133000081A4000000000000000000000001680FB1F800000217000000000000000000000000000000000000004D00000000kube-score-1.20.0/score/testdata/statefulset-host-antiaffinity-required.yamlapiVersion: apps/v1
kind: StatefulSet
metadata:
name: statefulset-required-anti-affinity
spec:
selector:
matchLabels:
app: foo
replicas: 10
template:
metadata:
labels:
app: foo
spec:
replicas: 10
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- topologyKey: "kubernetes.io/hostname"
labelSelector:
matchLabels:
app: foo
containers:
- name: foobar
image: foo:bar
07070100000134000081A4000000000000000000000001680FB1F80000011C000000000000000000000000000000000000005700000000kube-score-1.20.0/score/testdata/statefulset-host-antiaffinity-undefined-replicas.yamlapiVersion: apps/v1
kind: StatefulSet
metadata:
name: statefulset-host-antiaffinity-undefined-replicas
spec:
selector:
matchLabels:
app: foo
template:
metadata:
labels:
app: foo
spec:
containers:
- name: foobar
image: foo:bar
07070100000135000081A4000000000000000000000001680FB1F800000B36000000000000000000000000000000000000004100000000kube-score-1.20.0/score/testdata/statefulset-nested-ignores.yamlapiVersion: apps/v1
kind: StatefulSet
metadata:
name: trivy
namespace: trivy-staging
spec:
podManagementPolicy: Parallel
replicas: 3
revisionHistoryLimit: 10
selector:
matchLabels:
app.kubernetes.io/instance: trivy
app.kubernetes.io/name: trivy
serviceName: trivy
template:
metadata:
annotations:
kube-score/ignore: container-image-tag,pod-probes
labels:
app.kubernetes.io/instance: trivy
app.kubernetes.io/name: trivy
spec:
automountServiceAccountToken: false
containers:
- args:
- server
envFrom:
- configMapRef:
name: trivy
- secretRef:
name: trivy
image: aquasec/trivy:latest
imagePullPolicy: Always
livenessProbe:
failureThreshold: 10
httpGet:
path: /healthz
port: trivy-http
scheme: HTTP
initialDelaySeconds: 5
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 1
name: main
ports:
- containerPort: 4954
name: trivy-http
protocol: TCP
readinessProbe:
failureThreshold: 3
httpGet:
path: /healthz
port: trivy-http
scheme: HTTP
initialDelaySeconds: 5
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 1
resources:
limits:
cpu: "1"
ephemeral-storage: 128Mi
memory: 1Gi
requests:
cpu: 200m
memory: 512Mi
securityContext:
privileged: false
readOnlyRootFilesystem: true
runAsGroup: 65534
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
volumeMounts:
- mountPath: /tmp
name: tmp-data
- mountPath: /home/scanner/.cache
name: data
dnsPolicy: ClusterFirst
restartPolicy: Always
schedulerName: default-scheduler
securityContext:
fsGroup: 65534
runAsNonRoot: true
runAsUser: 65534
serviceAccount: trivy
serviceAccountName: trivy
terminationGracePeriodSeconds: 30
volumes:
- emptyDir: {}
name: tmp-data
updateStrategy:
rollingUpdate:
partition: 0
type: RollingUpdate
volumeClaimTemplates:
- apiVersion: v1
kind: PersistentVolumeClaim
metadata:
creationTimestamp: null
name: data
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 5Gi
volumeMode: Filesystem
status:
phase: Pending07070100000136000081A4000000000000000000000001680FB1F8000001D0000000000000000000000000000000000000006100000000kube-score-1.20.0/score/testdata/statefulset-poddisruptionbudget-v1beta1-expression-matches.yamlapiVersion: policy/v1beta1
kind: PodDisruptionBudget
metadata:
name: app-budget
spec:
minAvailable: 2
selector:
matchExpressions:
- key: app
operator: In
values: [foo, foo1, foo2]
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: statefulset-test-1
spec:
selector:
matchLabels:
app: foo
template:
metadata:
labels:
app: foo
spec:
containers:
- name: foobar
image: foo:bar
07070100000137000081A4000000000000000000000001680FB1F8000001D6000000000000000000000000000000000000006200000000kube-score-1.20.0/score/testdata/statefulset-poddisruptionbudget-v1beta1-expression-no-match.yamlapiVersion: policy/v1beta1
kind: PodDisruptionBudget
metadata:
name: app-budget
spec:
minAvailable: 2
selector:
matchExpressions:
- key: app
operator: In
values: ["not-foo", "not-foo2"]
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: statefulset-test-1
spec:
selector:
matchLabels:
app: foo
template:
metadata:
labels:
app: foo
spec:
containers:
- name: foobar
image: foo:bar
07070100000138000081A4000000000000000000000001680FB1F800000198000000000000000000000000000000000000005600000000kube-score-1.20.0/score/testdata/statefulset-poddisruptionbudget-v1beta1-matches.yamlapiVersion: policy/v1beta1
kind: PodDisruptionBudget
metadata:
name: app-budget
spec:
minAvailable: 2
selector:
matchLabels:
app: foo
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: statefulset-test-1
spec:
selector:
matchLabels:
app: foo
template:
metadata:
labels:
app: foo
spec:
containers:
- name: foobar
image: foo:bar
07070100000139000081A4000000000000000000000001680FB1F80000019C000000000000000000000000000000000000005700000000kube-score-1.20.0/score/testdata/statefulset-poddisruptionbudget-v1beta1-no-match.yamlapiVersion: policy/v1beta1
kind: PodDisruptionBudget
metadata:
name: app-budget
spec:
minAvailable: 2
selector:
matchLabels:
app: not-foo
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: statefulset-test-1
spec:
selector:
matchLabels:
app: foo
template:
metadata:
labels:
app: foo
spec:
containers:
- name: foobar
image: foo:bar
0707010000013A000081A4000000000000000000000001680FB1F8000001CA000000000000000000000000000000000000004F00000000kube-score-1.20.0/score/testdata/statefulset-service-name-different-label.yamlapiVersion: v1
kind: Service
metadata:
name: svc-test-1
spec:
clusterIP: "None"
selector:
app: foo
ports:
- protocol: TCP
port: 80
targetPort: 8080
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: statefulset-test-1
spec:
selector:
matchLabels:
app: foo
serviceName: svc-test-1
template:
metadata:
labels:
app: bar
spec:
containers:
- name: foobar
image: foo/bar:123
0707010000013B000081A4000000000000000000000001680FB1F8000001CA000000000000000000000000000000000000004E00000000kube-score-1.20.0/score/testdata/statefulset-service-name-different-name.yamlapiVersion: v1
kind: Service
metadata:
name: svc-test-2
spec:
clusterIP: "None"
selector:
app: foo
ports:
- protocol: TCP
port: 80
targetPort: 8080
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: statefulset-test-1
spec:
selector:
matchLabels:
app: foo
serviceName: svc-test-1
template:
metadata:
labels:
app: foo
spec:
containers:
- name: foobar
image: foo/bar:123
0707010000013C000081A4000000000000000000000001680FB1F8000001E2000000000000000000000000000000000000005300000000kube-score-1.20.0/score/testdata/statefulset-service-name-different-namespace.yaml
apiVersion: v1
kind: Service
metadata:
name: svc-test-1
spec:
clusterIP: "None"
selector:
app: foo
ports:
- protocol: TCP
port: 80
targetPort: 8080
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: statefulset-test-1
namespace: ns-test-1
spec:
selector:
matchLabels:
app: foo
serviceName: svc-test-1
template:
metadata:
labels:
app: foo
spec:
containers:
- name: foobar
image: foo/bar:123
0707010000013D000081A4000000000000000000000001680FB1F8000001B6000000000000000000000000000000000000004C00000000kube-score-1.20.0/score/testdata/statefulset-service-name-not-headless.yamlapiVersion: v1
kind: Service
metadata:
name: svc-test-1
spec:
selector:
app: foo
ports:
- protocol: TCP
port: 80
targetPort: 8080
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: statefulset-test-1
spec:
selector:
matchLabels:
app: foo
serviceName: svc-test-1
template:
metadata:
labels:
app: foo
spec:
containers:
- name: foobar
image: foo/bar:123
0707010000013E000081A4000000000000000000000001680FB1F8000001CA000000000000000000000000000000000000003F00000000kube-score-1.20.0/score/testdata/statefulset-service-name.yamlapiVersion: v1
kind: Service
metadata:
name: svc-test-1
spec:
clusterIP: "None"
selector:
app: foo
ports:
- protocol: TCP
port: 80
targetPort: 8080
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: statefulset-test-1
spec:
selector:
matchLabels:
app: foo
serviceName: svc-test-1
template:
metadata:
labels:
app: foo
spec:
containers:
- name: foobar
image: foo/bar:123
0707010000013F000081A4000000000000000000000001680FB1F800000128000000000000000000000000000000000000004100000000kube-score-1.20.0/score/testdata/statefulset-test-resources.yamlapiVersion: apps/v1
kind: StatefulSet
metadata:
name: statefulset-test-1
spec:
selector:
matchLabels:
app: foo
template:
spec:
containers:
- name: foobar
image: foo/bar:123
resources:
limits:
cpu: 200m
memory: 1Gi
07070100000140000041ED000000000000000000000002680FB1F800000000000000000000000000000000000000000000001C00000000kube-score-1.20.0/scorecard07070100000141000081A4000000000000000000000001680FB1F800000501000000000000000000000000000000000000002700000000kube-score-1.20.0/scorecard/enabled.gopackage scorecard
import (
"strings"
ks "github.com/zegl/kube-score/domain"
)
func (so *ScoredObject) isEnabled(check ks.Check, annotations, childAnnotations map[string]string) bool {
isIn := func(csv string, key string) bool {
for _, v := range strings.Split(csv, ",") {
v = strings.TrimSpace(v)
if v == key {
return true
}
if vals, ok := impliedIgnoreAnnotations[v]; ok {
for i := range vals {
if vals[i] == key {
return true
}
}
}
}
return false
}
if childAnnotations != nil && so.useIgnoreChecksAnnotation && isIn(childAnnotations[ignoredChecksAnnotation], check.ID) {
return false
}
if childAnnotations != nil && so.useOptionalChecksAnnotation && isIn(childAnnotations[optionalChecksAnnotation], check.ID) {
return true
}
if so.useIgnoreChecksAnnotation && isIn(annotations[ignoredChecksAnnotation], check.ID) {
return false
}
if so.useOptionalChecksAnnotation && isIn(annotations[optionalChecksAnnotation], check.ID) {
return true
}
// Enabled optional test from command line arguments
if _, ok := so.enabledOptionalTests[check.ID]; ok {
return true
}
// Optional checks are disabled unless explicitly allowed above
if check.Optional {
return false
}
// Enabled by default
return true
}
07070100000142000081A4000000000000000000000001680FB1F800000F74000000000000000000000000000000000000002900000000kube-score-1.20.0/scorecard/scorecard.gopackage scorecard
import (
"fmt"
"github.com/zegl/kube-score/config"
ks "github.com/zegl/kube-score/domain"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
const (
ignoredChecksAnnotation = "kube-score/ignore"
optionalChecksAnnotation = "kube-score/enable"
)
// if this, then that
var impliedIgnoreAnnotations = map[string][]string{
"container-resources": {"container-ephemeral-storage-request-and-limit"},
}
type Scorecard map[string]*ScoredObject
// New creates and initializes a new Scorecard
func New() Scorecard {
return make(Scorecard)
}
func (s Scorecard) NewObject(typeMeta metav1.TypeMeta, objectMeta metav1.ObjectMeta, cnf *config.RunConfiguration) *ScoredObject {
if cnf == nil {
cnf = &config.RunConfiguration{}
}
o := &ScoredObject{
TypeMeta: typeMeta,
ObjectMeta: objectMeta,
Checks: make([]TestScore, 0),
useIgnoreChecksAnnotation: cnf.UseIgnoreChecksAnnotation,
useOptionalChecksAnnotation: cnf.UseOptionalChecksAnnotation,
enabledOptionalTests: cnf.EnabledOptionalTests,
}
// If this object already exists, return the previous version
if object, ok := s[o.resourceRefKey()]; ok {
return object
}
s[o.resourceRefKey()] = o
return o
}
func (s Scorecard) AnyBelowOrEqualToGrade(threshold Grade) bool {
for _, o := range s {
if o.AnyBelowOrEqualToGrade(threshold) {
return true
}
}
return false
}
type ScoredObject struct {
TypeMeta metav1.TypeMeta
ObjectMeta metav1.ObjectMeta
FileLocation ks.FileLocation
Checks []TestScore
useIgnoreChecksAnnotation bool
useOptionalChecksAnnotation bool
enabledOptionalTests map[string]struct{}
}
func (so *ScoredObject) AnyBelowOrEqualToGrade(threshold Grade) bool {
for _, o := range so.Checks {
if !o.Skipped && o.Grade <= threshold {
return true
}
}
return false
}
func (so *ScoredObject) resourceRefKey() string {
return so.TypeMeta.Kind + "/" + so.TypeMeta.APIVersion + "/" + so.ObjectMeta.Namespace + "/" + so.ObjectMeta.Name
}
func (so *ScoredObject) HumanFriendlyRef() string {
s := so.ObjectMeta.Name
if so.ObjectMeta.Namespace != "" {
s += "/" + so.ObjectMeta.Namespace
}
s += " " + so.TypeMeta.APIVersion + "/" + so.TypeMeta.Kind
return s
}
func (so *ScoredObject) Add(ts TestScore, check ks.Check, locationer ks.FileLocationer, annotations ...map[string]string) {
ts.Check = check
so.FileLocation = locationer.FileLocation()
var skip bool
if annotations != nil {
if len(annotations) == 1 && !so.isEnabled(check, annotations[0], nil) {
skip = true
}
if len(annotations) == 2 && !so.isEnabled(check, annotations[0], annotations[1]) {
skip = true
}
}
// This test is ignored (via annotations), don't save the score
if skip {
ts.Skipped = true
ts.Comments = []TestScoreComment{{Summary: fmt.Sprintf("Skipped because %s is ignored", check.ID)}}
}
so.Checks = append(so.Checks, ts)
}
type TestScore struct {
Check ks.Check
Grade Grade
Skipped bool
Comments []TestScoreComment
}
type Grade int
const (
GradeCritical Grade = 1
GradeWarning Grade = 5
GradeAlmostOK Grade = 7
GradeAllOK Grade = 10
)
func (g Grade) String() string {
switch g {
case GradeCritical:
return "CRITICAL"
case GradeWarning:
return "WARNING"
case GradeAlmostOK, GradeAllOK:
return "OK"
default:
panic("Unknown grade")
}
}
type TestScoreComment struct {
Path string
Summary string
Description string
DocumentationURL string
}
func (ts *TestScore) AddComment(path, summary, description string) {
ts.Comments = append(ts.Comments, TestScoreComment{
Path: path,
Summary: summary,
Description: description,
})
}
func (ts *TestScore) AddCommentWithURL(path, summary, description, documentationURL string) {
ts.Comments = append(ts.Comments, TestScoreComment{
Path: path,
Summary: summary,
Description: description,
DocumentationURL: documentationURL,
})
}
07070100000143000081A4000000000000000000000001680FB1F80000007E000000000000000000000000000000000000001B00000000kube-score-1.20.0/tea.yaml# https://tea.xyz/what-is-this-file
---
version: 1.0.0
codeOwners:
- '0xf731ba6c4D26eBcD80793f41d7aedd64fe94E724'
quorum: 1
07070100000144000081A4000000000000000000000001680FB1F80000019A000000000000000000000000000000000000001E00000000kube-score-1.20.0/vercel.json{
"buildCommand": "GOOS=js GOARCH=wasm /vercel/go/bin/go1.22.5 build -o ./web/public/main.wasm ./cmd/wasm/main.go && cd web && ENABLE_EXPERIMENTAL_COREPACK=1 pnpm build",
"installCommand": "cd web && ENABLE_EXPERIMENTAL_COREPACK=1 pnpm install --frozen-lockfile && dnf install -y go && go install golang.org/dl/go1.22.5@latest && /vercel/go/bin/go1.22.5 download",
"outputDirectory": "./web/dist"
}07070100000145000041ED000000000000000000000002680FB1F800000000000000000000000000000000000000000000001600000000kube-score-1.20.0/web07070100000146000081A4000000000000000000000001680FB1F8000001B4000000000000000000000000000000000000002400000000kube-score-1.20.0/web/.eslintrc.cjsmodule.exports = {
root: true,
env: { browser: true, es2020: true },
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:react-hooks/recommended',
],
ignorePatterns: ['dist', '.eslintrc.cjs'],
parser: '@typescript-eslint/parser',
plugins: ['react-refresh'],
rules: {
'react-refresh/only-export-components': [
'warn',
{ allowConstantExport: true },
],
},
}
07070100000147000081A4000000000000000000000001680FB1F80000010B000000000000000000000000000000000000002100000000kube-score-1.20.0/web/.gitignore*.wasm
.vite
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
07070100000148000081A4000000000000000000000001680FB1F800000207000000000000000000000000000000000000002000000000kube-score-1.20.0/web/README.md# The kube-score website
This directory contains the source for kube-score.com.
kube-score is compiled to WASM (WebAssembly) for usage in browsers, and kube-score.com is running purely in the browser.
## Building
```bash
# Compile the wasm module (run from repo root)
GOOS=js GOARCH=wasm go build -o ./web/main.wasm ./cmd/wasm/main.go
```
```bash
# Build and run web app (from this directory)
pnpm install
pnpm dev
```
## Hosting
The site is built and hosted on Vercel. See vercel.json for build configuration.
07070100000149000081A4000000000000000000000001680FB1F8000003B8000000000000000000000000000000000000002100000000kube-score-1.20.0/web/index.html<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/assets/logo.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>kube-score - Kubernetes object analysis with recommendations for improved reliability and security</title>
<meta name="description"
content="kube-score is a tool that does static code analysis of your Kubernetes object definitions. The output is a list of recommendations of what you can improve to make your application more secure and resilient.">
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
<script src="wasm_exec.js"></script>
<script>
const go = new Go();
WebAssembly.instantiateStreaming(fetch("main.wasm"), go.importObject).then((result) => {
go.run(result.instance);
});
</script>
</body>
</html>
0707010000014A000081A4000000000000000000000001680FB1F80000030C000000000000000000000000000000000000002300000000kube-score-1.20.0/web/package.json{
"name": "web",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"preview": "vite preview"
},
"dependencies": {
"react": "^18.3.1",
"react-dom": "^18.3.1",
"styled-components": "^6.1.11"
},
"devDependencies": {
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"@typescript-eslint/eslint-plugin": "^8.8.1",
"@typescript-eslint/parser": "^8.8.1",
"@vitejs/plugin-react-swc": "^3.7.1",
"eslint": "^9.12.0",
"eslint-plugin-react-hooks": "^5.0.0",
"eslint-plugin-react-refresh": "^0.4.12",
"typescript": "^5.6.3",
"vite": "^5.4.8"
}
}
0707010000014B000081A4000000000000000000000001680FB1F80000EA7E000000000000000000000000000000000000002500000000kube-score-1.20.0/web/pnpm-lock.yamllockfileVersion: '9.0'
settings:
autoInstallPeers: true
excludeLinksFromLockfile: false
importers:
.:
dependencies:
react:
specifier: ^18.3.1
version: 18.3.1
react-dom:
specifier: ^18.3.1
version: 18.3.1(react@18.3.1)
styled-components:
specifier: ^6.1.11
version: 6.1.11(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
devDependencies:
'@types/react':
specifier: ^18.3.3
version: 18.3.3
'@types/react-dom':
specifier: ^18.3.0
version: 18.3.0
'@typescript-eslint/eslint-plugin':
specifier: ^7.13.1
version: 7.16.0(@typescript-eslint/parser@7.16.0(eslint@8.57.0)(typescript@5.5.3))(eslint@8.57.0)(typescript@5.5.3)
'@typescript-eslint/parser':
specifier: ^7.13.1
version: 7.16.0(eslint@8.57.0)(typescript@5.5.3)
'@vitejs/plugin-react-swc':
specifier: ^3.5.0
version: 3.7.0(vite@5.3.3)
eslint:
specifier: ^8.57.0
version: 8.57.0
eslint-plugin-react-hooks:
specifier: ^4.6.2
version: 4.6.2(eslint@8.57.0)
eslint-plugin-react-refresh:
specifier: ^0.4.7
version: 0.4.8(eslint@8.57.0)
typescript:
specifier: ^5.2.2
version: 5.5.3
vite:
specifier: ^5.3.1
version: 5.3.3
packages:
'@emotion/is-prop-valid@1.2.2':
resolution: {integrity: sha512-uNsoYd37AFmaCdXlg6EYD1KaPOaRWRByMCYzbKUX4+hhMfrxdVSelShywL4JVaAeM/eHUOSprYBQls+/neX3pw==}
'@emotion/memoize@0.8.1':
resolution: {integrity: sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==}
'@emotion/unitless@0.8.1':
resolution: {integrity: sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==}
'@esbuild/aix-ppc64@0.21.5':
resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==}
engines: {node: '>=12'}
cpu: [ppc64]
os: [aix]
'@esbuild/android-arm64@0.21.5':
resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==}
engines: {node: '>=12'}
cpu: [arm64]
os: [android]
'@esbuild/android-arm@0.21.5':
resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==}
engines: {node: '>=12'}
cpu: [arm]
os: [android]
'@esbuild/android-x64@0.21.5':
resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==}
engines: {node: '>=12'}
cpu: [x64]
os: [android]
'@esbuild/darwin-arm64@0.21.5':
resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==}
engines: {node: '>=12'}
cpu: [arm64]
os: [darwin]
'@esbuild/darwin-x64@0.21.5':
resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==}
engines: {node: '>=12'}
cpu: [x64]
os: [darwin]
'@esbuild/freebsd-arm64@0.21.5':
resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==}
engines: {node: '>=12'}
cpu: [arm64]
os: [freebsd]
'@esbuild/freebsd-x64@0.21.5':
resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==}
engines: {node: '>=12'}
cpu: [x64]
os: [freebsd]
'@esbuild/linux-arm64@0.21.5':
resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==}
engines: {node: '>=12'}
cpu: [arm64]
os: [linux]
'@esbuild/linux-arm@0.21.5':
resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==}
engines: {node: '>=12'}
cpu: [arm]
os: [linux]
'@esbuild/linux-ia32@0.21.5':
resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==}
engines: {node: '>=12'}
cpu: [ia32]
os: [linux]
'@esbuild/linux-loong64@0.21.5':
resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==}
engines: {node: '>=12'}
cpu: [loong64]
os: [linux]
'@esbuild/linux-mips64el@0.21.5':
resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==}
engines: {node: '>=12'}
cpu: [mips64el]
os: [linux]
'@esbuild/linux-ppc64@0.21.5':
resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==}
engines: {node: '>=12'}
cpu: [ppc64]
os: [linux]
'@esbuild/linux-riscv64@0.21.5':
resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==}
engines: {node: '>=12'}
cpu: [riscv64]
os: [linux]
'@esbuild/linux-s390x@0.21.5':
resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==}
engines: {node: '>=12'}
cpu: [s390x]
os: [linux]
'@esbuild/linux-x64@0.21.5':
resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==}
engines: {node: '>=12'}
cpu: [x64]
os: [linux]
'@esbuild/netbsd-x64@0.21.5':
resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==}
engines: {node: '>=12'}
cpu: [x64]
os: [netbsd]
'@esbuild/openbsd-x64@0.21.5':
resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==}
engines: {node: '>=12'}
cpu: [x64]
os: [openbsd]
'@esbuild/sunos-x64@0.21.5':
resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==}
engines: {node: '>=12'}
cpu: [x64]
os: [sunos]
'@esbuild/win32-arm64@0.21.5':
resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==}
engines: {node: '>=12'}
cpu: [arm64]
os: [win32]
'@esbuild/win32-ia32@0.21.5':
resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==}
engines: {node: '>=12'}
cpu: [ia32]
os: [win32]
'@esbuild/win32-x64@0.21.5':
resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==}
engines: {node: '>=12'}
cpu: [x64]
os: [win32]
'@eslint-community/eslint-utils@4.4.0':
resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
peerDependencies:
eslint: ^6.0.0 || ^7.0.0 || >=8.0.0
'@eslint-community/regexpp@4.11.0':
resolution: {integrity: sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A==}
engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0}
'@eslint/eslintrc@2.1.4':
resolution: {integrity: sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
'@eslint/js@8.57.0':
resolution: {integrity: sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
'@humanwhocodes/config-array@0.11.14':
resolution: {integrity: sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==}
engines: {node: '>=10.10.0'}
deprecated: Use @eslint/config-array instead
'@humanwhocodes/module-importer@1.0.1':
resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==}
engines: {node: '>=12.22'}
'@humanwhocodes/object-schema@2.0.3':
resolution: {integrity: sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==}
deprecated: Use @eslint/object-schema instead
'@nodelib/fs.scandir@2.1.5':
resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
engines: {node: '>= 8'}
'@nodelib/fs.stat@2.0.5':
resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==}
engines: {node: '>= 8'}
'@nodelib/fs.walk@1.2.8':
resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==}
engines: {node: '>= 8'}
'@rollup/rollup-android-arm-eabi@4.18.1':
resolution: {integrity: sha512-lncuC4aHicncmbORnx+dUaAgzee9cm/PbIqgWz1PpXuwc+sa1Ct83tnqUDy/GFKleLiN7ZIeytM6KJ4cAn1SxA==}
cpu: [arm]
os: [android]
'@rollup/rollup-android-arm64@4.18.1':
resolution: {integrity: sha512-F/tkdw0WSs4ojqz5Ovrw5r9odqzFjb5LIgHdHZG65dFI1lWTWRVy32KDJLKRISHgJvqUeUhdIvy43fX41znyDg==}
cpu: [arm64]
os: [android]
'@rollup/rollup-darwin-arm64@4.18.1':
resolution: {integrity: sha512-vk+ma8iC1ebje/ahpxpnrfVQJibTMyHdWpOGZ3JpQ7Mgn/3QNHmPq7YwjZbIE7km73dH5M1e6MRRsnEBW7v5CQ==}
cpu: [arm64]
os: [darwin]
'@rollup/rollup-darwin-x64@4.18.1':
resolution: {integrity: sha512-IgpzXKauRe1Tafcej9STjSSuG0Ghu/xGYH+qG6JwsAUxXrnkvNHcq/NL6nz1+jzvWAnQkuAJ4uIwGB48K9OCGA==}
cpu: [x64]
os: [darwin]
'@rollup/rollup-linux-arm-gnueabihf@4.18.1':
resolution: {integrity: sha512-P9bSiAUnSSM7EmyRK+e5wgpqai86QOSv8BwvkGjLwYuOpaeomiZWifEos517CwbG+aZl1T4clSE1YqqH2JRs+g==}
cpu: [arm]
os: [linux]
'@rollup/rollup-linux-arm-musleabihf@4.18.1':
resolution: {integrity: sha512-5RnjpACoxtS+aWOI1dURKno11d7krfpGDEn19jI8BuWmSBbUC4ytIADfROM1FZrFhQPSoP+KEa3NlEScznBTyQ==}
cpu: [arm]
os: [linux]
'@rollup/rollup-linux-arm64-gnu@4.18.1':
resolution: {integrity: sha512-8mwmGD668m8WaGbthrEYZ9CBmPug2QPGWxhJxh/vCgBjro5o96gL04WLlg5BA233OCWLqERy4YUzX3bJGXaJgQ==}
cpu: [arm64]
os: [linux]
'@rollup/rollup-linux-arm64-musl@4.18.1':
resolution: {integrity: sha512-dJX9u4r4bqInMGOAQoGYdwDP8lQiisWb9et+T84l2WXk41yEej8v2iGKodmdKimT8cTAYt0jFb+UEBxnPkbXEQ==}
cpu: [arm64]
os: [linux]
'@rollup/rollup-linux-powerpc64le-gnu@4.18.1':
resolution: {integrity: sha512-V72cXdTl4EI0x6FNmho4D502sy7ed+LuVW6Ym8aI6DRQ9hQZdp5sj0a2usYOlqvFBNKQnLQGwmYnujo2HvjCxQ==}
cpu: [ppc64]
os: [linux]
'@rollup/rollup-linux-riscv64-gnu@4.18.1':
resolution: {integrity: sha512-f+pJih7sxoKmbjghrM2RkWo2WHUW8UbfxIQiWo5yeCaCM0TveMEuAzKJte4QskBp1TIinpnRcxkquY+4WuY/tg==}
cpu: [riscv64]
os: [linux]
'@rollup/rollup-linux-s390x-gnu@4.18.1':
resolution: {integrity: sha512-qb1hMMT3Fr/Qz1OKovCuUM11MUNLUuHeBC2DPPAWUYYUAOFWaxInaTwTQmc7Fl5La7DShTEpmYwgdt2hG+4TEg==}
cpu: [s390x]
os: [linux]
'@rollup/rollup-linux-x64-gnu@4.18.1':
resolution: {integrity: sha512-7O5u/p6oKUFYjRbZkL2FLbwsyoJAjyeXHCU3O4ndvzg2OFO2GinFPSJFGbiwFDaCFc+k7gs9CF243PwdPQFh5g==}
cpu: [x64]
os: [linux]
'@rollup/rollup-linux-x64-musl@4.18.1':
resolution: {integrity: sha512-pDLkYITdYrH/9Cv/Vlj8HppDuLMDUBmgsM0+N+xLtFd18aXgM9Nyqupb/Uw+HeidhfYg2lD6CXvz6CjoVOaKjQ==}
cpu: [x64]
os: [linux]
'@rollup/rollup-win32-arm64-msvc@4.18.1':
resolution: {integrity: sha512-W2ZNI323O/8pJdBGil1oCauuCzmVd9lDmWBBqxYZcOqWD6aWqJtVBQ1dFrF4dYpZPks6F+xCZHfzG5hYlSHZ6g==}
cpu: [arm64]
os: [win32]
'@rollup/rollup-win32-ia32-msvc@4.18.1':
resolution: {integrity: sha512-ELfEX1/+eGZYMaCIbK4jqLxO1gyTSOIlZr6pbC4SRYFaSIDVKOnZNMdoZ+ON0mrFDp4+H5MhwNC1H/AhE3zQLg==}
cpu: [ia32]
os: [win32]
'@rollup/rollup-win32-x64-msvc@4.18.1':
resolution: {integrity: sha512-yjk2MAkQmoaPYCSu35RLJ62+dz358nE83VfTePJRp8CG7aMg25mEJYpXFiD+NcevhX8LxD5OP5tktPXnXN7GDw==}
cpu: [x64]
os: [win32]
'@swc/core-darwin-arm64@1.6.13':
resolution: {integrity: sha512-SOF4buAis72K22BGJ3N8y88mLNfxLNprTuJUpzikyMGrvkuBFNcxYtMhmomO0XHsgLDzOJ+hWzcgjRNzjMsUcQ==}
engines: {node: '>=10'}
cpu: [arm64]
os: [darwin]
'@swc/core-darwin-x64@1.6.13':
resolution: {integrity: sha512-AW8akFSC+tmPE6YQQvK9S2A1B8pjnXEINg+gGgw0KRUUXunvu1/OEOeC5L2Co1wAwhD7bhnaefi06Qi9AiwOag==}
engines: {node: '>=10'}
cpu: [x64]
os: [darwin]
'@swc/core-linux-arm-gnueabihf@1.6.13':
resolution: {integrity: sha512-f4gxxvDXVUm2HLYXRd311mSrmbpQF2MZ4Ja6XCQz1hWAxXdhRl1gpnZ+LH/xIfGSwQChrtLLVrkxdYUCVuIjFg==}
engines: {node: '>=10'}
cpu: [arm]
os: [linux]
'@swc/core-linux-arm64-gnu@1.6.13':
resolution: {integrity: sha512-Nf/eoW2CbG8s+9JoLtjl9FByBXyQ5cjdBsA4efO7Zw4p+YSuXDgc8HRPC+E2+ns0praDpKNZtLvDtmF2lL+2Gg==}
engines: {node: '>=10'}
cpu: [arm64]
os: [linux]
'@swc/core-linux-arm64-musl@1.6.13':
resolution: {integrity: sha512-2OysYSYtdw79prJYuKIiux/Gj0iaGEbpS2QZWCIY4X9sGoETJ5iMg+lY+YCrIxdkkNYd7OhIbXdYFyGs/w5LDg==}
engines: {node: '>=10'}
cpu: [arm64]
os: [linux]
'@swc/core-linux-x64-gnu@1.6.13':
resolution: {integrity: sha512-PkR4CZYJNk5hcd2+tMWBpnisnmYsUzazI1O5X7VkIGFcGePTqJ/bWlfUIVVExWxvAI33PQFzLbzmN5scyIUyGQ==}
engines: {node: '>=10'}
cpu: [x64]
os: [linux]
'@swc/core-linux-x64-musl@1.6.13':
resolution: {integrity: sha512-OdsY7wryTxCKwGQcwW9jwWg3cxaHBkTTHi91+5nm7hFPpmZMz1HivJrWAMwVE7iXFw+M4l6ugB/wCvpYrUAAjA==}
engines: {node: '>=10'}
cpu: [x64]
os: [linux]
'@swc/core-win32-arm64-msvc@1.6.13':
resolution: {integrity: sha512-ap6uNmYjwk9M/+bFEuWRNl3hq4VqgQ/Lk+ID/F5WGqczNr0L7vEf+pOsRAn0F6EV+o/nyb3ePt8rLhE/wjHpPg==}
engines: {node: '>=10'}
cpu: [arm64]
os: [win32]
'@swc/core-win32-ia32-msvc@1.6.13':
resolution: {integrity: sha512-IJ8KH4yIUHTnS/U1jwQmtbfQals7zWPG0a9hbEfIr4zI0yKzjd83lmtS09lm2Q24QBWOCFGEEbuZxR4tIlvfzA==}
engines: {node: '>=10'}
cpu: [ia32]
os: [win32]
'@swc/core-win32-x64-msvc@1.6.13':
resolution: {integrity: sha512-f6/sx6LMuEnbuxtiSL/EkR0Y6qUHFw1XVrh6rwzKXptTipUdOY+nXpKoh+1UsBm/r7H0/5DtOdrn3q5ZHbFZjQ==}
engines: {node: '>=10'}
cpu: [x64]
os: [win32]
'@swc/core@1.6.13':
resolution: {integrity: sha512-eailUYex6fkfaQTev4Oa3mwn0/e3mQU4H8y1WPuImYQESOQDtVrowwUGDSc19evpBbHpKtwM+hw8nLlhIsF+Tw==}
engines: {node: '>=10'}
peerDependencies:
'@swc/helpers': '*'
peerDependenciesMeta:
'@swc/helpers':
optional: true
'@swc/counter@0.1.3':
resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==}
'@swc/types@0.1.9':
resolution: {integrity: sha512-qKnCno++jzcJ4lM4NTfYifm1EFSCeIfKiAHAfkENZAV5Kl9PjJIyd2yeeVv6c/2CckuLyv2NmRC5pv6pm2WQBg==}
'@types/estree@1.0.5':
resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==}
'@types/prop-types@15.7.12':
resolution: {integrity: sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==}
'@types/react-dom@18.3.0':
resolution: {integrity: sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg==}
'@types/react@18.3.3':
resolution: {integrity: sha512-hti/R0pS0q1/xx+TsI73XIqk26eBsISZ2R0wUijXIngRK9R/e7Xw/cXVxQK7R5JjW+SV4zGcn5hXjudkN/pLIw==}
'@types/stylis@4.2.5':
resolution: {integrity: sha512-1Xve+NMN7FWjY14vLoY5tL3BVEQ/n42YLwaqJIPYhotZ9uBHt87VceMwWQpzmdEt2TNXIorIFG+YeCUUW7RInw==}
'@typescript-eslint/eslint-plugin@7.16.0':
resolution: {integrity: sha512-py1miT6iQpJcs1BiJjm54AMzeuMPBSPuKPlnT8HlfudbcS5rYeX5jajpLf3mrdRh9dA/Ec2FVUY0ifeVNDIhZw==}
engines: {node: ^18.18.0 || >=20.0.0}
peerDependencies:
'@typescript-eslint/parser': ^7.0.0
eslint: ^8.56.0
typescript: '*'
peerDependenciesMeta:
typescript:
optional: true
'@typescript-eslint/parser@7.16.0':
resolution: {integrity: sha512-ar9E+k7CU8rWi2e5ErzQiC93KKEFAXA2Kky0scAlPcxYblLt8+XZuHUZwlyfXILyQa95P6lQg+eZgh/dDs3+Vw==}
engines: {node: ^18.18.0 || >=20.0.0}
peerDependencies:
eslint: ^8.56.0
typescript: '*'
peerDependenciesMeta:
typescript:
optional: true
'@typescript-eslint/scope-manager@7.16.0':
resolution: {integrity: sha512-8gVv3kW6n01Q6TrI1cmTZ9YMFi3ucDT7i7aI5lEikk2ebk1AEjrwX8MDTdaX5D7fPXMBLvnsaa0IFTAu+jcfOw==}
engines: {node: ^18.18.0 || >=20.0.0}
'@typescript-eslint/type-utils@7.16.0':
resolution: {integrity: sha512-j0fuUswUjDHfqV/UdW6mLtOQQseORqfdmoBNDFOqs9rvNVR2e+cmu6zJu/Ku4SDuqiJko6YnhwcL8x45r8Oqxg==}
engines: {node: ^18.18.0 || >=20.0.0}
peerDependencies:
eslint: ^8.56.0
typescript: '*'
peerDependenciesMeta:
typescript:
optional: true
'@typescript-eslint/types@7.16.0':
resolution: {integrity: sha512-fecuH15Y+TzlUutvUl9Cc2XJxqdLr7+93SQIbcZfd4XRGGKoxyljK27b+kxKamjRkU7FYC6RrbSCg0ALcZn/xw==}
engines: {node: ^18.18.0 || >=20.0.0}
'@typescript-eslint/typescript-estree@7.16.0':
resolution: {integrity: sha512-a5NTvk51ZndFuOLCh5OaJBELYc2O3Zqxfl3Js78VFE1zE46J2AaVuW+rEbVkQznjkmlzWsUI15BG5tQMixzZLw==}
engines: {node: ^18.18.0 || >=20.0.0}
peerDependencies:
typescript: '*'
peerDependenciesMeta:
typescript:
optional: true
'@typescript-eslint/utils@7.16.0':
resolution: {integrity: sha512-PqP4kP3hb4r7Jav+NiRCntlVzhxBNWq6ZQ+zQwII1y/G/1gdIPeYDCKr2+dH6049yJQsWZiHU6RlwvIFBXXGNA==}
engines: {node: ^18.18.0 || >=20.0.0}
peerDependencies:
eslint: ^8.56.0
'@typescript-eslint/visitor-keys@7.16.0':
resolution: {integrity: sha512-rMo01uPy9C7XxG7AFsxa8zLnWXTF8N3PYclekWSrurvhwiw1eW88mrKiAYe6s53AUY57nTRz8dJsuuXdkAhzCg==}
engines: {node: ^18.18.0 || >=20.0.0}
'@ungap/structured-clone@1.2.0':
resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==}
'@vitejs/plugin-react-swc@3.7.0':
resolution: {integrity: sha512-yrknSb3Dci6svCd/qhHqhFPDSw0QtjumcqdKMoNNzmOl5lMXTTiqzjWtG4Qask2HdvvzaNgSunbQGet8/GrKdA==}
peerDependencies:
vite: ^4 || ^5
acorn-jsx@5.3.2:
resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
peerDependencies:
acorn: ^6.0.0 || ^7.0.0 || ^8.0.0
acorn@8.12.1:
resolution: {integrity: sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==}
engines: {node: '>=0.4.0'}
hasBin: true
ajv@6.12.6:
resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==}
ansi-regex@5.0.1:
resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
engines: {node: '>=8'}
ansi-styles@4.3.0:
resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
engines: {node: '>=8'}
argparse@2.0.1:
resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
array-union@2.1.0:
resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==}
engines: {node: '>=8'}
balanced-match@1.0.2:
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
brace-expansion@1.1.11:
resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==}
brace-expansion@2.0.1:
resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==}
braces@3.0.3:
resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==}
engines: {node: '>=8'}
callsites@3.1.0:
resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
engines: {node: '>=6'}
camelize@1.0.1:
resolution: {integrity: sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==}
chalk@4.1.2:
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
engines: {node: '>=10'}
color-convert@2.0.1:
resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
engines: {node: '>=7.0.0'}
color-name@1.1.4:
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
concat-map@0.0.1:
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
cross-spawn@7.0.3:
resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==}
engines: {node: '>= 8'}
css-color-keywords@1.0.0:
resolution: {integrity: sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==}
engines: {node: '>=4'}
css-to-react-native@3.2.0:
resolution: {integrity: sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==}
csstype@3.1.3:
resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==}
debug@4.3.5:
resolution: {integrity: sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==}
engines: {node: '>=6.0'}
peerDependencies:
supports-color: '*'
peerDependenciesMeta:
supports-color:
optional: true
deep-is@0.1.4:
resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
dir-glob@3.0.1:
resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==}
engines: {node: '>=8'}
doctrine@3.0.0:
resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==}
engines: {node: '>=6.0.0'}
esbuild@0.21.5:
resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==}
engines: {node: '>=12'}
hasBin: true
escape-string-regexp@4.0.0:
resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==}
engines: {node: '>=10'}
eslint-plugin-react-hooks@4.6.2:
resolution: {integrity: sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ==}
engines: {node: '>=10'}
peerDependencies:
eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0
eslint-plugin-react-refresh@0.4.8:
resolution: {integrity: sha512-MIKAclwaDFIiYtVBLzDdm16E+Ty4GwhB6wZlCAG1R3Ur+F9Qbo6PRxpA5DK7XtDgm+WlCoAY2WxAwqhmIDHg6Q==}
peerDependencies:
eslint: '>=7'
eslint-scope@7.2.2:
resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
eslint-visitor-keys@3.4.3:
resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
eslint@8.57.0:
resolution: {integrity: sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
hasBin: true
espree@9.6.1:
resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
esquery@1.6.0:
resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==}
engines: {node: '>=0.10'}
esrecurse@4.3.0:
resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==}
engines: {node: '>=4.0'}
estraverse@5.3.0:
resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==}
engines: {node: '>=4.0'}
esutils@2.0.3:
resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==}
engines: {node: '>=0.10.0'}
fast-deep-equal@3.1.3:
resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
fast-glob@3.3.2:
resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==}
engines: {node: '>=8.6.0'}
fast-json-stable-stringify@2.1.0:
resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==}
fast-levenshtein@2.0.6:
resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==}
fastq@1.17.1:
resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==}
file-entry-cache@6.0.1:
resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==}
engines: {node: ^10.12.0 || >=12.0.0}
fill-range@7.1.1:
resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
engines: {node: '>=8'}
find-up@5.0.0:
resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==}
engines: {node: '>=10'}
flat-cache@3.2.0:
resolution: {integrity: sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==}
engines: {node: ^10.12.0 || >=12.0.0}
flatted@3.3.1:
resolution: {integrity: sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==}
fs.realpath@1.0.0:
resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
fsevents@2.3.3:
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
os: [darwin]
glob-parent@5.1.2:
resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
engines: {node: '>= 6'}
glob-parent@6.0.2:
resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==}
engines: {node: '>=10.13.0'}
glob@7.2.3:
resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==}
deprecated: Glob versions prior to v9 are no longer supported
globals@13.24.0:
resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==}
engines: {node: '>=8'}
globby@11.1.0:
resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==}
engines: {node: '>=10'}
graphemer@1.4.0:
resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==}
has-flag@4.0.0:
resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
engines: {node: '>=8'}
ignore@5.3.1:
resolution: {integrity: sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==}
engines: {node: '>= 4'}
import-fresh@3.3.0:
resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==}
engines: {node: '>=6'}
imurmurhash@0.1.4:
resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==}
engines: {node: '>=0.8.19'}
inflight@1.0.6:
resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==}
deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.
inherits@2.0.4:
resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
is-extglob@2.1.1:
resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
engines: {node: '>=0.10.0'}
is-glob@4.0.3:
resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
engines: {node: '>=0.10.0'}
is-number@7.0.0:
resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
engines: {node: '>=0.12.0'}
is-path-inside@3.0.3:
resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==}
engines: {node: '>=8'}
isexe@2.0.0:
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
js-tokens@4.0.0:
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
js-yaml@4.1.0:
resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==}
hasBin: true
json-buffer@3.0.1:
resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==}
json-schema-traverse@0.4.1:
resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==}
json-stable-stringify-without-jsonify@1.0.1:
resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==}
keyv@4.5.4:
resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==}
levn@0.4.1:
resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
engines: {node: '>= 0.8.0'}
locate-path@6.0.0:
resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}
engines: {node: '>=10'}
lodash.merge@4.6.2:
resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
loose-envify@1.4.0:
resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
hasBin: true
merge2@1.4.1:
resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
engines: {node: '>= 8'}
micromatch@4.0.7:
resolution: {integrity: sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==}
engines: {node: '>=8.6'}
minimatch@3.1.2:
resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
minimatch@9.0.5:
resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==}
engines: {node: '>=16 || 14 >=14.17'}
ms@2.1.2:
resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==}
nanoid@3.3.7:
resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==}
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
hasBin: true
natural-compare@1.4.0:
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
once@1.4.0:
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
optionator@0.9.4:
resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==}
engines: {node: '>= 0.8.0'}
p-limit@3.1.0:
resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==}
engines: {node: '>=10'}
p-locate@5.0.0:
resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==}
engines: {node: '>=10'}
parent-module@1.0.1:
resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
engines: {node: '>=6'}
path-exists@4.0.0:
resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
engines: {node: '>=8'}
path-is-absolute@1.0.1:
resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==}
engines: {node: '>=0.10.0'}
path-key@3.1.1:
resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
engines: {node: '>=8'}
path-type@4.0.0:
resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==}
engines: {node: '>=8'}
picocolors@1.0.1:
resolution: {integrity: sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==}
picomatch@2.3.1:
resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
engines: {node: '>=8.6'}
postcss-value-parser@4.2.0:
resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==}
postcss@8.4.38:
resolution: {integrity: sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==}
engines: {node: ^10 || ^12 || >=14}
postcss@8.4.39:
resolution: {integrity: sha512-0vzE+lAiG7hZl1/9I8yzKLx3aR9Xbof3fBHKunvMfOCYAtMhrsnccJY2iTURb9EZd5+pLuiNV9/c/GZJOHsgIw==}
engines: {node: ^10 || ^12 || >=14}
prelude-ls@1.2.1:
resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
engines: {node: '>= 0.8.0'}
punycode@2.3.1:
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
engines: {node: '>=6'}
queue-microtask@1.2.3:
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
react-dom@18.3.1:
resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==}
peerDependencies:
react: ^18.3.1
react@18.3.1:
resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==}
engines: {node: '>=0.10.0'}
resolve-from@4.0.0:
resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
engines: {node: '>=4'}
reusify@1.0.4:
resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==}
engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
rimraf@3.0.2:
resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==}
deprecated: Rimraf versions prior to v4 are no longer supported
hasBin: true
rollup@4.18.1:
resolution: {integrity: sha512-Elx2UT8lzxxOXMpy5HWQGZqkrQOtrVDDa/bm9l10+U4rQnVzbL/LgZ4NOM1MPIDyHk69W4InuYDF5dzRh4Kw1A==}
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
hasBin: true
run-parallel@1.2.0:
resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
scheduler@0.23.2:
resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==}
semver@7.6.2:
resolution: {integrity: sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==}
engines: {node: '>=10'}
hasBin: true
shallowequal@1.1.0:
resolution: {integrity: sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==}
shebang-command@2.0.0:
resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
engines: {node: '>=8'}
shebang-regex@3.0.0:
resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}
engines: {node: '>=8'}
slash@3.0.0:
resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==}
engines: {node: '>=8'}
source-map-js@1.2.0:
resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==}
engines: {node: '>=0.10.0'}
strip-ansi@6.0.1:
resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==}
engines: {node: '>=8'}
strip-json-comments@3.1.1:
resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
engines: {node: '>=8'}
styled-components@6.1.11:
resolution: {integrity: sha512-Ui0jXPzbp1phYij90h12ksljKGqF8ncGx+pjrNPsSPhbUUjWT2tD1FwGo2LF6USCnbrsIhNngDfodhxbegfEOA==}
engines: {node: '>= 16'}
peerDependencies:
react: '>= 16.8.0'
react-dom: '>= 16.8.0'
stylis@4.3.2:
resolution: {integrity: sha512-bhtUjWd/z6ltJiQwg0dUfxEJ+W+jdqQd8TbWLWyeIJHlnsqmGLRFFd8e5mA0AZi/zx90smXRlN66YMTcaSFifg==}
supports-color@7.2.0:
resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
engines: {node: '>=8'}
text-table@0.2.0:
resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==}
to-regex-range@5.0.1:
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
engines: {node: '>=8.0'}
ts-api-utils@1.3.0:
resolution: {integrity: sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==}
engines: {node: '>=16'}
peerDependencies:
typescript: '>=4.2.0'
tslib@2.6.2:
resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==}
type-check@0.4.0:
resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}
engines: {node: '>= 0.8.0'}
type-fest@0.20.2:
resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==}
engines: {node: '>=10'}
typescript@5.5.3:
resolution: {integrity: sha512-/hreyEujaB0w76zKo6717l3L0o/qEUtRgdvUBvlkhoWeOVMjMuHNHk0BRBzikzuGDqNmPQbg5ifMEqsHLiIUcQ==}
engines: {node: '>=14.17'}
hasBin: true
uri-js@4.4.1:
resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
vite@5.3.3:
resolution: {integrity: sha512-NPQdeCU0Dv2z5fu+ULotpuq5yfCS1BzKUIPhNbP3YBfAMGJXbt2nS+sbTFu+qchaqWTD+H3JK++nRwr6XIcp6A==}
engines: {node: ^18.0.0 || >=20.0.0}
hasBin: true
peerDependencies:
'@types/node': ^18.0.0 || >=20.0.0
less: '*'
lightningcss: ^1.21.0
sass: '*'
stylus: '*'
sugarss: '*'
terser: ^5.4.0
peerDependenciesMeta:
'@types/node':
optional: true
less:
optional: true
lightningcss:
optional: true
sass:
optional: true
stylus:
optional: true
sugarss:
optional: true
terser:
optional: true
which@2.0.2:
resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
engines: {node: '>= 8'}
hasBin: true
word-wrap@1.2.5:
resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==}
engines: {node: '>=0.10.0'}
wrappy@1.0.2:
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
yocto-queue@0.1.0:
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
engines: {node: '>=10'}
snapshots:
'@emotion/is-prop-valid@1.2.2':
dependencies:
'@emotion/memoize': 0.8.1
'@emotion/memoize@0.8.1': {}
'@emotion/unitless@0.8.1': {}
'@esbuild/aix-ppc64@0.21.5':
optional: true
'@esbuild/android-arm64@0.21.5':
optional: true
'@esbuild/android-arm@0.21.5':
optional: true
'@esbuild/android-x64@0.21.5':
optional: true
'@esbuild/darwin-arm64@0.21.5':
optional: true
'@esbuild/darwin-x64@0.21.5':
optional: true
'@esbuild/freebsd-arm64@0.21.5':
optional: true
'@esbuild/freebsd-x64@0.21.5':
optional: true
'@esbuild/linux-arm64@0.21.5':
optional: true
'@esbuild/linux-arm@0.21.5':
optional: true
'@esbuild/linux-ia32@0.21.5':
optional: true
'@esbuild/linux-loong64@0.21.5':
optional: true
'@esbuild/linux-mips64el@0.21.5':
optional: true
'@esbuild/linux-ppc64@0.21.5':
optional: true
'@esbuild/linux-riscv64@0.21.5':
optional: true
'@esbuild/linux-s390x@0.21.5':
optional: true
'@esbuild/linux-x64@0.21.5':
optional: true
'@esbuild/netbsd-x64@0.21.5':
optional: true
'@esbuild/openbsd-x64@0.21.5':
optional: true
'@esbuild/sunos-x64@0.21.5':
optional: true
'@esbuild/win32-arm64@0.21.5':
optional: true
'@esbuild/win32-ia32@0.21.5':
optional: true
'@esbuild/win32-x64@0.21.5':
optional: true
'@eslint-community/eslint-utils@4.4.0(eslint@8.57.0)':
dependencies:
eslint: 8.57.0
eslint-visitor-keys: 3.4.3
'@eslint-community/regexpp@4.11.0': {}
'@eslint/eslintrc@2.1.4':
dependencies:
ajv: 6.12.6
debug: 4.3.5
espree: 9.6.1
globals: 13.24.0
ignore: 5.3.1
import-fresh: 3.3.0
js-yaml: 4.1.0
minimatch: 3.1.2
strip-json-comments: 3.1.1
transitivePeerDependencies:
- supports-color
'@eslint/js@8.57.0': {}
'@humanwhocodes/config-array@0.11.14':
dependencies:
'@humanwhocodes/object-schema': 2.0.3
debug: 4.3.5
minimatch: 3.1.2
transitivePeerDependencies:
- supports-color
'@humanwhocodes/module-importer@1.0.1': {}
'@humanwhocodes/object-schema@2.0.3': {}
'@nodelib/fs.scandir@2.1.5':
dependencies:
'@nodelib/fs.stat': 2.0.5
run-parallel: 1.2.0
'@nodelib/fs.stat@2.0.5': {}
'@nodelib/fs.walk@1.2.8':
dependencies:
'@nodelib/fs.scandir': 2.1.5
fastq: 1.17.1
'@rollup/rollup-android-arm-eabi@4.18.1':
optional: true
'@rollup/rollup-android-arm64@4.18.1':
optional: true
'@rollup/rollup-darwin-arm64@4.18.1':
optional: true
'@rollup/rollup-darwin-x64@4.18.1':
optional: true
'@rollup/rollup-linux-arm-gnueabihf@4.18.1':
optional: true
'@rollup/rollup-linux-arm-musleabihf@4.18.1':
optional: true
'@rollup/rollup-linux-arm64-gnu@4.18.1':
optional: true
'@rollup/rollup-linux-arm64-musl@4.18.1':
optional: true
'@rollup/rollup-linux-powerpc64le-gnu@4.18.1':
optional: true
'@rollup/rollup-linux-riscv64-gnu@4.18.1':
optional: true
'@rollup/rollup-linux-s390x-gnu@4.18.1':
optional: true
'@rollup/rollup-linux-x64-gnu@4.18.1':
optional: true
'@rollup/rollup-linux-x64-musl@4.18.1':
optional: true
'@rollup/rollup-win32-arm64-msvc@4.18.1':
optional: true
'@rollup/rollup-win32-ia32-msvc@4.18.1':
optional: true
'@rollup/rollup-win32-x64-msvc@4.18.1':
optional: true
'@swc/core-darwin-arm64@1.6.13':
optional: true
'@swc/core-darwin-x64@1.6.13':
optional: true
'@swc/core-linux-arm-gnueabihf@1.6.13':
optional: true
'@swc/core-linux-arm64-gnu@1.6.13':
optional: true
'@swc/core-linux-arm64-musl@1.6.13':
optional: true
'@swc/core-linux-x64-gnu@1.6.13':
optional: true
'@swc/core-linux-x64-musl@1.6.13':
optional: true
'@swc/core-win32-arm64-msvc@1.6.13':
optional: true
'@swc/core-win32-ia32-msvc@1.6.13':
optional: true
'@swc/core-win32-x64-msvc@1.6.13':
optional: true
'@swc/core@1.6.13':
dependencies:
'@swc/counter': 0.1.3
'@swc/types': 0.1.9
optionalDependencies:
'@swc/core-darwin-arm64': 1.6.13
'@swc/core-darwin-x64': 1.6.13
'@swc/core-linux-arm-gnueabihf': 1.6.13
'@swc/core-linux-arm64-gnu': 1.6.13
'@swc/core-linux-arm64-musl': 1.6.13
'@swc/core-linux-x64-gnu': 1.6.13
'@swc/core-linux-x64-musl': 1.6.13
'@swc/core-win32-arm64-msvc': 1.6.13
'@swc/core-win32-ia32-msvc': 1.6.13
'@swc/core-win32-x64-msvc': 1.6.13
'@swc/counter@0.1.3': {}
'@swc/types@0.1.9':
dependencies:
'@swc/counter': 0.1.3
'@types/estree@1.0.5': {}
'@types/prop-types@15.7.12': {}
'@types/react-dom@18.3.0':
dependencies:
'@types/react': 18.3.3
'@types/react@18.3.3':
dependencies:
'@types/prop-types': 15.7.12
csstype: 3.1.3
'@types/stylis@4.2.5': {}
'@typescript-eslint/eslint-plugin@7.16.0(@typescript-eslint/parser@7.16.0(eslint@8.57.0)(typescript@5.5.3))(eslint@8.57.0)(typescript@5.5.3)':
dependencies:
'@eslint-community/regexpp': 4.11.0
'@typescript-eslint/parser': 7.16.0(eslint@8.57.0)(typescript@5.5.3)
'@typescript-eslint/scope-manager': 7.16.0
'@typescript-eslint/type-utils': 7.16.0(eslint@8.57.0)(typescript@5.5.3)
'@typescript-eslint/utils': 7.16.0(eslint@8.57.0)(typescript@5.5.3)
'@typescript-eslint/visitor-keys': 7.16.0
eslint: 8.57.0
graphemer: 1.4.0
ignore: 5.3.1
natural-compare: 1.4.0
ts-api-utils: 1.3.0(typescript@5.5.3)
optionalDependencies:
typescript: 5.5.3
transitivePeerDependencies:
- supports-color
'@typescript-eslint/parser@7.16.0(eslint@8.57.0)(typescript@5.5.3)':
dependencies:
'@typescript-eslint/scope-manager': 7.16.0
'@typescript-eslint/types': 7.16.0
'@typescript-eslint/typescript-estree': 7.16.0(typescript@5.5.3)
'@typescript-eslint/visitor-keys': 7.16.0
debug: 4.3.5
eslint: 8.57.0
optionalDependencies:
typescript: 5.5.3
transitivePeerDependencies:
- supports-color
'@typescript-eslint/scope-manager@7.16.0':
dependencies:
'@typescript-eslint/types': 7.16.0
'@typescript-eslint/visitor-keys': 7.16.0
'@typescript-eslint/type-utils@7.16.0(eslint@8.57.0)(typescript@5.5.3)':
dependencies:
'@typescript-eslint/typescript-estree': 7.16.0(typescript@5.5.3)
'@typescript-eslint/utils': 7.16.0(eslint@8.57.0)(typescript@5.5.3)
debug: 4.3.5
eslint: 8.57.0
ts-api-utils: 1.3.0(typescript@5.5.3)
optionalDependencies:
typescript: 5.5.3
transitivePeerDependencies:
- supports-color
'@typescript-eslint/types@7.16.0': {}
'@typescript-eslint/typescript-estree@7.16.0(typescript@5.5.3)':
dependencies:
'@typescript-eslint/types': 7.16.0
'@typescript-eslint/visitor-keys': 7.16.0
debug: 4.3.5
globby: 11.1.0
is-glob: 4.0.3
minimatch: 9.0.5
semver: 7.6.2
ts-api-utils: 1.3.0(typescript@5.5.3)
optionalDependencies:
typescript: 5.5.3
transitivePeerDependencies:
- supports-color
'@typescript-eslint/utils@7.16.0(eslint@8.57.0)(typescript@5.5.3)':
dependencies:
'@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0)
'@typescript-eslint/scope-manager': 7.16.0
'@typescript-eslint/types': 7.16.0
'@typescript-eslint/typescript-estree': 7.16.0(typescript@5.5.3)
eslint: 8.57.0
transitivePeerDependencies:
- supports-color
- typescript
'@typescript-eslint/visitor-keys@7.16.0':
dependencies:
'@typescript-eslint/types': 7.16.0
eslint-visitor-keys: 3.4.3
'@ungap/structured-clone@1.2.0': {}
'@vitejs/plugin-react-swc@3.7.0(vite@5.3.3)':
dependencies:
'@swc/core': 1.6.13
vite: 5.3.3
transitivePeerDependencies:
- '@swc/helpers'
acorn-jsx@5.3.2(acorn@8.12.1):
dependencies:
acorn: 8.12.1
acorn@8.12.1: {}
ajv@6.12.6:
dependencies:
fast-deep-equal: 3.1.3
fast-json-stable-stringify: 2.1.0
json-schema-traverse: 0.4.1
uri-js: 4.4.1
ansi-regex@5.0.1: {}
ansi-styles@4.3.0:
dependencies:
color-convert: 2.0.1
argparse@2.0.1: {}
array-union@2.1.0: {}
balanced-match@1.0.2: {}
brace-expansion@1.1.11:
dependencies:
balanced-match: 1.0.2
concat-map: 0.0.1
brace-expansion@2.0.1:
dependencies:
balanced-match: 1.0.2
braces@3.0.3:
dependencies:
fill-range: 7.1.1
callsites@3.1.0: {}
camelize@1.0.1: {}
chalk@4.1.2:
dependencies:
ansi-styles: 4.3.0
supports-color: 7.2.0
color-convert@2.0.1:
dependencies:
color-name: 1.1.4
color-name@1.1.4: {}
concat-map@0.0.1: {}
cross-spawn@7.0.3:
dependencies:
path-key: 3.1.1
shebang-command: 2.0.0
which: 2.0.2
css-color-keywords@1.0.0: {}
css-to-react-native@3.2.0:
dependencies:
camelize: 1.0.1
css-color-keywords: 1.0.0
postcss-value-parser: 4.2.0
csstype@3.1.3: {}
debug@4.3.5:
dependencies:
ms: 2.1.2
deep-is@0.1.4: {}
dir-glob@3.0.1:
dependencies:
path-type: 4.0.0
doctrine@3.0.0:
dependencies:
esutils: 2.0.3
esbuild@0.21.5:
optionalDependencies:
'@esbuild/aix-ppc64': 0.21.5
'@esbuild/android-arm': 0.21.5
'@esbuild/android-arm64': 0.21.5
'@esbuild/android-x64': 0.21.5
'@esbuild/darwin-arm64': 0.21.5
'@esbuild/darwin-x64': 0.21.5
'@esbuild/freebsd-arm64': 0.21.5
'@esbuild/freebsd-x64': 0.21.5
'@esbuild/linux-arm': 0.21.5
'@esbuild/linux-arm64': 0.21.5
'@esbuild/linux-ia32': 0.21.5
'@esbuild/linux-loong64': 0.21.5
'@esbuild/linux-mips64el': 0.21.5
'@esbuild/linux-ppc64': 0.21.5
'@esbuild/linux-riscv64': 0.21.5
'@esbuild/linux-s390x': 0.21.5
'@esbuild/linux-x64': 0.21.5
'@esbuild/netbsd-x64': 0.21.5
'@esbuild/openbsd-x64': 0.21.5
'@esbuild/sunos-x64': 0.21.5
'@esbuild/win32-arm64': 0.21.5
'@esbuild/win32-ia32': 0.21.5
'@esbuild/win32-x64': 0.21.5
escape-string-regexp@4.0.0: {}
eslint-plugin-react-hooks@4.6.2(eslint@8.57.0):
dependencies:
eslint: 8.57.0
eslint-plugin-react-refresh@0.4.8(eslint@8.57.0):
dependencies:
eslint: 8.57.0
eslint-scope@7.2.2:
dependencies:
esrecurse: 4.3.0
estraverse: 5.3.0
eslint-visitor-keys@3.4.3: {}
eslint@8.57.0:
dependencies:
'@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0)
'@eslint-community/regexpp': 4.11.0
'@eslint/eslintrc': 2.1.4
'@eslint/js': 8.57.0
'@humanwhocodes/config-array': 0.11.14
'@humanwhocodes/module-importer': 1.0.1
'@nodelib/fs.walk': 1.2.8
'@ungap/structured-clone': 1.2.0
ajv: 6.12.6
chalk: 4.1.2
cross-spawn: 7.0.3
debug: 4.3.5
doctrine: 3.0.0
escape-string-regexp: 4.0.0
eslint-scope: 7.2.2
eslint-visitor-keys: 3.4.3
espree: 9.6.1
esquery: 1.6.0
esutils: 2.0.3
fast-deep-equal: 3.1.3
file-entry-cache: 6.0.1
find-up: 5.0.0
glob-parent: 6.0.2
globals: 13.24.0
graphemer: 1.4.0
ignore: 5.3.1
imurmurhash: 0.1.4
is-glob: 4.0.3
is-path-inside: 3.0.3
js-yaml: 4.1.0
json-stable-stringify-without-jsonify: 1.0.1
levn: 0.4.1
lodash.merge: 4.6.2
minimatch: 3.1.2
natural-compare: 1.4.0
optionator: 0.9.4
strip-ansi: 6.0.1
text-table: 0.2.0
transitivePeerDependencies:
- supports-color
espree@9.6.1:
dependencies:
acorn: 8.12.1
acorn-jsx: 5.3.2(acorn@8.12.1)
eslint-visitor-keys: 3.4.3
esquery@1.6.0:
dependencies:
estraverse: 5.3.0
esrecurse@4.3.0:
dependencies:
estraverse: 5.3.0
estraverse@5.3.0: {}
esutils@2.0.3: {}
fast-deep-equal@3.1.3: {}
fast-glob@3.3.2:
dependencies:
'@nodelib/fs.stat': 2.0.5
'@nodelib/fs.walk': 1.2.8
glob-parent: 5.1.2
merge2: 1.4.1
micromatch: 4.0.7
fast-json-stable-stringify@2.1.0: {}
fast-levenshtein@2.0.6: {}
fastq@1.17.1:
dependencies:
reusify: 1.0.4
file-entry-cache@6.0.1:
dependencies:
flat-cache: 3.2.0
fill-range@7.1.1:
dependencies:
to-regex-range: 5.0.1
find-up@5.0.0:
dependencies:
locate-path: 6.0.0
path-exists: 4.0.0
flat-cache@3.2.0:
dependencies:
flatted: 3.3.1
keyv: 4.5.4
rimraf: 3.0.2
flatted@3.3.1: {}
fs.realpath@1.0.0: {}
fsevents@2.3.3:
optional: true
glob-parent@5.1.2:
dependencies:
is-glob: 4.0.3
glob-parent@6.0.2:
dependencies:
is-glob: 4.0.3
glob@7.2.3:
dependencies:
fs.realpath: 1.0.0
inflight: 1.0.6
inherits: 2.0.4
minimatch: 3.1.2
once: 1.4.0
path-is-absolute: 1.0.1
globals@13.24.0:
dependencies:
type-fest: 0.20.2
globby@11.1.0:
dependencies:
array-union: 2.1.0
dir-glob: 3.0.1
fast-glob: 3.3.2
ignore: 5.3.1
merge2: 1.4.1
slash: 3.0.0
graphemer@1.4.0: {}
has-flag@4.0.0: {}
ignore@5.3.1: {}
import-fresh@3.3.0:
dependencies:
parent-module: 1.0.1
resolve-from: 4.0.0
imurmurhash@0.1.4: {}
inflight@1.0.6:
dependencies:
once: 1.4.0
wrappy: 1.0.2
inherits@2.0.4: {}
is-extglob@2.1.1: {}
is-glob@4.0.3:
dependencies:
is-extglob: 2.1.1
is-number@7.0.0: {}
is-path-inside@3.0.3: {}
isexe@2.0.0: {}
js-tokens@4.0.0: {}
js-yaml@4.1.0:
dependencies:
argparse: 2.0.1
json-buffer@3.0.1: {}
json-schema-traverse@0.4.1: {}
json-stable-stringify-without-jsonify@1.0.1: {}
keyv@4.5.4:
dependencies:
json-buffer: 3.0.1
levn@0.4.1:
dependencies:
prelude-ls: 1.2.1
type-check: 0.4.0
locate-path@6.0.0:
dependencies:
p-locate: 5.0.0
lodash.merge@4.6.2: {}
loose-envify@1.4.0:
dependencies:
js-tokens: 4.0.0
merge2@1.4.1: {}
micromatch@4.0.7:
dependencies:
braces: 3.0.3
picomatch: 2.3.1
minimatch@3.1.2:
dependencies:
brace-expansion: 1.1.11
minimatch@9.0.5:
dependencies:
brace-expansion: 2.0.1
ms@2.1.2: {}
nanoid@3.3.7: {}
natural-compare@1.4.0: {}
once@1.4.0:
dependencies:
wrappy: 1.0.2
optionator@0.9.4:
dependencies:
deep-is: 0.1.4
fast-levenshtein: 2.0.6
levn: 0.4.1
prelude-ls: 1.2.1
type-check: 0.4.0
word-wrap: 1.2.5
p-limit@3.1.0:
dependencies:
yocto-queue: 0.1.0
p-locate@5.0.0:
dependencies:
p-limit: 3.1.0
parent-module@1.0.1:
dependencies:
callsites: 3.1.0
path-exists@4.0.0: {}
path-is-absolute@1.0.1: {}
path-key@3.1.1: {}
path-type@4.0.0: {}
picocolors@1.0.1: {}
picomatch@2.3.1: {}
postcss-value-parser@4.2.0: {}
postcss@8.4.38:
dependencies:
nanoid: 3.3.7
picocolors: 1.0.1
source-map-js: 1.2.0
postcss@8.4.39:
dependencies:
nanoid: 3.3.7
picocolors: 1.0.1
source-map-js: 1.2.0
prelude-ls@1.2.1: {}
punycode@2.3.1: {}
queue-microtask@1.2.3: {}
react-dom@18.3.1(react@18.3.1):
dependencies:
loose-envify: 1.4.0
react: 18.3.1
scheduler: 0.23.2
react@18.3.1:
dependencies:
loose-envify: 1.4.0
resolve-from@4.0.0: {}
reusify@1.0.4: {}
rimraf@3.0.2:
dependencies:
glob: 7.2.3
rollup@4.18.1:
dependencies:
'@types/estree': 1.0.5
optionalDependencies:
'@rollup/rollup-android-arm-eabi': 4.18.1
'@rollup/rollup-android-arm64': 4.18.1
'@rollup/rollup-darwin-arm64': 4.18.1
'@rollup/rollup-darwin-x64': 4.18.1
'@rollup/rollup-linux-arm-gnueabihf': 4.18.1
'@rollup/rollup-linux-arm-musleabihf': 4.18.1
'@rollup/rollup-linux-arm64-gnu': 4.18.1
'@rollup/rollup-linux-arm64-musl': 4.18.1
'@rollup/rollup-linux-powerpc64le-gnu': 4.18.1
'@rollup/rollup-linux-riscv64-gnu': 4.18.1
'@rollup/rollup-linux-s390x-gnu': 4.18.1
'@rollup/rollup-linux-x64-gnu': 4.18.1
'@rollup/rollup-linux-x64-musl': 4.18.1
'@rollup/rollup-win32-arm64-msvc': 4.18.1
'@rollup/rollup-win32-ia32-msvc': 4.18.1
'@rollup/rollup-win32-x64-msvc': 4.18.1
fsevents: 2.3.3
run-parallel@1.2.0:
dependencies:
queue-microtask: 1.2.3
scheduler@0.23.2:
dependencies:
loose-envify: 1.4.0
semver@7.6.2: {}
shallowequal@1.1.0: {}
shebang-command@2.0.0:
dependencies:
shebang-regex: 3.0.0
shebang-regex@3.0.0: {}
slash@3.0.0: {}
source-map-js@1.2.0: {}
strip-ansi@6.0.1:
dependencies:
ansi-regex: 5.0.1
strip-json-comments@3.1.1: {}
styled-components@6.1.11(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
dependencies:
'@emotion/is-prop-valid': 1.2.2
'@emotion/unitless': 0.8.1
'@types/stylis': 4.2.5
css-to-react-native: 3.2.0
csstype: 3.1.3
postcss: 8.4.38
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
shallowequal: 1.1.0
stylis: 4.3.2
tslib: 2.6.2
stylis@4.3.2: {}
supports-color@7.2.0:
dependencies:
has-flag: 4.0.0
text-table@0.2.0: {}
to-regex-range@5.0.1:
dependencies:
is-number: 7.0.0
ts-api-utils@1.3.0(typescript@5.5.3):
dependencies:
typescript: 5.5.3
tslib@2.6.2: {}
type-check@0.4.0:
dependencies:
prelude-ls: 1.2.1
type-fest@0.20.2: {}
typescript@5.5.3: {}
uri-js@4.4.1:
dependencies:
punycode: 2.3.1
vite@5.3.3:
dependencies:
esbuild: 0.21.5
postcss: 8.4.39
rollup: 4.18.1
optionalDependencies:
fsevents: 2.3.3
which@2.0.2:
dependencies:
isexe: 2.0.0
word-wrap@1.2.5: {}
wrappy@1.0.2: {}
yocto-queue@0.1.0: {}
0707010000014C000041ED000000000000000000000002680FB1F800000000000000000000000000000000000000000000001D00000000kube-score-1.20.0/web/public0707010000014D000081A4000000000000000000000001680FB1F80000412E000000000000000000000000000000000000002A00000000kube-score-1.20.0/web/public/wasm_exec.js// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
"use strict";
(() => {
const enosys = () => {
const err = new Error("not implemented");
err.code = "ENOSYS";
return err;
};
if (!globalThis.fs) {
let outputBuf = "";
globalThis.fs = {
constants: { O_WRONLY: -1, O_RDWR: -1, O_CREAT: -1, O_TRUNC: -1, O_APPEND: -1, O_EXCL: -1 }, // unused
writeSync(fd, buf) {
outputBuf += decoder.decode(buf);
const nl = outputBuf.lastIndexOf("\n");
if (nl != -1) {
console.log(outputBuf.substring(0, nl));
outputBuf = outputBuf.substring(nl + 1);
}
return buf.length;
},
write(fd, buf, offset, length, position, callback) {
if (offset !== 0 || length !== buf.length || position !== null) {
callback(enosys());
return;
}
const n = this.writeSync(fd, buf);
callback(null, n);
},
chmod(path, mode, callback) { callback(enosys()); },
chown(path, uid, gid, callback) { callback(enosys()); },
close(fd, callback) { callback(enosys()); },
fchmod(fd, mode, callback) { callback(enosys()); },
fchown(fd, uid, gid, callback) { callback(enosys()); },
fstat(fd, callback) { callback(enosys()); },
fsync(fd, callback) { callback(null); },
ftruncate(fd, length, callback) { callback(enosys()); },
lchown(path, uid, gid, callback) { callback(enosys()); },
link(path, link, callback) { callback(enosys()); },
lstat(path, callback) { callback(enosys()); },
mkdir(path, perm, callback) { callback(enosys()); },
open(path, flags, mode, callback) { callback(enosys()); },
read(fd, buffer, offset, length, position, callback) { callback(enosys()); },
readdir(path, callback) { callback(enosys()); },
readlink(path, callback) { callback(enosys()); },
rename(from, to, callback) { callback(enosys()); },
rmdir(path, callback) { callback(enosys()); },
stat(path, callback) { callback(enosys()); },
symlink(path, link, callback) { callback(enosys()); },
truncate(path, length, callback) { callback(enosys()); },
unlink(path, callback) { callback(enosys()); },
utimes(path, atime, mtime, callback) { callback(enosys()); },
};
}
if (!globalThis.process) {
globalThis.process = {
getuid() { return -1; },
getgid() { return -1; },
geteuid() { return -1; },
getegid() { return -1; },
getgroups() { throw enosys(); },
pid: -1,
ppid: -1,
umask() { throw enosys(); },
cwd() { throw enosys(); },
chdir() { throw enosys(); },
}
}
if (!globalThis.crypto) {
throw new Error("globalThis.crypto is not available, polyfill required (crypto.getRandomValues only)");
}
if (!globalThis.performance) {
throw new Error("globalThis.performance is not available, polyfill required (performance.now only)");
}
if (!globalThis.TextEncoder) {
throw new Error("globalThis.TextEncoder is not available, polyfill required");
}
if (!globalThis.TextDecoder) {
throw new Error("globalThis.TextDecoder is not available, polyfill required");
}
const encoder = new TextEncoder("utf-8");
const decoder = new TextDecoder("utf-8");
globalThis.Go = class {
constructor() {
this.argv = ["js"];
this.env = {};
this.exit = (code) => {
if (code !== 0) {
console.warn("exit code:", code);
}
};
this._exitPromise = new Promise((resolve) => {
this._resolveExitPromise = resolve;
});
this._pendingEvent = null;
this._scheduledTimeouts = new Map();
this._nextCallbackTimeoutID = 1;
const setInt64 = (addr, v) => {
this.mem.setUint32(addr + 0, v, true);
this.mem.setUint32(addr + 4, Math.floor(v / 4294967296), true);
}
const setInt32 = (addr, v) => {
this.mem.setUint32(addr + 0, v, true);
}
const getInt64 = (addr) => {
const low = this.mem.getUint32(addr + 0, true);
const high = this.mem.getInt32(addr + 4, true);
return low + high * 4294967296;
}
const loadValue = (addr) => {
const f = this.mem.getFloat64(addr, true);
if (f === 0) {
return undefined;
}
if (!isNaN(f)) {
return f;
}
const id = this.mem.getUint32(addr, true);
return this._values[id];
}
const storeValue = (addr, v) => {
const nanHead = 0x7FF80000;
if (typeof v === "number" && v !== 0) {
if (isNaN(v)) {
this.mem.setUint32(addr + 4, nanHead, true);
this.mem.setUint32(addr, 0, true);
return;
}
this.mem.setFloat64(addr, v, true);
return;
}
if (v === undefined) {
this.mem.setFloat64(addr, 0, true);
return;
}
let id = this._ids.get(v);
if (id === undefined) {
id = this._idPool.pop();
if (id === undefined) {
id = this._values.length;
}
this._values[id] = v;
this._goRefCounts[id] = 0;
this._ids.set(v, id);
}
this._goRefCounts[id]++;
let typeFlag = 0;
switch (typeof v) {
case "object":
if (v !== null) {
typeFlag = 1;
}
break;
case "string":
typeFlag = 2;
break;
case "symbol":
typeFlag = 3;
break;
case "function":
typeFlag = 4;
break;
}
this.mem.setUint32(addr + 4, nanHead | typeFlag, true);
this.mem.setUint32(addr, id, true);
}
const loadSlice = (addr) => {
const array = getInt64(addr + 0);
const len = getInt64(addr + 8);
return new Uint8Array(this._inst.exports.mem.buffer, array, len);
}
const loadSliceOfValues = (addr) => {
const array = getInt64(addr + 0);
const len = getInt64(addr + 8);
const a = new Array(len);
for (let i = 0; i < len; i++) {
a[i] = loadValue(array + i * 8);
}
return a;
}
const loadString = (addr) => {
const saddr = getInt64(addr + 0);
const len = getInt64(addr + 8);
return decoder.decode(new DataView(this._inst.exports.mem.buffer, saddr, len));
}
const timeOrigin = Date.now() - performance.now();
this.importObject = {
_gotest: {
add: (a, b) => a + b,
},
gojs: {
// Go's SP does not change as long as no Go code is running. Some operations (e.g. calls, getters and setters)
// may synchronously trigger a Go event handler. This makes Go code get executed in the middle of the imported
// function. A goroutine can switch to a new stack if the current stack is too small (see morestack function).
// This changes the SP, thus we have to update the SP used by the imported function.
// func wasmExit(code int32)
"runtime.wasmExit": (sp) => {
sp >>>= 0;
const code = this.mem.getInt32(sp + 8, true);
this.exited = true;
delete this._inst;
delete this._values;
delete this._goRefCounts;
delete this._ids;
delete this._idPool;
this.exit(code);
},
// func wasmWrite(fd uintptr, p unsafe.Pointer, n int32)
"runtime.wasmWrite": (sp) => {
sp >>>= 0;
const fd = getInt64(sp + 8);
const p = getInt64(sp + 16);
const n = this.mem.getInt32(sp + 24, true);
fs.writeSync(fd, new Uint8Array(this._inst.exports.mem.buffer, p, n));
},
// func resetMemoryDataView()
"runtime.resetMemoryDataView": (sp) => {
sp >>>= 0;
this.mem = new DataView(this._inst.exports.mem.buffer);
},
// func nanotime1() int64
"runtime.nanotime1": (sp) => {
sp >>>= 0;
setInt64(sp + 8, (timeOrigin + performance.now()) * 1000000);
},
// func walltime() (sec int64, nsec int32)
"runtime.walltime": (sp) => {
sp >>>= 0;
const msec = (new Date).getTime();
setInt64(sp + 8, msec / 1000);
this.mem.setInt32(sp + 16, (msec % 1000) * 1000000, true);
},
// func scheduleTimeoutEvent(delay int64) int32
"runtime.scheduleTimeoutEvent": (sp) => {
sp >>>= 0;
const id = this._nextCallbackTimeoutID;
this._nextCallbackTimeoutID++;
this._scheduledTimeouts.set(id, setTimeout(
() => {
this._resume();
while (this._scheduledTimeouts.has(id)) {
// for some reason Go failed to register the timeout event, log and try again
// (temporary workaround for https://github.com/golang/go/issues/28975)
console.warn("scheduleTimeoutEvent: missed timeout event");
this._resume();
}
},
getInt64(sp + 8),
));
this.mem.setInt32(sp + 16, id, true);
},
// func clearTimeoutEvent(id int32)
"runtime.clearTimeoutEvent": (sp) => {
sp >>>= 0;
const id = this.mem.getInt32(sp + 8, true);
clearTimeout(this._scheduledTimeouts.get(id));
this._scheduledTimeouts.delete(id);
},
// func getRandomData(r []byte)
"runtime.getRandomData": (sp) => {
sp >>>= 0;
crypto.getRandomValues(loadSlice(sp + 8));
},
// func finalizeRef(v ref)
"syscall/js.finalizeRef": (sp) => {
sp >>>= 0;
const id = this.mem.getUint32(sp + 8, true);
this._goRefCounts[id]--;
if (this._goRefCounts[id] === 0) {
const v = this._values[id];
this._values[id] = null;
this._ids.delete(v);
this._idPool.push(id);
}
},
// func stringVal(value string) ref
"syscall/js.stringVal": (sp) => {
sp >>>= 0;
storeValue(sp + 24, loadString(sp + 8));
},
// func valueGet(v ref, p string) ref
"syscall/js.valueGet": (sp) => {
sp >>>= 0;
const result = Reflect.get(loadValue(sp + 8), loadString(sp + 16));
sp = this._inst.exports.getsp() >>> 0; // see comment above
storeValue(sp + 32, result);
},
// func valueSet(v ref, p string, x ref)
"syscall/js.valueSet": (sp) => {
sp >>>= 0;
Reflect.set(loadValue(sp + 8), loadString(sp + 16), loadValue(sp + 32));
},
// func valueDelete(v ref, p string)
"syscall/js.valueDelete": (sp) => {
sp >>>= 0;
Reflect.deleteProperty(loadValue(sp + 8), loadString(sp + 16));
},
// func valueIndex(v ref, i int) ref
"syscall/js.valueIndex": (sp) => {
sp >>>= 0;
storeValue(sp + 24, Reflect.get(loadValue(sp + 8), getInt64(sp + 16)));
},
// valueSetIndex(v ref, i int, x ref)
"syscall/js.valueSetIndex": (sp) => {
sp >>>= 0;
Reflect.set(loadValue(sp + 8), getInt64(sp + 16), loadValue(sp + 24));
},
// func valueCall(v ref, m string, args []ref) (ref, bool)
"syscall/js.valueCall": (sp) => {
sp >>>= 0;
try {
const v = loadValue(sp + 8);
const m = Reflect.get(v, loadString(sp + 16));
const args = loadSliceOfValues(sp + 32);
const result = Reflect.apply(m, v, args);
sp = this._inst.exports.getsp() >>> 0; // see comment above
storeValue(sp + 56, result);
this.mem.setUint8(sp + 64, 1);
} catch (err) {
sp = this._inst.exports.getsp() >>> 0; // see comment above
storeValue(sp + 56, err);
this.mem.setUint8(sp + 64, 0);
}
},
// func valueInvoke(v ref, args []ref) (ref, bool)
"syscall/js.valueInvoke": (sp) => {
sp >>>= 0;
try {
const v = loadValue(sp + 8);
const args = loadSliceOfValues(sp + 16);
const result = Reflect.apply(v, undefined, args);
sp = this._inst.exports.getsp() >>> 0; // see comment above
storeValue(sp + 40, result);
this.mem.setUint8(sp + 48, 1);
} catch (err) {
sp = this._inst.exports.getsp() >>> 0; // see comment above
storeValue(sp + 40, err);
this.mem.setUint8(sp + 48, 0);
}
},
// func valueNew(v ref, args []ref) (ref, bool)
"syscall/js.valueNew": (sp) => {
sp >>>= 0;
try {
const v = loadValue(sp + 8);
const args = loadSliceOfValues(sp + 16);
const result = Reflect.construct(v, args);
sp = this._inst.exports.getsp() >>> 0; // see comment above
storeValue(sp + 40, result);
this.mem.setUint8(sp + 48, 1);
} catch (err) {
sp = this._inst.exports.getsp() >>> 0; // see comment above
storeValue(sp + 40, err);
this.mem.setUint8(sp + 48, 0);
}
},
// func valueLength(v ref) int
"syscall/js.valueLength": (sp) => {
sp >>>= 0;
setInt64(sp + 16, parseInt(loadValue(sp + 8).length));
},
// valuePrepareString(v ref) (ref, int)
"syscall/js.valuePrepareString": (sp) => {
sp >>>= 0;
const str = encoder.encode(String(loadValue(sp + 8)));
storeValue(sp + 16, str);
setInt64(sp + 24, str.length);
},
// valueLoadString(v ref, b []byte)
"syscall/js.valueLoadString": (sp) => {
sp >>>= 0;
const str = loadValue(sp + 8);
loadSlice(sp + 16).set(str);
},
// func valueInstanceOf(v ref, t ref) bool
"syscall/js.valueInstanceOf": (sp) => {
sp >>>= 0;
this.mem.setUint8(sp + 24, (loadValue(sp + 8) instanceof loadValue(sp + 16)) ? 1 : 0);
},
// func copyBytesToGo(dst []byte, src ref) (int, bool)
"syscall/js.copyBytesToGo": (sp) => {
sp >>>= 0;
const dst = loadSlice(sp + 8);
const src = loadValue(sp + 32);
if (!(src instanceof Uint8Array || src instanceof Uint8ClampedArray)) {
this.mem.setUint8(sp + 48, 0);
return;
}
const toCopy = src.subarray(0, dst.length);
dst.set(toCopy);
setInt64(sp + 40, toCopy.length);
this.mem.setUint8(sp + 48, 1);
},
// func copyBytesToJS(dst ref, src []byte) (int, bool)
"syscall/js.copyBytesToJS": (sp) => {
sp >>>= 0;
const dst = loadValue(sp + 8);
const src = loadSlice(sp + 16);
if (!(dst instanceof Uint8Array || dst instanceof Uint8ClampedArray)) {
this.mem.setUint8(sp + 48, 0);
return;
}
const toCopy = src.subarray(0, dst.length);
dst.set(toCopy);
setInt64(sp + 40, toCopy.length);
this.mem.setUint8(sp + 48, 1);
},
"debug": (value) => {
console.log(value);
},
}
};
}
async run(instance) {
if (!(instance instanceof WebAssembly.Instance)) {
throw new Error("Go.run: WebAssembly.Instance expected");
}
this._inst = instance;
this.mem = new DataView(this._inst.exports.mem.buffer);
this._values = [ // JS values that Go currently has references to, indexed by reference id
NaN,
0,
null,
true,
false,
globalThis,
this,
];
this._goRefCounts = new Array(this._values.length).fill(Infinity); // number of references that Go has to a JS value, indexed by reference id
this._ids = new Map([ // mapping from JS values to reference ids
[0, 1],
[null, 2],
[true, 3],
[false, 4],
[globalThis, 5],
[this, 6],
]);
this._idPool = []; // unused ids that have been garbage collected
this.exited = false; // whether the Go program has exited
// Pass command line arguments and environment variables to WebAssembly by writing them to the linear memory.
let offset = 4096;
const strPtr = (str) => {
const ptr = offset;
const bytes = encoder.encode(str + "\0");
new Uint8Array(this.mem.buffer, offset, bytes.length).set(bytes);
offset += bytes.length;
if (offset % 8 !== 0) {
offset += 8 - (offset % 8);
}
return ptr;
};
const argc = this.argv.length;
const argvPtrs = [];
this.argv.forEach((arg) => {
argvPtrs.push(strPtr(arg));
});
argvPtrs.push(0);
const keys = Object.keys(this.env).sort();
keys.forEach((key) => {
argvPtrs.push(strPtr(`${key}=${this.env[key]}`));
});
argvPtrs.push(0);
const argv = offset;
argvPtrs.forEach((ptr) => {
this.mem.setUint32(offset, ptr, true);
this.mem.setUint32(offset + 4, 0, true);
offset += 8;
});
// The linker guarantees global data starts from at least wasmMinDataAddr.
// Keep in sync with cmd/link/internal/ld/data.go:wasmMinDataAddr.
const wasmMinDataAddr = 4096 + 8192;
if (offset >= wasmMinDataAddr) {
throw new Error("total length of command line and environment variables exceeds limit");
}
this._inst.exports.run(argc, argv);
if (this.exited) {
this._resolveExitPromise();
}
await this._exitPromise;
}
_resume() {
if (this.exited) {
throw new Error("Go program has already exited");
}
this._inst.exports.resume();
if (this.exited) {
this._resolveExitPromise();
}
}
_makeFuncWrapper(id) {
const go = this;
return function () {
const event = { id: id, this: this, args: arguments };
go._pendingEvent = event;
go._resume();
return event.result;
};
}
}
})();0707010000014E000041ED000000000000000000000002680FB1F800000000000000000000000000000000000000000000001A00000000kube-score-1.20.0/web/src0707010000014F000081A4000000000000000000000001680FB1F800000882000000000000000000000000000000000000002200000000kube-score-1.20.0/web/src/App.tsximport styled from 'styled-components'
import kubeScoreLogo from './assets/logo.svg'
import Run from './Run'
function App() {
return (
<RootContainer>
<div style={{ display: 'flex', gap: 16, alignItems: "center", justifyItems: "center", justifyContent: "space-between", flex: 1, flexShrink: 0, flexWrap: "wrap", width: "100%" }}>
<div style={{ display: 'flex', gap: 8, alignItems: "center", flex: 1, flexShrink: 0, }}>
<img src={kubeScoreLogo} style={{ height: 60 }} alt="kube-score logo" />
<h1 style={{ fontSize: 32, textWrap: "nowrap" }}>kube-score</h1>
</div>
<div style={{ display: 'flex', gap: 16, alignItems: "center", fontSize: 16, flex: 1, flexShrink: 0 }}>
<div style={{ flex: 1 }}></div>
<a href="https://github.com/zegl/kube-score">GitHub</a>
<a href="https://github.com/zegl/kube-score">README</a>
<a href="https://github.com/zegl/kube-score/blob/master/README_CHECKS.md">Docs</a>
</div>
</div>
<div style={{ fontSize: 14 }}>
<p>Kubernetes object analysis with recommendations for improved reliability and security.</p>
<p><code>kube-score</code> is a tool that does static code analysis of your Kubernetes object definitions. The
output is
a list of
recommendations of what you can improve to make your application more secure and resilient.</p>
<p><code>kube-score</code> is <a href="https://github.com/zegl/kube-score">open-source and available under the
MIT-license</a>. For more information about how to use kube-score, see <a
href="https://github.com/zegl/kube-score">zegl/kube-score</a> on GitHub.
Use this website to easily test kube-score, just paste your object definition YAML or JSON in the box below.
</p>
<p>
This tool is running 100% in your browser, no data is sent to any server!
</p>
</div>
<Run />
</RootContainer>
)
}
const RootContainer = styled.div`
margin: 0 auto;
padding: 0.5;
text-align: left;
@media (min-width: 1024px) {
padding: 2rem;
}
`
export default App
07070100000150000081A4000000000000000000000001680FB1F80000102A000000000000000000000000000000000000002200000000kube-score-1.20.0/web/src/Run.tsximport { useEffect, useState } from "react";
import './assets/term.css'
import styled from "styled-components";
type Format = "human" | "ci" | "json" | "sarif" | "junit"
const FormatNames: Record<Format, string> = {
"human": "CLI",
"ci": "CI",
"json": "JSON",
"sarif": "SARIF",
"junit": "JUnit",
}
// Types for the WebAssembly module
declare global {
interface Window {
handleScore: (input: string, type: Format) => string;
}
}
function Run() {
const [input, setInput] = useState(`apiVersion: apps/v1
kind: Deployment
metadata:
name: statefulset-test-1
spec:
template:
metadata:
labels:
app: foo
spec:
containers:
- name: foobar
image: foo:bar
---
apiVersion: policy/v1beta1
kind: PodDisruptionBudget
metadata:
name: app-budget
spec:
minAvailable: 2
selector:
matchLabels:
app: not-foo`)
const [wasmLoading, setWasmLoading] = useState(false)
const [result, setResult] = useState("")
const [format, setFormat] = useState<Format>("human")
const scoreWhenReady = () => {
if (typeof window.handleScore === "undefined") {
setWasmLoading(true)
setTimeout(scoreWhenReady, 100);
return;
}
score();
}
const score = () => {
setWasmLoading(false)
setResult("Running...")
setResult(window.handleScore(input, format));
}
useEffect(() => {
scoreWhenReady()
}, [input, format])
const output = wasmLoading ? "LOADING_WASM" :
!input ? "NO_INPUT" :
result ? "RESULT" : "RUNNING" as const
return (
<div>
<div style={{color: "#4e4e4e", fontSize: 12}}>Output format</div>
<div style={{ display: "flex", gap: 2, alignItems: "center" }}>
{Object.entries(FormatNames).map(([f, name]) => <OutputFormatButton $selected={format == f} key={f} onClick={() => setFormat(f as Format)}>{name}</OutputFormatButton>)}
</div>
<InputOutputContainer>
<InputTextarea value={input} onChange={(e) => setInput(e.target.value)}></InputTextarea>
<div style={{ flex: 1, flexShrink: 0, overflow: "hidden", display: "flex", gap: 4, flexDirection: "column" }}>
<TermContainer>
{output === "LOADING_WASM" && <div>Loading WebAssembly...</div>}
{output === "NO_INPUT" && <div>Paste your Kubernetes YAML in the textarea to get started</div>}
{output === "RESULT" && <>
{format === "human" ? <div dangerouslySetInnerHTML={{ __html: result }}></div> : <div>{result}</div>}
</>}
{output === "RUNNING" && <div>Running...</div>}
</TermContainer></div>
</InputOutputContainer>
</div>
);
}
const InputOutputContainer = styled.div`
display: flex;
gap: 8px;
margin-top: 16px;
flex-direction: column;
@media (min-width: 1024px) {
flex-direction: row;
}
`
const OutputFormatButton = styled.button<{ $selected: boolean; }>`
background: ${props => props.$selected ? "#c172a9" : "#474747"};
color: ${props => props.$selected ? "white" : "white"};
border-radius: 4px;
padding: 6px 10px;
border: 0;
outline-offset: 2px;
cursor: pointer;
font-size: 14px;
`
const TermContainer = styled.div`
background: #171717;
border-radius: 5px;
color: white;
word-break: break-word;
overflow-wrap: break-word;
font-family: "SFMono-Regular", Monaco, Menlo, Consolas, "Liberation Mono", Courier, monospace;
font-size: 14px;
line-height: 20px;
padding: 14px 18px;
white-space: pre-wrap;
flex: 1;
flex-shrink: 0;
`
const InputTextarea = styled.textarea`
border-radius: 5px;
font-family: "SFMono-Regular", Monaco, Menlo, Consolas, "Liberation Mono", Courier, monospace;
font-size: 14px;
line-height: 20px;
padding: 14px 18px;
flex: 1;
flex-shrink: 0;
min-height: 200px
`
export default Run;07070100000151000041ED000000000000000000000002680FB1F800000000000000000000000000000000000000000000002100000000kube-score-1.20.0/web/src/assets07070100000152000081A4000000000000000000000001680FB1F800000456000000000000000000000000000000000000002A00000000kube-score-1.20.0/web/src/assets/logo.svg<?xml version="1.0" encoding="UTF-8"?>
<svg viewBox="0 195 900 300" xmlns="http://www.w3.org/2000/svg">
<path d="m799.56 319.34l0.44 57.6-199.47 115.27 0.88995-58.45 198.14-114.42z" fill="#6c97b4"/>
<path d="m699.63 319.29l-49.65 28.56-0.01001-0.01001-50.42-28.81-0.73999-57.94 100.82 58.2z" fill="#99d6ff"/>
<path d="m601.42 433.76l-0.88995 58.45-150.29-86.8-50.27-29.03-50.14-28.95-50.06-28.92v-57.97l301.65 173.22z" fill="#99d6ff"/>
<path d="m598.81 261.09l0.73999 57.94-49.84 28.78-50.11-28.94 99.21-57.78z" fill="#6c97b4"/>
<path d="m450.24 405.41l-0.019989 0.029999-149.56 86.17-0.089996-57.85 99.4-57.38 50.27 29.03z" fill="#6c97b4"/>
<path d="m300.57 433.76l0.089996 57.85-200.66-115.82v-57.82l200.57 115.79z" fill="#99d6ff"/>
<path d="m299.77 260.54v57.97l-49.85 28.7-50.27-28.98 100.12-57.69z" fill="#6c97b4"/>
<path d="M499.6,318.87l50.11,28.94,50.08,28.91L650,347.85l49.65-28.56-100.82-58.2Zm-50.15-29,149.43-86.32L799.56,319.34,601.42,433.77,299.77,260.54,199.65,318.23l50.27,29,50.19,28.93,49.73-28.72,50.13,29-99.4,57.39L100,318,299,203Z" fill="#ffd4f2"/>
</svg>
07070100000153000081A4000000000000000000000001680FB1F80000292A000000000000000000000000000000000000002A00000000kube-score-1.20.0/web/src/assets/term.css.term-fg1 {}
/* don't bold beccause it looks weird */
.term-fg2 {
color: #838887;
}
/* faint (decreased intensity) - same as gray really */
.term-fg3 {
font-style: italic;
}
/* italic */
.term-fg4 {
text-decoration: underline;
}
/* underline */
.term-fg5 {
animation: blink-animation 1s steps(3, start) infinite;
}
/* blink */
.term-fg9 {
text-decoration: line-through;
}
/* crossed-out */
.term-fg30 {
color: #666666;
}
/* black (but we can't use black, so a diff color) */
.term-fg31 {
color: #ff7070;
}
/* red */
.term-fg32 {
color: #b0f986;
}
/* green */
.term-fg33 {
color: #c6c502;
}
/* yellow */
.term-fg34 {
color: #8db7e0;
}
/* blue */
.term-fg35 {
color: #f271fb;
}
/* magenta */
.term-fg36 {
color: #6bf7ff;
}
/* cyan */
/* high intense colors */
.term-fgi1 {
color: #5ef765;
}
.term-fgi90 {
color: #838887;
}
/* grey */
.term-fgi91 {
color: #ff3333;
}
/* red */
.term-fgi92 {
color: #00FF00;
}
/* green */
.term-fgi93 {
color: #fffc67;
}
/* yellow */
.term-fgi94 {
color: #6871ff;
}
/* blue */
.term-fgi95 {
color: #ff76ff;
}
/* magenta */
.term-fgi96 {
color: #60fcff;
}
/* cyan */
/* background colors */
.term-bg40 {
background: #676767;
}
/* grey */
.term-bg41 {
background: #ff4343;
}
/* red */
.term-bg42 {
background: #99ff5f;
}
/* green */
/* custom foreground/background combos for readability */
.term-fg31.term-bg40 {
color: #F8A39F;
}
/* xterm colors */
.term-fgx16 {
color: #000000;
}
.term-fgx17 {
color: #00005f;
}
.term-fgx18 {
color: #000087;
}
.term-fgx19 {
color: #0000af;
}
.term-fgx20 {
color: #0000d7;
}
.term-fgx21 {
color: #0000ff;
}
.term-fgx22 {
color: #005f00;
}
.term-fgx23 {
color: #005f5f;
}
.term-fgx24 {
color: #005f87;
}
.term-fgx25 {
color: #005faf;
}
.term-fgx26 {
color: #005fd7;
}
.term-fgx27 {
color: #005fff;
}
.term-fgx28 {
color: #008700;
}
.term-fgx29 {
color: #00875f;
}
.term-fgx30 {
color: #008787;
}
.term-fgx31 {
color: #0087af;
}
.term-fgx32 {
color: #0087d7;
}
.term-fgx33 {
color: #0087ff;
}
.term-fgx34 {
color: #00af00;
}
.term-fgx35 {
color: #00af5f;
}
.term-fgx36 {
color: #00af87;
}
.term-fgx37 {
color: #00afaf;
}
.term-fgx38 {
color: #00afd7;
}
.term-fgx39 {
color: #00afff;
}
.term-fgx40 {
color: #00d700;
}
.term-fgx41 {
color: #00d75f;
}
.term-fgx42 {
color: #00d787;
}
.term-fgx43 {
color: #00d7af;
}
.term-fgx44 {
color: #00d7d7;
}
.term-fgx45 {
color: #00d7ff;
}
.term-fgx46 {
color: #00ff00;
}
.term-fgx47 {
color: #00ff5f;
}
.term-fgx48 {
color: #00ff87;
}
.term-fgx49 {
color: #00ffaf;
}
.term-fgx50 {
color: #00ffd7;
}
.term-fgx51 {
color: #00ffff;
}
.term-fgx52 {
color: #5f0000;
}
.term-fgx53 {
color: #5f005f;
}
.term-fgx54 {
color: #5f0087;
}
.term-fgx55 {
color: #5f00af;
}
.term-fgx56 {
color: #5f00d7;
}
.term-fgx57 {
color: #5f00ff;
}
.term-fgx58 {
color: #5f5f00;
}
.term-fgx59 {
color: #5f5f5f;
}
.term-fgx60 {
color: #5f5f87;
}
.term-fgx61 {
color: #5f5faf;
}
.term-fgx62 {
color: #5f5fd7;
}
.term-fgx63 {
color: #5f5fff;
}
.term-fgx64 {
color: #5f8700;
}
.term-fgx65 {
color: #5f875f;
}
.term-fgx66 {
color: #5f8787;
}
.term-fgx67 {
color: #5f87af;
}
.term-fgx68 {
color: #5f87d7;
}
.term-fgx69 {
color: #5f87ff;
}
.term-fgx70 {
color: #5faf00;
}
.term-fgx71 {
color: #5faf5f;
}
.term-fgx72 {
color: #5faf87;
}
.term-fgx73 {
color: #5fafaf;
}
.term-fgx74 {
color: #5fafd7;
}
.term-fgx75 {
color: #5fafff;
}
.term-fgx76 {
color: #5fd700;
}
.term-fgx77 {
color: #5fd75f;
}
.term-fgx78 {
color: #5fd787;
}
.term-fgx79 {
color: #5fd7af;
}
.term-fgx80 {
color: #5fd7d7;
}
.term-fgx81 {
color: #5fd7ff;
}
.term-fgx82 {
color: #5fff00;
}
.term-fgx83 {
color: #5fff5f;
}
.term-fgx84 {
color: #5fff87;
}
.term-fgx85 {
color: #5fffaf;
}
.term-fgx86 {
color: #5fffd7;
}
.term-fgx87 {
color: #5fffff;
}
.term-fgx88 {
color: #870000;
}
.term-fgx89 {
color: #87005f;
}
.term-fgx90 {
color: #870087;
}
.term-fgx91 {
color: #8700af;
}
.term-fgx92 {
color: #8700d7;
}
.term-fgx93 {
color: #8700ff;
}
.term-fgx94 {
color: #875f00;
}
.term-fgx95 {
color: #875f5f;
}
.term-fgx96 {
color: #875f87;
}
.term-fgx97 {
color: #875faf;
}
.term-fgx98 {
color: #875fd7;
}
.term-fgx99 {
color: #875fff;
}
.term-fgx100 {
color: #878700;
}
.term-fgx101 {
color: #87875f;
}
.term-fgx102 {
color: #878787;
}
.term-fgx103 {
color: #8787af;
}
.term-fgx104 {
color: #8787d7;
}
.term-fgx105 {
color: #8787ff;
}
.term-fgx106 {
color: #87af00;
}
.term-fgx107 {
color: #87af5f;
}
.term-fgx108 {
color: #87af87;
}
.term-fgx109 {
color: #87afaf;
}
.term-fgx110 {
color: #87afd7;
}
.term-fgx111 {
color: #87afff;
}
.term-fgx112 {
color: #87d700;
}
.term-fgx113 {
color: #87d75f;
}
.term-fgx114 {
color: #87d787;
}
.term-fgx115 {
color: #87d7af;
}
.term-fgx116 {
color: #87d7d7;
}
.term-fgx117 {
color: #87d7ff;
}
.term-fgx118 {
color: #87ff00;
}
.term-fgx119 {
color: #87ff5f;
}
.term-fgx120 {
color: #87ff87;
}
.term-fgx121 {
color: #87ffaf;
}
.term-fgx122 {
color: #87ffd7;
}
.term-fgx123 {
color: #87ffff;
}
.term-fgx124 {
color: #af0000;
}
.term-fgx125 {
color: #af005f;
}
.term-fgx126 {
color: #af0087;
}
.term-fgx127 {
color: #af00af;
}
.term-fgx128 {
color: #af00d7;
}
.term-fgx129 {
color: #af00ff;
}
.term-fgx130 {
color: #af5f00;
}
.term-fgx131 {
color: #af5f5f;
}
.term-fgx132 {
color: #af5f87;
}
.term-fgx133 {
color: #af5faf;
}
.term-fgx134 {
color: #af5fd7;
}
.term-fgx135 {
color: #af5fff;
}
.term-fgx136 {
color: #af8700;
}
.term-fgx137 {
color: #af875f;
}
.term-fgx138 {
color: #af8787;
}
.term-fgx139 {
color: #af87af;
}
.term-fgx140 {
color: #af87d7;
}
.term-fgx141 {
color: #af87ff;
}
.term-fgx142 {
color: #afaf00;
}
.term-fgx143 {
color: #afaf5f;
}
.term-fgx144 {
color: #afaf87;
}
.term-fgx145 {
color: #afafaf;
}
.term-fgx146 {
color: #afafd7;
}
.term-fgx147 {
color: #afafff;
}
.term-fgx148 {
color: #afd700;
}
.term-fgx149 {
color: #afd75f;
}
.term-fgx150 {
color: #afd787;
}
.term-fgx151 {
color: #afd7af;
}
.term-fgx152 {
color: #afd7d7;
}
.term-fgx153 {
color: #afd7ff;
}
.term-fgx154 {
color: #afff00;
}
.term-fgx155 {
color: #afff5f;
}
.term-fgx156 {
color: #afff87;
}
.term-fgx157 {
color: #afffaf;
}
.term-fgx158 {
color: #afffd7;
}
.term-fgx159 {
color: #afffff;
}
.term-fgx160 {
color: #d70000;
}
.term-fgx161 {
color: #d7005f;
}
.term-fgx162 {
color: #d70087;
}
.term-fgx163 {
color: #d700af;
}
.term-fgx164 {
color: #d700d7;
}
.term-fgx165 {
color: #d700ff;
}
.term-fgx166 {
color: #d75f00;
}
.term-fgx167 {
color: #d75f5f;
}
.term-fgx168 {
color: #d75f87;
}
.term-fgx169 {
color: #d75faf;
}
.term-fgx170 {
color: #d75fd7;
}
.term-fgx171 {
color: #d75fff;
}
.term-fgx172 {
color: #d78700;
}
.term-fgx173 {
color: #d7875f;
}
.term-fgx174 {
color: #d78787;
}
.term-fgx175 {
color: #d787af;
}
.term-fgx176 {
color: #d787d7;
}
.term-fgx177 {
color: #d787ff;
}
.term-fgx178 {
color: #d7af00;
}
.term-fgx179 {
color: #d7af5f;
}
.term-fgx180 {
color: #d7af87;
}
.term-fgx181 {
color: #d7afaf;
}
.term-fgx182 {
color: #d7afd7;
}
.term-fgx183 {
color: #d7afff;
}
.term-fgx184 {
color: #d7d700;
}
.term-fgx185 {
color: #d7d75f;
}
.term-fgx186 {
color: #d7d787;
}
.term-fgx187 {
color: #d7d7af;
}
.term-fgx188 {
color: #d7d7d7;
}
.term-fgx189 {
color: #d7d7ff;
}
.term-fgx190 {
color: #d7ff00;
}
.term-fgx191 {
color: #d7ff5f;
}
.term-fgx192 {
color: #d7ff87;
}
.term-fgx193 {
color: #d7ffaf;
}
.term-fgx194 {
color: #d7ffd7;
}
.term-fgx195 {
color: #d7ffff;
}
.term-fgx196 {
color: #ff0000;
}
.term-fgx197 {
color: #ff005f;
}
.term-fgx198 {
color: #ff0087;
}
.term-fgx199 {
color: #ff00af;
}
.term-fgx200 {
color: #ff00d7;
}
.term-fgx201 {
color: #ff00ff;
}
.term-fgx202 {
color: #ff5f00;
}
.term-fgx203 {
color: #ff5f5f;
}
.term-fgx204 {
color: #ff5f87;
}
.term-fgx205 {
color: #ff5faf;
}
.term-fgx206 {
color: #ff5fd7;
}
.term-fgx207 {
color: #ff5fff;
}
.term-fgx208 {
color: #ff8700;
}
.term-fgx209 {
color: #ff875f;
}
.term-fgx210 {
color: #ff8787;
}
.term-fgx211 {
color: #ff87af;
}
.term-fgx212 {
color: #ff87d7;
}
.term-fgx213 {
color: #ff87ff;
}
.term-fgx214 {
color: #ffaf00;
}
.term-fgx215 {
color: #ffaf5f;
}
.term-fgx216 {
color: #ffaf87;
}
.term-fgx217 {
color: #ffafaf;
}
.term-fgx218 {
color: #ffafd7;
}
.term-fgx219 {
color: #ffafff;
}
.term-fgx220 {
color: #ffd700;
}
.term-fgx221 {
color: #ffd75f;
}
.term-fgx222 {
color: #ffd787;
}
.term-fgx223 {
color: #ffd7af;
}
.term-fgx224 {
color: #ffd7d7;
}
.term-fgx225 {
color: #ffd7ff;
}
.term-fgx226 {
color: #ffff00;
}
.term-fgx227 {
color: #ffff5f;
}
.term-fgx228 {
color: #ffff87;
}
.term-fgx229 {
color: #ffffaf;
}
.term-fgx230 {
color: #ffffd7;
}
.term-fgx231 {
color: #ffffff;
}
.term-fgx232 {
color: #080808;
}
.term-fgx233 {
color: #121212;
}
.term-fgx234 {
color: #1c1c1c;
}
.term-fgx235 {
color: #262626;
}
.term-fgx236 {
color: #303030;
}
.term-fgx237 {
color: #3a3a3a;
}
.term-fgx238 {
color: #444444;
}
.term-fgx239 {
color: #4e4e4e;
}
.term-fgx240 {
color: #585858;
}
.term-fgx241 {
color: #626262;
}
.term-fgx242 {
color: #6c6c6c;
}
.term-fgx243 {
color: #767676;
}
.term-fgx244 {
color: #808080;
}
.term-fgx245 {
color: #8a8a8a;
}
.term-fgx246 {
color: #949494;
}
.term-fgx247 {
color: #9e9e9e;
}
.term-fgx248 {
color: #a8a8a8;
}
.term-fgx249 {
color: #b2b2b2;
}
.term-fgx250 {
color: #bcbcbc;
}
.term-fgx251 {
color: #c6c6c6;
}
.term-fgx252 {
color: #d0d0d0;
}
.term-fgx253 {
color: #dadada;
}
.term-fgx254 {
color: #e4e4e4;
}
.term-fgx255 {
color: #eeeeee;
}
07070100000154000081A4000000000000000000000001680FB1F80000016B000000000000000000000000000000000000002400000000kube-score-1.20.0/web/src/index.css:root {
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
line-height: 1.5;
font-weight: 400;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
a {
font-weight: 500;
color: #646cff;
text-decoration: inherit;
}
a:hover {
color: #535bf2;
}07070100000155000081A4000000000000000000000001680FB1F8000000EC000000000000000000000000000000000000002300000000kube-score-1.20.0/web/src/main.tsximport React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App.tsx'
import './index.css'
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<App />
</React.StrictMode>,
)
07070100000156000081A4000000000000000000000001680FB1F800000026000000000000000000000000000000000000002800000000kube-score-1.20.0/web/src/vite-env.d.ts/// <reference types="vite/client" />
07070100000157000081A4000000000000000000000001680FB1F8000002A5000000000000000000000000000000000000002800000000kube-score-1.20.0/web/tsconfig.app.json{
"compilerOptions": {
"composite": true,
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"moduleDetection": "force",
"noEmit": true,
"jsx": "react-jsx",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
},
"include": ["src"]
}
07070100000158000081A4000000000000000000000001680FB1F80000008B000000000000000000000000000000000000002400000000kube-score-1.20.0/web/tsconfig.json{
"files": [],
"references": [
{
"path": "./tsconfig.app.json"
},
{
"path": "./tsconfig.node.json"
}
]
}
07070100000159000081A4000000000000000000000001680FB1F800000145000000000000000000000000000000000000002900000000kube-score-1.20.0/web/tsconfig.node.json{
"compilerOptions": {
"composite": true,
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
"skipLibCheck": true,
"module": "ESNext",
"moduleResolution": "bundler",
"allowSyntheticDefaultImports": true,
"strict": true,
"noEmit": true
},
"include": ["vite.config.ts"]
}
0707010000015A000081A4000000000000000000000001680FB1F8000000A7000000000000000000000000000000000000002500000000kube-score-1.20.0/web/vite.config.tsimport { defineConfig } from 'vite'
import react from '@vitejs/plugin-react-swc'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
})
07070100000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000B00000000TRAILER!!!1218 blocks