├── .codefresh ├── build.yml └── release.yml ├── .drone.yml ├── .eslintrc.yml ├── .gitattributes ├── .github └── workflows │ ├── carvel-ytt.yaml │ └── test.yaml ├── .gitignore ├── .pre-commit-hooks.yaml ├── .prettierrc ├── Dockerfile ├── LICENSE ├── README.md ├── action.yml ├── assets ├── .gitignore └── .npmignore ├── bin └── kubevious ├── build-executables.sh ├── build.sh ├── configuration.sh ├── crds ├── cluster-rule.yaml ├── library.yaml ├── rule-applicator.yaml └── rule.yaml ├── dependencies.sh ├── docker └── docker-entrypoint.sh ├── docs └── cicd │ ├── README.md │ ├── drone.md │ └── github.md ├── k8s ├── activate-kubeconfig.sh ├── configuration.sh ├── delete.sh ├── exec-kubectl.sh ├── install-manifests.sh ├── recreate.sh └── start.sh ├── kitchen-sink ├── command │ └── extract-manifests │ │ ├── index.ts │ │ ├── output.ts │ │ └── types.ts └── local-registry-accessor.ts ├── kubevious.sh ├── package-npm-and-install.sh ├── package.json ├── prepare-docker.sh ├── prepare.sh ├── run-dev.sh ├── run-docker.sh ├── run-test.sh ├── samples ├── argo-rollout │ ├── analysis-template.yaml │ ├── cluster-analysis-template.yaml │ ├── config-map-db.yaml │ ├── hpa.yaml │ ├── rollout.yaml │ ├── rule-replica-count.yaml │ └── service.yaml ├── bad-json.json ├── bad-yaml.yaml ├── cert-manager.yaml ├── cluster-role.yaml ├── config-map-1.yaml ├── cr-default-ns.yaml ├── cr-good.yaml ├── cr-invalid.yaml ├── cr-unknown.yaml ├── crd-invalid.yaml ├── crd.yaml ├── deployment-2.yaml ├── deployment-3.yaml ├── deployment.yaml ├── empty.yaml ├── hpa.yaml ├── ingress-class │ ├── kong.yaml │ └── nginx.yaml ├── invalid-service-1.yaml ├── invalid-service-2.yaml ├── invalid-service-3.yaml ├── invalid-service-4.yaml ├── invalid-service-5.yaml ├── invalid-service-6.yaml ├── istio-crds.yaml ├── istio-gateway.yaml ├── istio-test.yaml ├── istio.yaml ├── multiple-manifests.yaml ├── network-policy.yaml ├── payload-pod.json ├── payload-service.json ├── pepsi │ ├── cert-manager.yaml │ ├── certificate-check-applicator-pepsi.yaml │ ├── deployment.yaml │ ├── hpa.yaml │ ├── ingress.yaml │ ├── rule-replica-count.yaml │ └── service.yaml ├── pod.yaml ├── pods.yaml ├── prometheus.yaml ├── rules │ ├── bad-rule.yaml │ ├── certificate-check-applicator-kubevious.yaml │ ├── certificate-check.yaml │ ├── cluster-role.yaml │ ├── env-from.yaml │ ├── latest-image.yaml │ ├── rule-istio-gateway-host-check.yaml │ ├── service-selector-cached.yaml │ ├── service-selector-check.yaml │ ├── service-selector-transform.yaml │ ├── service-selector-union.yaml │ └── test-rule.yaml ├── sealed-secret │ ├── secret-ref-test.yaml │ └── secret-shortcut.yaml ├── service-empty-map.yaml ├── service.yaml ├── ss │ └── sample-ss.yaml ├── sveltos │ ├── cluster-profile.yaml │ └── crd-cluster-profile.yaml └── traefik.yaml ├── scripts ├── compile-executables.sh ├── debugging │ └── run-builder-docker.sh ├── prepare-package.sh ├── setup-google-action-version.sh └── setup-pre-commit-hook-version.sh ├── src ├── api-schema │ ├── crd-schema-to-json-schema-converter.ts │ ├── k8s-api-schema-fetcher.ts │ └── k8s-api-schema-registry.ts ├── commands │ ├── guard │ │ ├── command.ts │ │ ├── docs.ts │ │ ├── format.ts │ │ ├── index.ts │ │ ├── output.ts │ │ ├── samples.ts │ │ └── types.ts │ ├── index-library │ │ ├── command.ts │ │ ├── format.ts │ │ ├── index.ts │ │ ├── output.ts │ │ └── types.ts │ ├── install-git-hook │ │ ├── command.ts │ │ ├── format.ts │ │ ├── guard │ │ │ └── index.ts │ │ ├── index.ts │ │ ├── lint │ │ │ └── index.ts │ │ ├── output.ts │ │ ├── rule-library │ │ │ └── index.ts │ │ └── types.ts │ ├── lint │ │ ├── command.ts │ │ ├── docs.ts │ │ ├── format.ts │ │ ├── index.ts │ │ ├── output.ts │ │ ├── samples.ts │ │ └── types.ts │ ├── list-known-k8s-versions │ │ ├── index.ts │ │ ├── output.ts │ │ └── types.ts │ ├── program │ │ ├── docs.ts │ │ └── index.ts │ └── support │ │ └── index.ts ├── index.ts ├── infra │ └── command-action.ts ├── input │ ├── input-source-extractor.ts │ ├── input-source.ts │ ├── original-source.ts │ └── utils.ts ├── k8s-connector │ └── k8s-cluster-connector.ts ├── logger.ts ├── manifests │ ├── counters.ts │ ├── k8s-manifest.ts │ ├── manifest-package.ts │ ├── manifest-source.ts │ └── manifests-loader.ts ├── path-resolver.ts ├── preprocessors │ └── executor.ts ├── processors │ └── default-namespace-setter.ts ├── registry │ ├── cached-k8s-registry.ts │ ├── client-side-filtering.ts │ ├── combined-k8s-registry.ts │ ├── local-k8s-registry.ts │ ├── local-registry-populator.ts │ ├── local-source-registry.ts │ └── remote-k8s-registry.ts ├── rules-engine │ ├── compiler │ │ ├── cache │ │ │ └── processor.ts │ │ ├── rule-compiler.ts │ │ ├── target │ │ │ └── processor.ts │ │ └── validator │ │ │ └── processor.ts │ ├── execution │ │ ├── execution-context.ts │ │ ├── manifest-violation.ts │ │ ├── rule-execution-runtime.ts │ │ ├── rule-runtime.ts │ │ └── rules-runtime.ts │ ├── helpers │ │ ├── data-structs │ │ │ └── index.ts │ │ ├── gateway-api │ │ │ └── index.ts │ │ ├── image │ │ │ └── index.ts │ │ ├── label-lookup │ │ │ ├── dict.ts │ │ │ └── index.ts │ │ ├── labels │ │ │ └── index.ts │ │ ├── name-lookup │ │ │ ├── dict.ts │ │ │ └── index.ts │ │ ├── rule-helpers.ts │ │ └── vendor │ │ │ └── traefik.ts │ ├── query-executor.ts │ ├── query-spec │ │ ├── base.ts │ │ ├── filter │ │ │ └── filter-target-query.ts │ │ ├── first │ │ │ └── first-target-query.ts │ │ ├── k8s │ │ │ └── k8s-target-query.ts │ │ ├── manual │ │ │ └── manual-target-query.ts │ │ ├── scope-builder.ts │ │ ├── shortcut │ │ │ └── shortcut-target-query.ts │ │ ├── sync-scope-builder.ts │ │ ├── transform-many │ │ │ └── transform-many-target-query.ts │ │ ├── transform │ │ │ └── transform-target-query.ts │ │ └── union │ │ │ └── union-target-query.ts │ ├── query │ │ ├── base.ts │ │ ├── filter │ │ │ └── filter-query-executor.ts │ │ ├── first │ │ │ └── first-query-executor.ts │ │ ├── k8s │ │ │ └── k8s-query-executor.ts │ │ ├── manual │ │ │ └── manual-query-executor.ts │ │ ├── query-executor-scope.ts │ │ ├── query-executor.ts │ │ ├── shortcut │ │ │ ├── library.ts │ │ │ └── shortcut-query-executor.ts │ │ ├── transform-many │ │ │ └── transform-many-query-executor.ts │ │ ├── transform │ │ │ └── transform-query-executor.ts │ │ └── union │ │ │ └── union-query-executor.ts │ ├── registry │ │ ├── rule-registry.ts │ │ └── types.ts │ ├── reporting │ │ └── rule-engine-reporter.ts │ ├── scope-builders.ts │ ├── scope.ts │ ├── script-item.ts │ └── spec │ │ └── rule-spec.ts ├── screen │ ├── docs.ts │ ├── index.ts │ ├── manifest.ts │ ├── package-renderer.ts │ └── spinner.ts ├── types │ ├── base-object.ts │ ├── k8s.ts │ ├── kubevious.ts │ ├── manifest-result.ts │ ├── manifest.ts │ ├── result.ts │ ├── rules-result.ts │ └── source-result.ts ├── utils │ ├── k8s-manifest-sanitizer.ts │ ├── k8s.ts │ ├── path.ts │ ├── release.ts │ ├── stream.ts │ └── version-checker.ts ├── validation │ ├── k8s-manifest-validator.ts │ └── k8s-package-validator.ts └── version.ts ├── test ├── helper-k8s-manifest.ts ├── helper-parse-image.ts ├── helper-user-path-parser.ts ├── is-web-path.ts ├── path-resolve.ts ├── traefik.yaml └── utils │ └── logger.ts ├── tools └── kubevious-npm-validate-nested-dependencies.sh ├── tsconfig.json ├── version.sh └── yarn.lock /.drone.yml: -------------------------------------------------------------------------------- 1 | kind: pipeline 2 | type: docker 3 | name: default 4 | 5 | steps: 6 | - name: kubevious-file-check 7 | image: kubevious/cli 8 | settings: 9 | manifests: samples/argo-rollout 10 | crds: https://raw.githubusercontent.com/kubevious/demos/main/crds/argo-rollouts/crds.yaml 11 | - name: kubevious-helm-check 12 | image: kubevious/cli 13 | settings: 14 | helm_repo_url: https://helm.kubevious.io 15 | helm_repo_name: kubevious 16 | helm_chart: kubevious 17 | helm_namespace: kubevious 18 | helm_include_crds: true -------------------------------------------------------------------------------- /.eslintrc.yml: -------------------------------------------------------------------------------- 1 | env: 2 | browser: false 3 | commonjs: true 4 | es2020: true 5 | extends: 6 | - 'eslint:recommended' 7 | - 'plugin:@typescript-eslint/recommended' 8 | parser: '@typescript-eslint/parser' 9 | parserOptions: 10 | ecmaVersion: 12 11 | plugins: 12 | - '@typescript-eslint' 13 | rules: {} 14 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf -------------------------------------------------------------------------------- /.github/workflows/carvel-ytt.yaml: -------------------------------------------------------------------------------- 1 | name: Carvel YTT Test 2 | 3 | on: [push] 4 | 5 | jobs: 6 | kubevious_validation: 7 | runs-on: ubuntu-latest 8 | name: Validation Run 9 | steps: 10 | - name: Checkout 11 | uses: actions/checkout@v3 12 | 13 | - name: Checkout Carvel-YTT 14 | uses: actions/checkout@v3 15 | with: 16 | repository: vmware-tanzu/carvel-ytt 17 | path: carvel-ytt.git 18 | token: ${{ secrets.GITHUB_TOKEN }} 19 | 20 | - name: Run Carvel-YTT 21 | id: carvel-ytt-template 22 | uses: vmware-tanzu/carvel-setup-action@v1 23 | with: 24 | token: ${{ secrets.GITHUB_TOKEN }} 25 | - run: | 26 | ytt -f carvel-ytt.git/examples/playground/getting-started/example-hello-world/config.yml > ytt-example.yaml 27 | - name: Validate Carvel-YTT 28 | id: carvel-ytt-validation 29 | uses: kubevious/cli@main 30 | with: 31 | manifests: ytt-example.yaml -------------------------------------------------------------------------------- /.github/workflows/test.yaml: -------------------------------------------------------------------------------- 1 | name: Guard Test 2 | 3 | on: [push] 4 | 5 | jobs: 6 | kubevious_validation: 7 | runs-on: ubuntu-latest 8 | name: Kubevious CLI Validation Run 9 | steps: 10 | - name: Checkout 11 | uses: actions/checkout@v3 12 | # with: 13 | # path: cli.git 14 | # - name: Checkout Mock Repo 15 | # uses: actions/checkout@v3 16 | # with: 17 | # repository: kubevious/mock-data 18 | # path: mock-data.git 19 | # - name: 'Prepare Packages' 20 | # working-directory: ./cli.git 21 | # run: './scripts/prepare-package.sh' 22 | # - name: 'Build' 23 | # working-directory: ./cli.git 24 | # run: 'docker build -t kubevious/cli .' 25 | - name: Validation of manifest files 26 | id: file-validation 27 | uses: kubevious/cli@main 28 | with: 29 | manifests: samples/argo-rollout 30 | crds: https://raw.githubusercontent.com/kubevious/demos/main/crds/argo-rollouts/crds.yaml 31 | - name: Validation of Helm Charts 32 | id: chart-validation 33 | uses: kubevious/cli@main 34 | with: 35 | helm_repo_url: https://helm.kubevious.io 36 | helm_repo_name: kubevious 37 | helm_chart: kubevious 38 | helm_namespace: kubevious 39 | helm_include_crds: true -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | 3 | # Logs 4 | logs 5 | *.log 6 | npm-debug.log* 7 | yarn-debug.log* 8 | yarn-error.log* 9 | 10 | # Runtime data 11 | pids 12 | *.pid 13 | *.seed 14 | *.pid.lock 15 | 16 | # Directory for instrumented libs generated by jscoverage/JSCover 17 | lib-cov 18 | 19 | # Coverage directory used by tools like istanbul 20 | coverage 21 | 22 | # nyc test coverage 23 | .nyc_output 24 | 25 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 26 | .grunt 27 | 28 | # Bower dependency directory (https://bower.io/) 29 | bower_components 30 | 31 | # node-waf configuration 32 | .lock-wscript 33 | 34 | # Compiled binary addons (https://nodejs.org/api/addons.html) 35 | build/Release 36 | 37 | # Dependency directories 38 | node_modules/ 39 | jspm_packages/ 40 | 41 | # TypeScript v1 declaration files 42 | typings/ 43 | 44 | # Optional npm cache directory 45 | .npm 46 | 47 | # Optional eslint cache 48 | .eslintcache 49 | 50 | # Optional REPL history 51 | .node_repl_history 52 | 53 | # Output of 'npm pack' 54 | *.tgz 55 | 56 | # Yarn Integrity file 57 | .yarn-integrity 58 | 59 | # dotenv environment variables file 60 | .env 61 | 62 | # next.js build output 63 | .next 64 | 65 | 66 | .history 67 | 68 | .idea 69 | 70 | dist 71 | 72 | binary/ -------------------------------------------------------------------------------- /.pre-commit-hooks.yaml: -------------------------------------------------------------------------------- 1 | - id: kubevious-index-library 2 | name: kubevious index rules library 3 | description: Index Kubevious rules library 4 | types: [file, yaml] 5 | language: docker_image 6 | pass_filenames: false 7 | entry: 'kubevious/cli:1.0.64' 8 | args: ['index-library', '/src'] 9 | - id: kubevious-lint 10 | name: Kubevious Lint checking for manifest API validity 11 | description: Kubevious Lint checks for manifest API validity 12 | types: [file, yaml] 13 | language: docker_image 14 | pass_filenames: false 15 | entry: 'kubevious/cli:1.0.64' 16 | args: ['lint', '/src'] 17 | - id: kubevious-guard 18 | name: Kubevious Guard checking for manifest validity and violations 19 | description: Kubevious Guard checks for manifest API validity and violations of best practices 20 | types: [file, yaml] 21 | language: docker_image 22 | pass_filenames: false 23 | entry: 'kubevious/cli:1.0.64' 24 | args: ['guard', '/src'] 25 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 120, 3 | "trailingComma": "all", 4 | "singleQuote": true, 5 | "tabWidth": 4, 6 | "semi": true, 7 | "insertPragma": false 8 | } -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Step 1 : Builder image 3 | FROM kubevious/node-builder:22 4 | RUN node --version 5 | RUN npm --version 6 | RUN yarn --version 7 | WORKDIR /app 8 | # COPY ASSESTS FROM MOCK-DATA 9 | RUN mkdir -p ./assets/k8s-api-json-schema 10 | RUN git clone https://github.com/kubevious/mock-data.git mock-data.git 11 | RUN cp mock-data.git/k8s-api-json-schema/*.json ./assets/k8s-api-json-schema/ 12 | RUN rm -rf mock-data.git 13 | # COPY CRD ASSETS 14 | COPY ./crds ./assets/crds 15 | RUN find ./assets 16 | # COPY SOURCES 17 | COPY ./package*.json ./ 18 | COPY ./yarn.lock ./ 19 | RUN yarn install --frozen-lockfile 20 | COPY ./bin ./bin 21 | COPY ./src ./src 22 | COPY ./tsconfig.json ./ 23 | # BUILD 24 | RUN npm run build 25 | RUN npm pack 26 | RUN mv kubevious-$(node -p -e "require('./package.json').version").tgz kubevious.tgz 27 | 28 | ############################################################################### 29 | # Step 2 : Runner image 30 | FROM node:18-alpine 31 | RUN apk update && apk upgrade && \ 32 | apk --no-cache add ca-certificates bash openssl git curl wget 33 | # DATA 34 | RUN mkdir -p /data 35 | # HELM 36 | ENV HELM_CACHE_HOME=/data/helm/cache 37 | ENV HELM_CONFIG_HOME=/data/helm/config 38 | ENV HELM_DATA_HOME=/data/helm/data 39 | RUN mkdir -p ${HELM_CACHE_HOME} 40 | RUN mkdir -p ${HELM_CONFIG_HOME} 41 | RUN mkdir -p ${HELM_DATA_HOME} 42 | WORKDIR /tmp 43 | ADD https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 install_helm.sh 44 | RUN chmod +x install_helm.sh 45 | RUN bash install_helm.sh 46 | RUN rm install_helm.sh 47 | # KUSTOMIZE 48 | ENV KUSTOMIZE_VER=5.2.1 49 | WORKDIR /tmp 50 | RUN curl -L https://github.com/kubernetes-sigs/kustomize/releases/download/kustomize%2Fv${KUSTOMIZE_VER}/kustomize_v${KUSTOMIZE_VER}_linux_amd64.tar.gz -o kustomize.tar.gz 51 | RUN tar -xvf kustomize.tar.gz 52 | RUN chmod +x kustomize 53 | RUN rm kustomize.tar.gz 54 | RUN mv kustomize /usr/local/bin/ 55 | RUN ls -la /usr/local/bin/kustomize 56 | # Kubevious CLI 57 | WORKDIR /app 58 | COPY --from=0 /app/kubevious.tgz ./ 59 | RUN npm install -g ./kubevious.tgz 60 | RUN rm -rf /app 61 | WORKDIR /src 62 | COPY ./docker/docker-entrypoint.sh / 63 | RUN addgroup -S kubevious && adduser -S kubevious -G kubevious -h /src 64 | RUN chown -R kubevious:kubevious /data 65 | USER kubevious 66 | ENTRYPOINT ["/docker-entrypoint.sh"] -------------------------------------------------------------------------------- /action.yml: -------------------------------------------------------------------------------- 1 | name: Kubevious CLI 2 | description: Use the Kubevious CLI in a github action 3 | author: kubevious 4 | inputs: 5 | helm_repo_url: 6 | description: Helm repository URL 7 | required: false 8 | default: '' 9 | helm_repo_name: 10 | description: Name alias for Helm repository URL 11 | required: false 12 | default: '' 13 | helm_chart: 14 | description: Helm repository chart name 15 | required: false 16 | default: '' 17 | helm_namespace: 18 | description: Helm release namespace 19 | required: false 20 | default: '' 21 | helm_include_crds: 22 | description: Indicates whether or not to include CRDs in Helm release 23 | required: false 24 | default: 'false' 25 | helm_override: 26 | description: Helm overrides 27 | required: false 28 | default: '' 29 | manifests: 30 | description: Path to manifest files, directories or URLs to validate 31 | required: false 32 | default: '' 33 | crds: 34 | description: Path to CRDs to include in validation 35 | required: false 36 | default: '' 37 | mocks: 38 | description: Path to mocked manifests to include in validation 39 | required: false 40 | default: '' 41 | k8s_version: 42 | description: Version of Kubernetes to validate againts 43 | required: false 44 | default: '' 45 | live_k8s: 46 | description: Validate againts live Kubernetes version 47 | required: false 48 | default: 'false' 49 | ignore_unknown: 50 | description: Ignore unknown Kubernetes resources 51 | required: false 52 | default: 'false' 53 | ignore_non_k8s: 54 | description: Ignore non-Kubernetes YAML files 55 | required: false 56 | default: 'false' 57 | skip_rules: 58 | description: Skip rules by name 59 | required: false 60 | default: '' 61 | only_rules: 62 | description: Run only specified rules 63 | required: false 64 | default: '' 65 | skip_rule_categories: 66 | description: Skip rules by category 67 | required: false 68 | default: '' 69 | only_rule_categories: 70 | description: Run only specified rules by category 71 | required: false 72 | default: '' 73 | detailed_output: 74 | description: Output in details 75 | required: false 76 | default: 'false' 77 | json_output: 78 | description: Output in JSON format 79 | required: false 80 | default: 'false' 81 | other_args: 82 | description: Additional arguments 83 | required: false 84 | default: '' 85 | runs: 86 | using: docker 87 | image: docker://kubevious/cli:1.0.64 88 | # image: Dockerfile 89 | branding: 90 | icon: 'shield' 91 | color: 'green' 92 | -------------------------------------------------------------------------------- /assets/.gitignore: -------------------------------------------------------------------------------- 1 | k8s-api-json-schema 2 | crds -------------------------------------------------------------------------------- /assets/.npmignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kubevious/cli/880eca26397f94e9b6c7d848968f50a56baebafc/assets/.npmignore -------------------------------------------------------------------------------- /bin/kubevious: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | require('../dist/index') -------------------------------------------------------------------------------- /build-executables.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | MY_PATH="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/$(basename "${BASH_SOURCE[0]}")" 3 | MY_DIR="$(dirname $MY_PATH)" 4 | cd $MY_DIR 5 | 6 | source configuration.sh 7 | 8 | echo ">>>" 9 | echo ">>> Installing YARN Packages" 10 | echo ">>>" 11 | yarn install --frozen-lockfile 12 | RESULT=$? 13 | if [ $RESULT -ne 0 ]; then 14 | echo "Failed to download packages" 15 | exit 1; 16 | fi 17 | 18 | echo ">>>" 19 | echo ">>> Building..." 20 | echo ">>>" 21 | ./build.sh 22 | RESULT=$? 23 | if [ $RESULT -ne 0 ]; then 24 | echo "Build failed" 25 | exit 1; 26 | fi 27 | 28 | pwd 29 | 30 | echo ">>>" 31 | echo ">>> Preparing Binary Package..." 32 | echo ">>>" 33 | ./scripts/prepare-package.sh 34 | RESULT=$? 35 | if [ $RESULT -ne 0 ]; then 36 | echo "Prepare Package Failed" 37 | exit 1; 38 | fi 39 | 40 | echo ">>>" 41 | echo ">>> Compiling Executable..." 42 | echo ">>>" 43 | ./scripts/compile-executables.sh 44 | RESULT=$? 45 | if [ $RESULT -ne 0 ]; then 46 | echo "Compile Executables Failed" 47 | exit 1; 48 | fi 49 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | MY_PATH="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/$(basename "${BASH_SOURCE[0]}")" 3 | export MY_DIR="$(dirname $MY_PATH)" 4 | 5 | cd ${MY_DIR} 6 | ${MY_DIR}/../workspace.git/kubevious-repo-build.sh -------------------------------------------------------------------------------- /configuration.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | MY_PATH="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/$(basename "${BASH_SOURCE[0]}")" 3 | MY_DIR="$(dirname $MY_PATH)" 4 | 5 | export BINARY_DIR=${MY_DIR}/binary 6 | export K8S_API_SCHEMA_DIR=${MY_DIR}/../mock-data.git/k8s-api-json-schema/ 7 | export KUBEVIOUS_CRDS_DIR=${MY_DIR}/crds/ 8 | 9 | export IMAGE_NAME=kubevious-cli 10 | export CONTAINER_NAME=kubevious-cli-test 11 | 12 | ## PLATFORM TARGETS 13 | # NODE_VERSION=node14 14 | # TARGETS=() 15 | # TARGETS+=("${NODE_VERSION}-alpine-x64") 16 | # TARGETS+=("${NODE_VERSION}-alpine-arm64") 17 | # TARGETS+=("${NODE_VERSION}-linux-x64") 18 | # TARGETS+=("${NODE_VERSION}-linux-arm64") 19 | # TARGETS+=("${NODE_VERSION}-linuxstatic-x64") 20 | # TARGETS+=("${NODE_VERSION}-linuxstatic-arm64") 21 | # TARGETS+=("${NODE_VERSION}-win-x64") 22 | # TARGETS+=("${NODE_VERSION}-win-arm64") 23 | # TARGETS+=("${NODE_VERSION}-macos-x64") 24 | # TARGETS+=("${NODE_VERSION}-macos-arm64") -------------------------------------------------------------------------------- /crds/cluster-rule.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apiextensions.k8s.io/v1 2 | kind: CustomResourceDefinition 3 | metadata: 4 | name: clusterrules.kubevious.io 5 | spec: 6 | group: kubevious.io 7 | scope: Cluster 8 | names: 9 | plural: clusterrules 10 | singular: clusterrule 11 | kind: ClusterRule 12 | versions: 13 | - name: v1alpha1 14 | served: true 15 | storage: true 16 | schema: 17 | openAPIV3Schema: 18 | type: object 19 | properties: 20 | spec: 21 | type: object 22 | properties: 23 | summary: 24 | type: string 25 | description: 26 | type: string 27 | target: 28 | type: string 29 | globalCache: 30 | type: string 31 | cache: 32 | type: string 33 | rule: 34 | type: string 35 | values: 36 | type: object 37 | x-kubernetes-preserve-unknown-fields: true 38 | disabled: 39 | type: boolean 40 | application: 41 | type: object 42 | properties: 43 | clustered: 44 | type: boolean 45 | useApplicator: 46 | type: boolean 47 | onlySelectedNamespaces: 48 | type: boolean 49 | namespaces: 50 | type: array 51 | items: 52 | type: object 53 | properties: 54 | name: 55 | type: string 56 | values: 57 | type: object 58 | x-kubernetes-preserve-unknown-fields: true 59 | required: 60 | - name 61 | dependencies: 62 | type: array 63 | items: 64 | type: object 65 | properties: 66 | name: 67 | type: string 68 | minVersion: 69 | type: string 70 | maxVersion: 71 | type: string 72 | range: 73 | type: string 74 | required: 75 | - name 76 | categories: 77 | type: array 78 | items: 79 | type: string 80 | required: 81 | - target 82 | - rule 83 | required: 84 | - spec -------------------------------------------------------------------------------- /crds/library.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apiextensions.k8s.io/v1 2 | kind: CustomResourceDefinition 3 | metadata: 4 | name: libraries.kubevious.io 5 | spec: 6 | group: kubevious.io 7 | scope: Cluster 8 | names: 9 | plural: libraries 10 | singular: library 11 | kind: Library 12 | versions: 13 | - name: v1alpha1 14 | served: true 15 | storage: true 16 | schema: 17 | openAPIV3Schema: 18 | type: object 19 | properties: 20 | spec: 21 | type: object 22 | properties: 23 | rules: 24 | type: array 25 | items: 26 | type: object 27 | properties: 28 | name: 29 | type: string 30 | path: 31 | type: string 32 | category: # TODO: Retire 33 | type: string 34 | location: 35 | type: string 36 | summary: 37 | type: string 38 | categories: 39 | type: array 40 | items: 41 | type: string 42 | required: 43 | - name 44 | - path 45 | required: 46 | - rules 47 | required: 48 | - spec -------------------------------------------------------------------------------- /crds/rule-applicator.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apiextensions.k8s.io/v1 2 | kind: CustomResourceDefinition 3 | metadata: 4 | name: ruleapplicators.kubevious.io 5 | spec: 6 | group: kubevious.io 7 | scope: Namespaced 8 | names: 9 | plural: ruleapplicators 10 | singular: ruleapplicator 11 | kind: RuleApplicator 12 | versions: 13 | - name: v1alpha1 14 | served: true 15 | storage: true 16 | schema: 17 | openAPIV3Schema: 18 | type: object 19 | properties: 20 | spec: 21 | type: object 22 | properties: 23 | clusterRuleRef: 24 | type: object 25 | properties: 26 | name: 27 | type: string 28 | required: 29 | - name 30 | disabled: 31 | type: boolean 32 | values: 33 | type: object 34 | x-kubernetes-preserve-unknown-fields: true 35 | required: 36 | - clusterRuleRef 37 | required: 38 | - spec -------------------------------------------------------------------------------- /crds/rule.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apiextensions.k8s.io/v1 2 | kind: CustomResourceDefinition 3 | metadata: 4 | name: rules.kubevious.io 5 | spec: 6 | group: kubevious.io 7 | scope: Namespaced 8 | names: 9 | plural: rules 10 | singular: rule 11 | kind: Rule 12 | versions: 13 | - name: v1alpha1 14 | served: true 15 | storage: true 16 | schema: 17 | openAPIV3Schema: 18 | type: object 19 | properties: 20 | spec: 21 | type: object 22 | properties: 23 | summary: 24 | type: string 25 | description: 26 | type: string 27 | target: 28 | type: string 29 | globalCache: 30 | type: string 31 | cache: 32 | type: string 33 | rule: 34 | type: string 35 | values: 36 | type: object 37 | x-kubernetes-preserve-unknown-fields: true 38 | disabled: 39 | type: boolean 40 | dependencies: 41 | type: array 42 | items: 43 | type: object 44 | properties: 45 | name: 46 | type: string 47 | minVersion: 48 | type: string 49 | maxVersion: 50 | type: string 51 | range: 52 | type: string 53 | required: 54 | - name 55 | categories: 56 | type: array 57 | items: 58 | type: string 59 | required: 60 | - target 61 | - rule 62 | required: 63 | - spec -------------------------------------------------------------------------------- /dependencies.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | REPO_DEPENDENCIES=( 4 | "the-lodash" 5 | "the-logger" 6 | "the-promise" 7 | "k8s-super-client" 8 | "@kubevious/entity-meta" 9 | "@kubevious/state-registry" 10 | "@kubevious/kubik" 11 | ) 12 | 13 | FORCE_RESOLVE_DEPENDENCIES=( 14 | "the-lodash" 15 | "the-logger" 16 | "the-promise" 17 | "k8s-super-client" 18 | "@kubevious/entity-meta" 19 | "@kubevious/state-registry" 20 | "@kubevious/kubik" 21 | ) 22 | -------------------------------------------------------------------------------- /docs/cicd/README.md: -------------------------------------------------------------------------------- 1 | # CI/CD Integration 2 | 3 | We support out-of-the-box integration with the following CI/CD pipelines and processes. Follow the examples below to integrate with your processes: 4 | 5 | - [GitHub Actions](/docs/cicd/github.md) 6 | - [Drone](/docs/cicd/drone.md) 7 | 8 | You can also use [kubevious-cli](https://github.com/kubevious/cli/releases) binaries or [kubevious/cli](https://hub.docker.com/u/kubevious) docker image to manually into any CI/CD processes. 9 | 10 | Feel free to let us know if you need help with integration by opening an issue here: https://github.com/kubevious/cli/issues or talking to us in [Slack](https://kubevious.io/slack). 11 | 12 | -------------------------------------------------------------------------------- /docs/cicd/drone.md: -------------------------------------------------------------------------------- 1 | # Drone Integration 2 | 3 | The workflow example below validates a directory of manifests and a Helm chart. 4 | 5 | Learn more about flags and arguments here: https://plugins.drone.io/plugins/kubevious 6 | 7 | ```yaml 8 | kind: pipeline 9 | type: docker 10 | name: default 11 | 12 | steps: 13 | 14 | - name: kubevious-file-check 15 | image: kubevious/cli 16 | settings: 17 | manifests: samples/argo-rollout 18 | crds: https://raw.githubusercontent.com/kubevious/demos/main/crds/argo-rollouts/crds.yaml 19 | 20 | - name: kubevious-helm-check 21 | image: kubevious/cli 22 | settings: 23 | helm_repo_url: https://helm.kubevious.io 24 | helm_repo_name: kubevious 25 | helm_chart: kubevious 26 | helm_namespace: kubevious 27 | helm_include_crds: true 28 | ``` -------------------------------------------------------------------------------- /docs/cicd/github.md: -------------------------------------------------------------------------------- 1 | # GitHub Actions Integration 2 | 3 | The workflow example below validates a directory of manifests and a Helm chart. 4 | 5 | See how it runs here: https://github.com/kubevious/cli/actions/workflows/test.yaml 6 | 7 | ```yaml 8 | on: [push] 9 | 10 | jobs: 11 | validation_run: 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | 16 | - name: Checkout 17 | uses: actions/checkout@v3' 18 | 19 | - name: Validation of manifest files 20 | id: files-validation 21 | uses: kubevious/cli@main 22 | with: 23 | manifests: samples/argo-rollout 24 | crds: https://raw.githubusercontent.com/kubevious/demos/main/crds/argo-rollouts/crds.yaml 25 | 26 | - name: Validation of Helm Charts 27 | id: chart-validation 28 | uses: kubevious/cli@main 29 | with: 30 | helm_repo_url: https://helm.kubevious.io 31 | helm_repo_name: kubevious 32 | helm_chart: kubevious 33 | helm_namespace: kubevious 34 | helm_include_crds: true 35 | ``` 36 | 37 | -------------------------------------------------------------------------------- /k8s/activate-kubeconfig.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | MY_PATH="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/$(basename "${BASH_SOURCE[0]}")" 3 | MY_DIR="$(dirname $MY_PATH)" 4 | 5 | source ${MY_DIR}/configuration.sh 6 | 7 | ${K8S_DIR}/activate-kubeconfig.sh -------------------------------------------------------------------------------- /k8s/configuration.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | MY_PATH="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/$(basename "${BASH_SOURCE[0]}")" 3 | MY_DIR="$(dirname $MY_PATH)" 4 | 5 | export DEPS_REPO_DIR=${MY_DIR}/../../dependencies.git 6 | export K8S_DIR=${DEPS_REPO_DIR}/k8s 7 | 8 | source ${K8S_DIR}/configuration.sh -------------------------------------------------------------------------------- /k8s/delete.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | MY_PATH="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/$(basename "${BASH_SOURCE[0]}")" 3 | MY_DIR="$(dirname $MY_PATH)" 4 | 5 | source ${MY_DIR}/configuration.sh 6 | 7 | ${K8S_DIR}/delete.sh -------------------------------------------------------------------------------- /k8s/exec-kubectl.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | MY_PATH="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/$(basename "${BASH_SOURCE[0]}")" 3 | MY_DIR="$(dirname $MY_PATH)" 4 | 5 | source ${MY_DIR}/configuration.sh 6 | 7 | ${K8S_DIR}/exec-kubectl.sh $@ -------------------------------------------------------------------------------- /k8s/install-manifests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | MY_PATH="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/$(basename "${BASH_SOURCE[0]}")" 3 | MY_DIR="$(dirname $MY_PATH)" 4 | 5 | source ${MY_DIR}/configuration.sh 6 | 7 | echo "" 8 | echo "*** " 9 | echo "*** Setting Up CLI CRDs" 10 | echo "*** " 11 | 12 | MY_PATH="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/$(basename "${BASH_SOURCE[0]}")" 13 | MY_DIR="$(dirname $MY_PATH)" 14 | 15 | ${K8S_DIR}/exec-kubectl.sh apply -f ${MY_DIR}/../manifests/ 16 | 17 | ${K8S_DIR}/exec-kubectl.sh create namespace pepsi -------------------------------------------------------------------------------- /k8s/recreate.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | MY_PATH="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/$(basename "${BASH_SOURCE[0]}")" 3 | MY_DIR="$(dirname $MY_PATH)" 4 | 5 | 6 | ${MY_DIR}/delete.sh 7 | 8 | ${MY_DIR}/start.sh -------------------------------------------------------------------------------- /k8s/start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | MY_PATH="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/$(basename "${BASH_SOURCE[0]}")" 3 | MY_DIR="$(dirname $MY_PATH)" 4 | 5 | source ${MY_DIR}/configuration.sh 6 | 7 | ${K8S_DIR}/start.sh 8 | 9 | MY_PATH="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/$(basename "${BASH_SOURCE[0]}")" 10 | MY_DIR="$(dirname $MY_PATH)" 11 | ${MY_DIR}/install-manifests.sh -------------------------------------------------------------------------------- /kitchen-sink/command/extract-manifests/index.ts: -------------------------------------------------------------------------------- 1 | import { Command } from 'commander'; 2 | import { logger } from '../../logger'; 3 | 4 | import { ManifetsLoader } from '../../tools/manifests-loader' 5 | import { ExtractManifestsResult }from './types'; 6 | import { output } from './output'; 7 | import { CommandBuilder } from '../../infra/command-action'; 8 | import { ManifestPackage } from '../../tools/manifest-package'; 9 | 10 | export default function (program: Command) 11 | { 12 | program 13 | .command('extract') 14 | .description('Extracts and lists Kubernetes Manifests') 15 | .argument('', 'Path to file, directory, URL, or search pattern') 16 | .action( 17 | 18 | new CommandBuilder() 19 | .perform(async (path) => { 20 | logger.info("path: ", path); 21 | 22 | const loader = new ManifetsLoader(logger); 23 | const manifestPackage = await loader.load(path); 24 | return manifestPackage; 25 | 26 | }) 27 | .format(manifestPackage => { 28 | const result : ExtractManifestsResult = { 29 | manifestPackage: manifestPackage 30 | } 31 | return result; 32 | }) 33 | .output(output) 34 | .build() 35 | 36 | ) 37 | ; 38 | } 39 | -------------------------------------------------------------------------------- /kitchen-sink/command/extract-manifests/output.ts: -------------------------------------------------------------------------------- 1 | import chalk from 'chalk'; 2 | import emoji from 'node-emoji'; 3 | import { logger } from '../../logger'; 4 | import { PackageRenderer } from '../../tools/package-renderer'; 5 | 6 | import { ExtractManifestsResult } from "./types"; 7 | 8 | export function output(result: ExtractManifestsResult) 9 | { 10 | const renderer = new PackageRenderer(logger); 11 | renderer.renderPackageFiles(result.manifestPackage); 12 | renderer.renderPackageFileErrors(result.manifestPackage); 13 | renderer.renderPackageManifests(result.manifestPackage); 14 | } -------------------------------------------------------------------------------- /kitchen-sink/command/extract-manifests/types.ts: -------------------------------------------------------------------------------- 1 | import { ManifestPackage } from '../../tools/manifest-package'; 2 | 3 | export interface ExtractManifestsResult 4 | { 5 | manifestPackage: ManifestPackage 6 | } -------------------------------------------------------------------------------- /kubevious.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | MY_PATH="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/$(basename "${BASH_SOURCE[0]}")" 3 | MY_DIR="$(dirname $MY_PATH)" 4 | 5 | source ${MY_DIR}/configuration.sh 6 | 7 | ${MY_DIR}/build.sh 8 | RESULT=$? 9 | if [ $RESULT -ne 0 ]; then 10 | echo "Build failed" 11 | exit 1; 12 | fi 13 | 14 | export LOG_TO_FILE=false 15 | export LOG_LEVEL=warn 16 | # export NODE_ENV=development 17 | 18 | node ${MY_DIR} $@ 19 | 20 | EXIT_CODE=$? 21 | echo "EXIT CODE: $EXIT_CODE" 22 | exit $EXIT_CODE -------------------------------------------------------------------------------- /package-npm-and-install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | MY_PATH="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/$(basename "${BASH_SOURCE[0]}")" 3 | MY_DIR="$(dirname $MY_PATH)" 4 | cd $MY_DIR 5 | 6 | source configuration.sh 7 | 8 | ./scripts/prepare-package.sh 9 | 10 | npm pack 11 | 12 | npm install -g kubevious-$(node -p -e "require('./package.json').version").tgz -------------------------------------------------------------------------------- /prepare-docker.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | MY_PATH="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/$(basename "${BASH_SOURCE[0]}")" 3 | MY_DIR="$(dirname $MY_PATH)" 4 | cd $MY_DIR 5 | 6 | source configuration.sh 7 | 8 | ./scripts/prepare-package.sh 9 | 10 | docker build \ 11 | -f Dockerfile \ 12 | -t ${IMAGE_NAME} \ 13 | --progress=plain \ 14 | . 15 | 16 | echo "*** RUN WITH:" 17 | echo " $ ./run-docker.sh" 18 | 19 | -------------------------------------------------------------------------------- /prepare.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | MY_PATH="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/$(basename "${BASH_SOURCE[0]}")" 3 | export MY_DIR="$(dirname $MY_PATH)" 4 | 5 | ${MY_DIR}/../workspace.git/kubevious-repo-prepare.sh -------------------------------------------------------------------------------- /run-dev.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | MY_PATH="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/$(basename "${BASH_SOURCE[0]}")" 3 | MY_DIR="$(dirname $MY_PATH)" 4 | 5 | source ${MY_DIR}/configuration.sh 6 | 7 | ${MY_DIR}/build.sh 8 | RESULT=$? 9 | if [ $RESULT -ne 0 ]; then 10 | echo "Build failed" 11 | exit 1; 12 | fi 13 | 14 | export LOG_TO_FILE=true 15 | export NODE_ENV=development 16 | export LOG_LEVEL=info 17 | 18 | node ${MY_DIR} $@ 19 | 20 | EXIT_CODE=$? 21 | echo "EXIT CODE: $EXIT_CODE" 22 | exit $EXIT_CODE -------------------------------------------------------------------------------- /run-docker.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | MY_PATH="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/$(basename "${BASH_SOURCE[0]}")" 3 | MY_DIR="$(dirname $MY_PATH)" 4 | cd $MY_DIR 5 | 6 | source configuration.sh 7 | 8 | docker run -it -v ${MY_DIR}/samples:/src ${IMAGE_NAME} $@ 9 | 10 | RESULT=$? 11 | echo "RESULT: $RESULT" 12 | 13 | # docker run kubevious-cli --help 14 | # docker run kubevious-cli --version 15 | # docker run -v ${PWD}/samples:/src kubevious-cli lint /src 16 | # helm template traefik/traefik | docker run -i kubevious-cli lint 17 | -------------------------------------------------------------------------------- /run-test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | MY_PATH="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/$(basename "${BASH_SOURCE[0]}")" 3 | MY_DIR="$(dirname $MY_PATH)" 4 | cd $MY_DIR 5 | 6 | npm test ${@} -------------------------------------------------------------------------------- /samples/argo-rollout/analysis-template.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: argoproj.io/v1alpha1 2 | kind: AnalysisTemplate 3 | metadata: 4 | name: not-found-percentage 5 | namespace: argo-test 6 | spec: 7 | args: 8 | - name: service-name 9 | metrics: 10 | - name: not-found-percentage 11 | successCondition: result[0] <= 0.10 12 | provider: 13 | prometheus: 14 | address: http://prometheus.monitoring:9090 15 | query: | 16 | avg(rate(app_not_founds_total{kubernetes_namespace="default", kubernetes_name="{{args.service-name}}"}[5m])) / 17 | (avg(rate(app_requests_total{kubernetes_namespace="default", kubernetes_name="{{args.service-name}}"}[5m])) > 0) or 18 | avg(rate(app_requests_total{kubernetes_namespace="default", kubernetes_name="{{args.service-name}}"}[5m])) 19 | -------------------------------------------------------------------------------- /samples/argo-rollout/cluster-analysis-template.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: argoproj.io/v1alpha1 2 | kind: ClusterAnalysisTemplate 3 | metadata: 4 | name: success-rate 5 | spec: 6 | args: 7 | - name: service-name 8 | - name: prometheus-port 9 | value: '9090' 10 | metrics: 11 | - name: success-rate 12 | successCondition: result[0] >= 0.95 13 | provider: 14 | prometheus: 15 | address: "http://prometheus.example.com:{{args.prometheus-port}}" 16 | query: | 17 | sum(irate( 18 | istio_requests_total{reporter="source",destination_service=~"{{args.service-name}}",response_code!~"5.*"}[5m] 19 | )) / 20 | sum(irate( 21 | istio_requests_total{reporter="source",destination_service=~"{{args.service-name}}"}[5m] 22 | )) -------------------------------------------------------------------------------- /samples/argo-rollout/config-map-db.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: db-config 5 | namespace: argo-test 6 | data: 7 | db_host: my_db -------------------------------------------------------------------------------- /samples/argo-rollout/hpa.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: autoscaling/v1 2 | kind: HorizontalPodAutoscaler 3 | metadata: 4 | name: hpa-rollout-example 5 | namespace: argo-test 6 | spec: 7 | maxReplicas: 10 8 | minReplicas: 5 9 | scaleTargetRef: 10 | apiVersion: argoproj.io/v1alpha1 11 | kind: Rollout 12 | name: app-1 13 | targetCPUUtilizationPercentage: 80 -------------------------------------------------------------------------------- /samples/argo-rollout/rollout.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: argoproj.io/v1alpha1 2 | kind: Rollout 3 | metadata: 4 | name: app-1 5 | namespace: argo-test 6 | labels: 7 | app: app-1 8 | spec: 9 | replicas: 5 10 | revisionHistoryLimit: 2 11 | selector: 12 | matchLabels: 13 | app: app-1 14 | strategy: 15 | canary: 16 | steps: 17 | - setWeight: 20 18 | - pause: {duration: 5m} 19 | - analysis: 20 | templates: 21 | - templateName: not-found-percentage 22 | args: 23 | - name: service-name 24 | value: app-1 25 | - analysis: 26 | templates: 27 | - templateName: success-rate 28 | clusterScope: true 29 | args: 30 | - name: service-name 31 | value: guestbook-svc.default.svc.cluster.local 32 | template: 33 | metadata: 34 | labels: 35 | app: app-1 36 | spec: 37 | containers: 38 | - name: node 39 | image: sckmkny/app-1:0.2.0 40 | ports: 41 | - name: node 42 | containerPort: 3000 43 | envFrom: 44 | - configMapRef: 45 | name: db-config 46 | 47 | -------------------------------------------------------------------------------- /samples/argo-rollout/rule-replica-count.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: kubevious.io/v1alpha1 3 | kind: RuleApplicator 4 | metadata: 5 | name: rule-replica-count 6 | namespace: argo-test 7 | spec: 8 | clusterRuleRef: 9 | name: replica-count-check 10 | # disabled: true 11 | values: 12 | minReplicas: 5 13 | maxReplicas: 20 -------------------------------------------------------------------------------- /samples/argo-rollout/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: app-1 5 | namespace: argo-test 6 | labels: 7 | app: app-1 8 | spec: 9 | ports: 10 | - name: http 11 | port: 80 12 | protocol: TCP 13 | targetPort: node 14 | selector: 15 | app: app-1 16 | -------------------------------------------------------------------------------- /samples/bad-json.json: -------------------------------------------------------------------------------- 1 | { 2 | "apiVersion": "v1", 3 | "kind": "Pod", 4 | x "metadata": {}, 5 | "spec": { 6 | "containers": [ 7 | { 8 | "name": "abcd", 9 | "ports": [ 10 | { 11 | "name": "xxx", 12 | "containerPort": 1234, 13 | "protocol": "xxxxx" 14 | }, 15 | { 16 | "name": "xxx" 17 | } 18 | ] 19 | 20 | 21 | } 22 | ] 23 | } 24 | } -------------------------------------------------------------------------------- /samples/bad-yaml.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Servicezz 3 | metadata: 4 | labels: 5 | app: db 6 | name: db 7 | spec: 8 | type: ClusterIP 9 | portish: 10 | - name: "db-service" 11 | port: 5432 12 | targetPort: 5432 13 | selector: 14 | app: db 15 | 16 | -------------------------------------------------------------------------------- /samples/cert-manager.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: cert-manager.io/v1 3 | kind: Certificate 4 | metadata: 5 | name: demo-kubevious-tls-v2 6 | namespace: kubevious 7 | spec: 8 | dnsNames: 9 | - nonpresent.kubevious.io 10 | issuerRef: 11 | kind: ClusterIssuer 12 | name: letsencrypt-v2 13 | group: cert-manager.io 14 | secretName: demo-kubevious-tls 15 | 16 | --- 17 | apiVersion: cert-manager.io/v1 18 | kind: ClusterIssuer 19 | metadata: 20 | name: letsencrypt-v2 21 | spec: 22 | acme: 23 | email: foo@bar.com 24 | server: https://acme-v02.api.letsencrypt.org/directory 25 | solvers: 26 | - http01: 27 | ingress: 28 | class: nginx 29 | preferredChain: '' 30 | privateKeySecretRef: 31 | name: letsencrypt 32 | 33 | 34 | --- 35 | apiVersion: cert-manager.io/v1 36 | kind: Certificate 37 | metadata: 38 | name: demo-kubevious-tls-local 39 | namespace: kubevious 40 | spec: 41 | dnsNames: 42 | - nonpresent.kubevious.io 43 | issuerRef: 44 | name: letsencrypt-local 45 | secretName: demo-kubevious-tls-local 46 | 47 | --- 48 | apiVersion: cert-manager.io/v1 49 | kind: Issuer 50 | metadata: 51 | name: letsencrypt-local 52 | namespace: kubevious 53 | spec: 54 | acme: 55 | email: foo@bar.com 56 | server: https://acme-v02.api.letsencrypt.org/directory 57 | solvers: 58 | - http01: 59 | ingress: 60 | class: nginx 61 | preferredChain: '' 62 | privateKeySecretRef: 63 | name: letsencrypt 64 | -------------------------------------------------------------------------------- /samples/cluster-role.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: role-grantor 5 | rules: 6 | - apiGroups: ["rbac.authorization.k8s.io"] 7 | resources: ["rolebindings"] 8 | verbs: ["create"] 9 | - apiGroups: ["rbac.authorization.k8s.io"] 10 | resources: ["clusterroles"] 11 | verbs: ["bind"] 12 | # omit resourceNames to allow binding any ClusterRole 13 | resourceNames: ["admin","edit","view"] -------------------------------------------------------------------------------- /samples/config-map-1.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: test-config 5 | namespace: ordering 6 | data: 7 | foo: bar -------------------------------------------------------------------------------- /samples/cr-default-ns.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: example.com/v1alpha1 2 | kind: MyPlatform 3 | metadata: 4 | name: test-dotnet-app-2 5 | spec: 6 | appId: testdotnetapp 7 | language: csharp 8 | os: linux 9 | instanceSize: small 10 | environmentType: dev 11 | replicas: 3 12 | -------------------------------------------------------------------------------- /samples/cr-good.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: example.com/v1alpha1 2 | kind: MyPlatform 3 | metadata: 4 | name: test-dotnet-app 5 | namespace: coke 6 | spec: 7 | appId: testdotnetapp 8 | language: csharp 9 | os: linux 10 | instanceSize: small 11 | environmentType: dev 12 | replicas: 3 13 | -------------------------------------------------------------------------------- /samples/cr-invalid.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: example.com/v1alpha1 2 | kind: MyPlatform 3 | metadata: 4 | name: test-dotnet-app 5 | spec: 6 | appId: testdotnetapp 7 | language: csharp 8 | os: linux 9 | instanceSize: small 10 | environmentType: development 11 | replicas: 3 12 | -------------------------------------------------------------------------------- /samples/cr-unknown.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: example.com/v1 2 | kind: MyResource 3 | metadata: 4 | name: test-dotnet-app 5 | spec: 6 | appId: testdotnetapp 7 | language: csharp 8 | os: linux 9 | instanceSize: small 10 | environmentType: development 11 | replicas: 3 12 | -------------------------------------------------------------------------------- /samples/crd-invalid.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apiextensions.k8s.io/v1 2 | kind: CustomResourceDefinition 3 | metadata: 4 | # name must match the spec fields below, and be in the form: . 5 | name: myplatformanothers.example.com 6 | spec: 7 | # group name to use for REST API: /apis// 8 | group: example.com 9 | names: 10 | # plural name to be used in the URL: /apis/// 11 | plural: myplatformanothers 12 | # singular name to be used as an alias on the CLI and for display 13 | singular: myplatformanother 14 | # kind is normally the CamelCased singular type. Your resource manifests use this. 15 | kind: MyPlatformAnother 16 | # shortNames allow shorter string to match your resource on the CLI 17 | shortNames: 18 | - myp 19 | # either Namespaced or Cluster 20 | scope: Namespaced 21 | versions: 22 | - name: v1alpha1 23 | # Each version can be enabled/disabled by Served flag. 24 | served: true 25 | # One and only one version must be marked as the storage version. 26 | storage: true 27 | schema: 28 | openAPIV3Schema: 29 | type: object 30 | properties: 31 | spec: 32 | type: object 33 | properties: 34 | appId: 35 | type: not-a-good-type 36 | language: 37 | type: string 38 | enum: 39 | - csharp 40 | - python 41 | - go 42 | os: 43 | type: string 44 | enum: 45 | - windows 46 | - linux 47 | instanceSize: 48 | type: string 49 | enum: 50 | - small 51 | - medium 52 | - large 53 | environmentType: 54 | type: string 55 | enum: 56 | - dev 57 | - test 58 | - prod 59 | replicas: 60 | type: integer 61 | minimum: 1 62 | required: ["appId", "language", "environmentType"] 63 | required: ["spec"] 64 | -------------------------------------------------------------------------------- /samples/crd.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apiextensions.k8s.io/v1 2 | kind: CustomResourceDefinition 3 | metadata: 4 | # name must match the spec fields below, and be in the form: . 5 | name: myplatforms.example.com 6 | spec: 7 | # group name to use for REST API: /apis// 8 | group: example.com 9 | names: 10 | # plural name to be used in the URL: /apis/// 11 | plural: myplatforms 12 | # singular name to be used as an alias on the CLI and for display 13 | singular: myplatform 14 | # kind is normally the CamelCased singular type. Your resource manifests use this. 15 | kind: MyPlatform 16 | # shortNames allow shorter string to match your resource on the CLI 17 | shortNames: 18 | - myp 19 | # either Namespaced or Cluster 20 | scope: Namespaced 21 | versions: 22 | - name: v1alpha1 23 | # Each version can be enabled/disabled by Served flag. 24 | served: true 25 | # One and only one version must be marked as the storage version. 26 | storage: true 27 | schema: 28 | openAPIV3Schema: 29 | type: object 30 | properties: 31 | spec: 32 | type: object 33 | properties: 34 | appId: 35 | type: string 36 | language: 37 | type: string 38 | enum: 39 | - csharp 40 | - python 41 | - go 42 | os: 43 | type: string 44 | enum: 45 | - windows 46 | - linux 47 | instanceSize: 48 | type: string 49 | enum: 50 | - small 51 | - medium 52 | - large 53 | environmentType: 54 | type: string 55 | enum: 56 | - dev 57 | - test 58 | - prod 59 | replicas: 60 | type: integer 61 | minimum: 1 62 | required: ["appId", "language", "environmentType"] 63 | required: ["spec"] 64 | -------------------------------------------------------------------------------- /samples/deployment-2.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: orderservice 5 | namespace: ordering 6 | spec: 7 | replicas: 2 8 | selector: 9 | matchLabels: 10 | app: orderservice 11 | template: 12 | metadata: 13 | labels: 14 | app: orderservice 15 | spec: 16 | serviceAccountName: default 17 | terminationGracePeriodSeconds: 5 18 | containers: 19 | - name: server 20 | image: gcr.io/google-samples/microservices-demo/orderservice:v0.3.6 21 | ports: 22 | - containerPort: 8080 23 | env: 24 | - name: PORT 25 | value: "8080" 26 | - name: DISABLE_TRACING 27 | value: "1" 28 | - name: DISABLE_PROFILER 29 | value: "1" 30 | readinessProbe: 31 | periodSeconds: 5 32 | exec: 33 | command: ["/bin/grpc_health_probe", "-addr=:8080"] 34 | livenessProbe: 35 | periodSeconds: 5 36 | exec: 37 | command: ["/bin/grpc_health_probe", "-addr=:8080"] 38 | resources: 39 | requests: 40 | cpu: 10m 41 | memory: 64Mi 42 | limits: 43 | cpu: 200m 44 | memory: 128Mi -------------------------------------------------------------------------------- /samples/deployment-3.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: new-service 5 | namespace: ordering 6 | spec: 7 | replicas: 2 8 | selector: 9 | matchLabels: 10 | app: orderservice 11 | template: 12 | metadata: 13 | labels: 14 | app: orderservice 15 | spec: 16 | serviceAccountName: default 17 | terminationGracePeriodSeconds: 5 18 | containers: 19 | - name: server 20 | image: gcr.io/google-samples/microservices-demo/orderservice:v0.3.6 21 | ports: 22 | - containerPort: 8080 23 | env: 24 | - name: PORT 25 | value: "8080" 26 | - name: DISABLE_TRACING 27 | value: "1" 28 | - name: DISABLE_PROFILER 29 | value: "1" 30 | - name: MISSING_CONFIG_MAP_REF 31 | valueFrom: 32 | configMapKeyRef: 33 | name: some-missing-config 34 | key: foo 35 | - name: MISSING_CONFIG_MAP_KEY_REF 36 | valueFrom: 37 | configMapKeyRef: 38 | name: test-config 39 | key: bar 40 | - name: GOOD_CONFIG_MAP_KEY_REF 41 | valueFrom: 42 | configMapKeyRef: 43 | name: test-config 44 | key: foo 45 | - name: OPTIONAL_MISSING_CONFIG_MAP_REF 46 | valueFrom: 47 | configMapKeyRef: 48 | name: some-missing-config 49 | key: foo 50 | optional: true 51 | - name: OPTIONAL_MISSING_CONFIG_MAP_KEY_REF 52 | valueFrom: 53 | configMapKeyRef: 54 | name: test-config 55 | key: bar 56 | optional: true 57 | - name: OPTIONAL_GOOD_CONFIG_MAP_KEY_REF 58 | valueFrom: 59 | configMapKeyRef: 60 | name: test-config 61 | key: foo 62 | optional: true 63 | envFrom: 64 | - configMapRef: 65 | name: test-config 66 | - configMapRef: 67 | name: some-missing-config 68 | readinessProbe: 69 | periodSeconds: 5 70 | exec: 71 | command: ["/bin/grpc_health_probe", "-addr=:8080"] 72 | livenessProbe: 73 | periodSeconds: 5 74 | exec: 75 | command: ["/bin/grpc_health_probe", "-addr=:8080"] 76 | resources: 77 | requests: 78 | cpu: 10m 79 | memory: 64Mi 80 | limits: 81 | cpu: 200m 82 | memory: 128Mi -------------------------------------------------------------------------------- /samples/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: emailservice 5 | spec: 6 | replicas: 1 7 | selector: 8 | matchLabels: 9 | app: emailservice 10 | template: 11 | metadata: 12 | labels: 13 | app: emailservice 14 | spec: 15 | serviceAccountName: default 16 | terminationGracePeriodSeconds: 5 17 | containers: 18 | - name: server 19 | image: gcr.io/google-samples/microservices-demo/emailservice:v0.3.6 20 | ports: 21 | - containerPort: 8080 22 | env: 23 | - name: PORT 24 | value: "8080" 25 | - name: DISABLE_TRACING 26 | value: "1" 27 | - name: DISABLE_PROFILER 28 | value: "1" 29 | readinessProbe: 30 | periodSeconds: 5 31 | exec: 32 | command: ["/bin/grpc_health_probe", "-addr=:8080"] 33 | livenessProbe: 34 | periodSeconds: 5 35 | exec: 36 | command: ["/bin/grpc_health_probe", "-addr=:8080"] 37 | resources: 38 | requests: 39 | cpu: 10m 40 | memory: 64Mi 41 | limits: 42 | cpu: 200m 43 | memory: 128Mi -------------------------------------------------------------------------------- /samples/empty.yaml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kubevious/cli/880eca26397f94e9b6c7d848968f50a56baebafc/samples/empty.yaml -------------------------------------------------------------------------------- /samples/hpa.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: autoscaling/v2 2 | kind: HorizontalPodAutoscaler 3 | metadata: 4 | name: orderservice 5 | namespace: ordering 6 | spec: 7 | scaleTargetRef: 8 | apiVersion: apps/v1 9 | kind: Deployment 10 | name: orderservice 11 | minReplicas: 2 12 | maxReplicas: 10 13 | metrics: 14 | - type: Resource 15 | resource: 16 | name: cpu 17 | target: 18 | type: Utilization 19 | averageUtilization: 50 -------------------------------------------------------------------------------- /samples/ingress-class/kong.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: networking.k8s.io/v1 2 | kind: IngressClass 3 | metadata: 4 | annotations: 5 | ingressclass.kubernetes.io/is-default-class: "true" 6 | labels: 7 | app.kubernetes.io/component: controller 8 | app.kubernetes.io/instance: ingress-kong 9 | app.kubernetes.io/name: ingress-kong 10 | app.kubernetes.io/part-of: ingress-kong 11 | app.kubernetes.io/version: 1.5.1 12 | name: kong 13 | spec: 14 | controller: k8s.io/ingress-kong 15 | 16 | -------------------------------------------------------------------------------- /samples/ingress-class/nginx.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: networking.k8s.io/v1 2 | kind: IngressClass 3 | metadata: 4 | annotations: 5 | ingressclass.kubernetes.io/is-default-class: "true" 6 | labels: 7 | app.kubernetes.io/component: controller 8 | app.kubernetes.io/instance: ingress-nginx 9 | app.kubernetes.io/name: ingress-nginx 10 | app.kubernetes.io/part-of: ingress-nginx 11 | app.kubernetes.io/version: 1.5.1 12 | name: nginx 13 | spec: 14 | controller: k8s.io/ingress-nginx 15 | 16 | -------------------------------------------------------------------------------- /samples/invalid-service-1.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | labels: 5 | app: emailservice 6 | name: emailservice-1 7 | spec: 8 | type: ClusterIP 9 | ports: 10 | - name: "my-port-is-not-set" 11 | targetPort: 5432 12 | selector: 13 | app: emailservice 14 | 15 | -------------------------------------------------------------------------------- /samples/invalid-service-2.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | labels: 5 | app: db 6 | name: db-2 7 | spec: 8 | type: ClusterIP 9 | portish: 10 | - name: "db-service" 11 | port: 5432 12 | targetPort: 5432 13 | selector: 14 | app: db 15 | 16 | -------------------------------------------------------------------------------- /samples/invalid-service-3.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | labels: 5 | app: db 6 | name: db-3 7 | spec: 8 | type: ClusterIP 9 | ports: 10 | - name: "db-service" 11 | port: 5432 12 | targetPort: 5432 13 | protocol: ZTCP 14 | selector: 15 | app: db 16 | 17 | -------------------------------------------------------------------------------- /samples/invalid-service-4.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | labels: 5 | app: db 6 | name: db-4 7 | spec: 8 | type: ClusterIP 9 | ports: 10 | - name: "db-service" 11 | port: "5432" 12 | targetPort: 5432 13 | protocol: TCP 14 | selector: 15 | app: db 16 | 17 | -------------------------------------------------------------------------------- /samples/invalid-service-5.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | labels: 5 | app: db 6 | name: db-5 7 | spec: 8 | type: ClusterIP 9 | ports: 10 | - name: true 11 | port: 5432 12 | targetPort: 5432 13 | protocol: TCP 14 | selector: 15 | app: db 16 | 17 | -------------------------------------------------------------------------------- /samples/invalid-service-6.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | labels: 5 | app: db 6 | name: db-6 7 | spec: 8 | type: ClusterIP 9 | ports: [] 10 | selector: 11 | app: db -------------------------------------------------------------------------------- /samples/istio-gateway.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: networking.istio.io/v1alpha3 2 | kind: Gateway 3 | metadata: 4 | name: frontend-gateway 5 | namespace: hipster 6 | labels: 7 | app: hipster 8 | spec: 9 | selector: 10 | istio: ingressgateway # use Istio default gateway implementation 11 | servers: 12 | - port: 13 | number: 80 14 | name: http 15 | protocol: HTTP 16 | hosts: 17 | - "*" -------------------------------------------------------------------------------- /samples/istio-test.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apps/v1 3 | kind: Deployment 4 | metadata: 5 | name: backend-api 6 | spec: 7 | replicas: 1 8 | selector: 9 | matchLabels: 10 | app: backend-api 11 | template: 12 | metadata: 13 | labels: 14 | image: dev/backend-api:v1 15 | ports: 16 | - containerPort: 8000 17 | imagePullPolicy: Always 18 | envFrom: 19 | - secretRef: 20 | name: backend-api 21 | imagePullSecrets: 22 | - name: regcred 23 | --- 24 | 25 | apiVersion: networking.istio.io/v1alpha3 26 | kind: VirtualService 27 | metadata: 28 | name: backend-api 29 | spec: 30 | hosts: 31 | - backend-api 32 | http: 33 | - route: 34 | - destination: 35 | host: backend-api 36 | port: 37 | number: 80 38 | --- 39 | apiVersion: v1 40 | kind: Service 41 | metadata: 42 | name: backend-api 43 | spec: 44 | ports: 45 | - port: 80 46 | targetPort: 8000 47 | selector: 48 | app: backend-api 49 | 50 | --- 51 | apiVersion: networking.istio.io/v1alpha3 52 | kind: Gateway 53 | metadata: 54 | name: generic-gateway 55 | spec: 56 | selector: 57 | istio: ingressgateway # use Istio default gateway implementation 58 | servers: 59 | - port: 60 | number: 80 61 | name: http 62 | protocol: HTTP 63 | hosts: 64 | - "example.com" 65 | - "example.io" 66 | --- 67 | apiVersion: networking.istio.io/v1alpha3 68 | kind: Gateway 69 | metadata: 70 | name: generic-gateway 71 | spec: 72 | selector: 73 | istio: ingressgateway # use Istio default gateway implementation 74 | servers: 75 | - port: 76 | number: 80 77 | name: http 78 | protocol: HTTP 79 | hosts: 80 | - "example.com" 81 | - "example.io" 82 | 83 | --- 84 | apiVersion: networking.istio.io/v1alpha3 85 | kind: VirtualService 86 | metadata: 87 | name: frontend 88 | spec: 89 | hosts: 90 | - "example.io" 91 | gateways: 92 | - generic-gateway 93 | http: 94 | - route: 95 | - destination: 96 | host: frontend 97 | --- 98 | apiVersion: v1 99 | kind: Service 100 | metadata: 101 | name: frontend 102 | spec: 103 | ports: 104 | - port: 80 105 | targetPort: 3000 106 | selector: 107 | app: frontend -------------------------------------------------------------------------------- /samples/multiple-manifests.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Service 4 | metadata: 5 | name: emailservice 6 | spec: 7 | type: ClusterIP 8 | selector: 9 | app: emailservice 10 | ports: 11 | - name: grpc 12 | port: 5000 13 | targetPort: 8080 14 | --- 15 | apiVersion: apps/v1 16 | kind: Deployment 17 | metadata: 18 | name: checkoutservice 19 | spec: 20 | selector: 21 | matchLabels: 22 | app: checkoutservice 23 | template: 24 | metadata: 25 | labels: 26 | app: checkoutservice 27 | spec: 28 | serviceAccountName: default 29 | containers: 30 | - name: server 31 | image: gcr.io/google-samples/microservices-demo/checkoutservice:v0.3.6 32 | ports: 33 | - containerPort: 5050 34 | readinessProbe: 35 | exec: 36 | command: ["/bin/grpc_health_probe", "-addr=:5050"] 37 | livenessProbe: 38 | exec: 39 | command: ["/bin/grpc_health_probe", "-addr=:5050"] 40 | env: 41 | - name: PORT 42 | value: "5050" 43 | - name: PRODUCT_CATALOG_SERVICE_ADDR 44 | value: "productcatalogservice:3550" 45 | - name: SHIPPING_SERVICE_ADDR 46 | value: "shippingservice:50051" 47 | - name: PAYMENT_SERVICE_ADDR 48 | value: "paymentservice:50051" 49 | - name: EMAIL_SERVICE_ADDR 50 | value: "emailservice:5000" 51 | - name: CURRENCY_SERVICE_ADDR 52 | value: "currencyservice:7000" 53 | - name: CART_SERVICE_ADDR 54 | value: "cartservice:7070" 55 | - name: DISABLE_STATS 56 | value: "1" 57 | - name: DISABLE_TRACING 58 | value: "1" 59 | - name: DISABLE_PROFILER 60 | value: "1" 61 | # - name: JAEGER_SERVICE_ADDR 62 | # value: "jaeger-collector:14268" 63 | resources: 64 | requests: 65 | cpu: 50m 66 | memory: 64Mi 67 | limits: 68 | cpu: 200m 69 | memory: 128Mi 70 | --- 71 | apiVersion: v1 72 | kind: Service 73 | metadata: 74 | name: checkoutservice 75 | spec: 76 | type: ClusterIP 77 | selector: 78 | app: checkoutservice 79 | ports: 80 | - name: grpc 81 | port: 5050 82 | targetPort: 5050 -------------------------------------------------------------------------------- /samples/network-policy.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: networking.k8s.io/v1 2 | kind: NetworkPolicy 3 | metadata: 4 | name: adservice 5 | spec: 6 | podSelector: 7 | matchLabels: 8 | app: adservice 9 | policyTypes: 10 | - Ingress 11 | - Egress 12 | ingress: 13 | - from: 14 | - podSelector: 15 | matchLabels: 16 | app: frontend 17 | ports: 18 | - port: 9555 19 | protocol: TCP 20 | egress: 21 | - {} -------------------------------------------------------------------------------- /samples/payload-pod.json: -------------------------------------------------------------------------------- 1 | { 2 | "apiVersion": "v1", 3 | "kind": "Pod", 4 | "metadata": {}, 5 | "spec": { 6 | "containers": [ 7 | { 8 | "name": "abcd", 9 | "ports": [ 10 | { 11 | "name": "xxx", 12 | "containerPort": 1234, 13 | "protocol": "xxxxx" 14 | }, 15 | { 16 | "name": "xxx" 17 | } 18 | ] 19 | 20 | 21 | } 22 | ] 23 | } 24 | } -------------------------------------------------------------------------------- /samples/payload-service.json: -------------------------------------------------------------------------------- 1 | { 2 | "apiVersion": "v1", 3 | "kind": "Service", 4 | "metadata": {}, 5 | "spec": { 6 | "ports": [ 7 | { 8 | "name": "xxx", 9 | "targetPort": "true" 10 | }, 11 | { 12 | "name": "yyy", 13 | "targetPort": 123 14 | }, 15 | { 16 | "name": "yyy", 17 | "targetPort": "true" 18 | } 19 | ] 20 | } 21 | } -------------------------------------------------------------------------------- /samples/pepsi/cert-manager.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: cert-manager.io/v1 3 | kind: Certificate 4 | metadata: 5 | name: demo-pepsi 6 | namespace: pepsi 7 | spec: 8 | dnsNames: 9 | - nonpresent.kubevious.io 10 | issuerRef: 11 | kind: ClusterIssuer 12 | name: pepsi-issuer 13 | group: cert-manager.io 14 | secretName: demo-kubevious-tls 15 | 16 | --- 17 | apiVersion: cert-manager.io/v1 18 | kind: ClusterIssuer 19 | metadata: 20 | name: pepsi-issuer 21 | spec: 22 | acme: 23 | email: foo@bar.com 24 | server: https://acme-v02.api.letsencrypt.org/directory 25 | solvers: 26 | - http01: 27 | ingress: 28 | class: nginx 29 | preferredChain: '' 30 | privateKeySecretRef: 31 | name: letsencrypt 32 | -------------------------------------------------------------------------------- /samples/pepsi/certificate-check-applicator-pepsi.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: kubevious.io/v1alpha1 3 | kind: RuleApplicator 4 | metadata: 5 | name: certificate-check-applicator 6 | namespace: pepsi 7 | spec: 8 | clusterRuleRef: 9 | name: cert-manager.certificate-check 10 | # disabled: true 11 | values: 12 | domain: pepsi.com 13 | -------------------------------------------------------------------------------- /samples/pepsi/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: emailservice 5 | namespace: pepsi 6 | spec: 7 | replicas: 7 8 | selector: 9 | matchLabels: 10 | app: emailservice 11 | template: 12 | metadata: 13 | labels: 14 | app: emailservice 15 | spec: 16 | serviceAccountName: default 17 | terminationGracePeriodSeconds: 5 18 | containers: 19 | - name: server 20 | image: gcr.io/google-samples/microservices-demo/emailservice:1234 21 | ports: 22 | - containerPort: 8080 23 | env: 24 | - name: PORT 25 | value: "8080" 26 | - name: DISABLE_TRACING 27 | value: "1" 28 | - name: DISABLE_PROFILER 29 | value: "1" 30 | readinessProbe: 31 | periodSeconds: 5 32 | exec: 33 | command: ["/bin/grpc_health_probe", "-addr=:8080"] 34 | livenessProbe: 35 | periodSeconds: 5 36 | exec: 37 | command: ["/bin/grpc_health_probe", "-addr=:8080"] 38 | resources: 39 | requests: 40 | cpu: 10m 41 | memory: 64Mi 42 | limits: 43 | cpu: 200m 44 | memory: 128Mi -------------------------------------------------------------------------------- /samples/pepsi/hpa.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: autoscaling/v2 2 | kind: HorizontalPodAutoscaler 3 | metadata: 4 | name: emailservice 5 | namespace: pepsi 6 | spec: 7 | scaleTargetRef: 8 | apiVersion: apps/v1 9 | kind: Deployment 10 | name: emailservice 11 | minReplicas: 5 12 | maxReplicas: 15 13 | metrics: 14 | - type: Resource 15 | resource: 16 | name: cpu 17 | target: 18 | type: Utilization 19 | averageUtilization: 50 20 | - type: ContainerResource 21 | containerResource: 22 | name: cpu 23 | container: server 24 | target: 25 | type: Utilization 26 | averageUtilization: 60 -------------------------------------------------------------------------------- /samples/pepsi/ingress.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: networking.k8s.io/v1 2 | kind: Ingress 3 | metadata: 4 | name: emailservice 5 | namespace: pepsi 6 | spec: 7 | tls: 8 | - hosts: 9 | - cafe.example.com 10 | secretName: cafe-secret 11 | rules: 12 | - host: cafe.example.com 13 | http: 14 | paths: 15 | - path: / 16 | pathType: Prefix 17 | backend: 18 | service: 19 | name: emailservice 20 | port: 21 | number: 80 -------------------------------------------------------------------------------- /samples/pepsi/rule-replica-count.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: kubevious.io/v1alpha1 3 | kind: RuleApplicator 4 | metadata: 5 | name: rule-replica-count 6 | namespace: pepsi 7 | spec: 8 | clusterRuleRef: 9 | name: replica-count-check 10 | # disabled: true 11 | values: 12 | minReplicas: 5 13 | maxReplicas: 20 -------------------------------------------------------------------------------- /samples/pepsi/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | labels: 5 | app: emailservice 6 | name: emailservice 7 | namespace: pepsi 8 | spec: 9 | type: ClusterIP 10 | ports: 11 | - name: http 12 | port: 80 13 | targetPort: 8080 14 | selector: 15 | app: emailservice -------------------------------------------------------------------------------- /samples/pod.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: nginx 5 | namespace: ordering 6 | spec: 7 | containers: 8 | - name: nginx 9 | image: nginx:1.14.2 10 | ports: 11 | - containerPort: 80 -------------------------------------------------------------------------------- /samples/pods.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Pod 4 | metadata: 5 | name: nginx 6 | spec: 7 | containers: 8 | - name: nginx 9 | image: nginx:1.14.2 10 | ports: 11 | - containerPort: 80 12 | 13 | --- 14 | apiVersion: v1 15 | kind: Pod 16 | metadata: 17 | name: nginx 18 | spec: 19 | containers: 20 | - name: nginx 21 | image: nginx:1.14.2 22 | ports: 23 | - containerPort: 80 -------------------------------------------------------------------------------- /samples/rules/bad-rule.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kubevious.io/v1alpha1 2 | kind: Rule 3 | metadata: 4 | name: bad-rule 5 | spec: 6 | target: | 7 | ApiVersion('cert-manager.io/v1') 8 | .Kind('Certificate') 9 | xxx 10 | rule: | 11 | ddd 12 | const issuer = ApiVersion('cert-manager.io/v1') 13 | .Kind(config.spec?.issuerRef?.kind) 14 | .name(config.spec?.issuerRef?.name) 15 | .single(); 16 | if (!issuer) { 17 | error('Could not find the Certificate Issuer'); 18 | } else { 19 | const email = issuer.config.spec?.acme?.email ?? ""; 20 | if (!email.endsWith('example.com')) { 21 | error(`Using not approved email: ${email}`); 22 | } 23 | } -------------------------------------------------------------------------------- /samples/rules/certificate-check-applicator-kubevious.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: kubevious.io/v1alpha1 3 | kind: RuleApplicator 4 | metadata: 5 | name: certificate-check-applicator 6 | namespace: kubevious 7 | spec: 8 | clusterRuleRef: 9 | name: cert-manager.certificate-check 10 | # disabled: true 11 | values: 12 | domain: kubevious.io 13 | -------------------------------------------------------------------------------- /samples/rules/certificate-check.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kubevious.io/v1alpha1 2 | kind: ClusterRule 3 | metadata: 4 | name: cert-manager.certificate-check 5 | spec: 6 | values: 7 | domain: example.com 8 | target: | 9 | ApiVersion('cert-manager.io/v1') 10 | .Kind('Certificate') 11 | rule: | 12 | const issuerRef = config.spec?.issuerRef; 13 | if (!issuerRef) { 14 | error('issuerRef is not set'); 15 | return; 16 | } 17 | const issuerRefKind = issuerRef.kind ?? 'Issuer'; 18 | const issuerRefName = config.spec?.issuerRef?.name; 19 | if (!issuerRefName) { 20 | error('issuerRef.name is not set.'); 21 | return; 22 | } 23 | const issuer = ApiVersion('cert-manager.io/v1') 24 | .Kind(issuerRefKind) 25 | .name(issuerRefName) 26 | .isClusterScope(issuerRefKind.startsWith('Cluster')) 27 | .single(); 28 | if (!issuer) { 29 | error('Could not find the Certificate Issuer'); 30 | } else { 31 | const email = issuer.config.spec?.acme?.email ?? ""; 32 | if (!email.endsWith(values.domain)) { 33 | error(`Using not approved email: ${email}. Should be from ${values.domain} domain.`); 34 | } 35 | } 36 | application: 37 | useApplicator: true 38 | # onlySelectedNamespaces: true 39 | # namespaces: 40 | # - name: kubevious 41 | # values: 42 | # domain: kubevious.io 43 | # - name: pepsi 44 | # - name: foo 45 | # disabled: true 46 | -------------------------------------------------------------------------------- /samples/rules/cluster-role.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kubevious.io/v1alpha1 2 | kind: ClusterRule 3 | metadata: 4 | name: cluster-role-check 5 | spec: 6 | target: | 7 | ApiVersion('rbac.authorization.k8s.io/v1') 8 | .Kind('ClusterRole') 9 | rule: | 10 | 11 | application: 12 | clustered: true -------------------------------------------------------------------------------- /samples/rules/env-from.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kubevious.io/v1alpha1 2 | kind: ClusterRule 3 | metadata: 4 | name: deployment-env-from-check 5 | spec: 6 | target: | 7 | Shortcut("PodSpec") 8 | cache: | 9 | cache.configMaps = []; 10 | 11 | for(const configMap of ApiVersion('v1') 12 | .Kind("ConfigMap") 13 | .many()) 14 | { 15 | cache.configMaps[configMap.name] = true; 16 | } 17 | rule: | 18 | for(const container of config.spec?.containers ?? []) 19 | { 20 | for(const envFrom of container.envFrom ?? []) 21 | { 22 | if (envFrom.configMapRef) 23 | { 24 | if (!cache.configMaps[envFrom.configMapRef.name]) 25 | { 26 | if (!envFrom.configMapRef.optional) 27 | { 28 | error(`Could not find ConfigMap ${envFrom.configMapRef.name}`); 29 | } 30 | } 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /samples/rules/latest-image.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kubevious.io/v1alpha1 2 | kind: ClusterRule 3 | metadata: 4 | name: container-image-check 5 | spec: 6 | target: | 7 | Shortcut("ContainerSpec") 8 | rule: | 9 | if (helpers.parseImage(config.spec.image).tag === 'latest') { 10 | error(`Latest Image Tags not allowed: ${config.spec.image}`); 11 | } 12 | -------------------------------------------------------------------------------- /samples/rules/rule-istio-gateway-host-check.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kubevious.io/v1alpha1 2 | kind: ClusterRule 3 | metadata: 4 | name: istio-gateway-host-check 5 | spec: 6 | target: | 7 | ApiVersion('networking.istio.io/v1beta1') 8 | .Kind('VirtualService') 9 | .label('app', 'hipster') 10 | rule: | 11 | for(let gatewayName of (config.spec?.gateways || [])) 12 | { 13 | let gateway = 14 | ApiVersion('networking.istio.io/v1beta1') 15 | .Kind('Gateway') 16 | .name(gatewayName) 17 | .single(); 18 | 19 | if (!gateway) { 20 | error(`Did not find Gateway ${gatewayName}`); 21 | } else { 22 | // warning(`Found Gateway ${gatewayName}`); 23 | 24 | for(const gatewayServer of (gateway.config.spec?.servers || [])) 25 | { 26 | for(const host of gatewayServer.hosts || []) 27 | { 28 | if (host == "*") { 29 | error("Wildcard hosts are not allowed!"); 30 | } 31 | } 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /samples/rules/service-selector-cached.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kubevious.io/v1alpha1 2 | kind: ClusterRule 3 | metadata: 4 | name: service-selector-v2 5 | spec: 6 | target: | 7 | ApiVersion('v1') 8 | .Kind('Service') 9 | cache: | 10 | cache.apps = helpers.newLabelLookupDict(); 11 | 12 | for(const app of ApiVersion('apps/v1') 13 | .Kind("Deployment") 14 | .many()) 15 | { 16 | cache.apps.add(app, app.config.spec?.template?.metadata?.labels); 17 | } 18 | rule: | 19 | if (!config.spec.selector) { 20 | return; 21 | } 22 | 23 | if (cache.apps.resolveSelector(config.spec.selector).length == 0) 24 | { 25 | error(`Could not find Applications for Service. Selector: ${helpers.labelsToString(config.spec.selector)}`); 26 | } -------------------------------------------------------------------------------- /samples/rules/service-selector-check.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kubevious.io/v1alpha1 2 | kind: ClusterRule 3 | metadata: 4 | name: service-selector-check 5 | spec: 6 | target: | 7 | ApiVersion('v1') 8 | .Kind('Service') 9 | rule: | 10 | const apps = ApiVersion('apps/v1') 11 | .Kind("Deployment") 12 | .many(); 13 | 14 | const myApps = []; 15 | for(const app of apps) 16 | { 17 | if (matchesDict(config.spec.selector ?? {}, app.config.spec.template.metadata.labels ?? {})) 18 | { 19 | myApps.push(app); 20 | } 21 | } 22 | if (myApps.length === 0) { 23 | error("Could not find Applications for Service"); 24 | } 25 | 26 | function matchesDict(selector, labels) 27 | { 28 | for(const key of Object.keys(selector)) 29 | { 30 | if (selector[key] !== labels[key]) 31 | { 32 | return false; 33 | } 34 | } 35 | return true; 36 | } -------------------------------------------------------------------------------- /samples/rules/service-selector-transform.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kubevious.io/v1alpha1 2 | kind: ClusterRule 3 | metadata: 4 | name: service-selector-v2 5 | spec: 6 | target: | 7 | Union( 8 | ApiVersion('v1') 9 | .Kind('Service') 10 | ) 11 | cache: | 12 | cache.apps = []; 13 | 14 | const items = 15 | Union( 16 | Transform( 17 | ApiVersion('apps/v1') 18 | .Kind("Deployment") 19 | ).To(item => { 20 | return { 21 | synthetic: true, 22 | apiVersion: 'v1', 23 | kind: PodSpec, 24 | metadata: config.spec?.template?.metadata, 25 | spec: config.spec?.template?.spec 26 | } 27 | }), 28 | ) 29 | .many() 30 | ; 31 | 32 | for(const app of items) 33 | { 34 | cache.apps.push({ 35 | kind: app.kind, 36 | name: app.name, 37 | labels: app.config.spec.template.metadata.labels ?? {} 38 | }); 39 | } 40 | rule: | 41 | if (!config.spec.selector) { 42 | return; 43 | } 44 | 45 | for(const app of cache.apps) 46 | { 47 | if (matchesDict(config.spec.selector, app.labels)) 48 | { 49 | return; 50 | } 51 | } 52 | 53 | error("Could not find Applications for Service"); 54 | 55 | function matchesDict(selector, labels) 56 | { 57 | for(const key of Object.keys(selector)) 58 | { 59 | if (selector[key] !== labels[key]) 60 | { 61 | return false; 62 | } 63 | } 64 | return true; 65 | } -------------------------------------------------------------------------------- /samples/rules/service-selector-union.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kubevious.io/v1alpha1 2 | kind: ClusterRule 3 | metadata: 4 | name: service-selector-v2 5 | spec: 6 | target: | 7 | Union( 8 | ApiVersion('v1') 9 | .Kind('Service') 10 | ) 11 | cache: | 12 | cache.apps = []; 13 | 14 | const items = 15 | Union( 16 | ApiVersion('apps/v1') 17 | .Kind("Deployment") 18 | ) 19 | .many() 20 | ; 21 | 22 | for(const app of items) 23 | { 24 | cache.apps.push({ 25 | kind: app.kind, 26 | name: app.name, 27 | labels: app.config.spec.template.metadata.labels ?? {} 28 | }); 29 | } 30 | rule: | 31 | if (!config.spec.selector) { 32 | return; 33 | } 34 | 35 | for(const app of cache.apps) 36 | { 37 | if (matchesDict(config.spec.selector, app.labels)) 38 | { 39 | return; 40 | } 41 | } 42 | 43 | error("Could not find Applications for Service"); 44 | 45 | function matchesDict(selector, labels) 46 | { 47 | for(const key of Object.keys(selector)) 48 | { 49 | if (selector[key] !== labels[key]) 50 | { 51 | return false; 52 | } 53 | } 54 | return true; 55 | } -------------------------------------------------------------------------------- /samples/rules/test-rule.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kubevious.io/v1alpha1 2 | kind: Rule 3 | metadata: 4 | name: bad-rule 5 | spec: 6 | target: | 7 | ApiVersion('cert-manager.io/v1') 8 | .Kind('Certificate') 9 | xxx 10 | rule: | 11 | ddd 12 | const issuer = ApiVersion('cert-manager.io/v1') 13 | .Kind(config.spec?.issuerRef?.kind) 14 | .name(config.spec?.issuerRef?.name) 15 | .single(); 16 | if (!issuer) { 17 | error('Could not find the Certificate Issuer'); 18 | } else { 19 | const email = issuer.config.spec?.acme?.email ?? ""; 20 | if (!email.endsWith('example.com')) { 21 | error(`Using not approved email: ${email}`); 22 | } 23 | } 24 | values: 25 | foo: bar 26 | notFoo: 123 27 | # complexFoo: 28 | # innerFoo: innerBar -------------------------------------------------------------------------------- /samples/sealed-secret/secret-ref-test.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apps/v1 3 | kind: Deployment 4 | metadata: 5 | name: backend-api 6 | spec: 7 | replicas: 1 8 | selector: 9 | matchLabels: 10 | app: backend-api 11 | template: 12 | metadata: 13 | labels: 14 | image: dev/backend-api:v1 15 | spec: 16 | containers: 17 | - name: web 18 | image: nginx:1 19 | ports: 20 | - containerPort: 8000 21 | imagePullPolicy: Always 22 | envFrom: 23 | - secretRef: 24 | name: backend-api 25 | imagePullSecrets: 26 | - name: regcred 27 | 28 | --- 29 | apiVersion: v1 30 | kind: Secret 31 | metadata: 32 | name: backend-apiXXX 33 | data: 34 | foo: YmFy 35 | 36 | --- 37 | apiVersion: bitnami.com/v1alpha1 38 | kind: SealedSecret 39 | metadata: 40 | name: backend-apiZ 41 | spec: 42 | encryptedData: 43 | foo: xxxxxxxx -------------------------------------------------------------------------------- /samples/sealed-secret/secret-shortcut.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kubevious.io/v1alpha1 2 | kind: ClusterRule 3 | metadata: 4 | name: deployment-env-from-check 5 | spec: 6 | target: | 7 | Shortcut("ContainerSpec") 8 | rule: | 9 | for(const envFrom of config.spec.envFrom ?? []) 10 | { 11 | if (envFrom.secretRef) 12 | { 13 | const secret = Shortcut("Secret", envFrom.secretRef.name) 14 | .single(); 15 | 16 | if (!secret) 17 | { 18 | error(`Could not find Secret ${envFrom.secretRef.name}`); 19 | } 20 | 21 | } 22 | } -------------------------------------------------------------------------------- /samples/service-empty-map.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Service 4 | metadata: 5 | name: my-service 6 | spec: 7 | selector: 8 | ports: 9 | - protocol: TCP 10 | name: http 11 | port: 80 12 | targetPort: 9001 -------------------------------------------------------------------------------- /samples/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | labels: 5 | app: emailservice 6 | name: emailservice 7 | spec: 8 | type: ClusterIP 9 | ports: 10 | - name: http 11 | port: 80 12 | targetPort: 8080 13 | selector: 14 | app: emailservice 15 | 16 | -------------------------------------------------------------------------------- /samples/ss/sample-ss.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: nginx 5 | labels: 6 | app: nginx 7 | spec: 8 | ports: 9 | - port: 80 10 | name: web 11 | clusterIP: None 12 | selector: 13 | app: nginx 14 | --- 15 | apiVersion: apps/v1 16 | kind: StatefulSet 17 | metadata: 18 | name: web 19 | spec: 20 | selector: 21 | matchLabels: 22 | app: nginx # has to match .spec.template.metadata.labels 23 | serviceName: "nginx" 24 | replicas: 3 # by default is 1 25 | minReadySeconds: 10 # by default is 0 26 | template: 27 | metadata: 28 | labels: 29 | app: nginx # has to match .spec.selector.matchLabels 30 | spec: 31 | terminationGracePeriodSeconds: 10 32 | containers: 33 | - name: nginx 34 | image: registry.k8s.io/nginx-slim:0.8 35 | ports: 36 | - containerPort: 80 37 | name: web 38 | volumeMounts: 39 | - name: www 40 | mountPath: /usr/share/nginx/html 41 | volumeClaimTemplates: 42 | - metadata: 43 | name: www 44 | spec: 45 | accessModes: [ "ReadWriteOnce" ] 46 | storageClassName: "my-storage-class" 47 | resources: 48 | requests: 49 | storage: 1Gi -------------------------------------------------------------------------------- /samples/sveltos/cluster-profile.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: config.projectsveltos.io/v1alpha1 2 | kind: ClusterProfile 3 | metadata: 4 | name: clusterprofile-sample 5 | spec: 6 | clusterSelector: env=fv 7 | syncMode: Continuous 8 | helmCharts: 9 | - repositoryURL: https://kyverno.github.io/kyverno/ 10 | repositoryName: kyverno 11 | chartName: kyverno/kyverno 12 | chartVersion: v2.5.0 13 | releaseName: kyverno-latest 14 | releaseNamespace: kyverno 15 | values: 16 | replicaCount: 3 17 | podAnnotations: 18 | annotation1: annotation-value-1 19 | helmChartAction: Install 20 | - repositoryURL: https://helm.nginx.com/stable 21 | repositoryName: nginx-stable 22 | chartName: nginx-stable/nginx-ingress 23 | chartVersion: 0.14.0 24 | releaseName: nginx-latest 25 | releaseNamespace: nginx 26 | helmChartAction: Install 27 | - repositoryURL: https://charts.bitnami.com/bitnami 28 | repositoryName: bitnami 29 | chartName: bitnami/contour 30 | chartVersion: 9.1.2 31 | releaseName: contour 32 | releaseNamespace: projectcontour 33 | helmChartAction: Install 34 | policyRefs: 35 | - name: kyverno-disallow-gateway-2 36 | namespace: default 37 | -------------------------------------------------------------------------------- /scripts/compile-executables.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | MY_PATH="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/$(basename "${BASH_SOURCE[0]}")" 3 | MY_DIR="$(dirname $MY_PATH)" 4 | cd ${MY_DIR}/.. 5 | 6 | source configuration.sh 7 | 8 | rm -rf ${BINARY_DIR}/* 9 | 10 | mkdir ${BINARY_DIR} 11 | 12 | source version.sh 13 | 14 | echo "${PRODUCT_VERSION}" > ${BINARY_DIR}/version 15 | 16 | docker run \ 17 | -it \ 18 | --rm \ 19 | --name "kubevious-cli" \ 20 | -h "kubevious-cli" \ 21 | -v ${MY_DIR}:/repo \ 22 | -w /repo \ 23 | kubevious/node-executable-builder:v1 bash -c 'pkg . --debug --no-bytecode' 24 | RESULT=$? 25 | if [ $RESULT -ne 0 ]; then 26 | echo "Failed to Build Packages" 27 | exit 1; 28 | fi 29 | 30 | ls -la ${BINARY_DIR}/ -------------------------------------------------------------------------------- /scripts/debugging/run-builder-docker.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | MY_PATH="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/$(basename "${BASH_SOURCE[0]}")" 3 | MY_DIR="$(dirname $MY_PATH)" 4 | cd ${MY_DIR}/../.. 5 | 6 | source configuration.sh 7 | 8 | docker run \ 9 | -it \ 10 | --rm \ 11 | --name "kubevious-cli" \ 12 | -h "kubevious-cli" \ 13 | -v ${MY_DIR}:/repo \ 14 | kubevious/node-executable-builder:v1 bash -------------------------------------------------------------------------------- /scripts/prepare-package.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | MY_PATH="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/$(basename "${BASH_SOURCE[0]}")" 3 | MY_DIR="$(dirname $MY_PATH)" 4 | cd ${MY_DIR}/.. 5 | 6 | source configuration.sh 7 | 8 | echo " >>> Copying API Schemas to assets..." 9 | export SNAPSHOT_K8S_API_SCHEMA_DIR=${MY_DIR}/assets/k8s-api-json-schema 10 | rm -rf ${SNAPSHOT_K8S_API_SCHEMA_DIR} 11 | mkdir -p ${SNAPSHOT_K8S_API_SCHEMA_DIR} 12 | cp -rf ${K8S_API_SCHEMA_DIR}/*.json ${SNAPSHOT_K8S_API_SCHEMA_DIR} 13 | 14 | 15 | echo " >>> Copying CRDs to assets..." 16 | export SNAPSHOT_CRDs_DIR=${MY_DIR}/assets/crds 17 | rm -rf ${SNAPSHOT_CRDs_DIR} 18 | mkdir -p ${SNAPSHOT_CRDs_DIR} 19 | cp -rf ${MY_DIR}/crds/*.yaml ${SNAPSHOT_CRDs_DIR} 20 | 21 | echo " >>> Assets: " 22 | find ${MY_DIR}/assets -------------------------------------------------------------------------------- /scripts/setup-google-action-version.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | MY_PATH="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/$(basename "${BASH_SOURCE[0]}")" 3 | MY_DIR="$(dirname $MY_PATH)" 4 | cd ${MY_DIR}/.. 5 | 6 | source version.sh 7 | 8 | yq -i '.runs.image |= "docker://kubevious/cli:'${PRODUCT_VERSION}'"' action.yml -------------------------------------------------------------------------------- /scripts/setup-pre-commit-hook-version.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | MY_PATH="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/$(basename "${BASH_SOURCE[0]}")" 3 | MY_DIR="$(dirname $MY_PATH)" 4 | cd ${MY_DIR}/.. 5 | 6 | source version.sh 7 | 8 | yq -i '.[].entry |= "kubevious/cli:'${PRODUCT_VERSION}'"' .pre-commit-hooks.yaml -------------------------------------------------------------------------------- /src/commands/guard/docs.ts: -------------------------------------------------------------------------------- 1 | import { generateUsageSample } from "../../screen/docs"; 2 | import { SAMPLES } from "./samples"; 3 | 4 | export const SUMMARY = "Lint local manifests and validate Kubernetes for cross-manifest violations and errors."; 5 | 6 | export const DESCRIPTION = `${SUMMARY} 7 | 8 | ${generateUsageSample(SAMPLES)} 9 | `; -------------------------------------------------------------------------------- /src/commands/guard/format.ts: -------------------------------------------------------------------------------- 1 | import _ from 'the-lodash'; 2 | 3 | import { GuardCommandData, GuardResult } from "./types"; 4 | import { logger } from '../../logger'; 5 | 6 | import { calculateRuleEngineCounters } from '../../manifests/counters'; 7 | 8 | const myLogger = logger.sublogger('GuardFormat'); 9 | 10 | export function formatResult({ 11 | severity, 12 | manifestPackage, 13 | k8sSchemaInfo, 14 | rulesRuntime, 15 | lintResult, 16 | rulesResult, 17 | } : GuardCommandData) : GuardResult 18 | { 19 | myLogger.info("Severity: %s", severity); 20 | 21 | myLogger.info("LintResult Severity: %s", lintResult.severity); 22 | myLogger.info("LintResult Success: %s", lintResult.success); 23 | 24 | myLogger.info("RulesResult Severity: %s", rulesResult.severity); 25 | myLogger.info("RulesResult Success: %s", rulesResult.success); 26 | 27 | const success = (severity == 'pass') || (severity == 'warning'); 28 | 29 | const result: GuardResult = { 30 | success: success, 31 | severity: severity, 32 | 33 | lintResult : lintResult, 34 | rules: rulesResult, 35 | 36 | counters: calculateRuleEngineCounters(rulesResult, manifestPackage) 37 | }; 38 | 39 | logger.verbose("RULES RESULT: ", rulesResult); 40 | logger.info("RULE COUNTERS: ", result.counters); 41 | 42 | return result; 43 | } 44 | -------------------------------------------------------------------------------- /src/commands/guard/samples.ts: -------------------------------------------------------------------------------- 1 | import { ToolUsageSamples } from "../../screen/docs"; 2 | 3 | export const SAMPLES : ToolUsageSamples = [ 4 | { 5 | title: 'Validate single file', 6 | code: 'kubevious guard sample.yaml', 7 | }, 8 | { 9 | title: 'Validate directory and a file', 10 | code: 'kubevious guard manifests/ mock/sealed-secret.yaml', 11 | }, 12 | { 13 | title: 'Validate HELM Chart', 14 | code: 'kubevious guard path/to/helm/chart', 15 | }, 16 | { 17 | title: 'Validate HELM Chart with overrides', 18 | code: 'kubevious guard @helm@the-helm-chart@path/to/overrides.yaml', 19 | }, 20 | { 21 | title: 'Validate Custom Resource and CRD', 22 | code: 'kubevious guard my-resource.yaml my-crd.yaml', 23 | }, 24 | { 25 | title: 'Validate Custom Resources towards already configured CRDs in K8s Clutser', 26 | code: 'kubevious guard my-resource.yaml --live-k8s', 27 | }, 28 | { 29 | title: 'Detailed output', 30 | code: 'kubevious guard --detailed @helm@traefik/traefik@namespace=traefik@release-name=traefik', 31 | }, 32 | ] -------------------------------------------------------------------------------- /src/commands/guard/types.ts: -------------------------------------------------------------------------------- 1 | import { K8sApiSchemaFetcherResult } from "../../api-schema/k8s-api-schema-fetcher"; 2 | import { ManifestPackage } from "../../manifests/manifest-package"; 3 | import { RulesRuntime } from "../../rules-engine/execution/rules-runtime"; 4 | import { LintCommandOptions, LintManifestsResult } from "../lint/types"; 5 | import { LocalK8sRegistry } from "../../registry/local-k8s-registry"; 6 | import { ResultObject, RuleEngineCounters } from "../../types/result"; 7 | import { RuleEngineResult } from "../../types/rules-result"; 8 | 9 | export interface GuardCommandOptions extends LintCommandOptions { 10 | 11 | includeRemoteTargets: boolean; 12 | 13 | skipLocalRules: boolean; 14 | 15 | skipRemoteRules: boolean; 16 | 17 | skipRules: string[]; 18 | onlyRules: string[]; 19 | skipRuleCategories: string[]; 20 | onlyRuleCategories: string[]; 21 | 22 | areNamespacesSpecified: boolean; 23 | namespace?: string; 24 | namespaces: string[]; 25 | skipClusterScope: boolean; 26 | 27 | skipCommunityRules: boolean; 28 | skipRuleLibraries: boolean; 29 | } 30 | 31 | export interface GuardCommandData extends ResultObject { 32 | manifestPackage: ManifestPackage, 33 | k8sSchemaInfo: K8sApiSchemaFetcherResult, 34 | rulesRuntime: RulesRuntime, 35 | 36 | localK8sRegistry: LocalK8sRegistry, 37 | 38 | lintResult: LintManifestsResult, 39 | rulesResult: RuleEngineResult 40 | } 41 | 42 | export interface GuardResult extends ResultObject 43 | { 44 | success: boolean; 45 | 46 | lintResult: LintManifestsResult; 47 | 48 | rules: RuleEngineResult; 49 | 50 | counters: RuleEngineCounters; 51 | } 52 | -------------------------------------------------------------------------------- /src/commands/index-library/format.ts: -------------------------------------------------------------------------------- 1 | import _ from 'the-lodash'; 2 | 3 | import { logger } from '../../logger'; 4 | import { IndexLibraryCommandData, IndexLibraryResult } from "./types"; 5 | 6 | import { formatResult as guardFormatResult } from '../guard/format' 7 | 8 | export function formatResult({ 9 | success, 10 | manifestPackage, 11 | guardCommandData, 12 | libraryPath, 13 | library 14 | }: IndexLibraryCommandData) : IndexLibraryResult 15 | { 16 | 17 | const guardResult = guardFormatResult(guardCommandData); 18 | 19 | const result: IndexLibraryResult = { 20 | success: success, 21 | 22 | guardResult: guardResult, 23 | 24 | libraryPath: libraryPath, 25 | library: library, 26 | } 27 | 28 | return result; 29 | } 30 | -------------------------------------------------------------------------------- /src/commands/index-library/index.ts: -------------------------------------------------------------------------------- 1 | import _ from 'the-lodash'; 2 | import { Command } from 'commander'; 3 | import { CommandBuilder } from '../../infra/command-action'; 4 | 5 | import { IndexLibraryCommandData, IndexLibraryCommandOptions, IndexLibraryResult } from './types'; 6 | import { command, massageIndexOptions } from './command'; 7 | import { formatResult } from './format'; 8 | import { output } from './output'; 9 | 10 | export default function (program: Command) 11 | { 12 | program 13 | .command('index-library') 14 | .description('Generate index for rules library repository. Creates index.yaml file.') 15 | .argument('', 'Path to the repository which contiains Kubevious rules.') 16 | .option('--name ', 'Library name.') 17 | .option('--json', 'Output command result in JSON.') 18 | .action( 19 | 20 | new CommandBuilder() 21 | .perform(async (dir: string, options: Partial) => { 22 | 23 | const myOptions = massageIndexOptions(options); 24 | 25 | return command(dir, myOptions); 26 | 27 | }) 28 | .format(formatResult) 29 | .output(output) 30 | .decideSuccess(result => { 31 | if (!result.success) 32 | { 33 | return 100; 34 | } 35 | return 0; 36 | }) 37 | .build() 38 | 39 | ); 40 | } 41 | -------------------------------------------------------------------------------- /src/commands/index-library/output.ts: -------------------------------------------------------------------------------- 1 | import { IndexLibraryResult } from "./types"; 2 | import { OBJECT_ICONS, print, printProcessStatus, printSubTitle } from '../../screen'; 3 | 4 | import { output as guardOutput } from '../guard/output' 5 | 6 | export function output(result: IndexLibraryResult) 7 | { 8 | guardOutput(result.guardResult); 9 | 10 | print(); 11 | 12 | printSubTitle('Rule Library'); 13 | print(`Rule Count: ${result.library.count}`) 14 | print(`Location Count: ${result.library.locationCount}`) 15 | print(); 16 | 17 | for(const location of result.library.locations) 18 | { 19 | print(`${OBJECT_ICONS.ruleLocation.get()} ${location.name}`, 2); 20 | print(`Rule Count: ${location.count}`, 5) 21 | for(const rule of location.rules) 22 | { 23 | print(`${OBJECT_ICONS.rule.get()} ${rule.title}`, 5); 24 | if (rule.categories.length > 0) { 25 | print(rule.categories.map(x => `${OBJECT_ICONS.ruleCategory.get()} ${x}`).join(' '), 9); 26 | } 27 | print(`Name: ${rule.name}`, 9); 28 | print(`Path: ${rule.path}`, 9); 29 | print(); 30 | } 31 | } 32 | 33 | print(`Library Index: ${result.libraryPath}`); 34 | 35 | printProcessStatus(result.success ? 'pass' : 'fail', 'Index Generation'); 36 | } 37 | -------------------------------------------------------------------------------- /src/commands/index-library/types.ts: -------------------------------------------------------------------------------- 1 | import { ManifestPackage } from "../../manifests/manifest-package"; 2 | import { ClusterRuleK8sSpec } from "../../rules-engine/spec/rule-spec"; 3 | import { GuardCommandData, GuardResult } from "../guard/types"; 4 | 5 | export interface IndexLibraryCommandOptions { 6 | name: string 7 | } 8 | 9 | export interface IndexLibraryCommandData { 10 | success: boolean, 11 | manifestPackage: ManifestPackage, 12 | guardCommandData: GuardCommandData, 13 | 14 | libraryDir: string, 15 | libraryPath: string, 16 | library: Library, 17 | } 18 | 19 | export interface IndexLibraryResult 20 | { 21 | success: boolean; 22 | 23 | guardResult: GuardResult; 24 | 25 | libraryPath: string, 26 | library: Library, 27 | } 28 | 29 | 30 | export interface LibraryRule 31 | { 32 | title: string, 33 | name: string, 34 | path: string, 35 | category: string, // TODO: RETIRE 36 | location: string, 37 | summary: string, 38 | description: string, 39 | categories: string[], 40 | 41 | ruleSpec: ClusterRuleK8sSpec, 42 | } 43 | 44 | export interface LibraryLocation 45 | { 46 | name: string; 47 | count: number; 48 | rules: LibraryRule[]; 49 | } 50 | 51 | export interface Library 52 | { 53 | count: number; 54 | locationCount: number; 55 | locations: LibraryLocation[]; 56 | } -------------------------------------------------------------------------------- /src/commands/install-git-hook/format.ts: -------------------------------------------------------------------------------- 1 | import _ from 'the-lodash'; 2 | 3 | import { logger as rootLogger } from '../../logger'; 4 | import { InstallHookCommandData } from './types'; 5 | 6 | 7 | const logger = rootLogger.sublogger('InstallGitHookFormat'); 8 | 9 | export function formatResult(commandData : InstallHookCommandData) : InstallHookCommandData 10 | { 11 | return commandData; 12 | } 13 | -------------------------------------------------------------------------------- /src/commands/install-git-hook/guard/index.ts: -------------------------------------------------------------------------------- 1 | import _ from 'the-lodash'; 2 | import { Command } from 'commander'; 3 | import { logger } from '../../../logger'; 4 | 5 | import { CommandBuilder } from '../../../infra/command-action'; 6 | 7 | import { command } from '../command'; 8 | import { formatResult } from '../format'; 9 | import { output } from '../output'; 10 | import { InstallHookCommandData } from '../types'; 11 | 12 | const HOOK_ID = 'kubevious-guard'; 13 | 14 | export default function (program: Command) 15 | { 16 | program 17 | .command('guard') 18 | .description('Installs a Git pre-commit hook to Guard Kubernetes Manifests') 19 | .argument('[path]', 'Path to git repository') 20 | .option('--json', 'Output command result in JSON.') 21 | .action( 22 | new CommandBuilder() 23 | .perform(async (path?: string) => { 24 | 25 | return command(path, { hook: HOOK_ID }) 26 | }) 27 | .format(formatResult) 28 | .output(output) 29 | .build() 30 | 31 | ); 32 | } 33 | -------------------------------------------------------------------------------- /src/commands/install-git-hook/index.ts: -------------------------------------------------------------------------------- 1 | import _ from 'the-lodash'; 2 | import { Command } from 'commander'; 3 | 4 | import setupRulesLibrary from './rule-library'; 5 | import setupLint from './lint'; 6 | import setupGuard from './guard'; 7 | 8 | export default function (program: Command) 9 | { 10 | const command = 11 | program 12 | .command('install-git-hook') 13 | .summary("Set up GIT pre-commit hooks to validate Kubernetes manifests before committing.") 14 | .description(` 15 | Uses pre-commit project to set up GIT pre-commit hooks. 16 | Learn more: https://pre-commit.com/ 17 | 18 | Hooks execute in containers, so Docker Desktop should be running. 19 | Available hooks: https://github.com/kubevious/cli/blob/main/.pre-commit-hooks.yaml 20 | `); 21 | 22 | setupGuard(command); 23 | setupLint(command); 24 | setupRulesLibrary(command); 25 | } 26 | -------------------------------------------------------------------------------- /src/commands/install-git-hook/lint/index.ts: -------------------------------------------------------------------------------- 1 | import _ from 'the-lodash'; 2 | import { Command } from 'commander'; 3 | import { logger } from '../../../logger'; 4 | 5 | import { CommandBuilder } from '../../../infra/command-action'; 6 | 7 | import { command } from '../command'; 8 | import { formatResult } from '../format'; 9 | import { output } from '../output'; 10 | import { InstallHookCommandData } from '../types'; 11 | 12 | const HOOK_ID = 'kubevious-lint'; 13 | 14 | export default function (program: Command) 15 | { 16 | program 17 | .command('lint') 18 | .description('Installs a Git pre-commit hook to Lint Kubernetes Manifests') 19 | .argument('[path]', 'Path to git repository') 20 | .option('--json', 'Output command result in JSON.') 21 | .action( 22 | new CommandBuilder() 23 | .perform(async (path?: string) => { 24 | 25 | return command(path, { hook: HOOK_ID }) 26 | }) 27 | .format(formatResult) 28 | .output(output) 29 | .build() 30 | 31 | ); 32 | } 33 | -------------------------------------------------------------------------------- /src/commands/install-git-hook/output.ts: -------------------------------------------------------------------------------- 1 | import { print, printErrorLine, printErrors, printInfoLine, printProcessStatus } from '../../screen'; 2 | import { InstallHookCommandData } from './types'; 3 | 4 | 5 | export function output(result: InstallHookCommandData) 6 | { 7 | 8 | for(const step of result.steps) 9 | { 10 | if (step.success) { 11 | printInfoLine(step.name, 3); 12 | } else { 13 | printErrorLine(step.name, 3); 14 | } 15 | } 16 | 17 | printProcessStatus(result.success ? 'pass' : 'fail', 'Install Git Hook'); 18 | 19 | printErrors(result.errors, 3); 20 | 21 | if (result.success) { 22 | print('Now you can run: ', 3); 23 | print(`$> cd ${result.repoPath}`, 5); 24 | print(`$> git add .pre-commit-config.yaml`, 5); 25 | print('$> pre-commit autoupdate', 5); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/commands/install-git-hook/rule-library/index.ts: -------------------------------------------------------------------------------- 1 | import _ from 'the-lodash'; 2 | import { Command } from 'commander'; 3 | import { logger } from '../../../logger'; 4 | 5 | import { CommandBuilder } from '../../../infra/command-action'; 6 | 7 | import { command } from '../command'; 8 | import { formatResult } from '../format'; 9 | import { output } from '../output'; 10 | import { InstallHookCommandData } from '../types'; 11 | 12 | const HOOK_ID = 'kubevious-index-library'; 13 | 14 | export default function (program: Command) 15 | { 16 | program 17 | .command('rule-library') 18 | .description('Installs a Git pre-commit hook to index rules library') 19 | .argument('[path]', 'Path to git repository') 20 | .option('--json', 'Output command result in JSON.') 21 | .action( 22 | new CommandBuilder() 23 | .perform(async (path?: string) => { 24 | 25 | return command(path, { hook: HOOK_ID }) 26 | }) 27 | .format(formatResult) 28 | .output(output) 29 | .build() 30 | 31 | ); 32 | } 33 | -------------------------------------------------------------------------------- /src/commands/install-git-hook/types.ts: -------------------------------------------------------------------------------- 1 | export interface InstallHookCommandData { 2 | success: boolean, 3 | hook: { 4 | repo: string, 5 | id: string 6 | }, 7 | errors: string[], 8 | repoPath: string, 9 | preCommitConfigPath: string, 10 | 11 | steps: { 12 | name: string, 13 | success: boolean 14 | }[], 15 | } 16 | 17 | export interface InstallHookOptions { 18 | hook: string 19 | } 20 | 21 | 22 | export interface PreCommitConfigSpec 23 | { 24 | repos: PreCommitRepoSpec[]; 25 | } 26 | 27 | export interface PreCommitRepoSpec 28 | { 29 | repo: string; 30 | rev?: string; 31 | hooks?: PreCommitHookSpec[]; 32 | } 33 | 34 | 35 | export interface PreCommitHookSpec 36 | { 37 | id: string; 38 | } -------------------------------------------------------------------------------- /src/commands/lint/docs.ts: -------------------------------------------------------------------------------- 1 | import { generateUsageSample } from "../../screen/docs"; 2 | import { SAMPLES } from "./samples"; 3 | 4 | export const SUMMARY = "Check Kubernetes manifests for API syntax validity."; 5 | 6 | export const DESCRIPTION = `${SUMMARY} 7 | 8 | ${generateUsageSample(SAMPLES)}`; 9 | 10 | 11 | export const ARG_PATH = 'Path to files, directories, search patterns, URLs, HELM Charts, Kustomize, etc'; 12 | 13 | export const OPTION_IGNORE_UNKNOWN = 'Ignore unknown resources. Use when manifests include CRDs and not using --live-k8s option.'; 14 | export const OPTION_IGNORE_NON_K8S = 'Ignore non-k8s files.'; 15 | export const OPTION_SKIP_APPLY_CRDS = 'Skips CRD application.'; 16 | export const OPTION_STREAM = 'Also read manifests from stream.'; 17 | export const OPTION_K8S_VERSION = 'Target Kubernetes version. Do not use with --live-k8s option.'; 18 | export const OPTION_LIVE_K8S = 'Lint against live Kubernetes cluster. Allows validation of CRDs. Do not use with --k8s-version option.'; 19 | export const OPTION_K8S_SKIP_TLS_VERIFY = 'Skips TLS certificate verification when connecting to live K8s. Has effects when using with --live-k8s option.'; 20 | export const OPTION_KUBECONFIG = 'Optionally set the path to the kubeconfig file. Use with --live-k8s option.'; 21 | export const OPTION_IGNORE_FILE = 'Path to .gitignore file to filter out input patterns.'; 22 | export const OPTION_IGNORE_PATTERNS = 'File patters to ignore.'; 23 | export const OPTION_DETAILED = 'Detailed output.'; 24 | export const OPTION_JSON = 'Output command result in JSON.'; 25 | export const OPTION_ALLOW_DUPLICATES = 'Allow duplicate manifests.'; 26 | -------------------------------------------------------------------------------- /src/commands/lint/format.ts: -------------------------------------------------------------------------------- 1 | import _ from 'the-lodash'; 2 | 3 | import { LintCommandData, LintManifestsResult } from "./types"; 4 | import { logger } from '../../logger'; 5 | import { calculateManifestPackageCounters } from '../../manifests/counters'; 6 | 7 | const myLogger = logger.sublogger('LintFormat'); 8 | 9 | export function formatResult({ 10 | manifestPackage, 11 | k8sSchemaInfo, 12 | } : LintCommandData) : LintManifestsResult 13 | { 14 | const packageResult = manifestPackage.exportResult(); 15 | 16 | // myLogger.info(">>>>>>>>>>>>>>>>>>>>>>>"); 17 | // myLogger.info("RESULT: ", packageResult); 18 | // myLogger.info(">>>>>>>>>>>>>>>>>>>>>>>"); 19 | 20 | const success = packageResult.severity == 'pass' || packageResult.severity == 'warning'; 21 | 22 | myLogger.info("Success: %s", success); 23 | 24 | const result: LintManifestsResult = { 25 | success: success, 26 | severity: packageResult.severity, 27 | 28 | packageResult: packageResult, 29 | 30 | targetK8sVersion: k8sSchemaInfo.targetVersion || undefined, 31 | selectedK8sVersion: k8sSchemaInfo.selectedVersion || undefined, 32 | foundK8sVersion: k8sSchemaInfo.found, 33 | foundExactK8sVersion: k8sSchemaInfo.foundExact, 34 | 35 | counters: calculateManifestPackageCounters(packageResult) 36 | }; 37 | 38 | // outputSource.manifests = 39 | // _.chain(outputSource.manifests) 40 | // .orderBy([x => x.namespace, x => x.api, x => x.version, x => x.kind, x => x.name]) 41 | // .value(); 42 | 43 | // result.sources = 44 | // _.chain(result.sources) 45 | // .orderBy([x => x.kind, x => x.path]) 46 | // .value(); 47 | 48 | return result; 49 | } -------------------------------------------------------------------------------- /src/commands/lint/index.ts: -------------------------------------------------------------------------------- 1 | import _ from 'the-lodash'; 2 | import { Command } from 'commander'; 3 | import { logger } from '../../logger'; 4 | 5 | import { CommandBuilder } from '../../infra/command-action'; 6 | import { LintCommandData, LintCommandOptions, LintManifestsResult } from './types'; 7 | 8 | import { command, massageLintOptions } from './command'; 9 | import { formatResult } from './format'; 10 | import { output } from './output'; 11 | 12 | import { DESCRIPTION, OPTION_ALLOW_DUPLICATES, OPTION_IGNORE_FILE, OPTION_IGNORE_PATTERNS, OPTION_K8S_SKIP_TLS_VERIFY, SUMMARY } from './docs'; 13 | import { ARG_PATH, OPTION_DETAILED, OPTION_IGNORE_NON_K8S, OPTION_IGNORE_UNKNOWN, OPTION_JSON, OPTION_K8S_VERSION, OPTION_KUBECONFIG, OPTION_LIVE_K8S, OPTION_SKIP_APPLY_CRDS, OPTION_STREAM } from './docs'; 14 | 15 | export default function (program: Command) 16 | { 17 | program 18 | .command('lint') 19 | .summary(SUMMARY) 20 | .description(DESCRIPTION) 21 | .argument('[path...]', ARG_PATH) 22 | .option('--ignore-unknown', OPTION_IGNORE_UNKNOWN) 23 | .option('--ignore-non-k8s', OPTION_IGNORE_NON_K8S) 24 | .option('--skip-apply-crds', OPTION_SKIP_APPLY_CRDS) 25 | .option('--stream', OPTION_STREAM) 26 | .option('--k8s-version ', OPTION_K8S_VERSION) 27 | .option('--live-k8s', OPTION_LIVE_K8S) 28 | .option('--kubeconfig ', OPTION_KUBECONFIG) 29 | .option('--k8s-skip-tls-verify', OPTION_K8S_SKIP_TLS_VERIFY) 30 | .option('--allow-duplicates', OPTION_ALLOW_DUPLICATES) 31 | .option('--gitignore ', OPTION_IGNORE_FILE) 32 | .option('--ignore-patterns [patterns...]', OPTION_IGNORE_PATTERNS) 33 | .option('--detailed', OPTION_DETAILED) 34 | .option('--json', OPTION_JSON) 35 | .action( 36 | new CommandBuilder() 37 | .perform(async (path: string[], options: Partial) => { 38 | 39 | const myOptions = massageLintOptions(options); 40 | 41 | return command(path, myOptions); 42 | }) 43 | .format(formatResult) 44 | .output(output) 45 | .decideSuccess(result => { 46 | if (!result.success) 47 | { 48 | return 100; 49 | } 50 | return 0; 51 | }) 52 | .build() 53 | 54 | ); 55 | } 56 | -------------------------------------------------------------------------------- /src/commands/lint/output.ts: -------------------------------------------------------------------------------- 1 | import { OBJECT_ICONS, print, printFailLine, printInfoLine, printProcessStatus, printSectionTitle, printSummaryCounter, printWarningLine, STATUS_ICONS } from '../../screen'; 2 | import { LintManifestsResult } from "./types"; 3 | import { outputManifestPackageResultManifests, outputManifestPackageResultSources } from '../../screen/manifest'; 4 | 5 | export interface LintOutputParams { 6 | skipResult?: boolean, 7 | skipSummary?: boolean 8 | } 9 | 10 | export function output(result: LintManifestsResult, detailed?: boolean, params?: LintOutputParams) 11 | { 12 | params = params ?? {}; 13 | params.skipSummary = params.skipSummary ?? false; 14 | params.skipResult = params.skipResult ?? false; 15 | 16 | if (!result.foundK8sVersion) { 17 | printFailLine(`Failed to find Kubernetes Version ${result.targetK8sVersion}`); 18 | } 19 | else { 20 | if (!result.foundExactK8sVersion) { 21 | printWarningLine(`Could not find requested Kubernetes Version: ${result.targetK8sVersion}`); 22 | } 23 | } 24 | printInfoLine(`Linting against Kubernetes Version: ${result.selectedK8sVersion}`); 25 | print(); 26 | 27 | outputManifestPackageResultSources(result.packageResult, detailed); 28 | outputManifestPackageResultManifests(result.packageResult, detailed); 29 | 30 | if (!params.skipSummary) 31 | { 32 | outputLintSummary(result); 33 | } 34 | 35 | if (!params.skipResult) 36 | { 37 | if (!detailed) { 38 | print(); 39 | printInfoLine(`Run with --detailed to see all sources and manifests`); 40 | } 41 | 42 | printProcessStatus(result.severity, 'Lint'); 43 | } 44 | } 45 | 46 | export function outputLintSummary(result: LintManifestsResult) 47 | { 48 | printSectionTitle('Summary'); 49 | 50 | const lintCounters = result.counters; 51 | 52 | print(`${OBJECT_ICONS.source.get()} Sources: ${lintCounters.sources.total}`, 4); 53 | printSummaryCounter(STATUS_ICONS.failed, 'Sources with Errors', lintCounters.sources.withErrors); 54 | 55 | print(`${OBJECT_ICONS.manifest.get()} Manifests: ${lintCounters.manifests.total}`, 4); 56 | printSummaryCounter(STATUS_ICONS.passed, 'Valid Manifests', lintCounters.manifests.passed); 57 | printSummaryCounter(STATUS_ICONS.failed, 'Manifests with Errors', lintCounters.manifests.withErrors); 58 | printSummaryCounter(STATUS_ICONS.warning, 'Manifests with Warnings', lintCounters.manifests.withWarnings); 59 | } 60 | 61 | -------------------------------------------------------------------------------- /src/commands/lint/samples.ts: -------------------------------------------------------------------------------- 1 | import { ToolUsageSamples } from "../../screen/docs"; 2 | 3 | export const SAMPLES : ToolUsageSamples = [ 4 | { 5 | title: 'Lint single file', 6 | code: 'kubevious lint sample.yaml', 7 | }, 8 | { 9 | title: 'Lint directory and a file', 10 | code: 'kubevious lint manifests/ mock/sealed-secret.yaml', 11 | }, 12 | { 13 | title: 'Lint HELM Chart', 14 | code: 'kubevious lint path/to/helm/chart', 15 | }, 16 | { 17 | title: 'Lint HELM Chart with overrides', 18 | code: 'kubevious lint @helm@the-helm-chart@values=path/to/overrides.yaml', 19 | }, 20 | { 21 | title: 'Lint Custom Resource and CRD', 22 | code: 'kubevious lint my-resource.yaml my-crd.yaml', 23 | }, 24 | { 25 | title: 'Lint Custom Resources towards already configured CRDs in K8s Clutser', 26 | code: 'kubevious lint my-resource.yaml --live-k8s', 27 | }, 28 | { 29 | title: 'Detailed output', 30 | code: 'kubevious lint --detailed @helm@traefik/traefik@namespace=traefik@release-name=traefik', 31 | }, 32 | 33 | ] -------------------------------------------------------------------------------- /src/commands/lint/types.ts: -------------------------------------------------------------------------------- 1 | import { K8sApiSchemaFetcherResult } from "../../api-schema/k8s-api-schema-fetcher"; 2 | import { InputSourceExtractor } from "../../input/input-source-extractor"; 3 | import { K8sClusterConnector } from "../../k8s-connector/k8s-cluster-connector"; 4 | import { ManifestPackage } from "../../manifests/manifest-package"; 5 | import { ManifestLoader } from "../../manifests/manifests-loader"; 6 | import { ManifestPackageResult } from "../../types/manifest-result"; 7 | import { ManifestPackageCounters, ResultObject } from "../../types/result"; 8 | 9 | export interface LintCommandOptions { 10 | k8sVersion?: string; 11 | ignoreUnknown: boolean; 12 | ignoreNonK8s: boolean; 13 | stream: boolean; 14 | skipApplyCrds: boolean; 15 | allowDuplicates: boolean; 16 | 17 | liveK8s: boolean; 18 | kubeconfig?: string; 19 | k8sSkipTlsVerify: boolean; 20 | gitignore?: string; 21 | ignorePatterns : string[]; 22 | } 23 | 24 | 25 | export interface LintCommandData { 26 | k8sConnector: K8sClusterConnector, 27 | manifestPackage: ManifestPackage, 28 | k8sSchemaInfo: K8sApiSchemaFetcherResult, 29 | 30 | inputSourceExtractor: InputSourceExtractor, 31 | manifestLoader: ManifestLoader, 32 | } 33 | 34 | export type LintSeverity = 'pass' | 'fail' | 'warning'; 35 | export interface LintStatus 36 | { 37 | success: boolean, 38 | severity: LintSeverity, 39 | errors?: string[], 40 | warnings?: string[] 41 | } 42 | 43 | export interface LintManifestsResult extends ResultObject 44 | { 45 | success: boolean; 46 | 47 | targetK8sVersion?: string; 48 | selectedK8sVersion?: string; 49 | foundK8sVersion: boolean; 50 | foundExactK8sVersion: boolean; 51 | 52 | packageResult: ManifestPackageResult; 53 | 54 | counters: ManifestPackageCounters; 55 | } -------------------------------------------------------------------------------- /src/commands/list-known-k8s-versions/index.ts: -------------------------------------------------------------------------------- 1 | import _ from 'the-lodash'; 2 | import { Command } from 'commander'; 3 | import { logger } from '../../logger'; 4 | 5 | 6 | import { CommandBuilder } from '../../infra/command-action'; 7 | 8 | import { KnownK8sVersionsResult } from './types'; 9 | import { output } from './output'; 10 | import { K8sApiSchemaRegistry } from '../../api-schema/k8s-api-schema-registry'; 11 | 12 | export default function (program: Command) 13 | { 14 | program 15 | .command('list-known-k8s-versions') 16 | .description('List of known K8s versions') 17 | .option('--json', 'Output command result in JSON.') 18 | .action( 19 | new CommandBuilder() 20 | .perform(async () => { 21 | 22 | const k8sApiRegistry = new K8sApiSchemaRegistry(logger); 23 | await k8sApiRegistry.init(); 24 | 25 | return k8sApiRegistry.getVersions(); 26 | }) 27 | .format(versions => { 28 | const result: KnownK8sVersionsResult = { 29 | versions: versions 30 | } 31 | return result; 32 | }) 33 | .output(output) 34 | .build() 35 | 36 | ); 37 | } 38 | -------------------------------------------------------------------------------- /src/commands/list-known-k8s-versions/output.ts: -------------------------------------------------------------------------------- 1 | import { printSectionTitle, SOURCE_ICONS, print } from "../../screen"; 2 | import { KnownK8sVersionsResult } from "./types"; 3 | 4 | export function output(result: KnownK8sVersionsResult) 5 | { 6 | printSectionTitle('Known K8s Versions'); 7 | print(); 8 | 9 | for(const version of result.versions) 10 | { 11 | print(`${SOURCE_ICONS.k8s.get()} ${version}`, 2); 12 | } 13 | } -------------------------------------------------------------------------------- /src/commands/list-known-k8s-versions/types.ts: -------------------------------------------------------------------------------- 1 | 2 | export interface KnownK8sVersionsResult 3 | { 4 | versions: string[]; 5 | } -------------------------------------------------------------------------------- /src/commands/program/docs.ts: -------------------------------------------------------------------------------- 1 | 2 | export const SUMMARY = 'Kubevious CLI validates Kubernetes manifests for misconfigurations and violations.'; 3 | 4 | export const DESCRIPTION = ` 5 | NKXW 6 | WXx:,;oKW 7 | Nk;.....,oKW 8 | No'.......,oKW 9 | Nkc'.......,o0W 10 | WKxkNNkc'.......,oKW 11 | Kc.'ckNNkc'.......,oKW 12 | WWWNNNNWW Nk;...'ckNNkc'.......cK 13 | WX0kdocc:::::clodk0NW NOc'......'ckNNkc'...'cON 14 | N0dc;'...............:ONNOc'..........'ckNNkl:ckN 15 | WKx:'.................'c0WKl'...........,,;oX WNW 16 | WKd;.....................;d0X0d;........;d0KXW 17 | Nk:.........................'ckXKx;....;xXW 18 | Xd,............................':kXKo,:xXW 19 | Xo'...............................'l0NKXNXW 20 | Wd'..................................;kXOc:O 21 | Nkooooooooooooooooooooooooooooooooooooxkdod0W 22 | WNK0000000000000000000000000000000000KK0000KKK0000KKXW 23 | Kl,''''''''''''''''''''''''''''''''''''''''''''''''';xW 24 | NkdoooooooooooooooooooooooooooooooooooooooooooooooooxKW 25 | WNKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKNW 26 | Nd,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,dN 27 | Nl....................''''....................oN 28 | Nl................';ok000Oko;.................oN 29 | Nl...............'oXWKkxxkKWXo'...............oN 30 | Nl...............lXWO;....;kWXl...............oN 31 | Nl..............'dWWd'.....lNWd'.............'oN 32 | WKOkkkkkkkkkkkkkOXWWKOkkkkkKW XOOOOOOOOOOOOOOOKW 33 | 34 | ${SUMMARY} 35 | Find more information at: https://github.com/kubevious/cli 36 | 37 | Usage: kubevious [options] [command]`; -------------------------------------------------------------------------------- /src/commands/program/index.ts: -------------------------------------------------------------------------------- 1 | import { program } from 'commander'; 2 | import VERSION from '../../version' 3 | 4 | import setupLintManifests from '../lint'; 5 | import setupGuard from '../guard'; 6 | import setupListKnownK8sVersions from '../list-known-k8s-versions'; 7 | import setupIndexLibrary from '../index-library'; 8 | import setupInstallGitHook from '../install-git-hook'; 9 | import setupSupport from '../support'; 10 | import { DESCRIPTION, SUMMARY } from './docs'; 11 | 12 | export function setupProgram() 13 | { 14 | program 15 | .name('kubevious') 16 | .summary(SUMMARY) 17 | .description(DESCRIPTION) 18 | .version(VERSION); 19 | 20 | setupGuard(program); 21 | setupLintManifests(program); 22 | setupListKnownK8sVersions(program); 23 | setupIndexLibrary(program); 24 | setupInstallGitHook(program); 25 | setupSupport(program); 26 | 27 | program.parse(); 28 | } -------------------------------------------------------------------------------- /src/commands/support/index.ts: -------------------------------------------------------------------------------- 1 | import _ from 'the-lodash'; 2 | import { Command } from 'commander'; 3 | import VERSION from '../../version'; 4 | 5 | import { printSectionTitle, OTHER_ICONS, print } from "../../screen"; 6 | 7 | export default function (program: Command) 8 | { 9 | program 10 | .command('support') 11 | .description('Get help and support.') 12 | .action( 13 | (...args: any[]) => { 14 | 15 | printSectionTitle(`Kubevious CLI ${VERSION}`); 16 | 17 | print(); 18 | 19 | print(`${OTHER_ICONS.email.get()} support@kubevious.io`, 4); 20 | print(`${OTHER_ICONS.bug.get()} https://github.com/kubevious/cli/issues`, 4); 21 | print(`${OTHER_ICONS.slack.get()} https://kubevious.io/slack`, 4); 22 | 23 | print(); 24 | 25 | print(`${OTHER_ICONS.party.get()} Lets us know if you encounter issues, have a feature request, or need help writing custom rules!`); 26 | 27 | print(); 28 | 29 | } 30 | ); 31 | } 32 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env node 2 | 3 | import _ from 'the-lodash'; 4 | import { logger } from './logger'; 5 | 6 | import { setupProgram } from './commands/program'; 7 | 8 | logger.info("[execPath]: %s", process.execPath) 9 | logger.info("[execArgv]: ", process.execArgv) 10 | logger.info("[argv]: ", process.argv) 11 | 12 | setupProgram(); -------------------------------------------------------------------------------- /src/input/utils.ts: -------------------------------------------------------------------------------- 1 | import _ from 'the-lodash'; 2 | import { ManifestSourceType } from "../types/manifest"; 3 | import { isWebPath } from '../utils/path'; 4 | 5 | export interface UserPathSuffix 6 | { 7 | key: string, 8 | value: string, 9 | } 10 | 11 | export type UserPathSuffixes = UserPathSuffix[]; 12 | 13 | export interface ParsedUserPath { 14 | kind: ManifestSourceType, 15 | path: string, 16 | suffixes: UserPathSuffixes, 17 | isInvalid?: boolean, 18 | } 19 | 20 | export function parseUserInputPath(str: string) : ParsedUserPath 21 | { 22 | let parts = str.split("@"); 23 | 24 | let head : string = ''; 25 | let kind : ManifestSourceType = 'file'; 26 | 27 | if (_.startsWith(str, '@')) 28 | { 29 | parts = _.drop(parts); 30 | 31 | head = _.head(parts) ?? ''; 32 | parts = _.drop(parts); 33 | 34 | kind = head as ManifestSourceType; 35 | 36 | if (kind !== 'helm') { 37 | return { 38 | isInvalid: true, 39 | kind: 'file', 40 | path: str, 41 | suffixes: [] 42 | } 43 | } 44 | 45 | head = _.head(parts) ?? ""; 46 | parts = _.drop(parts); 47 | 48 | } 49 | else 50 | { 51 | head = _.head(parts) ?? ""; 52 | parts = _.drop(parts); 53 | 54 | const isWeb = isWebPath(head); 55 | if (isWeb) { 56 | kind = 'web'; 57 | } 58 | } 59 | 60 | if (!head) { 61 | return { 62 | isInvalid: true, 63 | kind: kind, 64 | path: '', 65 | suffixes: [] 66 | } 67 | } 68 | 69 | const result = { 70 | kind: kind, 71 | path: head, 72 | suffixes: parts.map(x => parseSuffix(x)) 73 | } 74 | 75 | return result; 76 | } 77 | 78 | function parseSuffix(str: string) : UserPathSuffix 79 | { 80 | const index = str.indexOf("="); 81 | if (index === -1) { 82 | return { 83 | key: '', 84 | value: str 85 | } 86 | } else { 87 | return { 88 | key: str.substring(0, index), 89 | value: str.substring(index + 1) 90 | } 91 | } 92 | 93 | } -------------------------------------------------------------------------------- /src/k8s-connector/k8s-cluster-connector.ts: -------------------------------------------------------------------------------- 1 | import { ILogger } from 'the-logger'; 2 | import { connectDefaultRemoteCluster, connectRemoteCluster, KubernetesClient } from 'k8s-super-client'; 3 | import { spinOperation } from '../screen/spinner'; 4 | import { ClusterConnectParams } from 'k8s-super-client/dist/connector-types'; 5 | 6 | export class K8sClusterConnector 7 | { 8 | private _logger: ILogger; 9 | private _client: KubernetesClient | null = null; 10 | private _isUsed = false; 11 | private _isConnected = false; 12 | 13 | constructor(logger: ILogger) 14 | { 15 | this._logger = logger.sublogger('K8sApiSchemaFetcher'); 16 | } 17 | 18 | get isUsed() { 19 | return this._isUsed; 20 | } 21 | 22 | get isConnected() { 23 | return this._isConnected; 24 | } 25 | 26 | get client() { 27 | return this._client; 28 | } 29 | 30 | async setup(connect: boolean, kubeconfigpath? : string, k8sSkipTlsVerify?: boolean) 31 | { 32 | if (!connect) { 33 | return Promise.resolve(); 34 | } 35 | 36 | this._isUsed = true; 37 | 38 | const spinner = spinOperation('Connecting to K8s Cluster...'); 39 | 40 | const skipAPIFetch = false; // true 41 | 42 | return Promise.resolve() 43 | .then(() => { 44 | const connectParams : ClusterConnectParams = { 45 | skipAPIFetch: skipAPIFetch, 46 | skipTLSVerify: k8sSkipTlsVerify 47 | } 48 | 49 | const k8sLogger = this._logger.sublogger('k8s'); 50 | if (kubeconfigpath) { 51 | this._logger.info("[fetchRemote] path: %s", kubeconfigpath); 52 | return connectRemoteCluster(k8sLogger, kubeconfigpath, undefined, connectParams) 53 | } else { 54 | return connectDefaultRemoteCluster(k8sLogger, connectParams) 55 | } 56 | }) 57 | .then(client => { 58 | spinner.complete('Connected to K8s Cluster.') 59 | this._logger.info("Connected."); 60 | this._isConnected = true; 61 | this._client = client; 62 | }) 63 | .catch(reason => { 64 | const errorMsg = `Could not connect to Kubernetes cluster. ${reason?.message}`; 65 | spinner.fail(errorMsg); 66 | }) 67 | } 68 | } -------------------------------------------------------------------------------- /src/logger.ts: -------------------------------------------------------------------------------- 1 | 2 | import _ from 'the-lodash'; 3 | import { LoggerOptions, LogLevel, setupRootLogger } from 'the-logger'; 4 | 5 | const loggerOptions = new LoggerOptions(); 6 | 7 | loggerOptions.pretty(true); 8 | 9 | let logLevel = LogLevel.warn; 10 | if (process.env.LOG_LEVEL) { 11 | logLevel = process.env.LOG_LEVEL; 12 | } 13 | loggerOptions.level(logLevel); 14 | 15 | if (process.env.LOG_TO_FILE && 16 | (process.env.LOG_TO_FILE == 'true' || process.env.LOG_TO_FILE == 'yes')) 17 | { 18 | loggerOptions.enableFile(true); 19 | loggerOptions.cleanOnStart(true); 20 | } 21 | 22 | 23 | const LogLevelOverridePrefix = 'LOG_LEVEL_' 24 | for(const key of _.keys(process.env).filter(x => _.startsWith(x, LogLevelOverridePrefix))) 25 | { 26 | const sublevelName = key.substring(LogLevelOverridePrefix.length); 27 | const logLevel = process.env[key]; 28 | loggerOptions.subLevel(sublevelName, logLevel); 29 | } 30 | 31 | 32 | export const rootLogger = setupRootLogger('CLI', loggerOptions); 33 | export const logger = rootLogger.logger; 34 | -------------------------------------------------------------------------------- /src/path-resolver.ts: -------------------------------------------------------------------------------- 1 | import Path from 'path'; 2 | 3 | export class PathResolver 4 | { 5 | 6 | get distDir() { 7 | return __dirname; 8 | } 9 | 10 | get rootDir() { 11 | return Path.resolve(this.distDir, '..') 12 | } 13 | 14 | get assetsDir() { 15 | return Path.resolve(this.rootDir, 'assets') 16 | } 17 | 18 | get k8sApiSchemaDir() { 19 | if(process.env.K8S_API_SCHEMA_DIR) { 20 | return process.env.K8S_API_SCHEMA_DIR; 21 | } else { 22 | return Path.join(this.assetsDir, 'k8s-api-json-schema'); 23 | } 24 | } 25 | 26 | get cliCrdsDir() { 27 | if(process.env.KUBEVIOUS_CRDS_DIR) { 28 | return process.env.KUBEVIOUS_CRDS_DIR; 29 | } else { 30 | return Path.join(this.assetsDir, 'crds'); 31 | } 32 | } 33 | 34 | } -------------------------------------------------------------------------------- /src/processors/default-namespace-setter.ts: -------------------------------------------------------------------------------- 1 | import _ from 'the-lodash'; 2 | import { ILogger } from 'the-logger'; 3 | 4 | import { K8sApiJsonSchema } from 'k8s-super-client/dist/open-api/converter/types'; 5 | import { ManifestPackage } from '../manifests/manifest-package'; 6 | import { K8sOpenApiResource } from 'k8s-super-client/dist'; 7 | import { getJsonSchemaResourceKey } from '../utils/k8s'; 8 | import { K8sManifest } from '../manifests/k8s-manifest'; 9 | 10 | 11 | export class DefaultNamespaceSetter 12 | { 13 | private _logger: ILogger; 14 | private _k8sJsonSchema : K8sApiJsonSchema; 15 | private _manifestPackage: ManifestPackage; 16 | 17 | constructor(logger: ILogger, k8sJsonSchema : K8sApiJsonSchema, manifestPackage: ManifestPackage) 18 | { 19 | this._logger = logger.sublogger('DefaultNamespaceSetter'); 20 | this._k8sJsonSchema = k8sJsonSchema; 21 | this._manifestPackage = manifestPackage; 22 | } 23 | 24 | process() 25 | { 26 | for(const manifest of this._manifestPackage.manifests) 27 | { 28 | this._processManifest(manifest); 29 | } 30 | } 31 | 32 | private _processManifest(manifest: K8sManifest) 33 | { 34 | if (manifest.id.namespace) { 35 | return; 36 | } 37 | 38 | const apiResource : K8sOpenApiResource = { 39 | group: manifest.id.api ?? '', 40 | version: manifest.id.version, 41 | kind: manifest.id.kind 42 | } 43 | const resourceKey = getJsonSchemaResourceKey(apiResource); 44 | const resourceInfo = this._k8sJsonSchema.resources[resourceKey]; 45 | 46 | if (!resourceInfo) { 47 | return; 48 | } 49 | 50 | if (resourceInfo.namespaced) { 51 | manifest.id.namespace = 'default'; 52 | if (!manifest.config.metadata) { 53 | manifest.config.metadata = {}; 54 | } 55 | manifest.config.metadata.namespace = manifest.id.namespace; 56 | } 57 | } 58 | 59 | } -------------------------------------------------------------------------------- /src/registry/cached-k8s-registry.ts: -------------------------------------------------------------------------------- 1 | import _ from 'the-lodash'; 2 | import { ILogger } from 'the-logger'; 3 | import { K8sTargetFilter } from '../rules-engine/query-spec/k8s/k8s-target-query'; 4 | import { RegistryQueryExecutor } from '../rules-engine/query-executor'; 5 | import { K8sManifest } from '../manifests/k8s-manifest'; 6 | 7 | export class CachedK8sRegistry implements RegistryQueryExecutor 8 | { 9 | private _logger: ILogger; 10 | private _innerRegistry: RegistryQueryExecutor; 11 | 12 | private _cache : Record = {}; 13 | 14 | constructor(logger: ILogger, innerRegistry: RegistryQueryExecutor) 15 | { 16 | this._logger = logger.sublogger('CachedK8sRegistry'); 17 | this._innerRegistry = innerRegistry; 18 | } 19 | 20 | query(query: K8sTargetFilter) : K8sManifest[] 21 | { 22 | // this._logger.info("[query] ", query); 23 | 24 | const key = _.stableStringify(query); 25 | const cachedResult = this._cache[key]; 26 | if (_.isNotNullOrUndefined(cachedResult)) { 27 | return cachedResult; 28 | } 29 | 30 | const result = this._innerRegistry.query(query); 31 | this._cache[key] = result; 32 | return result; 33 | } 34 | 35 | } -------------------------------------------------------------------------------- /src/registry/client-side-filtering.ts: -------------------------------------------------------------------------------- 1 | import _ from 'the-lodash'; 2 | import { K8sManifest } from '../manifests/k8s-manifest'; 3 | import { KeyValueDict } from "../rules-engine/query-spec/k8s/k8s-target-query"; 4 | 5 | 6 | export class ClientSideFiltering 7 | { 8 | private _items: K8sManifest[]; 9 | 10 | constructor(items: K8sManifest[]) 11 | { 12 | this._items = items; 13 | } 14 | 15 | get items() { 16 | return this._items; 17 | } 18 | 19 | applyLabelFilter(labelFilters?: KeyValueDict[]) 20 | { 21 | if (labelFilters && labelFilters.length > 0) { 22 | this._items = this._items.filter(x => this._doesAnyMatch(labelFilters!, (labelFilter) => { 23 | for (const key of _.keys(labelFilter)) { 24 | const labels = x.config.metadata?.labels ?? {}; 25 | if (labels[key] != labelFilter[key]) { 26 | return false 27 | } 28 | } 29 | return true; 30 | })); 31 | } 32 | } 33 | 34 | private _doesAnyMatch(matchers: T[], cb: ((value: T) => boolean)) : boolean { 35 | if (matchers.length == 0) { 36 | return true; 37 | } 38 | 39 | for(const matcher of matchers) { 40 | const isMatch = cb(matcher); 41 | if (isMatch) { 42 | return true; 43 | } 44 | } 45 | return false; 46 | } 47 | } -------------------------------------------------------------------------------- /src/registry/combined-k8s-registry.ts: -------------------------------------------------------------------------------- 1 | import _ from 'the-lodash'; 2 | import { ILogger } from 'the-logger'; 3 | import { K8sTargetFilter } from '../rules-engine/query-spec/k8s/k8s-target-query'; 4 | import { RegistryQueryExecutor } from '../rules-engine/query-executor'; 5 | import { K8sManifest } from '../manifests/k8s-manifest'; 6 | 7 | export class CombinedK8sRegistry implements RegistryQueryExecutor 8 | { 9 | private _logger: ILogger; 10 | private _innerRegistries: RegistryQueryExecutor[]; 11 | 12 | constructor(logger: ILogger, innerRegistries: RegistryQueryExecutor[]) 13 | { 14 | this._logger = logger.sublogger('CombinedK8sRegistry'); 15 | this._innerRegistries = innerRegistries; 16 | } 17 | 18 | query(query: K8sTargetFilter) : K8sManifest[] 19 | { 20 | this._logger.info("[query] ", query); 21 | 22 | const dict : Record = {}; 23 | 24 | for(const registry of this._innerRegistries) 25 | { 26 | const manifests = registry.query(query); 27 | for(const x of manifests) 28 | { 29 | if (!dict[x.idKey]) { 30 | dict[x.idKey] = x; 31 | } 32 | } 33 | } 34 | 35 | return _.values(dict); 36 | } 37 | 38 | } -------------------------------------------------------------------------------- /src/registry/local-registry-populator.ts: -------------------------------------------------------------------------------- 1 | import _ from 'the-lodash'; 2 | import { ILogger } from 'the-logger'; 3 | 4 | import { K8sApiJsonSchema } from 'k8s-super-client/dist/open-api/converter/types'; 5 | import { ManifestPackage } from '../manifests/manifest-package'; 6 | 7 | import { LocalK8sRegistry } from './local-k8s-registry'; 8 | import { LocalSourceRegistry } from './local-source-registry'; 9 | import { K8sManifest } from '../manifests/k8s-manifest'; 10 | 11 | interface LocalRegistryPopulatorParams 12 | { 13 | allowDuplicates: boolean; 14 | } 15 | 16 | export class LocalRegistryPopulator 17 | { 18 | private _logger: ILogger; 19 | private _k8sJsonSchema : K8sApiJsonSchema; 20 | private _manifestPackage: ManifestPackage; 21 | private _options: LocalRegistryPopulatorParams; 22 | 23 | private _localSourceRegistry: LocalSourceRegistry; 24 | private _localK8sRegistry : LocalK8sRegistry; 25 | 26 | constructor(logger: ILogger, 27 | k8sJsonSchema: K8sApiJsonSchema, 28 | manifestPackage: ManifestPackage, 29 | options: LocalRegistryPopulatorParams) 30 | { 31 | this._logger = logger.sublogger('LocalRegistryPopulator'); 32 | this._k8sJsonSchema = k8sJsonSchema; 33 | this._manifestPackage = manifestPackage; 34 | this._options = options; 35 | 36 | this._localSourceRegistry = new LocalSourceRegistry(logger, manifestPackage); 37 | this._localK8sRegistry = new LocalK8sRegistry(logger); 38 | } 39 | 40 | get localK8sRegistry() { 41 | return this._localK8sRegistry; 42 | } 43 | 44 | process() 45 | { 46 | for(const manifest of this._manifestPackage.manifests) 47 | { 48 | this._processManifest(manifest); 49 | } 50 | 51 | if (this._options.allowDuplicates) 52 | { 53 | this._localSourceRegistry.cleanupDuplicates(); 54 | } 55 | else 56 | { 57 | this._localSourceRegistry.enforceDuplicateCheck(); 58 | } 59 | 60 | for(const manifest of this._manifestPackage.manifests) 61 | { 62 | this._localK8sRegistry.loadManifest(manifest); 63 | } 64 | } 65 | 66 | private _processManifest(manifest: K8sManifest) 67 | { 68 | this._localSourceRegistry.add(manifest); 69 | } 70 | 71 | } -------------------------------------------------------------------------------- /src/registry/local-source-registry.ts: -------------------------------------------------------------------------------- 1 | import _ from 'the-lodash'; 2 | import { ILogger } from 'the-logger'; 3 | import { K8sManifest } from '../manifests/k8s-manifest'; 4 | import { ManifestPackage } from '../manifests/manifest-package'; 5 | 6 | export class LocalSourceRegistry 7 | { 8 | private _logger: ILogger; 9 | private _multiFlatDict : Record = {}; 10 | private _flatDict : Record = {}; 11 | private _manifestPackage: ManifestPackage; 12 | 13 | constructor(logger: ILogger, manifestPackage: ManifestPackage) 14 | { 15 | this._logger = logger.sublogger('LocalSourceRegistry'); 16 | this._manifestPackage = manifestPackage; 17 | } 18 | 19 | get manifests() { 20 | return _.values(this._flatDict); 21 | } 22 | 23 | add(manifest: K8sManifest) 24 | { 25 | const key = _.stableStringify(manifest.id); 26 | this._flatDict[key] = manifest; 27 | if (!this._multiFlatDict[key]) { 28 | this._multiFlatDict[key] = []; 29 | } 30 | this._multiFlatDict[key].push(manifest); 31 | } 32 | 33 | extractDuplicateGroups() : K8sManifest[][] 34 | { 35 | const duplicateGroups : K8sManifest[][] = [] 36 | for(const key of _.keys(this._multiFlatDict)) 37 | { 38 | const manifests = this._multiFlatDict[key]; 39 | if (manifests.length > 1) 40 | { 41 | duplicateGroups.push(manifests); 42 | } 43 | } 44 | return duplicateGroups; 45 | } 46 | 47 | enforceDuplicateCheck() 48 | { 49 | const duplicateGroups = this.extractDuplicateGroups(); 50 | for(const manifests of duplicateGroups) 51 | { 52 | for(const manifest of manifests) 53 | { 54 | const message = 'Manifest is defined in multiple sources.'; 55 | manifest.reportError(message); 56 | } 57 | } 58 | } 59 | 60 | cleanupDuplicates() 61 | { 62 | const duplicateGroups = this.extractDuplicateGroups(); 63 | for(const manifests of duplicateGroups) 64 | { 65 | for(const manifest of _.dropRight(manifests, 1)) 66 | { 67 | const message = 'Manifest is duplicate and is skipped.'; 68 | manifest.reportWarning(message); 69 | // manifest.isSkipped = true; 70 | } 71 | 72 | { 73 | const message = 'Duplicate manifests found.'; 74 | _.last(manifests)!.reportWarning(message); 75 | } 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/rules-engine/execution/execution-context.ts: -------------------------------------------------------------------------------- 1 | import { ILogger } from "the-logger"; 2 | import { ManifestPackage } from "../../manifests/manifest-package"; 3 | import { RegistryQueryExecutor } from "../query-executor"; 4 | import { QueryExecutor } from "../query/query-executor"; 5 | 6 | export class ExecutionContext 7 | { 8 | private _logger : ILogger; 9 | private _registryQueryExecutor : RegistryQueryExecutor; 10 | private _manifestPackage : ManifestPackage; 11 | private _queryExecutor : QueryExecutor; 12 | 13 | constructor(logger : ILogger, registryQueryExecutor : RegistryQueryExecutor, manifestPackage : ManifestPackage) 14 | { 15 | this._logger = logger.sublogger("ExecutionContext"); 16 | this._registryQueryExecutor = registryQueryExecutor; 17 | this._manifestPackage = manifestPackage; 18 | this._queryExecutor = new QueryExecutor(this); 19 | } 20 | 21 | get logger () { 22 | return this._logger; 23 | } 24 | 25 | get registryQueryExecutor() { 26 | return this._registryQueryExecutor; 27 | } 28 | 29 | get manifestPackage() { 30 | return this._manifestPackage; 31 | } 32 | 33 | get queryExecutor() { 34 | return this._queryExecutor; 35 | } 36 | } -------------------------------------------------------------------------------- /src/rules-engine/execution/manifest-violation.ts: -------------------------------------------------------------------------------- 1 | import { K8sManifest } from "../../manifests/k8s-manifest"; 2 | import { BaseObject } from "../../types/base-object"; 3 | import { ManifestResult } from "../../types/manifest-result"; 4 | 5 | export class ManifestViolation extends BaseObject 6 | { 7 | private _manifest: K8sManifest; 8 | 9 | constructor(manifest: K8sManifest) 10 | { 11 | super(); 12 | this._manifest = manifest; 13 | } 14 | 15 | get manifest() { 16 | return this._manifest; 17 | } 18 | 19 | exportResult() : ManifestResult 20 | { 21 | const result : ManifestResult = { 22 | ...this.manifest.id, 23 | ...this.extractBaseResult(), 24 | sources: this.manifest.exportSourcesResult(), 25 | }; 26 | 27 | return result; 28 | } 29 | } -------------------------------------------------------------------------------- /src/rules-engine/helpers/data-structs/index.ts: -------------------------------------------------------------------------------- 1 | import _ from 'the-lodash'; 2 | 3 | export function findDuplicates(items: T[], selector?: (item: T) => string) : string[] 4 | { 5 | if (!items) { 6 | return []; 7 | } 8 | 9 | let list : any[] = items; 10 | list = list.filter(x => x); 11 | 12 | if (selector) { 13 | list = items.map(selector); 14 | } 15 | 16 | const dict: Record = {}; 17 | const duplicates: Record = {}; 18 | 19 | for(const x of list) 20 | { 21 | const key = x.toString(); 22 | if (dict[key]) { 23 | duplicates[key] = true; 24 | } else { 25 | dict[key] = true; 26 | } 27 | } 28 | 29 | return _.keys(duplicates); 30 | } 31 | -------------------------------------------------------------------------------- /src/rules-engine/helpers/gateway-api/index.ts: -------------------------------------------------------------------------------- 1 | 2 | const DICT : { 3 | [group: string] : { 4 | [kind: string] : { 5 | [name: string] : boolean 6 | } 7 | } 8 | } = { 9 | 'traefik.containo.us': { 10 | 'TraefikService': { 11 | 'api@internal': true, 12 | 'default@internal': true, 13 | }, 14 | }, 15 | }; 16 | 17 | export function isInternalService(group: string, kind: string, name: string) 18 | { 19 | const x = DICT[group]; 20 | if (!x) { 21 | return false; 22 | } 23 | const y = x[kind]; 24 | if (!y) { 25 | return false; 26 | } 27 | const z = y[name]; 28 | if (!z) { 29 | return false; 30 | } 31 | return true; 32 | } 33 | 34 | -------------------------------------------------------------------------------- /src/rules-engine/helpers/image/index.ts: -------------------------------------------------------------------------------- 1 | import _ from 'the-lodash'; 2 | 3 | export function parseImage(fullImage: string) : ImageTag 4 | { 5 | if (!fullImage) { 6 | return { 7 | isInvalid: true, 8 | registry: '', 9 | repository: fullImage, 10 | namespace: '', 11 | name: fullImage, 12 | tag: 'latest', 13 | } 14 | } 15 | 16 | let parts = fullImage.split('/'); 17 | if (parts.length === 0) { 18 | return { 19 | isInvalid: true, 20 | registry: '', 21 | repository: fullImage, 22 | namespace: '', 23 | name: fullImage, 24 | tag: 'latest', 25 | } 26 | } 27 | 28 | const tagInfo = parseTag(_.last(parts)!); 29 | 30 | parts = _.dropRight(parts); 31 | parts.push(tagInfo.name); 32 | 33 | let registry = 'docker.io'; 34 | if (hasRegistry(parts)) { 35 | registry = _.head(parts)!; 36 | parts = _.drop(parts); 37 | } 38 | 39 | let namespace = ''; 40 | if (parts.length >= 2) { 41 | namespace = _.dropRight(parts).join('/'); 42 | } 43 | 44 | const repository = parts.join('/'); 45 | 46 | const result : ImageTag = { 47 | registry: registry, 48 | repository: repository, 49 | namespace: namespace, 50 | name: _.last(parts) ?? '', 51 | tag: tagInfo.tag 52 | } 53 | 54 | return result 55 | } 56 | 57 | function parseTag(str: string) : { name: string, tag: string } 58 | { 59 | const index = str.lastIndexOf(':'); 60 | if (index === -1) { 61 | return { 62 | name: str, 63 | tag: 'latest' 64 | } 65 | } else { 66 | return { 67 | name: str.substring(0, index), 68 | tag: str.substring(index + 1), 69 | } 70 | } 71 | } 72 | 73 | function hasRegistry(parts: string[]) 74 | { 75 | if (parts.length >= 3) { 76 | return true; 77 | } 78 | 79 | return false; 80 | } 81 | 82 | export interface ImageTag 83 | { 84 | isInvalid?: boolean, 85 | registry: string, 86 | repository: string, 87 | namespace: string, 88 | name: string, 89 | tag: string, 90 | } -------------------------------------------------------------------------------- /src/rules-engine/helpers/label-lookup/dict.ts: -------------------------------------------------------------------------------- 1 | import _ from 'the-lodash'; 2 | import { ScriptItem } from "../../script-item"; 3 | 4 | export class LabelLookupDict 5 | { 6 | private _labels : LabelItem[] = [] 7 | private _globalLookup : Record = {}; 8 | 9 | add(item: ScriptItem, labels: Record | undefined) 10 | { 11 | labels = labels ?? {}; 12 | 13 | const labelItem : LabelItem = { 14 | item: item, 15 | labels: labels, 16 | lookup: {} 17 | }; 18 | this._labels.push(labelItem); 19 | 20 | for(const key of _.keys(labels)) 21 | { 22 | const lookupKey = makeLookupKey(key, labels[key]); 23 | labelItem.lookup[lookupKey] = true; 24 | 25 | if (!this._globalLookup[lookupKey]) { 26 | this._globalLookup[lookupKey] = []; 27 | } 28 | this._globalLookup[lookupKey].push(labelItem); 29 | } 30 | 31 | } 32 | 33 | resolveSelector(selector: Record | undefined) : ScriptItem[] 34 | { 35 | selector = selector ?? {}; 36 | 37 | const selectorLookupKeys : string[] = [] 38 | for(const key of Object.keys(selector)) 39 | { 40 | const lookupKey = makeLookupKey(key, selector[key]); 41 | selectorLookupKeys.push(lookupKey); 42 | } 43 | 44 | const found: ScriptItem[] = [] 45 | for(const labelItem of this._labels) 46 | { 47 | if (this._matchesDict(labelItem, selectorLookupKeys)) 48 | { 49 | found.push(labelItem.item); 50 | } 51 | } 52 | return found; 53 | } 54 | 55 | matchesSelector(selector: Record | undefined) : boolean 56 | { 57 | return this.resolveSelector(selector).length > 0; 58 | } 59 | 60 | private _matchesDict(labelItem: LabelItem, selectorLookupKeys : string[]) 61 | { 62 | for(const lookupKey of selectorLookupKeys) 63 | { 64 | if (!labelItem.lookup[lookupKey]) { 65 | return false; 66 | } 67 | } 68 | return true; 69 | } 70 | } 71 | 72 | interface LabelItem 73 | { 74 | item: ScriptItem, 75 | labels: Record, 76 | lookup: Record, 77 | } 78 | 79 | function makeLookupKey(key: string, value: string) 80 | { 81 | return _.stableStringify({ key, value }); 82 | } -------------------------------------------------------------------------------- /src/rules-engine/helpers/label-lookup/index.ts: -------------------------------------------------------------------------------- 1 | import { LabelLookupDict } from "./dict"; 2 | 3 | export function newLabelLookupDict() 4 | { 5 | return new LabelLookupDict(); 6 | } -------------------------------------------------------------------------------- /src/rules-engine/helpers/labels/index.ts: -------------------------------------------------------------------------------- 1 | import _ from 'the-lodash'; 2 | 3 | export function labelsToString(dict?: Record) : string 4 | { 5 | dict = dict || {}; 6 | const parts = _.orderBy(_.keys(dict)).map(x => `${x}=${dict![x]}`); 7 | return `[${parts.join(', ')}]`; 8 | } -------------------------------------------------------------------------------- /src/rules-engine/helpers/name-lookup/dict.ts: -------------------------------------------------------------------------------- 1 | import _ from 'the-lodash'; 2 | import { ScriptItem } from "../../script-item"; 3 | 4 | export class NameLookupDict 5 | { 6 | private _dict : Record = {}; 7 | 8 | get length() { 9 | return _.keys(this._dict).length; 10 | } 11 | 12 | add(item: ScriptItem) 13 | { 14 | this._dict[item.name ?? ""] = item; 15 | } 16 | 17 | addMany(items?: ScriptItem[]) 18 | { 19 | if (items) { 20 | for(const x of items) 21 | { 22 | this.add(x); 23 | } 24 | } 25 | } 26 | 27 | resolve(name: string) : ScriptItem | null 28 | { 29 | name = name ?? ""; 30 | return this._dict[name] ?? null; 31 | } 32 | 33 | contains(name: string) : boolean 34 | { 35 | return this.resolve(name) ? true : false; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/rules-engine/helpers/name-lookup/index.ts: -------------------------------------------------------------------------------- 1 | import { ScriptItem } from "../../script-item"; 2 | import { NameLookupDict } from "./dict"; 3 | 4 | export function newNameLookupDict(items?: ScriptItem[]) 5 | { 6 | const dict = new NameLookupDict(); 7 | dict.addMany(items); 8 | return dict; 9 | } -------------------------------------------------------------------------------- /src/rules-engine/helpers/rule-helpers.ts: -------------------------------------------------------------------------------- 1 | import { parseImage } from './image'; 2 | import { newLabelLookupDict } from './label-lookup'; 3 | import { newNameLookupDict } from './name-lookup'; 4 | import { labelsToString } from './labels'; 5 | import { findDuplicates } from './data-structs'; 6 | import { isInternalService } from './gateway-api'; 7 | import { parseRef } from './vendor/traefik'; 8 | 9 | export const RULE_HELPERS = { 10 | parseImage, 11 | newLabelLookupDict, 12 | newNameLookupDict, 13 | labelsToString, 14 | findDuplicates, 15 | 16 | gateway: { 17 | isInternalService: isInternalService 18 | }, 19 | 20 | traefik: { 21 | parseRef: parseRef 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/rules-engine/helpers/vendor/traefik.ts: -------------------------------------------------------------------------------- 1 | export function parseRef(ref: string) : { name: string, isK8s: boolean, type: string } 2 | { 3 | const parts = ref.split('@'); 4 | if (parts.length == 1) { 5 | return { 6 | name: ref, 7 | type: 'kubernetescrd', 8 | isK8s: true, 9 | } 10 | } else if (parts.length == 2) { 11 | return { 12 | name: parts[0], 13 | type: parts[1], 14 | isK8s: parts[1] === 'kubernetescrd' 15 | } 16 | } else { 17 | return { 18 | name: ref, 19 | type: '', 20 | isK8s: false 21 | }; 22 | } 23 | } 24 | 25 | -------------------------------------------------------------------------------- /src/rules-engine/query-executor.ts: -------------------------------------------------------------------------------- 1 | import { K8sManifest } from "../manifests/k8s-manifest"; 2 | import { KeyValueDict } from "./query-spec/k8s/k8s-target-query"; 3 | 4 | export interface RegistryQueryExecutor 5 | { 6 | query(query: RegistryQueryOptions) : K8sManifest[] 7 | } 8 | 9 | export interface RegistryQueryOptions 10 | { 11 | apiName?: string, 12 | version?: string, 13 | kind?: string, 14 | namespace?: string | null, 15 | 16 | nameFilters?: string[], 17 | labelFilters?: KeyValueDict[], 18 | } 19 | -------------------------------------------------------------------------------- /src/rules-engine/query-spec/base.ts: -------------------------------------------------------------------------------- 1 | import { ScriptItem } from "../script-item"; 2 | 3 | export interface BaseTargetQuery 4 | { 5 | kind: TargetQueryKind; 6 | } 7 | 8 | export interface SyncBaseTargetQuery extends BaseTargetQuery 9 | { 10 | many() : ScriptItem[]; 11 | single() : ScriptItem | null; 12 | count() : number; 13 | } 14 | 15 | export enum TargetQueryKind 16 | { 17 | Shortcut = 'Shortcut', 18 | 19 | K8s = 'K8s', 20 | 21 | Union = 'Union', 22 | Transform = 'Transform', 23 | TransformMany = 'TransformMany', 24 | Filter = 'Filter', 25 | First = 'First', 26 | 27 | Manual = 'Manual', 28 | } 29 | 30 | export interface QueryScopeLimiter 31 | { 32 | namespace?: string | null | undefined; 33 | } 34 | -------------------------------------------------------------------------------- /src/rules-engine/query-spec/filter/filter-target-query.ts: -------------------------------------------------------------------------------- 1 | import { ScriptItem } from '../../script-item'; 2 | import { BaseTargetQuery, TargetQueryKind } from '../base'; 3 | 4 | export type FilterFunc = (item: ScriptItem) => boolean; 5 | 6 | export class FilterTargetQuery implements BaseTargetQuery 7 | { 8 | private _kind = TargetQueryKind.Filter; 9 | 10 | _inner: BaseTargetQuery; 11 | _func?: FilterFunc; 12 | 13 | constructor(inner: BaseTargetQuery) 14 | { 15 | this._inner = inner; 16 | } 17 | 18 | get kind() { 19 | return this._kind; 20 | } 21 | 22 | Criteria(func: FilterFunc) 23 | { 24 | this._func = func; 25 | return this; 26 | } 27 | 28 | } 29 | 30 | -------------------------------------------------------------------------------- /src/rules-engine/query-spec/first/first-target-query.ts: -------------------------------------------------------------------------------- 1 | import { BaseTargetQuery, TargetQueryKind } from '../base'; 2 | 3 | export class FirstTargetQuery implements BaseTargetQuery 4 | { 5 | private _kind = TargetQueryKind.Union; 6 | 7 | _inner: BaseTargetQuery[]; 8 | 9 | constructor(...inner: BaseTargetQuery[]) 10 | { 11 | this._inner = inner; 12 | } 13 | 14 | get kind() { 15 | return this._kind; 16 | } 17 | } 18 | 19 | -------------------------------------------------------------------------------- /src/rules-engine/query-spec/k8s/k8s-target-query.ts: -------------------------------------------------------------------------------- 1 | import { parseApiVersion } from '../../../utils/k8s'; 2 | import { BaseTargetQuery, TargetQueryKind } from '../base'; 3 | 4 | export interface KeyValueDict { 5 | [name: string]: string 6 | } 7 | 8 | export interface K8sTargetFilter 9 | { 10 | apiName?: string, 11 | version?: string, 12 | kind?: string, 13 | 14 | namespace?: string | null, 15 | isAllNamespaces?: boolean, 16 | isClusterScope?: boolean, 17 | 18 | nameFilters?: string[], 19 | labelFilters?: KeyValueDict[], 20 | } 21 | 22 | export class K8sTargetQuery implements BaseTargetQuery 23 | { 24 | private _kind = TargetQueryKind.K8s; 25 | 26 | _data : K8sTargetFilter = { 27 | 28 | namespace: null, 29 | isAllNamespaces : false, 30 | isClusterScope: false, 31 | 32 | nameFilters: [], 33 | labelFilters: [], 34 | } 35 | 36 | get kind() { 37 | return this._kind; 38 | } 39 | 40 | ApiVersion(apiVersion: string) 41 | { 42 | const parsed = parseApiVersion(apiVersion); 43 | if (!parsed) { 44 | throw new Error(`Invalid apiVersion: ${apiVersion}`); 45 | } 46 | this._data.apiName = parsed.group; 47 | this._data.version = parsed.version; 48 | return this; 49 | } 50 | 51 | Api(apiName: string) 52 | { 53 | this._data.apiName = apiName; 54 | return this; 55 | } 56 | 57 | Version(version: string) 58 | { 59 | this._data.version = version; 60 | return this; 61 | } 62 | 63 | Kind(kind: string) 64 | { 65 | this._data.kind = kind; 66 | return this; 67 | } 68 | 69 | namespace(value: string) 70 | { 71 | this._data.namespace = value; 72 | return this; 73 | } 74 | 75 | isClusterScope(value?: boolean) 76 | { 77 | this._data.isClusterScope = value ?? false; 78 | return this; 79 | } 80 | 81 | allNamespaces() 82 | { 83 | this._data.isAllNamespaces = true; 84 | return this; 85 | } 86 | 87 | name(value: string) 88 | { 89 | this._data.nameFilters!.push(value); 90 | return this; 91 | } 92 | 93 | label(key: string, value: string) { 94 | const filter: KeyValueDict = {}; 95 | filter[key] = value; 96 | return this.labels(filter) 97 | } 98 | 99 | labels(value: KeyValueDict) { 100 | this._data.labelFilters!.push(value) 101 | return this 102 | } 103 | 104 | } -------------------------------------------------------------------------------- /src/rules-engine/query-spec/manual/manual-target-query.ts: -------------------------------------------------------------------------------- 1 | import { ScriptItem } from '../../script-item'; 2 | import { BaseTargetQuery, TargetQueryKind } from '../base'; 3 | 4 | export interface ManualQueryFuncArgs 5 | { 6 | many(query: BaseTargetQuery) : ScriptItem[]; 7 | single(query: BaseTargetQuery) : ScriptItem | null; 8 | count(query: BaseTargetQuery) : number; 9 | } 10 | 11 | export type ManualQueryFunc = (args: ManualQueryFuncArgs) => ScriptItem[]; 12 | 13 | export class ManualTargetQuery implements BaseTargetQuery 14 | { 15 | private _kind = TargetQueryKind.Manual; 16 | 17 | _func: ManualQueryFunc; 18 | 19 | constructor(func: ManualQueryFunc) 20 | { 21 | this._func = func; 22 | } 23 | 24 | get kind() { 25 | return this._kind; 26 | } 27 | 28 | } 29 | 30 | -------------------------------------------------------------------------------- /src/rules-engine/query-spec/shortcut/shortcut-target-query.ts: -------------------------------------------------------------------------------- 1 | import { BaseTargetQuery, TargetQueryKind } from '../base'; 2 | 3 | 4 | export class ShortcutTargetQuery implements BaseTargetQuery 5 | { 6 | private _kind = TargetQueryKind.Shortcut; 7 | 8 | _name : string | null = null; 9 | _args : any[] = []; 10 | 11 | get kind() { 12 | return this._kind; 13 | } 14 | 15 | Shortcut(name: string, ...args: any[]) 16 | { 17 | this._name = name; 18 | this._args = args ?? []; 19 | return this; 20 | } 21 | 22 | } -------------------------------------------------------------------------------- /src/rules-engine/query-spec/sync-scope-builder.ts: -------------------------------------------------------------------------------- 1 | import _ from 'the-lodash' 2 | import { ILogger } from 'the-logger/dist'; 3 | import { ExecutionContext } from '../execution/execution-context'; 4 | import { QueryExecutorScope } from '../query/query-executor-scope'; 5 | import { QueryScopeLimiter, SyncBaseTargetQuery } from './base'; 6 | import { TargetQueryFunc, TARGET_QUERY_BUILDER_DICT } from './scope-builder'; 7 | 8 | export type SyncTargetQueryFunc = (...args : any[]) => SyncBaseTargetQuery; 9 | 10 | export function buildQueryableScope(executionContext : ExecutionContext, limiter: QueryScopeLimiter) : Record 11 | { 12 | const syncQueryBuilder : Record = {}; 13 | 14 | for(const key of _.keys(TARGET_QUERY_BUILDER_DICT)) 15 | { 16 | const builder = TARGET_QUERY_BUILDER_DICT[key]; 17 | const syncQuery = new SyncQueryBuilder(executionContext, builder); 18 | 19 | syncQueryBuilder[key] = syncQuery.wrap(limiter); 20 | } 21 | 22 | return syncQueryBuilder; 23 | } 24 | 25 | class SyncQueryBuilder 26 | { 27 | private _logger : ILogger; 28 | private _executionContext : ExecutionContext; 29 | private _queryBuilder: TargetQueryFunc; 30 | 31 | constructor(executionContext : ExecutionContext, queryBuilder: TargetQueryFunc) 32 | { 33 | this._executionContext = executionContext; 34 | this._queryBuilder = queryBuilder; 35 | this._logger = this._executionContext.logger.sublogger("SyncQueryBuilder"); 36 | } 37 | 38 | wrap(limiter: QueryScopeLimiter) 39 | { 40 | const queryExecutorScope = new QueryExecutorScope(this._executionContext, limiter); 41 | 42 | const wrapper = (...args : any[]) => { 43 | 44 | const queryTarget = this._queryBuilder.apply(null, args) as SyncBaseTargetQuery; 45 | 46 | queryTarget.many = () => { 47 | return queryExecutorScope.many(queryTarget); 48 | }; 49 | 50 | queryTarget.single = () => { 51 | return queryExecutorScope.single(queryTarget); 52 | }; 53 | 54 | queryTarget.count = () => { 55 | return queryExecutorScope.count(queryTarget); 56 | }; 57 | 58 | return queryTarget; 59 | }; 60 | 61 | return wrapper; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/rules-engine/query-spec/transform-many/transform-many-target-query.ts: -------------------------------------------------------------------------------- 1 | import { K8sObject } from '../../../types/k8s'; 2 | import { ScriptItem } from '../../script-item'; 3 | import { BaseTargetQuery, TargetQueryKind } from '../base'; 4 | 5 | export type TransformerManyFunc = (item: ScriptItem) => K8sObject[]; 6 | 7 | export class TransformManyTargetQuery implements BaseTargetQuery 8 | { 9 | private _kind = TargetQueryKind.TransformMany; 10 | 11 | _inner: BaseTargetQuery; 12 | _func?: TransformerManyFunc; 13 | 14 | constructor(inner: BaseTargetQuery) 15 | { 16 | this._inner = inner; 17 | } 18 | 19 | get kind() { 20 | return this._kind; 21 | } 22 | 23 | To(func: TransformerManyFunc) 24 | { 25 | this._func = func; 26 | return this; 27 | } 28 | 29 | } 30 | 31 | -------------------------------------------------------------------------------- /src/rules-engine/query-spec/transform/transform-target-query.ts: -------------------------------------------------------------------------------- 1 | import { K8sObject } from '../../../types/k8s'; 2 | import { ScriptItem } from '../../script-item'; 3 | import { BaseTargetQuery, TargetQueryKind } from '../base'; 4 | 5 | export type TransformerFunc = (item: ScriptItem) => K8sObject; 6 | 7 | export class TransformTargetQuery implements BaseTargetQuery 8 | { 9 | private _kind = TargetQueryKind.Transform; 10 | 11 | _inner: BaseTargetQuery; 12 | _func?: TransformerFunc; 13 | 14 | constructor(inner: BaseTargetQuery) 15 | { 16 | this._inner = inner; 17 | } 18 | 19 | get kind() { 20 | return this._kind; 21 | } 22 | 23 | To(func: TransformerFunc) 24 | { 25 | this._func = func; 26 | return this; 27 | } 28 | 29 | } 30 | 31 | -------------------------------------------------------------------------------- /src/rules-engine/query-spec/union/union-target-query.ts: -------------------------------------------------------------------------------- 1 | import { BaseTargetQuery, TargetQueryKind } from '../base'; 2 | 3 | export class UnionTargetQuery implements BaseTargetQuery 4 | { 5 | private _kind = TargetQueryKind.Union; 6 | 7 | _inner: BaseTargetQuery[]; 8 | 9 | constructor(...inner: BaseTargetQuery[]) 10 | { 11 | this._inner = inner; 12 | } 13 | 14 | get kind() { 15 | return this._kind; 16 | } 17 | } 18 | 19 | -------------------------------------------------------------------------------- /src/rules-engine/query/base.ts: -------------------------------------------------------------------------------- 1 | import { BaseTargetQuery, QueryScopeLimiter } from "../query-spec/base"; 2 | import { ScriptItem } from "../script-item"; 3 | 4 | export interface IQueryExecutor 5 | { 6 | execute(query: T, limiter: QueryScopeLimiter) : QueryResult; 7 | } 8 | 9 | export interface QueryResult { 10 | success: boolean, 11 | messages?: string[], 12 | items?: ScriptItem[], 13 | } 14 | -------------------------------------------------------------------------------- /src/rules-engine/query/filter/filter-query-executor.ts: -------------------------------------------------------------------------------- 1 | 2 | import _ from 'the-lodash' 3 | import { ILogger } from 'the-logger'; 4 | import { ExecutionContext } from '../../execution/execution-context'; 5 | import { QueryResult } from '../base'; 6 | import { IQueryExecutor } from '../base'; 7 | import { QueryScopeLimiter } from '../../query-spec/base'; 8 | import { FilterTargetQuery } from '../../query-spec/filter/filter-target-query'; 9 | 10 | export class FilterQueryExecutor implements IQueryExecutor 11 | { 12 | private _logger : ILogger; 13 | private _executionContext : ExecutionContext; 14 | 15 | constructor(executionContext : ExecutionContext) 16 | { 17 | this._executionContext = executionContext; 18 | this._logger = executionContext.logger.sublogger("FilterQueryExecutor"); 19 | } 20 | 21 | execute(query: FilterTargetQuery, limiter: QueryScopeLimiter) : QueryResult 22 | { 23 | const result : QueryResult = { 24 | success: true, 25 | items: [] 26 | }; 27 | 28 | const innerResult = this._executionContext.queryExecutor.execute(query._inner, limiter); 29 | 30 | if (innerResult.items) 31 | { 32 | for(const item of innerResult.items) 33 | { 34 | if (query._func) 35 | { 36 | if (query._func(item)) 37 | { 38 | result.items!.push(item); 39 | } 40 | } 41 | } 42 | } 43 | 44 | return result; 45 | } 46 | 47 | } -------------------------------------------------------------------------------- /src/rules-engine/query/first/first-query-executor.ts: -------------------------------------------------------------------------------- 1 | 2 | import _ from 'the-lodash' 3 | import { ILogger } from 'the-logger'; 4 | import { ExecutionContext } from '../../execution/execution-context'; 5 | import { QueryResult } from '../base'; 6 | import { IQueryExecutor } from '../base'; 7 | import { QueryScopeLimiter } from '../../query-spec/base'; 8 | import { FirstTargetQuery } from '../../query-spec/first/first-target-query'; 9 | 10 | export class FirstQueryExecutor implements IQueryExecutor 11 | { 12 | private _logger : ILogger; 13 | private _executionContext : ExecutionContext; 14 | 15 | constructor(executionContext : ExecutionContext) 16 | { 17 | this._executionContext = executionContext; 18 | this._logger = executionContext.logger.sublogger("FirstQueryExecutor"); 19 | } 20 | 21 | execute(query: FirstTargetQuery, limiter: QueryScopeLimiter) : QueryResult 22 | { 23 | for(const innerQuery of query._inner) 24 | { 25 | const innerResult = this._executionContext.queryExecutor.execute(innerQuery, limiter); 26 | if (innerResult.items) 27 | { 28 | const item = _.head(innerResult.items); 29 | if (item) { 30 | return { 31 | success: true, 32 | items: [item] 33 | } 34 | } 35 | } 36 | } 37 | 38 | return { 39 | success: false, 40 | items: [] 41 | } 42 | } 43 | 44 | } -------------------------------------------------------------------------------- /src/rules-engine/query/k8s/k8s-query-executor.ts: -------------------------------------------------------------------------------- 1 | 2 | import _ from 'the-lodash' 3 | import { ILogger } from 'the-logger'; 4 | import { ScriptItem } from '../../script-item'; 5 | import { ExecutionContext } from '../../execution/execution-context'; 6 | import { K8sTargetQuery } from '../../query-spec/k8s/k8s-target-query'; 7 | import { QueryResult } from '../base'; 8 | import { IQueryExecutor } from '../base'; 9 | import { QueryScopeLimiter } from '../../query-spec/base'; 10 | import { RegistryQueryOptions } from '../../query-executor'; 11 | 12 | 13 | export class K8sQueryExecutor implements IQueryExecutor 14 | { 15 | private _logger : ILogger; 16 | private _executionContext : ExecutionContext; 17 | 18 | constructor(executionContext : ExecutionContext) 19 | { 20 | this._executionContext = executionContext; 21 | this._logger = executionContext.logger.sublogger("K8sQueryExecutor"); 22 | } 23 | 24 | execute(query: K8sTargetQuery, limiter: QueryScopeLimiter) : QueryResult 25 | { 26 | // this._logger.info("[execute] RUNNING QUERY...."); 27 | 28 | const queryData = query._data; 29 | 30 | if (!queryData.kind) { 31 | // this._logger.info("[execute] Exiting. No Kind..."); 32 | return { 33 | success: false, 34 | messages: ['Kind not set'], 35 | } 36 | } 37 | 38 | if (_.isUndefined(queryData.apiName)) { 39 | // this._logger.info("[execute] Exiting. No ApiName..."); 40 | return { 41 | success: false, 42 | messages: ['apiName not set'], 43 | } 44 | } 45 | 46 | const k8sQueryFilter : RegistryQueryOptions = { 47 | apiName: queryData.apiName, 48 | version: queryData.version, 49 | kind: queryData.kind, 50 | 51 | nameFilters: queryData.nameFilters, 52 | labelFilters: queryData.labelFilters, 53 | } 54 | 55 | const isClusterScope = queryData.isClusterScope; 56 | if (!isClusterScope) 57 | { 58 | if (!queryData.isAllNamespaces) 59 | { 60 | k8sQueryFilter.namespace = (queryData.namespace ?? limiter.namespace) || undefined; 61 | } 62 | } 63 | // this._logger.info("[execute] Filter: ", k8sQueryFilter); 64 | 65 | const manifests = this._executionContext.registryQueryExecutor.query(k8sQueryFilter); 66 | 67 | const result : QueryResult = { 68 | success: true, 69 | items: manifests.map(x => new ScriptItem(x)) 70 | }; 71 | 72 | // this._logger.info("[execute] RESULT COUNT: %s", result.items!.length); 73 | 74 | return result; 75 | } 76 | 77 | } -------------------------------------------------------------------------------- /src/rules-engine/query/manual/manual-query-executor.ts: -------------------------------------------------------------------------------- 1 | 2 | import _ from 'the-lodash' 3 | import { ILogger } from 'the-logger'; 4 | import { ExecutionContext } from '../../execution/execution-context'; 5 | import { QueryResult } from '../base'; 6 | import { IQueryExecutor } from '../base'; 7 | import { QueryScopeLimiter } from '../../query-spec/base'; 8 | import { ManualQueryFuncArgs, ManualTargetQuery } from '../../query-spec/manual/manual-target-query'; 9 | import { QueryExecutorScope } from '../query-executor-scope'; 10 | 11 | export class ManualQueryExecutor implements IQueryExecutor 12 | { 13 | private _logger : ILogger; 14 | private _executionContext : ExecutionContext; 15 | 16 | constructor(executionContext : ExecutionContext) 17 | { 18 | this._executionContext = executionContext; 19 | this._logger = executionContext.logger.sublogger("ManualQueryExecutor"); 20 | } 21 | 22 | execute(query: ManualTargetQuery, limiter: QueryScopeLimiter) : QueryResult 23 | { 24 | const result : QueryResult = { 25 | success: true, 26 | items: [] 27 | }; 28 | 29 | const func = query._func; 30 | if (func) 31 | { 32 | const queryScope = new QueryExecutorScope(this._executionContext, limiter); 33 | const funcArgs : ManualQueryFuncArgs = { 34 | many: (innerQuery) => queryScope.many(innerQuery), 35 | single: (innerQuery) => queryScope.single(innerQuery), 36 | count: (innerQuery) => queryScope.count(innerQuery) 37 | } 38 | 39 | const scriptItems = func(funcArgs) ?? []; 40 | for (const item of scriptItems) 41 | { 42 | result.items!.push(item); 43 | } 44 | } 45 | 46 | return result; 47 | } 48 | 49 | } -------------------------------------------------------------------------------- /src/rules-engine/query/query-executor-scope.ts: -------------------------------------------------------------------------------- 1 | import _ from 'the-lodash'; 2 | import { ILogger } from "the-logger"; 3 | import { ExecutionContext } from "../execution/execution-context"; 4 | import { BaseTargetQuery, QueryScopeLimiter } from "../query-spec/base"; 5 | import { ScriptItem } from '../script-item'; 6 | 7 | export class QueryExecutorScope 8 | { 9 | private _logger : ILogger; 10 | private _executionContext : ExecutionContext; 11 | private _limiter: QueryScopeLimiter; 12 | 13 | constructor(executionContext : ExecutionContext, limiter: QueryScopeLimiter) 14 | { 15 | this._logger = executionContext.logger.sublogger("QueryExecutorScope"); 16 | this._executionContext = executionContext; 17 | this._limiter = limiter; 18 | } 19 | 20 | many(query: BaseTargetQuery) : ScriptItem[] 21 | { 22 | const result = this._executionContext.queryExecutor.execute(query, this._limiter); 23 | if (result.messages) { 24 | for(const x of result.messages) { 25 | this._logger.error("Error in query: %s", x); 26 | } 27 | } 28 | return result.items ?? []; 29 | } 30 | 31 | single(query: BaseTargetQuery) : ScriptItem | null 32 | { 33 | return _.head(this.many(query)) ?? null; 34 | } 35 | 36 | count(query: BaseTargetQuery) : number 37 | { 38 | return this.many(query).length; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/rules-engine/query/query-executor.ts: -------------------------------------------------------------------------------- 1 | import { ILogger } from "the-logger"; 2 | import { ExecutionContext } from "../execution/execution-context"; 3 | import { BaseTargetQuery, QueryScopeLimiter, TargetQueryKind } from "../query-spec/base"; 4 | import { IQueryExecutor, QueryResult } from "./base"; 5 | import { FilterQueryExecutor } from "./filter/filter-query-executor"; 6 | import { FirstQueryExecutor } from "./first/first-query-executor"; 7 | import { K8sQueryExecutor } from "./k8s/k8s-query-executor"; 8 | import { ManualQueryExecutor } from "./manual/manual-query-executor"; 9 | import { ShortcutQueryExecutor } from "./shortcut/shortcut-query-executor"; 10 | import { TransformManyQueryExecutor } from "./transform-many/transform-many-query-executor"; 11 | import { TransformQueryExecutor } from "./transform/transform-query-executor"; 12 | import { UnionQueryExecutor } from "./union/union-query-executor"; 13 | 14 | export class QueryExecutor implements IQueryExecutor 15 | { 16 | private _logger : ILogger; 17 | private _executionContext : ExecutionContext; 18 | private _resolvers: Record> = {}; 19 | 20 | constructor(executionContext : ExecutionContext) 21 | { 22 | this._logger = executionContext.logger.sublogger("QueryExecutor"); 23 | this._executionContext = executionContext; 24 | 25 | this._setup(); 26 | } 27 | 28 | private _setup() 29 | { 30 | this._resolvers[TargetQueryKind.Shortcut] = new ShortcutQueryExecutor(this._executionContext); 31 | this._resolvers[TargetQueryKind.K8s] = new K8sQueryExecutor(this._executionContext); 32 | this._resolvers[TargetQueryKind.Union] = new UnionQueryExecutor(this._executionContext); 33 | this._resolvers[TargetQueryKind.Transform] = new TransformQueryExecutor(this._executionContext); 34 | this._resolvers[TargetQueryKind.TransformMany] = new TransformManyQueryExecutor(this._executionContext); 35 | this._resolvers[TargetQueryKind.Filter] = new FilterQueryExecutor(this._executionContext); 36 | this._resolvers[TargetQueryKind.First] = new FirstQueryExecutor(this._executionContext); 37 | this._resolvers[TargetQueryKind.Manual] = new ManualQueryExecutor(this._executionContext); 38 | } 39 | 40 | execute(query: BaseTargetQuery, limiter: QueryScopeLimiter) : QueryResult 41 | { 42 | this._logger.debug("[execute] %s", query.kind); 43 | 44 | const resolver = this._resolvers[query.kind]; 45 | if (!resolver) { 46 | this._logger.error("Internal Error. Unknown query kind: %s", query.kind); 47 | 48 | return { 49 | success: false, 50 | messages: [ 51 | `Internal Error. Unknown query kind: ${query.kind}` 52 | ] 53 | }; 54 | } 55 | 56 | return resolver.execute(query, limiter); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/rules-engine/query/shortcut/shortcut-query-executor.ts: -------------------------------------------------------------------------------- 1 | 2 | import _ from 'the-lodash' 3 | import { ILogger } from 'the-logger'; 4 | import { ExecutionContext } from '../../execution/execution-context'; 5 | import { QueryResult } from '../base'; 6 | import { IQueryExecutor } from '../base'; 7 | import { BaseTargetQuery, QueryScopeLimiter } from '../../query-spec/base'; 8 | import { ShortcutTargetQuery } from '../../query-spec/shortcut/shortcut-target-query'; 9 | import { TARGET_QUERY_BUILDER_OBJ } from '../../query-spec/scope-builder'; 10 | import { setup } from './library'; 11 | 12 | export type ShortcutFunc = (...args: any[]) => BaseTargetQuery; 13 | 14 | export class ShortcutQueryExecutor implements IQueryExecutor 15 | { 16 | private _logger : ILogger; 17 | private _executionContext : ExecutionContext; 18 | 19 | private _shortcuts : Record = {}; 20 | 21 | constructor(executionContext : ExecutionContext) 22 | { 23 | this._executionContext = executionContext; 24 | this._logger = executionContext.logger.sublogger("ShortcutQueryExecutor"); 25 | 26 | setup(this); 27 | } 28 | 29 | execute(query: ShortcutTargetQuery, limiter: QueryScopeLimiter) : QueryResult 30 | { 31 | const shortcutFunc = this._shortcuts[query._name!]; 32 | if (!shortcutFunc) { 33 | return { 34 | success: false, 35 | messages: [`Unknown shortcut: ${query._name}`] 36 | } 37 | } 38 | 39 | const shortcut = shortcutFunc(...query._args); 40 | return this._executionContext.queryExecutor.execute(shortcut, limiter); 41 | } 42 | 43 | setup(name: string, func: ShortcutFunc) 44 | { 45 | this._shortcuts[name] = func; 46 | } 47 | 48 | } -------------------------------------------------------------------------------- /src/rules-engine/query/transform-many/transform-many-query-executor.ts: -------------------------------------------------------------------------------- 1 | 2 | import _ from 'the-lodash' 3 | import { ILogger } from 'the-logger'; 4 | import { ScriptItem } from '../../script-item'; 5 | import { ExecutionContext } from '../../execution/execution-context'; 6 | import { QueryResult } from '../base'; 7 | import { IQueryExecutor } from '../base'; 8 | import { QueryScopeLimiter } from '../../query-spec/base'; 9 | import { K8sManifest } from '../../../manifests/k8s-manifest'; 10 | import { TransformManyTargetQuery } from '../../query-spec/transform-many/transform-many-target-query'; 11 | 12 | export class TransformManyQueryExecutor implements IQueryExecutor 13 | { 14 | private _logger : ILogger; 15 | private _executionContext : ExecutionContext; 16 | 17 | constructor(executionContext : ExecutionContext) 18 | { 19 | this._executionContext = executionContext; 20 | this._logger = executionContext.logger.sublogger("TransformManyQueryExecutor"); 21 | } 22 | 23 | execute(query: TransformManyTargetQuery, limiter: QueryScopeLimiter) : QueryResult 24 | { 25 | const result : QueryResult = { 26 | success: true, 27 | items: [] 28 | }; 29 | 30 | const innerResult = this._executionContext.queryExecutor.execute(query._inner, limiter); 31 | 32 | if (innerResult.items) 33 | { 34 | for(const item of innerResult.items) 35 | { 36 | if (query._func) 37 | { 38 | const k8sObjs = query._func(item); 39 | for(const k8sObj of k8sObjs) 40 | { 41 | const manifest = new K8sManifest(k8sObj, item.manifest.source); 42 | result.items!.push(new ScriptItem(manifest)); 43 | } 44 | } 45 | } 46 | } 47 | 48 | return result; 49 | } 50 | 51 | } -------------------------------------------------------------------------------- /src/rules-engine/query/transform/transform-query-executor.ts: -------------------------------------------------------------------------------- 1 | 2 | import _ from 'the-lodash' 3 | import { ILogger } from 'the-logger'; 4 | import { ScriptItem } from '../../script-item'; 5 | import { ExecutionContext } from '../../execution/execution-context'; 6 | import { QueryResult } from '../base'; 7 | import { IQueryExecutor } from '../base'; 8 | import { QueryScopeLimiter } from '../../query-spec/base'; 9 | import { TransformTargetQuery } from '../../query-spec/transform/transform-target-query'; 10 | import { K8sManifest } from '../../../manifests/k8s-manifest'; 11 | 12 | export class TransformQueryExecutor implements IQueryExecutor 13 | { 14 | private _logger : ILogger; 15 | private _executionContext : ExecutionContext; 16 | 17 | constructor(executionContext : ExecutionContext) 18 | { 19 | this._executionContext = executionContext; 20 | this._logger = executionContext.logger.sublogger("TransformQueryExecutor"); 21 | } 22 | 23 | execute(query: TransformTargetQuery, limiter: QueryScopeLimiter) : QueryResult 24 | { 25 | const result : QueryResult = { 26 | success: true, 27 | items: [] 28 | }; 29 | 30 | const innerResult = this._executionContext.queryExecutor.execute(query._inner, limiter); 31 | 32 | if (innerResult.items) 33 | { 34 | for(const item of innerResult.items) 35 | { 36 | if (query._func) 37 | { 38 | const k8sObj = query._func(item); 39 | const manifest = new K8sManifest(k8sObj, item.manifest.source); 40 | result.items!.push(new ScriptItem(manifest)); 41 | } 42 | } 43 | } 44 | 45 | return result; 46 | } 47 | 48 | } -------------------------------------------------------------------------------- /src/rules-engine/query/union/union-query-executor.ts: -------------------------------------------------------------------------------- 1 | 2 | import _ from 'the-lodash' 3 | import { ILogger } from 'the-logger'; 4 | import { ScriptItem } from '../../script-item'; 5 | import { ExecutionContext } from '../../execution/execution-context'; 6 | import { QueryResult } from '../base'; 7 | import { IQueryExecutor } from '../base'; 8 | import { UnionTargetQuery } from '../../query-spec/union/union-target-query'; 9 | import { QueryScopeLimiter } from '../../query-spec/base'; 10 | 11 | export class UnionQueryExecutor implements IQueryExecutor 12 | { 13 | private _logger : ILogger; 14 | private _executionContext : ExecutionContext; 15 | 16 | constructor(executionContext : ExecutionContext) 17 | { 18 | this._executionContext = executionContext; 19 | this._logger = executionContext.logger.sublogger("UnionQueryExecutor"); 20 | } 21 | 22 | execute(query: UnionTargetQuery, limiter: QueryScopeLimiter) : QueryResult 23 | { 24 | const _dict : Record = {}; 25 | 26 | for(const innerQuery of query._inner) 27 | { 28 | const innerResult = this._executionContext.queryExecutor.execute(innerQuery, limiter); 29 | 30 | if (innerResult.items) 31 | { 32 | for(const item of innerResult.items) 33 | { 34 | _dict[item.manifest.idKey] = item; 35 | } 36 | } 37 | } 38 | 39 | const result : QueryResult = { 40 | success: true, 41 | items: _.values(_dict) 42 | }; 43 | 44 | return result; 45 | } 46 | 47 | } -------------------------------------------------------------------------------- /src/rules-engine/registry/types.ts: -------------------------------------------------------------------------------- 1 | import { K8sManifest } from "../../manifests/k8s-manifest"; 2 | import { ManifestSourceId } from "../../types/manifest"; 3 | import { ClusterRuleK8sSpec, RuleApplicatorK8sSpec, RuleDependencies, RuleK8sSpec, RuleOverrideValues } from "../spec/rule-spec"; 4 | 5 | export enum RuleKind { 6 | ClusterRule = 'ClusterRule', 7 | Rule = 'Rule', 8 | RuleApplicator = 'RuleApplicator', 9 | } 10 | 11 | export interface RuleObject { 12 | source: ManifestSourceId, 13 | kind: RuleKind; 14 | namespace?: string; 15 | name: string; 16 | target: string; 17 | globalCache?: string; 18 | cache?: string; 19 | script: string; 20 | values: RuleOverrideValues; 21 | } 22 | 23 | export interface RuleApplicationScope { 24 | namespace?: string; 25 | } 26 | 27 | export interface CommonRule { 28 | manifest: K8sManifest, 29 | 30 | source: ManifestSourceId, 31 | kind: RuleKind; 32 | name: string; 33 | categories: string[]; 34 | categoryDict: Record; 35 | 36 | target: string; 37 | globalCache?: string; 38 | cache?: string; 39 | script: string; 40 | 41 | values: RuleOverrideValues; 42 | 43 | isDisabled: boolean; 44 | dependencies: RuleDependencies; 45 | hasUnmedDependency: boolean; 46 | } 47 | 48 | export interface ClusterRule extends CommonRule { 49 | application?: RuleApplicationScope; 50 | 51 | clustered: boolean; 52 | useApplicator: boolean; 53 | onlySelectedNamespaces: boolean; 54 | namespaces: Record; 57 | 58 | spec: ClusterRuleK8sSpec; 59 | } 60 | 61 | export interface NamespaceRule extends CommonRule { 62 | namespace: string; 63 | application?: RuleApplicationScope; 64 | 65 | spec: RuleK8sSpec; 66 | } 67 | 68 | export interface ApplicatorRule { 69 | manifest: K8sManifest, 70 | source: ManifestSourceId, 71 | kind: RuleKind; 72 | namespace: string; 73 | name: string; 74 | application?: RuleApplicationScope; 75 | values: RuleOverrideValues; 76 | 77 | spec: RuleApplicatorK8sSpec; 78 | } 79 | -------------------------------------------------------------------------------- /src/rules-engine/reporting/rule-engine-reporter.ts: -------------------------------------------------------------------------------- 1 | import { ILogger } from "the-logger"; 2 | import { RuleObject } from "../registry/types"; 3 | import { ManifestPackage } from "../../manifests/manifest-package"; 4 | import { K8sManifest } from "../../manifests/k8s-manifest"; 5 | 6 | export class RuleEngineReporter 7 | { 8 | private _logger: ILogger; 9 | private _manifestPackage : ManifestPackage; 10 | 11 | constructor(logger: ILogger, manifestPackage : ManifestPackage) 12 | { 13 | this._logger = logger.sublogger('RuleEngineReporter'); 14 | this._manifestPackage = manifestPackage; 15 | } 16 | 17 | get manifestPackage() { 18 | return this._manifestPackage; 19 | } 20 | 21 | reportError(rule: RuleObject, manifest: K8sManifest, msg: string) 22 | { 23 | manifest.reportError(msg); 24 | } 25 | 26 | reportWarning(rule: RuleObject, manifest: K8sManifest, msg: string) 27 | { 28 | manifest.reportWarning(msg); 29 | } 30 | 31 | } -------------------------------------------------------------------------------- /src/rules-engine/scope-builders.ts: -------------------------------------------------------------------------------- 1 | 2 | export interface RootScopeBuilder 3 | { 4 | setup(name: string, func: any) : void; 5 | } -------------------------------------------------------------------------------- /src/rules-engine/scope.ts: -------------------------------------------------------------------------------- 1 | export type KindType = string; 2 | 3 | export type ScopeFinalizer = () => void; 4 | 5 | export class Scope { 6 | private _query: BaseScopeQuery | null = null; 7 | private _finalizers: ScopeFinalizer[] = []; 8 | 9 | get query() { 10 | return this._query; 11 | } 12 | 13 | // registerFinalizer(finalizer: ScopeFinalizer) 14 | // { 15 | // this._finalizers.push(finalizer); 16 | // } 17 | 18 | // finalize() { 19 | // for(const finalizer of this._finalizers) 20 | // { 21 | // finalizer(); 22 | // } 23 | // } 24 | 25 | // setupQuery(query: BaseScopeQuery) 26 | // { 27 | // this._query = query; 28 | // } 29 | 30 | } 31 | 32 | export enum ScopeQueryKind 33 | { 34 | K8s = 'K8s' 35 | } 36 | 37 | export interface BaseScopeQuery 38 | { 39 | kind: ScopeQueryKind, 40 | } 41 | -------------------------------------------------------------------------------- /src/rules-engine/script-item.ts: -------------------------------------------------------------------------------- 1 | import { K8sManifest } from "../manifests/k8s-manifest"; 2 | 3 | 4 | export class ScriptItem 5 | { 6 | private _manifest: K8sManifest; 7 | 8 | constructor(manifest: K8sManifest) { 9 | this._manifest = manifest; 10 | } 11 | 12 | get config() { 13 | return this._manifest.config; 14 | } 15 | 16 | get manifest() { 17 | return this._manifest; 18 | } 19 | 20 | get apiVersion() { 21 | return this.config.apiVersion; 22 | } 23 | 24 | get kind() { 25 | return this.config.kind; 26 | } 27 | 28 | get name() { 29 | return this.config?.metadata?.name || ""; 30 | } 31 | 32 | get namespace () { 33 | return this.config?.metadata?.namespace; 34 | } 35 | 36 | get labels() { 37 | return this.config?.metadata?.labels || {}; 38 | } 39 | 40 | get annotations() { 41 | return this.config?.metadata?.annotations || {}; 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/rules-engine/spec/rule-spec.ts: -------------------------------------------------------------------------------- 1 | import { K8sMetadata } from "../../types/k8s"; 2 | 3 | export type RuleOverrideValues = Record; 4 | 5 | export interface RuleDependency 6 | { 7 | name: string, 8 | minVersion?: string, 9 | maxVersion?: string, 10 | range?: string, 11 | } 12 | 13 | export type RuleDependencies = RuleDependency[]; 14 | 15 | export interface BaseRuleK8sSpec 16 | { 17 | target: string, 18 | globalCache?: string, 19 | cache?: string, 20 | rule: string, 21 | 22 | summary?: string, 23 | description?: string, 24 | categories?: string[], 25 | 26 | disabled?: boolean, 27 | 28 | values?: RuleOverrideValues, 29 | dependencies?: RuleDependencies, 30 | } 31 | export interface ClusterRuleK8sSpec extends BaseRuleK8sSpec 32 | { 33 | application?: ClusterRuleApplication, 34 | } 35 | 36 | export interface ClusterRuleK8sSpec extends BaseRuleK8sSpec 37 | { 38 | application?: ClusterRuleApplication, 39 | } 40 | 41 | export interface ClusterRuleApplication { 42 | clustered?: false, 43 | useApplicator?: false, 44 | onlySelectedNamespaces?: false, 45 | namespaces?: { 46 | name: string, 47 | values: RuleOverrideValues 48 | }[], 49 | } 50 | 51 | export interface RuleK8sSpec extends BaseRuleK8sSpec 52 | { 53 | } 54 | 55 | export interface RuleApplicatorK8sSpec 56 | { 57 | clusterRuleRef: { 58 | name: string 59 | }, 60 | description?: string, 61 | disabled?: boolean, 62 | values?: RuleOverrideValues 63 | } 64 | 65 | export interface LibraryRuleRefK8sSpec 66 | { 67 | name: string, 68 | path: string, 69 | location: string, 70 | summary: string, 71 | categories: string[] 72 | } 73 | 74 | export interface LibraryK8sSpec 75 | { 76 | rules: LibraryRuleRefK8sSpec[]; 77 | } 78 | 79 | 80 | export interface LibraryK8sObject 81 | { 82 | apiVersion: string; 83 | kind: string; 84 | metadata?: K8sMetadata; 85 | spec: LibraryK8sSpec; 86 | } -------------------------------------------------------------------------------- /src/screen/docs.ts: -------------------------------------------------------------------------------- 1 | export interface ToolUsageSample 2 | { 3 | title: string, 4 | code: string, 5 | } 6 | 7 | export type ToolUsageSamples = ToolUsageSample[]; 8 | 9 | export function generateUsageSample(samples: ToolUsageSamples): string 10 | { 11 | const parts = samples.map(x => generateItem(x)); 12 | 13 | return parts.join("\n\n"); 14 | } 15 | 16 | export function generateItem(sample: ToolUsageSample): string 17 | { 18 | return `👉 ${sample.title}: 19 | $ ${sample.code}`; 20 | } -------------------------------------------------------------------------------- /src/screen/spinner.ts: -------------------------------------------------------------------------------- 1 | import _ from 'the-lodash'; 2 | import ora from 'ora'; 3 | import { indentify } from './'; 4 | 5 | export function spinOperation(name: string, indent?: number) : ISpinner 6 | { 7 | const spinner = ora(indentify(name ?? "", indent)).start(); 8 | spinner.spinner = 'moon'; 9 | return { 10 | update: (newName: string) => { 11 | spinner.text = indentify(newName ?? "", indent); 12 | }, 13 | complete: (newName?: string) => { 14 | spinner.succeed(indentify(newName ?? "", indent)); 15 | }, 16 | fail: (err: string) => { 17 | spinner.color = 'red'; 18 | spinner.fail(indentify(err ?? "", indent)); 19 | } 20 | } 21 | } 22 | 23 | 24 | 25 | 26 | export interface ISpinner 27 | { 28 | update(newName: string) : void; 29 | complete(newName?: string) : void; 30 | fail(newName: string) : void; 31 | } -------------------------------------------------------------------------------- /src/types/base-object.ts: -------------------------------------------------------------------------------- 1 | import _ from 'the-lodash'; 2 | import { ResultObject, setupBaseObjectSeverity } from "./result"; 3 | 4 | export class BaseObject 5 | { 6 | private _selfSuccess = true; 7 | private _selfErrors: string[] = []; 8 | private _selfWarnings: string[] = []; 9 | 10 | public get success() { 11 | return this._selfSuccess; 12 | } 13 | 14 | public get selfErrors(): string[] { 15 | return this._selfErrors; 16 | } 17 | 18 | public get errors() { 19 | return this.selfErrors; //TODO: 20 | } 21 | 22 | public get selfWarnings(): string[] { 23 | return this._selfWarnings; 24 | } 25 | 26 | public get warnings() { 27 | return this.selfWarnings; //TODO: 28 | } 29 | 30 | public reportError(msg: string) 31 | { 32 | this._selfErrors.push(msg); 33 | this._selfSuccess = false; 34 | } 35 | 36 | public reportErrors(msgs?: string[]) 37 | { 38 | for(const msg of msgs ?? []) 39 | { 40 | this.reportError(msg); 41 | } 42 | } 43 | 44 | public reportWarning(msg: string) 45 | { 46 | this._selfWarnings.push(msg); 47 | } 48 | 49 | public reportWarnings(msgs?: string[]) 50 | { 51 | for(const msg of msgs ?? []) 52 | { 53 | this.reportWarning(msg); 54 | } 55 | } 56 | 57 | protected extractBaseResult() : ResultObject { 58 | const result : ResultObject = { 59 | severity: 'pass', 60 | } 61 | 62 | if (this.errors.length > 0) 63 | { 64 | if (!result.messages) { 65 | result.messages = []; 66 | } 67 | result.messages = _.concat(result.messages, this.errors.map(x => ({ severity: 'error', msg: x }))); 68 | } 69 | 70 | if (this.warnings.length > 0) 71 | { 72 | if (!result.messages) { 73 | result.messages = []; 74 | } 75 | result.messages = _.concat(result.messages, this.warnings.map(x => ({ severity: 'warning', msg: x }))); 76 | } 77 | 78 | setupBaseObjectSeverity(result); 79 | 80 | return result; 81 | } 82 | 83 | protected yieldChildren() : BaseObject[] 84 | { 85 | return []; 86 | } 87 | } -------------------------------------------------------------------------------- /src/types/k8s.ts: -------------------------------------------------------------------------------- 1 | import _ from 'the-lodash'; 2 | import { parseApiVersion } from "../utils/k8s"; 3 | 4 | export interface K8sObject 5 | { 6 | apiVersion: string; 7 | kind: string; 8 | metadata?: K8sMetadata; 9 | [key : string] : any; 10 | } 11 | 12 | export interface K8sMetadata 13 | { 14 | namespace?: string; 15 | name?: string; 16 | ownerReferences?: any[]; 17 | [key : string] : any; 18 | } 19 | 20 | export interface K8sObjectKey 21 | { 22 | apiVersion: string; 23 | kind: string; 24 | namespace?: string; 25 | name?: string; 26 | } 27 | 28 | export interface K8sObjectId 29 | { 30 | apiVersion: string; 31 | api?: string; 32 | version: string; 33 | kind: string; 34 | namespace?: string; 35 | name?: string; 36 | } 37 | 38 | export function makeId(k8sObject: K8sObject): K8sObjectId 39 | { 40 | const apiVersionParts = parseApiVersion(k8sObject.apiVersion); 41 | 42 | return { 43 | apiVersion: k8sObject.apiVersion, 44 | api: apiVersionParts!.group, 45 | version: apiVersionParts!.version, 46 | kind: k8sObject.kind, 47 | namespace: k8sObject.metadata?.namespace, 48 | name: k8sObject.metadata?.name, 49 | } 50 | } 51 | 52 | export function makeK8sKey(k8sObject: K8sObject): K8sObjectKey 53 | { 54 | return { 55 | apiVersion: k8sObject.apiVersion, 56 | kind: k8sObject.kind, 57 | namespace: k8sObject.metadata?.namespace, 58 | name: k8sObject.metadata?.name, 59 | } 60 | } 61 | 62 | export function makeK8sKeyStr(k8sObject: K8sObject): string 63 | { 64 | return _.stableStringify(makeK8sKey(k8sObject)); 65 | } -------------------------------------------------------------------------------- /src/types/kubevious.ts: -------------------------------------------------------------------------------- 1 | 2 | export const KUBEVIOUS_API_NAME = 'kubevious.io'; 3 | export const KUBEVIOUS_API_VERSION = 'v1alpha1'; 4 | 5 | export enum KubeviousKinds { 6 | Library = "Library", 7 | ClusterRule = "ClusterRule", 8 | Rule = "Rule", 9 | RuleApplicator = "RuleApplicator", 10 | } -------------------------------------------------------------------------------- /src/types/manifest-result.ts: -------------------------------------------------------------------------------- 1 | 2 | import { K8sObjectId } from "./k8s"; 3 | import { ResultObject } from "./result"; 4 | import { SourceInfoResult, SourceResult } from "./source-result"; 5 | 6 | export interface ManifestInfoResult extends K8sObjectId, ResultObject 7 | { 8 | } 9 | 10 | export interface ManifestResult extends ManifestInfoResult 11 | { 12 | sources: SourceInfoResult[]; 13 | } 14 | 15 | export interface ManifestPackageResult extends ResultObject 16 | { 17 | manifests: ManifestResult[]; 18 | rootSource: SourceResult; 19 | } 20 | -------------------------------------------------------------------------------- /src/types/manifest.ts: -------------------------------------------------------------------------------- 1 | export type ManifestSourceType = "file" | "web" | "stream" | "k8s" | "root" | "kustomize" | "helm"; 2 | 3 | export interface ErrorStatus 4 | { 5 | success: boolean, 6 | errors?: string[], 7 | warnings?: string[] 8 | } 9 | 10 | export interface ManifestSourceId 11 | { 12 | kind: ManifestSourceType; 13 | path: string; 14 | } 15 | 16 | export interface ManifestId 17 | { 18 | apiVersion: string; 19 | kind: string; 20 | namespace?: string; 21 | name?: string; 22 | } 23 | -------------------------------------------------------------------------------- /src/types/result.ts: -------------------------------------------------------------------------------- 1 | import _ from 'the-lodash'; 2 | 3 | export type ResultObjectSeverity = 'pass' | 'fail' | 'warning'; 4 | export type ResultMessageSeverity = 'error' | 'warning'; 5 | 6 | export interface ResultMessage 7 | { 8 | severity: ResultMessageSeverity; 9 | msg: string; 10 | } 11 | 12 | export interface ResultObject 13 | { 14 | severity: ResultObjectSeverity; 15 | messages?: ResultMessage[]; 16 | } 17 | 18 | export interface ManifestPackageCounters 19 | { 20 | sources: { 21 | total: number, 22 | withErrors: number, 23 | withWarnings: number 24 | }, 25 | manifests: { 26 | total: number, 27 | passed: number, 28 | withErrors: number, 29 | withWarnings: number 30 | } 31 | } 32 | 33 | export interface RuleEngineCounters 34 | { 35 | rules: { 36 | total: number, 37 | failed: number, 38 | passed: number, 39 | withErrors: number, 40 | withWarnings: number 41 | }, 42 | manifests: { 43 | total: number, 44 | processed: number, 45 | passed: number, 46 | withErrors: number, 47 | withWarnings: number 48 | } 49 | } 50 | 51 | 52 | export function makeObjectSeverity(severity: ResultObjectSeverity, others: ResultObjectSeverity[]) : ResultObjectSeverity 53 | { 54 | const items = _.flatten([[severity], others]); 55 | const dict = _.makeBoolDict(items); 56 | if (dict['fail']) { 57 | return 'fail'; 58 | } 59 | if (dict['warning']) { 60 | return 'warning'; 61 | } 62 | return 'pass'; 63 | } 64 | 65 | export function makeObjectSeverityFromChildren(severity: ResultObjectSeverity, children: ResultObject[]) : ResultObjectSeverity 66 | { 67 | return makeObjectSeverity(severity, children.map(x => x.severity)); 68 | } 69 | 70 | 71 | export function setupBaseObjectSeverity(obj: ResultObject) 72 | { 73 | obj.severity = makeObjectSeverity('pass', (obj.messages ?? []).map(x => { 74 | if (x.severity === 'error') { 75 | return 'fail'; 76 | } 77 | if (x.severity === 'warning') { 78 | return 'warning'; 79 | } 80 | return 'pass'; 81 | })); 82 | } -------------------------------------------------------------------------------- /src/types/rules-result.ts: -------------------------------------------------------------------------------- 1 | import { ManifestResult } from "./manifest-result"; 2 | import { ResultObject, ResultObjectSeverity } from "./result"; 3 | 4 | export interface RuleEngineResult extends ResultObject 5 | { 6 | success: boolean; 7 | rules: RuleResult[]; 8 | } 9 | 10 | 11 | export interface RuleResult 12 | { 13 | ruleManifest: ManifestResult, 14 | ruleCategories: string[], 15 | namespace?: string, 16 | isSkipped: boolean, 17 | compiled: boolean; 18 | pass: boolean; 19 | ruleSeverity : ResultObjectSeverity; 20 | hasViolationErrors: boolean; 21 | hasViolationWarnings: boolean; 22 | errors?: string[]; 23 | violations: ManifestResult[]; 24 | passed: ManifestResult[]; 25 | } 26 | -------------------------------------------------------------------------------- /src/types/source-result.ts: -------------------------------------------------------------------------------- 1 | import { ManifestSourceType } from "./manifest"; 2 | import { ManifestInfoResult } from "./manifest-result"; 3 | import { ResultObject } from "./result"; 4 | 5 | export interface SourceInfoResult extends ResultObject 6 | { 7 | kind: ManifestSourceType; 8 | path: string; 9 | } 10 | 11 | export interface SourceResult extends SourceInfoResult 12 | { 13 | children?: SourceResult[]; 14 | manifests?: ManifestInfoResult[]; 15 | } 16 | -------------------------------------------------------------------------------- /src/utils/k8s-manifest-sanitizer.ts: -------------------------------------------------------------------------------- 1 | import _ from 'the-lodash'; 2 | 3 | export function sanitizeYaml(obj: any) : any 4 | { 5 | if (_.isArray(obj)) { 6 | return obj.map(x => sanitizeYaml(x)); 7 | } 8 | 9 | if (_.isPlainObject(obj)) { 10 | for(const key of _.keys(obj)) 11 | { 12 | obj[key] = sanitizeYaml(obj[key]); 13 | } 14 | } 15 | 16 | if (_.isNull(obj)) { 17 | return undefined; 18 | } 19 | 20 | return obj; 21 | } 22 | -------------------------------------------------------------------------------- /src/utils/k8s.ts: -------------------------------------------------------------------------------- 1 | import _ from 'the-lodash'; 2 | import { K8sOpenApiResource } from "k8s-super-client"; 3 | import { K8sObject, K8sObjectId } from "../types/k8s"; 4 | 5 | export function parseApiVersion(apiVersion : string) : { group: string, version: string } | null 6 | { 7 | const parts = apiVersion.split('/'); 8 | if (parts.length === 1) { 9 | return { 10 | group: '', 11 | version: parts[0] 12 | } 13 | } 14 | if (parts.length === 2) { 15 | return { 16 | group: parts[0], 17 | version: parts[1] 18 | } 19 | } 20 | 21 | return null; 22 | } 23 | 24 | export function getApiResourceId(k8sManifest : K8sObject) : K8sOpenApiResource | null 25 | { 26 | if (!k8sManifest.kind) { 27 | return null; 28 | } 29 | 30 | const apiVersionParts = parseApiVersion(k8sManifest.apiVersion); 31 | if (!apiVersionParts) { 32 | return null; 33 | } 34 | 35 | return { 36 | group: apiVersionParts.group, 37 | version: apiVersionParts.version, 38 | kind: k8sManifest.kind 39 | } 40 | } 41 | 42 | export function getJsonSchemaResourceKey(apiResource : K8sOpenApiResource) : string 43 | { 44 | return _.stableStringify(apiResource); 45 | } 46 | 47 | export function isCRD(id: K8sObjectId) 48 | { 49 | return (id.apiVersion === 'apiextensions.k8s.io/v1') && (id.kind === 'CustomResourceDefinition'); 50 | } -------------------------------------------------------------------------------- /src/utils/path.ts: -------------------------------------------------------------------------------- 1 | import _ from 'the-lodash'; 2 | import Path from 'path'; 3 | 4 | export function isWebPath(fileOrPath : string) 5 | { 6 | return _.startsWith(fileOrPath, 'http://') || _.startsWith(fileOrPath, 'https://'); 7 | } 8 | 9 | export function resolvePath(path: string, parentDirOrPath?: string) 10 | { 11 | if (parentDirOrPath && !isAbsolutePath(path)) 12 | { 13 | if (isWebPath(parentDirOrPath)) 14 | { 15 | if (!_.endsWith('/')) { 16 | parentDirOrPath = `${parentDirOrPath}/`; 17 | } 18 | path = `${parentDirOrPath}${path}`; 19 | return path; 20 | } 21 | else 22 | { 23 | path = Path.resolve(parentDirOrPath, path); 24 | return path; 25 | } 26 | } 27 | 28 | if (!isWebPath(path)) 29 | { 30 | path = Path.resolve(path); 31 | } 32 | return path; 33 | } 34 | 35 | export function isAbsolutePath(fileOrPath : string) 36 | { 37 | return _.startsWith(fileOrPath, '/') || isWebPath(fileOrPath); 38 | } 39 | 40 | export function getParentDir(fileOrUrl: string) 41 | { 42 | return Path.dirname(fileOrUrl); 43 | } 44 | 45 | export function makeRelativePath(fileOrUrl: string, parentDirOrUrl: string) 46 | { 47 | if (isWebPath(fileOrUrl)) { 48 | return fileOrUrl; 49 | } 50 | 51 | fileOrUrl = Path.normalize(fileOrUrl); 52 | parentDirOrUrl = Path.normalize(parentDirOrUrl); 53 | 54 | return Path.relative(parentDirOrUrl, fileOrUrl); 55 | } -------------------------------------------------------------------------------- /src/utils/release.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | 3 | export async function getLatestReleaseTag() 4 | { 5 | const RELEASE_URL = 'https://api.github.com/repos/kubevious/cli/releases/latest'; 6 | const result = await axios.get(RELEASE_URL) 7 | 8 | const tag = result?.data?.tag_name; 9 | if (!tag) { 10 | throw new Error("Could not get the release tag."); 11 | } 12 | 13 | return tag; 14 | } -------------------------------------------------------------------------------- /src/utils/stream.ts: -------------------------------------------------------------------------------- 1 | import { MyPromise } from 'the-promise'; 2 | import { spinOperation } from '../screen/spinner'; 3 | 4 | export function readFromInputStream() 5 | { 6 | const stream = process.stdin; 7 | 8 | const spinner = spinOperation('Reading manifests from stdin...'); 9 | 10 | let receivedSomething = false; 11 | 12 | const timer = setTimeout(() => { 13 | spinner.update('Reading manifests from stdin. Did you pipe something?') 14 | }, 5 * 1000); 15 | 16 | const terminateTimer = () => { 17 | clearTimeout(timer); 18 | } 19 | 20 | return MyPromise.construct((resolve, reject) => 21 | { 22 | let data = ''; 23 | stream.on('readable', () => { 24 | const chunk = stream.read(); 25 | if (chunk !== null) { 26 | data += chunk; 27 | 28 | if (!receivedSomething) { 29 | receivedSomething = true; 30 | terminateTimer(); 31 | } 32 | } 33 | }); 34 | 35 | stream.on('end', () => { 36 | terminateTimer(); 37 | spinner.complete('Received manifests from stdin.'); 38 | resolve(data); 39 | }); 40 | 41 | stream.on('error', (err) => { 42 | terminateTimer(); 43 | spinner.fail(`Failed to read from stdin. ${err}`); 44 | reject(err); 45 | }); 46 | }); 47 | } 48 | -------------------------------------------------------------------------------- /src/utils/version-checker.ts: -------------------------------------------------------------------------------- 1 | import * as semver from 'semver'; 2 | import VERSION from '../version'; 3 | 4 | export function checkKubeviousVersion(minVersion: string | undefined, 5 | maxVersion: string | undefined, 6 | range: string | undefined) : string[] 7 | { 8 | const issues : string[] = []; 9 | 10 | if (minVersion) { 11 | if (!semver.gte(VERSION, minVersion)) { 12 | issues.push(`Try installing Kubevious CLI version ${minVersion} or higher.`); 13 | } 14 | } 15 | 16 | if (maxVersion) { 17 | if (!semver.lte(VERSION, maxVersion)) { 18 | issues.push(`Try installing Kubevious CLI version ${maxVersion} or lower.`); 19 | } 20 | } 21 | 22 | if (range) { 23 | if (!semver.satisfies(VERSION, range)) { 24 | issues.push(`Try installing Kubevious CLI version ${range}.`); 25 | } 26 | } 27 | 28 | return issues; 29 | } -------------------------------------------------------------------------------- /src/version.ts: -------------------------------------------------------------------------------- 1 | // This file is generated and updated from CI/CD 2 | export default "v1.0.64"; 3 | -------------------------------------------------------------------------------- /test/helper-k8s-manifest.ts: -------------------------------------------------------------------------------- 1 | import 'mocha'; 2 | import should = require('should'); 3 | 4 | import _ from 'the-lodash'; 5 | 6 | import { sanitizeYaml } from '../src/utils/k8s-manifest-sanitizer'; 7 | 8 | import { logger } from './utils/logger'; 9 | 10 | describe('helper-k8s-manifest-sanitizer', function() { 11 | 12 | it('case-01', function() { 13 | const manfiest = { 14 | apiVersion: "apps/v1", 15 | kind: "Deployment", 16 | spec: { 17 | replicas: 1, 18 | template: { 19 | containers: [ 20 | { 21 | name: "test", 22 | volumes: [ 23 | { 24 | name: "datadir", 25 | mountPath: "/bitnami/mongodb", 26 | subPath: null 27 | } 28 | ] 29 | } 30 | ] 31 | } 32 | } 33 | }; 34 | 35 | const result = sanitizeYaml(manfiest); 36 | logger.info("RESULT: ", result); 37 | should(result).be.ok(); 38 | should(result.apiVersion).be.equal("apps/v1"); 39 | should(result.spec?.replicas).be.equal(1); 40 | should(result.spec?.template?.containers[0].volumes[0].name).be.equal("datadir"); 41 | should(result.spec?.template?.containers[0].volumes[0].subPath).be.undefined(); 42 | }); 43 | 44 | }); -------------------------------------------------------------------------------- /test/is-web-path.ts: -------------------------------------------------------------------------------- 1 | import 'mocha'; 2 | import should = require('should'); 3 | 4 | import _ from 'the-lodash'; 5 | 6 | import { isWebPath } from '../src/utils/path'; 7 | 8 | import { logger } from './utils/logger'; 9 | import Path from 'path'; 10 | 11 | describe('is-web-path', function() { 12 | 13 | it('case-01', function() { 14 | const result = isWebPath('samples/test.yaml'); 15 | should(result).be.false(); 16 | }); 17 | 18 | 19 | it('case-02', function() { 20 | const result = isWebPath('/usr/local/samples/test.yamlzz'); 21 | should(result).be.false(); 22 | }); 23 | 24 | 25 | it('case-03', function() { 26 | const result = isWebPath('/usr/local/samples/test.yaml'); 27 | should(result).be.false(); 28 | }); 29 | 30 | it('case-03', function() { 31 | const result = isWebPath('samples/test.yaml'); 32 | should(result).be.false(); 33 | }); 34 | 35 | 36 | it('case-04', function() { 37 | const result = isWebPath('http://samples/test.yaml'); 38 | should(result).be.true(); 39 | }); 40 | 41 | it('case-05', function() { 42 | const result = isWebPath('https://samples/test.yaml'); 43 | should(result).be.true(); 44 | }); 45 | 46 | 47 | }); 48 | -------------------------------------------------------------------------------- /test/path-resolve.ts: -------------------------------------------------------------------------------- 1 | import 'mocha'; 2 | import should = require('should'); 3 | 4 | import _ from 'the-lodash'; 5 | 6 | import { resolvePath } from '../src/utils/path'; 7 | 8 | import { logger } from './utils/logger'; 9 | import Path from 'path'; 10 | 11 | describe('helper-path-resolve', function() { 12 | 13 | it('case-01', function() { 14 | const result = resolvePath('samples/test.yaml'); 15 | should(result).be.equal(Path.resolve('./samples/test.yaml')); 16 | }); 17 | 18 | 19 | it('case-02', function() { 20 | const result = resolvePath('samples/test.yaml', '/usr/local'); 21 | should(result).be.equal('/usr/local/samples/test.yaml'); 22 | }); 23 | 24 | it('case-02x', function() { 25 | const result = resolvePath('samples/test.yaml', '/usr/local/'); 26 | should(result).be.equal('/usr/local/samples/test.yaml'); 27 | }); 28 | 29 | it('case-03', function() { 30 | const result = resolvePath('http://samples/test.yaml'); 31 | should(result).be.equal('http://samples/test.yaml'); 32 | }); 33 | 34 | it('case-04', function() { 35 | const result = resolvePath('http://samples/test.yaml', '/usr/local/sample.yaml'); 36 | should(result).be.equal('http://samples/test.yaml'); 37 | }); 38 | 39 | it('case-05', function() { 40 | const result = resolvePath('https://samples/test.yaml', 'https://example.com/another/index.yaml'); 41 | should(result).be.equal('https://samples/test.yaml'); 42 | }); 43 | 44 | 45 | }); -------------------------------------------------------------------------------- /test/utils/logger.ts: -------------------------------------------------------------------------------- 1 | 2 | import _ from 'the-lodash'; 3 | import { LoggerOptions, LogLevel, setupRootLogger } from 'the-logger'; 4 | 5 | const loggerOptions = new LoggerOptions() 6 | .level(LogLevel.info) 7 | .enableFile(false) 8 | .pretty(true) 9 | ; 10 | 11 | const rootLogger = setupRootLogger('TEST', loggerOptions); 12 | export const logger = rootLogger.logger; 13 | -------------------------------------------------------------------------------- /tools/kubevious-npm-validate-nested-dependencies.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | export MY_DIR=$(pwd) 4 | 5 | echo "*** Kubevious Validate NPM Nested Dependencies..." 6 | echo " REPO: ${MY_DIR}" 7 | 8 | unset FORCE_RESOLVE_DEPENDENCIES 9 | source ${MY_DIR}/dependencies.sh 10 | 11 | if [[ ! -d node_modules ]]; then 12 | echo "ERROR: no node_modules found" 13 | exit 1; 14 | fi; 15 | 16 | for module in node_modules/@kubevious/*; do 17 | 18 | for DEP_NAME in "${FORCE_RESOLVE_DEPENDENCIES[@]}" 19 | do 20 | NESTED_DEP_PATH="${module}/node_modules/${DEP_NAME}" 21 | if [[ -d ${NESTED_DEP_PATH} ]]; then 22 | echo "ERROR: NESTED DEP -> ${NESTED_DEP_PATH}" 23 | exit 1 24 | fi 25 | done 26 | 27 | done 28 | -------------------------------------------------------------------------------- /version.sh: -------------------------------------------------------------------------------- 1 | # This file is generated and updated from CI/CD 2 | export PRODUCT_VERSION=1.0.64 3 | --------------------------------------------------------------------------------