diff --git a/.codecov.yml b/.codecov.yml index c166a21886..f3e7c00ed0 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -2,10 +2,15 @@ coverage: status: project: default: + informational: true target: auto threshold: 0% patch: default: + informational: true target: auto threshold: 0% changes: false +ignore: + - "pkg/client" + - "**/*generated*.go" diff --git a/.fossa.yml b/.fossa.yml index 442038aed1..7d51bf2a7c 100644 --- a/.fossa.yml +++ b/.fossa.yml @@ -4,4 +4,4 @@ targets: - type: setuptools paths: exclude: - - docs + - site diff --git a/.github/ISSUE_TEMPLATE/BUG-REPORT.yml b/.github/ISSUE_TEMPLATE/BUG-REPORT.yml new file mode 100644 index 0000000000..ddc587f1a5 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/BUG-REPORT.yml @@ -0,0 +1,82 @@ +name: Bug Report +description: File a bug report. +title: "[Bug]: " +labels: ["bug", "needs triage"] +body: + - type: markdown + attributes: + value: | + Thanks for taking the time to fill out this bug report! + - type: dropdown + id: version + attributes: + label: Version + description: What version of our F5 NGINX Ingress Controller are you running? + options: + - edge + - 3.7.0 + - 3.6.2 + - 3.6.1 + - 3.6.0 + - 3.5.2 + - 3.5.1 + - 3.5.0 + - 3.4.3 + - 3.4.2 + - 3.4.1 + - 3.4.0 + - 3.3.2 + - 3.3.1 + - 3.3.0 + - 3.2.1 + - 3.2.0 + - 3.1.1 + - 3.1.0 + - 3.0.2 + - 3.0.1 + - 3.0.0 + - 2.4.2 + - 2.4.1 + - 2.4.0 + - 2.3.1 + - 2.3.0 + - 2.2.2 + - 2.2.1 + - 2.2.0 + - 2.1.2 + - 2.1.1 + - 2.1.0 + - 2.0.3 + - 2.0.2 + - 2.0.1 + - 2.0.0 + default: 0 + validations: + required: true + - type: dropdown + id: platform + attributes: + label: What Kubernetes platforms are you running on? + options: + - Kind + - Minikube + - Rancher + - EKS Amazon + - AKS Azure + - GKE Google Cloud + - Openshift + - Other + default: 0 + validations: + required: true + - type: textarea + id: steps-to-reproduce + attributes: + label: Steps to reproduce + description: These steps will help us best reproduce the issue and come to a resolution. + placeholder: | + 1. Deploy x to '...' using some.yaml + 2. View logs on '....' + 3. See error + validations: + required: true diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index a1dec93f78..0000000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,30 +0,0 @@ ---- -name: Bug report -about: Create a report to help us improve -title: '' -labels: '' -assignees: '' - ---- - -**Describe the bug** -A clear and concise description of what the bug is. - -**To Reproduce** -Steps to reproduce the behavior: -1. Deploy x to '...' using some.yaml -2. View logs on '....' -3. See error - -**Expected behavior** -A clear and concise description of what you expected to happen. - -**Your environment** -* Version of the Ingress Controller - release version or a specific commit -* Version of Kubernetes -* Kubernetes platform (e.g. Mini-kube or GCP) -* Using NGINX or NGINX Plus - - -**Additional context** -Add any other context about the problem here. Any log files you want to share. diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 5281f1e1f0..c9afef021b 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,7 +1,10 @@ ### Proposed changes -Describe the use case and detail of the change. If this PR addresses an issue on GitHub, make sure to include a link to that issue here in this description (not in the title of the PR). + +Describe the use case and detail of the change. If this PR addresses an issue on GitHub, make sure to include a link to +that issue here in this description (not in the title of the PR). ### Checklist + Before creating a PR, run through this checklist and mark each as complete. - [ ] I have read the [CONTRIBUTING](https://github.com/nginxinc/kubernetes-ingress/blob/main/CONTRIBUTING.md) doc diff --git a/.github/actionlint.yaml b/.github/actionlint.yaml new file mode 100644 index 0000000000..ee03a71d8b --- /dev/null +++ b/.github/actionlint.yaml @@ -0,0 +1,8 @@ +self-hosted-runner: + # Labels of self-hosted runner in array of strings. + labels: + - kic-plus +# Configuration variables in array of strings defined in your repository or +# organization. `null` means disabling configuration variables check. +# Empty array means no configuration variable is allowed. +config-variables: null diff --git a/.github/actions/certify-openshift-image/action.yml b/.github/actions/certify-openshift-image/action.yml new file mode 100644 index 0000000000..b4da12ad14 --- /dev/null +++ b/.github/actions/certify-openshift-image/action.yml @@ -0,0 +1,61 @@ +name: Certify Openshift Image +description: This action will attempt to certify an image for use in Openshift + +inputs: + image: + description: The image manifest to certify in the format /: + required: true + project_id: + description: The certification project id + required: true + pyxis_token: + description: The Pyxis API Token + required: true + preflight_version: + description: The version of the preflight utility to install + required: false + default: 1.9.1 + platforms: + description: A comma separated list of architectures in the image manifest to certify + required: false + default: "amd64,arm64,ppc64le,s390x" + submit: + description: Submit results to Redhat PYAXIS + required: false + default: true + +outputs: + result: + description: Did the certification succeed? + value: ${{ steps.result.outputs.result == 0 && true || false }} + +runs: + using: composite + steps: + - name: Install openshift-preflight + run: | + curl -fsSL https://github.com/redhat-openshift-ecosystem/openshift-preflight/releases/download/${{ inputs.preflight_version }}/preflight-linux-amd64 --output preflight + chmod +x preflight + shell: bash + + - name: Certify Images + id: result + run: | + result=0 + if [ -z "${{ inputs.platforms }}" ]; then + # list of platforms passed + IFS=',' read -ra arch_list <<< "${{ inputs.platforms }}" + for arch in "${arch_list[@]}"; do + architecture=("${arch#*/}") + ./preflight check container ${{ inputs.image }} --pyxis-api-token ${{ inputs.pyxis_token }} --certification-project-id ${{ inputs.project_id }} --platform $architecture ${{ inputs.submit && '--submit' || '' }} + if [ $? -ne 0 ]; then + result=1 + fi + done + else + # no platforms passed, this is either a manifest or a single platform image + ./preflight check container ${{ inputs.image }} --pyxis-api-token ${{ inputs.pyxis_token }} --certification-project-id ${{ inputs.project_id }} ${{ inputs.submit && '--submit' || '' }} + result=$? + fi + echo "result=$result" >> $GITHUB_OUTPUT + shell: bash diff --git a/.github/actions/smoke-tests/action.yaml b/.github/actions/smoke-tests/action.yaml index 348a10d550..8b0ad78447 100644 --- a/.github/actions/smoke-tests/action.yaml +++ b/.github/actions/smoke-tests/action.yaml @@ -1,4 +1,3 @@ - name: Run Smoke Tests description: Run Smoke Tests for the project @@ -10,110 +9,102 @@ inputs: description: Timeout to use default: 75s required: false - image: - description: Docker image to use - default: debian + image-type: + description: Image type to test + required: true + image-name: + description: Docker image name to test + required: true + tag: + description: Docker image tag to test + required: true + test-image: + description: Test Docker image to use + default: gcr.io/f5-gcs-7899-ptg-ingrss-ctlr/dev/test-runner:latest required: false marker: description: Marker to use required: false - nginx-key: - description: Nginx key to use + label: + description: Label for test + required: false + azure-ad-secret: + description: Azure Active Directory secret for JWKs + required: false + registry-token: + description: JWT token for accessing container registry required: false - nginx-crt: - description: Nginx cert to use + plus-jwt: + description: JWT for NGINX Plus required: false outputs: test-results-name: description: Test results name - value: tests-${{ steps.k8s.outputs.cluster }} + value: ${{ steps.k8s.outputs.test_name }} + test-results-path: + description: Test results full path + value: ${{ steps.k8s.outputs.test_output_path }} runs: using: composite steps: - - name: Fetch Cached Artifacts - uses: actions/cache@v3 - with: - path: ${{ github.workspace }}/dist - key: nginx-ingress-${{ github.run_id }}-${{ github.run_number }}-single - - - name: Ingress type - id: ingress-type - run: | - echo "name=nginx${{ contains(inputs.image, 'plus') && '-plus' || '' }}-ingress" >> $GITHUB_OUTPUT - echo "tag=${{ inputs.image }}${{ contains(inputs.marker, 'dos') && '-dos' || '' }}${{ contains(inputs.marker, 'appprotect') && '-nap' || '' }}-${{ github.sha }}" >> $GITHUB_OUTPUT - echo "modules=${{ contains(inputs.marker, 'appprotect') && 'waf' || '' }}${{ contains(inputs.marker, 'dos') && 'dos' || '' }}" >> $GITHUB_OUTPUT - shell: bash - - - name: Docker Buildx - uses: docker/setup-buildx-action@v2 - - - name: Build ${{ inputs.image }} Container - uses: docker/build-push-action@v3 - with: - file: build/Dockerfile - context: '.' - cache-from: type=gha,scope=${{ inputs.image }}${{ contains(inputs.marker, 'dos') && '-dos' || '' }}${{ contains(inputs.marker, 'appprotect') && '-nap' || '' }} - cache-to: type=gha,scope=${{ inputs.image }}${{ contains(inputs.marker, 'dos') && '-dos' || '' }}${{ contains(inputs.marker, 'appprotect') && '-nap' || '' }},mode=max - target: goreleaser - tags: 'docker.io/nginx/${{ steps.ingress-type.outputs.name }}:${{ steps.ingress-type.outputs.tag }}' - load: true - pull: true - build-args: | - BUILD_OS=${{ inputs.image }} - IC_VERSION=CI - ${{ steps.ingress-type.outputs.modules != '' && format('NAP_MODULES={0}', steps.ingress-type.outputs.modules) || '' }} - ${{ contains(inputs.marker, 'appprotect') && 'DEBIAN_VERSION=buster-slim' || '' }} - secrets: | - ${{ contains(inputs.image, 'plus') && format('"nginx-repo.crt={0}"', inputs.nginx-crt) || '' }} - ${{ contains(inputs.image, 'plus') && format('"nginx-repo.key={0}"', inputs.nginx-key) || '' }} - - - name: Build Test-Runner Container - uses: docker/build-push-action@v3 - with: - file: tests/docker/Dockerfile - context: '.' - cache-from: type=gha,scope=test-runner - cache-to: type=gha,scope=test-runner,mode=max - tags: test-runner:${{ github.sha }} - pull: true - load: true - - name: Deploy Kubernetes id: k8s run: | - kind create cluster --name ${{ github.run_id }} --image=kindest/node:v${{ inputs.k8s-version }} --config ${{ github.workspace }}/tests/ci-files/ci-kind-config.yaml --kubeconfig kube-${{ github.run_id }} --wait ${{ inputs.k8s-timeout }} - kind load docker-image docker.io/nginx/${{ steps.ingress-type.outputs.name }}:${{ steps.ingress-type.outputs.tag }} --name ${{ github.run_id }} - marker="${{ inputs.marker }}" - sanitized_marker="${marker// /_}" - name="${sanitized_marker:-${{ inputs.k8s-version }}}" - echo "cluster_ip=$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' ${{ github.run_id }}-control-plane)" >> $GITHUB_OUTPUT - echo "cluster=$(echo nginx-${{ inputs.image }}-$name)" >> $GITHUB_OUTPUT + make -f tests/Makefile create-kind-cluster K8S_CLUSTER_NAME=${{ github.run_id }} K8S_CLUSTER_VERSION=${{ inputs.k8s-version }} K8S_TIMEOUT=${{ inputs.k8s-timeout }} + make -f tests/Makefile image-load REGISTRY="" PREFIX=${{ inputs.image-name }} TAG=${{ inputs.tag }} K8S_CLUSTER_NAME=${{ github.run_id }} + label="${{ inputs.label }}" + nospaces="${label// /_}" + noslash="${nospaces//\//_}" + sanitized_marker="${noslash//\'/}" + name="${sanitized_marker}-${{ inputs.k8s-version }}" + cluster_ip=$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' ${{ github.run_id }}-control-plane) + test_name=tests-nginx-${{ inputs.image-type }}-${name}.html + test_output_path=${{ github.workspace }}/tests/${test_name} + echo "cluster_ip=${cluster_ip}" >> $GITHUB_OUTPUT + echo "test_name=${test_name}" >> $GITHUB_OUTPUT + echo "test_output_path=${test_output_path}" >> $GITHUB_OUTPUT + echo "Output:" + echo " cluster_ip=${cluster_ip}" + echo " test_output_path=${test_output_path}" shell: bash - name: Setup Kubeconfig run: | - sed -i 's|server:.*|server: https://${{ steps.k8s.outputs.cluster_ip }}:6443|' kube-${{ github.run_id }} + sed -i 's|server:.*|server: https://${{ steps.k8s.outputs.cluster_ip }}:6443|' ~/.kube/kind/config shell: bash - name: Run Smoke Tests + id: smoke-tests run: | - touch tests-${{ steps.k8s.outputs.cluster }}.html + touch ${{ steps.k8s.outputs.test_output_path }} docker run --rm \ --name test-runner-${{ github.run_id }} \ --network=kind \ - -v ${{ github.workspace }}/tests/tests-${{ steps.k8s.outputs.cluster }}.html:/workspace/tests/tests-${{ steps.k8s.outputs.cluster }}.html \ - -v ${{ github.workspace }}/kube-${{ github.run_id }}:/root/.kube/config test-runner:${{ github.sha }} \ + -v "/var/run/docker.sock:/var/run/docker.sock" \ + -v ~/.docker:/root/.docker \ + -v ${{ github.workspace }}/tests:/workspace/tests \ + -v ${{ github.workspace }}/examples/common-secrets:/workspace/examples/common-secrets \ + -v ${{ github.workspace }}/deployments:/workspace/deployments \ + -v ${{ github.workspace }}/charts:/workspace/charts \ + -v ${{ github.workspace }}/config:/workspace/config \ + -v ${{ github.workspace }}/pyproject.toml:/workspace/pyproject.toml \ + -v ${{ steps.k8s.outputs.test_output_path }}:${{ steps.k8s.outputs.test_output_path }} \ + -v ~/.kube/kind/config:/root/.kube/config ${{ inputs.test-image }} \ + --docker-registry-user=oauth2accesstoken \ + --docker-registry-token=${{ inputs.registry-token }} \ --context=kind-${{ github.run_id }} \ - --image=docker.io/nginx/${{ steps.ingress-type.outputs.name }}:${{ steps.ingress-type.outputs.tag }} \ + --image=${{ inputs.image-name }}:${{ inputs.tag }} \ --image-pull-policy=Never \ - --ic-type=${{ steps.ingress-type.outputs.name }} \ + --ic-type=nginx${{ contains(inputs.image-type, 'plus') && '-plus' || '' }}-ingress \ --service=nodeport --node-ip=${{ steps.k8s.outputs.cluster_ip }} \ - --html=tests-${{ steps.k8s.outputs.cluster }}.html \ + --html=${{ steps.k8s.outputs.test_output_path }} \ --self-contained-html \ --durations=10 \ --show-ic-logs=yes \ + --ad-secret=${{ inputs.azure-ad-secret }} \ + --plus-jwt=${{ inputs.plus-jwt }} \ -m ${{ inputs.marker != '' && inputs.marker || '""' }} working-directory: ./tests shell: bash diff --git a/.github/config/config-gcr-retag b/.github/config/config-gcr-retag new file mode 100644 index 0000000000..07e0e71beb --- /dev/null +++ b/.github/config/config-gcr-retag @@ -0,0 +1,7 @@ +export TARGET_REGISTRY=gcr.io/f5-gcs-7899-ptg-ingrss-ctlr/dev +declare -a PLUS_TAG_POSTFIX_LIST=("" "-alpine" "-alpine-fips" "-mktpl") +declare -a NAP_WAF_TAG_POSTFIX_LIST=("" "-mktpl" "-alpine-fips") +declare -a NAP_WAFV5_TAG_POSTFIX_LIST=("" "-alpine-fips") +declare -a NAP_DOS_TAG_POSTFIX_LIST=("" "-mktpl") +declare -a NAP_WAF_DOS_TAG_POSTFIX_LIST=("" "-mktpl") +declare -a ADDITIONAL_TAGS=() diff --git a/.github/config/config-oss-dockerhub b/.github/config/config-oss-dockerhub new file mode 100644 index 0000000000..a25443b2b7 --- /dev/null +++ b/.github/config/config-oss-dockerhub @@ -0,0 +1,6 @@ +export TARGET_REGISTRY=docker.io +export TARGET_OSS_IMAGE_PREFIX="nginx/nginx-ingress" +export PUBLISH_PLUS=false +export PUBLISH_WAF=false +export PUBLISH_DOS=false +export PUBLISH_WAF_DOS=false diff --git a/.github/config/config-oss-ecr b/.github/config/config-oss-ecr new file mode 100644 index 0000000000..626d0c3748 --- /dev/null +++ b/.github/config/config-oss-ecr @@ -0,0 +1,6 @@ +export TARGET_REGISTRY=public.ecr.aws +export TARGET_OSS_IMAGE_PREFIX="nginx/nginx-ingress" +export PUBLISH_PLUS=false +export PUBLISH_WAF=false +export PUBLISH_DOS=false +export PUBLISH_WAF_DOS=false diff --git a/.github/config/config-oss-gcr-release b/.github/config/config-oss-gcr-release new file mode 100644 index 0000000000..89fded6495 --- /dev/null +++ b/.github/config/config-oss-gcr-release @@ -0,0 +1,5 @@ +export TARGET_REGISTRY=gcr.io/f5-gcs-7899-ptg-ingrss-ctlr/release +export PUBLISH_PLUS=false +export PUBLISH_WAF=false +export PUBLISH_DOS=false +export PUBLISH_WAF_DOS=false diff --git a/.github/config/config-oss-github b/.github/config/config-oss-github new file mode 100644 index 0000000000..7a875b18d5 --- /dev/null +++ b/.github/config/config-oss-github @@ -0,0 +1,6 @@ +export TARGET_REGISTRY=ghcr.io +export TARGET_OSS_IMAGE_PREFIX="nginxinc/kubernetes-ingress" +export PUBLISH_PLUS=false +export PUBLISH_WAF=false +export PUBLISH_DOS=false +export PUBLISH_WAF_DOS=false diff --git a/.github/config/config-oss-quay b/.github/config/config-oss-quay new file mode 100644 index 0000000000..cfe25520cc --- /dev/null +++ b/.github/config/config-oss-quay @@ -0,0 +1,6 @@ +export TARGET_REGISTRY=quay.io +export TARGET_OSS_IMAGE_PREFIX="nginx/nginx-ingress" +export PUBLISH_PLUS=false +export PUBLISH_WAF=false +export PUBLISH_DOS=false +export PUBLISH_WAF_DOS=false diff --git a/.github/config/config-plus-azure b/.github/config/config-plus-azure new file mode 100644 index 0000000000..e9e70b04de --- /dev/null +++ b/.github/config/config-plus-azure @@ -0,0 +1,11 @@ +export TARGET_REGISTRY=nginxmktpl.azurecr.io +export TARGET_PLUS_IMAGE_PREFIX="marketplaceimages/nginx-plus-ingress" +export TARGET_NAP_WAF_IMAGE_PREFIX="marketplaceimages/nginx-plus-ingress-nap" +export TARGET_NAP_DOS_IMAGE_PREFIX="marketplaceimages/nginx-plus-ingress-dos" +export TARGET_NAP_WAF_DOS_IMAGE_PREFIX="marketplaceimages/nginx-plus-ingress-nap-dos" +declare -a PLUS_TAG_POSTFIX_LIST=("") +declare -a NAP_WAF_TAG_POSTFIX_LIST=("") +declare -a NAP_DOS_TAG_POSTFIX_LIST=("") +declare -a NAP_WAFV5_TAG_POSTFIX_LIST=() +declare -a NAP_WAF_DOS_TAG_POSTFIX_LIST=("") +export PUBLISH_OSS=false diff --git a/.github/config/config-plus-ecr b/.github/config/config-plus-ecr new file mode 100644 index 0000000000..c7f5c853d4 --- /dev/null +++ b/.github/config/config-plus-ecr @@ -0,0 +1,11 @@ +export TARGET_REGISTRY=709825985650.dkr.ecr.us-east-1.amazonaws.com +export TARGET_PLUS_IMAGE_PREFIX=nginx/nginx-plus-ingress +export TARGET_NAP_WAF_IMAGE_PREFIX=nginx/nginx-plus-ingress-nap +export TARGET_NAP_DOS_IMAGE_PREFIX=nginx/nginx-plus-ingress-dos +export TARGET_NAP_WAF_DOS_IMAGE_PREFIX=nginx/nginx-plus-ingress-dos-nap +declare -a PLUS_TAG_POSTFIX_LIST=("-mktpl") +declare -a NAP_WAF_TAG_POSTFIX_LIST=("-mktpl") +declare -a NAP_DOS_TAG_POSTFIX_LIST=("-mktpl") +declare -a NAP_WAFV5_TAG_POSTFIX_LIST=() +declare -a NAP_WAF_DOS_TAG_POSTFIX_LIST=("-mktpl") +export PUBLISH_OSS=false diff --git a/.github/config/config-plus-gcr-public b/.github/config/config-plus-gcr-public new file mode 100644 index 0000000000..47accb500f --- /dev/null +++ b/.github/config/config-plus-gcr-public @@ -0,0 +1,10 @@ +export PUBLISH_OSS=false +export PUBLISH_WAF_DOS=false +export TARGET_REGISTRY=gcr.io/f5-7626-networks-public +export TARGET_PLUS_IMAGE_PREFIX=nginxinc/nginx-plus-ingress +export TARGET_NAP_WAF_IMAGE_PREFIX=nginxinc/nginx-plus-ingress-nap +export TARGET_NAP_DOS_IMAGE_PREFIX=nginxinc/nginx-plus-ingress-dos +declare -a PLUS_TAG_POSTFIX_LIST=("") +declare -a NAP_WAF_TAG_POSTFIX_LIST=("") +declare -a NAP_WAFV5_TAG_POSTFIX_LIST=() +declare -a NAP_DOS_TAG_POSTFIX_LIST=() diff --git a/.github/config/config-plus-gcr-release b/.github/config/config-plus-gcr-release new file mode 100644 index 0000000000..9cf8fb9723 --- /dev/null +++ b/.github/config/config-plus-gcr-release @@ -0,0 +1,8 @@ +export TARGET_REGISTRY=gcr.io/f5-gcs-7899-ptg-ingrss-ctlr/release +declare -a PLUS_TAG_POSTFIX_LIST=("" "-alpine" "-alpine-fips" "-mktpl") +declare -a NAP_WAF_TAG_POSTFIX_LIST=("" "-alpine-fips" "-mktpl") +declare -a NAP_WAFV5_TAG_POSTFIX_LIST=("" "-alpine-fips") +declare -a NAP_DOS_TAG_POSTFIX_LIST=("" "-mktpl") +declare -a NAP_WAF_DOS_TAG_POSTFIX_LIST=("" "-mktpl") +declare -a ADDITIONAL_TAGS=("latest" "${ADDITIONAL_TAG}") +export PUBLISH_OSS=false diff --git a/.github/config/config-plus-nginx b/.github/config/config-plus-nginx new file mode 100644 index 0000000000..b7633a1434 --- /dev/null +++ b/.github/config/config-plus-nginx @@ -0,0 +1,8 @@ +export TARGET_REGISTRY=docker-mgmt.nginx.com +export TARGET_NAP_WAF_DOS_IMAGE_PREFIX="nginx-ic-nap-dos/nginx-plus-ingress" +declare -a PLUS_TAG_POSTFIX_LIST=("" "-alpine" "-alpine-fips") +declare -a NAP_WAF_TAG_POSTFIX_LIST=("" "-alpine-fips") +declare -a NAP_WAFV5_TAG_POSTFIX_LIST=("" "-alpine-fips") +declare -a NAP_DOS_TAG_POSTFIX_LIST=("") +declare -a NAP_WAF_DOS_TAG_POSTFIX_LIST=("") +export PUBLISH_OSS=false diff --git a/.github/data/matrix-images-nap.json b/.github/data/matrix-images-nap.json new file mode 100644 index 0000000000..a391e9314b --- /dev/null +++ b/.github/data/matrix-images-nap.json @@ -0,0 +1,37 @@ +{ + "image": [ + "debian-plus-nap" + ], + "platforms": [ + "linux/amd64" + ], + "target": [ + "goreleaser", + "aws" + ], + "nap_modules": [ + "dos", + "waf", + "waf,dos" + ], + "include": [ + { + "image": "alpine-plus-nap-fips", + "target": "goreleaser", + "platforms": "linux/amd64", + "nap_modules": "waf" + }, + { + "image": "alpine-plus-nap-v5-fips", + "target": "goreleaser", + "platforms": "linux/amd64", + "nap_modules": "waf" + }, + { + "image": "debian-plus-nap-v5", + "target": "goreleaser", + "platforms": "linux/amd64", + "nap_modules": "waf" + } + ] +} diff --git a/.github/data/matrix-images-oss.json b/.github/data/matrix-images-oss.json new file mode 100644 index 0000000000..7c94faf8e3 --- /dev/null +++ b/.github/data/matrix-images-oss.json @@ -0,0 +1,9 @@ +{ + "image": [ + "debian", + "alpine" + ], + "platforms": [ + "linux/arm, linux/arm64, linux/amd64, linux/ppc64le, linux/s390x" + ] +} diff --git a/.github/data/matrix-images-plus.json b/.github/data/matrix-images-plus.json new file mode 100644 index 0000000000..b74a88d670 --- /dev/null +++ b/.github/data/matrix-images-plus.json @@ -0,0 +1,20 @@ +{ + "image": [ + "debian-plus", + "alpine-plus", + "alpine-plus-fips" + ], + "platforms": [ + "linux/arm64, linux/amd64" + ], + "target": [ + "goreleaser" + ], + "include": [ + { + "image": "debian-plus", + "platforms": "linux/arm64, linux/amd64", + "target": "aws" + } + ] +} diff --git a/.github/data/matrix-regression.json b/.github/data/matrix-regression.json new file mode 100644 index 0000000000..c63a26bbaf --- /dev/null +++ b/.github/data/matrix-regression.json @@ -0,0 +1,35 @@ +{ + "k8s": [], + "images": [ + { + "label": "regression", + "image": "debian", + "type": "oss", + "marker": "'not upgrade'", + "platforms": "linux/arm, linux/arm64, linux/amd64, linux/ppc64le, linux/s390x" + }, + { + "label": "regression", + "image": "debian-plus", + "type": "plus", + "marker": "'not upgrade and not agent'", + "platforms": "linux/arm64, linux/amd64" + }, + { + "label": "regression", + "image": "debian-plus-nap", + "type": "plus", + "nap_modules": "waf", + "marker": "'appprotect or agent'", + "platforms": "linux/arm64, linux/amd64" + }, + { + "label": "regression", + "image": "debian-plus-nap-v5", + "type": "plus", + "nap_modules": "waf", + "marker": "appprotect_waf_v5", + "platforms": "linux/arm64, linux/amd64" + } + ] +} diff --git a/.github/data/matrix-smoke-nap.json b/.github/data/matrix-smoke-nap.json new file mode 100644 index 0000000000..b2d6f4a400 --- /dev/null +++ b/.github/data/matrix-smoke-nap.json @@ -0,0 +1,77 @@ +{ + "images": [ + { + "label": "AP_WAF 1/4", + "image": "debian-plus-nap", + "type": "plus", + "nap_modules": "waf", + "marker": "appprotect_waf_policies_allow", + "platforms": "linux/amd64" + }, + { + "label": "AP_WAF 2/4", + "image": "debian-plus-nap", + "type": "plus", + "nap_modules": "waf", + "marker": "'appprotect_waf_policies and not appprotect_waf_policies_allow and not appprotect_waf_policies_vsr'", + "platforms": "linux/amd64" + }, + { + "label": "AP_WAF 3/4", + "image": "alpine-plus-nap-fips", + "type": "plus", + "nap_modules": "waf", + "marker": "appprotect_waf_policies_grpc", + "platforms": "linux/amd64" + }, + { + "label": "AP_WAF 4/4", + "image": "debian-plus-nap", + "type": "plus", + "nap_modules": "waf", + "marker": "'appprotect_watch or appprotect_batch or appprotect_integration or appprotect_waf_policies_vsr'", + "platforms": "linux/amd64" + }, + { + "label": "AP_WAF_V5 1/1", + "image": "debian-plus-nap-v5", + "type": "plus", + "nap_modules": "waf", + "marker": "appprotect_waf_v5", + "platforms": "linux/amd64" + }, + { + "label": "AP_DOS 1/3", + "image": "debian-plus-nap", + "type": "plus", + "nap_modules": "dos", + "marker": "'dos and not dos_learning and not dos_ingress'", + "platforms": "linux/amd64" + }, + { + "label": "AP_DOS 2/3", + "image": "debian-plus-nap", + "type": "plus", + "nap_modules": "dos", + "marker": "'dos_ingress and not dos_learning'", + "platforms": "linux/amd64" + }, + { + "label": "AP_DOS 3/3", + "image": "debian-plus-nap", + "type": "plus", + "nap_modules": "dos", + "marker": "dos_learning", + "platforms": "linux/amd64" + }, + { + "label": "AGENT 1/1", + "image": "debian-plus-nap", + "type": "plus", + "nap_modules": "waf", + "marker": "agent", + "platforms": "linux/amd64" + } + ], + "k8s": [] +} diff --git a/.github/data/matrix-smoke-oss.json b/.github/data/matrix-smoke-oss.json new file mode 100644 index 0000000000..52a9a7f456 --- /dev/null +++ b/.github/data/matrix-smoke-oss.json @@ -0,0 +1,82 @@ +{ + "images": [ + { + "label": "ingresses 1/2", + "image": "debian", + "type": "oss", + "marker": "'ingresses and not annotations and not basic_auth and not hsts and not watch_namespace and not wildcard_tls'", + "platforms": "linux/arm, linux/arm64, linux/amd64, linux/ppc64le, linux/s390x" + }, + { + "label": "ingresses 2/2", + "image": "debian", + "type": "oss", + "marker": "'annotations or basic_auth or hsts or watch_namespace or wildcard_tls'", + "platforms": "linux/arm, linux/arm64, linux/amd64, linux/ppc64le, linux/s390x" + }, + { + "label": "VSR 1/3", + "image": "alpine", + "type": "oss", + "marker": "'vsr and not vsr_upstream and not vsr_grpc and not vsr_status and not vsr_canary and not vsr_routing and not vsr_api and not vsr_redirects and not vsr_rewrite and not vsr_canned and not vsr_basic'", + "platforms": "linux/arm, linux/arm64, linux/amd64, linux/ppc64le, linux/s390x" + }, + { + "label": "VSR 2/3", + "image": "alpine", + "type": "oss", + "marker": "'vsr_basic or vsr_canned or vsr_rewrite or vsr_redirects or vsr_upstream'", + "platforms": "linux/arm, linux/arm64, linux/amd64, linux/ppc64le, linux/s390x" + }, + { + "label": "VSR 3/3", + "image": "alpine", + "type": "oss", + "marker": "'vsr_api or vsr_routing or vsr_canary or vsr_status or vsr_grpc'", + "platforms": "linux/arm, linux/arm64, linux/amd64, linux/ppc64le, linux/s390x" + }, + { + "label": "policies 1/2", + "image": "alpine", + "type": "oss", + "marker": "'policies and not policies_rl and not policies_ac and not policies_jwt and not policies_mtls'", + "platforms": "linux/arm, linux/arm64, linux/amd64, linux/ppc64le, linux/s390x" + }, + { + "label": "policies 2/2", + "image": "alpine", + "type": "oss", + "marker": "'policies_rl or policies_ac or policies_jwt or policies_mtls'", + "platforms": "linux/arm, linux/arm64, linux/amd64, linux/ppc64le, linux/s390x" + }, + { + "label": "VS 1/3", + "image": "debian", + "type": "oss", + "marker": "'vs and not vs_ipv6 and not vs_rewrite and not vs_responses and not vs_grpc and not vs_redirects and not vs_externalname and not vs_externaldns and not vs_certmanager and not vs_api and not vs_backup and not vs_use_cluster_ip and not vs_canary and not vs_upstream and not vs_config_map'", + "platforms": "linux/arm, linux/arm64, linux/amd64, linux/ppc64le, linux/s390x" + }, + { + "label": "VS 2/3", + "image": "debian", + "type": "oss", + "marker": "'vs_grpc or vs_redirects or vs_externalname or vs_externaldns or vs_api or vs_backup or vs_use_cluster_ip or vs_canary or vs_upstream or vs_config_map'", + "platforms": "linux/arm, linux/arm64, linux/amd64, linux/ppc64le, linux/s390x" + }, + { + "label": "VS 3/3", + "image": "debian", + "type": "oss", + "marker": "'vs_responses or vs_ipv6 or vs_rewrite or vs_certmanager'", + "platforms": "linux/arm, linux/arm64, linux/amd64, linux/ppc64le, linux/s390x" + }, + { + "label": "TS", + "image": "debian", + "type": "oss", + "marker": "ts", + "platforms": "linux/arm64, linux/amd64, linux/ppc64le, linux/s390x" + } + ], + "k8s": [] +} diff --git a/.github/data/matrix-smoke-plus.json b/.github/data/matrix-smoke-plus.json new file mode 100644 index 0000000000..a67fa4addb --- /dev/null +++ b/.github/data/matrix-smoke-plus.json @@ -0,0 +1,82 @@ +{ + "images": [ + { + "label": "VS 1/3", + "image": "debian-plus", + "type": "plus", + "marker": "'vs and not vs_ipv6 and not vs_rewrite and not vs_responses and not vs_grpc and not vs_redirects and not vs_externalname and not vs_externaldns and not vs_certmanager and not vs_api and not vs_backup and not vs_use_cluster_ip and not vs_canary and not vs_upstream and not vs_config_map'", + "platforms": "linux/arm64, linux/amd64" + }, + { + "label": "VS 2/3", + "image": "debian-plus", + "type": "plus", + "marker": "'vs_grpc or vs_redirects or vs_externalname or vs_externaldns or vs_api or vs_backup or vs_use_cluster_ip or vs_canary or vs_upstream or vs_config_map'", + "platforms": "linux/arm64, linux/amd64" + }, + { + "label": "VS 3/3", + "image": "debian-plus", + "type": "plus", + "marker": "'vs_responses or vs_ipv6 or vs_rewrite or vs_certmanager'", + "platforms": "linux/arm64, linux/amd64" + }, + { + "label": "TS", + "image": "debian-plus", + "type": "plus", + "marker": "ts", + "platforms": "linux/arm64, linux/amd64" + }, + { + "label": "ingresses 1/2", + "image": "alpine-plus", + "type": "plus", + "marker": "'ingresses and not annotations and not basic_auth and not hsts and not watch_namespace and not wildcard_tls'", + "platforms": "linux/arm64, linux/amd64" + }, + { + "label": "ingresses 2/2", + "image": "alpine-plus-fips", + "type": "plus", + "marker": "'annotations or basic_auth or hsts or watch_namespace or wildcard_tls'", + "platforms": "linux/arm64, linux/amd64" + }, + { + "label": "VSR 1/3", + "image": "alpine-plus", + "type": "plus", + "marker": "'vsr and not vsr_upstream and not vsr_grpc and not vsr_status and not vsr_canary and not vsr_routing and not vsr_api and not vsr_redirects and not vsr_rewrite and not vsr_canned and not vsr_basic'", + "platforms": "linux/arm64, linux/amd64" + }, + { + "label": "VSR 2/3", + "image": "alpine-plus-fips", + "type": "plus", + "marker": "'vsr_basic or vsr_canned or vsr_rewrite or vsr_redirects or vsr_upstream'", + "platforms": "linux/arm64, linux/amd64" + }, + { + "label": "VSR 3/3", + "image": "alpine-plus", + "type": "plus", + "marker": "'vsr_api or vsr_routing or vsr_canary or vsr_status or vsr_grpc'", + "platforms": "linux/arm64, linux/amd64" + }, + { + "label": "policies 1/2", + "image": "alpine-plus", + "type": "plus", + "marker": "'policies and not policies_ac and not policies_jwt and not policies_mtls'", + "platforms": "linux/arm64, linux/amd64, linux/s390x" + }, + { + "label": "policies 2/2", + "image": "debian-plus", + "type": "plus", + "marker": "'policies_ac or policies_jwt or policies_mtls'", + "platforms": "linux/arm64, linux/amd64, linux/s390x" + } + ], + "k8s": [] +} diff --git a/.github/data/patch-images.json b/.github/data/patch-images.json new file mode 100644 index 0000000000..22b2662e35 --- /dev/null +++ b/.github/data/patch-images.json @@ -0,0 +1,92 @@ +[ + { + "source_image": "gcr.io/f5-gcs-7899-ptg-ingrss-ctlr/release/nginx-ic/nginx-ingress", + "source_os": "debian", + "target_image": "gcr.io/f5-gcs-7899-ptg-ingrss-ctlr/dev/nginx-ic/nginx-ingress", + "platforms": "linux/arm, linux/arm64, linux/amd64, linux/ppc64le, linux/s390x" + }, + { + "source_image": "gcr.io/f5-gcs-7899-ptg-ingrss-ctlr/release/nginx-ic/nginx-ingress", + "source_os": "alpine", + "target_image": "gcr.io/f5-gcs-7899-ptg-ingrss-ctlr/dev/nginx-ic/nginx-ingress", + "platforms": "linux/arm, linux/arm64, linux/amd64, linux/ppc64le, linux/s390x" + }, + { + "source_image": "gcr.io/f5-gcs-7899-ptg-ingrss-ctlr/release/nginx-ic/nginx-plus-ingress", + "source_os": "debian", + "target_image": "gcr.io/f5-gcs-7899-ptg-ingrss-ctlr/dev/nginx-ic/nginx-plus-ingress", + "platforms": "linux/arm64, linux/amd64" + }, + { + "source_image": "gcr.io/f5-gcs-7899-ptg-ingrss-ctlr/release/nginx-ic/nginx-plus-ingress", + "source_os": "mktpl", + "target_image": "gcr.io/f5-gcs-7899-ptg-ingrss-ctlr/dev/nginx-ic/nginx-plus-ingress", + "platforms": "linux/arm64, linux/amd64" + }, + { + "source_image": "gcr.io/f5-gcs-7899-ptg-ingrss-ctlr/release/nginx-ic/nginx-plus-ingress", + "source_os": "alpine", + "target_image": "gcr.io/f5-gcs-7899-ptg-ingrss-ctlr/dev/nginx-ic/nginx-plus-ingress", + "platforms": "linux/arm64, linux/amd64" + }, + { + "source_image": "gcr.io/f5-gcs-7899-ptg-ingrss-ctlr/release/nginx-ic/nginx-plus-ingress", + "source_os": "alpine-fips", + "target_image": "gcr.io/f5-gcs-7899-ptg-ingrss-ctlr/dev/nginx-ic/nginx-plus-ingress", + "platforms": "linux/arm64, linux/amd64" + }, + { + "source_image": "gcr.io/f5-gcs-7899-ptg-ingrss-ctlr/release/nginx-ic-nap/nginx-plus-ingress", + "source_os": "debian", + "target_image": "gcr.io/f5-gcs-7899-ptg-ingrss-ctlr/dev/nginx-ic-nap/nginx-plus-ingress", + "platforms": "linux/amd64" + }, + { + "source_image": "gcr.io/f5-gcs-7899-ptg-ingrss-ctlr/release/nginx-ic-nap/nginx-plus-ingress", + "source_os": "mktpl", + "target_image": "gcr.io/f5-gcs-7899-ptg-ingrss-ctlr/dev/nginx-ic-nap/nginx-plus-ingress", + "platforms": "linux/amd64" + }, + { + "source_image": "gcr.io/f5-gcs-7899-ptg-ingrss-ctlr/release/nginx-ic-nap/nginx-plus-ingress", + "source_os": "alpine-fips", + "target_image": "gcr.io/f5-gcs-7899-ptg-ingrss-ctlr/dev/nginx-ic-nap/nginx-plus-ingress", + "platforms": "linux/amd64" + }, + { + "source_image": "gcr.io/f5-gcs-7899-ptg-ingrss-ctlr/release/nginx-ic-nap-v5/nginx-plus-ingress", + "source_os": "debian", + "target_image": "gcr.io/f5-gcs-7899-ptg-ingrss-ctlr/dev/nginx-ic-nap-v5/nginx-plus-ingress", + "platforms": "linux/amd64" + }, + { + "source_image": "gcr.io/f5-gcs-7899-ptg-ingrss-ctlr/release/nginx-ic-nap-v5/nginx-plus-ingress", + "source_os": "alpine-fips", + "target_image": "gcr.io/f5-gcs-7899-ptg-ingrss-ctlr/dev/nginx-ic-nap-v5/nginx-plus-ingress", + "platforms": "linux/amd64" + }, + { + "source_image": "gcr.io/f5-gcs-7899-ptg-ingrss-ctlr/release/nginx-ic-dos/nginx-plus-ingress", + "source_os": "debian", + "target_image": "gcr.io/f5-gcs-7899-ptg-ingrss-ctlr/dev/nginx-ic-dos/nginx-plus-ingress", + "platforms": "linux/amd64" + }, + { + "source_image": "gcr.io/f5-gcs-7899-ptg-ingrss-ctlr/release/nginx-ic-dos/nginx-plus-ingress", + "source_os": "mktpl", + "target_image": "gcr.io/f5-gcs-7899-ptg-ingrss-ctlr/dev/nginx-ic-dos/nginx-plus-ingress", + "platforms": "linux/amd64" + }, + { + "source_image": "gcr.io/f5-gcs-7899-ptg-ingrss-ctlr/release/nginx-ic-dos-nap/nginx-plus-ingress", + "source_os": "debian", + "target_image": "gcr.io/f5-gcs-7899-ptg-ingrss-ctlr/dev/nginx-ic-dos-nap/nginx-plus-ingress", + "platforms": "linux/amd64" + }, + { + "source_image": "gcr.io/f5-gcs-7899-ptg-ingrss-ctlr/release/nginx-ic-dos-nap/nginx-plus-ingress", + "source_os": "mktpl", + "target_image": "gcr.io/f5-gcs-7899-ptg-ingrss-ctlr/dev/nginx-ic-dos-nap/nginx-plus-ingress", + "platforms": "linux/amd64" + } +] diff --git a/.github/data/version.txt b/.github/data/version.txt new file mode 100644 index 0000000000..aa03bad5f2 --- /dev/null +++ b/.github/data/version.txt @@ -0,0 +1,2 @@ +IC_VERSION=4.1.0 +HELM_CHART_VERSION=2.1.0 diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 83be78dba3..55c11a6ac1 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,38 +1,61 @@ version: 2 updates: - - package-ecosystem: "github-actions" - directory: "/" + - package-ecosystem: github-actions + directory: / schedule: interval: daily - reviewers: - - "nginxinc/kic" - - package-ecosystem: "gomod" - directory: "/" + groups: + actions: + update-types: + - "major" + - "minor" + - "patch" + + - package-ecosystem: gomod + directory: / schedule: interval: daily - reviewers: - - "nginxinc/kic" - - package-ecosystem: "docker" - directory: "/build" + groups: + go: + update-types: + - "major" + - "minor" + - "patch" + + - package-ecosystem: docker + directory: /build schedule: interval: daily - reviewers: - - "nginxinc/kic" - - package-ecosystem: "docker" - directory: "/tests/docker" + groups: + docker-images: + update-types: + - "major" + - "minor" + - "patch" + + - package-ecosystem: docker + directory: /tests schedule: interval: daily - reviewers: - - "nginxinc/kic" - - package-ecosystem: "pip" - directory: "/tests" + groups: + docker-tests: + update-types: + - "major" + - "minor" + - "patch" + + - package-ecosystem: pip + directory: /tests schedule: - interval: daily - reviewers: - - "nginxinc/kic" - - package-ecosystem: "pip" - directory: "/perf-tests" + interval: weekly + groups: + python: + update-types: + - "major" + - "minor" + - "patch" + + - package-ecosystem: gomod + directory: /site schedule: - interval: daily - reviewers: - - "nginxinc/kic" + interval: weekly diff --git a/.github/labeler.yml b/.github/labeler.yml index c03285e9bc..fb698cc48f 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -1,22 +1,50 @@ +change: + - head-branch: ['^change/'] + enhancement: -- branch: ['feature/**', 'feat/**', 'enhancement/**', 'enh/**'] + - head-branch: ['^feature/', '^feat/', '^enhancement/', '^enh/'] bug: -- branch: ['fix/**', 'bug/**'] + - head-branch: ['^fix/', '^bug/'] chore: -- branch: ['chore/**'] + - all: + - changed-files: + - any-glob-to-any-file: ['.github/**/*', 'hack/**/*', '*'] + - all-globs-to-all-files: ['!*.md', '!go.mod', '!go.sum', '!*.go'] + +github_actions: + - changed-files: + - any-glob-to-any-file: ['.github/**/*.yml', '.github/**/*.yaml', '.github/**/*.md'] + +go: + - changed-files: + - any-glob-to-any-file: ['**/*.go', 'go.mod', 'go.sum'] + +python: + - changed-files: + - any-glob-to-any-file: ['tests/**/*.py', 'tests/**/requirements.txt', 'tests/**/*.yaml', 'perf-tests/**/*.py', 'perf-tests/**/*.yaml'] tests: -- branch: ['tests/**', 'test/**'] -- tests/**/* -- perf-tests/**/* + - all: + - changed-files: + - any-glob-to-any-file: ['tests/**/*', 'perf-tests/**/*'] + - all-globs-to-all-files: ['!tests/requirements.txt', '!perf-tests/requirements.txt'] documentation: -- branch: ['docs/**', 'doc/**'] -- '**/*.md' + - head-branch: ['^docs/', '^doc/'] + - changed-files: + - any-glob-to-any-file: '**/*.md' dependencies: -- branch: ['deps/**', 'dep/**', 'dependabot/**'] -- go.mod -- go.sum + - head-branch: ['^deps/', '^dep/', '^dependabot/', 'pre-commit-ci-update-config'] + - changed-files: + - any-glob-to-any-file: ['go.mod', 'go.sum'] + +helm_chart: + - changed-files: + - any-glob-to-any-file: 'charts/nginx-ingress/**/*' + +docker: + - changed-files: + - any-glob-to-any-file: '**/Dockerfile' diff --git a/.github/labels.yml b/.github/labels.yml deleted file mode 100644 index b61a945ba4..0000000000 --- a/.github/labels.yml +++ /dev/null @@ -1,54 +0,0 @@ -- color: fc2929 - description: An issue reporting a potential bug - name: bug -- color: b60205 - description: Pull requests that introduce a change - name: change -- color: 3a2716 - description: Pull requests for routine tasks - name: chore -- color: 0366d6 - description: Pull requests that update a dependency file - name: dependencies -- color: 21ceff - description: Pull requests that update Docker code - name: docker -- color: c5def5 - description: Pull requests/issues for documentation - name: documentation -- color: 84b6eb - description: Pull requests for new features/feature enhancements - name: enhancement -- color: "000000" - description: Pull requests that update Github_actions code - name: github_actions -- color: 16e2e2 - description: Pull requests that update Go code - name: go -- color: c5def5 - description: Gathering information - name: in_review -- color: db754c - description: An issue that proposes a feature request - name: proposal -- color: 2b67c6 - description: Pull requests that update Python code - name: python -- color: cc317c - description: An issue asking a question - name: question -- color: FEF2C0 - description: Pull requests that don't need to be added to the changelog - name: skip-changelog -- color: 8E7888 - description: Pull requests/issues with no activity - name: stale -- color: A4EF7D - description: Pull requests that update tests - name: tests -- color: C2E0C6 - description: Waiting for author's response - name: waiting for response -- color: ffffff - description: An issue that does not need to be fixed - name: wontfix diff --git a/.github/release.yml b/.github/release.yml index 5ff5cedd02..10209f2eee 100644 --- a/.github/release.yml +++ b/.github/release.yml @@ -1,26 +1,29 @@ changelog: exclude: labels: - - skip-changelog + - skip changelog categories: - - title: 🚀 Features - labels: - - enhancement - title: 💣 Breaking Changes labels: - change + - title: 🚀 Features + labels: + - enhancement - title: 🐛 Bug Fixes labels: - bug - - title: 📝 Documentation + - title: 📦 Helm Chart labels: - - documentation + - helm_chart - title: 🧪 Tests labels: - tests - title: 🔨 Maintenance labels: - chore + - title: 📝 Documentation + labels: + - documentation - title: ⬆️ Dependencies labels: - dependencies diff --git a/.github/scripts/copy-images.sh b/.github/scripts/copy-images.sh new file mode 100755 index 0000000000..bb3a2240ea --- /dev/null +++ b/.github/scripts/copy-images.sh @@ -0,0 +1,186 @@ +#!/usr/bin/env bash + +set -eo pipefail + +# shellcheck disable=SC2155 +export ROOTDIR=$(git rev-parse --show-toplevel || echo ".") + +SKOPEO_BIN=skopeo +if [ -n "$CI" ]; then + SKOPEO_BIN="docker run --rm -v $HOME/.docker/config.json:/tmp/auth.json $(grep skopeo "${ROOTDIR}/tests/Dockerfile" | grep FROM | cut -d ' ' -f 2)" +fi + +## Setup inputs + +SOURCE_TAG=${SOURCE_TAG:-stable} +TARGET_TAG=${TARGET_TAG:-edge} +ADDITIONAL_TAG=${ADDITIONAL_TAG:-""} + +SOURCE_REGISTRY=${1:-"gcr.io/f5-gcs-7899-ptg-ingrss-ctlr/dev"} +TARGET_REGISTRY=${2:-"gcr.io/f5-gcs-7899-ptg-ingrss-ctlr/release"} + +REGISTRY_USERNAME=${REGISTRY_USERNAME:-""} +REGISTRY_PASSWORD=${REGISTRY_PASSWORD:-""} + +PUBLISH_OSS=${PUBLISH_OSS:-true} +PUBLISH_PLUS=${PUBLISH_PLUS:-true} +PUBLISH_WAF=${PUBLISH_WAF:-true} +PUBLISH_DOS=${PUBLISH_DOS:-true} +PUBLISH_WAF_DOS=${PUBLISH_WAF_DOS:-true} + +DRY_RUN=${DRY_RUN:-false} + +SOURCE_OSS_IMAGE_PREFIX=${SOURCE_OSS_IMAGE_PREFIX:-"nginx-ic/nginx-ingress"} + +TARGET_OSS_IMAGE_PREFIX=${TARGET_OSS_IMAGE_PREFIX:-"nginx-ic/nginx-ingress"} + +SOURCE_PLUS_IMAGE_PREFIX=${SOURCE_PLUS_IMAGE_PREFIX:-"nginx-ic/nginx-plus-ingress"} +SOURCE_NAP_WAF_IMAGE_PREFIX=${SOURCE_NAP_WAF_IMAGE_PREFIX:-"nginx-ic-nap/nginx-plus-ingress"} +SOURCE_NAP_WAFV5_IMAGE_PREFIX=${SOURCE_NAP_WAFV5_IMAGE_PREFIX:-"nginx-ic-nap-v5/nginx-plus-ingress"} +SOURCE_NAP_DOS_IMAGE_PREFIX=${SOURCE_NAP_DOS_IMAGE_PREFIX:-"nginx-ic-dos/nginx-plus-ingress"} +SOURCE_NAP_WAF_DOS_IMAGE_PREFIX=${SOURCE_NAP_WAF_DOS_IMAGE_PREFIX:-"nginx-ic-dos-nap/nginx-plus-ingress"} + +TARGET_PLUS_IMAGE_PREFIX=${TARGET_PLUS_IMAGE_PREFIX:-"nginx-ic/nginx-plus-ingress"} +TARGET_NAP_WAF_IMAGE_PREFIX=${TARGET_NAP_WAF_IMAGE_PREFIX:-"nginx-ic-nap/nginx-plus-ingress"} +TARGET_NAP_WAFV5_IMAGE_PREFIX=${TARGET_NAP_WAFV5_IMAGE_PREFIX:-"nginx-ic-nap-v5/nginx-plus-ingress"} +TARGET_NAP_DOS_IMAGE_PREFIX=${TARGET_NAP_DOS_IMAGE_PREFIX:-"nginx-ic-dos/nginx-plus-ingress"} +TARGET_NAP_WAF_DOS_IMAGE_PREFIX=${TARGET_NAP_WAF_DOS_IMAGE_PREFIX:-"nginx-ic-dos-nap/nginx-plus-ingress"} + +declare -a OSS_TAG_POSTFIX_LIST=("" "-ubi" "-alpine") +declare -a PLUS_TAG_POSTFIX_LIST=("" "-ubi" "-alpine" "-alpine-fips") +declare -a NAP_WAF_TAG_POSTFIX_LIST=("" "-ubi" "-alpine-fips") +declare -a NAP_WAFV5_TAG_POSTFIX_LIST=("" "-ubi" "-alpine-fips") +declare -a NAP_DOS_TAG_POSTFIX_LIST=("" "-ubi") +declare -a NAP_WAF_DOS_TAG_POSTFIX_LIST=("" "-ubi") + +CONFIG_PATH=${CONFIG_PATH:-~/.nic-release/config} +if [ -f "$CONFIG_PATH" ]; then + # shellcheck source=/dev/null + . "$CONFIG_PATH" +fi + +SOURCE_OPTS=${SOURCE_OPTS:-""} +if [[ $SOURCE_REGISTRY =~ mgmt ]] || [[ $SOURCE_REGISTRY =~ private ]] ; then + if [ "${CI}" != 'true' ]; then + SOURCE_OPTS="--src-username ${REGISTRY_USERNAME} --src-password ${REGISTRY_PASSWORD}" + fi +fi + +TARGET_OPTS=${TARGET_OPTS:-""} +if [[ $TARGET_REGISTRY =~ mgmt ]]; then + if [ "${CI}" != 'true' ]; then + TARGET_OPTS="--dest-username ${REGISTRY_USERNAME} --dest-password ${REGISTRY_PASSWORD}" + fi +fi + +# cannot push the same tag twice +IS_IMMUTABLE=false +if [[ $TARGET_REGISTRY =~ 709825985650.dkr.ecr ]]; then + IS_IMMUTABLE=true +fi + +ARCH_OPTS="-a" +if [[ $TARGET_REGISTRY =~ f5-7626-networks-public ]] || [[ $TARGET_REGISTRY =~ nginxmktpl ]]; then + ARCH_OPTS="--override-os linux --override-arch amd64" +fi + +## Main publish loops + +if $PUBLISH_OSS; then + for postfix in "${OSS_TAG_POSTFIX_LIST[@]}"; do + image=${SOURCE_REGISTRY}/${SOURCE_OSS_IMAGE_PREFIX}:${SOURCE_TAG}${postfix} + echo "Processing image ${image}" + new_tag=${TARGET_REGISTRY}/${TARGET_OSS_IMAGE_PREFIX}:${TARGET_TAG}${postfix} + echo " Pushing image OSS ${new_tag}..." + if ! $DRY_RUN; then + ${SKOPEO_BIN} copy --retry-times 5 ${ARCH_OPTS} ${SOURCE_OPTS} ${TARGET_OPTS} docker://${image} docker://${new_tag} + fi + done +else + echo "Skipping Publish OSS flow" +fi + +if $PUBLISH_PLUS; then + for postfix in "${PLUS_TAG_POSTFIX_LIST[@]}"; do + image=${SOURCE_REGISTRY}/${SOURCE_PLUS_IMAGE_PREFIX}:${SOURCE_TAG}${postfix} + echo "Processing image ${image}" + new_tag=${TARGET_REGISTRY}/${TARGET_PLUS_IMAGE_PREFIX}:${TARGET_TAG}${postfix} + if $IS_IMMUTABLE && ${SKOPEO_BIN} --override-os linux --override-arch amd64 inspect docker://${new_tag} > /dev/null 2>&1; then + echo " ECR is immutable & tag ${new_tag} already exists, skipping." + else + echo " Pushing image Plus ${new_tag}..." + if ! $DRY_RUN; then + ${SKOPEO_BIN} copy --retry-times 5 ${ARCH_OPTS} ${SOURCE_OPTS} ${TARGET_OPTS} docker://${image} docker://${new_tag} + fi + fi + done +else + echo "Skipping Publish Plus flow" +fi + +if $PUBLISH_WAF; then + for postfix in "${NAP_WAF_TAG_POSTFIX_LIST[@]}"; do + image=${SOURCE_REGISTRY}/${SOURCE_NAP_WAF_IMAGE_PREFIX}:${SOURCE_TAG}${postfix} + echo "Processing image ${image}" + new_tag=${TARGET_REGISTRY}/${TARGET_NAP_WAF_IMAGE_PREFIX}:${TARGET_TAG}${postfix} + if $IS_IMMUTABLE && ${SKOPEO_BIN} --override-os linux --override-arch amd64 inspect docker://${new_tag} > /dev/null 2>&1; then + echo " ECR is immutable & tag ${new_tag} already exists, skipping." + else + echo " Pushing image NAP WAF ${new_tag}..." + if ! $DRY_RUN; then + ${SKOPEO_BIN} copy --retry-times 5 ${ARCH_OPTS} ${SOURCE_OPTS} ${TARGET_OPTS} docker://${image} docker://${new_tag} + fi + fi + done + for postfix in "${NAP_WAFV5_TAG_POSTFIX_LIST[@]}"; do + image=${SOURCE_REGISTRY}/${SOURCE_NAP_WAFV5_IMAGE_PREFIX}:${SOURCE_TAG}${postfix} + echo "Processing image ${image}" + new_tag=${TARGET_REGISTRY}/${TARGET_NAP_WAFV5_IMAGE_PREFIX}:${TARGET_TAG}${postfix} + if $IS_IMMUTABLE && ${SKOPEO_BIN} --override-os linux --override-arch amd64 inspect docker://${new_tag} > /dev/null 2>&1; then + echo " ECR is immutable & tag ${new_tag} already exists, skipping." + else + echo " Pushing image NAP WAFV5 ${new_tag}..." + if ! $DRY_RUN; then + ${SKOPEO_BIN} copy --retry-times 5 ${ARCH_OPTS} ${SOURCE_OPTS} ${TARGET_OPTS} docker://${image} docker://${new_tag} + fi + fi + done +else + echo "Skipping Publish Plus WAF flow" +fi + +if $PUBLISH_DOS; then + for postfix in "${NAP_DOS_TAG_POSTFIX_LIST[@]}"; do + image=${SOURCE_REGISTRY}/${SOURCE_NAP_DOS_IMAGE_PREFIX}:${SOURCE_TAG}${postfix} + echo "Processing image ${image}" + new_tag=${TARGET_REGISTRY}/${TARGET_NAP_DOS_IMAGE_PREFIX}:${TARGET_TAG}${postfix} + if $IS_IMMUTABLE && ${SKOPEO_BIN} --override-os linux --override-arch amd64 inspect docker://${new_tag} > /dev/null 2>&1; then + echo " ECR is immutable & tag ${new_tag} already exists, skipping." + else + echo " Pushing image NAP DOS ${new_tag}..." + if ! $DRY_RUN; then + ${SKOPEO_BIN} copy --retry-times 5 ${ARCH_OPTS} ${SOURCE_OPTS} ${TARGET_OPTS} docker://${image} docker://${new_tag} + fi + fi + done +else + echo "Skipping Publish Plus DOS flow" +fi + +if $PUBLISH_WAF_DOS; then + for postfix in "${NAP_WAF_DOS_TAG_POSTFIX_LIST[@]}"; do + image=${SOURCE_REGISTRY}/${SOURCE_NAP_WAF_DOS_IMAGE_PREFIX}:${SOURCE_TAG}${postfix} + echo "Processing image ${image}" + new_tag=${TARGET_REGISTRY}/${TARGET_NAP_WAF_DOS_IMAGE_PREFIX}:${TARGET_TAG}${postfix} + if $IS_IMMUTABLE && ${SKOPEO_BIN} --override-os linux --override-arch amd64 inspect docker://${new_tag} > /dev/null 2>&1; then + echo " ECR is immutable & tag ${new_tag} already exists, skipping." + else + echo " Pushing image NAP WAF/DOS ${new_tag}..." + if ! $DRY_RUN; then + ${SKOPEO_BIN} copy --retry-times 5 ${ARCH_OPTS} ${SOURCE_OPTS} ${TARGET_OPTS} docker://${image} docker://${new_tag} + fi + fi + done +else + echo "Skipping Publish Plus WAF/DOS flow" +fi diff --git a/.github/scripts/create-release-tarballs.sh b/.github/scripts/create-release-tarballs.sh new file mode 100755 index 0000000000..4954c4dfe5 --- /dev/null +++ b/.github/scripts/create-release-tarballs.sh @@ -0,0 +1,43 @@ +#!/usr/bin/env bash + +set -e + +directory=$1 +version=$2 +tarball_dir=${TARBALL_DIR:-tarballs} +releases=$(find "${directory}" -mindepth 1 -type d) +syft_binary=${SYFT_BIN:-"syft"} +cosign_binary=${COSIGN_BIN:-"cosign"} + +if [ ! -d "${tarball_dir}" ]; then + mkdir "${tarball_dir}" +fi + +for i in ${releases}; do + # fix for v1 in kubernetes-ingress_linux_amd64_v1 + if [[ ${i} =~ v1 ]]; then + mv "${i}" "${i%*_v1}" + i=${i%*_v1} + fi + + if [[ ${i} =~ aws ]]; then + continue + fi + product_name=$(basename "${i}" | cut -d '_' -f 1) + product_arch=$(echo "${i}" | cut -d '_' -f 2-) + product_release="${product_name}_${version}_${product_arch}" + # shellcheck disable=SC2086 + tarball_name="${tarball_dir}/${product_release}.tar.gz" + cp -r "${i}" "${directory}/${product_release}" + cp README.md LICENSE CHANGELOG.md "${directory}/${product_release}" + + tar -czf "${tarball_name}" "${directory}/${product_release}" + ${syft_binary} scan file:"${directory}/${product_release}/nginx-ingress" -o spdx-json > "${tarball_name}.spdx.json" + pushd "${tarball_dir}" + sha256sum "${product_release}.tar.gz" >> "${product_name}_${version}_checksums.txt" + sha256sum "${product_release}.tar.gz.spdx.json" >> "${product_name}_${version}_checksums.txt" + popd +done + +checksum_file=$(ls "${tarball_dir}"/*_checksums.txt ) +${cosign_binary} sign-blob "${checksum_file}" --output-signature="${checksum_file}.sig" --output-certificate="${checksum_file}.pem" -y diff --git a/.github/scripts/docker-updater.sh b/.github/scripts/docker-updater.sh new file mode 100755 index 0000000000..6827e70cd1 --- /dev/null +++ b/.github/scripts/docker-updater.sh @@ -0,0 +1,75 @@ +#!/usr/bin/env bash + +set -o pipefail + +SCRIPT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P)" +DOCKER_FILE=${SCRIPT_ROOT}/build/Dockerfile +exclude_strings="" + +# Parse command line arguments +while [[ $# -gt 0 ]]; do + key="$1" + case $key in + --exclude) + exclude_strings="$2" + shift + shift + ;; + *) + DOCKER_FILE="$1" + shift + ;; + esac +done + +# Check if the file exists +if [ ! -f "$DOCKER_FILE" ]; then + echo "File $DOCKER_FILE does not exist." + exit 1 +fi + +function contains_excluded() { + local line="$1" + local exclude="$2" + local IFS=',' + local excluded=($exclude) + for word in "${excluded[@]}"; do + if [[ "$line" == *"$word"* ]]; then + return 0 + fi + done + return 1 +} + +function check_sha() { + image_sha="$1" + image=$(echo "$image_sha" | cut -d '@' -f1) + tag_sha=$(echo "$image_sha" | cut -d '@' -f2) + + docker pull -q "$image" > /dev/null + latest_digest=$(docker inspect --format='{{index .RepoDigests 0}}' "$image") + latest_sha=$(echo "$latest_digest" | cut -d '@' -f2) + + if [ "$tag_sha" = "$latest_sha" ]; then + echo "The provided SHA256 hash is the latest for $image" + else + echo "> A newer version of $image is available:" + echo "> - $image@$tag_sha" + echo "> + $image@$latest_sha" + echo "> updating $DOCKER_FILE" + sed -i -e "s/$tag_sha/$latest_sha/g" "$DOCKER_FILE" + fi +} +if [ -n "$exclude_strings" ]; then + echo "excluding images containing one of: '$exclude_strings'" +fi +while IFS= read -r line; do + if [[ $line =~ ^FROM\ (.+@.+) ]]; then + image=$(echo "${BASH_REMATCH[1]}" | awk '{print $1}') + if [ -n "$exclude_strings" ] && contains_excluded "$line" "$exclude_strings"; then + echo "Skipping $image" + continue + fi + check_sha "$image" + fi +done < "$DOCKER_FILE" diff --git a/.github/scripts/exclude_ci_files.txt b/.github/scripts/exclude_ci_files.txt new file mode 100644 index 0000000000..c82a068a93 --- /dev/null +++ b/.github/scripts/exclude_ci_files.txt @@ -0,0 +1,54 @@ +.github/actionlint.yaml +.github/dependabot.yml +.github/labeler.yml +.github/PULL_REQUEST_TEMPLATE.md +.github/release.yml +.github/actions/certify-openshift-image/action.yml +.github/config/config-oss-* +.github/config/config-plus-* +.github/data/matrix-regression.json +.github/ISSUE_TEMPLATE/* +.github/scripts/create-release-tarballs.sh +.github/scripts/docker-updater.sh +.github/scripts/release-notes-update.sh +.github/scripts/release-version-update.sh +.github/workflows/build-base-images.yml +.github/workflows/build-ot-dependency.yml +.github/workflows/build-test-image.yml +.github/workflows/build-ubi-dependency.yml +.github/workflows/build-single-image.yml +.github/workflows/cache-update.yml +.github/workflows/certify-ubi-image.yml +.github/workflows/cherry-pick.yml +.github/workflows/codeql-analysis.yml +.github/workflows/create-release-branch.yml +.github/workflows/dependabot-auto-merge.yml +.github/workflows/dependabot-hugo.yml +.github/workflows/dependency-review.yml +.github/workflows/dockerhub-description.yml +.github/workflows/docs-build-push.yml +.github/workflows/f5-cla.yml +.github/workflows/fossa.yml +.github/workflows/image-promotion.yml +.github/workflows/issues.yaml +.github/workflows/labeler.yml +.github/workflows/lint-format.yml +.github/workflows/mend.yml +.github/workflows/notifications.yml +.github/workflows/oss-release.yml +.github/workflows/patch-image.yml +.github/workflows/plus-release.yml +.github/workflows/publish-helm.yml +.github/workflows/regression.yml +.github/workflows/release-pr.yml +.github/workflows/release.yml +.github/workflows/retag-images.yml +.github/workflows/scorecards.yml +.github/workflows/single-image-regression.yml +.github/workflows/stale.yml +.github/workflows/update-docker-images.yml +.github/workflows/update-docker-sha.yml +.github/workflows/update-kubernetes-version.yml +.github/workflows/update-release-draft.yml +.github/workflows/updates-notification.yml +.github/workflows/version-bump.yml diff --git a/.github/scripts/release-notes-update.sh b/.github/scripts/release-notes-update.sh new file mode 100755 index 0000000000..2ae80ebb00 --- /dev/null +++ b/.github/scripts/release-notes-update.sh @@ -0,0 +1,55 @@ +#!/usr/bin/env bash + +set -o pipefail + +ROOTDIR=$(git rev-parse --show-toplevel || echo ".") +TMPDIR=/tmp +DEBUG=${DEBUG:-"false"} + +DOCS_TO_UPDATE_FOLDER=${ROOTDIR}/site/content + + usage() { + echo "Usage: $0 " + exit 1 + } + +ic_version=$1 +helm_chart_version=$2 +k8s_versions=$3 +release_date=$4 + +if [ -z "${ic_version}" ]; then + usage +fi + +if [ -z "${helm_chart_version}" ]; then + usage +fi + +if [ -z "${k8s_versions}" ]; then + usage +fi + +if [ -z "${release_date}" ]; then + usage +fi + +# update releases docs +file_path=${DOCS_TO_UPDATE_FOLDER}/releases.md +if [ "${DEBUG}" != "false" ]; then + echo "Processing ${file_path}" +fi +file_name=$(basename "${file_path}") +mv "${file_path}" "${TMPDIR}/${file_name}" +sed -e "8r ${ROOTDIR}/hack/changelog-template.txt" "${TMPDIR}/${file_name}" | sed \ + -e "s/%%TITLE%%/## $ic_version/g" \ + -e "s/%%IC_VERSION%%/$ic_version/g" \ + -e "s/%%HELM_CHART_VERSION%%/$helm_chart_version/g" \ + -e "s/%%K8S_VERSIONS%%/$k8s_versions.\n/g" \ + -e "s/%%RELEASE_DATE%%/$release_date/g" \ + > ${file_path} +if [ $? -ne 0 ]; then + echo "ERROR: failed processing ${file_path}" + mv "${TMPDIR}/${file_name}" "${file_path}" + exit 2 +fi diff --git a/.github/scripts/release-version-update.sh b/.github/scripts/release-version-update.sh new file mode 100755 index 0000000000..92d14bad69 --- /dev/null +++ b/.github/scripts/release-version-update.sh @@ -0,0 +1,142 @@ +#!/usr/bin/env bash + +set -o pipefail + +ROOTDIR=$(git rev-parse --show-toplevel || echo ".") +TMPDIR=/tmp +HELM_CHART_PATH="${ROOTDIR}/charts/nginx-ingress" +DEPLOYMENT_PATH="${ROOTDIR}/deployments" +EXAMPLES_PATH="${ROOTDIR}/examples" +DEBUG=${DEBUG:-"false"} + +DOCS_TO_UPDATE_FOLDER=${ROOTDIR}/site/content +FILES_TO_UPDATE_IC_VERSION=( + "${ROOTDIR}/.github/data/version.txt" + "${ROOTDIR}/README.md" + "${DEPLOYMENT_PATH}/daemon-set/nginx-ingress.yaml" + "${DEPLOYMENT_PATH}/daemon-set/nginx-plus-ingress.yaml" + "${DEPLOYMENT_PATH}/deployment/nginx-ingress.yaml" + "${DEPLOYMENT_PATH}/deployment/nginx-plus-ingress.yaml" + "${HELM_CHART_PATH}/Chart.yaml" + "${HELM_CHART_PATH}/values-icp.yaml" + "${HELM_CHART_PATH}/values-nsm.yaml" + "${HELM_CHART_PATH}/values-plus.yaml" + "${HELM_CHART_PATH}/values.yaml" +) +FILE_TO_UPDATE_HELM_CHART_VERSION=( + "${ROOTDIR}/.github/data/version.txt" + "${HELM_CHART_PATH}/Chart.yaml" +) + + usage() { + echo "Usage: $0 " + exit 1 + } + +current_ic_version=$1 +current_helm_chart_version=$2 +current_operator_version=$3 +new_ic_version=$4 +new_helm_chart_version=$5 +new_operator_version=$6 + +if [ -z "${current_ic_version}" ]; then + usage +fi + +if [ -z "${current_helm_chart_version}" ]; then + usage +fi + +if [ -z "${current_operator_version}" ]; then + usage +fi + +if [ -z "${new_ic_version}" ]; then + usage +fi + +if [ -z "${new_helm_chart_version}" ]; then + usage +fi + +if [ -z "${new_operator_version}" ]; then + usage +fi + + +escaped_current_ic_version=$(printf '%s' "$current_ic_version" | sed -e 's/\./\\./g'); +escaped_current_helm_chart_version=$(printf '%s' "$current_helm_chart_version" | sed -e 's/\./\\./g'); +escaped_current_operator_version=$(printf '%s' "$current_operator_version" | sed -e 's/\./\\./g'); + +echo "Updating versions: " +echo "ic_version: ${current_ic_version} -> ${new_ic_version}" +echo "helm_chart_version: ${current_helm_chart_version} -> ${new_helm_chart_version}" +echo "operator_version: ${current_operator_version} -> ${new_operator_version}" + +regex_ic="s#$escaped_current_ic_version#$new_ic_version#g" +regex_helm="s#$escaped_current_helm_chart_version#$new_helm_chart_version#g" +regex_operator="s#$escaped_current_operator_version#$new_operator_version#g" + +mv "${HELM_CHART_PATH}/values.schema.json" "${TMPDIR}/" +jq --arg version "${new_ic_version}" \ + '.properties.controller.properties.image.properties.tag.default = $version | .properties.controller.properties.image.properties.tag.examples[0] = $version | .properties.controller.examples[0].image.tag = $version | .properties.controller.properties.image.examples[0].tag = $version | .examples[0].controller.image.tag = $version' \ + ${TMPDIR}/values.schema.json \ + > "${HELM_CHART_PATH}/values.schema.json" +rc=$? +if [ $rc -ne 0 ]; then + echo "ERROR: failed updating ic_version in values.schema.json" + mv "${TMPDIR}/values.schema.json" "${HELM_CHART_PATH}/values.schema.json" + exit 2 +fi + +# update helm chart & deployment files with IC version +for i in "${FILES_TO_UPDATE_IC_VERSION[@]}"; do + if [ "${DEBUG}" != "false" ]; then + echo "Processing ${i}" + fi + file_name=$(basename "${i}") + mv "${i}" "${TMPDIR}/${file_name}" + cat "${TMPDIR}/${file_name}" | sed -e "$regex_ic" > "${i}" + if [ $? -ne 0 ]; then + echo "ERROR: failed processing ${i}" + mv "${TMPDIR}/${file_name}" "${i}" + exit 2 + fi +done + +# update helm chart files with helm chart version +for i in "${FILE_TO_UPDATE_HELM_CHART_VERSION[@]}"; do + if [ "${DEBUG}" != "false" ]; then + echo "Processing ${i}" + fi + file_name=$(basename "${i}") + mv "${i}" "${TMPDIR}/${file_name}" + cat "${TMPDIR}/${file_name}" | sed -e "$regex_helm" > "${i}" + if [ $? -ne 0 ]; then + echo "ERROR: failed processing ${i}" + mv "${TMPDIR}/${file_name}" "${i}" + exit 2 + fi +done + +# update docs with new versions +echo -n "${new_ic_version}" > ./site/layouts/shortcodes/nic-version.html +echo -n "${new_helm_chart_version}" > ./site/layouts/shortcodes/nic-helm-version.html +echo -n "${new_operator_version}" > ./site/layouts/shortcodes/nic-operator-version.html + +# update examples with new versions +example_files=$(find "${EXAMPLES_PATH}" -type f -name "*.md") +for i in ${example_files}; do + if [ "${DEBUG}" != "false" ]; then + echo "Processing ${i}" + fi + file_name=$(basename "${i}") + mv "${i}" "${TMPDIR}/${file_name}" + cat "${TMPDIR}/${file_name}" | sed -e "$regex_ic" | sed -e "$regex_helm" | sed -e "$regex_operator" > "${i}" + if [ $? -ne 0 ]; then + echo "ERROR: failed processing ${i}" + mv "${TMPDIR}/${file_name}" "${i}" + exit 2 + fi +done diff --git a/.github/scripts/variables.sh b/.github/scripts/variables.sh new file mode 100755 index 0000000000..e12953942c --- /dev/null +++ b/.github/scripts/variables.sh @@ -0,0 +1,73 @@ +#!/usr/bin/env bash + +if [ "$1" = "" ]; then + echo "ERROR: paramater needed" + exit 2 +fi + +INPUT=$1 +ROOTDIR=$(git rev-parse --show-toplevel || echo ".") +if [ "$PWD" != "$ROOTDIR" ]; then + # shellcheck disable=SC2164 + cd "$ROOTDIR"; +fi + +get_docker_md5() { + docker_md5=$(find build .github/data/version.txt -type f ! -name "*.md" -exec md5sum {} + | LC_ALL=C sort | md5sum | awk '{ print $1 }') + echo "${docker_md5:0:8}" +} + +get_go_code_md5() { + find . -type f \( -name "*.go" -o -name go.mod -o -name go.sum -o -name "*.tmpl" -o -name "version.txt" -o -name "*.js" \) -not -path "./site*" -exec md5sum {} + | LC_ALL=C sort | md5sum | awk '{ print $1 }' +} + +get_tests_md5() { + find tests perf-tests .github/data/version.txt -type f -exec md5sum {} + | LC_ALL=C sort | md5sum | awk '{ print $1 }' +} + +get_chart_md5() { + find charts .github/data/version.txt -type f -exec md5sum {} + | LC_ALL=C sort | md5sum | awk '{ print $1 }' +} + +get_actions_md5() { + exclude_list="$(dirname $0)/exclude_ci_files.txt" + find_command="find .github -type f -not -path '${exclude_list}'" + while IFS= read -r file + do + find_command+=" -not -path '$file'" + done < "$exclude_list" + + find_command+=" -exec md5sum {} +" + eval "$find_command" | LC_ALL=C sort | md5sum | awk '{ print $1 }' +} + +get_build_tag() { + echo "$(get_docker_md5) $(get_go_code_md5)" | md5sum | awk '{ print $1 }' +} + +get_stable_tag() { + echo "$(get_build_tag) $(get_tests_md5) $(get_chart_md5) $(get_actions_md5)" | md5sum | awk '{ print $1 }' +} + +case $INPUT in + docker_md5) + echo "docker_md5=$(get_docker_md5)" + ;; + + go_code_md5) + echo "go_code_md5=$(get_go_code_md5)" + ;; + + build_tag) + echo "build_tag=t-$(get_build_tag)" + ;; + + stable_tag) + echo "stable_tag=s-$(get_stable_tag)" + ;; + + *) + echo "ERROR: option not found" + exit 2 + ;; +esac diff --git a/.github/workflows/build-base-images.yml b/.github/workflows/build-base-images.yml new file mode 100644 index 0000000000..36c1b472de --- /dev/null +++ b/.github/workflows/build-base-images.yml @@ -0,0 +1,251 @@ +name: Build Base Images + +on: + workflow_dispatch: + workflow_call: + schedule: + - cron: "30 4 * * 1-5" # run Mon-Fri at 04:30 UTC + +defaults: + run: + shell: bash + +concurrency: + group: ${{ github.ref_name }}-base-image + cancel-in-progress: false + +permissions: + contents: read + +jobs: + checks: + name: Checks and variables + runs-on: ubuntu-24.04 + outputs: + docker_md5: ${{ steps.vars.outputs.docker_md5 }} + ic_version: ${{ steps.vars.outputs.ic_version }} + image_matrix_oss: ${{ steps.vars.outputs.image_matrix_oss }} + image_matrix_plus: ${{ steps.vars.outputs.image_matrix_plus }} + image_matrix_nap: ${{ steps.vars.outputs.image_matrix_nap }} + steps: + - name: Checkout Repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: Output Variables + id: vars + run: | + ./.github/scripts/variables.sh docker_md5 >> $GITHUB_OUTPUT + source .github/data/version.txt + echo "ic_version=${IC_VERSION}" >> $GITHUB_OUTPUT + echo "image_matrix_oss=$(cat .github/data/matrix-images-oss.json | jq -c)" >> $GITHUB_OUTPUT + echo "image_matrix_plus=$(cat .github/data/matrix-images-plus.json | jq -c)" >> $GITHUB_OUTPUT + echo "image_matrix_nap=$(cat .github/data/matrix-images-nap.json | jq -c)" >> $GITHUB_OUTPUT + cat $GITHUB_OUTPUT + + build-oss: + name: Build OSS base images + runs-on: ubuntu-24.04 + needs: checks + permissions: + contents: read + pull-requests: write # for scout report + id-token: write + strategy: + fail-fast: false + matrix: ${{ fromJSON( needs.checks.outputs.image_matrix_oss ) }} + steps: + - name: Checkout Repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: Docker Buildx + uses: docker/setup-buildx-action@6524bf65af31da8d45b59e8c27de4bd072b392f5 # v3.8.0 + + - name: Setup QEMU + uses: docker/setup-qemu-action@49b3bc8e6bdd4a60e6116a5414239cba5943d3cf # v3.2.0 + with: + platforms: arm,arm64,ppc64le,s390x + + - name: Authenticate to Google Cloud + id: auth + uses: google-github-actions/auth@6fc4af4b145ae7821d527454aa9bd537d1f2dc5f # v2.1.7 + with: + token_format: access_token + workload_identity_provider: ${{ secrets.GCR_WORKLOAD_IDENTITY }} + service_account: ${{ secrets.GCR_SERVICE_ACCOUNT }} + + - name: Login to GCR + uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 + with: + registry: gcr.io + username: oauth2accesstoken + password: ${{ steps.auth.outputs.access_token }} + + - name: Docker meta + id: meta + uses: docker/metadata-action@369eb591f429131d6889c46b94e711f089e6ca96 # v5.6.1 + with: + images: | + name=gcr.io/f5-gcs-7899-ptg-ingrss-ctlr/dev/nginx-ic-base/oss + flavor: | + suffix=-${{ matrix.image }},onlatest=false + tags: | + type=raw,value=${{ needs.checks.outputs.docker_md5 }},enable=${{ needs.checks.outputs.docker_md5 != '' }} + + - name: Build Base Container + uses: docker/build-push-action@48aba3b46d1b1fec4febb7c5d0c644b249a11355 # v6.10.0 + with: + file: build/Dockerfile + context: "." + cache-from: type=gha,scope=${{ matrix.image }} + cache-to: type=gha,scope=${{ matrix.image }},mode=max + target: common + tags: ${{ steps.meta.outputs.tags }} + platforms: ${{ matrix.platforms }} + pull: true + push: true + build-args: | + BUILD_OS=${{ matrix.image }} + IC_VERSION=${{ needs.checks.outputs.ic_version }} + + build-plus: + name: Build Plus base images + runs-on: ubuntu-24.04 + needs: checks + permissions: + contents: read + id-token: write + pull-requests: write # for scout report + strategy: + fail-fast: false + matrix: ${{ fromJSON( needs.checks.outputs.image_matrix_plus ) }} + steps: + - name: Checkout Repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: Docker Buildx + uses: docker/setup-buildx-action@6524bf65af31da8d45b59e8c27de4bd072b392f5 # v3.8.0 + + - name: Setup QEMU + uses: docker/setup-qemu-action@49b3bc8e6bdd4a60e6116a5414239cba5943d3cf # v3.2.0 + with: + platforms: arm64,s390x + + - name: Authenticate to Google Cloud + id: auth + uses: google-github-actions/auth@6fc4af4b145ae7821d527454aa9bd537d1f2dc5f # v2.1.7 + with: + token_format: access_token + workload_identity_provider: ${{ secrets.GCR_WORKLOAD_IDENTITY }} + service_account: ${{ secrets.GCR_SERVICE_ACCOUNT }} + + - name: Login to GCR + uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 + with: + registry: gcr.io + username: oauth2accesstoken + password: ${{ steps.auth.outputs.access_token }} + + - name: Docker meta + id: meta + uses: docker/metadata-action@369eb591f429131d6889c46b94e711f089e6ca96 # v5.6.1 + with: + images: | + name=gcr.io/f5-gcs-7899-ptg-ingrss-ctlr/dev/nginx-ic-base/plus + flavor: | + suffix=-${{ matrix.image }},onlatest=false + tags: | + type=raw,value=${{ needs.checks.outputs.docker_md5 }},enable=${{ needs.checks.outputs.docker_md5 != '' }} + + - name: Build Base Container + uses: docker/build-push-action@48aba3b46d1b1fec4febb7c5d0c644b249a11355 # v6.10.0 + with: + file: build/Dockerfile + context: "." + cache-from: type=gha,scope=${{ matrix.image }} + cache-to: type=gha,scope=${{ matrix.image }},mode=max + target: common + tags: ${{ steps.meta.outputs.tags }} + platforms: ${{ matrix.platforms }} + pull: true + push: true + build-args: | + BUILD_OS=${{ matrix.image }} + IC_VERSION=${{ needs.checks.outputs.ic_version }} + secrets: | + "nginx-repo.crt=${{ secrets.NGINX_CRT }}" + "nginx-repo.key=${{ secrets.NGINX_KEY }}" + + build-plus-nap: + name: Build Plus NAP base images + runs-on: ubuntu-24.04 + needs: checks + permissions: + contents: read + id-token: write + pull-requests: write # for scout report + strategy: + fail-fast: false + matrix: ${{ fromJSON( needs.checks.outputs.image_matrix_nap ) }} + steps: + - name: Checkout Repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: Docker Buildx + uses: docker/setup-buildx-action@6524bf65af31da8d45b59e8c27de4bd072b392f5 # v3.8.0 + + - name: Authenticate to Google Cloud + id: auth + uses: google-github-actions/auth@6fc4af4b145ae7821d527454aa9bd537d1f2dc5f # v2.1.7 + with: + token_format: access_token + workload_identity_provider: ${{ secrets.GCR_WORKLOAD_IDENTITY }} + service_account: ${{ secrets.GCR_SERVICE_ACCOUNT }} + + - name: Login to GCR + uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 + with: + registry: gcr.io + username: oauth2accesstoken + password: ${{ steps.auth.outputs.access_token }} + + - name: NAP modules + id: nap_modules + run: | + [[ "${{ matrix.nap_modules }}" == "waf,dos" ]] && modules="waf-dos" || modules="${{ matrix.nap_modules }}" + echo "modules=${modules}" >> $GITHUB_OUTPUT + [[ "${{ matrix.nap_modules }}" =~ waf ]] && agent="true" || agent="false" + echo "agent=${agent}" >> $GITHUB_OUTPUT + if: ${{ matrix.nap_modules != '' }} + + - name: Docker meta + id: meta + uses: docker/metadata-action@369eb591f429131d6889c46b94e711f089e6ca96 # v5.6.1 + with: + images: | + name=gcr.io/f5-gcs-7899-ptg-ingrss-ctlr/dev/nginx-ic-base/plus + flavor: | + suffix=-${{ matrix.image }}-${{ steps.nap_modules.outputs.modules }},onlatest=false + tags: | + type=raw,value=${{ needs.checks.outputs.docker_md5 }},enable=${{ needs.checks.outputs.docker_md5 != '' }} + + - name: Build Base Container + uses: docker/build-push-action@48aba3b46d1b1fec4febb7c5d0c644b249a11355 # v6.10.0 + with: + file: build/Dockerfile + context: "." + cache-from: type=gha,scope=${{ matrix.image }}-${{ steps.nap_modules.outputs.modules }} + cache-to: type=gha,scope=${{ matrix.image }}-${{ steps.nap_modules.outputs.modules }},mode=max + target: common + tags: ${{ steps.meta.outputs.tags }} + platforms: ${{ matrix.platforms }} + pull: true + push: true + build-args: | + BUILD_OS=${{ matrix.image }} + IC_VERSION=${{ needs.checks.outputs.ic_version }} + NAP_MODULES=${{ matrix.nap_modules }} + ${{ contains(matrix.nap_modules,'waf') && format('NGINX_AGENT={0}', steps.nap_modules.outputs.agent) || '' }} + secrets: | + "nginx-repo.crt=${{ secrets.NGINX_AP_CRT }}" + "nginx-repo.key=${{ secrets.NGINX_AP_KEY }}" + ${{ contains(matrix.image, 'ubi') && format('"rhel_license={0}"', secrets.RHEL_LICENSE) || '' }} diff --git a/.github/workflows/build-oss.yml b/.github/workflows/build-oss.yml index fb8b86fdc5..9ba58cf79c 100644 --- a/.github/workflows/build-oss.yml +++ b/.github/workflows/build-oss.yml @@ -12,7 +12,23 @@ on: tag: required: false type: string - sha_long: + go-md5: + required: true + type: string + base-image-md5: + required: true + type: string + branch: + required: true + type: string + authenticated: + required: true + type: boolean + full-build: + description: Always build base image + type: boolean + default: false + ic-version: required: false type: string @@ -20,154 +36,178 @@ defaults: run: shell: bash +permissions: + contents: read + jobs: build: - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 + permissions: + contents: read # for docker/build-push-action to read repo content + id-token: write # for OIDC login to GCR + packages: write # for docker/build-push-action to push to GHCR + pull-requests: write # for scout report outputs: version: ${{ steps.meta.outputs.version }} image_digest: ${{ steps.build-push.outputs.digest }} steps: - name: Checkout Repository - uses: actions/checkout@v3 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: - ref: ${{ inputs.tag != '' && format('refs/tags/v{0}', inputs.tag) || github.ref }} + ref: ${{ inputs.branch }} fetch-depth: 0 - - name: Fetch Cached Artifacts - uses: actions/cache@v3 + - name: Authenticate to Google Cloud + id: auth + uses: google-github-actions/auth@6fc4af4b145ae7821d527454aa9bd537d1f2dc5f # v2.1.7 with: - path: ${{ github.workspace }}/dist - key: nginx-ingress-${{ github.run_id }}-${{ github.run_number }}-multi + token_format: access_token + workload_identity_provider: ${{ secrets.GCR_WORKLOAD_IDENTITY }} + service_account: ${{ secrets.GCR_SERVICE_ACCOUNT }} + if: ${{ inputs.authenticated }} - - name: Setup QEMU - uses: docker/setup-qemu-action@v2 + - name: Login to GCR + uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 with: - platforms: arm,arm64,ppc64le,s390x - if: github.event_name != 'pull_request' - - - name: Docker Buildx - uses: docker/setup-buildx-action@v2 - - name: DockerHub Login - uses: docker/login-action@v2 - with: - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_PASSWORD }} - if: github.event_name != 'pull_request' - - - name: Login to GitHub Container Registry - uses: docker/login-action@v2 - with: - registry: ghcr.io - username: ${{ github.repository_owner }} - password: ${{ secrets.GITHUB_TOKEN }} - if: github.event_name != 'pull_request' - - - name: Login to Public ECR - uses: docker/login-action@v2 - with: - registry: public.ecr.aws - username: ${{ secrets.AWS_ACCESS_KEY_ID }} - password: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - if: github.event_name != 'pull_request' - - - name: Login to Quay.io - uses: docker/login-action@v2 - with: - registry: quay.io - username: ${{ secrets.QUAY_USERNAME }} - password: ${{ secrets.QUAY_ROBOT_TOKEN }} - if: github.event_name != 'pull_request' - - - name: Get short tag - id: tag - run: | - version="${{ inputs.tag }}" - short="${version%.*}" - echo "short=$short" >> $GITHUB_OUTPUT - if: ${{ inputs.tag != '' }} + registry: gcr.io + username: oauth2accesstoken + password: ${{ steps.auth.outputs.access_token }} + if: ${{ inputs.authenticated }} - name: Docker meta id: meta - uses: docker/metadata-action@v4 + uses: docker/metadata-action@369eb591f429131d6889c46b94e711f089e6ca96 # v5.6.1 with: + context: workflow images: | - name=nginx/nginx-ingress - name=ghcr.io/nginxinc/kubernetes-ingress - name=public.ecr.aws/nginx/nginx-ingress - name=quay.io/nginx/nginx-ingress + name=gcr.io/f5-gcs-7899-ptg-ingrss-ctlr/dev/nginx-ic/nginx-ingress flavor: | - latest=${{ (inputs.tag != '' && 'true') || 'auto' }} - suffix=${{ contains(inputs.image, 'ubi') && '-ubi' || '' }}${{ contains(inputs.image, 'alpine') && '-alpine' || '' }},onlatest=true + suffix=${{ contains(inputs.image, 'ubi') && '-ubi' || '' }}${{ contains(inputs.image, 'alpine') && '-alpine' || '' }} tags: | - type=edge - type=ref,event=pr - type=schedule - type=semver,pattern={{version}} - type=semver,pattern={{major}}.{{minor}} - type=raw,value=${{ inputs.tag }},enable=${{ inputs.tag != '' }} - type=raw,value=${{ steps.tag.outputs.short }},enable=${{ inputs.tag != '' }} + type=raw,value=${{ inputs.tag }} labels: | org.opencontainers.image.description=NGINX Ingress Controller for Kubernetes - org.opencontainers.image.documentation=https://docs.nginx.com/nginx-ingress-controller - org.opencontainers.image.vendor=NGINX Inc - org.opencontainers.image.revision=${{ inputs.sha_long != '' && inputs.sha_long || github.sha }} io.artifacthub.package.readme-url=https://raw.githubusercontent.com/nginxinc/kubernetes-ingress/main/README.md io.artifacthub.package.logo-url=https://docs.nginx.com/nginx-ingress-controller/images/icons/NGINX-Ingress-Controller-product-icon.svg io.artifacthub.package.maintainers=[{"name":"NGINX Inc","email":"kubernetes@nginx.com"}] io.artifacthub.package.license=Apache-2.0 io.artifacthub.package.keywords=kubernetes,ingress,nginx,controller + env: + DOCKER_METADATA_ANNOTATIONS_LEVELS: manifest,index + + - name: Set base name variable + id: base_name + run: | + base_image="gcr.io/f5-gcs-7899-ptg-ingrss-ctlr/dev/nginx-ic-base/oss:${{ inputs.base-image-md5 }}-${{ inputs.image }}" + echo "image=${base_image}" >> $GITHUB_OUTPUT + + - name: Check if images exist + id: images_exist + run: | + if docker manifest inspect ${{ steps.base_name.outputs.image }}; then + echo "base_exists=true" >> $GITHUB_OUTPUT + fi + if docker manifest inspect ${{ steps.meta.outputs.tags }}; then + echo "target_exists=true" >> $GITHUB_OUTPUT + fi + if: ${{ inputs.authenticated && ! inputs.full-build }} + + - name: Setup QEMU + uses: docker/setup-qemu-action@49b3bc8e6bdd4a60e6116a5414239cba5943d3cf # v3.2.0 + with: + platforms: arm,arm64,ppc64le,s390x + if: ${{ steps.images_exist.outputs.base_exists != 'true' || steps.images_exist.outputs.target_exists != 'true' }} + + - name: Docker Buildx + uses: docker/setup-buildx-action@6524bf65af31da8d45b59e8c27de4bd072b392f5 # v3.8.0 + if: ${{ steps.images_exist.outputs.base_exists != 'true' || steps.images_exist.outputs.target_exists != 'true' }} + + - name: Build Base Container + uses: docker/build-push-action@48aba3b46d1b1fec4febb7c5d0c644b249a11355 # v6.10.0 + with: + file: build/Dockerfile + context: "." + cache-to: type=gha,scope=${{ inputs.image }},mode=max + target: common + tags: ${{ steps.base_name.outputs.image }} + platforms: ${{ inputs.platforms }} + pull: true + push: true + no-cache: true + build-args: | + BUILD_OS=${{ inputs.image }} + IC_VERSION=${{ inputs.ic-version && inputs.ic-version || steps.meta.outputs.version }} + if: ${{ inputs.authenticated && steps.images_exist.outputs.base_exists != 'true' }} + + - name: Debug values + run: | + echo "authenticated: ${{ inputs.authenticated }}" + echo "base_exists: ${{ steps.images_exist.outputs.base_exists }}" + echo "target_exists: ${{ steps.images_exist.outputs.target_exists }}" + echo "full-build: ${{ inputs.full-build }}" + + - name: Fetch Cached Artifacts + uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 + with: + path: ${{ github.workspace }}/dist + key: nginx-ingress-${{ inputs.go-md5 }} + fail-on-cache-miss: true + if: ${{ steps.images_exist.outputs.base_exists != 'true' || steps.images_exist.outputs.target_exists != 'true' }} - name: Build Docker image - uses: docker/build-push-action@v3 + uses: docker/build-push-action@48aba3b46d1b1fec4febb7c5d0c644b249a11355 # v6.10.0 id: build-push with: file: build/Dockerfile - context: '.' + context: "." cache-from: type=gha,scope=${{ inputs.image }} cache-to: type=gha,scope=${{ inputs.image }},mode=max - target: goreleaser + target: goreleaser${{ inputs.authenticated && '-prebuilt' || '' }} tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} - platforms: ${{ github.event_name != 'pull_request' && inputs.platforms || '' }} - load: ${{ github.event_name == 'pull_request' }} - push: ${{ github.event_name != 'pull_request' }} + annotations: ${{ steps.meta.outputs.annotations }} + platforms: ${{ inputs.platforms }} + load: false + push: ${{ inputs.authenticated }} pull: true - no-cache: ${{ github.event_name != 'pull_request' }} + sbom: ${{ inputs.authenticated }} + provenance: false build-args: | BUILD_OS=${{ inputs.image }} - IC_VERSION=${{ github.event_name == 'pull_request' && 'CI' || steps.meta.outputs.version }} + ${{ inputs.authenticated && format('PREBUILT_BASE_IMG={0}', steps.base_name.outputs.image) }} + IC_VERSION=${{ inputs.ic-version && inputs.ic-version || steps.meta.outputs.version }} + if: ${{ steps.images_exist.outputs.base_exists != 'true' || steps.images_exist.outputs.target_exists != 'true' }} - - name: Run Trivy vulnerability scanner - uses: aquasecurity/trivy-action@0.7.1 - continue-on-error: true - with: - image-ref: nginx/nginx-ingress:${{ steps.meta.outputs.version }} - format: 'sarif' - output: 'trivy-results-${{ inputs.image }}.sarif' - ignore-unfixed: 'true' - - - name: Upload Trivy scan results to GitHub Security tab - uses: github/codeql-action/upload-sarif@v2 - continue-on-error: true + - name: Make directory for security scan results + run: | + mkdir -p "${{ inputs.image }}-results/" + if: ${{ inputs.authenticated && steps.build-push.conclusion == 'success' }} + + # - name: Run Trivy vulnerability scanner + # uses: aquasecurity/trivy-action@6e7b7d1fd3e4fef0c5fa8cce1229c54b2c9bd0d8 # 0.24.0 + # with: + # image-ref: ${{ steps.meta.outputs.tags }} + # format: "sarif" + # output: "${{ inputs.image }}-results/trivy.sarif" + # ignore-unfixed: "true" + # if: ${{ inputs.authenticated && steps.build-push.conclusion == 'success' }} + + - name: DockerHub Login for Docker Scout + uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 with: - sarif_file: 'trivy-results-${{ inputs.image }}.sarif' + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + if: ${{ inputs.authenticated && steps.build-push.conclusion == 'success' }} - - name: Upload Scan Results - uses: actions/upload-artifact@v3 - continue-on-error: true - with: - name: 'trivy-results-${{ inputs.image }}.sarif' - path: 'trivy-results-${{ inputs.image }}.sarif' - if: always() - - send-notification: - name: Send Notification - needs: build - uses: ./.github/workflows/updates-notification.yml + - name: Run Docker Scout vulnerability scanner + id: docker-scout + uses: docker/scout-action@b23590dc1e4d09febc00cfcbc51e9e8c0f7ee9f3 # v1.16.1 with: - sha_long: ${{ inputs.sha_long }} - tag: ${{ inputs.tag }} - version: ${{ needs.build.outputs.version }} - image_digest: ${{ needs.build.outputs.image_digest }} - secrets: inherit - if: ${{ inputs.tag != '' }} + command: cves + image: ${{ steps.meta.outputs.tags }} + ignore-base: true + sarif-file: "${{ inputs.image }}-results/scout.sarif" + write-comment: false + github-token: ${{ secrets.GITHUB_TOKEN }} # to be able to write the comment + summary: true + if: ${{ inputs.authenticated && steps.build-push.conclusion == 'success' }} diff --git a/.github/workflows/build-ot-dependency.yml b/.github/workflows/build-ot-dependency.yml new file mode 100644 index 0000000000..ddefeec915 --- /dev/null +++ b/.github/workflows/build-ot-dependency.yml @@ -0,0 +1,100 @@ +name: Build OpenTracing Dependency + +on: + schedule: + - cron: "30 4 * * 1" # run Mon at 04:30 UTC + workflow_dispatch: + inputs: + nginx_version: + type: string + description: "NGINX Version to build OT for" + required: false + +env: + PLATFORMS: "linux/arm,linux/amd64,linux/arm64,linux/ppc64le,linux/s390x" + +concurrency: + group: ${{ github.ref_name }}-ot-build + cancel-in-progress: true + +permissions: + contents: read + +jobs: + build-docker: + name: Build Docker Image + runs-on: ubuntu-24.04 + permissions: + packages: write + contents: read + strategy: + fail-fast: false + matrix: + os: [debian, alpine] + steps: + - name: Checkout Repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + repository: opentracing-contrib/nginx-opentracing + ref: master + + - name: Variables + id: var + run: | + if [ -n "${{ inputs.nginx_version }}" ]; then + nginx_version="${{ inputs.nginx_version }}" + else + nginx_version=$(grep -m1 'FROM nginx:' > $GITHUB_OUTPUT + + - name: Setup QEMU + uses: docker/setup-qemu-action@49b3bc8e6bdd4a60e6116a5414239cba5943d3cf # v3.2.0 + with: + platforms: arm,arm64,ppc64le,s390x + + - name: Docker Buildx + uses: docker/setup-buildx-action@6524bf65af31da8d45b59e8c27de4bd072b392f5 # v3.8.0 + with: + buildkitd-flags: --debug + + - name: Login to GitHub Container Registry + uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + if: github.event_name != 'pull_request' + + - name: Docker meta + id: meta + uses: docker/metadata-action@369eb591f429131d6889c46b94e711f089e6ca96 # v5.6.1 + with: + images: | + name=ghcr.io/nginxinc/dependencies/nginx-ot,enable=true + flavor: suffix=${{ matrix.os != 'debian' && format('-{0}', matrix.os) || '' }},onlatest=true + tags: | + type=raw,value=nginx-${{ steps.var.outputs.nginx_version }},enable=true + env: + DOCKER_METADATA_ANNOTATIONS_LEVELS: manifest,index + + - name: Build and push + uses: docker/build-push-action@48aba3b46d1b1fec4febb7c5d0c644b249a11355 # v6.10.0 + with: + file: ./Dockerfile + context: "." + pull: true + push: true + platforms: "linux/arm,linux/amd64,linux/arm64,linux/ppc64le,linux/s390x" + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + annotations: ${{ steps.meta.outputs.annotations }} + cache-from: type=gha,scope=${{ matrix.os }} + cache-to: type=gha,scope=${{ matrix.os }},mode=max + target: final + sbom: false + provenance: mode=max + build-args: | + BUILD_OS=${{ matrix.os }} + NGINX_VERSION=${{ steps.var.outputs.nginx_version }} diff --git a/.github/workflows/build-plus.yml b/.github/workflows/build-plus.yml index 3176d0240d..17f3825b88 100644 --- a/.github/workflows/build-plus.yml +++ b/.github/workflows/build-plus.yml @@ -9,10 +9,32 @@ on: image: required: true type: string + tag: + required: false + type: string + go-md5: + required: true + type: string + base-image-md5: + required: false + type: string + branch: + required: false + type: string + nap-modules: + required: false + type: string target: required: true type: string - nap_modules: + authenticated: + required: true + type: boolean + full-build: + description: Always build base image + type: boolean + default: false + ic-version: required: false type: string @@ -20,142 +42,193 @@ defaults: run: shell: bash +permissions: + contents: read + jobs: build: - runs-on: ubuntu-22.04 - steps: + permissions: + contents: read # for docker/build-push-action to read repo content + id-token: write # for OIDC login to AWS + pull-requests: write # for scout report + runs-on: ubuntu-24.04 + steps: - name: Checkout Repository - uses: actions/checkout@v3 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: + ref: ${{ inputs.branch }} fetch-depth: 0 - - name: Fetch Cached Artifacts - uses: actions/cache@v3 + - name: Authenticate to Google Cloud + id: auth + uses: google-github-actions/auth@6fc4af4b145ae7821d527454aa9bd537d1f2dc5f # v2.1.7 with: - path: ${{ github.workspace }}/dist - key: nginx-ingress-${{ github.run_id }}-${{ github.run_number }}-multi - - - name: Setup QEMU - uses: docker/setup-qemu-action@v2 - with: - platforms: arm64 - if: github.event_name != 'pull_request' - - - name: Docker Buildx - uses: docker/setup-buildx-action@v2 + token_format: access_token + workload_identity_provider: ${{ secrets.GCR_WORKLOAD_IDENTITY }} + service_account: ${{ secrets.GCR_SERVICE_ACCOUNT }} + if: ${{ inputs.authenticated }} - - name: GCR Login - uses: docker/login-action@v2 + - name: Login to GCR + uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 with: registry: gcr.io - username: _json_key - password: ${{ secrets.GCR_JSON_KEY }} - if: github.event_name != 'pull_request' + username: oauth2accesstoken + password: ${{ steps.auth.outputs.access_token }} + if: ${{ inputs.authenticated }} - - name: Login to ECR - uses: docker/login-action@v2 - with: - registry: 709825985650.dkr.ecr.us-east-1.amazonaws.com - username: ${{ secrets.AWS_ACCESS_KEY_ID }} - password: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - if: startsWith(github.ref, 'refs/tags/') + - name: NAP modules + id: nap_modules + run: | + [[ "${{ inputs.nap-modules }}" == "waf,dos" ]] && modules="waf-dos" || name="${{ inputs.nap-modules }}" + echo "name=${name}" >> $GITHUB_OUTPUT + [[ "${{ inputs.nap-modules }}" == "waf,dos" ]] && modules="both" || modules="${{ inputs.nap-modules }}" + echo "modules=${modules}" >> $GITHUB_OUTPUT + [[ "${{ inputs.nap-modules }}" =~ waf ]] && agent="true" || agent="false" + echo "agent=${agent}" >> $GITHUB_OUTPUT + if: ${{ inputs.nap-modules != '' }} - name: Docker meta id: meta - uses: docker/metadata-action@v4 + uses: docker/metadata-action@369eb591f429131d6889c46b94e711f089e6ca96 # v5.6.1 with: images: | - name=gcr.io/f5-gcs-7899-ptg-ingrss-ctlr/dev/nginx-ic${{ contains(inputs.nap_modules, 'dos') && '-dos' || '' }}${{ contains(inputs.nap_modules, 'waf') && '-nap' || '' }}/nginx-plus-ingress - name=gcr.io/f5-gcs-7899-ptg-ingrss-ctlr/release/nginx-ic${{ contains(inputs.nap_modules, 'dos') && '-dos' || '' }}${{ contains(inputs.nap_modules, 'waf') && '-nap' || '' }}/nginx-plus-ingress,enable=${{ startsWith(github.ref, 'refs/tags/') }} - name=709825985650.dkr.ecr.us-east-1.amazonaws.com/nginx/nginx-plus-ingress${{ contains(inputs.nap_modules, 'dos') && '-dos' || '' }}${{ contains(inputs.nap_modules, 'waf') && '-nap' || '' }},enable=${{ startsWith(github.ref, 'refs/tags/') && contains(inputs.target, 'aws') }} + name=gcr.io/f5-gcs-7899-ptg-ingrss-ctlr/dev/nginx-ic${{ contains(inputs.nap-modules, 'dos') && '-dos' || '' }}${{ contains(inputs.nap-modules, 'waf') && '-nap' || '' }}${{ contains(inputs.image, 'v5') && '-v5' || '' }}/nginx-plus-ingress flavor: | - suffix=${{ contains(inputs.image, 'ubi') && '-ubi' || '' }}${{ contains(inputs.image, 'alpine') && '-alpine' || '' }}${{ contains(inputs.target, 'aws') && '-mktpl' || '' }},onlatest=true - latest=${{ contains(inputs.target, 'aws') && 'false' || 'auto' }} + suffix=${{ contains(inputs.image, 'ubi-9') && '-ubi' || '' }}${{ contains(inputs.image, 'ubi-8') && '-ubi8' || '' }}${{ contains(inputs.image, 'alpine') && '-alpine' || '' }}${{ contains(inputs.target, 'aws') && '-mktpl' || '' }}${{ contains(inputs.image, 'fips') && '-fips' || ''}} tags: | - type=edge - type=ref,event=pr - type=schedule,pattern={{date 'YYYYMMDD'}} - type=semver,pattern={{version}} + type=raw,value=${{ inputs.tag }} labels: | org.opencontainers.image.description=NGINX Plus Ingress Controller for Kubernetes - org.opencontainers.image.documentation=https://docs.nginx.com/nginx-ingress-controller - org.opencontainers.image.vendor=NGINX Inc + env: + DOCKER_METADATA_ANNOTATIONS_LEVELS: ${{ contains(inputs.target, 'aws') && 'manifest' || 'manifest,index' }} - - name: NAP modules - id: nap_modules + - name: Set base name variable + id: base_name + run: | + base_image="gcr.io/f5-gcs-7899-ptg-ingrss-ctlr/dev/nginx-ic-base/plus:${{ inputs.base-image-md5 }}-${{ inputs.image }}${{ steps.nap_modules.outputs.name != '' && format('-{0}', steps.nap_modules.outputs.name) || '' }}${{ contains(inputs.image, 'v5') && '-v5' || '' }}" + echo "image=${base_image}" >> $GITHUB_OUTPUT + + - name: Check if images exist + id: images_exist run: | - modules="" - if [[ "${{ inputs.nap_modules }}" == "waf,dos" ]]; then - modules="both" - else - modules="${{ inputs.nap_modules }}" + if docker pull ${{ steps.base_name.outputs.image }}; then + echo "base_exists=true" >> $GITHUB_OUTPUT fi - echo "modules=${modules}" >> $GITHUB_OUTPUT - if: ${{ inputs.nap_modules != '' }} + if docker manifest inspect ${{ steps.meta.outputs.tags }}; then + echo "target_exists=true" >> $GITHUB_OUTPUT + fi + if: ${{ inputs.authenticated && ! inputs.full-build }} - - name: Build Plus Docker image - uses: docker/build-push-action@v3 + - name: Setup QEMU + uses: docker/setup-qemu-action@49b3bc8e6bdd4a60e6116a5414239cba5943d3cf # v3.2.0 + with: + platforms: arm,arm64,ppc64le,s390x + if: ${{ steps.images_exist.outputs.base_exists != 'true' || steps.images_exist.outputs.target_exists != 'true' }} + + - name: Docker Buildx + uses: docker/setup-buildx-action@6524bf65af31da8d45b59e8c27de4bd072b392f5 # v3.8.0 + if: ${{ steps.images_exist.outputs.base_exists != 'true' || steps.images_exist.outputs.target_exists != 'true' }} + + - name: Build Base Container + uses: docker/build-push-action@48aba3b46d1b1fec4febb7c5d0c644b249a11355 # v6.10.0 with: file: build/Dockerfile - context: '.' - cache-from: type=gha,scope=${{ inputs.image }}${{ contains(inputs.nap_modules, 'dos') && '-dos' || '' }}${{ contains(inputs.nap_modules, 'waf') && '-nap' || '' }} - cache-to: type=gha,scope=${{ inputs.image }}${{ contains(inputs.nap_modules, 'dos') && '-dos' || '' }}${{ contains(inputs.nap_modules, 'waf') && '-nap' || '' }},mode=max - target: ${{ inputs.target }} - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - platforms: ${{ github.event_name != 'pull_request' && inputs.platforms || '' }} - load: ${{ github.event_name == 'pull_request' }} - push: ${{ github.event_name != 'pull_request' }} + context: "." + cache-to: type=gha,scope=${{ inputs.image }}${{ steps.nap_modules.outputs.name != '' && format('-{0}', steps.nap_modules.outputs.name) || '' }},mode=max + target: common + tags: ${{ steps.base_name.outputs.image }} + platforms: ${{ inputs.platforms }} pull: true - no-cache: ${{ github.event_name != 'pull_request' }} + push: true + no-cache: true build-args: | BUILD_OS=${{ inputs.image }} - IC_VERSION=${{ startsWith(github.ref, 'refs/tags/') && steps.meta.outputs.version || 'CI' }} - ${{ inputs.nap_modules != '' && format('NAP_MODULES={0}', inputs.nap_modules) || '' }} - ${{ steps.nap_modules.outputs.modules != '' && format('NAP_MODULES_AWS={0}', steps.nap_modules.outputs.modules) || '' }} - ${{ contains(inputs.nap_modules, 'waf') && 'DEBIAN_VERSION=buster-slim' || '' }} + IC_VERSION=${{ inputs.ic-version && inputs.ic-version || steps.meta.outputs.version }} + ${{ inputs.nap-modules != '' && format('NAP_MODULES={0}', steps.nap_modules.outputs.name) || '' }} + ${{ contains(inputs.nap-modules,'waf') && format('NGINX_AGENT={0}', steps.nap_modules.outputs.agent) || '' }} secrets: | - "nginx-repo.crt=${{ inputs.nap_modules != '' && secrets.NGINX_AP_CRT || secrets.NGINX_CRT }}" - "nginx-repo.key=${{ inputs.nap_modules != '' && secrets.NGINX_AP_KEY || secrets.NGINX_KEY }}" + "nginx-repo.crt=${{ inputs.nap-modules != '' && secrets.NGINX_AP_CRT || secrets.NGINX_CRT }}" + "nginx-repo.key=${{ inputs.nap-modules != '' && secrets.NGINX_AP_KEY || secrets.NGINX_KEY }}" + ${{ inputs.nap-modules != '' && contains(inputs.image, 'ubi') && format('"rhel_license={0}"', secrets.RHEL_LICENSE) || '' }} + if: ${{ inputs.authenticated && steps.images_exist.outputs.base_exists != 'true' }} - - name: Load image for Trivy - uses: docker/build-push-action@v3 + - name: Debug values + run: | + echo "authenticated: ${{ inputs.authenticated }}" + echo "images_exist: ${{ steps.images_exist.outputs.base_exists }}" + echo "target_exists: ${{ steps.images_exist.outputs.target_exists }}" + echo "full-build: ${{ inputs.full-build }}" + + - name: Fetch Cached Artifacts + uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 + with: + path: ${{ github.workspace }}/dist + key: nginx-ingress-${{ inputs.go-md5 }} + fail-on-cache-miss: true + if: ${{ steps.images_exist.outputs.base_exists != 'true' || steps.images_exist.outputs.target_exists != 'true' }} + + - name: Build Docker image + uses: docker/build-push-action@48aba3b46d1b1fec4febb7c5d0c644b249a11355 # v6.10.0 + id: build-push with: file: build/Dockerfile - context: '.' - cache-from: type=gha,scope=${{ inputs.image }} - target: ${{ inputs.target }} - tags: docker.io/${{ inputs.image }}:${{ steps.meta.outputs.version }} - load: true + context: "." + cache-from: type=gha,scope=${{ inputs.image }}${{ steps.nap_modules.outputs.name != '' && format('-{0}', steps.nap_modules.outputs.name) || '' }} + cache-to: type=gha,scope=${{ inputs.image }}${{ steps.nap_modules.outputs.name != '' && format('-{0}', steps.nap_modules.outputs.name) || '' }},mode=max + target: ${{ inputs.target }}${{ inputs.authenticated && '-prebuilt' || '' }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + annotations: ${{ steps.meta.outputs.annotations }} + platforms: ${{ inputs.platforms }} + load: false + push: ${{ inputs.authenticated }} + pull: true + sbom: ${{ inputs.authenticated && !contains(inputs.target, 'aws') }} + provenance: false build-args: | BUILD_OS=${{ inputs.image }} - IC_VERSION=${{ startsWith(github.ref, 'refs/tags/') && steps.meta.outputs.version || 'CI' }} - ${{ inputs.nap_modules != '' && format('NAP_MODULES={0}', inputs.nap_modules) || '' }} - ${{ steps.nap_modules.outputs.modules != '' && format('NAP_MODULES_AWS={0}', steps.nap_modules.outputs.modules) || '' }} - ${{ contains(inputs.nap_modules, 'waf') && 'DEBIAN_VERSION=buster-slim' || '' }} + ${{ inputs.authenticated && format('PREBUILT_BASE_IMG={0}', steps.base_name.outputs.image ) }} + IC_VERSION=${{ inputs.ic-version && inputs.ic-version || steps.meta.outputs.version }} + ${{ inputs.nap-modules != '' && format('NAP_MODULES={0}', steps.nap_modules.outputs.name) || '' }} + ${{ contains(inputs.nap-modules,'waf') && format('NGINX_AGENT={0}', steps.nap_modules.outputs.agent) || '' }} + ${{ (contains(inputs.target, 'aws') && inputs.nap-modules != '') && format('NAP_MODULES_AWS={0}', steps.nap_modules.outputs.modules) || '' }} + ${{ contains(inputs.image, 'v5') && 'WAF_VERSION=v5' || '' }} secrets: | - "nginx-repo.crt=${{ inputs.nap_modules != '' && secrets.NGINX_AP_CRT || secrets.NGINX_CRT }}" - "nginx-repo.key=${{ inputs.nap_modules != '' && secrets.NGINX_AP_KEY || secrets.NGINX_KEY }}" + "nginx-repo.crt=${{ inputs.nap-modules != '' && secrets.NGINX_AP_CRT || secrets.NGINX_CRT }}" + "nginx-repo.key=${{ inputs.nap-modules != '' && secrets.NGINX_AP_KEY || secrets.NGINX_KEY }}" + ${{ contains(inputs.image, 'ubi') && format('"rhel_license={0}"', secrets.RHEL_LICENSE) || '' }} + if: ${{ steps.images_exist.outputs.base_exists != 'true' || steps.images_exist.outputs.target_exists != 'true' }} - - name: Run Trivy vulnerability scanner - uses: aquasecurity/trivy-action@0.8.0 - continue-on-error: true - with: - image-ref: docker.io/${{ inputs.image }}:${{ steps.meta.outputs.version }} - format: 'sarif' - output: 'trivy-results-${{ inputs.image }}.sarif' - ignore-unfixed: 'true' - - - name: Upload Trivy scan results to GitHub Security tab - uses: github/codeql-action/upload-sarif@v2 - continue-on-error: true + - name: Make directory for security scan results + run: | + mkdir -p "${{ inputs.image }}-results/" + if: ${{ inputs.authenticated && steps.build-push.conclusion == 'success' }} + + # - name: Run Trivy vulnerability scanner + # uses: aquasecurity/trivy-action@6e7b7d1fd3e4fef0c5fa8cce1229c54b2c9bd0d8 # 0.24.0 + # with: + # image-ref: ${{ steps.meta.outputs.tags }} + # format: "sarif" + # output: "${{ inputs.image }}-results/trivy.sarif" + # ignore-unfixed: "true" + # if: ${{ inputs.authenticated && steps.build-push.conclusion == 'success' }} + + - name: DockerHub Login for Docker Scout + uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 with: - sarif_file: 'trivy-results-${{ inputs.image }}.sarif' + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + if: ${{ inputs.authenticated && steps.build-push.conclusion == 'success' }} - - name: Upload Scan Results - uses: actions/upload-artifact@v3 - continue-on-error: true + - name: Run Docker Scout vulnerability scanner + id: docker-scout + uses: docker/scout-action@b23590dc1e4d09febc00cfcbc51e9e8c0f7ee9f3 # v1.16.1 with: - name: 'trivy-results-${{ inputs.image }}.sarif' - path: 'trivy-results-${{ inputs.image }}.sarif' - if: always() + command: cves + image: ${{ steps.meta.outputs.tags }} + ignore-base: true + sarif-file: "${{ inputs.image }}-results/scout.sarif" + write-comment: false + github-token: ${{ secrets.GITHUB_TOKEN }} # to be able to write the comment + summary: true + if: ${{ inputs.authenticated && steps.build-push.conclusion == 'success' }} diff --git a/.github/workflows/build-single-image.yml b/.github/workflows/build-single-image.yml new file mode 100644 index 0000000000..76659e81d8 --- /dev/null +++ b/.github/workflows/build-single-image.yml @@ -0,0 +1,115 @@ +name: Build single image +run-name: Building gcr.io/f5-gcs-7899-ptg-ingrss-ctlr/${{ github.actor }}-dev/${{ inputs.prefix }}:${{ inputs.tag }} by @${{ github.actor }} + +on: + workflow_dispatch: + inputs: + target: + description: 'Image build make target to call' + required: true + type: string + prefix: + description: 'Image prefix to use in GCR, e.g. nginx-ic/nginx-ingress' + required: true + type: string + tag: + description: 'Image tag to use in GCR, e.g. 3.7.0-SNAPSHOT' + required: true + type: string + branch: + description: 'Branch to checkout for build' + required: false + type: string + default: main + plus_repo: + description: 'Plus repo to install from' + required: true + default: 'pkgs.nginx.com' + type: choice + options: + - pkgs.nginx.com + - pkgs-test.nginx.com + +defaults: + run: + shell: bash + +permissions: + contents: read + +jobs: + build: + permissions: + contents: read # for docker/build-push-action to read repo content + id-token: write # for login to GCP + runs-on: ubuntu-24.04 + steps: + - name: Checkout Repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + ref: ${{ inputs.branch }} + fetch-depth: 0 + + - name: Output Variables + id: vars + run: | + ./.github/scripts/variables.sh go_code_md5 >> $GITHUB_OUTPUT + cat $GITHUB_OUTPUT + + - name: Setup Golang Environment + uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0 + with: + go-version-file: go.mod + + - name: Authenticate to Google Cloud + id: auth + uses: google-github-actions/auth@6fc4af4b145ae7821d527454aa9bd537d1f2dc5f # v2.1.7 + with: + token_format: access_token + workload_identity_provider: ${{ secrets.GCR_WORKLOAD_IDENTITY }} + service_account: ${{ secrets.GCR_SERVICE_ACCOUNT }} + + - name: Login to GCR + uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 + with: + registry: gcr.io + username: oauth2accesstoken + password: ${{ steps.auth.outputs.access_token }} + + - name: Setup plus credentials + run: | + printf '%s\n' "${CERT}" > nginx-repo.crt + printf '%s\n' "${KEY}" > nginx-repo.key + if [[ "${{ inputs.target }}" =~ ubi ]]; then + printf '%s\n' "${RHEL}" > rhel_license + fi + env: + CERT: ${{ secrets.NGINX_CRT }} + KEY: ${{ secrets.NGINX_KEY }} + RHEL: ${{ secrets.RHEL_LICENSE }} + if: ${{ contains(inputs.target, 'plus') }} + + - name: Fetch Cached Binary Artifacts + id: binary-cache + uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 + with: + path: ${{ github.workspace }}/dist + key: nginx-ingress-${{ steps.vars.outputs.go_code_md5 }} + + - name: Build Image + run: | + make ${{ inputs.target }} + env: + REGISTRY: gcr.io/f5-gcs-7899-ptg-ingrss-ctlr/${{ github.actor }}-dev + PREFIX: ${{ inputs.prefix }} + TAG: ${{ inputs.tag }} + PLUS_REPO: ${{ inputs.plus_repo }} + TARGET: goreleaser + + - name: Push image + run: + docker push ${REGISTRY}/${PREFIX}:${TAG} + env: + REGISTRY: gcr.io/f5-gcs-7899-ptg-ingrss-ctlr/${{ github.actor }}-dev + PREFIX: ${{ inputs.prefix }} + TAG: ${{ inputs.tag }} diff --git a/.github/workflows/build-test-image.yml b/.github/workflows/build-test-image.yml new file mode 100644 index 0000000000..629b2dfd09 --- /dev/null +++ b/.github/workflows/build-test-image.yml @@ -0,0 +1,61 @@ +name: Build Test Image + +on: + workflow_dispatch: + inputs: + force: + description: "Force rebuild of test image" + required: false + default: "false" + schedule: + - cron: "0 3 * * *" # run every day at 03:00 UTC + +defaults: + run: + shell: bash + +concurrency: + group: ${{ github.ref_name }}-build-test + cancel-in-progress: true + +permissions: + contents: read + id-token: write + +jobs: + build: + name: Build test image + runs-on: ubuntu-24.04 + steps: + - name: Checkout Repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: Docker Buildx + uses: docker/setup-buildx-action@6524bf65af31da8d45b59e8c27de4bd072b392f5 # v3.8.0 + + - name: Authenticate to Google Cloud + id: auth + uses: google-github-actions/auth@6fc4af4b145ae7821d527454aa9bd537d1f2dc5f # v2.1.7 + with: + token_format: access_token + workload_identity_provider: ${{ secrets.GCR_WORKLOAD_IDENTITY }} + service_account: ${{ secrets.GCR_SERVICE_ACCOUNT }} + + - name: Login to GCR + uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 + with: + registry: gcr.io + username: oauth2accesstoken + password: ${{ steps.auth.outputs.access_token }} + + - name: Build Test-Runner Container + uses: docker/build-push-action@48aba3b46d1b1fec4febb7c5d0c644b249a11355 # v6.10.0 + with: + file: tests/Dockerfile + context: "." + cache-from: type=gha,scope=test-runner + tags: | + gcr.io/f5-gcs-7899-ptg-ingrss-ctlr/dev/test-runner:${{ hashFiles('./tests/requirements.txt', './tests/Dockerfile') }} + gcr.io/f5-gcs-7899-ptg-ingrss-ctlr/dev/test-runner:latest + pull: true + push: true diff --git a/.github/workflows/build-ubi-dependency.yml b/.github/workflows/build-ubi-dependency.yml new file mode 100644 index 0000000000..9d0c2b1295 --- /dev/null +++ b/.github/workflows/build-ubi-dependency.yml @@ -0,0 +1,140 @@ +name: Build UBI ppc64le Dependency + +on: + push: + branches: + - main + paths: + - build/dependencies/Dockerfile.ubi + workflow_dispatch: + inputs: + nginx_version: + type: string + description: "NGINX Version to build for" + required: false + force: + type: boolean + description: "Force rebuild" + required: false + default: false + +env: + IMAGE_NAME: ghcr.io/nginxinc/dependencies/nginx-ubi-ppc64le + +concurrency: + group: ${{ github.ref_name }}-ubi-ppc64le-build + cancel-in-progress: true + +permissions: + contents: read + +jobs: + checks: + name: Check versions + runs-on: ubuntu-24.04 + permissions: + packages: read + contents: read + strategy: + fail-fast: false + outputs: + nginx_version: ${{ steps.var.outputs.nginx_version }} + njs_version: ${{ steps.var.outputs.njs_version }} + target_exists: ${{ steps.var.outputs.target_image_exists }} + steps: + - name: Checkout Repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: Login to GitHub Container Registry + uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Output Variables + id: var + run: | + if [ -n "${{ inputs.nginx_version }}" ]; then + nginx_v=${{ inputs.nginx_version }} + else + nginx_v=$(grep -m1 'FROM nginx:' Outputs -------------------------------" + echo "NJS_VERSION=$njs" + echo "nginx_version=${nginx_v}" + echo "njs_version=${njs}" + echo "target_image_exists=${target_image_exists}" + echo "nginx_version=${nginx_v}" >> $GITHUB_OUTPUT + echo "njs_version=${njs}" >> $GITHUB_OUTPUT + echo "target_image_exists=${target_image_exists}" >> $GITHUB_OUTPUT + + build-binaries: + name: Build Binary Container Image + if: ${{ needs.checks.outputs.target_exists != 'true' || inputs.force }} + needs: checks + runs-on: ubuntu-24.04 + permissions: + packages: write + contents: read + strategy: + fail-fast: false + steps: + - name: Checkout Repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: Setup QEMU + uses: docker/setup-qemu-action@49b3bc8e6bdd4a60e6116a5414239cba5943d3cf # v3.2.0 + with: + platforms: arm64,ppc64le,s390x + + - name: Docker Buildx + uses: docker/setup-buildx-action@6524bf65af31da8d45b59e8c27de4bd072b392f5 # v3.8.0 + + - name: Login to GitHub Container Registry + uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Docker meta + id: meta + uses: docker/metadata-action@369eb591f429131d6889c46b94e711f089e6ca96 # v5.6.1 + with: + images: | + name=${{ env.IMAGE_NAME }},enable=true + tags: | + type=raw,value=nginx-${{ needs.checks.outputs.nginx_version }},enable=true + env: + DOCKER_METADATA_ANNOTATIONS_LEVELS: manifest,index + + - name: Build and push + uses: docker/build-push-action@48aba3b46d1b1fec4febb7c5d0c644b249a11355 # v6.10.0 + with: + file: ./build/dependencies/Dockerfile.ubi + context: "." + pull: true + push: true + # build multi-arch so that it can be mounted from any image + # even though only ppc64le will contain binaries + platforms: "linux/amd64,linux/arm64,linux/ppc64le,linux/s390x" + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + annotations: ${{ steps.meta.outputs.annotations }} + cache-from: type=gha,scope=nginx-ubi-ppc64le + cache-to: type=gha,scope=nginx-ubi-ppc64le,mode=max + target: final + sbom: false + provenance: mode=max + build-args: | + NGINX=${{ needs.checks.outputs.nginx_version }} + NJS=${{ needs.checks.outputs.njs_version }} diff --git a/.github/workflows/cache-update.yml b/.github/workflows/cache-update.yml new file mode 100644 index 0000000000..9b93935a92 --- /dev/null +++ b/.github/workflows/cache-update.yml @@ -0,0 +1,110 @@ +name: Cache Update + +on: + workflow_dispatch: + +defaults: + run: + shell: bash + +concurrency: + group: ${{ github.ref_name }}-cache-update + cancel-in-progress: true + +permissions: + contents: read + +jobs: + checks: + name: Checks and variables + runs-on: ubuntu-24.04 + outputs: + go_code_md5: ${{ steps.vars.outputs.go_code_md5 }} + docker_md5: ${{ steps.vars.outputs.docker_md5 }} + image_matrix_oss: ${{ steps.vars.outputs.image_matrix_oss }} + image_matrix_plus: ${{ steps.vars.outputs.image_matrix_plus }} + image_matrix_nap: ${{ steps.vars.outputs.image_matrix_nap }} + steps: + - name: Checkout Repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: Output Variables + id: vars + run: | + ./.github/scripts/variables.sh go_code_md5 >> $GITHUB_OUTPUT + ./.github/scripts/variables.sh docker_md5 >> $GITHUB_OUTPUT + echo "image_matrix_oss=$(cat .github/data/matrix-images-oss.json | jq -c)" >> $GITHUB_OUTPUT + echo "image_matrix_plus=$(cat .github/data/matrix-images-plus.json | jq -c)" >> $GITHUB_OUTPUT + echo "image_matrix_nap=$(cat .github/data/matrix-images-nap.json | jq -c)" >> $GITHUB_OUTPUT + cat $GITHUB_OUTPUT + + build-docker: + name: Build Docker OSS + needs: [checks] + strategy: + fail-fast: false + matrix: ${{ fromJSON( needs.checks.outputs.image_matrix_oss ) }} + uses: ./.github/workflows/build-oss.yml + with: + platforms: ${{ matrix.platforms }} + image: ${{ matrix.image }} + go-md5: ${{ needs.checks.outputs.go_code_md5 }} + base-image-md5: ${{ needs.checks.outputs.docker_md5 }} + authenticated: false + tag: "edge" + branch: ${{ github.ref }} + permissions: + contents: read + actions: read + security-events: write + id-token: write + packages: write + pull-requests: write # for scout report + secrets: inherit + + build-docker-plus: + name: Build Docker Plus + needs: [checks] + strategy: + fail-fast: false + matrix: ${{ fromJSON( needs.checks.outputs.image_matrix_plus ) }} + uses: ./.github/workflows/build-plus.yml + with: + platforms: ${{ matrix.platforms }} + image: ${{ matrix.image }} + target: ${{ matrix.target }} + go-md5: ${{ needs.checks.outputs.go_code_md5 }} + base-image-md5: ${{ needs.checks.outputs.docker_md5 }} + authenticated: false + tag: "edge" + branch: ${{ github.ref }} + permissions: + contents: read + security-events: write + id-token: write + pull-requests: write # for scout report + secrets: inherit + + build-docker-nap: + name: Build Docker NAP + needs: [checks] + strategy: + fail-fast: false + matrix: ${{ fromJSON( needs.checks.outputs.image_matrix_nap ) }} + uses: ./.github/workflows/build-plus.yml + with: + platforms: ${{ matrix.platforms }} + image: ${{ matrix.image }} + target: ${{ matrix.target }} + go-md5: ${{ needs.checks.outputs.go_code_md5 }} + base-image-md5: ${{ needs.checks.outputs.docker_md5 }} + nap-modules: ${{ matrix.nap_modules }} + authenticated: false + tag: "edge" + branch: ${{ github.ref }} + permissions: + contents: read + security-events: write + id-token: write + pull-requests: write # for scout report + secrets: inherit diff --git a/.github/workflows/certify-ubi-image.yml b/.github/workflows/certify-ubi-image.yml new file mode 100644 index 0000000000..fd8a5aeac0 --- /dev/null +++ b/.github/workflows/certify-ubi-image.yml @@ -0,0 +1,49 @@ +name: Certify UBI image +run-name: Certify UBI image ${{ inputs.image }} by @${{ github.actor }} + +on: + workflow_dispatch: + inputs: + image: + description: "Image to certify" + required: true + type: string + submit: + description: "Submit results to Redhat" + required: false + type: boolean + default: false + preflight_version: + description: "Preflight version to use" + required: false + type: string + default: "1.11.1" + platforms: + description: A comma separated list of architectures in the image manifest to certify + required: false + default: "amd64,arm64,ppc64le,s390x" + +defaults: + run: + shell: bash + +permissions: + contents: read + +jobs: + certify-ubi-images: + name: Certify OpenShift UBI images + runs-on: ubuntu-24.04 + steps: + - name: Checkout + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: Certify UBI OSS images in quay + uses: ./.github/actions/certify-openshift-image + with: + image: ${{ inputs.image }} + project_id: ${{ secrets.CERTIFICATION_PROJECT_ID }} + pyxis_token: ${{ secrets.PYXIS_API_TOKEN }} + preflight_version: ${{ inputs.preflight_version }} + submit: ${{ inputs.submit || true }} + platforms: ${{ inputs.platforms }} diff --git a/.github/workflows/cherry-pick.yml b/.github/workflows/cherry-pick.yml new file mode 100644 index 0000000000..de97cb6041 --- /dev/null +++ b/.github/workflows/cherry-pick.yml @@ -0,0 +1,40 @@ +name: "Cherry-pick dependencies to release branch" +on: + pull_request: + branches: + - main + types: ["closed"] + +permissions: + contents: read + +jobs: + cherry_pick_to_release: + permissions: + contents: write + pull-requests: write + runs-on: ubuntu-24.04 + name: Cherry pick into release branch + if: ${{ (contains(github.event.pull_request.labels.*.name, 'dependencies') || contains(github.event.pull_request.labels.*.name, 'needs cherry pick')) && github.event.pull_request.merged == true }} + steps: + - name: Checkout + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + fetch-depth: 0 + token: ${{ secrets.NGINX_PAT }} + + - name: Set release branch variable + id: branch + run: | + branch=$(git branch -a | egrep '^\s+remotes/origin/release' | awk '{print $1}' | sort -u | tail -n 1) + release_branch=$(basename ${branch}) + echo "branch=${release_branch}" >> $GITHUB_OUTPUT + cat $GITHUB_OUTPUT + + - name: Cherry pick into ${{ steps.branch.outputs.branch }} + uses: carloscastrojumo/github-cherry-pick-action@503773289f4a459069c832dc628826685b75b4b3 # v1.0.10 + with: + branch: ${{ steps.branch.outputs.branch }} + token: ${{ secrets.NGINX_PAT }} + author: nginx-bot + title: "[cherry-pick] {old_title}" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e783f11432..b4996635bd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,176 +1,495 @@ name: CI +run-name: CI on "${{ github.head_ref && github.head_ref || github.ref }}" by @${{ github.actor }} on: - push: - branches: - - main - tags: - - 'v[0-9]+.[0-9]+.[0-9]+' pull_request: branches: - main - release-* - types: - - opened - - reopened - - synchronize - schedule: - - cron: '0 4 * * *' + merge_group: + workflow_dispatch: + inputs: + force: + type: boolean + description: "Force rebuild" + required: false + default: false defaults: run: shell: bash -env: - HELM_CHART_DIR: deployments/helm-chart - GIT_NAME: NGINX Kubernetes Team - GIT_MAIL: kubernetes@nginx.com - concurrency: group: ${{ github.ref_name }}-ci cancel-in-progress: true -jobs: +permissions: + contents: read +jobs: checks: name: Checks and variables - runs-on: ubuntu-20.04 + runs-on: ubuntu-24.04 + permissions: + contents: read + id-token: write outputs: - go_path: ${{ steps.go.outputs.go_path }} + docs_only: ${{ github.event.pull_request && steps.docs.outputs.docs_only == 'true' }} + some_docs: ${{ github.event.pull_request && steps.docs.outputs.some_docs == 'true' }} k8s_latest: ${{ steps.vars.outputs.k8s_latest }} + go_path: ${{ steps.vars.outputs.go_path }} + go_code_md5: ${{ steps.vars.outputs.go_code_md5 }} + binary_cache_hit: ${{ steps.binary-cache.outputs.cache-hit }} + chart_version: ${{ steps.vars.outputs.chart_version }} + ic_version: ${{ steps.vars.outputs.ic_version }} + docker_md5: ${{ steps.vars.outputs.docker_md5 }} + build_tag: ${{ steps.vars.outputs.build_tag }} + stable_tag: ${{ steps.vars.outputs.stable_tag }} + forked_workflow: ${{ steps.vars.outputs.forked_workflow }} + stable_image_exists: ${{ steps.stable_exists.outputs.exists }} + additional_tag: ${{ steps.vars.outputs.additional_tag }} + image_matrix_oss: ${{ steps.vars.outputs.image_matrix_oss }} + image_matrix_plus: ${{ steps.vars.outputs.image_matrix_plus }} + image_matrix_nap: ${{ steps.vars.outputs.image_matrix_nap }} steps: - name: Checkout Repository - uses: actions/checkout@v3 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + fetch-depth: 0 + + - name: Filter only docs changes + id: docs + run: | + files=$(git diff --name-only HEAD^ | egrep -v "^site/" | egrep -v "^examples/" | egrep -v "^README.md") + docs_files=$(git diff --name-only HEAD^ | grep "^site/") + if [ -z "$files" ]; then + echo "docs_only=true" >> $GITHUB_OUTPUT + else + echo "docs_only=false" >> $GITHUB_OUTPUT + fi + + if [ -n "$docs_files" ]; then + echo "some_docs=true" >> $GITHUB_OUTPUT + else + echo "some_docs=false" >> $GITHUB_OUTPUT + fi + + echo $files + echo $docs_files + cat $GITHUB_OUTPUT + shell: bash --noprofile --norc -o pipefail {0} + + - name: Setup Golang Environment + uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0 + with: + go-version-file: go.mod + - name: Output Variables id: vars run: | - echo "k8s_latest=$(grep -m1 'FROM kindest/node' > $GITHUB_OUTPUT + kindest_latest=$(curl -s "https://hub.docker.com/v2/repositories/kindest/node/tags" \ + | grep -o '"name": *"[^"]*' \ + | grep -o '[^"]*$' \ + | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' \ + | sort -rV \ + | head -n 1 \ + | sed 's/^.\{1\}//' \ + | tr -d '\n') + echo "k8s_latest=$kindest_latest" >> $GITHUB_OUTPUT + echo "go_path=$(go env GOPATH)" >> $GITHUB_OUTPUT + source .github/data/version.txt + echo "ic_version=${IC_VERSION}" >> $GITHUB_OUTPUT + echo "chart_version=${HELM_CHART_VERSION}" >> $GITHUB_OUTPUT + echo "forked_workflow=${{ (github.event.pull_request && github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name) || github.repository != 'nginxinc/kubernetes-ingress' }}" >> $GITHUB_OUTPUT + ./.github/scripts/variables.sh go_code_md5 >> $GITHUB_OUTPUT + ./.github/scripts/variables.sh docker_md5 >> $GITHUB_OUTPUT + ./.github/scripts/variables.sh build_tag >> $GITHUB_OUTPUT + ./.github/scripts/variables.sh stable_tag >> $GITHUB_OUTPUT + ref=${{ github.ref_name }} + if [[ $ref =~ merge ]]; then + additional_tag="pr-${ref%*/merge}" + else + additional_tag="${ref//\//-}" + fi + echo "additional_tag=${additional_tag}" >> $GITHUB_OUTPUT + echo "image_matrix_oss=$(cat .github/data/matrix-images-oss.json | jq -c)" >> $GITHUB_OUTPUT + echo "image_matrix_plus=$(cat .github/data/matrix-images-plus.json | jq -c)" >> $GITHUB_OUTPUT + echo "image_matrix_nap=$(cat .github/data/matrix-images-nap.json | jq -c)" >> $GITHUB_OUTPUT + cat $GITHUB_OUTPUT + + - name: Fetch Cached Binary Artifacts + id: binary-cache + uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 + with: + path: ${{ github.workspace }}/dist + key: nginx-ingress-${{ steps.vars.outputs.go_code_md5 }} + lookup-only: true + + - name: Authenticate to Google Cloud + id: auth + uses: google-github-actions/auth@6fc4af4b145ae7821d527454aa9bd537d1f2dc5f # v2.1.7 + with: + token_format: access_token + workload_identity_provider: ${{ secrets.GCR_WORKLOAD_IDENTITY }} + service_account: ${{ secrets.GCR_SERVICE_ACCOUNT }} + if: ${{ steps.vars.outputs.forked_workflow == 'false' }} + + - name: Login to GCR + uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 + with: + registry: gcr.io + username: oauth2accesstoken + password: ${{ steps.auth.outputs.access_token }} + if: ${{ steps.vars.outputs.forked_workflow == 'false' }} + + - name: Check if stable image exists + id: stable_exists + run: | + if docker pull gcr.io/f5-gcs-7899-ptg-ingrss-ctlr/dev/nginx-ic/nginx-ingress:${{ steps.vars.outputs.stable_tag }}; then + echo "exists=true" >> $GITHUB_OUTPUT + fi + if: ${{ steps.vars.outputs.forked_workflow == 'false' }} + + - name: Output variables + run: | + echo docs_only: ${{ github.event.pull_request && steps.docs.outputs.docs_only == 'true' }} + echo k8s_latest: ${{ steps.vars.outputs.k8s_latest }} + echo go_path: ${{ steps.vars.outputs.go_path }} + echo go_code_md5: ${{ steps.vars.outputs.go_code_md5 }} + echo binary_cache_hit: ${{ steps.binary-cache.outputs.cache-hit }} + echo chart_version: ${{ steps.vars.outputs.chart_version }} + echo ic_version: ${{ steps.vars.outputs.ic_version }} + echo docker_md5: ${{ steps.vars.outputs.docker_md5 }} + echo build_tag: ${{ steps.vars.outputs.build_tag }} + echo stable_tag: ${{ steps.vars.outputs.stable_tag }} + echo forked_workflow: ${{ steps.vars.outputs.forked_workflow }} + echo stable_image_exists: ${{ steps.stable_exists.outputs.exists }} + echo additional_tag: ${{ steps.vars.outputs.additional_tag }} + echo 'image_matrix_oss: ${{ steps.vars.outputs.image_matrix_oss }}' + echo 'image_matrix_plus: ${{ steps.vars.outputs.image_matrix_plus }}' + echo 'image_matrix_nap: ${{ steps.vars.outputs.image_matrix_nap }}' + + verify-codegen: + name: Verify generated code + runs-on: ubuntu-24.04 + permissions: + contents: read + needs: checks + steps: + - name: Checkout Repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: Setup Golang Environment - uses: actions/setup-go@v3 + uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0 with: go-version-file: go.mod - cache: true - - name: Determine GOPATH - id: go - run: echo "go_path=$(go env GOPATH)" >> $GITHUB_OUTPUT + - name: Check if go.mod and go.sum are up to date - run: | - go mod tidy && git diff --exit-code -- go.mod go.sum + run: go mod tidy && git diff --exit-code -- go.mod go.sum + - name: Check if CRDs changed - run: | - make update-crds && git diff --name-only --exit-code deployments/common/crds* deployments/helm-chart/crds* + run: make update-crds && git diff --name-only --exit-code config/crd/bases + - name: Check if Codegen changed run: | cd ../.. && mkdir -p github.com/nginxinc && mv kubernetes-ingress/kubernetes-ingress github.com/nginxinc/ && cd github.com/nginxinc/kubernetes-ingress make update-codegen && git diff --name-only --exit-code pkg/** cd ../../.. && mv github.com/nginxinc/kubernetes-ingress kubernetes-ingress/kubernetes-ingress - binary: - name: Build binary - runs-on: ubuntu-20.04 + - name: Install gofumpt + run: go install mvdan.cc/gofumpt@latest + + - name: Check if telemetry schema changed + run: | + export PATH=$PATH:$(go env GOPATH)/bin + make telemetry-schema && git diff --name-only --exit-code internal/telemetry + + - name: Check if make docs builds + if: ${{ needs.checks.outputs.some_docs == 'true' }} + run: cd site && make docs-ci + + unit-tests: + name: Unit Tests + runs-on: ubuntu-24.04 needs: checks steps: - name: Checkout Repository - uses: actions/checkout@v3 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: Setup Golang Environment + uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0 + with: + go-version-file: go.mod + if: ${{ needs.checks.outputs.binary_cache_hit != 'true' }} + + - name: Run Tests + run: make cover + if: ${{ needs.checks.outputs.binary_cache_hit != 'true' }} + + - name: Upload coverage to Codecov + uses: codecov/codecov-action@1e68e06f1dbfde0e4cefc87efeba9e4643565303 # v5.1.2 + with: + files: ./coverage.txt + token: ${{ secrets.CODECOV_TOKEN }} # required + if: ${{ needs.checks.outputs.binary_cache_hit != 'true' }} + + - name: Run static check + uses: dominikh/staticcheck-action@fe1dd0c3658873b46f8c9bb3291096a617310ca6 # v1.3.1 + with: + version: "v0.5.1" + install-go: false + if: ${{ needs.checks.outputs.binary_cache_hit != 'true' }} + + binaries: + name: Build Binaries + runs-on: ubuntu-24.04 + needs: [checks, unit-tests, verify-codegen] + permissions: + contents: write # for goreleaser/goreleaser-action to manage releases + id-token: write # for goreleaser/goreleaser-action to sign artifacts + issues: write # for goreleaser/goreleaser-action to close milestone + steps: + - name: Checkout Repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: fetch-depth: 0 + - name: Setup Golang Environment - uses: actions/setup-go@v3 + uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0 with: go-version-file: go.mod - cache: true - - name: Build binary - uses: goreleaser/goreleaser-action@v3 + if: ${{ needs.checks.outputs.binary_cache_hit != 'true' }} + + - name: Build binaries + uses: goreleaser/goreleaser-action@9ed2f89a662bf1735a48bc8557fd212fa902bebf # v6.1.0 with: version: latest - args: build --snapshot --rm-dist --single-target --id kubernetes-ingress + args: build --snapshot --clean env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GOPATH: ${{ needs.checks.outputs.go_path }} + AWS_PRODUCT_CODE: ${{ secrets.AWS_PRODUCT_CODE }} + AWS_PUB_KEY: ${{ secrets.AWS_PUB_KEY }} + AWS_NAP_DOS_PRODUCT_CODE: ${{ secrets.AWS_NAP_DOS_PRODUCT_CODE }} + AWS_NAP_DOS_PUB_KEY: ${{ secrets.AWS_NAP_DOS_PUB_KEY }} + AWS_NAP_WAF_PRODUCT_CODE: ${{ secrets.AWS_NAP_WAF_PRODUCT_CODE }} + AWS_NAP_WAF_PUB_KEY: ${{ secrets.AWS_NAP_WAF_PUB_KEY }} + AWS_NAP_WAF_DOS_PRODUCT_CODE: ${{ secrets.AWS_NAP_WAF_DOS_PRODUCT_CODE }} + AWS_NAP_WAF_DOS_PUB_KEY: ${{ secrets.AWS_NAP_WAF_DOS_PUB_KEY }} + GORELEASER_CURRENT_TAG: "v${{ needs.checks.outputs.ic_version }}" + if: ${{ needs.checks.outputs.binary_cache_hit != 'true' }} - name: Store Artifacts in Cache - uses: actions/cache@v3 + uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 with: path: ${{ github.workspace }}/dist - key: nginx-ingress-${{ github.run_id }}-${{ github.run_number }}-single + key: nginx-ingress-${{ needs.checks.outputs.go_code_md5 }} + if: ${{ needs.checks.outputs.binary_cache_hit != 'true' }} - unit-tests: - name: Unit Tests - runs-on: ubuntu-20.04 - needs: checks - steps: - - name: Checkout Repository - uses: actions/checkout@v3 - - name: Setup Golang Environment - uses: actions/setup-go@v3 - with: - go-version-file: go.mod - cache: true - - name: Run Tests - run: make cover - - name: Upload coverage to Codecov - uses: codecov/codecov-action@v3 - with: - files: ./coverage.txt + build-docker: + name: Build Docker OSS + needs: [binaries, checks] + strategy: + fail-fast: false + matrix: ${{ fromJSON( needs.checks.outputs.image_matrix_oss ) }} + uses: ./.github/workflows/build-oss.yml + with: + platforms: ${{ matrix.platforms }} + image: ${{ matrix.image }} + go-md5: ${{ needs.checks.outputs.go_code_md5 }} + base-image-md5: ${{ needs.checks.outputs.docker_md5 }} + authenticated: ${{ needs.checks.outputs.forked_workflow != 'true' }} + full-build: ${{ inputs.force && inputs.force || false }} + tag: ${{ needs.checks.outputs.build_tag }} + branch: ${{ (github.head_ref && needs.checks.outputs.forked_workflow != 'true') && github.head_ref || github.ref }} + ic-version: ${{ needs.checks.outputs.ic_version }} + permissions: + contents: read + actions: read + id-token: write + packages: write + pull-requests: write # for scout report + secrets: inherit + if: ${{ inputs.force || (needs.checks.outputs.forked_workflow == 'true' && needs.checks.outputs.docs_only == 'false') || (needs.checks.outputs.forked_workflow == 'false' && needs.checks.outputs.stable_image_exists != 'true' && needs.checks.outputs.docs_only == 'false') }} + + build-docker-plus: + name: Build Docker Plus + needs: [binaries, checks] + strategy: + fail-fast: false + matrix: ${{ fromJSON( needs.checks.outputs.image_matrix_plus ) }} + uses: ./.github/workflows/build-plus.yml + with: + platforms: ${{ matrix.platforms }} + image: ${{ matrix.image }} + target: ${{ matrix.target }} + go-md5: ${{ needs.checks.outputs.go_code_md5 }} + base-image-md5: ${{ needs.checks.outputs.docker_md5 }} + branch: ${{ (github.head_ref && needs.checks.outputs.forked_workflow != 'true') && github.head_ref || github.ref }} + tag: ${{ needs.checks.outputs.build_tag }} + authenticated: ${{ needs.checks.outputs.forked_workflow != 'true' }} + full-build: ${{ inputs.force && inputs.force || false }} + ic-version: ${{ needs.checks.outputs.ic_version }} + permissions: + contents: read + id-token: write + pull-requests: write # for scout report + secrets: inherit + if: ${{ inputs.force || (needs.checks.outputs.forked_workflow == 'true' && needs.checks.outputs.docs_only == 'false') || (needs.checks.outputs.forked_workflow == 'false' && needs.checks.outputs.stable_image_exists != 'true' && needs.checks.outputs.docs_only == 'false') }} + + build-docker-nap: + name: Build Docker NAP + needs: [binaries, checks] + strategy: + fail-fast: false + matrix: ${{ fromJSON( needs.checks.outputs.image_matrix_nap ) }} + uses: ./.github/workflows/build-plus.yml + with: + platforms: ${{ matrix.platforms }} + image: ${{ matrix.image }} + target: ${{ matrix.target }} + go-md5: ${{ needs.checks.outputs.go_code_md5 }} + base-image-md5: ${{ needs.checks.outputs.docker_md5 }} + branch: ${{ (github.head_ref && needs.checks.outputs.forked_workflow != 'true') && github.head_ref || github.ref }} + tag: ${{ needs.checks.outputs.build_tag }} + nap-modules: ${{ matrix.nap_modules }} + authenticated: ${{ needs.checks.outputs.forked_workflow != 'true' }} + full-build: ${{ inputs.force && inputs.force || false }} + ic-version: ${{ needs.checks.outputs.ic_version }} + permissions: + contents: read + id-token: write # gcr login + pull-requests: write # for scout report + secrets: inherit + if: ${{ inputs.force || (needs.checks.outputs.forked_workflow == 'true' && needs.checks.outputs.docs_only == 'false') || (needs.checks.outputs.forked_workflow == 'false' && needs.checks.outputs.stable_image_exists != 'true' && needs.checks.outputs.docs_only == 'false') }} + + tag-target: + name: Tag untested image with PR number + needs: [checks, build-docker, build-docker-plus, build-docker-nap] + permissions: + contents: read # To checkout repository + id-token: write # To sign into Google Container Registry + uses: ./.github/workflows/retag-images.yml + with: + source_tag: ${{ needs.checks.outputs.build_tag }} + target_tag: ${{ needs.checks.outputs.additional_tag }} + dry_run: false + secrets: inherit + if: ${{ inputs.force || (needs.checks.outputs.forked_workflow == 'true' && needs.checks.outputs.docs_only == 'false') || (needs.checks.outputs.forked_workflow == 'false' && needs.checks.outputs.stable_image_exists != 'true' && needs.checks.outputs.docs_only == 'false') }} helm-tests: - name: Helm Tests - runs-on: ubuntu-20.04 - needs: [binary, unit-tests, checks] + if: ${{ needs.checks.outputs.docs_only != 'true' }} + name: Helm Tests ${{ matrix.base-os }} + runs-on: ubuntu-24.04 + needs: [checks, binaries, build-docker, build-docker-plus] strategy: + fail-fast: false matrix: include: - - image: debian - type: oss - - image: debian-plus - type: plus + - base-os: debian + image: gcr.io/f5-gcs-7899-ptg-ingrss-ctlr/dev/nginx-ic/nginx-ingress + tag: ${{ needs.checks.outputs.build_tag }} + type: oss + - base-os: debian-plus + image: gcr.io/f5-gcs-7899-ptg-ingrss-ctlr/dev/nginx-ic/nginx-plus-ingress + tag: ${{ needs.checks.outputs.build_tag }} + type: plus + permissions: + contents: read + id-token: write steps: - name: Checkout Repository - uses: actions/checkout@v3 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: Authenticate to Google Cloud + id: auth + uses: google-github-actions/auth@6fc4af4b145ae7821d527454aa9bd537d1f2dc5f # v2.1.7 + with: + token_format: access_token + workload_identity_provider: ${{ secrets.GCR_WORKLOAD_IDENTITY }} + service_account: ${{ secrets.GCR_SERVICE_ACCOUNT }} + if: ${{ needs.checks.outputs.forked_workflow == 'false' || needs.checks.outputs.docs_only == 'false' }} + + - name: Login to GCR + uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 + with: + registry: gcr.io + username: oauth2accesstoken + password: ${{ steps.auth.outputs.access_token }} + if: ${{ needs.checks.outputs.forked_workflow == 'false' || needs.checks.outputs.docs_only == 'false' }} + + - name: Check if stable image exists + id: stable_exists + run: | + if docker pull ${{ matrix.image }}:${{ needs.checks.outputs.stable_tag }}; then + echo "exists=true" >> $GITHUB_OUTPUT + fi + if: ${{ needs.checks.outputs.forked_workflow == 'false' || needs.checks.outputs.docs_only == 'false' }} + + - name: Pull build image + run: | + docker pull ${{ matrix.image }}:${{ needs.checks.outputs.build_tag }} + if: ${{ ( needs.checks.outputs.forked_workflow == 'false' || needs.checks.outputs.docs_only == 'false' ) && steps.stable_exists.outputs.exists != 'true' }} + - name: Fetch Cached Artifacts - uses: actions/cache@v3 + uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 with: path: ${{ github.workspace }}/dist - key: nginx-ingress-${{ github.run_id }}-${{ github.run_number }}-single + key: nginx-ingress-${{ needs.checks.outputs.go_code_md5 }} + if: ${{ needs.checks.outputs.forked_workflow == 'true' && needs.checks.outputs.docs_only == 'false' }} + - name: Docker Buildx - uses: docker/setup-buildx-action@v2 - - name: Build Docker Image ${{ matrix.image }} - uses: docker/build-push-action@v3 + uses: docker/setup-buildx-action@6524bf65af31da8d45b59e8c27de4bd072b392f5 # v3.8.0 + if: ${{ needs.checks.outputs.forked_workflow == 'true' && needs.checks.outputs.docs_only == 'false' }} + + - name: Build Docker Image ${{ matrix.base-os }} + uses: docker/build-push-action@48aba3b46d1b1fec4febb7c5d0c644b249a11355 # v6.10.0 with: file: build/Dockerfile - context: '.' - cache-from: type=gha,scope=${{ matrix.image }} - cache-to: type=gha,scope=${{ matrix.image }},mode=max + context: "." + cache-from: type=gha,scope=${{ matrix.base-os }} target: goreleaser - tags: ${{ matrix.type }}:${{ github.sha }} + tags: "${{ matrix.image }}:${{ matrix.tag }}" pull: true load: true build-args: | - BUILD_OS=${{ matrix.image }} + BUILD_OS=${{ matrix.base-os }} IC_VERSION=CI secrets: | - ${{ contains(matrix.type, 'plus') && format('"nginx-repo.crt={0}"', secrets.NGINX_CRT) || '' }} - ${{ contains(matrix.type, 'plus') && format('"nginx-repo.key={0}"', secrets.NGINX_KEY) || '' }} + ${{ matrix.type == 'plus' && format('"nginx-repo.crt={0}"', secrets.NGINX_CRT) || '' }} + ${{ matrix.type == 'plus' && format('"nginx-repo.key={0}"', secrets.NGINX_KEY) || '' }} + if: ${{ needs.checks.outputs.forked_workflow == 'true' && needs.checks.outputs.docs_only == 'false' }} + - name: Deploy Kubernetes id: k8s run: | kind create cluster --name ${{ github.run_id }} --image=kindest/node:v${{ needs.checks.outputs.k8s_latest }} --wait 75s - kind load docker-image ${{ matrix.type }}:${{ github.sha }} --name ${{ github.run_id }} + kind load docker-image "${{ matrix.image }}:${{ matrix.tag }}" --name ${{ github.run_id }} + if: ${{ steps.stable_exists.outputs.exists != 'true' && needs.checks.outputs.docs_only == 'false' }} + + - name: Create Plus Secret + run: kubectl create secret generic license-token --from-literal=license.jwt="${{ secrets.PLUS_JWT }}" --type="nginx.com/license" + if: ${{ matrix.type == 'plus' && steps.stable_exists.outputs.exists != 'true' && needs.checks.outputs.docs_only == 'false' }} + - name: Install Chart run: > helm install ${{ matrix.type }} . - --set controller.image.repository=${{ matrix.type }} - --set controller.image.tag=${{ github.sha }} + --set controller.image.repository=${{ matrix.image }} + --set controller.image.tag=${{ matrix.tag }} --set controller.service.type=NodePort --set controller.nginxplus=${{ contains(matrix.type, 'plus') && 'true' || 'false' }} + --set controller.telemetryReporting.enable=false --wait - working-directory: ${{ github.workspace }}/deployments/helm-chart + working-directory: ${{ github.workspace }}/charts/nginx-ingress + if: ${{ steps.stable_exists.outputs.exists != 'true' && needs.checks.outputs.docs_only == 'false' }} + - name: Expose Test Ingresses run: | - kubectl port-forward service/${{ matrix.type }}-nginx-ingress 8080:80 & - kubectl port-forward service/${{ matrix.type }}-nginx-ingress 8443:443 & + kubectl port-forward service/${{ matrix.type }}-nginx-ingress-controller 8080:80 8443:443 & + if: ${{ steps.stable_exists.outputs.exists != 'true' && needs.checks.outputs.docs_only == 'false' }} + - name: Test HTTP run: | counter=0 @@ -181,271 +500,215 @@ jobs: fi printf '.'; counter=$(($counter+1)); sleep 5; done + if: ${{ steps.stable_exists.outputs.exists != 'true' && needs.checks.outputs.docs_only == 'false' }} + - name: Test HTTPS run: | counter=0 max_attempts=5 - until [ $(curl --write-out %{http_code} -ks --output /dev/null https://localhost:8443) -eq 404 ]; do + until [ $(curl --write-out %{http_code} -ks --output /dev/null https://localhost:8443) -eq 000 ]; do if [ ${counter} -eq ${max_attempts} ]; then exit 1 fi printf '.'; counter=$(($counter+1)); sleep 5; done + if: ${{ steps.stable_exists.outputs.exists != 'true' && needs.checks.outputs.docs_only == 'false' }} setup-matrix: + if: ${{ inputs.force || needs.checks.outputs.docs_only != 'true' }} name: Setup Matrix for Smoke Tests - runs-on: ubuntu-20.04 - needs: [checks, unit-tests] + runs-on: ubuntu-24.04 + needs: [binaries, checks] + permissions: + contents: read + id-token: write outputs: - matrix: ${{ steps.set-matrix.outputs.matrix }} + matrix_oss: ${{ steps.set-matrix.outputs.matrix_oss }} + matrix_plus: ${{ steps.set-matrix.outputs.matrix_plus }} + matrix_nap: ${{ steps.set-matrix.outputs.matrix_nap }} steps: + - name: Checkout Repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - id: set-matrix run: | - if [ "${{ github.event_name }}" != "schedule" ]; then - echo "matrix={\"images\": \ - [{\"image\": \"debian\", \"marker\": \"ingresses\"}, \ - {\"image\": \"alpine\", \"marker\":\"vsr\"}, \ - {\"image\": \"alpine\", \"marker\":\"policies\"}, \ - {\"image\": \"debian\", \"marker\": \"vs\"}, \ - {\"image\": \"ubi\", \"marker\": \"ts\"}, \ - {\"image\": \"debian-plus\", \"marker\": \"vs\"}, \ - {\"image\": \"debian-plus\", \"marker\": \"ts\"}, \ - {\"image\": \"alpine-plus\", \"marker\":\"ingresses\"}, \ - {\"image\": \"alpine-plus\", \"marker\": \"vsr\"}, \ - {\"image\": \"ubi-plus\", \"marker\": \"policies\"}, \ - {\"image\": \"debian-plus-nap\", \"marker\": \"dos\"}, \ - {\"image\": \"debian-plus-nap\", \"marker\": \"appprotect\"}], \ - \"k8s\": [\"${{ needs.checks.outputs.k8s_latest }}\"]}" >> $GITHUB_OUTPUT - else - echo "matrix={\"k8s\": [\"1.21.14\", \"1.22.15\", \"1.23.13\", \"1.24.7\", \"${{ needs.checks.outputs.k8s_latest }}\"], \ - \"images\": [{\"image\": \"debian\"}, {\"image\": \"debian-plus\"}]}" >> $GITHUB_OUTPUT - fi + echo "matrix_oss=$(cat .github/data/matrix-smoke-oss.json | jq -c --arg latest "${{ needs.checks.outputs.k8s_latest }}" '.k8s += [$latest]')" >> $GITHUB_OUTPUT + echo "matrix_plus=$(cat .github/data/matrix-smoke-plus.json | jq -c --arg latest "${{ needs.checks.outputs.k8s_latest }}" '.k8s += [$latest]')" >> $GITHUB_OUTPUT + echo "matrix_nap=$(cat .github/data/matrix-smoke-nap.json | jq -c --arg latest "${{ needs.checks.outputs.k8s_latest }}" '.k8s += [$latest]')" >> $GITHUB_OUTPUT - smoke-tests: - name: Smoke Tests - runs-on: ubuntu-20.04 - needs: setup-matrix - strategy: - fail-fast: false - matrix: ${{ fromJSON(needs.setup-matrix.outputs.matrix) }} - steps: - - name: Checkout Repository - uses: actions/checkout@v3 - - name: Run Smoke Tests - id: smoke-tests - uses: ./.github/actions/smoke-tests - with: - image: ${{ matrix.images.image != '' && matrix.images.image || 'debian' }} - marker: ${{ matrix.images.marker != '' && matrix.images.marker || '' }} - k8s-version: ${{ matrix.k8s }} - nginx-crt: ${{ contains(matrix.images.image, 'nap') && secrets.NGINX_AP_CRT || secrets.NGINX_CRT }} - nginx-key: ${{ contains(matrix.images.image, 'nap') && secrets.NGINX_AP_KEY || secrets.NGINX_KEY }} - - name: Upload Test Results - uses: actions/upload-artifact@v3 - with: - name: ${{ steps.smoke-tests.outputs.test-results-name }} - path: ${{ github.workspace }}/tests/${{ steps.smoke-tests.outputs.test-results-name }}.html - if: always() + - name: Docker Buildx + uses: docker/setup-buildx-action@6524bf65af31da8d45b59e8c27de4bd072b392f5 # v3.8.0 - build-binaries: - name: Build Binaries - runs-on: ubuntu-20.04 - needs: [checks, smoke-tests, helm-tests] - steps: - - name: Checkout Repository - uses: actions/checkout@v3 + - name: Authenticate to Google Cloud + id: auth + uses: google-github-actions/auth@6fc4af4b145ae7821d527454aa9bd537d1f2dc5f # v2.1.7 with: - fetch-depth: 0 - - name: Setup Golang Environment - uses: actions/setup-go@v3 - with: - go-version-file: go.mod - cache: true + token_format: access_token + workload_identity_provider: ${{ secrets.GCR_WORKLOAD_IDENTITY }} + service_account: ${{ secrets.GCR_SERVICE_ACCOUNT }} + if: ${{ needs.checks.outputs.forked_workflow == 'false' && needs.checks.outputs.docs_only == 'false' }} - - uses: actions/setup-node@v3 - - run: npm install js-yaml - continue-on-error: true - if: startsWith(github.ref, 'refs/tags/') - - name: Publish release on tag - uses: actions/github-script@v6 - continue-on-error: true + - name: Login to GCR + uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 with: - github-token: ${{secrets.GITHUB_TOKEN}} - script: | - const ref = context.ref.split("/")[2] - const yaml = require('js-yaml'); - - const releases = (await github.rest.repos.listReleases({ - owner: context.payload.repository.owner.login, - repo: context.payload.repository.name, - per_page: 100, - })).data - - const draft_release = releases.find(release => release.draft && release.tag_name === ref) - - const helm_file = (await github.rest.repos.getContent({ - owner: context.payload.repository.owner.login, - repo: context.payload.repository.name, - path: "deployments/helm-chart/Chart.yaml", - ref: ref, - })).data.content - - const helm_yaml = yaml.load(Buffer.from(helm_file, 'base64').toString()) - const helm_version = helm_yaml.version - console.log(`Helm version: ${helm_version}`) - - const update = await github.rest.repos.updateRelease({ - owner: context.payload.repository.owner.login, - repo: context.payload.repository.name, - release_id: draft_release.id, - body: draft_release.body.replace("%HELM_CHART_VERSION%", helm_version), - draft: false - }); - console.log(`Release published: ${update.data.html_url}`) - console.log(`Release notes: ${update.data.body}`) - if: startsWith(github.ref, 'refs/tags/') - - - name: Download Syft - uses: anchore/sbom-action/download-syft@v0.13.1 + registry: gcr.io + username: oauth2accesstoken + password: ${{ steps.auth.outputs.access_token }} + if: ${{ needs.checks.outputs.forked_workflow == 'false' && needs.checks.outputs.docs_only == 'false' }} - - name: Build binaries - uses: goreleaser/goreleaser-action@v3 - with: - version: latest - args: ${{ startsWith(github.ref, 'refs/tags/') && 'release' || 'build --snapshot' }} ${{ github.event_name == 'pull_request' && '--single-target' || '' }} --rm-dist - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - GOPATH: ${{ needs.checks.outputs.go_path }} - AWS_PRODUCT_CODE: ${{ secrets.AWS_PRODUCT_CODE }} - AWS_PUB_KEY: ${{ secrets.AWS_PUB_KEY }} - AWS_NAP_DOS_PRODUCT_CODE: ${{ secrets.AWS_NAP_DOS_PRODUCT_CODE }} - AWS_NAP_DOS_PUB_KEY: ${{ secrets.AWS_NAP_DOS_PUB_KEY }} - AWS_NAP_WAF_PRODUCT_CODE: ${{ secrets.AWS_NAP_WAF_PRODUCT_CODE }} - AWS_NAP_WAF_PUB_KEY: ${{ secrets.AWS_NAP_WAF_PUB_KEY }} - AWS_NAP_WAF_DOS_PRODUCT_CODE: ${{ secrets.AWS_NAP_WAF_DOS_PRODUCT_CODE }} - AWS_NAP_WAF_DOS_PUB_KEY: ${{ secrets.AWS_NAP_WAF_DOS_PUB_KEY }} - SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_COMMUNITY }} - AZURE_STORAGE_ACCOUNT: ${{ secrets.AZURE_STORAGE_ACCOUNT }} - AZURE_STORAGE_KEY: ${{ secrets.AZURE_STORAGE_KEY }} - AZURE_BUCKET_NAME: ${{ secrets.AZURE_BUCKET_NAME }} + - name: Check if test image exists + id: check-image + run: | + docker pull gcr.io/f5-gcs-7899-ptg-ingrss-ctlr/dev/test-runner:${{ hashFiles('./tests/requirements.txt', './tests/Dockerfile') || 'latest' }} + shell: bash + continue-on-error: true + if: ${{ needs.checks.outputs.forked_workflow == 'false' && needs.checks.outputs.docs_only == 'false' }} - - name: Store Artifacts in Cache - uses: actions/cache@v3 + - name: Build Test-Runner Container + uses: docker/build-push-action@48aba3b46d1b1fec4febb7c5d0c644b249a11355 # v6.10.0 with: - path: ${{ github.workspace }}/dist - key: nginx-ingress-${{ github.run_id }}-${{ github.run_number }}-multi + file: tests/Dockerfile + context: "." + cache-from: type=gha,scope=test-runner + tags: "gcr.io/f5-gcs-7899-ptg-ingrss-ctlr/dev/test-runner:${{ hashFiles('./tests/requirements.txt', './tests/Dockerfile') || 'latest' }}" + pull: true + push: ${{ needs.checks.outputs.forked_workflow == 'false' }} + load: false + if: ${{ steps.check-image.outcome == 'failure' && needs.checks.outputs.docs_only == 'false' }} - build-docker: - name: Build Docker OSS - needs: build-binaries + smoke-tests-oss: + if: ${{ inputs.force || needs.checks.outputs.docs_only != 'true' }} + name: ${{ matrix.images.label }} ${{ matrix.images.image }} ${{ matrix.k8s }} smoke tests + needs: + - checks + - setup-matrix + - build-docker strategy: - fail-fast: false - matrix: - image: [debian, alpine] - platforms: ["linux/arm, linux/arm64, linux/amd64, linux/ppc64le, linux/s390x"] - include: - - image: ubi - platforms: "linux/arm64, linux/amd64, linux/ppc64le, linux/s390x" - uses: ./.github/workflows/build-oss.yml - with: - platforms: ${{ matrix.platforms }} - image: ${{ matrix.image }} + fail-fast: false + matrix: ${{ fromJSON(needs.setup-matrix.outputs.matrix_oss) }} + permissions: + contents: read + id-token: write + uses: ./.github/workflows/setup-smoke.yml secrets: inherit + with: + image: ${{ matrix.images.image }} + target: ${{ matrix.images.target }} + nap-modules: ${{ matrix.images.nap_modules }} + marker: ${{ matrix.images.marker }} + label: ${{ matrix.images.label }} + go-md5: ${{ needs.checks.outputs.go_code_md5 }} + build-tag: ${{ needs.checks.outputs.build_tag }} + stable-tag: ${{ needs.checks.outputs.stable_tag }} + authenticated: ${{ needs.checks.outputs.forked_workflow != 'true' }} + k8s-version: ${{ matrix.k8s }} - build-docker-plus: - name: Build Docker Plus - needs: build-binaries + smoke-tests-plus: + if: ${{ inputs.force || needs.checks.outputs.docs_only != 'true' }} + name: ${{ matrix.images.label }} ${{ matrix.images.image }} ${{ matrix.k8s }} smoke tests + needs: + - checks + - setup-matrix + - build-docker-plus strategy: - fail-fast: false - matrix: - image: [debian-plus, alpine-plus] - platforms: ["linux/arm64, linux/amd64"] - target: [goreleaser, aws] - include: - - image: ubi-plus - platforms: "linux/arm64, linux/amd64, linux/s390x" - target: goreleaser - uses: ./.github/workflows/build-plus.yml - with: - platforms: ${{ matrix.platforms }} - image: ${{ matrix.image }} - target: ${{ matrix.target }} + fail-fast: false + matrix: ${{ fromJSON(needs.setup-matrix.outputs.matrix_plus) }} + permissions: + contents: read + id-token: write + uses: ./.github/workflows/setup-smoke.yml secrets: inherit + with: + image: ${{ matrix.images.image }} + target: ${{ matrix.images.target }} + nap-modules: ${{ matrix.images.nap_modules }} + marker: ${{ matrix.images.marker }} + label: ${{ matrix.images.label }} + go-md5: ${{ needs.checks.outputs.go_code_md5 }} + build-tag: ${{ needs.checks.outputs.build_tag }} + stable-tag: ${{ needs.checks.outputs.stable_tag }} + authenticated: ${{ needs.checks.outputs.forked_workflow != 'true' }} + k8s-version: ${{ matrix.k8s }} - build-docker-nap: - name: Build Docker NAP - needs: build-binaries + smoke-tests-nap: + if: ${{ inputs.force || needs.checks.outputs.docs_only != 'true' }} + name: ${{ matrix.images.label }} ${{ matrix.images.image }} ${{ matrix.k8s }} smoke tests + needs: + - checks + - setup-matrix + - build-docker-nap strategy: - fail-fast: false - matrix: - image: [debian-plus-nap] - platforms: ["linux/amd64"] - target: [goreleaser, aws] - nap_modules: [dos, waf, "waf,dos"] - uses: ./.github/workflows/build-plus.yml + fail-fast: false + matrix: ${{ fromJSON(needs.setup-matrix.outputs.matrix_nap) }} + permissions: + contents: read + id-token: write + uses: ./.github/workflows/setup-smoke.yml + secrets: inherit with: - platforms: ${{ matrix.platforms }} - image: ${{ matrix.image }} - target: ${{ matrix.target }} - nap_modules: ${{ matrix.nap_modules }} + image: ${{ matrix.images.image }} + target: ${{ matrix.images.target }} + nap-modules: ${{ matrix.images.nap_modules }} + marker: ${{ matrix.images.marker }} + label: ${{ matrix.images.label }} + go-md5: ${{ needs.checks.outputs.go_code_md5 }} + build-tag: ${{ needs.checks.outputs.build_tag }} + stable-tag: ${{ needs.checks.outputs.stable_tag }} + authenticated: ${{ needs.checks.outputs.forked_workflow != 'true' }} + k8s-version: ${{ matrix.k8s }} + + tag-stable: + name: Tag tested image as stable + needs: [checks, build-docker, build-docker-plus, build-docker-nap, smoke-tests-oss, smoke-tests-plus, smoke-tests-nap] + permissions: + contents: read # To checkout repository + id-token: write # To sign into Google Container Registry + uses: ./.github/workflows/retag-images.yml + with: + source_tag: ${{ needs.checks.outputs.build_tag }} + target_tag: ${{ needs.checks.outputs.stable_tag }} + dry_run: false secrets: inherit + if: ${{ inputs.force || (needs.checks.outputs.forked_workflow == 'true' && needs.checks.outputs.docs_only == 'false') || (needs.checks.outputs.forked_workflow == 'false' && needs.checks.outputs.stable_image_exists != 'true' && needs.checks.outputs.docs_only == 'false') }} - package-helm: - name: Package Helm Chart - runs-on: ubuntu-20.04 - needs: build-docker - outputs: - version: ${{ steps.var.outputs.helm_version }} - type: ${{ steps.var.outputs.helm_type }} - if: ${{ github.event_name == 'push' }} + final-results: + if: ${{ !cancelled() }} + runs-on: ubuntu-24.04 + name: Final CI Results + needs: [tag-stable, build-docker, build-docker-plus, build-docker-nap, smoke-tests-oss, smoke-tests-plus, smoke-tests-nap] steps: - - name: Checkout Repository - uses: actions/checkout@v3 - - name: Output Variables - id: var - run: | - if ${{ startsWith(github.ref, 'refs/tags/') }}; then - helm_version="$(helm show chart ${{ env.HELM_CHART_DIR }} | grep 'version:' | cut -d ' ' -f 2)" - helm_type="stable" - else - helm_version="0.0.0-edge" - helm_type="edge" + - run: | + tagResult="${{ needs.tag-stable.result }}" + smokeOSSResult="${{ needs.smoke-tests-oss.result }}" + smokePlusResult="${{ needs.smoke-tests-plus.result }}" + smokeNAPResult="${{ needs.smoke-tests-nap.result }}" + if [[ $tagResult != "success" && $tagResult != "skipped" ]]; then + exit 1 fi - echo "helm_version=$helm_version" >> $GITHUB_OUTPUT - echo "helm_type=$helm_type" >> $GITHUB_OUTPUT - - name: Lint - run: helm lint ${{ env.HELM_CHART_DIR }} - - name: Package - run: helm package --version ${{ steps.var.outputs.helm_version }} ${{ env.HELM_CHART_DIR }} - - name: Upload Chart - uses: actions/upload-artifact@v3 - with: - name: helm-chart - path: ${{ github.workspace }}/nginx-ingress-${{ steps.var.outputs.helm_version }}.tgz - - release-helm: - name: Release Helm Chart - runs-on: ubuntu-20.04 - needs: package-helm - if: ${{ github.event_name == 'push' }} - steps: - - name: Checkout Repository - uses: actions/checkout@v3 - with: - repository: nginxinc/helm-charts - fetch-depth: 1 - token: ${{ secrets.NGINX_PAT }} - - name: Remove previous Chart - run: rm -f ${{ github.workspace }}/${{ needs.package-helm.outputs.type }}/nginx-ingress-${{ needs.package-helm.outputs.version }}.tgz - - name: Retrieve latest Helm Chart - uses: actions/download-artifact@v3 - with: - name: helm-chart - path: ${{ github.workspace }}/${{ needs.package-helm.outputs.type }} - - name: Push Helm Chart - run: | - helm repo index ${{ needs.package-helm.outputs.type }} --url https://helm.nginx.com/${{ needs.package-helm.outputs.type }} - git add -A - git -c user.name='${{ env.GIT_NAME }}' -c user.email='${{ env.GIT_MAIL }}' \ - commit -m "NGINX Ingress Controller - Release ${{ needs.package-helm.outputs.type }} ${{ needs.package-helm.outputs.version }}" - git push -u origin master + if [[ $smokeOSSResult != "success" && $smokeOSSResult != "skipped" ]]; then + exit 1 + fi + if [[ $smokePlusResult != "success" && $smokePlusResult != "skipped" ]]; then + exit 1 + fi + if [[ $smokeNAPResult != "success" && $smokeNAPResult != "skipped" ]]; then + exit 1 + fi + + trigger-image-promotion: + name: Promote images on Force Run + needs: + - build-docker + - build-docker-plus + - build-docker-nap + - final-results + permissions: + contents: write # for pushing to Helm Charts repository + id-token: write # To sign into Google Container Registry + actions: read + packages: write # for helm to push to GHCR + security-events: write + pull-requests: write # for scout report + uses: ./.github/workflows/image-promotion.yml + secrets: inherit + if: ${{ inputs.force && inputs.force || false }} diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index ebc5924c66..de64d6d9ad 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -2,63 +2,106 @@ name: "CodeQL" on: push: - branches: [ main, release-* ] + branches: + - main + - release-* pull_request: # The branches below must be a subset of the branches above - branches: [ main ] + branches: + - main + merge_group: schedule: - - cron: '36 6 * * 4' + - cron: "36 6 * * 4" # run every Thursday at 06:36 UTC concurrency: group: ${{ github.ref_name }}-codeql cancel-in-progress: true -permissions: # added using https://github.com/step-security/secure-workflows +permissions: contents: read jobs: + checks: + name: Checks and variables + runs-on: ubuntu-24.04 + outputs: + docs_only: ${{ github.event.pull_request && steps.docs.outputs.docs_only == 'true' }} + steps: + - name: Checkout Repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + fetch-depth: 0 + + - name: Filter only docs changes + id: docs + run: | + files=$(git diff --name-only HEAD^ | egrep -v "^site/" | egrep -v "^examples/" | egrep -v "^README.md") + if [ -z "$files" ]; then + echo "docs_only=true" >> $GITHUB_OUTPUT + else + echo "docs_only=false" >> $GITHUB_OUTPUT + fi + echo $files + cat $GITHUB_OUTPUT + shell: bash --noprofile --norc -o pipefail {0} + analyze: + if: ${{ needs.checks.outputs.docs_only != 'true' }} + needs: [checks] permissions: - actions: read # for github/codeql-action/init to get workflow details - contents: read # for actions/checkout to fetch code - security-events: write # for github/codeql-action/autobuild to send a status report + actions: read # for github/codeql-action/init to get workflow details + contents: read # for actions/checkout to fetch code + security-events: write # for github/codeql-action/autobuild to send a status report name: Analyze - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 strategy: fail-fast: false matrix: - language: [ 'go', 'python' ] + language: ["go", "python"] + # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby', 'swift' ] + # Use only 'java' to analyze code written in Java, Kotlin or both + # Use only 'javascript' to analyze code written in JavaScript, TypeScript or both + # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support steps: - - name: Checkout repository - uses: actions/checkout@v3 - - # Initializes the CodeQL tools for scanning. - - name: Initialize CodeQL - uses: github/codeql-action/init@v2 - with: - languages: ${{ matrix.language }} - # If you wish to specify custom queries, you can do so here or in a config file. - # By default, queries listed here will override any specified in a config file. - # Prefix the list here with "+" to use these queries and those in the config file. - # queries: ./path/to/local/query, your-org/your-repo/queries@main - - # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). - # If this step fails, then you should remove it and run the build manually (see below) - - name: Autobuild - uses: github/codeql-action/autobuild@v2 - - # ℹ️ Command-line programs to run using the OS shell. - # 📚 https://git.io/JvXDl - - # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines - # and modify them (or add more) to build your code if your project - # uses a compiled language - - #- run: | - # make bootstrap - # make release - - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@48ab28a6f5dbc2a99bf1e0131198dd8f1df78169 # v3.28.0 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + + # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs + # queries: security-extended,security-and-quality + + - name: Setup Golang Environment + uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0 + with: + go-version-file: go.mod + if: matrix.language == 'go' + + # Autobuild attempts to build any compiled languages (C/C++, C#, Go, Java, or Swift). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@48ab28a6f5dbc2a99bf1e0131198dd8f1df78169 # v3.28.0 + + # ℹ️ Command-line programs to run using the OS shell. + # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun + + # If the Autobuild fails above, remove it and uncomment the following three lines. + # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. + + # - run: | + # echo "Run, Build Application using script" + # ./location_of_script_within_repo/buildscript.sh + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@48ab28a6f5dbc2a99bf1e0131198dd8f1df78169 # v3.28.0 + with: + category: "/language:${{matrix.language}}" diff --git a/.github/workflows/create-release-branch.yml b/.github/workflows/create-release-branch.yml new file mode 100644 index 0000000000..aa46d82644 --- /dev/null +++ b/.github/workflows/create-release-branch.yml @@ -0,0 +1,69 @@ +name: "Create release branch" + +on: + workflow_dispatch: + inputs: + release_version: + required: true + type: string + default: '0.0' + source_branch: + required: false + type: string + default: 'main' + branch_prefix: + required: false + type: string + default: 'release-' + update: + type: boolean + default: false + dry_run: + type: boolean + default: false + + +defaults: + run: + shell: bash + +permissions: + contents: read + +jobs: + create: + name: Create release branch + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Checkout NIC repo + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + ref: ${{ inputs.source_branch }} + + - name: Create new release branch + run: | + branch="${{ inputs.branch_prefix }}${{ inputs.release_version }}" + if git rev-parse --verify remotes/origin/${branch}; then + git checkout ${branch} + git pull + if ${{ inputs.update }}; then + echo "Updating from ${{ inputs.source_branch }}." + git merge -Xtheirs ${{ inputs.source_branch }} -m "chore: Merge branch ${{ inputs.source_branch }} into ${branch}" + else + echo "UPDATE not requested. Not making any changes" + fi + else + git checkout -b ${branch} + fi + + echo "Pushing to branch $branch" + if ! ${{ inputs.dry_run }}; then + git push origin "${branch}" + else + echo "DRY RUN not making any changes" + git push --dry-run origin "${branch}" + fi + env: + GITHUB_TOKEN: ${{ secrets.NGINX_PAT }} diff --git a/.github/workflows/dependabot-auto-merge.yml b/.github/workflows/dependabot-auto-merge.yml new file mode 100644 index 0000000000..86948ba61c --- /dev/null +++ b/.github/workflows/dependabot-auto-merge.yml @@ -0,0 +1,23 @@ +name: Dependabot auto-merge +on: pull_request_target + +permissions: + contents: read + +jobs: + dependabot: + runs-on: ubuntu-24.04 + if: ${{ github.event.pull_request.user.login == 'dependabot[bot]' }} + permissions: + pull-requests: write + contents: write + steps: + - name: Dependabot metadata + id: dependabot-metadata + uses: dependabot/fetch-metadata@dbb049abf0d677abbd7f7eee0375145b417fdd34 # v2.2.0 + + - name: Enable auto-merge for Dependabot PRs + run: gh pr merge --auto --squash "$PR_URL" + env: + PR_URL: ${{github.event.pull_request.html_url}} + GITHUB_TOKEN: ${{ secrets.NGINX_PAT }} diff --git a/.github/workflows/dependabot-hugo.yml b/.github/workflows/dependabot-hugo.yml new file mode 100644 index 0000000000..834cfffc2a --- /dev/null +++ b/.github/workflows/dependabot-hugo.yml @@ -0,0 +1,51 @@ +name: Run hugo commands on Dependabot PRs + +on: + pull_request: + paths: + - "site/go.mod" + merge_group: + +permissions: + contents: read + +defaults: + run: + shell: bash + +jobs: + build: + if: ${{ github.event.pull_request.user.login == 'dependabot[bot]' }} + runs-on: ubuntu-24.04 + permissions: + contents: write + pull-requests: read + steps: + - name: Fetch Dependabot metadata + id: dependabot-metadata + uses: dependabot/fetch-metadata@dbb049abf0d677abbd7f7eee0375145b417fdd34 # v2.2.0 + + - name: Checkout Repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + if: ${{ steps.dependabot-metadata.outputs.package-ecosystem == 'go_modules' && contains(steps.dependabot-metadata.outputs.dependency-names, 'hugo') }} + with: + ref: ${{ github.head_ref }} + token: ${{ secrets.NGINX_PAT }} + + - name: Setup Hugo + uses: peaceiris/actions-hugo@75d2e84710de30f6ff7268e08f310b60ef14033f # v3.0.0 + if: ${{ steps.dependabot-metadata.outputs.package-ecosystem == 'go_modules' && contains(steps.dependabot-metadata.outputs.dependency-names, 'hugo') }} + + - name: Run build + if: ${{ steps.dependabot-metadata.outputs.package-ecosystem == 'go_modules' && contains(steps.dependabot-metadata.outputs.dependency-names, 'hugo') }} + working-directory: site + run: | + hugo mod tidy + hugo mod verify + + - name: Commit changes + if: ${{ steps.dependabot-metadata.outputs.package-ecosystem == 'go_modules' && contains(steps.dependabot-metadata.outputs.dependency-names, 'hugo') }} + id: commit + uses: stefanzweifel/git-auto-commit-action@8621497c8c39c72f3e2a999a26b4ca1b5058a842 # v5.0.1 + with: + commit_message: "Update docs go.mod" diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml new file mode 100644 index 0000000000..a16c0d2f0d --- /dev/null +++ b/.github/workflows/dependency-review.yml @@ -0,0 +1,31 @@ +name: "Dependency Review" +on: + pull_request: + branches: + - main + - release-* + merge_group: + +concurrency: + group: ${{ github.ref_name }}-deps-review + cancel-in-progress: true + +permissions: + contents: read + +jobs: + dependency-review: + runs-on: ubuntu-24.04 + permissions: + contents: read # for actions/checkout + pull-requests: write # for actions/dependency-review-action to post comments + steps: + - name: "Checkout Repository" + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: "Dependency Review" + uses: actions/dependency-review-action@3b139cfc5fae8b618d3eae3675e383bb1769c019 # v4.5.0 + with: + config-file: "nginxinc/k8s-common/dependency-review-config.yml@main" + base-ref: ${{ github.event.pull_request.base.sha || github.event.repository.default_branch }} + head-ref: ${{ github.event.pull_request.base.sha || github.ref }} diff --git a/.github/workflows/dockerhub-description.yml b/.github/workflows/dockerhub-description.yml index 111dadfbae..6522cdaac9 100644 --- a/.github/workflows/dockerhub-description.yml +++ b/.github/workflows/dockerhub-description.yml @@ -11,20 +11,22 @@ concurrency: group: ${{ github.ref_name }}-dockerhub cancel-in-progress: true +permissions: + contents: read jobs: dockerHubDescription: - runs-on: ubuntu-20.04 + runs-on: ubuntu-24.04 if: ${{ github.event.repository.fork == false }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Modify readme for DockerHub run: | sed -i '3,4d' README.md - name: Docker Hub Description - uses: peter-evans/dockerhub-description@v3 + uses: peter-evans/dockerhub-description@e98e4d1628a5f3be2be7c231e50981aee98723ae # v4.0.0 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} diff --git a/.github/workflows/docs-build-push.yml b/.github/workflows/docs-build-push.yml new file mode 100644 index 0000000000..215fe5141e --- /dev/null +++ b/.github/workflows/docs-build-push.yml @@ -0,0 +1,51 @@ +name: Build and deploy docs +on: + workflow_dispatch: + inputs: + environment: + description: "Environment to deploy to" + required: true + default: "preview" + type: choice + options: + - preview + - dev + - staging + - prod + hugo_theme_override: + description: 'Override hugo theme (leave blank to use latest version)' + required: false + default: '' + type: string + workflow_call: + inputs: + environment: + description: "Environment to deploy to" + required: true + type: string + pull_request: + branches: + - "*" + paths: + - "site/**" + +permissions: + contents: read + +jobs: + call-docs-build-push: + uses: nginxinc/docs-actions/.github/workflows/docs-build-push.yml@9c59fab05a8131f4d691ba6ea2b6a119f3ef832a # v1.0.7 + permissions: + pull-requests: write # needed to write preview url comment to PR + contents: read + with: + production_url_path: "/nginx-ingress-controller" + preview_url_path: "/previews/nginx-ingress-controller" + docs_source_path: "public/nginx-ingress-controller" + docs_build_path: "./site" + doc_type: "hugo" + environment: ${{ inputs.environment }} + force_hugo_theme_version: ${{inputs.hugo_theme_override}} + secrets: + AZURE_CREDENTIALS: ${{ secrets.AZURE_CREDENTIALS_DOCS }} + AZURE_KEY_VAULT: ${{ secrets.AZURE_KEY_VAULT_DOCS }} diff --git a/.github/workflows/f5-cla.yml b/.github/workflows/f5-cla.yml new file mode 100644 index 0000000000..874ad62c14 --- /dev/null +++ b/.github/workflows/f5-cla.yml @@ -0,0 +1,51 @@ +name: F5 CLA + +on: + issue_comment: + types: + - created + pull_request_target: + types: + - opened + - synchronize + - reopened + +concurrency: + group: ${{ github.ref_name }}-cla + +permissions: + contents: read + +jobs: + f5-cla: + name: F5 CLA + runs-on: ubuntu-22.04 + permissions: + actions: write + contents: read + pull-requests: write + statuses: write + steps: + - name: Run F5 Contributor License Agreement (CLA) assistant + if: (github.event.comment.body == 'recheck' || github.event.comment.body == 'I have hereby read the F5 CLA and agree to its terms') || github.event_name == 'pull_request_target' + uses: contributor-assistant/github-action@ca4a40a7d1004f18d9960b404b97e5f30a505a08 # v2.6.1 + with: + # Any pull request targeting the following branch will trigger a CLA check. + branch: "main" + # Path to the CLA document. + path-to-document: "https://github.com/f5/.github/blob/main/CLA/cla-markdown.md" + # Custom CLA messages. + custom-notsigned-prcomment: "🎉 Thank you for your contribution! It appears you have not yet signed the F5 Contributor License Agreement (CLA), which is required for your changes to be incorporated into an F5 Open Source Software (OSS) project. Please kindly read the [F5 CLA](https://github.com/f5/.github/blob/main/CLA/cla-markdown.md) and reply on a new comment with the following text to agree:" + custom-pr-sign-comment: "I have hereby read the F5 CLA and agree to its terms" + custom-allsigned-prcomment: "✅ All required contributors have signed the F5 CLA for this PR. Thank you!" + # Remote repository storing CLA signatures. + remote-organization-name: "f5" + remote-repository-name: "f5-cla-data" + path-to-signatures: "signatures/beta/signatures.json" + # Comma separated list of usernames for maintainers or any other individuals who should not be prompted for a CLA. + allowlist: bot* + # Do not lock PRs after a merge. + lock-pullrequest-aftermerge: false + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PERSONAL_ACCESS_TOKEN: ${{ secrets.F5_CLA_TOKEN }} diff --git a/.github/workflows/fossa.yml b/.github/workflows/fossa.yml index 8e2bd2b0a7..dfaf08e4d0 100644 --- a/.github/workflows/fossa.yml +++ b/.github/workflows/fossa.yml @@ -5,27 +5,27 @@ on: branches: - main paths-ignore: - - 'docs/**' - - 'examples/**' - - '**.md' + - "site/**" + - "examples/**" + - "**.md" concurrency: group: ${{ github.ref_name }}-fossa cancel-in-progress: true -permissions: # added using https://github.com/step-security/secure-workflows +permissions: contents: read jobs: - scan: name: Fossa - runs-on: ubuntu-20.04 + runs-on: ubuntu-24.04 if: ${{ github.event.repository.fork == false }} steps: - name: Checkout Repository - uses: actions/checkout@v3 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: Scan - uses: fossas/fossa-action@v1 + uses: fossas/fossa-action@09bcf127dc0ccb4b5a023f6f906728878e8610ba # v1.4.0 with: api-key: ${{ secrets.FOSSA_TOKEN }} diff --git a/.github/workflows/image-promotion.yml b/.github/workflows/image-promotion.yml new file mode 100644 index 0000000000..3cbbe1e0d1 --- /dev/null +++ b/.github/workflows/image-promotion.yml @@ -0,0 +1,692 @@ +name: Image Promotion +# This workflow will: +# - build images for forked workflows +# - tag stable for forked workflows +# - tag edge for main workflows +# - tag release branch name for release branch workflows +# - release edge images & helm charts for edge +# - run Trivy & dockerscout scans for main & release branch images +# & upload results to Github security & Github Artifacts + +on: + push: + branches: + - main + - release-* + workflow_call: + +defaults: + run: + shell: bash + +concurrency: + group: ${{ github.ref_name }}-image-promotion + cancel-in-progress: true + +permissions: + contents: read + +jobs: + checks: + name: Checks and variables + runs-on: ubuntu-24.04 + permissions: + contents: read + id-token: write + outputs: + go_path: ${{ steps.vars.outputs.go_path }} + go_code_md5: ${{ steps.vars.outputs.go_code_md5 }} + binary_cache_hit: ${{ steps.binary-cache.outputs.cache-hit }} + chart_version: ${{ steps.vars.outputs.chart_version }} + ic_version: ${{ steps.vars.outputs.ic_version }} + docker_md5: ${{ steps.vars.outputs.docker_md5 }} + build_tag: ${{ steps.vars.outputs.build_tag }} + stable_tag: ${{ steps.vars.outputs.stable_tag }} + stable_image_exists: ${{ steps.stable_exists.outputs.exists }} + image_matrix_oss: ${{ steps.vars.outputs.image_matrix_oss }} + image_matrix_plus: ${{ steps.vars.outputs.image_matrix_plus }} + image_matrix_nap: ${{ steps.vars.outputs.image_matrix_nap }} + steps: + - name: Checkout Repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: Setup Golang Environment + uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0 + with: + go-version-file: go.mod + + - name: Set Variables + id: vars + run: | + echo "go_path=$(go env GOPATH)" >> $GITHUB_OUTPUT + source .github/data/version.txt + echo "ic_version=${IC_VERSION}" >> $GITHUB_OUTPUT + echo "chart_version=${HELM_CHART_VERSION}" >> $GITHUB_OUTPUT + ./.github/scripts/variables.sh go_code_md5 >> $GITHUB_OUTPUT + ./.github/scripts/variables.sh docker_md5 >> $GITHUB_OUTPUT + ./.github/scripts/variables.sh build_tag >> $GITHUB_OUTPUT + ./.github/scripts/variables.sh stable_tag >> $GITHUB_OUTPUT + echo "image_matrix_oss=$(cat .github/data/matrix-images-oss.json | jq -c)" >> $GITHUB_OUTPUT + echo "image_matrix_plus=$(cat .github/data/matrix-images-plus.json | jq -c)" >> $GITHUB_OUTPUT + echo "image_matrix_nap=$(cat .github/data/matrix-images-nap.json | jq -c)" >> $GITHUB_OUTPUT + + - name: Fetch Cached Binary Artifacts + id: binary-cache + uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 + with: + path: ${{ github.workspace }}/dist + key: nginx-ingress-${{ steps.vars.outputs.go_code_md5 }} + lookup-only: true + + - name: Authenticate to Google Cloud + id: auth + uses: google-github-actions/auth@6fc4af4b145ae7821d527454aa9bd537d1f2dc5f # v2.1.7 + with: + token_format: access_token + workload_identity_provider: ${{ secrets.GCR_WORKLOAD_IDENTITY }} + service_account: ${{ secrets.GCR_SERVICE_ACCOUNT }} + + - name: Login to GCR + uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 + with: + registry: gcr.io + username: oauth2accesstoken + password: ${{ steps.auth.outputs.access_token }} + + - name: Check if stable image exists + id: stable_exists + run: | + if docker pull gcr.io/f5-gcs-7899-ptg-ingrss-ctlr/dev/nginx-ic/nginx-ingress:${{ steps.vars.outputs.stable_tag }}; then + echo "exists=true" >> $GITHUB_OUTPUT + fi + + - name: Output variables + run: | + echo go_code_md5: ${{ steps.vars.outputs.go_code_md5 }} + echo go_path: ${{ steps.vars.outputs.go_path }} + echo binary_cache_hit: ${{ steps.binary-cache.outputs.cache-hit }} + echo chart_version: ${{ steps.vars.outputs.chart_version }} + echo ic_version: ${{ steps.vars.outputs.ic_version }} + echo docker_md5: ${{ steps.vars.outputs.docker_md5 }} + echo build_tag: ${{ steps.vars.outputs.build_tag }} + echo stable_tag: ${{ steps.vars.outputs.stable_tag }} + echo stable_image_exists: ${{ steps.stable_exists.outputs.exists }} + + govulncheck: + name: Run govulncheck + runs-on: ubuntu-24.04 + permissions: + contents: read + security-events: write + steps: + - name: Checkout Repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: Setup Golang Environment + uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0 + with: + go-version-file: go.mod + + - name: govulncheck + uses: golang/govulncheck-action@b625fbe08f3bccbe446d94fbf87fcc875a4f50ee # v1.0.4 + with: + output-format: sarif + output-file: govulncheck.sarif + + - name: Check SARIF file + id: check-sarif + run: | + if [ -s govulncheck.sarif ] && grep -q '"results":' govulncheck.sarif; then + echo "sarif_has_results=true" >> $GITHUB_OUTPUT + else + echo "sarif_has_results=false" >> $GITHUB_OUTPUT + fi + + - name: Upload SARIF file + uses: github/codeql-action/upload-sarif@48ab28a6f5dbc2a99bf1e0131198dd8f1df78169 # v3.28.0 + if: steps.check-sarif.outputs.sarif_has_results == 'true' + with: + sarif_file: govulncheck.sarif + + binaries: + name: Build Binaries + runs-on: ubuntu-24.04 + needs: [checks] + permissions: + contents: read + steps: + - name: Checkout Repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: Setup Golang Environment + uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0 + with: + go-version-file: go.mod + if: ${{ needs.checks.outputs.binary_cache_hit != 'true' }} + + - name: Build binaries + uses: goreleaser/goreleaser-action@9ed2f89a662bf1735a48bc8557fd212fa902bebf # v6.1.0 + with: + version: latest + args: build --snapshot --clean + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GOPATH: ${{ needs.checks.outputs.go_path }} + AWS_PRODUCT_CODE: ${{ secrets.AWS_PRODUCT_CODE }} + AWS_PUB_KEY: ${{ secrets.AWS_PUB_KEY }} + AWS_NAP_DOS_PRODUCT_CODE: ${{ secrets.AWS_NAP_DOS_PRODUCT_CODE }} + AWS_NAP_DOS_PUB_KEY: ${{ secrets.AWS_NAP_DOS_PUB_KEY }} + AWS_NAP_WAF_PRODUCT_CODE: ${{ secrets.AWS_NAP_WAF_PRODUCT_CODE }} + AWS_NAP_WAF_PUB_KEY: ${{ secrets.AWS_NAP_WAF_PUB_KEY }} + AWS_NAP_WAF_DOS_PRODUCT_CODE: ${{ secrets.AWS_NAP_WAF_DOS_PRODUCT_CODE }} + AWS_NAP_WAF_DOS_PUB_KEY: ${{ secrets.AWS_NAP_WAF_DOS_PUB_KEY }} + GORELEASER_CURRENT_TAG: "v${{ needs.checks.outputs.ic_version }}" + if: ${{ needs.checks.outputs.binary_cache_hit != 'true' }} + + - name: Store Artifacts in Cache + uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 + with: + path: ${{ github.workspace }}/dist + key: nginx-ingress-${{ needs.checks.outputs.go_code_md5 }} + if: ${{ needs.checks.outputs.binary_cache_hit != 'true' }} + + build-docker: + if: ${{ needs.checks.outputs.stable_image_exists != 'true' }} + name: Build Docker OSS + needs: [checks, binaries] + strategy: + fail-fast: false + matrix: ${{ fromJSON( needs.checks.outputs.image_matrix_oss ) }} + uses: ./.github/workflows/build-oss.yml + with: + platforms: ${{ matrix.platforms }} + image: ${{ matrix.image }} + go-md5: ${{ needs.checks.outputs.go_code_md5 }} + base-image-md5: ${{ needs.checks.outputs.docker_md5 }} + authenticated: true + tag: ${{ needs.checks.outputs.build_tag }} + branch: ${{ github.ref }} + ic-version: ${{ needs.checks.outputs.ic_version }} + permissions: + contents: read + actions: read + security-events: write + id-token: write + packages: write + pull-requests: write # for scout report + secrets: inherit + + build-docker-plus: + if: ${{ needs.checks.outputs.stable_image_exists != 'true' }} + name: Build Docker Plus + needs: [checks, binaries] + strategy: + fail-fast: false + matrix: ${{ fromJSON( needs.checks.outputs.image_matrix_plus ) }} + uses: ./.github/workflows/build-plus.yml + with: + platforms: ${{ matrix.platforms }} + image: ${{ matrix.image }} + target: ${{ matrix.target }} + go-md5: ${{ needs.checks.outputs.go_code_md5 }} + base-image-md5: ${{ needs.checks.outputs.docker_md5 }} + authenticated: true + tag: ${{ needs.checks.outputs.build_tag }} + branch: ${{ github.ref }} + ic-version: ${{ needs.checks.outputs.ic_version }} + permissions: + contents: read + actions: read + security-events: write + id-token: write + packages: write + pull-requests: write # for scout report + secrets: inherit + + build-docker-nap: + if: ${{ needs.checks.outputs.stable_image_exists != 'true' }} + name: Build Docker NAP + needs: [checks, binaries] + strategy: + fail-fast: false + matrix: ${{ fromJSON( needs.checks.outputs.image_matrix_nap ) }} + uses: ./.github/workflows/build-plus.yml + with: + platforms: ${{ matrix.platforms }} + image: ${{ matrix.image }} + target: ${{ matrix.target }} + go-md5: ${{ needs.checks.outputs.go_code_md5 }} + base-image-md5: ${{ needs.checks.outputs.docker_md5 }} + nap-modules: ${{ matrix.nap_modules }} + authenticated: true + tag: ${{ needs.checks.outputs.build_tag }} + branch: ${{ github.ref }} + ic-version: ${{ needs.checks.outputs.ic_version }} + permissions: + contents: read + actions: read + security-events: write + id-token: write + packages: write + pull-requests: write # for scout report + secrets: inherit + + tag-stable: + if: ${{ needs.checks.outputs.stable_image_exists != 'true' }} + name: Tag build image as stable + needs: [checks, build-docker, build-docker-plus, build-docker-nap] + permissions: + contents: read # To checkout repository + id-token: write # To sign into Google Container Registry + uses: ./.github/workflows/retag-images.yml + with: + source_tag: ${{ needs.checks.outputs.build_tag }} + target_tag: ${{ needs.checks.outputs.stable_tag }} + dry_run: false + secrets: inherit + + tag-candidate: + # pushes edge or release images to gcr/dev + # for main: this keeps a copy of edge in gcr/dev + # for release-*: this stages a release candidate in gcr/dev which can be used for release promotion + name: Tag tested image as stable + needs: + - checks + - build-docker + - build-docker-plus + - build-docker-nap + - tag-stable + permissions: + contents: read # To checkout repository + id-token: write # To sign into Google Container Registry + uses: ./.github/workflows/retag-images.yml + with: + source_tag: ${{ needs.checks.outputs.stable_tag }} + target_tag: ${{ github.ref_name == github.event.repository.default_branch && 'edge' || github.ref_name }} + dry_run: false + secrets: inherit + if: ${{ !cancelled() && !failure() }} + + release-oss: + # pushes edge images to docker hub + if: ${{ !cancelled() && !failure() && github.ref_name == github.event.repository.default_branch }} + name: Release Docker OSS + needs: [checks, build-docker] + uses: ./.github/workflows/oss-release.yml + with: + gcr_release_registry: false + ecr_public_registry: true + dockerhub_public_registry: true + quay_public_registry: true + github_public_registry: true + source_tag: ${{ needs.checks.outputs.stable_tag }} + target_tag: "edge" + branch: ${{ github.ref_name }} + dry_run: false + permissions: + contents: read + id-token: write + packages: write + secrets: inherit + + release-plus: + # pushes plus edge images to nginx registry + if: ${{ !cancelled() && !failure() && github.ref_name == github.event.repository.default_branch }} + name: Release Docker Plus + needs: [checks, build-docker-plus, build-docker-nap] + uses: ./.github/workflows/plus-release.yml + with: + nginx_registry: true + gcr_release_registry: false + gcr_mktpl_registry: false + ecr_mktpl_registry: false + az_mktpl_registry: false + source_tag: ${{ needs.checks.outputs.stable_tag }} + target_tag: "edge" + branch: ${{ github.ref_name }} + dry_run: false + permissions: + contents: read + id-token: write + secrets: inherit + + publish-helm-chart: + if: ${{ !cancelled() && !failure() && github.ref_name == github.event.repository.default_branch }} + name: Publish Helm Chart + needs: [checks] + uses: ./.github/workflows/publish-helm.yml + with: + branch: ${{ github.ref_name }} + ic_version: edge + chart_version: 0.0.0-edge + nginx_helm_repo: false + permissions: + contents: write # for pushing to Helm Charts repository + packages: write # for helm to push to GHCR + secrets: inherit + + certify-openshift-images: + if: ${{ !cancelled() && !failure() && github.ref_name == github.event.repository.default_branch }} + name: Certify OpenShift UBI images + runs-on: ubuntu-24.04 + needs: [release-oss] + steps: + - name: Checkout Repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: Certify UBI OSS images in quay + uses: ./.github/actions/certify-openshift-image + continue-on-error: true + with: + image: quay.io/nginx/nginx-ingress:edge-ubi + project_id: ${{ secrets.CERTIFICATION_PROJECT_ID }} + pyxis_token: ${{ secrets.PYXIS_API_TOKEN }} + preflight_version: 1.11.1 + + scan-docker-oss: + name: Scan ${{ matrix.image }}-${{ matrix.target }} + runs-on: ubuntu-24.04 + needs: [checks, tag-candidate] + permissions: + contents: read + id-token: write + security-events: write + if: ${{ !cancelled() && !failure() }} + strategy: + fail-fast: false + matrix: ${{ fromJSON( needs.checks.outputs.image_matrix_oss ) }} + steps: + - name: Checkout Repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: Make directory for security scan results + id: directory + run: | + directory=${{ matrix.image }}-${{ matrix.target }}-results + echo "directory=${directory}" >> $GITHUB_OUTPUT + mkdir -p "${directory}" + + - name: Docker meta + id: meta + uses: docker/metadata-action@369eb591f429131d6889c46b94e711f089e6ca96 # v5.6.1 + with: + context: workflow + images: | + name=gcr.io/f5-gcs-7899-ptg-ingrss-ctlr/dev/nginx-ic/nginx-ingress + flavor: | + suffix=${{ contains(matrix.image, 'ubi') && '-ubi' || '' }}${{ contains(matrix.image, 'alpine') && '-alpine' || '' }} + tags: | + type=raw,value=${{ github.ref_name == github.event.repository.default_branch && 'edge' || github.ref_name }} + + - name: Authenticate to Google Cloud + id: auth + uses: google-github-actions/auth@6fc4af4b145ae7821d527454aa9bd537d1f2dc5f # v2.1.7 + with: + token_format: access_token + workload_identity_provider: ${{ secrets.GCR_WORKLOAD_IDENTITY }} + service_account: ${{ secrets.GCR_SERVICE_ACCOUNT }} + + - name: Login to GCR + uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 + with: + registry: gcr.io + username: oauth2accesstoken + password: ${{ steps.auth.outputs.access_token }} + + # - name: Run Trivy vulnerability scanner + # uses: aquasecurity/trivy-action@6e7b7d1fd3e4fef0c5fa8cce1229c54b2c9bd0d8 # 0.24.0 + # continue-on-error: true + # with: + # image-ref: ${{ steps.meta.outputs.tags }} + # format: "sarif" + # output: "${{ steps.directory.outputs.directory }}/trivy.sarif" + # ignore-unfixed: "true" + + - name: DockerHub Login for Docker Scout + uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + + - name: Run Docker Scout vulnerability scanner + id: docker-scout + uses: docker/scout-action@b23590dc1e4d09febc00cfcbc51e9e8c0f7ee9f3 # v1.16.1 + with: + command: cves + image: ${{ steps.meta.outputs.tags }} + ignore-base: true + sarif-file: "${{ steps.directory.outputs.directory }}/scout.sarif" + write-comment: false + github-token: ${{ secrets.GITHUB_TOKEN }} # to be able to write the comment + summary: true + + - name: Upload Scan Results to Github Artifacts + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + with: + name: "${{ github.ref_name }}-${{ steps.directory.outputs.directory }}" + path: "${{ steps.directory.outputs.directory }}/" + overwrite: true + + - name: Upload Scan results to GitHub Security tab + uses: github/codeql-action/upload-sarif@48ab28a6f5dbc2a99bf1e0131198dd8f1df78169 # v3.28.0 + with: + sarif_file: "${{ steps.directory.outputs.directory }}/" + + scan-docker-plus: + name: Scan ${{ matrix.image }}-${{ matrix.target }} + runs-on: ubuntu-24.04 + needs: [checks, tag-candidate] + permissions: + contents: read + id-token: write + security-events: write + if: ${{ !cancelled() && !failure() }} + strategy: + fail-fast: false + matrix: ${{ fromJSON( needs.checks.outputs.image_matrix_plus ) }} + steps: + - name: Checkout Repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: Make directory for security scan results + id: directory + run: | + directory=${{ matrix.image }}-${{ matrix.target }}-results + echo "directory=${directory}" >> $GITHUB_OUTPUT + mkdir -p "${directory}" + + - name: Docker meta + id: meta + uses: docker/metadata-action@369eb591f429131d6889c46b94e711f089e6ca96 # v5.6.1 + with: + context: workflow + images: | + name=gcr.io/f5-gcs-7899-ptg-ingrss-ctlr/dev/nginx-ic/nginx-plus-ingress + flavor: | + suffix=${{ contains(matrix.image, 'ubi') && '-ubi' || '' }}${{ contains(matrix.image, 'alpine') && '-alpine' || '' }}${{ contains(matrix.target, 'aws') && '-mktpl' || '' }}${{ contains(matrix.image, 'fips') && '-fips' || ''}} + tags: | + type=raw,value=${{ github.ref_name == github.event.repository.default_branch && 'edge' || github.ref_name }} + + - name: Authenticate to Google Cloud + id: auth + uses: google-github-actions/auth@6fc4af4b145ae7821d527454aa9bd537d1f2dc5f # v2.1.7 + with: + token_format: access_token + workload_identity_provider: ${{ secrets.GCR_WORKLOAD_IDENTITY }} + service_account: ${{ secrets.GCR_SERVICE_ACCOUNT }} + + - name: Login to GCR + uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 + with: + registry: gcr.io + username: oauth2accesstoken + password: ${{ steps.auth.outputs.access_token }} + + # - name: Run Trivy vulnerability scanner + # uses: aquasecurity/trivy-action@6e7b7d1fd3e4fef0c5fa8cce1229c54b2c9bd0d8 # 0.24.0 + # continue-on-error: true + # with: + # image-ref: ${{ steps.meta.outputs.tags }} + # format: "sarif" + # output: "${{ steps.directory.outputs.directory }}/trivy.sarif" + # ignore-unfixed: "true" + + - name: DockerHub Login for Docker Scout + uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + + - name: Run Docker Scout vulnerability scanner + id: docker-scout + uses: docker/scout-action@b23590dc1e4d09febc00cfcbc51e9e8c0f7ee9f3 # v1.16.1 + with: + command: cves + image: ${{ steps.meta.outputs.tags }} + ignore-base: true + sarif-file: "${{ steps.directory.outputs.directory }}/scout.sarif" + write-comment: false + github-token: ${{ secrets.GITHUB_TOKEN }} # to be able to write the comment + summary: true + + - name: Upload Scan Results to Github Artifacts + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + with: + name: "${{ github.ref_name }}-${{ steps.directory.outputs.directory }}" + path: "${{ steps.directory.outputs.directory }}/" + overwrite: true + + - name: Upload Scan results to GitHub Security tab + uses: github/codeql-action/upload-sarif@48ab28a6f5dbc2a99bf1e0131198dd8f1df78169 # v3.28.0 + with: + sarif_file: "${{ steps.directory.outputs.directory }}/" + + scan-docker-nap: + name: Scan ${{ matrix.image }}-${{ matrix.target }}-${{ matrix.nap_modules }} + runs-on: ubuntu-24.04 + needs: [checks, tag-candidate] + permissions: + contents: read + id-token: write + security-events: write + if: ${{ !cancelled() && !failure() }} + strategy: + fail-fast: false + matrix: ${{ fromJSON( needs.checks.outputs.image_matrix_nap ) }} + steps: + - name: Checkout Repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: NAP modules + id: nap_modules + run: | + [[ "${{ matrix.nap_modules }}" == "waf,dos" ]] && modules="waf-dos" || name="${{ matrix.nap_modules }}" + echo "name=${name}" >> $GITHUB_OUTPUT + if: ${{ matrix.nap_modules != '' }} + + - name: Make directory for security scan results + id: directory + run: | + directory=${{ matrix.image }}-${{ matrix.target }}-${{ steps.nap_modules.outputs.name }}-results + echo "directory=${directory}" >> $GITHUB_OUTPUT + mkdir -p "${directory}" + + - name: Docker meta + id: meta + uses: docker/metadata-action@369eb591f429131d6889c46b94e711f089e6ca96 # v5.6.1 + with: + context: workflow + images: | + name=gcr.io/f5-gcs-7899-ptg-ingrss-ctlr/dev/nginx-ic${{ contains(matrix.nap_modules, 'dos') && '-dos' || '' }}${{ contains(matrix.nap_modules, 'waf') && '-nap' || '' }}${{ contains(matrix.image, 'v5') && '-v5' || '' }}/nginx-plus-ingress + flavor: | + suffix=${{ contains(matrix.image, 'ubi') && '-ubi' || '' }}${{ contains(matrix.image, 'alpine') && '-alpine' || '' }}${{ contains(matrix.target, 'aws') && '-mktpl' || '' }}${{ contains(matrix.image, 'fips') && '-fips' || ''}} + tags: | + type=raw,value=${{ github.ref_name == github.event.repository.default_branch && 'edge' || github.ref_name }} + + - name: Authenticate to Google Cloud + id: auth + uses: google-github-actions/auth@6fc4af4b145ae7821d527454aa9bd537d1f2dc5f # v2.1.7 + with: + token_format: access_token + workload_identity_provider: ${{ secrets.GCR_WORKLOAD_IDENTITY }} + service_account: ${{ secrets.GCR_SERVICE_ACCOUNT }} + + - name: Login to GCR + uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 + with: + registry: gcr.io + username: oauth2accesstoken + password: ${{ steps.auth.outputs.access_token }} + + # - name: Run Trivy vulnerability scanner + # uses: aquasecurity/trivy-action@6e7b7d1fd3e4fef0c5fa8cce1229c54b2c9bd0d8 # 0.24.0 + # continue-on-error: true + # with: + # image-ref: ${{ steps.meta.outputs.tags }} + # format: "sarif" + # output: "${{ steps.directory.outputs.directory }}/trivy.sarif" + # ignore-unfixed: "true" + + - name: DockerHub Login for Docker Scout + uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + + - name: Run Docker Scout vulnerability scanner + id: docker-scout + uses: docker/scout-action@b23590dc1e4d09febc00cfcbc51e9e8c0f7ee9f3 # v1.16.1 + with: + command: cves + image: ${{ steps.meta.outputs.tags }} + ignore-base: true + sarif-file: "${{ steps.directory.outputs.directory }}/scout.sarif" + write-comment: false + github-token: ${{ secrets.GITHUB_TOKEN }} # to be able to write the comment + summary: true + + - name: Upload Scan Results to Github Artifacts + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + with: + name: "${{ github.ref_name }}-${{ steps.directory.outputs.directory }}" + path: "${{ steps.directory.outputs.directory }}/" + overwrite: true + + - name: Upload Scan results to GitHub Security tab + uses: github/codeql-action/upload-sarif@48ab28a6f5dbc2a99bf1e0131198dd8f1df78169 # v3.28.0 + with: + sarif_file: "${{ steps.directory.outputs.directory }}/" + continue-on-error: true + + update-release-draft: + name: Update Release Draft + runs-on: ubuntu-24.04 + needs: [checks] + permissions: + contents: write + steps: + - name: Checkout Repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: Create/Update Draft + uses: lucacome/draft-release@5d29432a46bff6c122cd4b07a1fb94e1bb158d34 # v1.1.1 + id: release-notes + with: + minor-label: "enhancement" + major-label: "change" + publish: false + collapse-after: 50 + variables: | + helm-chart=${{ needs.checks.outputs.chart_version }} + notes-footer: | + ## Upgrade + - For NGINX, use the {{version}} images from our [DockerHub](https://hub.docker.com/r/nginx/nginx-ingress/tags?page=1&ordering=last_updated&name={{version-number}}), [GitHub Container](https://github.com/nginxinc/kubernetes-ingress/pkgs/container/kubernetes-ingress), [Amazon ECR Public Gallery](https://gallery.ecr.aws/nginx/nginx-ingress) or [Quay.io](https://quay.io/repository/nginx/nginx-ingress). + - For NGINX Plus, use the {{version}} images from the F5 Container registry, the [AWS Marketplace](https://aws.amazon.com/marketplace/search/?CREATOR=741df81b-dfdc-4d36-b8da-945ea66b522c&FULFILLMENT_OPTION_TYPE=CONTAINER&filters=CREATOR%2CFULFILLMENT_OPTION_TYPE), the [GCP Marketplace](https://console.cloud.google.com/marketplace/browse?filter=partner:F5,%20Inc.&filter=solution-type:k8s&filter=category:networking), [Azure Marketplace](https://azuremarketplace.microsoft.com/en-gb/marketplace/apps/category/containers?page=1&search=f5&subcategories=container-apps) or build your own image using the {{version}} source code. + - For Helm, use version {{helm-chart}} of the chart. + + ## Resources + - Documentation -- https://docs.nginx.com/nginx-ingress-controller/ + - Configuration examples -- https://github.com/nginxinc/kubernetes-ingress/tree/{{version}}/examples + - Helm Chart -- https://github.com/nginxinc/kubernetes-ingress/tree/{{version}}/deployments/helm-chart + - Operator -- https://github.com/nginxinc/nginx-ingress-helm-operator + if: ${{ github.event_name == 'push' && contains(github.ref_name, 'release-') }} diff --git a/.github/workflows/issues.yaml b/.github/workflows/issues.yaml index aeb1d8b192..8c9a37ccb5 100644 --- a/.github/workflows/issues.yaml +++ b/.github/workflows/issues.yaml @@ -4,23 +4,30 @@ on: issues: types: [opened] +permissions: + contents: read + jobs: comment: name: Issue comment if: ${{ !github.event.issue.pull_request }} - runs-on: ubuntu-20.04 + runs-on: ubuntu-24.04 + permissions: + contents: read + issues: write # for actions/github-script to create comments steps: - name: text id: controller if: contains(github.event.issue.body, 'nginx.ingress.kubernetes.io') run: | text="\n\n I\'ve parsed the text of your issue and it looks like you might be mixing up the two Ingress Controllers, please take a look at this [page](https://docs.nginx.com/nginx-ingress-controller/intro/nginx-ingress-controllers) to see the differences between \`nginxinc/kubernetes-ingress\` (this repo) and \`kubernetes/ingress-nginx\`." - echo "::set-output name=text::$text" + echo "text=$text" >> $GITHUB_OUTPUT + - name: Check if Issue author is Org member id: membercheck - uses: actions/github-script@v6 + uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 with: - github-token: ${{ secrets.GITHUB_TOKEN }} + retries: 3 script: | let member try { @@ -35,15 +42,16 @@ jobs: member = false } return member + - name: Send message - uses: actions/github-script@v6 + uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 if: steps.membercheck.outputs.result == 'false' with: - github-token: ${{secrets.GITHUB_TOKEN}} + retries: 3 script: | github.rest.issues.createComment({ issue_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo, - body: 'Hi @${{github.event.issue.user.login}} thanks for reporting! \n\n Be sure to check out the [docs](https://docs.nginx.com/nginx-ingress-controller) while you wait for a human to take a look at this :slightly_smiling_face:${{ steps.controller.outputs.text }}\n\n Cheers!' + body: 'Hi @${{github.event.issue.user.login}} thanks for reporting! \n\n Be sure to check out the [docs](https://docs.nginx.com/nginx-ingress-controller) and the [Contributing Guidelines](https://github.com/nginxinc/kubernetes-ingress/blob/main/CONTRIBUTING.md) while you wait for a human to take a look at this :slightly_smiling_face:${{ steps.controller.outputs.text }}\n\n Cheers!' }) diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml index 4413605165..df4b9be6e7 100644 --- a/.github/workflows/labeler.yml +++ b/.github/workflows/labeler.yml @@ -2,16 +2,20 @@ name: "Pull Request Labeler" on: - pull_request_target -permissions: # added using https://github.com/step-security/secure-workflows +permissions: contents: read jobs: triage: permissions: contents: read - pull-requests: write - runs-on: ubuntu-latest + pull-requests: write # for actions/labeler to add labels + runs-on: ubuntu-24.04 steps: - - uses: joshdales/labeler@4c74e8446142eeec7aa182f52ea24306a5479850 # if https://github.com/actions/labeler/pull/203 is merged, use the official action actions/labeler - with: - repo-token: "${{ secrets.GITHUB_TOKEN }}" + - name: Checkout Repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - uses: actions/labeler@8558fd74291d67161a8a78ce36a881fa63b766a9 # v5.0.0 + with: + repo-token: "${{ secrets.GITHUB_TOKEN }}" + sync-labels: true diff --git a/.github/workflows/lint-format.yml b/.github/workflows/lint-format.yml new file mode 100644 index 0000000000..b5c85f65e8 --- /dev/null +++ b/.github/workflows/lint-format.yml @@ -0,0 +1,91 @@ +name: Lint & Formatting + +on: + pull_request: + branches: + - main + merge_group: + +defaults: + run: + shell: bash + +concurrency: + group: ${{ github.ref_name }}-lint-format + cancel-in-progress: true + +permissions: + contents: read + +jobs: + + format: + name: Format + runs-on: ubuntu-24.04 + steps: + - name: Checkout Repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: Setup Golang Environment + uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0 + with: + go-version-file: go.mod + + - name: Run goimports & gofumpt + run: | + make format + git diff --exit-code + + lint: + name: Lint + runs-on: ubuntu-24.04 + permissions: + contents: read + pull-requests: read # for golangci-lint-action + steps: + - name: Checkout Repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: Setup Golang Environment + uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0 + with: + go-version-file: go.mod + + - name: Lint Code + uses: golangci/golangci-lint-action@971e284b6050e8a5849b72094c50ab08da042db8 # v6.1.1 + with: + only-new-issues: true + + actionlint: + name: Actionlint + runs-on: ubuntu-24.04 + steps: + - name: Checkout Repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - uses: reviewdog/action-actionlint@af17f9e3640ac863dbcc515d45f5f35d708d0faf # v1.62.0 + with: + actionlint_flags: -shellcheck "" + + chart-lint: + name: Chart Lint + runs-on: ubuntu-24.04 + steps: + - name: Checkout Repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: Lint chart + run: helm lint charts/nginx-ingress + + markdown-lint: + name: Markdown Lint + runs-on: ubuntu-24.04 + steps: + - name: Checkout Repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - uses: DavidAnson/markdownlint-cli2-action@a23dae216ce3fee4db69da41fed90d2a4af801cf # v19.0.0 + with: + config: .markdownlint-cli2.yaml + globs: "**/*.md" + fix: false diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml deleted file mode 100644 index b6dae5536f..0000000000 --- a/.github/workflows/lint.yml +++ /dev/null @@ -1,50 +0,0 @@ -name: Lint - -on: - pull_request: - branches: - - main - paths-ignore: - - 'docs/**' - - 'examples/**' - - '**.md' - types: - - opened - - reopened - - synchronize - -defaults: - run: - shell: bash - -concurrency: - group: ${{ github.ref_name }}-lint - cancel-in-progress: true - -jobs: - - lint: - name: Lint - runs-on: ubuntu-22.04 - steps: - - name: Checkout Repository - uses: actions/checkout@v3 - - name: Setup Golang Environment - uses: actions/setup-go@v3 - with: - go-version-file: go.mod - cache: true - - name: Lint Code - uses: golangci/golangci-lint-action@v3 - with: - only-new-issues: true - - actionlint: - name: Actionlint - runs-on: ubuntu-22.04 - steps: - - name: Checkout Repository - uses: actions/checkout@v3 - - uses: reviewdog/action-actionlint@v1 - with: - actionlint_flags: -shellcheck "" diff --git a/.github/workflows/mend.yml b/.github/workflows/mend.yml new file mode 100644 index 0000000000..fce35daef3 --- /dev/null +++ b/.github/workflows/mend.yml @@ -0,0 +1,49 @@ +name: Mend + +on: + push: + branches: + - main + paths-ignore: + - site/** + - examples/** + workflow_dispatch: + inputs: + branch: + type: string + required: false + default: main + workflow_call: + inputs: + branch: + type: string + required: true + +concurrency: + group: ${{ github.ref_name }}-mend + cancel-in-progress: true + +permissions: + contents: read + +jobs: + scan: + name: Mend + runs-on: ubuntu-24.04 + steps: + - name: Checkout Repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + ref: ${{ inputs.branch && inputs.branch || github.ref }} + + - name: Download agent + run: curl -fsSLJO https://github.com/whitesource/unified-agent-distribution/releases/latest/download/wss-unified-agent.jar + + - name: Verify JAR + run: jarsigner -verify wss-unified-agent.jar + + - name: Scan and upload + env: + PRODUCT_NAME: kubernetes-ingress-controller_${{ inputs.branch && inputs.branch || github.ref_name }} + PROJECT_NAME: nic + run: java -jar wss-unified-agent.jar -noConfig true -wss.url ${{ secrets.WSS_URL }} -apiKey ${{ secrets.WSS_NGINX_TOKEN }} -product $PRODUCT_NAME -project $PROJECT_NAME -d . diff --git a/.github/workflows/notifications.yml b/.github/workflows/notifications.yml index 2986d2848a..36e7807827 100644 --- a/.github/workflows/notifications.yml +++ b/.github/workflows/notifications.yml @@ -10,16 +10,25 @@ on: - "Lint" - "Update Docker Images" - "OpenSSF Scorecards" + - "Build OSS" + - "Build Plus" + - "Release NIC" types: - completed +permissions: + contents: read + jobs: on-failure: - runs-on: ubuntu-20.04 + runs-on: ubuntu-24.04 if: ${{ github.event.workflow_run.conclusion == 'failure' && github.event.repository.fork == false }} + permissions: + contents: read + actions: read # for 8398a7/action-slack steps: - name: Data - uses: actions/github-script@v6 + uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 continue-on-error: true id: data with: @@ -40,7 +49,7 @@ jobs: } - name: Send Notification - uses: 8398a7/action-slack@v3 + uses: 8398a7/action-slack@28ba43ae48961b90635b50953d216767a6bea486 # v3.16.2 with: status: custom custom_payload: | diff --git a/.github/workflows/oss-release.yml b/.github/workflows/oss-release.yml new file mode 100644 index 0000000000..b04d4d6f4a --- /dev/null +++ b/.github/workflows/oss-release.yml @@ -0,0 +1,293 @@ +name: "Release NGINX Ingress Controller OSS Images" + +on: + workflow_dispatch: + inputs: + gcr_release_registry: + required: true + type: boolean + ecr_public_registry: + required: true + type: boolean + dockerhub_public_registry: + required: true + type: boolean + quay_public_registry: + required: true + type: boolean + github_public_registry: + required: true + type: boolean + source_tag: + required: true + type: string + target_tag: + required: true + type: string + branch: + required: false + type: string + default: "main" + dry_run: + type: boolean + default: false + workflow_call: + inputs: + gcr_release_registry: + required: true + type: boolean + ecr_public_registry: + required: true + type: boolean + dockerhub_public_registry: + required: true + type: boolean + quay_public_registry: + required: true + type: boolean + github_public_registry: + required: true + type: boolean + source_tag: + required: true + type: string + target_tag: + required: true + type: string + branch: + required: false + type: string + default: "main" + dry_run: + type: boolean + default: false + +defaults: + run: + shell: bash + +permissions: + contents: read + +jobs: + release-to-gcr-release-registry: + name: Push images to the GCR Release Registry + runs-on: ubuntu-24.04 + permissions: + contents: read + id-token: write + if: ${{ inputs.gcr_release_registry }} + steps: + - name: Checkout Repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + ref: ${{ inputs.branch }} + + - name: Authenticate to Google Cloud + id: gcr-auth + uses: google-github-actions/auth@6fc4af4b145ae7821d527454aa9bd537d1f2dc5f # v2.1.7 + with: + token_format: access_token + workload_identity_provider: ${{ secrets.GCR_WORKLOAD_IDENTITY }} + service_account: ${{ secrets.GCR_SERVICE_ACCOUNT }} + + - name: Login to GCR + uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 + with: + registry: gcr.io + username: oauth2accesstoken + password: ${{ steps.gcr-auth.outputs.access_token }} + + - name: Publish OSS images + run: | + export CONFIG_PATH=.github/config/config-oss-gcr-release + export SOURCE_TAG=${{ inputs.source_tag }} + export TARGET_TAG=${{ inputs.target_tag }} + if ${{ inputs.dry_run }}; then + export DRY_RUN=true + fi + .github/scripts/copy-images.sh + + release-oss-to-ecr-public-registry: + name: Push OSS images to the AWS Public Registry + runs-on: ubuntu-24.04 + permissions: + contents: read + id-token: write + if: ${{ inputs.ecr_public_registry }} + steps: + - name: Checkout Repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + ref: ${{ inputs.branch }} + + - name: Authenticate to Google Cloud + id: gcr-auth + uses: google-github-actions/auth@6fc4af4b145ae7821d527454aa9bd537d1f2dc5f # v2.1.7 + with: + token_format: access_token + workload_identity_provider: ${{ secrets.GCR_WORKLOAD_IDENTITY }} + service_account: ${{ secrets.GCR_SERVICE_ACCOUNT }} + + - name: Login to GCR + uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 + with: + registry: gcr.io + username: oauth2accesstoken + password: ${{ steps.gcr-auth.outputs.access_token }} + + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@e3dd6a429d7300a6a4c196c26e071d42e0343502 # v4.0.2 + with: + aws-region: us-east-1 + role-to-assume: ${{ secrets.AWS_ROLE_PUBLIC_ECR }} + + - name: Login to Public ECR + uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 + with: + registry: public.ecr.aws + + - name: Publish images + run: | + export CONFIG_PATH=.github/config/config-oss-ecr + export SOURCE_TAG=${{ inputs.source_tag }} + export TARGET_TAG=${{ inputs.target_tag }} + if ${{ inputs.dry_run }}; then + export DRY_RUN=true + fi + .github/scripts/copy-images.sh + + release-oss-to-dockerhub-public-registry: + name: Push OSS images to the DockerHub Public Registry + runs-on: ubuntu-24.04 + permissions: + contents: read + id-token: write + if: ${{ inputs.dockerhub_public_registry }} + steps: + - name: Checkout Repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + ref: ${{ inputs.branch }} + + - name: Authenticate to Google Cloud + id: gcr-auth + uses: google-github-actions/auth@6fc4af4b145ae7821d527454aa9bd537d1f2dc5f # v2.1.7 + with: + token_format: access_token + workload_identity_provider: ${{ secrets.GCR_WORKLOAD_IDENTITY }} + service_account: ${{ secrets.GCR_SERVICE_ACCOUNT }} + + - name: Login to GCR + uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 + with: + registry: gcr.io + username: oauth2accesstoken + password: ${{ steps.gcr-auth.outputs.access_token }} + + - name: DockerHub Login + uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + + - name: Publish images + run: | + export CONFIG_PATH=.github/config/config-oss-dockerhub + export SOURCE_TAG=${{ inputs.source_tag }} + export TARGET_TAG=${{ inputs.target_tag }} + if ${{ inputs.dry_run }}; then + export DRY_RUN=true + fi + .github/scripts/copy-images.sh + + release-oss-to-quay-public-registry: + name: Push OSS images to the Quay Public Registry + runs-on: ubuntu-24.04 + permissions: + contents: read + id-token: write + if: ${{ inputs.quay_public_registry }} + steps: + - name: Checkout Repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + ref: ${{ inputs.branch }} + + - name: Authenticate to Google Cloud + id: gcr-auth + uses: google-github-actions/auth@6fc4af4b145ae7821d527454aa9bd537d1f2dc5f # v2.1.7 + with: + token_format: access_token + workload_identity_provider: ${{ secrets.GCR_WORKLOAD_IDENTITY }} + service_account: ${{ secrets.GCR_SERVICE_ACCOUNT }} + + - name: Login to GCR + uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 + with: + registry: gcr.io + username: oauth2accesstoken + password: ${{ steps.gcr-auth.outputs.access_token }} + + - name: Login to Quay.io + uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 + with: + registry: quay.io + username: ${{ secrets.QUAY_USERNAME }} + password: ${{ secrets.QUAY_ROBOT_TOKEN }} + + - name: Publish images + run: | + export CONFIG_PATH=.github/config/config-oss-quay + export SOURCE_TAG=${{ inputs.source_tag }} + export TARGET_TAG=${{ inputs.target_tag }} + if ${{ inputs.dry_run }}; then + export DRY_RUN=true + fi + .github/scripts/copy-images.sh + + release-oss-to-github-public-registry: + name: Push OSS images to the GitHub Public Registry + runs-on: ubuntu-24.04 + permissions: + contents: read + id-token: write + packages: write + if: ${{ inputs.github_public_registry }} + steps: + - name: Checkout Repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + ref: ${{ inputs.branch }} + + - name: Authenticate to Google Cloud + id: gcr-auth + uses: google-github-actions/auth@6fc4af4b145ae7821d527454aa9bd537d1f2dc5f # v2.1.7 + with: + token_format: access_token + workload_identity_provider: ${{ secrets.GCR_WORKLOAD_IDENTITY }} + service_account: ${{ secrets.GCR_SERVICE_ACCOUNT }} + + - name: Login to GCR + uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 + with: + registry: gcr.io + username: oauth2accesstoken + password: ${{ steps.gcr-auth.outputs.access_token }} + + - name: Login to GitHub Container Registry + uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Publish images + run: | + export CONFIG_PATH=.github/config/config-oss-github + export SOURCE_TAG=${{ inputs.source_tag }} + export TARGET_TAG=${{ inputs.target_tag }} + if ${{ inputs.dry_run }}; then + export DRY_RUN=true + fi + .github/scripts/copy-images.sh diff --git a/.github/workflows/patch-image.yml b/.github/workflows/patch-image.yml new file mode 100644 index 0000000000..2b08e1824b --- /dev/null +++ b/.github/workflows/patch-image.yml @@ -0,0 +1,84 @@ +name: Patch Docker Image + +on: + workflow_call: + inputs: + image: + description: The image name to patch + required: true + type: string + target_image: + description: The target name of the patched image + required: true + type: string + tag: + description: The image tag to patch + required: true + type: string + target_tag: + description: The target tag of the patched image + required: true + type: string + ic_version: + description: The IC version to label + required: true + type: string + platforms: + description: The platforms to patch + required: true + type: string + +defaults: + run: + shell: bash + +permissions: + contents: read + +jobs: + patch-image: + name: Patch image + runs-on: ubuntu-24.04 + permissions: + contents: read + id-token: write + steps: + - name: Checkout Repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: Docker Buildx + uses: docker/setup-buildx-action@6524bf65af31da8d45b59e8c27de4bd072b392f5 # v3.8.0 + + - name: Setup QEMU + uses: docker/setup-qemu-action@49b3bc8e6bdd4a60e6116a5414239cba5943d3cf # v3.2.0 + with: + platforms: arm,arm64,ppc64le,s390x + + - name: Authenticate to Google Cloud + id: auth + uses: google-github-actions/auth@6fc4af4b145ae7821d527454aa9bd537d1f2dc5f # v2.1.7 + with: + token_format: access_token + workload_identity_provider: ${{ secrets.GCR_WORKLOAD_IDENTITY }} + service_account: ${{ secrets.GCR_SERVICE_ACCOUNT }} + + - name: Login to GCR + uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 + with: + registry: gcr.io + username: oauth2accesstoken + password: ${{ steps.auth.outputs.access_token }} + + - name: Apply OS patches to Container + uses: docker/build-push-action@48aba3b46d1b1fec4febb7c5d0c644b249a11355 # v6.10.0 + with: + file: build/Dockerfile + context: "." + target: patched + tags: "${{ inputs.target_image }}:${{ inputs.target_tag }}" + platforms: ${{ inputs.platforms }} + pull: true + push: true + build-args: | + IMAGE_NAME=${{ inputs.image }}:${{ inputs.tag }} + IC_VERSION=${{ inputs.ic_version }} diff --git a/.github/workflows/plus-release.yml b/.github/workflows/plus-release.yml new file mode 100644 index 0000000000..c267bef495 --- /dev/null +++ b/.github/workflows/plus-release.yml @@ -0,0 +1,297 @@ +name: "Release NGINX Ingress Controller Plus Images" + +on: + workflow_dispatch: + inputs: + nginx_registry: + required: true + type: boolean + gcr_release_registry: + required: true + type: boolean + gcr_mktpl_registry: + required: true + type: boolean + ecr_mktpl_registry: + required: true + type: boolean + az_mktpl_registry: + required: true + type: boolean + source_tag: + required: true + type: string + target_tag: + required: true + type: string + branch: + required: false + type: string + default: "main" + dry_run: + type: boolean + default: false + workflow_call: + inputs: + nginx_registry: + required: true + type: boolean + gcr_release_registry: + required: true + type: boolean + gcr_mktpl_registry: + required: true + type: boolean + ecr_mktpl_registry: + required: true + type: boolean + az_mktpl_registry: + required: true + type: boolean + source_tag: + required: true + type: string + target_tag: + required: true + type: string + branch: + required: false + type: string + default: "main" + dry_run: + type: boolean + default: false + +defaults: + run: + shell: bash + +permissions: + contents: read + +jobs: + release-to-gcr-release-registry: + name: Push images to the GCR Release Registry + runs-on: ubuntu-24.04 + permissions: + contents: read + id-token: write + if: ${{ inputs.gcr_release_registry }} + steps: + - name: Checkout Repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + ref: ${{ inputs.branch }} + + - name: Authenticate to Google Cloud + id: gcr-auth + uses: google-github-actions/auth@6fc4af4b145ae7821d527454aa9bd537d1f2dc5f # v2.1.7 + with: + token_format: access_token + workload_identity_provider: ${{ secrets.GCR_WORKLOAD_IDENTITY }} + service_account: ${{ secrets.GCR_SERVICE_ACCOUNT }} + + - name: Login to GCR + uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 + with: + registry: gcr.io + username: oauth2accesstoken + password: ${{ steps.gcr-auth.outputs.access_token }} + + - name: Publish Plus images + run: | + export CONFIG_PATH=.github/config/config-plus-gcr-release + export SOURCE_TAG=${{ inputs.source_tag }} + export TARGET_TAG=${{ inputs.target_tag }} + if ${{ inputs.dry_run }}; then + export DRY_RUN=true + fi + .github/scripts/copy-images.sh + + release-to-nginx-registry: + name: Push Plus images to the NGINX Registry + runs-on: 'kic-plus' + permissions: + contents: read + id-token: write + if: ${{ inputs.nginx_registry }} + steps: + - name: Checkout Repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + ref: ${{ inputs.branch }} + + - name: Authenticate to Google Cloud + id: gcr-auth + uses: google-github-actions/auth@6fc4af4b145ae7821d527454aa9bd537d1f2dc5f # v2.1.7 + with: + token_format: access_token + workload_identity_provider: ${{ secrets.GCR_WORKLOAD_IDENTITY }} + service_account: ${{ secrets.GCR_SERVICE_ACCOUNT }} + + - name: Login to GCR + uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 + with: + registry: gcr.io + username: oauth2accesstoken + password: ${{ steps.gcr-auth.outputs.access_token }} + + - name: Get Id Token + uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 + id: idtoken + with: + script: | + let id_token = await core.getIDToken() + core.setOutput('id_token', id_token) + + - name: Login to NGINX Registry + uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 + with: + registry: docker-mgmt.nginx.com + username: ${{ steps.idtoken.outputs.id_token }} + password: ${{ github.actor }} + + - name: Publish images + run: | + export CONFIG_PATH=.github/config/config-plus-nginx + export SOURCE_TAG=${{ inputs.source_tag }} + export TARGET_TAG=${{ inputs.target_tag }} + if ${{ inputs.dry_run }}; then + export DRY_RUN=true + fi + .github/scripts/copy-images.sh + + release-plus-to-gcr-marketplace-registry: + name: Push Plus images to the GCR Marketplace Registry + runs-on: ubuntu-24.04 + permissions: + contents: read + id-token: write + if: ${{ inputs.gcr_mktpl_registry }} + steps: + - name: Checkout Repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + ref: ${{ inputs.branch }} + + - name: Authenticate to Google Cloud + id: gcr-priv-auth + uses: google-github-actions/auth@6fc4af4b145ae7821d527454aa9bd537d1f2dc5f # v2.1.7 + with: + token_format: access_token + workload_identity_provider: ${{ secrets.GCR_WORKLOAD_IDENTITY }} + service_account: ${{ secrets.GCR_SERVICE_ACCOUNT }} + + - name: Authenticate to Google Cloud Marketplace + id: gcr-mktpl-auth + uses: google-github-actions/auth@6fc4af4b145ae7821d527454aa9bd537d1f2dc5f # v2.1.7 + with: + token_format: access_token + workload_identity_provider: ${{ secrets.GCR_WORKLOAD_IDENTITY_MKTPL }} + service_account: ${{ secrets.GCR_SERVICE_ACCOUNT_MKTPL }} + + - name: Publish Plus images + run: | + export CONFIG_PATH=.github/config/config-plus-gcr-public + export SOURCE_TAG=${{ inputs.source_tag }} + export TARGET_TAG=${{ inputs.target_tag }} + export SOURCE_OPTS="--src-registry-token ${{ steps.gcr-priv-auth.outputs.access_token }}" + export TARGET_OPTS="--dest-registry-token ${{ steps.gcr-mktpl-auth.outputs.access_token }}" + if ${{ inputs.dry_run }}; then + export DRY_RUN=true + fi + .github/scripts/copy-images.sh + + release-plus-to-ecr-marketplace-registry: + name: Push Plus images to the AWS Marketplace Registry + runs-on: ubuntu-24.04 + permissions: + contents: read + id-token: write + if: ${{ inputs.ecr_mktpl_registry }} + steps: + - name: Checkout Repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + ref: ${{ inputs.branch }} + + - name: Authenticate to Google Cloud + id: gcr-auth + uses: google-github-actions/auth@6fc4af4b145ae7821d527454aa9bd537d1f2dc5f # v2.1.7 + with: + token_format: access_token + workload_identity_provider: ${{ secrets.GCR_WORKLOAD_IDENTITY }} + service_account: ${{ secrets.GCR_SERVICE_ACCOUNT }} + + - name: Login to GCR + uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 + with: + registry: gcr.io + username: oauth2accesstoken + password: ${{ steps.gcr-auth.outputs.access_token }} + + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@e3dd6a429d7300a6a4c196c26e071d42e0343502 # v4.0.2 + with: + aws-region: us-east-1 + role-to-assume: ${{ secrets.AWS_ROLE_MARKETPLACE }} + + - name: Login to ECR + uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 + with: + registry: 709825985650.dkr.ecr.us-east-1.amazonaws.com + + - name: Publish images + run: | + export CONFIG_PATH=.github/config/config-plus-ecr + export SOURCE_TAG=${{ inputs.source_tag }} + export TARGET_TAG=${{ inputs.target_tag }} + if ${{ inputs.dry_run }}; then + export DRY_RUN=true + fi + .github/scripts/copy-images.sh + + release-plus-to-azure-marketplace-registry: + name: Push Plus images to the Azure Marketplace Registry + runs-on: ubuntu-24.04 + permissions: + contents: read + id-token: write + if: ${{ inputs.az_mktpl_registry }} + steps: + - name: Checkout Repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + ref: ${{ inputs.branch }} + + - name: Authenticate to Google Cloud + id: gcr-auth + uses: google-github-actions/auth@6fc4af4b145ae7821d527454aa9bd537d1f2dc5f # v2.1.7 + with: + token_format: access_token + workload_identity_provider: ${{ secrets.GCR_WORKLOAD_IDENTITY }} + service_account: ${{ secrets.GCR_SERVICE_ACCOUNT }} + + - name: Login to GCR + uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 + with: + registry: gcr.io + username: oauth2accesstoken + password: ${{ steps.gcr-auth.outputs.access_token }} + + - name: Login to ACR + uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 + with: + registry: nginxmktpl.azurecr.io + username: ${{ secrets.AZ_MKTPL_ID }} + password: ${{ secrets.AZ_MKTPL_SECRET }} + + - name: Publish images + run: | + export CONFIG_PATH=.github/config/config-plus-azure + export SOURCE_TAG=${{ inputs.source_tag }} + export TARGET_TAG=${{ inputs.target_tag }} + if ${{ inputs.dry_run }}; then + export DRY_RUN=true + fi + .github/scripts/copy-images.sh diff --git a/.github/workflows/publish-helm.yml b/.github/workflows/publish-helm.yml new file mode 100644 index 0000000000..be31ee00a9 --- /dev/null +++ b/.github/workflows/publish-helm.yml @@ -0,0 +1,109 @@ +name: Publish Helm Chart + +on: + workflow_dispatch: + inputs: + branch: + description: "Release Helm chart from branch" + required: true + type: string + ic_version: + description: "Ingress Controller version" + required: true + type: string + chart_version: + description: "Helm Chart version" + required: true + type: string + nginx_helm_repo: + description: "Publish to the NGINX Helm repo" + required: true + type: boolean + workflow_call: + inputs: + branch: + description: "Release Helm chart from branch" + required: true + type: string + ic_version: + description: "Ingress Controller version" + required: true + type: string + chart_version: + description: "Helm Chart version" + required: true + type: string + nginx_helm_repo: + description: "Publish to the NGINX Helm repo" + required: true + type: boolean + +defaults: + run: + shell: bash + +concurrency: + group: ${{ github.ref_name }}-publish-helm + cancel-in-progress: true + +permissions: + contents: read + +jobs: + publish-helm: + name: Package and Publish Helm Chart + runs-on: ubuntu-24.04 + permissions: + contents: write # for pushing to Helm Charts repository + packages: write # for helm to push to GHCR + steps: + - name: Checkout Repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + ref: refs/heads/${{ inputs.branch }} + path: kic + + - name: Login to GitHub Container Registry + uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: DockerHub Login + uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + + - name: Package + id: package + run: | + helm_versions="--app-version ${{ inputs.ic_version }} --version ${{ inputs.chart_version }}" + output=$(helm package ${helm_versions} kic/charts/nginx-ingress) + echo "path=$(basename -- $(echo $output | cut -d: -f2))" >> $GITHUB_OUTPUT + + - name: Push to OCI registries + run: | + helm push ${{ steps.package.outputs.path }} oci://ghcr.io/nginxinc/charts + helm push ${{ steps.package.outputs.path }} oci://registry-1.docker.io/nginxcharts + + - name: Checkout Repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + repository: nginxinc/helm-charts + fetch-depth: 1 + token: ${{ secrets.NGINX_PAT }} + path: helm-charts + if: ${{ inputs.nginx_helm_repo }} + + - name: Push Helm Chart to Helm Charts Repository + run: | + mv ${{ steps.package.outputs.path }} ${{ github.workspace }}/helm-charts/stable/ + cd ${{ github.workspace }}/helm-charts + helm repo index stable --url https://helm.nginx.com/stable + git add -A + git -c user.name='NGINX Kubernetes Team' -c user.email='kubernetes@nginx.com' \ + commit -m "NGINX Ingress Controller - Release ${{ inputs.chart_version }}" + git push -u origin master + if: ${{ inputs.nginx_helm_repo }} diff --git a/.github/workflows/regression.yml b/.github/workflows/regression.yml new file mode 100644 index 0000000000..e908e7f1b2 --- /dev/null +++ b/.github/workflows/regression.yml @@ -0,0 +1,345 @@ +name: Run Regression tests +run-name: Run NIC Regression workflow, triggered from ${{ github.event_name }} by @${{ github.actor }} + +on: + schedule: + - cron: 00 03 * * * + workflow_dispatch: + inputs: + branch: + type: string + description: "Branch to run regression workflow on" + default: main + +defaults: + run: + shell: bash + +concurrency: + group: ${{ github.ref_name }}-regression + cancel-in-progress: true + +permissions: + contents: read + +jobs: + checks: + name: Checks and variables + runs-on: ubuntu-24.04 + permissions: + contents: read + id-token: write + outputs: + k8s_latest: ${{ steps.vars.outputs.k8s_latest }} + latest_kindest_node_versions: ${{ steps.vars.outputs.latest_kindest_node_versions }} + stable_tag: ${{ steps.vars.outputs.stable_tag }} + branch: ${{ steps.vars.outputs.branch }} + steps: + - name: Checkout Repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + ref: ${{ inputs.branch && inputs.branch || github.event.repository.default_branch }} + + - name: Output Variables + id: vars + run: | + kindest_latest=$(curl -s "https://hub.docker.com/v2/repositories/kindest/node/tags" \ + | grep -o '"name": *"[^"]*' \ + | grep -o '[^"]*$' \ + | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' \ + | sort -rV \ + | head -n 1 \ + | sed 's/^.\{1\}//' \ + | tr -d '\n') + echo "k8s_latest=$kindest_latest" >> $GITHUB_OUTPUT + kindest_versions=$(curl -s "https://hub.docker.com/v2/repositories/kindest/node/tags/?page_size=50" \ + | grep -o '"name": *"[^"]*' \ + | grep -o '[^"]*$' \ + | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' \ + | sort -rV \ + | awk -F. '!seen[$1"."$2]++' \ + | head -n 8 \ + | sort -V \ + | sed 's/v//g' \ + | sed 's/$//' \ + | sed 's/, $//' \ + | jq -R -s -c 'split("\n")[:-1]') + echo "latest_kindest_node_versions=$kindest_versions" >> $GITHUB_OUTPUT + source .github/data/version.txt + ./.github/scripts/variables.sh stable_tag >> $GITHUB_OUTPUT + branch=${{ github.event.repository.default_branch }} + if [ -n "${{ inputs.branch }}" ]; then + branch=${{ inputs.branch }} + fi + echo "branch=${branch}" >> $GITHUB_OUTPUT + + - name: Output variables + run: | + echo k8s_latest: ${{ steps.vars.outputs.k8s_latest }} + echo latest_kindest_node_versions: ${{ steps.vars.outputs.latest_kindest_node_versions }} + echo stable_tag: ${{ steps.vars.outputs.stable_tag }} + echo branch: ${{ steps.vars.outputs.branch }} + + unit-tests: + name: Unit Tests + runs-on: ubuntu-24.04 + needs: [checks] + steps: + - name: Checkout Repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + ref: ${{ needs.checks.outputs.branch }} + + - name: Setup Golang Environment + uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0 + with: + go-version-file: go.mod + + - name: Run Tests + run: make cover + + - name: Upload coverage to Codecov + uses: codecov/codecov-action@1e68e06f1dbfde0e4cefc87efeba9e4643565303 # v5.1.2 + with: + files: ./coverage.txt + token: ${{ secrets.CODECOV_TOKEN }} # required + + helm-tests: + name: Helm Tests ${{ matrix.base-os }} + runs-on: ubuntu-24.04 + needs: [checks] + strategy: + fail-fast: false + matrix: + include: + - base-os: debian + image: gcr.io/f5-gcs-7899-ptg-ingrss-ctlr/dev/nginx-ic/nginx-ingress + tag: ${{ needs.checks.outputs.stable_tag }} + type: oss + - base-os: debian-plus + image: gcr.io/f5-gcs-7899-ptg-ingrss-ctlr/dev/nginx-ic/nginx-plus-ingress + tag: ${{ needs.checks.outputs.stable_tag }} + type: plus + permissions: + contents: read + id-token: write + steps: + - name: Checkout Repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + ref: ${{ needs.checks.outputs.branch }} + + - name: Authenticate to Google Cloud + id: auth + uses: google-github-actions/auth@6fc4af4b145ae7821d527454aa9bd537d1f2dc5f # v2.1.7 + with: + token_format: access_token + workload_identity_provider: ${{ secrets.GCR_WORKLOAD_IDENTITY }} + service_account: ${{ secrets.GCR_SERVICE_ACCOUNT }} + + - name: Login to GCR + uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 + with: + registry: gcr.io + username: oauth2accesstoken + password: ${{ steps.auth.outputs.access_token }} + + - name: Pull build image + run: | + docker pull ${{ matrix.image }}:${{ matrix.tag }} + + - name: Deploy Kubernetes + id: k8s + run: | + kind create cluster --name ${{ github.run_id }} --image=kindest/node:v${{ needs.checks.outputs.k8s_latest }} --wait 75s + kind load docker-image "${{ matrix.image }}:${{ matrix.tag }}" --name ${{ github.run_id }} + + - name: Create Plus Secret + run: kubectl create secret generic license-token --from-literal=license.jwt="${{ secrets.PLUS_JWT }}" --type="nginx.com/license" + + - name: Install Chart + run: > + helm install + ${{ matrix.type }} + . + --set controller.image.repository=${{ matrix.image }} + --set controller.image.tag=${{ matrix.tag }} + --set controller.service.type=NodePort + --set controller.nginxplus=${{ contains(matrix.type, 'plus') && 'true' || 'false' }} + --set controller.telemetryReporting.enable=false + --wait + working-directory: ${{ github.workspace }}/charts/nginx-ingress + + - name: Expose Test Ingresses + run: | + kubectl port-forward service/${{ matrix.type }}-nginx-ingress-controller 8080:80 8443:443 & + + - name: Test HTTP + run: | + counter=0 + max_attempts=5 + until [ $(curl --write-out %{http_code} -s --output /dev/null http://localhost:8080) -eq 404 ]; do + if [ ${counter} -eq ${max_attempts} ]; then + exit 1 + fi + printf '.'; counter=$(($counter+1)); sleep 5; + done + + - name: Test HTTPS + run: | + counter=0 + max_attempts=5 + until [ $(curl --write-out %{http_code} -ks --output /dev/null https://localhost:8443) -eq 000 ]; do + if [ ${counter} -eq ${max_attempts} ]; then + exit 1 + fi + printf '.'; counter=$(($counter+1)); sleep 5; + done + + setup-regression-matrix: + name: Setup Matrix for Smoke Tests + runs-on: ubuntu-24.04 + needs: [checks] + permissions: + contents: read + id-token: write + outputs: + matrix: ${{ steps.set-matrix.outputs.matrix }} + steps: + - name: Checkout Repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + ref: ${{ needs.checks.outputs.branch }} + + - id: set-matrix + run: | + echo "matrix=$(cat .github/data/matrix-regression.json | jq -c --argjson latest '${{ needs.checks.outputs.latest_kindest_node_versions }}' '.k8s += $latest'))" >> $GITHUB_OUTPUT + + regression-tests: + name: ${{ matrix.images.label }} ${{ matrix.images.image }} ${{ matrix.k8s }} regression tests + runs-on: ubuntu-24.04 + needs: [checks, setup-regression-matrix] + strategy: + fail-fast: false + matrix: ${{ fromJSON(needs.setup-regression-matrix.outputs.matrix) }} + permissions: + contents: read + id-token: write + steps: + - name: Checkout Repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + ref: ${{ needs.checks.outputs.branch }} + + - name: Set image variables + id: image_details + run: | + echo "name=gcr.io/f5-gcs-7899-ptg-ingrss-ctlr/dev/nginx-ic${{ contains(matrix.images.nap_modules, 'dos') && '-dos' || '' }}${{ contains(matrix.images.nap_modules, 'waf') && '-nap' || '' }}${{ contains(matrix.images.image, 'v5') && '-v5' || '' }}/nginx${{ contains(matrix.images.image, 'plus') && '-plus' || '' }}-ingress" >> $GITHUB_OUTPUT + echo "tag=${{ needs.checks.outputs.stable_tag }}${{ contains(matrix.images.image, 'ubi') && '-ubi' || '' }}${{ contains(matrix.images.image, 'alpine') && '-alpine' || '' }}${{ contains(matrix.images.target, 'aws') && '-mktpl' || '' }}${{ contains(matrix.images.image, 'fips') && '-fips' || ''}}" >> $GITHUB_OUTPUT + + - name: Authenticate to Google Cloud + id: auth + uses: google-github-actions/auth@6fc4af4b145ae7821d527454aa9bd537d1f2dc5f # v2.1.7 + with: + token_format: access_token + workload_identity_provider: ${{ secrets.GCR_WORKLOAD_IDENTITY }} + service_account: ${{ secrets.GCR_SERVICE_ACCOUNT }} + + - name: Login to GCR + uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 + with: + registry: gcr.io + username: oauth2accesstoken + password: ${{ steps.auth.outputs.access_token }} + + - name: NAP modules + id: nap_modules + run: | + [[ "${{ matrix.images.nap_modules }}" == "waf,dos" ]] && modules="waf-dos" || modules="${{ matrix.images.nap_modules }}" + echo "modules=${modules}" >> $GITHUB_OUTPUT + if: ${{ matrix.images.nap_modules }} + + - name: Pull build image + run: | + docker pull ${{ steps.image_details.outputs.name }}:${{ steps.image_details.outputs.tag }} + + - name: Generate WAF v5 tgz from JSON + run: | + docker run --rm --user root -v /var/run/docker.sock:/var/run/docker.sock -v ${{ github.workspace }}/tests/data/ap-waf-v5:/data gcr.io/f5-gcs-7899-ptg-ingrss-ctlr/nap/waf-compiler:5.4.0 -p /data/wafv5.json -o /data/wafv5.tgz + if: ${{ contains(matrix.images.image, 'nap-v5')}} + + - name: Run Regression Tests + id: regression-tests + uses: ./.github/actions/smoke-tests + with: + image-type: ${{ matrix.images.image }} + image-name: ${{ steps.image_details.outputs.name }} + tag: ${{ steps.image_details.outputs.tag }} + marker: ${{ matrix.images.marker != '' && matrix.images.marker || '' }} + k8s-version: ${{ matrix.k8s }} + label: ${{ matrix.images.label }} + azure-ad-secret: ${{ secrets.AZURE_AD_AUTOMATION }} + registry-token: ${{ steps.auth.outputs.access_token }} + test-image: "gcr.io/f5-gcs-7899-ptg-ingrss-ctlr/dev/test-runner:${{ hashFiles('./tests/requirements.txt', './tests/Dockerfile') || 'latest' }}" + plus-jwt: ${{ secrets.PLUS_JWT }} + + - name: Upload Test Results + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + with: + name: ${{ steps.regression-tests.outputs.test-results-name }} + path: ${{ steps.regression-tests.outputs.test-results-path }} + if: ${{ !cancelled() }} + + tag-stable: + name: Tag tested image as nightly + needs: [checks, regression-tests] + permissions: + contents: read # To checkout repository + id-token: write # To sign into Google Container Registry + uses: ./.github/workflows/retag-images.yml + with: + source_tag: ${{ needs.checks.outputs.stable_tag }} + target_tag: nightly + dry_run: false + secrets: inherit + + release-oss: + # pushes nightly images to docker hub + name: Release Docker OSS + needs: [checks, regression-tests] + uses: ./.github/workflows/oss-release.yml + with: + gcr_release_registry: false + ecr_public_registry: true + dockerhub_public_registry: true + quay_public_registry: true + github_public_registry: true + source_tag: ${{ needs.checks.outputs.stable_tag }} + branch: ${{ needs.checks.outputs.branch }} + target_tag: "nightly" + dry_run: false + permissions: + contents: read + id-token: write + packages: write + secrets: inherit + + release-plus: + # pushes plus nightly images to nginx registry + name: Release Docker Plus + needs: [checks, regression-tests] + uses: ./.github/workflows/plus-release.yml + with: + nginx_registry: true + gcr_release_registry: false + gcr_mktpl_registry: false + ecr_mktpl_registry: false + az_mktpl_registry: false + source_tag: ${{ needs.checks.outputs.stable_tag }} + target_tag: "nightly" + branch: ${{ needs.checks.outputs.branch }} + dry_run: false + permissions: + contents: read + id-token: write + secrets: inherit diff --git a/.github/workflows/release-pr.yml b/.github/workflows/release-pr.yml new file mode 100644 index 0000000000..df3025068f --- /dev/null +++ b/.github/workflows/release-pr.yml @@ -0,0 +1,81 @@ +name: Release PR + +on: + workflow_dispatch: + inputs: + current_version: + description: "Current version to replace" + required: true + default: "3.3.2" + new_version: + description: "Version to release" + required: true + default: "3.4.3" + current_helm_version: + description: "Current helm version to replace" + required: true + default: "1.0.2" + new_helm_version: + description: "Helm version to release" + required: true + default: "1.1.3" + current_operator_version: + description: "Current operator version to replace" + required: true + default: "2.3.0" + new_operator_version: + description: "Operator version to release" + required: true + default: "2.3.1" + k8s_versions: + description: "Kubernetes versions this release has been tested on" + required: true + default: "x.xx-x.xx" + release_date: + description: "Date for this release" + required: true + default: "%d %b %Y" + +defaults: + run: + shell: bash + +permissions: + contents: read + +jobs: + release: + permissions: + contents: write + runs-on: ubuntu-24.04 + steps: + - name: Branch + id: branch + run: | + version=${{ github.event.inputs.new_version }} + version=${version%.*} + echo "branch=release-$version" >> $GITHUB_OUTPUT + + - name: Checkout Repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + ref: ${{ steps.branch.outputs.branch }} + token: ${{ secrets.NGINX_PAT }} + + - name: Replace + run: | + .github/scripts/release-version-update.sh \ + ${{ github.event.inputs.current_version }} ${{ github.event.inputs.current_helm_version }} ${{ github.event.inputs.current_operator_version }} \ + ${{ github.event.inputs.new_version }} ${{ github.event.inputs.new_helm_version }} ${{ github.event.inputs.new_operator_version }} + .github/scripts/release-notes-update.sh ${{ github.event.inputs.new_version }} ${{ github.event.inputs.new_helm_version }} "${{ github.event.inputs.k8s_versions }}" "${{ github.event.inputs.release_date }}" + + - name: Create Pull Request + uses: peter-evans/create-pull-request@67ccf781d68cd99b580ae25a5c18a1cc84ffff1f # v7.0.6 + with: + token: ${{ secrets.NGINX_PAT }} + commit-message: Release ${{ github.event.inputs.new_version }} + title: Release ${{ github.event.inputs.new_version }} + branch: docs/release-${{ github.event.inputs.new_version }} + author: nginx-bot + body: | + This automated PR updates the docs for ${{ github.event.inputs.new_version }} release. diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml deleted file mode 100644 index 2497d71b9f..0000000000 --- a/.github/workflows/release.yaml +++ /dev/null @@ -1,138 +0,0 @@ -name: Create Draft Release - -on: - push: - branches: - - release-* - workflow_dispatch: - inputs: - tagFrom: - description: The tag to create the release from. - required: true - type: string - tagTo: - description: The tag to create the release to. - required: true - type: string - branch: - description: The branch where the release will be created. - required: true - type: string - -jobs: - - binary: - name: Create Draft Release - runs-on: ubuntu-20.04 - steps: - - uses: actions/setup-node@v3 - - run: npm install semver - - uses: actions/github-script@v6 - continue-on-error: true - with: - script: | - const semver = require('semver'); - const ref = context.ref.split("/")[2] - - const releases = (await github.rest.repos.listReleases({ - owner: context.payload.repository.owner.login, - repo: context.payload.repository.name, - per_page: 100, - })).data - - let latest_release - const latest_release_current_branch = releases.find(release => !release.draft && release.tag_name.startsWith("v" + ref.split("-")[1])) - - if (latest_release_current_branch === undefined){ - latest_release = (await github.rest.repos.getLatestRelease({ - owner: context.payload.repository.owner.login, - repo: context.payload.repository.name, - })).data.tag_name - } else { - latest_release = latest_release_current_branch.tag_name - } - - let tagFrom, tagTo, branch - if (context.eventName === 'workflow_dispatch'){ - console.log(`Dispatch run with inputs: ${JSON.stringify(context.payload.inputs)}`) - ;({ tagFrom, tagTo, branch } = context.payload.inputs) - } else { - ;({ tagFrom, tagTo, branch } = { - tagFrom: latest_release, - tagTo: 'next', - branch: ref, - }) - console.log(`Push run with: { tagFrom: ${tagFrom}, tagTo: ${tagTo}, branch: ${branch} }`) - } - console.log(`The latest release was ${tagFrom}`) - - let version = tagTo.replace('v', '') - if (version === 'next'){ - const temp_notes = (await github.rest.repos.generateReleaseNotes({ - owner: context.payload.repository.owner.login, - repo: context.payload.repository.name, - tag_name: tagTo, - previous_tag_name: tagFrom, - target_commitish: branch, - })).data.body - - let level - temp_notes.includes("### 🚀 Features") ? level = 'minor' : level = 'patch' - temp_notes.includes("### 💣 Breaking Changes") ? level = 'major' : level = level - version = semver.inc(tagFrom, level) - console.log(`The level of the release is ${level}`) - } - const draft = releases.find((r) => r.draft && r.tag_name === "v"+version) - const draft_found = !(draft === undefined) - - console.log(`The next version is v${version}`) - - const footer = ` - ## Upgrade - - For NGINX, use the v${version} image from our [DockerHub](https://hub.docker.com/r/nginx/nginx-ingress/tags?page=1&ordering=last_updated&name=${version}), [GitHub Container](https://github.com/nginxinc/kubernetes-ingress/pkgs/container/kubernetes-ingress), [Amazon ECR Public Gallery](https://gallery.ecr.aws/nginx/nginx-ingress) or [Quay.io](https://quay.io/repository/nginx/nginx-ingress). - - For NGINX Plus, use the v${version} image from the F5 Container registry or the [AWS Marketplace](https://aws.amazon.com/marketplace/search/?CREATOR=741df81b-dfdc-4d36-b8da-945ea66b522c&FULFILLMENT_OPTION_TYPE=CONTAINER&filters=CREATOR%2CFULFILLMENT_OPTION_TYPE) or build your own image using the v${version} source code. - - For Helm, use version %HELM_CHART_VERSION% of the chart. - - ## Resources - - Documentation -- https://docs.nginx.com/nginx-ingress-controller/ - - Configuration examples -- https://github.com/nginxinc/kubernetes-ingress/tree/v${version}/examples - - Helm Chart -- https://github.com/nginxinc/kubernetes-ingress/tree/v${version}/deployments/helm-chart - - Operator -- https://github.com/nginxinc/nginx-ingress-operator/ - ` - - const release_notes = (await github.rest.repos.generateReleaseNotes({ - owner: context.payload.repository.owner.login, - repo: context.payload.repository.name, - tag_name: 'v' + version, - previous_tag_name: tagFrom, - target_commitish: branch, - })) - - let release - if (draft_found){ - console.log("Draft found") - release = (await github.rest.repos.updateRelease({ - owner: context.payload.repository.owner.login, - repo: context.payload.repository.name, - release_id: draft.id, - tag_name: 'v' + version, - target_commitish: branch, - name: 'v' + version, - body: release_notes.data.body + footer, - draft: true, - })) - } else { - console.log("Draft not found") - release = (await github.rest.repos.createRelease({ - owner: context.payload.repository.owner.login, - repo: context.payload.repository.name, - tag_name: 'v' + version, - target_commitish: ref, - name: 'v' + version, - body: release_notes.data.body + footer, - draft: true, - })) - } - - console.log(`Release created: ${release.data.html_url}`) - console.log(`Release notes: ${release_notes.data.body}`) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000000..0e87afaea1 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,687 @@ +name: Release NIC +run-name: ${{ inputs.dry_run && '[DRY RUN] ' || '' }}Release NIC ${{ inputs.nic_version }} from ${{ inputs.release_branch }} by @${{ github.actor }} + +on: + workflow_dispatch: + inputs: + nic_version: + description: "Version to release" + required: true + type: string + source_tag: + description: "Source tag to release" + required: false + type: string + chart_version: + description: "Helm Chart version to release" + required: false + type: string + cnab_version: + description: "CNAB version for Azure Marketplace" + required: false + type: string + operator_version: + description: "Operator version to set" + required: false + type: string + release_branch: + description: "Branch to release from" + required: true + type: string + dry_run: + description: "Dry Run?" + type: boolean + default: false + skip_step: + description: "Comma separated list of jobs to skip" + # publish-helm-chart,operator,release-oss,release-plus,certify-openshift-images,aws-marketplace,azure-marketplace,gcp-marketplace,azure-upload,github-release,release-image-notification + type: string + required: false + +defaults: + run: + shell: bash + +permissions: + contents: read + +jobs: + variables: + name: Set Variables + runs-on: ubuntu-24.04 + permissions: + contents: read + outputs: + source_tag: ${{ steps.vars.outputs.stable_tag }} + short_tag: ${{ steps.vars.outputs.short_tag }} + go_code_md5: ${{ steps.vars.outputs.go_code_md5 }} + binary_cache_sign_hit: ${{ steps.binary-cache-sign.outputs.cache-hit }} + date: ${{ steps.vars.outputs.date }} + k8s_version: ${{ steps.vars.outputs.k8s_version }} + steps: + - name: Checkout Repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + fetch-depth: 0 + ref: ${{ inputs.release_branch }} + + - name: Output Variables + id: vars + run: | + if [ -n "${{ inputs.source_tag }}" ]; then + echo "stable_tag=${{ inputs.source_tag }}" >> $GITHUB_OUTPUT + else + ./.github/scripts/variables.sh stable_tag >> $GITHUB_OUTPUT + fi + tag=${{ inputs.nic_version }} + echo "short_tag=${tag%.*}" >> $GITHUB_OUTPUT + ./.github/scripts/variables.sh go_code_md5 >> $GITHUB_OUTPUT + date=$(date "+%Y%m%d") + echo "date=${date}" >> $GITHUB_OUTPUT + k8s_version=$(grep kindest tests/Dockerfile | cut -d ':' -f 2 | cut -d '@' -f 1) + echo "k8s_version=${k8s_version}" >> $GITHUB_OUTPUT + cat $GITHUB_OUTPUT + + - name: Fetch Cached Signed Binary Artifacts + id: binary-cache-sign + uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 + with: + path: ${{ github.workspace }}/tarballs + key: nginx-ingress-release-${{ steps.vars.outputs.go_code_md5 }} + lookup-only: true + + tag: + name: Create Tag on release branch in NIC repo + runs-on: ubuntu-24.04 + permissions: + contents: write + steps: + - name: Checkout NIC repo + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + ref: ${{ inputs.release_branch }} + fetch-depth: 0 + + - name: Create new release Tag + run: | + git config --global user.email "kubernetes@nginx.com" + git config --global user.name "NGINX Kubernetes Team" + branch="${{ inputs.release_branch }}" + tag="v${{ inputs.nic_version }}" + if ! git rev-parse --verify refs/tags/${tag}; then + echo "Adding tag ${tag}." + git tag -a ${tag} -m "Version ${tag#v*}" + echo "Pushing to tag ${tag} to branch ${branch}" + if ! ${{ inputs.dry_run }}; then + git push origin "${tag}" + else + echo "DRY RUN not making any changes" + git push --dry-run origin "${tag}" + fi + else + echo "Warning: Tag ${tag} already exists. Not making any changes" + fi + env: + GITHUB_TOKEN: ${{ secrets.NGINX_PAT }} + + mend: + if: ${{ ! cancelled() && ! failure() && ! inputs.dry_run && ! contains(inputs.skip_step, 'mend') }} + name: Run Mend workflow + uses: ./.github/workflows/mend.yml + needs: [tag] + with: + branch: "v${{ inputs.nic_version }}" + secrets: inherit + + release-oss: + if: ${{ ! cancelled() && ! failure() && ! contains(inputs.skip_step, 'release-oss') }} + name: Release Docker OSS + needs: [variables] + uses: ./.github/workflows/oss-release.yml + strategy: + fail-fast: false + matrix: + tag: + - "${{ inputs.nic_version }}" + - "${{ needs.variables.outputs.short_tag }}" + - "${{ inputs.nic_version }}-${{ needs.variables.outputs.date }}" + - "latest" + with: + gcr_release_registry: true + ecr_public_registry: true + dockerhub_public_registry: true + quay_public_registry: true + github_public_registry: true + source_tag: ${{ needs.variables.outputs.source_tag }} + target_tag: ${{ matrix.tag }} + branch: ${{ inputs.release_branch }} + dry_run: ${{ inputs.dry_run }} + permissions: + contents: read + id-token: write + packages: write + secrets: inherit + + release-plus-gcr-nginx: + if: ${{ ! cancelled() && ! failure() && ! contains(inputs.skip_step, 'release-plus') }} + name: Release Docker Plus + needs: [variables] + uses: ./.github/workflows/plus-release.yml + strategy: + fail-fast: false + matrix: + tag: + - "${{ inputs.nic_version }}" + - "${{ needs.variables.outputs.short_tag }}" + - "${{ inputs.nic_version }}-${{ needs.variables.outputs.date }}" + - "latest" + with: + gcr_release_registry: true + nginx_registry: true + gcr_mktpl_registry: false + ecr_mktpl_registry: false + az_mktpl_registry: false + source_tag: ${{ needs.variables.outputs.source_tag }} + target_tag: ${{ inputs.nic_version }} + branch: ${{ inputs.release_branch }} + dry_run: ${{ inputs.dry_run }} + permissions: + contents: read + id-token: write + secrets: inherit + + release-plus-gcr-mktpl: + if: ${{ ! cancelled() && ! failure() && ! contains(inputs.skip_step, 'release-plus') }} + name: Release Docker Plus + needs: [variables] + uses: ./.github/workflows/plus-release.yml + strategy: + fail-fast: false + matrix: + tag: + - "${{ inputs.nic_version }}" + - "${{ needs.variables.outputs.short_tag }}" + - "${{ inputs.nic_version }}-${{ needs.variables.outputs.date }}" + with: + gcr_release_registry: false + nginx_registry: false + gcr_mktpl_registry: true + ecr_mktpl_registry: false + az_mktpl_registry: false + source_tag: ${{ needs.variables.outputs.source_tag }} + target_tag: ${{ inputs.nic_version }} + branch: ${{ inputs.release_branch }} + dry_run: ${{ inputs.dry_run }} + permissions: + contents: read + id-token: write + secrets: inherit + + release-plus-aws-mktpl: + if: ${{ ! cancelled() && ! failure() && ! contains(inputs.skip_step, 'release-plus') }} + name: Release Docker Plus + needs: [variables] + uses: ./.github/workflows/plus-release.yml + strategy: + fail-fast: false + matrix: + tag: + - "${{ inputs.nic_version }}" + - "${{ inputs.nic_version }}-${{ needs.variables.outputs.date }}" + with: + gcr_release_registry: false + nginx_registry: false + gcr_mktpl_registry: false + ecr_mktpl_registry: true + az_mktpl_registry: false + source_tag: ${{ needs.variables.outputs.source_tag }} + target_tag: ${{ inputs.nic_version }} + branch: ${{ inputs.release_branch }} + dry_run: ${{ inputs.dry_run }} + permissions: + contents: read + id-token: write + secrets: inherit + + release-plus-azure-mktpl: + if: ${{ ! cancelled() && ! failure() && ! contains(inputs.skip_step, 'release-plus') }} + name: Release Docker Plus + needs: [variables] + uses: ./.github/workflows/plus-release.yml + strategy: + fail-fast: false + matrix: + tag: + - "${{ inputs.nic_version }}" + - "${{ inputs.nic_version }}-${{ needs.variables.outputs.date }}" + with: + gcr_release_registry: false + nginx_registry: false + gcr_mktpl_registry: false + ecr_mktpl_registry: false + az_mktpl_registry: true + source_tag: ${{ needs.variables.outputs.source_tag }} + target_tag: ${{ inputs.nic_version }} + branch: ${{ inputs.release_branch }} + dry_run: ${{ inputs.dry_run }} + permissions: + contents: read + id-token: write + secrets: inherit + + publish-helm-chart: + if: ${{ ! cancelled() && ! failure() && ! inputs.dry_run && ! contains(inputs.skip_step, 'publish-helm-chart') }} + name: Publish Helm Chart + uses: ./.github/workflows/publish-helm.yml + with: + branch: ${{ inputs.release_branch }} + ic_version: ${{ inputs.nic_version }} + chart_version: ${{ inputs.chart_version }} + nginx_helm_repo: true + permissions: + contents: write # for pushing to Helm Charts repository + packages: write # for helm to push to GHCR + secrets: inherit + + certify-openshift-images: + if: ${{ ! cancelled() && ! failure() && ! inputs.dry_run && ! contains(inputs.skip_step, 'certify-openshift-images') }} + name: Certify OpenShift UBI images + runs-on: ubuntu-24.04 + needs: [release-oss] + steps: + - name: Checkout Repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + ref: ${{ inputs.release_branch }} + + - name: Certify UBI OSS images in quay + uses: ./.github/actions/certify-openshift-image + continue-on-error: true + with: + image: quay.io/nginx/nginx-ingress:${{ inputs.nic_version }}-ubi + project_id: ${{ secrets.CERTIFICATION_PROJECT_ID }} + pyxis_token: ${{ secrets.PYXIS_API_TOKEN }} + preflight_version: 1.11.1 + + operator: + if: ${{ ! cancelled() && ! failure() && ! inputs.dry_run && ! contains(inputs.skip_step, 'operator') && !contains(inputs.skip_step, 'publish-helm-chart') }} + name: Trigger PR for Operator + runs-on: ubuntu-24.04 + needs: [variables,publish-helm-chart] + steps: + - name: + uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 + with: + github-token: ${{ secrets.NGINX_PAT }} + script: | + await github.rest.actions.createWorkflowDispatch({ + owner: context.repo.owner, + repo: 'nginx-ingress-helm-operator', + workflow_id: 'sync-chart.yml', + ref: 'main', + inputs: { + chart_version: '${{ inputs.chart_version }}', + operator_version: '${{ inputs.operator_version }}', + k8s_version: '${{ needs.variables.outputs.k8s_version }}', + dry_run: '${{ inputs.dry_run }}' + }, + }) + + gcp-marketplace: + if: ${{ ! cancelled() && ! failure() && ! inputs.dry_run && ! contains(inputs.skip_step, 'gcp-marketplace') }} + name: Trigger PR for GCP Marketplace + runs-on: ubuntu-24.04 + needs: [publish-helm-chart,release-plus-gcr-mktpl] + steps: + - name: + uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 + with: + github-token: ${{ secrets.NGINX_PAT }} + script: | + await github.rest.actions.createWorkflowDispatch({ + owner: context.repo.owner, + repo: 'kubernetes-ingress-gcp', + workflow_id: 'sync-chart.yml', + ref: 'main', + inputs: { + chart_version: '${{ inputs.chart_version }}' + }, + }) + + azure-marketplace: + if: ${{ ! cancelled() && ! failure() && ! inputs.dry_run && ! contains(inputs.skip_step, 'azure-marketplace') }} + name: Trigger CNAB Build for Azure Marketplace + runs-on: ubuntu-24.04 + needs: [publish-helm-chart,release-plus-azure-mktpl] + steps: + - name: + uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 + with: + github-token: ${{ secrets.NGINX_PAT }} + script: | + await github.rest.actions.createWorkflowDispatch({ + owner: context.repo.owner, + repo: 'kubernetes-ingress-azure', + workflow_id: 'build-cnab.yml', + ref: 'main', + inputs: { + chart_version: '${{ inputs.chart_version }}', + ic_version: '${{ inputs.nic_version }}', + cnab_version: '${{ inputs.cnab_version }}' + }, + }) + + aws-marketplace: + if: ${{ ! cancelled() && ! failure() && ! inputs.dry_run && ! contains(inputs.skip_step, 'aws-marketplace') }} + name: Publish to AWS Marketplace + runs-on: ubuntu-24.04 + needs: [release-plus-aws-mktpl] + permissions: + contents: read + id-token: write + strategy: + fail-fast: false + matrix: + include: + - image: 709825985650.dkr.ecr.us-east-1.amazonaws.com/nginx/nginx-plus-ingress:${{ inputs.nic_version }}-mktpl + product_id: AWS_PRODUCT_ID + - image: 709825985650.dkr.ecr.us-east-1.amazonaws.com/nginx/nginx-plus-ingress-nap:${{ inputs.nic_version }}-mktpl + product_id: AWS_NAP_WAF_PRODUCT_ID + - image: 709825985650.dkr.ecr.us-east-1.amazonaws.com/nginx/nginx-plus-ingress-dos:${{ inputs.nic_version }}-mktpl + product_id: AWS_NAP_DOS_PRODUCT_ID + - image: 709825985650.dkr.ecr.us-east-1.amazonaws.com/nginx/nginx-plus-ingress-dos-nap:${{ inputs.nic_version }}-mktpl + product_id: AWS_NAP_WAF_DOS_PRODUCT_ID + steps: + - name: Checkout Repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + ref: ${{ inputs.release_branch }} + + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@e3dd6a429d7300a6a4c196c26e071d42e0343502 # v4.0.2 + with: + aws-region: us-east-1 + role-to-assume: ${{ secrets.AWS_ROLE_MARKETPLACE }} + + - name: Publish to AWS Marketplace + uses: nginxinc/aws-marketplace-publish@108e752101152582ed409c5faed859a891e0d7aa # v1.0.7 + continue-on-error: true + with: + version: ${{ inputs.nic_version }} + product-id: ${{ secrets[matrix.product_id] }} + registry: ${{ matrix.image }} + release-notes: https://github.com/nginxinc/kubernetes-ingress/releases/tag/v${{ inputs.nic_version }} + description: | + Best-in-class traffic management solution for services in Amazon EKS. + This is the official implementation of NGINX Ingress Controller (based on NGINX Plus) from NGINX. + usage-instructions: | + This container requires Kubernetes and can be deployed to EKS. + Review the installation instructions https://docs.nginx.com/nginx-ingress-controller/installation/ and utilize the deployment resources available https://github.com/nginxinc/kubernetes-ingress/tree/v${{ inputs.nic_version }}/deployments + Use this image instead of building your own. + + binaries: + name: Process Binaries + runs-on: ubuntu-24.04 + needs: [variables] + permissions: + contents: read + id-token: write # for cosign to sign artifacts + steps: + - name: Checkout Repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + ref: ${{ inputs.release_branch }} + + - name: Fetch Binary Artifacts from Cache + uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 + with: + path: ${{ github.workspace }}/dist + key: nginx-ingress-${{ needs.variables.outputs.go_code_md5 }} + if: ${{ needs.variables.outputs.binary_cache_sign_hit != 'true' }} + + - name: Download Syft + id: syft + uses: anchore/sbom-action/download-syft@df80a981bc6edbc4e220a492d3cbe9f5547a6e75 # v0.17.9 + if: ${{ needs.variables.outputs.binary_cache_sign_hit != 'true' }} + + - name: Install Cosign + uses: sigstore/cosign-installer@dc72c7d5c4d10cd6bcb8cf6e3fd625a9e5e537da # v3.7.0 + if: ${{ needs.variables.outputs.binary_cache_sign_hit != 'true' }} + + - name: Create Tarballs + run: | + ./.github/scripts/create-release-tarballs.sh dist ${{ inputs.nic_version }} + env: + SYFT_BIN: ${{ steps.syft.outputs.cmd }} + if: ${{ needs.variables.outputs.binary_cache_sign_hit != 'true' }} + + - name: Store Tarball Artifacts in Cache + uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 + with: + path: ${{ github.workspace }}/tarballs + key: nginx-ingress-release-${{ needs.variables.outputs.go_code_md5 }} + if: ${{ needs.variables.outputs.binary_cache_sign_hit != 'true' }} + + azure-upload: + if: ${{ ! cancelled() && ! failure() && ! contains(inputs.skip_step, 'azure-upload') }} + name: Upload packages to Azure + runs-on: ubuntu-24.04 + needs: [variables, binaries] + permissions: + id-token: write + contents: read + steps: + - name: Checkout Repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + ref: ${{ inputs.release_branch }} + + - name: Fetch Cached Tarball Artifacts + uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 + with: + key: nginx-ingress-release-${{ needs.variables.outputs.go_code_md5 }} + path: ${{ github.workspace }}/tarballs + fail-on-cache-miss: true + + - name: Azure login + uses: azure/login@a65d910e8af852a8061c627c456678983e180302 # v2.2.0 + with: + client-id: ${{ secrets.AZURE_CLIENT_ID }} + tenant-id: ${{ secrets.AZURE_TENANT_ID }} + subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} + + - name: Azure Upload Release Packages + uses: azure/CLI@089eac9d8cc39f5d003e94f8b65efc51076c9cbd # v2.1.0 + with: + inlineScript: | + for i in $(find tarballs -type f); do + echo -n "Uploading ${i} to kubernetes-ingress/v${{ inputs.nic_version }}/${i##*/} ... " + if ${{ ! inputs.dry_run}}; then + az storage blob upload --auth-mode=login -f "$i" -c ${{ secrets.AZURE_BUCKET_NAME }} \ + --account-name ${{ secrets.AZURE_STORAGE_ACCOUNT }} --overwrite -n kubernetes-ingress/v${{ inputs.nic_version }}/${i##*/} + echo "done" + else + echo "skipped, dry_run." + fi + done + + github-release: + if: ${{ ! cancelled() && ! failure() && ! contains(inputs.skip_step, 'github-release') }} + name: Publish release to GitHub + runs-on: ubuntu-24.04 + needs: [variables, binaries, release-oss, release-plus-gcr-nginx, azure-upload] + permissions: + contents: write # to modify the release + issues: write # to close milestone + actions: read # for slack notification + steps: + - name: Checkout Repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + ref: ${{ inputs.release_branch }} + + - name: Fetch Cached Tarball Artifacts + uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 + with: + key: nginx-ingress-release-${{ needs.variables.outputs.go_code_md5 }} + path: ${{ github.workspace }}/tarballs + fail-on-cache-miss: true + + - name: Upload Release Assets + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # clobber overwrites existing assets of the same name + run: | + if ! ${{ inputs.dry_run }}; then + gh release upload --clobber v${{ inputs.nic_version }} \ + $(find ./tarballs -type f) + else + echo "Skipping adding binaries to Github Release, DRY_RUN" + fi + + - name: Close Release Milestone + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + milestone_number=$(gh api \ + -H "Accept: application/vnd.github.v3+json" \ + /repos/${{ github.repository }}/milestones \ + | jq --arg version "v${{ inputs.nic_version }}" -r \ + '.[] | select(.title == $version) | .number') + if [ -n "${milestone_number}" ]; then + if ! ${{ inputs.dry_run }}; then + gh api --method PATCH -H "Accept: application/vnd.github.v3+json" \ + /repos/${{ github.repository }}/milestones/${milestone_number} \ + -f "title=v${{ inputs.nic_version }}" \ + -f "state=closed"; + else + echo "Skipping closing Github Release milestone, DRY_RUN" + fi + else + echo "Github Milestone not available, closed already." + fi + + - name: Get Github release id + id: release-id + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + release_id=$(gh api \ + -H "Accept: application/vnd.github.v3+json" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + /repos/${{ github.repository }}/releases \ + | jq --arg version "v${{ inputs.nic_version }}" -r \ + '.[] | select(.name == $version) | .id') + echo "release_id=${release_id}" >> $GITHUB_OUTPUT + + - name: Publish Github Release + uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 + with: + script: | + const {RELEASE_ID} = process.env + const release = (await github.rest.repos.updateRelease({ + owner: context.payload.repository.owner.login, + repo: context.payload.repository.name, + release_id: `${RELEASE_ID}`, + draft: false, + })) + console.log(`Release published: ${release.data.html_url}`) + env: + RELEASE_ID: ${{ steps.release-id.outputs.release_id }} + if: ${{ ! inputs.dry_run }} + + - name: Send Notification + uses: 8398a7/action-slack@28ba43ae48961b90635b50953d216767a6bea486 # v3.16.2 + with: + status: custom + custom_payload: | + { + "blocks": [ + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": "NGINX Ingress Controller v${{ inputs.nic_version }} is out! Check it out: https://github.com/nginxinc/kubernetes-ingress/releases/tag/v${{ inputs.nic_version }}" + } + } + ] + } + env: + SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_COMMUNITY }} + if: ${{ ! inputs.dry_run }} + + release-image-notification: + if: ${{ ! cancelled() && ! failure() && ! inputs.dry_run && ! contains(inputs.skip_step, 'release-image-notification') }} + name: Notify Slack channels about image release + runs-on: ubuntu-24.04 + needs: [variables, binaries, release-oss, release-plus-gcr-nginx] + permissions: + contents: read + actions: read + strategy: + fail-fast: false + matrix: + image: ["nginx/nginx-ingress:${{ inputs.nic_version }}", "nginx/nginx-ingress:${{ inputs.nic_version }}-ubi", "nginx/nginx-ingress:${{ inputs.nic_version }}-alpine"] + steps: + - name: Checkout Repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + ref: ${{ inputs.release_branch }} + + - name: Get Image manifest digest + id: digest + run: | + digest=$(docker buildx imagetools inspect ${{ matrix.image }} --format '{{ json . }}' | jq -r .manifest.digest) + + - name: Get Image tag + id: tag + run: | + tag=$(echo ${{ matrix.image }} | cut -d ':' -f 2) + + - name: Get variables for Slack + id: slack + run: | + echo "message=$(git log -1 --pretty=%s)" >> $GITHUB_OUTPUT + echo "date=$(date +%s)" >> $GITHUB_OUTPUT + echo "sha_short=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT + echo "sha_long=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT + + - name: Send Notification + uses: 8398a7/action-slack@28ba43ae48961b90635b50953d216767a6bea486 # v3.16.2 + with: + status: custom + custom_payload: | + { + username: "Docker", + icon_emoji: ":docker:", + mention: "here", + attachments: [{ + title: `New Docker image was pushed to DockerHub for ${process.env.AS_REPO}`, + color: "good", + fields: [{ + title: "Docker Image", + value: ``, + short: true + }, + { + title: "Image digest", + value: "${{ steps.digest.outputs.digest }}", + short: true + }, + { + title: "Commit Message", + value: `${{ steps.slack.outputs.message }}`, + short: true + }, + { + title: "Commit Hash", + value: ``, + short: true + }], + footer: "Update DockerHub Image", + footer_icon: "https://raw.githubusercontent.com/docker-library/docs/c350af05d3fac7b5c3f6327ac82fe4d990d8729c/docker/logo.png", + ts: ${{ steps.slack.outputs.date }} + }] + } + env: + SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }} diff --git a/.github/workflows/retag-images.yml b/.github/workflows/retag-images.yml new file mode 100644 index 0000000000..acea71cdd1 --- /dev/null +++ b/.github/workflows/retag-images.yml @@ -0,0 +1,68 @@ +name: "Retag Dev Images" + +on: + workflow_dispatch: + inputs: + source_tag: + required: true + type: string + target_tag: + required: true + type: string + dry_run: + type: boolean + default: false + workflow_call: + inputs: + source_tag: + required: true + type: string + target_tag: + required: true + type: string + dry_run: + type: boolean + default: false + +defaults: + run: + shell: bash + +permissions: + contents: read + +jobs: + copy-to-gcr-dev-registry: + name: Re-tag images in GCR Dev Registry + runs-on: ubuntu-24.04 + permissions: + contents: read + id-token: write + steps: + - name: Checkout Repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: Authenticate to Google Cloud + id: gcr-auth + uses: google-github-actions/auth@6fc4af4b145ae7821d527454aa9bd537d1f2dc5f # v2.1.7 + with: + token_format: access_token + workload_identity_provider: ${{ secrets.GCR_WORKLOAD_IDENTITY }} + service_account: ${{ secrets.GCR_SERVICE_ACCOUNT }} + + - name: Login to GCR + uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 + with: + registry: gcr.io + username: oauth2accesstoken + password: ${{ steps.gcr-auth.outputs.access_token }} + + - name: Retag images + run: | + export CONFIG_PATH=.github/config/config-gcr-retag + export SOURCE_TAG=${{ inputs.source_tag }} + export TARGET_TAG=${{ inputs.target_tag }} + if ${{ inputs.dry_run }}; then + export DRY_RUN=true + fi + .github/scripts/copy-images.sh diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index 7c3ca5be7e..442c2a97c9 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -1,36 +1,40 @@ name: OpenSSF Scorecards on: - # Only the default branch is supported. + # For Branch-Protection check. Only the default branch is supported. See + # https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection branch_protection_rule: + # To guarantee Maintained check is occasionally updated. See + # https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained schedule: - - cron: '43 20 * * 0' + - cron: "43 20 * * 0" # run every Sunday at 20:43 UTC push: - branches: [ "main" ] + branches: + - main # Declare default permissions as read only. permissions: read-all jobs: analysis: - name: Scorecards analysis - runs-on: ubuntu-latest + name: Scorecard analysis + runs-on: ubuntu-24.04 permissions: # Needed to upload the results to code-scanning dashboard. security-events: write - # Used to receive a badge. + # Needed to publish results and get a badge (see publish_results below). id-token: write - # Needs for private repositories. - contents: read - actions: read + # Uncomment the permissions below if installing in a private repository. + # contents: read + # actions: read steps: - name: "Checkout code" - uses: actions/checkout@a12a3943b4bdde767164f792f33f40b04645d846 # tag=v3.0.0 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: persist-credentials: false - name: "Run analysis" - uses: ossf/scorecard-action@99c53751e09b9529366343771cc321ec74e9bd3d # tag=v2.0.5 + uses: ossf/scorecard-action@62b2cac7ed8198b15735ed49ab1e5cf35480ba46 # v2.4.0 with: results_file: results.sarif results_format: sarif @@ -45,7 +49,7 @@ jobs: # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF # format to the repository Actions tab. - name: "Upload artifact" - uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535 # tag=v3.0.0 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: SARIF file path: results.sarif @@ -53,6 +57,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@5f532563584d71fdef14ee64d17bafb34f751ce5 # tag=v1.0.26 + uses: github/codeql-action/upload-sarif@48ab28a6f5dbc2a99bf1e0131198dd8f1df78169 # v3.28.0 with: sarif_file: results.sarif diff --git a/.github/workflows/setup-smoke.yml b/.github/workflows/setup-smoke.yml new file mode 100644 index 0000000000..e4fbb5b141 --- /dev/null +++ b/.github/workflows/setup-smoke.yml @@ -0,0 +1,176 @@ +name: Setup Smoke tests + +on: + workflow_call: + inputs: + image: + required: true + type: string + target: + required: true + type: string + nap-modules: + required: true + type: string + marker: + required: true + type: string + label: + required: true + type: string + go-md5: + required: true + type: string + build-tag: + required: true + type: string + stable-tag: + required: true + type: string + authenticated: + required: true + type: boolean + k8s-version: + required: true + type: string + +defaults: + run: + shell: bash + +permissions: + contents: read + +jobs: + setup-smoke: + permissions: + contents: read # for docker/build-push-action to read repo content + id-token: write # for OIDC login to GCR + runs-on: ubuntu-24.04 + steps: + - name: Checkout Repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: Set image variables + id: image_details + run: | + echo "name=gcr.io/f5-gcs-7899-ptg-ingrss-ctlr/dev/nginx-ic${{ contains(inputs.nap-modules, 'dos') && '-dos' || '' }}${{ contains(inputs.nap-modules, 'waf') && '-nap' || '' }}${{ contains(inputs.image, 'v5') && '-v5' || '' }}/nginx${{ contains(inputs.image, 'plus') && '-plus' || '' }}-ingress" >> $GITHUB_OUTPUT + echo "build_tag=${{ inputs.build-tag }}${{ contains(inputs.image, 'ubi-9') && '-ubi' || '' }}${{ contains(inputs.image, 'ubi-8') && '-ubi8' || '' }}${{ contains(inputs.image, 'alpine') && '-alpine' || '' }}${{ contains(inputs.target, 'aws') && '-mktpl' || '' }}${{ contains(inputs.image, 'fips') && '-fips' || ''}}" >> $GITHUB_OUTPUT + echo "stable_tag=${{ inputs.stable-tag }}${{ contains(inputs.image, 'ubi-9') && '-ubi' || '' }}${{ contains(inputs.image, 'ubi-8') && '-ubi8' || '' }}${{ contains(inputs.image, 'alpine') && '-alpine' || '' }}${{ contains(inputs.target, 'aws') && '-mktpl' || '' }}${{ contains(inputs.image, 'fips') && '-fips' || ''}}" >> $GITHUB_OUTPUT + + - name: Authenticate to Google Cloud + id: auth + uses: google-github-actions/auth@6fc4af4b145ae7821d527454aa9bd537d1f2dc5f # v2.1.7 + with: + token_format: access_token + workload_identity_provider: ${{ secrets.GCR_WORKLOAD_IDENTITY }} + service_account: ${{ secrets.GCR_SERVICE_ACCOUNT }} + if: ${{ inputs.authenticated }} + + - name: Login to GCR + uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 + with: + registry: gcr.io + username: oauth2accesstoken + password: ${{ steps.auth.outputs.access_token }} + if: ${{ inputs.authenticated }} + + - name: Check if stable image exists + id: stable_exists + run: | + if docker pull ${{ steps.image_details.outputs.name }}:${{ steps.image_details.outputs.stable_tag }}; then + echo "exists=true" >> $GITHUB_OUTPUT + fi + if: ${{ inputs.authenticated }} + + - name: NAP modules + id: nap_modules + run: | + [[ "${{ inputs.nap-modules }}" == "waf,dos" ]] && modules="waf-dos" || modules="${{ inputs.nap-modules }}" + echo "modules=${modules}" >> $GITHUB_OUTPUT + [[ "${{ inputs.nap-modules }}" =~ waf ]] && agent="true" || agent="false" + echo "agent=${agent}" >> $GITHUB_OUTPUT + if: ${{ inputs.nap-modules }} + + - name: Pull build image + run: | + docker pull ${{ steps.image_details.outputs.name }}:${{ steps.image_details.outputs.build_tag }} + if: ${{ inputs.authenticated && steps.stable_exists.outputs.exists != 'true' }} + + - name: Fetch Cached Artifacts + uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 + with: + path: ${{ github.workspace }}/dist + key: nginx-ingress-${{ inputs.go-md5 }} + fail-on-cache-miss: true + if: ${{ !inputs.authenticated }} + + - name: Check if test image exists + id: check-image + run: | + docker manifest inspect "gcr.io/f5-gcs-7899-ptg-ingrss-ctlr/dev/test-runner:${{ hashFiles('./tests/requirements.txt', './tests/Dockerfile') || 'latest' }}" + shell: bash + continue-on-error: true + if: ${{ inputs.authenticated }} + + - name: Build Test-Runner Container + uses: docker/build-push-action@48aba3b46d1b1fec4febb7c5d0c644b249a11355 # v6.10.0 + with: + file: tests/Dockerfile + context: "." + cache-from: type=gha,scope=test-runner + tags: "gcr.io/f5-gcs-7899-ptg-ingrss-ctlr/dev/test-runner:${{ hashFiles('./tests/requirements.txt', './tests/Dockerfile') || 'latest' }}" + pull: true + push: ${{ inputs.authenticated }} + load: ${{ !inputs.authenticated }} + if: ${{ ( !inputs.authenticated || steps.check-image.outcome == 'failure' ) }} + + - name: Build ${{ inputs.image }} Container + uses: docker/build-push-action@48aba3b46d1b1fec4febb7c5d0c644b249a11355 # v6.10.0 + with: + file: build/Dockerfile + context: "." + cache-from: type=gha,scope=${{ inputs.image }}${{ steps.nap_modules.outputs.name != '' && format('-{0}', steps.nap_modules.outputs.name) || '' }} + target: goreleaser + tags: "${{ steps.image_details.outputs.name }}:${{ steps.image_details.outputs.build_tag }}" + load: true + pull: true + build-args: | + BUILD_OS=${{ inputs.image }} + IC_VERSION=CI + ${{ contains(inputs.image, 'nap') && format('NAP_MODULES={0}', steps.nap_modules.outputs.modules) || '' }} + ${{ contains(inputs.nap-modules,'waf') && format('NGINX_AGENT={0}', steps.nap_modules.outputs.agent) || '' }} + ${{ contains(inputs.marker, 'appprotect') && 'DEBIAN_VERSION=buster-slim' || '' }} + secrets: | + ${{ contains(inputs.image, 'nap') && format('"nginx-repo.crt={0}"', secrets.NGINX_AP_CRT) || format('"nginx-repo.crt={0}"', secrets.NGINX_CRT) }} + ${{ contains(inputs.image, 'nap') && format('"nginx-repo.key={0}"', secrets.NGINX_AP_KEY) || format('"nginx-repo.key={0}"', secrets.NGINX_KEY) }} + ${{ contains(inputs.image, 'ubi') && format('"rhel_license={0}"', secrets.RHEL_LICENSE) || '' }} + if: ${{ !inputs.authenticated }} + + - name: Generate WAF v5 tgz from JSON + run: | + docker run --rm --user root -v /var/run/docker.sock:/var/run/docker.sock -v ${{ github.workspace }}/tests/data/ap-waf-v5:/data gcr.io/f5-gcs-7899-ptg-ingrss-ctlr/nap/waf-compiler:5.4.0 -p /data/wafv5.json -o /data/wafv5.tgz + if: ${{ contains(inputs.image, 'nap-v5')}} + + - name: Run Smoke Tests + id: smoke-tests + uses: ./.github/actions/smoke-tests + with: + image-type: ${{ inputs.image }} + image-name: ${{ steps.image_details.outputs.name }} + tag: ${{ steps.image_details.outputs.build_tag }} + marker: ${{ inputs.marker != '' && inputs.marker || '' }} + label: ${{ inputs.label }} + k8s-version: ${{ inputs.k8s-version }} + azure-ad-secret: ${{ secrets.AZURE_AD_AUTOMATION }} + registry-token: ${{ steps.auth.outputs.access_token }} + test-image: "gcr.io/f5-gcs-7899-ptg-ingrss-ctlr/dev/test-runner:${{ hashFiles('./tests/requirements.txt', './tests/Dockerfile') || 'latest' }}" + plus-jwt: ${{ secrets.PLUS_JWT }} + if: ${{ steps.stable_exists.outputs.exists != 'true' }} + + - name: Upload Test Results + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + with: + name: ${{ steps.smoke-tests.outputs.test-results-name }} + path: ${{ steps.smoke-tests.outputs.test-results-path }} + if: ${{ !cancelled() && steps.stable_exists.outputs.exists != 'true' }} diff --git a/.github/workflows/single-image-regression.yml b/.github/workflows/single-image-regression.yml new file mode 100644 index 0000000000..957f9bd5ca --- /dev/null +++ b/.github/workflows/single-image-regression.yml @@ -0,0 +1,112 @@ +name: Run python tests on single image +run-name: Testing ${{ inputs.image }}:${{ inputs.tag }} on ${{ inputs.k8s-version }} by @${{ github.actor }} + +on: + workflow_dispatch: + inputs: + image: + type: string + description: "Image to test" + required: true + tag: + type: string + description: "Image tag to test" + required: true + k8s-version: + type: string + description: "k8s version to test with, e.g. 1.30.0" + required: true + type: + type: string + description: "oss or plus" + required: false + default: oss + marker: + type: string + description: "pytest markers to apply" + required: false + default: "'not upgrade'" + test-image-tag: + type: string + description: "The tag for the test image" + required: false + default: latest + workflow_call: + inputs: + image: + type: string + description: "Image to test" + required: true + tag: + type: string + description: "Image tag to test" + required: true + k8s-version: + type: string + description: "e.g. 1.30.0" + required: true + type: + type: string + description: "oss or plus" + required: false + default: oss + marker: + type: string + description: "pytest markers to apply" + required: false + default: "'not upgrade'" + +defaults: + run: + shell: bash + +concurrency: + group: ${{ github.ref_name }}-single-run + cancel-in-progress: false + +permissions: + contents: read + +jobs: + checks: + name: Run regression + runs-on: ubuntu-24.04 + permissions: + contents: read + id-token: write + steps: + - name: Checkout Repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: Authenticate to Google Cloud + id: auth + uses: google-github-actions/auth@6fc4af4b145ae7821d527454aa9bd537d1f2dc5f # v2.1.7 + with: + token_format: access_token + workload_identity_provider: ${{ secrets.GCR_WORKLOAD_IDENTITY }} + service_account: ${{ secrets.GCR_SERVICE_ACCOUNT }} + + - name: Login to GCR + uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 + with: + registry: gcr.io + username: oauth2accesstoken + password: ${{ steps.auth.outputs.access_token }} + + - name: Pull image to local docker engine + run: | + docker pull ${{ inputs.image }}:${{ inputs.tag }} + + - name: Run Tests + uses: ./.github/actions/smoke-tests + with: + image-type: ${{ inputs.type }} + image-name: ${{ inputs.image }} + tag: ${{ inputs.tag }} + marker: ${{ inputs.marker }} + label: "${{ inputs.image }} regression" + k8s-version: ${{ inputs.k8s-version }} + azure-ad-secret: ${{ secrets.AZURE_AD_AUTOMATION }} + registry-token: ${{ steps.auth.outputs.access_token }} + test-image: "gcr.io/f5-gcs-7899-ptg-ingrss-ctlr/dev/test-runner:${{ inputs.test-image-tag }}" + plus-jwt: ${{ secrets.PLUS_JWT }} diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 5ca0e00269..43d555599a 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -1,29 +1,29 @@ -name: 'Close stale issues and PRs' +name: "Close stale issues and PRs" on: schedule: - - cron: '30 1 * * *' + - cron: "30 1 * * *" # run every day at 01:30 UTC -permissions: # added using https://github.com/step-security/secure-workflows +permissions: contents: read jobs: stale: permissions: - issues: write # for actions/stale to close stale issues - pull-requests: write # for actions/stale to close stale PRs - runs-on: ubuntu-20.04 + issues: write # for actions/stale to close stale issues + pull-requests: write # for actions/stale to close stale PRs + runs-on: ubuntu-24.04 steps: - - uses: actions/stale@v6 + - uses: actions/stale@28ca1036281a5e5922ead5184a1bbf96e5fc984e # v9.0.0 with: repo-token: ${{ secrets.GITHUB_TOKEN }} - stale-issue-message: 'This issue is stale because it has been open 90 days with no activity. Remove stale label or comment or this will be closed in 10 days.' - stale-pr-message: 'This PR is stale because it has been open 90 days with no activity. Remove stale label or comment or this will be closed in 10 days.' - close-issue-message: 'This issue was closed because it has been stalled for 10 days with no activity.' - close-pr-message: 'This PR was closed because it has been stalled for 10 days with no activity.' - stale-issue-label: 'stale' - stale-pr-label: 'stale' + stale-issue-message: "This issue is stale because it has been open 90 days with no activity. Remove stale label or comment or this will be closed in 10 days." + stale-pr-message: "This PR is stale because it has been open 90 days with no activity. Remove stale label or comment or this will be closed in 10 days." + close-issue-message: "This issue was closed because it has been stalled for 10 days with no activity." + close-pr-message: "This PR was closed because it has been stalled for 10 days with no activity." + stale-issue-label: "stale" + stale-pr-label: "stale" exempt-all-assignees: true - exempt-issue-labels: 'proposal' + exempt-issue-labels: "proposal" operations-per-run: 100 days-before-stale: 90 days-before-close: 10 diff --git a/.github/workflows/sync.yml b/.github/workflows/sync.yml deleted file mode 100644 index 59692331d3..0000000000 --- a/.github/workflows/sync.yml +++ /dev/null @@ -1,52 +0,0 @@ -name: Sync - -on: - workflow_run: - branches: main - workflows: - - "CI" - types: - - completed - -concurrency: - group: ${{ github.ref_name }}-sync - cancel-in-progress: true - -jobs: - # This job sync this repo to our internal repo - repo-sync: - runs-on: ubuntu-20.04 - if: ${{ github.event.repository.fork == false }} - steps: - - name: Repo Sync - uses: wei/git-sync@v3 - with: - source_repo: "nginxinc/kubernetes-ingress" - source_branch: "main" - destination_repo: ${{ secrets.SYNC_DEST_REPO_URL }} - destination_branch: "main" - ssh_private_key: ${{ secrets.SYNC_SSH_PRIVATE_KEY }} - - # This job sync the labels across the various repos - labels-sync: - strategy: - # don't break another job if one is failed - fail-fast: false - matrix: - repo: - - nginxinc/kubernetes-ingress - - nginxinc/nginx-ingress-helm-operator - - nginxinc/nginx-prometheus-exporter - - nginxinc/nginx-plus-go-client - - nginxinc/nginx-asg-sync - runs-on: ubuntu-20.04 - if: ${{ github.event.repository.fork == false }} - steps: - - name: Checkout - uses: actions/checkout@v3 - - name: Sync Labels - uses: micnncim/action-label-syncer@v1 - with: - repository: ${{ matrix.repo }} - token: ${{ secrets.NGINX_PAT }} - prune: true diff --git a/.github/workflows/update-docker-images.yml b/.github/workflows/update-docker-images.yml index 090ec9aa68..fe65c8267e 100644 --- a/.github/workflows/update-docker-images.yml +++ b/.github/workflows/update-docker-images.yml @@ -2,8 +2,15 @@ name: Update Docker Images on: schedule: - - cron: '0 1 * * *' + - cron: "0 1 * * 0" # run every week at 01:00 UTC on Sunday workflow_dispatch: + inputs: + tag: + description: "Update images with tag" + required: true + dry_run: + type: boolean + default: false defaults: run: @@ -13,179 +20,169 @@ concurrency: group: ${{ github.ref_name }}-update cancel-in-progress: true -jobs: +permissions: + contents: read +jobs: variables: - name: Get versions of base images - runs-on: ubuntu-20.04 + name: Set variables for workflow + runs-on: ubuntu-24.04 outputs: - kic-tag: ${{ steps.kic.outputs.tag }} - versions: ${{ steps.versions.outputs.matrix }} - sha_short: ${{ steps.vars.outputs.sha_short }} - sha_long: ${{ steps.vars.outputs.sha_long }} - k8s_version: ${{ steps.vars.outputs.k8s_version }} + tag: ${{ steps.kic.outputs.tag }} + short_tag: ${{ steps.kic.outputs.short }} + date: ${{ steps.kic.outputs.date }} + matrix: ${{ steps.kic.outputs.matrix }} steps: - name: Checkout Repository - uses: actions/checkout@v3 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: fetch-depth: 0 - - name: Set KIC version + + - name: Set variables id: kic run: | tag="$(git tag --sort=-version:refname | head -n1)" - echo "tag=${tag//v}" >> $GITHUB_OUTPUT - - name: Checkout Repository at ${{ steps.kic.outputs.tag }} - uses: actions/checkout@v3 - with: - ref: refs/tags/v${{ steps.kic.outputs.tag }} - - name: Set NGINX versions - id: versions - run: | - nginx=library/$(grep -m1 "FROM nginx:" < build/Dockerfile | awk -F" " '{print $2}') - nginx_alpine=library/nginx:$(grep -m1 "FROM.*nginx:.*alpine" < build/Dockerfile | awk -F"[ :]" '{print $3}') - nginx_ubi=$(grep "FROM redhat" < build/Dockerfile | awk -F" " '{print $2}') - echo "matrix=[{\"version\": \"${nginx}\", \"distro\": \"debian\"}, {\"version\": \"${nginx_alpine}\", \"distro\": \"alpine\"}, {\"version\": \"${nginx_ubi}\", \"distro\": \"ubi\"}]" >> $GITHUB_OUTPUT - - name: Set other variables - id: vars - run: | - echo "sha_short=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT - echo "sha_long=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT - echo "k8s_version=$(grep -m1 'FROM kindest/node' > $GITHUB_OUTPUT + if [ -n "${{ inputs.tag }}" ]; then + tag=${{ inputs.tag }} + else + tag=${tag//v} + fi + echo "tag=${tag}" >> $GITHUB_OUTPUT + date=$(date "+%Y%m%d") + echo "date=${date}" >> $GITHUB_OUTPUT + short="${tag%.*}" + echo "short=$short" >> $GITHUB_OUTPUT + echo "matrix=$(cat .github/data/patch-images.json | jq -c)" >> $GITHUB_OUTPUT + cat $GITHUB_OUTPUT - check: - name: Check if updates are needed - runs-on: ubuntu-20.04 - needs: variables - outputs: - needs-updating-debian: ${{ steps.needs.outputs.debian }} - needs-updating-alpine: ${{ steps.needs.outputs.alpine }} - needs-updating-ubi: ${{ steps.needs.outputs.ubi }} + patch-images: + name: Patch Images + needs: [variables] strategy: + fail-fast: false matrix: - base_image: ${{ fromJson(needs.variables.outputs.versions) }} - steps: - - name: Build KIC tag - id: dist - run: | - if [ ${{ matrix.base_image.distro }} == "debian" ]; then dist=""; else dist="-${{ matrix.base_image.distro }}"; fi - echo "tag=${{ needs.variables.outputs.kic-tag }}${dist}" >> $GITHUB_OUTPUT - - name: Check if update available for ${{ matrix.base_image.version }} - id: update - uses: lucacome/docker-image-update-checker@v1 - with: - base-image: ${{ matrix.base_image.version}} - image: nginx/nginx-ingress:${{ steps.dist.outputs.tag }} - - id: needs - run: echo "${{ matrix.base_image.distro }}=${{ steps.update.outputs.needs-updating }}" >> $GITHUB_OUTPUT + include: ${{ fromJSON( needs.variables.outputs.matrix ) }} + uses: ./.github/workflows/patch-image.yml + with: + platforms: ${{ matrix.platforms }} + image: ${{ matrix.source_image }} + tag: ${{ matrix.source_os == 'debian' && needs.variables.outputs.tag || format('{0}-{1}', needs.variables.outputs.tag, matrix.source_os) }} + ic_version: ${{ needs.variables.outputs.tag }} + target_image: ${{ matrix.target_image }} + target_tag: ${{ matrix.source_os == 'debian' && format('{0}-{1}', needs.variables.outputs.tag, needs.variables.outputs.date) || format('{0}-{1}-{2}', needs.variables.outputs.tag, needs.variables.outputs.date, matrix.source_os) }} + permissions: + contents: read + id-token: write + secrets: inherit - binary: - if: ${{ needs.check.outputs.needs-updating-debian == 'true' || needs.check.outputs.needs-updating-alpine == 'true' || needs.check.outputs.needs-updating-ubi == 'true' }} - name: Build binaries - runs-on: ubuntu-20.04 - needs: [check, variables] - steps: - - name: Checkout Repository - uses: actions/checkout@v3 - with: - fetch-depth: 0 - ref: refs/tags/v${{ needs.variables.outputs.kic-tag }} - - name: Setup Golang Environment - uses: actions/setup-go@v3 - with: - go-version-file: go.mod - cache: true - - name: Determine GOPATH - id: go - run: echo "go_path=$(go env GOPATH)" >> $GITHUB_OUTPUT - - name: Build binaries - uses: goreleaser/goreleaser-action@v3 - with: - version: latest - args: build --rm-dist --id kubernetes-ingress - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - GOPATH: ${{ steps.go.outputs.go_path }} - - name: Store Artifacts in Cache - uses: actions/cache@v3 - with: - path: ${{ github.workspace }}/dist - key: nginx-ingress-${{ github.run_id }}-${{ github.run_number }} + release-oss-internal: + name: "Publish Docker OSS ${{ needs.variables.outputs.tag }}-${{ needs.variables.outputs.date }} to internal release Registries" + needs: [variables, patch-images] + uses: ./.github/workflows/oss-release.yml + with: + gcr_release_registry: true + ecr_public_registry: false + dockerhub_public_registry: false + quay_public_registry: false + github_public_registry: false + source_tag: "${{ needs.variables.outputs.tag }}-${{ needs.variables.outputs.date }}" + target_tag: "${{ needs.variables.outputs.tag }}-${{ needs.variables.outputs.date }}" + branch: "release-${{ needs.variables.outputs.short_tag }}" + dry_run: ${{ inputs.dry_run || false }} + permissions: + contents: read + id-token: write + packages: write + secrets: inherit - test: - name: Run tests - runs-on: ubuntu-20.04 - needs: [check, variables, binary] + release-oss-public: + name: Publish Docker OSS ${{ matrix.tag }} to Public Registries + needs: [variables, patch-images] strategy: + fail-fast: false matrix: - include: - - image: debian - marker: ingresses - needs-updating: ${{ needs.check.outputs.needs-updating-debian }} - - image: alpine - marker: "vs vsr" - needs-updating: ${{ needs.check.outputs.needs-updating-alpine }} - - image: ubi - marker: "policies ts" - needs-updating: ${{ needs.check.outputs.needs-updating-ubi }} - steps: - - name: Checkout Repository - uses: actions/checkout@v3 - with: - ref: refs/tags/v${{ needs.variables.outputs.kic-tag }} - if: ${{ matrix.needs-updating == 'true' }} - - name: Fetch Cached Artifacts - uses: actions/cache@v3 - with: - path: ${{ github.workspace }}/dist - key: nginx-ingress-${{ github.run_id }}-${{ github.run_number }} - if: ${{ matrix.needs-updating == 'true' }} - - name: Run Smoke Tests - id: smoke-tests - uses: ./.github/actions/smoke-tests - with: - image: ${{ matrix.image }} - marker: ${{ matrix.marker }} - k8s-version: ${{ needs.variables.outputs.k8s_version }} - if: ${{ matrix.needs-updating == 'true' }} - - name: Upload Test Results - uses: actions/upload-artifact@v3 - with: - name: ${{ steps.smoke-tests.outputs.test-results-name }} - path: ${{ github.workspace }}/tests/${{ steps.smoke-tests.outputs.test-results-name }}.html - if: always() - - release-docker-debian: - name: Release Debian Image - needs: [binary, check, variables] - uses: ./.github/workflows/build-oss.yml + tag: + - "${{ needs.variables.outputs.tag }}" + - "${{ needs.variables.outputs.short_tag }}" + - "${{ needs.variables.outputs.tag }}-${{ needs.variables.outputs.date }}" + - "latest" + uses: ./.github/workflows/oss-release.yml with: - platforms: linux/arm,linux/arm64,linux/amd64,linux/ppc64le,linux/s390x - image: debian - tag: ${{ needs.variables.outputs.kic-tag }} - sha_long: ${{ needs.variables.outputs.sha_long }} + gcr_release_registry: false + ecr_public_registry: true + dockerhub_public_registry: true + quay_public_registry: true + github_public_registry: true + source_tag: "${{ needs.variables.outputs.tag }}-${{ needs.variables.outputs.date }}" + target_tag: ${{ matrix.tag }} + branch: "release-${{ needs.variables.outputs.short_tag }}" + dry_run: ${{ inputs.dry_run || false }} + permissions: + contents: read + id-token: write + packages: write secrets: inherit - if: ${{ needs.check.outputs.needs-updating-debian }} - release-docker-alpine: - name: Release Alpine Image - needs: [binary, check, variables] - uses: ./.github/workflows/build-oss.yml + release-plus-nginx: + name: Publish Docker Plus ${{ matrix.tag }} to NGINX registry + needs: [variables, patch-images] + strategy: + fail-fast: false + matrix: + tag: + - "${{ needs.variables.outputs.tag }}" + - "${{ needs.variables.outputs.short_tag }}" + - "${{ needs.variables.outputs.tag }}-${{ needs.variables.outputs.date }}" + - "latest" + uses: ./.github/workflows/plus-release.yml with: - platforms: linux/arm,linux/arm64,linux/amd64,linux/ppc64le,linux/s390x - image: alpine - tag: ${{ needs.variables.outputs.kic-tag }} - sha_long: ${{ needs.variables.outputs.sha_long }} + nginx_registry: true + gcr_release_registry: false + gcr_mktpl_registry: false + ecr_mktpl_registry: false + az_mktpl_registry: false + source_tag: "${{ needs.variables.outputs.tag }}-${{ needs.variables.outputs.date }}" + target_tag: ${{ matrix.tag }} + branch: "release-${{ needs.variables.outputs.short_tag }}" + dry_run: ${{ inputs.dry_run || false }} + permissions: + contents: read + id-token: write secrets: inherit - if: ${{ needs.check.outputs.needs-updating-alpine }} - release-docker-ubi: - name: Release UBI Image - needs: [binary, check, variables] - uses: ./.github/workflows/build-oss.yml + release-plus-internal: + name: Publish Docker Plus ${{ needs.variables.outputs.tag }}-${{ needs.variables.outputs.date }} to internal release Registries + needs: [variables, patch-images] + uses: ./.github/workflows/plus-release.yml with: - platforms: linux/arm64,linux/amd64,linux/s390x - image: ubi - tag: ${{ needs.variables.outputs.kic-tag }} - sha_long: ${{ needs.variables.outputs.sha_long }} + nginx_registry: false + gcr_release_registry: true + gcr_mktpl_registry: false + ecr_mktpl_registry: false + az_mktpl_registry: false + source_tag: "${{ needs.variables.outputs.tag }}-${{ needs.variables.outputs.date }}" + target_tag: "${{ needs.variables.outputs.tag }}-${{ needs.variables.outputs.date }}" + branch: "release-${{ needs.variables.outputs.short_tag }}" + dry_run: ${{ inputs.dry_run || false }} + permissions: + contents: read + id-token: write secrets: inherit - if: ${{ needs.check.outputs.needs-updating-ubi }} + + certify-openshift-images: + name: Certify OpenShift UBI images + runs-on: ubuntu-24.04 + needs: [variables, release-oss-public] + steps: + - name: Checkout Repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: Certify UBI OSS images in quay + uses: ./.github/actions/certify-openshift-image + with: + image: quay.io/nginx/nginx-ingress:${{ needs.variables.outputs.tag }}-ubi + project_id: ${{ secrets.CERTIFICATION_PROJECT_ID }} + pyxis_token: ${{ secrets.PYXIS_API_TOKEN }} + platforms: "" + preflight_version: 1.11.1 + submit: ${{ ! inputs.dry_run || true }} diff --git a/.github/workflows/update-docker-sha.yml b/.github/workflows/update-docker-sha.yml new file mode 100644 index 0000000000..1eb2776695 --- /dev/null +++ b/.github/workflows/update-docker-sha.yml @@ -0,0 +1,99 @@ +name: "Update pinned container SHAs" +run-name: Update pinned container SHAs, triggered from ${{ github.event_name }} by @${{ github.actor }} + +on: + workflow_dispatch: + inputs: + source_branch: + required: true + type: string + default: "main" + excludes: + description: Comma separated list of strings to exclude images from the update + required: false + type: string + default: "" + dry_run: + type: boolean + default: false + schedule: + - cron: "0 1 * * 1-5" # 01:00 UTC Mon-Fri + +defaults: + run: + shell: bash + +permissions: + contents: read + +jobs: + vars: + permissions: + contents: read + runs-on: ubuntu-24.04 + outputs: + source_branch: ${{ steps.vars.outputs.source_branch }} + steps: + - name: Set vars + id: vars + run: | + source_branch=main + if [ -n "${{ inputs.source_branch }}" ]; then + source_branch=${{ inputs.source_branch }} + fi + echo "source_branch=${source_branch}" >> $GITHUB_OUTPUT + + update-docker-sha: + permissions: + contents: write + runs-on: ubuntu-24.04 + needs: [vars] + steps: + - name: Checkout Repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + ref: ${{ needs.vars.outputs.source_branch }} + + - name: Update images + id: update_images + run: | + ARGS="" + if [ -n ${{ github.event.inputs.excludes }} ]; then + ARGS="--exclude ${{ github.event.inputs.excludes }}" + fi + .github/scripts/docker-updater.sh ./build/Dockerfile $ARGS + .github/scripts/docker-updater.sh ./build/dependencies/Dockerfile.ubi $ARGS + .github/scripts/docker-updater.sh ./tests/Dockerfile $ARGS + files=$(git diff --name-only) + if [[ $files == *"Dockerfile"* ]]; then + echo "change_detected=true" >> $GITHUB_OUTPUT + else + echo "change_detected=false" >> $GITHUB_OUTPUT + fi + docker_md5=$(find . -type f -name "Dockerfile*" -exec md5sum {} + | LC_ALL=C sort | md5sum | awk '{ print $1 }') + echo "docker_md5=${docker_md5:0:8}" >> $GITHUB_OUTPUT + echo $GITHUB_OUTPUT + + - name: Create Pull Request + uses: peter-evans/create-pull-request@67ccf781d68cd99b580ae25a5c18a1cc84ffff1f # v7.0.6 + id: pr + with: + token: ${{ secrets.NGINX_PAT }} + commit-message: Update docker images ${{ steps.update_images.outputs.docker_md5 }} + title: Docker image update ${{ steps.update_images.outputs.docker_md5 }} + branch: deps/image-update-${{ needs.vars.outputs.source_branch }}-${{ steps.update_images.outputs.docker_md5 }} + author: nginx-bot + labels: | + dependencies + docker + needs cherry pick + body: | + This automated PR updates pinned container image SHAs to latest. + if: ${{ !inputs.dry_run && steps.update_images.outputs.change_detected == 'true' }} + + - name: Enable auto-merge for Docker update PRs + run: gh pr merge --auto --squash "$PR_URL" + env: + PR_URL: ${{ steps.pr.outputs.pull-request-url }} + GITHUB_TOKEN: ${{ secrets.NGINX_PAT }} + if: ${{ !inputs.dry_run && steps.update_images.outputs.change_detected == 'true' }} diff --git a/.github/workflows/update-kubernetes-version.yml b/.github/workflows/update-kubernetes-version.yml new file mode 100644 index 0000000000..8afa03edd6 --- /dev/null +++ b/.github/workflows/update-kubernetes-version.yml @@ -0,0 +1,55 @@ +name: Update Kubernetes version in Helm chart +on: + push: + branches: + - main + - release-* + paths: + - tests/Dockerfile + +concurrency: + group: ${{ github.ref_name }}-k8s-version + cancel-in-progress: true + +permissions: + contents: read + +jobs: + update-k8s-version: + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: Get current k8s version from Kind image + id: k8s-version + run: | + v=$(grep kindest tests/Dockerfile | cut -d ':' -f 2 | cut -d '@' -f 1) + echo "version=${v}" >> $GITHUB_OUTPUT + cat $GITHUB_OUTPUT + + - name: Search for the version in the schema file + id: search + run: | + found="false" + if grep -q ${{ steps.k8s-version.outputs.version }} charts/nginx-ingress/values.schema.json; then + found="true" + fi + echo "found=$found" >> $GITHUB_OUTPUT + cat $GITHUB_OUTPUT + + - name: Replace version in Helm schema file + run: | + sed -i -e "s#/v[0-9]\+\.[0-9]\+\.[0-9]\+/_definitions.json#/${{ steps.k8s-version.outputs.version }}/_definitions.json#" charts/nginx-ingress/values.schema.json + if: ${{ steps.search.outputs.found == 'false' }} + + - name: Create Pull Request + uses: peter-evans/create-pull-request@67ccf781d68cd99b580ae25a5c18a1cc84ffff1f # v7.0.6 + with: + token: ${{ secrets.NGINX_PAT }} + commit-message: update kubernetes version to ${{ steps.k8s-version.outputs.version }} in helm schema + title: update kubernetes version to ${{ steps.k8s-version.outputs.version }} in helm schema + branch: chore/k8s-${{ steps.k8s-version.outputs.version }} + author: nginx-bot + body: | + This automated PR updates the helm schema k8s version to ${{ steps.k8s-version.outputs.version }}. + if: ${{ steps.search.outputs.found == 'false' }} diff --git a/.github/workflows/update-release-draft.yml b/.github/workflows/update-release-draft.yml new file mode 100644 index 0000000000..bf5c1cd2bb --- /dev/null +++ b/.github/workflows/update-release-draft.yml @@ -0,0 +1,83 @@ +name: Update GitHub Release Draft + +on: + workflow_dispatch: + inputs: + branch: + description: "Release branch to update from" + required: true + type: string + +defaults: + run: + shell: bash + +concurrency: + group: ${{ github.ref_name }}-release-draft + cancel-in-progress: true + +permissions: + contents: read + +jobs: + variables: + name: Set variables + runs-on: ubuntu-24.04 + permissions: + contents: read + outputs: + chart_version: ${{ steps.vars.outputs.chart_version }} + steps: + - name: Checkout Repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + ref: ${{ inputs.branch }} + + - name: Setup Golang Environment + uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0 + with: + go-version-file: go.mod + + - name: Set Variables + id: vars + run: | + source .github/data/version.txt + echo "chart_version=${HELM_CHART_VERSION}" >> $GITHUB_OUTPUT + + - name: Output variables + run: | + echo chart_version: ${{ steps.vars.outputs.chart_version }} + + update-release-draft: + name: Update Release Draft + runs-on: ubuntu-24.04 + needs: [variables] + permissions: + contents: write + steps: + - name: Checkout Repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + ref: ${{ inputs.branch }} + + - name: Create/Update Draft + uses: lucacome/draft-release@5d29432a46bff6c122cd4b07a1fb94e1bb158d34 # v1.1.1 + id: release-notes + with: + minor-label: "enhancement" + major-label: "change" + publish: false + collapse-after: 50 + variables: | + helm-chart=${{ needs.variables.outputs.chart_version }} + notes-footer: | + ## Upgrade + - For NGINX, use the {{version}} images from our [DockerHub](https://hub.docker.com/r/nginx/nginx-ingress/tags?page=1&ordering=last_updated&name={{version-number}}), [GitHub Container](https://github.com/nginxinc/kubernetes-ingress/pkgs/container/kubernetes-ingress), [Amazon ECR Public Gallery](https://gallery.ecr.aws/nginx/nginx-ingress) or [Quay.io](https://quay.io/repository/nginx/nginx-ingress). + - For NGINX Plus, use the {{version}} images from the F5 Container registry, the [AWS Marketplace](https://aws.amazon.com/marketplace/search/?CREATOR=741df81b-dfdc-4d36-b8da-945ea66b522c&FULFILLMENT_OPTION_TYPE=CONTAINER&filters=CREATOR%2CFULFILLMENT_OPTION_TYPE), the [GCP Marketplace](https://console.cloud.google.com/marketplace/browse?filter=partner:F5,%20Inc.&filter=solution-type:k8s&filter=category:networking), [Azure Marketplace](https://azuremarketplace.microsoft.com/en-gb/marketplace/apps/category/containers?page=1&search=f5&subcategories=container-apps) or build your own image using the {{version}} source code. + - For Helm, use version {{helm-chart}} of the chart. + + ## Resources + - Documentation -- https://docs.nginx.com/nginx-ingress-controller/ + - Configuration examples -- https://github.com/nginxinc/kubernetes-ingress/tree/{{version}}/examples + - Helm Chart -- https://github.com/nginxinc/kubernetes-ingress/tree/{{version}}/deployments/helm-chart + - Operator -- https://github.com/nginxinc/nginx-ingress-helm-operator diff --git a/.github/workflows/updates-notification.yml b/.github/workflows/updates-notification.yml index 70129cf808..0a757a47fb 100644 --- a/.github/workflows/updates-notification.yml +++ b/.github/workflows/updates-notification.yml @@ -9,10 +9,10 @@ on: version: required: true type: string - sha_long: + image_digest: required: true type: string - image_digest: + slack_webhook_url: required: true type: string @@ -20,23 +20,32 @@ defaults: run: shell: bash +permissions: + contents: read + jobs: send-notifications: name: Send Notifications - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 + permissions: + contents: read + actions: read # for 8398a7/action-slack steps: - name: Checkout Repository - uses: actions/checkout@v3 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: - ref: ${{ inputs.sha_long }} + ref: refs/tags/v${{ inputs.tag }} + - name: Get variables for Slack id: slack run: | - echo "message=$(git log -1 --pretty=%B)" >> $GITHUB_OUTPUT + echo "message=$(git log -1 --pretty=%s)" >> $GITHUB_OUTPUT echo "date=$(date +%s)" >> $GITHUB_OUTPUT echo "sha_short=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT + echo "sha_long=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT + - name: Send Notification - uses: 8398a7/action-slack@v3 + uses: 8398a7/action-slack@28ba43ae48961b90635b50953d216767a6bea486 # v3.16.2 with: status: custom custom_payload: | @@ -64,7 +73,7 @@ jobs: }, { title: "Commit Hash", - value: ``, + value: ``, short: true }], footer: "Update DockerHub Image", @@ -73,4 +82,4 @@ jobs: }] } env: - SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }} + SLACK_WEBHOOK_URL: ${{ inputs.slack_webhook_url }} diff --git a/.github/workflows/version-bump.yml b/.github/workflows/version-bump.yml new file mode 100644 index 0000000000..d65682c834 --- /dev/null +++ b/.github/workflows/version-bump.yml @@ -0,0 +1,60 @@ +name: "Bump the IC & Helm chart version" + +on: + workflow_dispatch: + inputs: + source_branch: + required: true + type: string + default: "main" + ic_version: + required: true + type: string + default: "0.0.0" + helm_chart_version: + required: true + type: string + default: "0.0.0" + +defaults: + run: + shell: bash + +permissions: + contents: read + +jobs: + version-bump: + permissions: + contents: write + runs-on: ubuntu-24.04 + steps: + - name: Checkout Repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + ref: ${{ inputs.source_branch }} + + - name: Replace Versions + run: | + yq -i e '.version = env(CHART_VERSION) | .appVersion = env(IC_VERSION)' charts/nginx-ingress/Chart.yaml + cat charts/nginx-ingress/Chart.yaml + cat > .github/data/version.txt << EOF + IC_VERSION=${IC_VERSION} + HELM_CHART_VERSION=${CHART_VERSION} + EOF + cat .github/data/version.txt + env: + IC_VERSION: ${{ inputs.ic_version }} + CHART_VERSION: ${{ inputs.helm_chart_version }} + + - name: Create Pull Request + uses: peter-evans/create-pull-request@67ccf781d68cd99b580ae25a5c18a1cc84ffff1f # v7.0.6 + with: + token: ${{ secrets.NGINX_PAT }} + commit-message: Version Bump for ${{ github.event.inputs.ic_version }} + title: Version Bump for ${{ github.event.inputs.ic_version }} + branch: chore/version-bump-${{ github.event.inputs.ic_version }} + author: nginx-bot + labels: chore + body: | + This automated PR updates the NIC & Helm chart versions for the upcoming ${{ github.event.inputs.ic_version }} release. diff --git a/.gitignore b/.gitignore index 1c9fa19f8d..d4235ebe69 100644 --- a/.gitignore +++ b/.gitignore @@ -50,12 +50,9 @@ default.pem hack/controller-gen-* # Ignore local docs dependencies -site/ venv/ -docs/public -docs/themes/f5-hugo -.netlify/ -docs/.netlify +site/public +site/themes/f5-hugo # trivy container scanning cache .trivycache/ @@ -67,3 +64,7 @@ coverage.out node_modules package-lock.json package.json + +# kind kube-config +kube-local +venv/ diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml deleted file mode 100644 index 71834b1c68..0000000000 --- a/.gitlab-ci.yml +++ /dev/null @@ -1,4 +0,0 @@ -include: - - project: "f5/nginx/kic/kic-pipelines" - file: "/include/ingress-controller.yml" - ref: "master" diff --git a/.golangci.yml b/.golangci.yml index 8e38536056..0c14a26c50 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -17,7 +17,6 @@ linters-settings: - name: if-return - name: increment-decrement - name: indent-error-flow - - name: package-comments - name: range - name: receiver-naming - name: redefines-builtin-id @@ -62,5 +61,10 @@ issues: max-issues-per-linter: 0 max-same-issues: 0 exclude-use-default: false + exclude-rules: + - path: _test\.go + text: "Potential hardcoded credentials" + linters: + - gosec run: timeout: 5m diff --git a/.goreleaser.yml b/.goreleaser.yml index 8ecdbf5d60..00f6170095 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -1,3 +1,5 @@ +version: 2 + env: - CGO_ENABLED=0 @@ -93,36 +95,48 @@ builds: tags: - aws -archives: - - id: kubernetes-ingress - builds: [kubernetes-ingress] - changelog: - skip: true + disable: true -checksum: - name_template: 'checksums.txt' +archives: + - id: archives + builds: [kubernetes-ingress] sboms: - - artifacts: archive - ids: [kubernetes-ingress] + - id: sboms + artifacts: archive + ids: [archives] + documents: + - "${artifact}.spdx.json" release: - ids: [kubernetes-ingress] - extra_files: - - glob: ./dist/**.sbom + ids: [archives, sboms, signs] blobs: - provider: azblob bucket: '{{.Env.AZURE_BUCKET_NAME}}' - extra_files: - - glob: ./dist/**.sbom -milestones: - - close: true +signs: + - id: signs + cmd: cosign + artifacts: checksum + output: true + certificate: '${artifact}.pem' + args: + - sign-blob + - "--output-signature=${signature}" + - "--output-certificate=${certificate}" + - "${artifact}" + - "--yes" announce: slack: enabled: true channel: '#announcements' message_template: 'NGINX Ingress Controller {{ .Tag }} is out! Check it out: {{ .ReleaseURL }}' + +milestones: + - close: true + +snapshot: + name_template: '{{.Version}}' diff --git a/.markdownlint-cli2.yaml b/.markdownlint-cli2.yaml new file mode 100644 index 0000000000..0634b21718 --- /dev/null +++ b/.markdownlint-cli2.yaml @@ -0,0 +1,22 @@ +# Rule configuration. +# For rule descriptions and how to fix: https://github.com/DavidAnson/markdownlint/tree/main#rules--aliases +config: + ul-style: + style: dash + no-duplicate-heading: + siblings_only: true + line-length: false + +# Hide the Finding: when markdownlint fixes files +noProgress: true + +# Hide the markdownlint-cli and markdownlint versions on each block +noBanner: true + +# Define glob expressions to ignore +ignores: + - ".github/**" + - "site/**" # Ignore docs folder for now + +# Fix any fixable errors +fix: true diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 7f94becf67..106ce7bff0 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,20 +1,23 @@ # See https://pre-commit.com for more information # See https://pre-commit.com/hooks.html for more hooks -exclude: (^docs/_vendor/|.*pb2.*) +exclude: (^site/_vendor/|.*pb2.*) repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.3.0 + rev: v5.0.0 hooks: - id: trailing-whitespace + exclude: '(\.md|\.snap|\.avdl)$' - id: end-of-file-fixer + exclude: site/layouts/shortcodes/nic-.*.html - id: check-yaml args: [--allow-multiple-documents] - exclude: ^(deployments/helm-chart.*/templates|deployments/helm-chart/crds) + exclude: ^(charts/nginx-ingress/templates) - id: check-ast - id: check-added-large-files - id: check-merge-conflict - id: check-shebang-scripts-are-executable - id: check-executables-have-shebangs + exclude: '(\.snap)$' - id: check-symlinks - id: check-case-conflict - id: check-vcs-permalinks @@ -29,6 +32,9 @@ repos: - id: no-commit-to-branch - id: requirements-txt-fixer - id: fix-byte-order-marker + - id: detect-private-key + exclude: ^(examples/|tests/|internal/k8s/secrets/) + - repo: local hooks: - id: golang-diff @@ -37,30 +43,65 @@ repos: language: system types: [go] pass_filenames: false + - repo: https://github.com/golangci/golangci-lint - rev: v1.50.1 + rev: v1.63.4 hooks: - id: golangci-lint args: [--new-from-patch=/tmp/diff.patch] + - repo: https://github.com/asottile/pyupgrade - rev: v3.2.0 + rev: v3.19.1 hooks: - id: pyupgrade + - repo: https://github.com/PyCQA/isort - rev: 5.10.1 + rev: 5.13.2 hooks: - id: isort + - repo: https://github.com/psf/black - rev: 22.10.0 + rev: 24.10.0 hooks: - id: black + + - repo: https://github.com/PyCQA/autoflake + rev: v2.3.1 + hooks: + - id: autoflake + files: tests/.*\.py$ + args: + [ + "--in-place", + "--remove-all-unused-imports", + "--remove-unused-variable", + ] + - repo: https://github.com/python-jsonschema/check-jsonschema - rev: 0.18.4 + rev: 0.30.0 hooks: - id: check-jsonschema name: "Check Helm Chart JSON Schema" - files: deployments/helm-chart/values.yaml + files: charts/nginx-ingress/values.yaml types: [yaml] - args: ['--schemafile', 'deployments/helm-chart/values.schema.json'] + args: ["--schemafile", "charts/nginx-ingress/values.schema.json"] + + - repo: https://github.com/DavidAnson/markdownlint-cli2 + rev: v0.17.1 + hooks: + - id: markdownlint-cli2 + + - repo: https://github.com/rhysd/actionlint + rev: v1.7.6 + hooks: + - id: actionlint + name: Lint GitHub Actions workflow files + description: Runs actionlint to lint GitHub Actions workflow files + language: golang + types: ["yaml"] + files: ^\.github/workflows/ + entry: actionlint + args: ["-shellcheck", ""] + ci: - skip: [golang-diff, golangci-lint, check-jsonschema] + skip: [golang-diff, golangci-lint, check-jsonschema, markdownlint-cli2] diff --git a/CHANGELOG.md b/CHANGELOG.md index 759c2d5f06..67707865a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,1066 +1,1375 @@ # Changelog -### 2.4.1 +Starting with version 1.11.0, an automatically generated list of changes can be found on the [GitHub Releases page](https://github.com/nginxinc/kubernetes-ingress/releases). -An automatically generated list of changes can be found on GitHub at: [2.4.1 Release](https://github.com/nginxinc/kubernetes-ingress/releases/tag/v2.4.1) +A curated list of changes can be found in the [Releases](http://docs.nginx.com/nginx-ingress-controller/releases/) page +on NGINX Documentation website. -A curated list of changes can be found on the [Releases](http://docs.nginx.com/nginx-ingress-controller/releases/) page on the NGINX Documentation website. - -### 2.4.0 - -An automatically generated list of changes can be found on GitHub at: [2.4.0 Release](https://github.com/nginxinc/kubernetes-ingress/releases/tag/v2.4.0) - -A curated list of changes can be found on the [Releases](http://docs.nginx.com/nginx-ingress-controller/releases/) page on the NGINX Documentation website. - -### 2.3.1 - -An automatically generated list of changes can be found on GitHub at: [2.3.1 Release](https://github.com/nginxinc/kubernetes-ingress/releases/tag/v2.3.1) - -A curated list of changes can be found on the [Releases](http://docs.nginx.com/nginx-ingress-controller/releases/) page on the NGINX Documentation website. - -### 2.3.0 - -An automatically generated list of changes can be found on GitHub at: [2.3.0 Release](https://github.com/nginxinc/kubernetes-ingress/releases/tag/v2.3.0) - -A curated list of changes can be found on the [Releases](http://docs.nginx.com/nginx-ingress-controller/releases/) page on the NGINX Documentation website. - -### 2.2.2 - -An automatically generated list of changes can be found on Github at: [2.2.2 Release](https://github.com/nginxinc/kubernetes-ingress/releases/tag/v2.2.2) - -A curated list of changes can be found on the [Releases](http://docs.nginx.com/nginx-ingress-controller/releases/) page on the NGINX Documentation website. - -### 2.2.1 - -An automatically generated list of changes can be found on Github at: [2.2.1 Release](https://github.com/nginxinc/kubernetes-ingress/releases/tag/v2.2.1) - -A curated list of changes can be found on the [Releases](http://docs.nginx.com/nginx-ingress-controller/releases/) page on the NGINX Documentation website. - -### 2.2.0 - -An automatically generated list of changes can be found on GitHub at: [2.2.0 Release](https://github.com/nginxinc/kubernetes-ingress/releases/tag/v2.2.0) - -A curated list of changes can be found on the [Releases](http://docs.nginx.com/nginx-ingress-controller/releases/) page on the NGINX Documentation website. - -### 2.1.2 - -An automatically generated list of changes can be found on GitHub at: [2.1.2 Release](https://github.com/nginxinc/kubernetes-ingress/releases/tag/v2.1.2) - -A curated list of changes can be found in the [Releases](http://docs.nginx.com/nginx-ingress-controller/releases/) page on NGINX Documentation website. - -### 1.12.4 - -An automatically generated list of changes can be found on GitHub at: [1.12.4 Release](https://github.com/nginxinc/kubernetes-ingress/releases/tag/v1.12.4) - -A curated list of changes can be found in the [Releases](http://docs.nginx.com/nginx-ingress-controller/releases/) page on NGINX Documentation website. - -### 2.1.1 - -An automatically generated list of changes can be found on GitHub at: [2.1.1 Release](https://github.com/nginxinc/kubernetes-ingress/releases/tag/v2.1.1) - -A curated list of changes can be found in the [Releases](http://docs.nginx.com/nginx-ingress-controller/releases/) page on NGINX Documentation website. - -### 2.1.0 - -An automatically generated list of changes can be found on GitHub at: [2.1.0 Release](https://github.com/nginxinc/kubernetes-ingress/releases/tag/v2.1.0) - -A curated list of changes can be found in the [Releases](http://docs.nginx.com/nginx-ingress-controller/releases/) page on NGINX Documentation website. - -### 2.0.3 - -An automatically generated list of changes can be found on GitHub at: [2.0.3 Release](https://github.com/nginxinc/kubernetes-ingress/releases/tag/v2.0.3) - -A curated list of changes can be found in the [Releases](http://docs.nginx.com/nginx-ingress-controller/releases/) page on NGINX Documentation website. - -### 1.12.3 - -An automatically generated list of changes can be found on GitHub at: [1.12.3 Release](https://github.com/nginxinc/kubernetes-ingress/releases/tag/v1.12.3) - -A curated list of changes can be found in the [Releases](http://docs.nginx.com/nginx-ingress-controller/releases/) page on NGINX Documentation website. - -### 2.0.2 - -An automatically generated list of changes can be found on GitHub at: [2.0.2 Release](https://github.com/nginxinc/kubernetes-ingress/releases/tag/v2.0.2) - -A curated list of changes can be found in the [Releases](http://docs.nginx.com/nginx-ingress-controller/releases/) page on NGINX Documentation website. - -### 2.0.1 - -An automatically generated list of changes can be found on GitHub at: [2.0.1 Release](https://github.com/nginxinc/kubernetes-ingress/releases/tag/v2.0.1) - -A curated list of changes can be found in the [Releases](http://docs.nginx.com/nginx-ingress-controller/releases/) page on NGINX Documentation website. - -### 1.12.2 - -An automatically generated list of changes can be found on GitHub at: [1.12.2 Release](https://github.com/nginxinc/kubernetes-ingress/releases/tag/v1.12.2) - -A curated list of changes can be found in the [Releases](http://docs.nginx.com/nginx-ingress-controller/releases/) page on NGINX Documentation website. - -### 2.0.0 - -An automatically generated list of changes can be found on GitHub at: [2.0.0 Release](https://github.com/nginxinc/kubernetes-ingress/releases/tag/v2.0.0) - -A curated list of changes can be found in the [Releases](http://docs.nginx.com/nginx-ingress-controller/releases/) page on NGINX Documentation website. - -### 1.12.1 - -An automatically generated list of changes can be found on GitHub at: [1.12.1 Release](https://github.com/nginxinc/kubernetes-ingress/releases/tag/v1.12.1) - -A curated list of changes can be found in the [Releases](http://docs.nginx.com/nginx-ingress-controller/releases/) page on NGINX Documentation website. - -### 1.12.0 - -An automatically generated list of changes can be found on GitHub at: [1.12.0 Release](https://github.com/nginxinc/kubernetes-ingress/releases/tag/v1.12.0) - -A curated list of changes can be found in the [Releases](http://docs.nginx.com/nginx-ingress-controller/releases/) page on NGINX Documentation website. - -### 1.11.3 - -An automatically generated list of changes can be found on GitHub at: [1.11.3 Release](https://github.com/nginxinc/kubernetes-ingress/releases/tag/v1.11.3) - -A curated list of changes can be found in the [Releases](http://docs.nginx.com/nginx-ingress-controller/releases/) page on NGINX Documentation website. - -### 1.11.2 - -An automatically generated list of changes can be found on GitHub at: [1.11.2 Release](https://github.com/nginxinc/kubernetes-ingress/releases/tag/v1.11.2) - -A curated list of changes can be found in the [Releases](http://docs.nginx.com/nginx-ingress-controller/releases/) page on NGINX Documentation website. - -### 1.11.1 - -An automatically generated list of changes can be found on GitHub at: [1.11.1 Release](https://github.com/nginxinc/kubernetes-ingress/releases/tag/v1.11.1) - -A curated list of changes can be found in the [Releases](http://docs.nginx.com/nginx-ingress-controller/releases/) page on NGINX Documentation website. - -### 1.11.0 - -An automatically generated list of changes can be found on GitHub at: [1.11.0 Release](https://github.com/nginxinc/kubernetes-ingress/releases/tag/v1.11.0) - -A curated list of changes can be found in the [Releases](http://docs.nginx.com/nginx-ingress-controller/releases/) page on NGINX Documentation website. - -### 1.10.1 +## 1.10.1 CHANGES: -* Update NGINX version to 1.19.8. -* Add Kubernetes 1.20 support. -* [1373](https://github.com/nginxinc/kubernetes-ingress/pull/1373), [1439](https://github.com/nginxinc/kubernetes-ingress/pull/1439), [1440](https://github.com/nginxinc/kubernetes-ingress/pull/1440): Fix various issues in the Makefile. In 1.10.0, a bug was introduced that prevented building Ingress Controller images on versions of make < 4.1. + +- Update NGINX version to 1.19.8. +- Add Kubernetes 1.20 support. +- [1373](https://github.com/nginxinc/kubernetes-ingress/pull/1373), + [1439](https://github.com/nginxinc/kubernetes-ingress/pull/1439), + [1440](https://github.com/nginxinc/kubernetes-ingress/pull/1440): Fix various issues in the Makefile. In 1.10.0, a bug + was introduced that prevented building Ingress Controller images on versions of make < 4.1. HELM CHART: -* The version of the Helm chart is now 0.8.1. + +- The version of the Helm chart is now 0.8.1. UPGRADE: -* For NGINX, use the 1.10.1 image from our DockerHub: `nginx/nginx-ingress:1.10.1`, `nginx/nginx-ingress:1.10.1-alpine` or `nginx/nginx-ingress:1.10.1-ubi` -* For NGINX Plus, please build your own image using the 1.10.1 source code. -* For Helm, use version 0.8.1 of the chart. -### 1.10.0 +- For NGINX, use the 1.10.1 image from our DockerHub: `nginx/nginx-ingress:1.10.1`, `nginx/nginx-ingress:1.10.1-alpine` + or `nginx/nginx-ingress:1.10.1-ubi` +- For NGINX Plus, please build your own image using the 1.10.1 source code. +- For Helm, use version 0.8.1 of the chart. + +## 1.10.0 OVERVIEW: Release 1.10.0 includes: -* Open ID Connect authentication policy. -* Improved handling of Secret resources with extended validation and error reporting. -* Improved visibility with Prometheus metrics for the configuration workqueue and the ability to annotate NGINX logs with the metadata of Kubernetes resources. -* NGINX App Protect User-Defined signatures support. -* Improved validation of Ingress annotations. + +- Open ID Connect authentication policy. +- Improved handling of Secret resources with extended validation and error reporting. +- Improved visibility with Prometheus metrics for the configuration workqueue and the ability to annotate NGINX logs + with the metadata of Kubernetes resources. +- NGINX App Protect User-Defined signatures support. +- Improved validation of Ingress annotations. You will find the complete changelog for release 1.10.0, including bug fixes, improvements, and changes below. FEATURES FOR POLICY RESOURCE: -* [1304](https://github.com/nginxinc/kubernetes-ingress/pull/1304) Add Open ID Connect policy. + +- [1304](https://github.com/nginxinc/kubernetes-ingress/pull/1304) Add Open ID Connect policy. FEATURES FOR NGINX APP PROTECT: -* [1281](https://github.com/nginxinc/kubernetes-ingress/pull/1281) Add support for App Protect User Defined Signatures. + +- [1281](https://github.com/nginxinc/kubernetes-ingress/pull/1281) Add support for App Protect User Defined Signatures. FEATURES: -* [1266](https://github.com/nginxinc/kubernetes-ingress/pull/1266) Add workqueue metrics to Prometheus metrics. -* [1233](https://github.com/nginxinc/kubernetes-ingress/pull/1233) Annotate tcp metrics with k8s object labels. -* [1231](https://github.com/nginxinc/kubernetes-ingress/pull/1231) Support k8s objects variables in log format. + +- [1266](https://github.com/nginxinc/kubernetes-ingress/pull/1266) Add workqueue metrics to Prometheus metrics. +- [1233](https://github.com/nginxinc/kubernetes-ingress/pull/1233) Annotate tcp metrics with k8s object labels. +- [1231](https://github.com/nginxinc/kubernetes-ingress/pull/1231) Support k8s objects variables in log format. IMPROVEMENTS: -* [1270](https://github.com/nginxinc/kubernetes-ingress/pull/1270) and [1277](https://github.com/nginxinc/kubernetes-ingress/pull/1277) Improve validation of Ingress annotations. -* [1265](https://github.com/nginxinc/kubernetes-ingress/pull/1265) Report warnings for misconfigured TLS and JWK secrets. -* [1262](https://github.com/nginxinc/kubernetes-ingress/pull/1262) Use setcap(8) only once. [1263](https://github.com/nginxinc/kubernetes-ingress/pull/1263) Use chown(8) only once. [1264](https://github.com/nginxinc/kubernetes-ingress/pull/1264) Use mkdir(1) only once. Thanks to [Sergey A. Osokin](https://github.com/osokin). -* [1256](https://github.com/nginxinc/kubernetes-ingress/pull/1256) and [1260](https://github.com/nginxinc/kubernetes-ingress/pull/1260) Improve handling of secret resources. -* [1240](https://github.com/nginxinc/kubernetes-ingress/pull/1240) Validate TLS and CA secrets. -* [1235](https://github.com/nginxinc/kubernetes-ingress/pull/1235) Use buildkit secret flag for NGINX plus images. -* Documentation improvements: [1282](https://github.com/nginxinc/kubernetes-ingress/pull/1282), [1293](https://github.com/nginxinc/kubernetes-ingress/pull/1293), [1303](https://github.com/nginxinc/kubernetes-ingress/pull/1303), [1315](https://github.com/nginxinc/kubernetes-ingress/pull/1315). + +- [1270](https://github.com/nginxinc/kubernetes-ingress/pull/1270) and + [1277](https://github.com/nginxinc/kubernetes-ingress/pull/1277) Improve validation of Ingress annotations. +- [1265](https://github.com/nginxinc/kubernetes-ingress/pull/1265) Report warnings for misconfigured TLS and JWK + secrets. +- [1262](https://github.com/nginxinc/kubernetes-ingress/pull/1262) Use setcap(8) only once. + [1263](https://github.com/nginxinc/kubernetes-ingress/pull/1263) Use chown(8) only once. + [1264](https://github.com/nginxinc/kubernetes-ingress/pull/1264) Use mkdir(1) only once. Thanks to [Sergey A. + Osokin](https://github.com/osokin). +- [1256](https://github.com/nginxinc/kubernetes-ingress/pull/1256) and + [1260](https://github.com/nginxinc/kubernetes-ingress/pull/1260) Improve handling of secret resources. +- [1240](https://github.com/nginxinc/kubernetes-ingress/pull/1240) Validate TLS and CA secrets. +- [1235](https://github.com/nginxinc/kubernetes-ingress/pull/1235) Use buildkit secret flag for NGINX plus images. +- Documentation improvements: [1282](https://github.com/nginxinc/kubernetes-ingress/pull/1282), + [1293](https://github.com/nginxinc/kubernetes-ingress/pull/1293), + [1303](https://github.com/nginxinc/kubernetes-ingress/pull/1303), + [1315](https://github.com/nginxinc/kubernetes-ingress/pull/1315). HELM CHART: -* The version of the helm chart is now 0.8.0. -* [1290](https://github.com/nginxinc/kubernetes-ingress/pull/1290) Add new preview policies parameter to chart. `controller.enablePreviewPolicies` was added. -* [1232](https://github.com/nginxinc/kubernetes-ingress/pull/1232) Replace deprecated imagePullSecrets helm setting. `controller.serviceAccount.imagePullSecrets` was removed. `controller.serviceAccount.imagePullSecretName` was added. -* [1228](https://github.com/nginxinc/kubernetes-ingress/pull/1228) Fix installation of ingressclass on Kubernetes versions `v1.18.x-*` + +- The version of the helm chart is now 0.8.0. +- [1290](https://github.com/nginxinc/kubernetes-ingress/pull/1290) Add new preview policies parameter to chart. + `controller.enablePreviewPolicies` was added. +- [1232](https://github.com/nginxinc/kubernetes-ingress/pull/1232) Replace deprecated imagePullSecrets helm setting. + `controller.serviceAccount.imagePullSecrets` was removed. `controller.serviceAccount.imagePullSecretName` was added. +- [1228](https://github.com/nginxinc/kubernetes-ingress/pull/1228) Fix installation of ingressclass on Kubernetes + versions `v1.18.x-*` CHANGES: -* [1299](https://github.com/nginxinc/kubernetes-ingress/pull/1299) Update NGINX App Protect version to 2.3 and debian distribution to `debian:buster-slim`. -* [1291](https://github.com/nginxinc/kubernetes-ingress/pull/1291) Update NGINX OSS to `1.19.6`. Update NGINX Plus to `R23`. -* [1290](https://github.com/nginxinc/kubernetes-ingress/pull/1290) Graduate policy resource and accessControl policy to generally available. -* [1225](https://github.com/nginxinc/kubernetes-ingress/pull/1225) Require secrets to have types. -* [1237](https://github.com/nginxinc/kubernetes-ingress/pull/1237) Deprecate support for helm2 clients. + +- [1299](https://github.com/nginxinc/kubernetes-ingress/pull/1299) Update NGINX App Protect version to 2.3 and debian + distribution to `debian:buster-slim`. +- [1291](https://github.com/nginxinc/kubernetes-ingress/pull/1291) Update NGINX OSS to `1.19.6`. Update NGINX Plus to + `R23`. +- [1290](https://github.com/nginxinc/kubernetes-ingress/pull/1290) Graduate policy resource and accessControl policy to + generally available. +- [1225](https://github.com/nginxinc/kubernetes-ingress/pull/1225) Require secrets to have types. +- [1237](https://github.com/nginxinc/kubernetes-ingress/pull/1237) Deprecate support for helm2 clients. UPGRADE: -* For NGINX, use the 1.10.0 image from our DockerHub: `nginx/nginx-ingress:1.10.0`, `nginx/nginx-ingress:1.10.0-alpine` or `nginx-ingress:1.10.0-ubi` -* For NGINX Plus, please build your own image using the 1.10.0 source code. -* For Helm, use version 0.8.0 of the chart. -* As a result of [1270](https://github.com/nginxinc/kubernetes-ingress/pull/1270) and [1277](https://github.com/nginxinc/kubernetes-ingress/pull/1277), the Ingress Controller improved validation of Ingress annotations: more annotations are validated and validation errors are reported via events for Ingress resources. Additionally, the default behavior for invalid annotation values was changed: instead of using the default values, the Ingress Controller will reject a resource with an invalid annotation value, which will make clients see `404` responses from NGINX. See this [document](https://docs.nginx.com/nginx-ingress-controller/configuration/ingress-resources/advanced-configuration-with-annotations/#validation) to learn more. Before upgrading, ensure the Ingress resources don't have annotations with invalid values. Otherwise, after the upgrade, the Ingress Controller will reject such resources. -* In [1232](https://github.com/nginxinc/kubernetes-ingress/pull/1232) `controller.serviceAccount.imagePullSecrets` was removed. Use the new `controller.serviceAccount.imagePullSecretName` instead. -* The Policy resource was promoted to `v1`. If you used the `alpha1` version, the policies are needed to be recreated with the `v1` version. Before upgrading the Ingress Controller, run the following command to remove the `alpha1` policies CRD (that will also remove all existing `alpha1` policies): - ``` + +- For NGINX, use the 1.10.0 image from our DockerHub: `nginx/nginx-ingress:1.10.0`, `nginx/nginx-ingress:1.10.0-alpine` + or `nginx-ingress:1.10.0-ubi` +- For NGINX Plus, please build your own image using the 1.10.0 source code. +- For Helm, use version 0.8.0 of the chart. +- As a result of [1270](https://github.com/nginxinc/kubernetes-ingress/pull/1270) and + [1277](https://github.com/nginxinc/kubernetes-ingress/pull/1277), the Ingress Controller improved validation of + Ingress annotations: more annotations are validated and validation errors are reported via events for Ingress + resources. Additionally, the default behavior for invalid annotation values was changed: instead of using the default + values, the Ingress Controller will reject a resource with an invalid annotation value, which will make clients see + `404` responses from NGINX. See this + [document](https://docs.nginx.com/nginx-ingress-controller/configuration/ingress-resources/advanced-configuration-with-annotations/#validation) + to learn more. Before upgrading, ensure the Ingress resources don't have annotations with invalid values. Otherwise, + after the upgrade, the Ingress Controller will reject such resources. +- In [1232](https://github.com/nginxinc/kubernetes-ingress/pull/1232) `controller.serviceAccount.imagePullSecrets` was + removed. Use the new `controller.serviceAccount.imagePullSecretName` instead. +- The Policy resource was promoted to `v1`. If you used the `alpha1` version, the policies are needed to be recreated + with the `v1` version. Before upgrading the Ingress Controller, run the following command to remove the `alpha1` + policies CRD (that will also remove all existing `alpha1` policies): + + ```console kubectl delete crd policies.k8s.nginx.org ``` - As part of the upgrade, make sure to create the `v1` policies CRD. See the corresponding instructions for the [manifests](https://docs.nginx.com/nginx-ingress-controller/installation/installation-with-manifests/#create-custom-resources) and [Helm](https://docs.nginx.com/nginx-ingress-controller/installation/installation-with-helm/#upgrading-the-crds) installations. - Also note that all policies except for `accessControl` are still in preview. To enable them, run the Ingress Controller with `- -enable-preview-policies` command-line argument (`controller.enablePreviewPolicies` Helm parameter). -* It is necessary to update secret resources. See the section UPDATING SECRETS below. + As part of the upgrade, make sure to create the `v1` policies CRD. See the corresponding instructions for the + [manifests](https://docs.nginx.com/nginx-ingress-controller/installation/installation-with-manifests/#create-custom-resources) + and [Helm](https://docs.nginx.com/nginx-ingress-controller/installation/installation-with-helm/#upgrading-the-crds) + installations. + + Also note that all policies except for `accessControl` are still in preview. To enable them, run the Ingress + Controller with `- -enable-preview-policies` command-line argument (`controller.enablePreviewPolicies` Helm + parameter). +- It is necessary to update secret resources. See the section UPDATING SECRETS below. UPDATING SECRETS: -In [1225](https://github.com/nginxinc/kubernetes-ingress/pull/1225), as part of improving how the Ingress Controller handles secret resources, we added a requirement for secrets to be of one of the following types: +In [1225](https://github.com/nginxinc/kubernetes-ingress/pull/1225), as part of improving how the Ingress Controller +handles secret resources, we added a requirement for secrets to be of one of the following types: + - `kubernetes.io/tls` for TLS secrets. - `nginx.org/jwk` for JWK secrets. - `nginx.org/ca` for CA secrets. -The Ingress Controller now ignores secrets that are not of a supported type. As a consequence, special upgrade steps are required. +The Ingress Controller now ignores secrets that are not of a supported type. As a consequence, special upgrade steps are +required. + +Before upgrading, ensure that the secrets referenced in Ingress, VirtualServer or Policies resources are of a supported +type, which is configured via the `type` field. Because that field is immutable, it is necessary to either: -Before upgrading, ensure that the secrets referenced in Ingress, VirtualServer or Policies resources are of a supported type, which is configured via the `type` field. Because that field is immutable, it is necessary to either: -* Recreate the secrets. Note that in this case, the client traffic for the affected resources will be rejected for the period during which a secret doesn't exist in the cluster. -* Create copies of the secrets and update the affected resources to reference the copies. The copies need to be of a supported type. In contrast with the previous options, this will not make NGINX reject the client traffic. +- Recreate the secrets. Note that in this case, the client traffic for the affected resources will be rejected for the + period during which a secret doesn't exist in the cluster. +- Create copies of the secrets and update the affected resources to reference the copies. The copies need to be of a + supported type. In contrast with the previous options, this will not make NGINX reject the client traffic. -It is also necessary to update the default server secret and the wildcard secret (if it was configured) in case their type is not `kubernetes.io/tls`. The steps depend on how you installed the Ingress Controller: via manifests or Helm. Performing the steps will not lead to a disruption of the client traffic, as the Ingress Controller retains the default and wildcard secrets if they are removed. +It is also necessary to update the default server secret and the wildcard secret (if it was configured) in case their +type is not `kubernetes.io/tls`. The steps depend on how you installed the Ingress Controller: via manifests or Helm. +Performing the steps will not lead to a disruption of the client traffic, as the Ingress Controller retains the default +and wildcard secrets if they are removed. For *manifests installation*: + 1. Recreate the default server secret and the wildcard secret with the type `kubernetes.io/tls`. 1. Upgrade the Ingress Controller. For *Helm installation*, there two cases: -1. If Helm created the secrets (you configured `controller.defaultTLS.cert` and `controller.defaultTLS.key` for the default secret and `controller.wildcardTLS.cert` and `controller.wildcardTLS.key` for the wildcard secret), then no special upgrade steps are required: during the upgrade, the Helm will remove the existing default and wildcard secrets and create new ones with different names with the type `kubernetes.io/tls`. -1. If you created the secrets separately from Helm (you configured `controller.defaultTLS.secret` for the default secret and `controller.wildcardTLS.secret` for the wildcard secret): + +1. If Helm created the secrets (you configured `controller.defaultTLS.cert` and `controller.defaultTLS.key` for the + default secret and `controller.wildcardTLS.cert` and `controller.wildcardTLS.key` for the wildcard secret), then no + special upgrade steps are required: during the upgrade, the Helm will remove the existing default and wildcard + secrets and create new ones with different names with the type `kubernetes.io/tls`. +1. If you created the secrets separately from Helm (you configured `controller.defaultTLS.secret` for the default secret + and `controller.wildcardTLS.secret` for the wildcard secret): 1. Recreate the secrets with the type `kubernetes.io/tls`. 1. Upgrade to the new Helm release. NOTES: -* Helm 2 clients are no longer supported due to reaching End of Life: https://helm.sh/blog/helm-2-becomes-unsupported/ -### 1.9.1 +- Helm 2 clients are no longer supported due to reaching End of Life: + +## 1.9.1 CHANGES: -* Fix deployment of ingressclass resource via helm on some versions of Kubernetes. -* Update the base ubi images to 8.3. -* Renew CA cert for egress-mtls example. -* Add imagePullSecretName support to helm chart. + +- Fix deployment of ingressclass resource via helm on some versions of Kubernetes. +- Update the base ubi images to 8.3. +- Renew CA cert for egress-mtls example. +- Add imagePullSecretName support to helm chart. HELM CHART: -* The version of the Helm chart is now 0.7.1. + +- The version of the Helm chart is now 0.7.1. UPGRADE: -* For NGINX, use the 1.9.1 image from our DockerHub: `nginx/nginx-ingress:1.9.1`, `nginx/nginx-ingress:1.9.1-alpine` or `nginx/nginx-ingress:1.9.1-ubi` -* For NGINX Plus, please build your own image using the 1.9.1 source code. -* For Helm, use version 0.7.1 of the chart. -### 1.9.0 +- For NGINX, use the 1.9.1 image from our DockerHub: `nginx/nginx-ingress:1.9.1`, `nginx/nginx-ingress:1.9.1-alpine` or + `nginx/nginx-ingress:1.9.1-ubi` +- For NGINX Plus, please build your own image using the 1.9.1 source code. +- For Helm, use version 0.7.1 of the chart. + +## 1.9.0 OVERVIEW: Release 1.9.0 includes: -* Support for new Prometheus metrics and enhancements of the existing ones, including configuration reload reason, NGINX worker processes count, upstream latency, and more. -* Support for rate limiting, JWT authentication, ingress(client) and egress(upstream) mutual TLS via the Policy resource. -* Support for the latest Ingress resource features and the IngressClass resource. -* Support for NGINX Service Mesh. + +- Support for new Prometheus metrics and enhancements of the existing ones, including configuration reload reason, NGINX + worker processes count, upstream latency, and more. +- Support for rate limiting, JWT authentication, ingress(client) and egress(upstream) mutual TLS via the Policy + resource. +- Support for the latest Ingress resource features and the IngressClass resource. +- Support for NGINX Service Mesh. You will find the complete changelog for release 1.9.0, including bug fixes, improvements, and changes below. FEATURES FOR POLICY RESOURCE: -* [1180](https://github.com/nginxinc/kubernetes-ingress/pull/1180) Add support for EgressMTLS. -* [1166](https://github.com/nginxinc/kubernetes-ingress/pull/1166) Add IngressMTLS policy support. -* [1154](https://github.com/nginxinc/kubernetes-ingress/pull/1154) Add JWT policy support. -* [1120](https://github.com/nginxinc/kubernetes-ingress/pull/1120) Add RateLimit policy support. -* [1058](https://github.com/nginxinc/kubernetes-ingress/pull/1058) Support policies in VS routes and VSR subroutes. + +- [1180](https://github.com/nginxinc/kubernetes-ingress/pull/1180) Add support for EgressMTLS. +- [1166](https://github.com/nginxinc/kubernetes-ingress/pull/1166) Add IngressMTLS policy support. +- [1154](https://github.com/nginxinc/kubernetes-ingress/pull/1154) Add JWT policy support. +- [1120](https://github.com/nginxinc/kubernetes-ingress/pull/1120) Add RateLimit policy support. +- [1058](https://github.com/nginxinc/kubernetes-ingress/pull/1058) Support policies in VS routes and VSR subroutes. FEATURES FOR NGINX APP PROTECT: -* [1147](https://github.com/nginxinc/kubernetes-ingress/pull/1147) Add option to specify other log destinations in AppProtect. -* [1131](https://github.com/nginxinc/kubernetes-ingress/pull/1131) Update packages and CRDs to AppProtect 2.0. This update includes features such as: [JSON Schema Validation](https://docs.nginx.com/nginx-app-protect/configuration#applying-a-json-schema), [User-Defined URLs](https://docs.nginx.com/nginx-app-protect/configuration/#user-defined-urls) and [User-Defined Parameters](https://docs.nginx.com/nginx-app-protect/configuration/#user-defined-parameters). See the [release notes](https://docs.nginx.com/nginx-app-protect/releases/#release-2-0) for a complete feature list. -* [1100](https://github.com/nginxinc/kubernetes-ingress/pull/1100) Add external references to AppProtect. -* [1085](https://github.com/nginxinc/kubernetes-ingress/pull/1085) Add installation of threat campaigns package. + +- [1147](https://github.com/nginxinc/kubernetes-ingress/pull/1147) Add option to specify other log destinations in + AppProtect. +- [1131](https://github.com/nginxinc/kubernetes-ingress/pull/1131) Update packages and CRDs to AppProtect 2.0. This + update includes features such as: [JSON Schema + Validation](https://docs.nginx.com/nginx-app-protect/configuration#applying-a-json-schema), [User-Defined + URLs](https://docs.nginx.com/nginx-app-protect/configuration/#user-defined-urls) and [User-Defined + Parameters](https://docs.nginx.com/nginx-app-protect/configuration/#user-defined-parameters). See the [release + notes](https://docs.nginx.com/nginx-app-protect/releases/#release-2-0) for a complete feature list. +- [1100](https://github.com/nginxinc/kubernetes-ingress/pull/1100) Add external references to AppProtect. +- [1085](https://github.com/nginxinc/kubernetes-ingress/pull/1085) Add installation of threat campaigns package. FEATURES: -* [1133](https://github.com/nginxinc/kubernetes-ingress/pull/1133) Add support for IngressClass resources. -* [1130](https://github.com/nginxinc/kubernetes-ingress/pull/1130) Add prometheus latency collector. -* [1076](https://github.com/nginxinc/kubernetes-ingress/pull/1076) Add prometheus worker process metrics. -* [1075](https://github.com/nginxinc/kubernetes-ingress/pull/1075) Add support for NGINX Service Mesh internal routes. + +- [1133](https://github.com/nginxinc/kubernetes-ingress/pull/1133) Add support for IngressClass resources. +- [1130](https://github.com/nginxinc/kubernetes-ingress/pull/1130) Add prometheus latency collector. +- [1076](https://github.com/nginxinc/kubernetes-ingress/pull/1076) Add prometheus worker process metrics. +- [1075](https://github.com/nginxinc/kubernetes-ingress/pull/1075) Add support for NGINX Service Mesh internal routes. IMPROVEMENTS: -* [1178](https://github.com/nginxinc/kubernetes-ingress/pull/1178) Resolve host collisions in VirtualServer and Ingresses. -* [1158](https://github.com/nginxinc/kubernetes-ingress/pull/1158) Support variables in action proxy headers. -* [1137](https://github.com/nginxinc/kubernetes-ingress/pull/1137) Add pod_owner label to metrics when -spire-agent-address is set. -* [1107](https://github.com/nginxinc/kubernetes-ingress/pull/1107) Extend Upstream Servers with pod_name label. -* [1099](https://github.com/nginxinc/kubernetes-ingress/pull/1099) Add reason label to total_reload metrics. -* [1088](https://github.com/nginxinc/kubernetes-ingress/pull/1088) Extend Upstream Servers and Server Zones metrics, thanks to [Raúl](https://github.com/Rulox). -* [1080](https://github.com/nginxinc/kubernetes-ingress/pull/1080) Support pathType field in the Ingress resource. -* [1078](https://github.com/nginxinc/kubernetes-ingress/pull/1078) Remove trailing blank lines in vs/vsr snippets. -* Documentation improvements: [1083](https://github.com/nginxinc/kubernetes-ingress/pull/1083), [1092](https://github.com/nginxinc/kubernetes-ingress/pull/1092), [1089](https://github.com/nginxinc/kubernetes-ingress/pull/1089), [1174](https://github.com/nginxinc/kubernetes-ingress/pull/1174), [1175](https://github.com/nginxinc/kubernetes-ingress/pull/1175), [1171](https://github.com/nginxinc/kubernetes-ingress/pull/1171). + +- [1178](https://github.com/nginxinc/kubernetes-ingress/pull/1178) Resolve host collisions in VirtualServer and + Ingresses. +- [1158](https://github.com/nginxinc/kubernetes-ingress/pull/1158) Support variables in action proxy headers. +- [1137](https://github.com/nginxinc/kubernetes-ingress/pull/1137) Add pod_owner label to metrics when + -spire-agent-address is set. +- [1107](https://github.com/nginxinc/kubernetes-ingress/pull/1107) Extend Upstream Servers with pod_name label. +- [1099](https://github.com/nginxinc/kubernetes-ingress/pull/1099) Add reason label to total_reload metrics. +- [1088](https://github.com/nginxinc/kubernetes-ingress/pull/1088) Extend Upstream Servers and Server Zones metrics, + thanks to [Raúl](https://github.com/Rulox). +- [1080](https://github.com/nginxinc/kubernetes-ingress/pull/1080) Support pathType field in the Ingress resource. +- [1078](https://github.com/nginxinc/kubernetes-ingress/pull/1078) Remove trailing blank lines in vs/vsr snippets. +- Documentation improvements: [1083](https://github.com/nginxinc/kubernetes-ingress/pull/1083), + [1092](https://github.com/nginxinc/kubernetes-ingress/pull/1092), + [1089](https://github.com/nginxinc/kubernetes-ingress/pull/1089), + [1174](https://github.com/nginxinc/kubernetes-ingress/pull/1174), + [1175](https://github.com/nginxinc/kubernetes-ingress/pull/1175), + [1171](https://github.com/nginxinc/kubernetes-ingress/pull/1171). BUGFIXES: -* [1179](https://github.com/nginxinc/kubernetes-ingress/pull/1179) Fix TransportServers in debian AppProtect image. -* [1129](https://github.com/nginxinc/kubernetes-ingress/pull/1129) Support real-ip in default server. -* [1110](https://github.com/nginxinc/kubernetes-ingress/pull/1110) Add missing threat campaigns key to AppProtect CRD. + +- [1179](https://github.com/nginxinc/kubernetes-ingress/pull/1179) Fix TransportServers in debian AppProtect image. +- [1129](https://github.com/nginxinc/kubernetes-ingress/pull/1129) Support real-ip in default server. +- [1110](https://github.com/nginxinc/kubernetes-ingress/pull/1110) Add missing threat campaigns key to AppProtect CRD. HELM CHART: -* The version of the helm chart is now 0.7.0 -* [1105](https://github.com/nginxinc/kubernetes-ingress/pull/1105) Fix GlobalConfiguration support in helm chart. -* Add new parameters to the Chart: `controller.setAsDefaultIngress`, `controller.enableLatencyMetrics`. Added in [1133](https://github.com/nginxinc/kubernetes-ingress/pull/1133) and [1148](https://github.com/nginxinc/kubernetes-ingress/pull/1148). + +- The version of the helm chart is now 0.7.0 +- [1105](https://github.com/nginxinc/kubernetes-ingress/pull/1105) Fix GlobalConfiguration support in helm chart. +- Add new parameters to the Chart: `controller.setAsDefaultIngress`, `controller.enableLatencyMetrics`. Added in + [1133](https://github.com/nginxinc/kubernetes-ingress/pull/1133) and + [1148](https://github.com/nginxinc/kubernetes-ingress/pull/1148). CHANGES: -* [1182](https://github.com/nginxinc/kubernetes-ingress/pull/1182) Update NGINX version to 1.19.3. + +- [1182](https://github.com/nginxinc/kubernetes-ingress/pull/1182) Update NGINX version to 1.19.3. UPGRADE: -* For NGINX, use the 1.9.0 image from our DockerHub: `nginx/nginx-ingress:1.9.0`, `nginx/nginx-ingress:1.9.0-alpine` or `nginx-ingress:1.9.0-ubi` -* For NGINX Plus, please build your own image using the 1.9.0 source code. -* For Helm, use version 0.7.0 of the chart. -For Kubernetes >= 1.18, when upgrading using the [manifests](https://docs.nginx.com/nginx-ingress-controller/installation/installation-with-manifests/), make sure to update the [ClusterRole](deployments/rbac/rbac.yaml) and create the [IngressClass resource](deployments/common/ingress-class.yaml), which is required for Kubernetes >= 1.18. Otherwise, the Ingress Controller will fail to start. If you run multiple NGINX Ingress Controllers in the cluster, each Ingress Controller has to have its own IngressClass resource. Make sure your Ingress resources have the `ingressClassName` field or the `kubernetes.io/ingress.class` annotation set to the name of the IngressClass resource. Otherwise, the Ingress Controller will ignore them. +- For NGINX, use the 1.9.0 image from our DockerHub: `nginx/nginx-ingress:1.9.0`, `nginx/nginx-ingress:1.9.0-alpine` or + `nginx-ingress:1.9.0-ubi` +- For NGINX Plus, please build your own image using the 1.9.0 source code. +- For Helm, use version 0.7.0 of the chart. + +For Kubernetes >= 1.18, when upgrading using the +[manifests](https://docs.nginx.com/nginx-ingress-controller/installation/installation-with-manifests/), make sure to +update the [ClusterRole](deployments/rbac/rbac.yaml) and create the [IngressClass +resource](deployments/common/ingress-class.yaml), which is required for Kubernetes >= 1.18. Otherwise, the Ingress +Controller will fail to start. If you run multiple NGINX Ingress Controllers in the cluster, each Ingress Controller has +to have its own IngressClass resource. Make sure your Ingress resources have the `ingressClassName` field or the +`kubernetes.io/ingress.class` annotation set to the name of the IngressClass resource. Otherwise, the Ingress Controller +will ignore them. HELM UPGRADE: -* If you're using custom resources like VirtualServer and TransportServer (`controller.enableCustomResources` is set to `true`), after you run the `helm upgrade` command, the CRDs will not be upgraded. After running the `helm upgrade` command, run `kubectl apply -f deployments/helm-chart/crds` to upgrade the CRDs. -* For Kubernetes >= 1.18, a dedicated IngressClass resource, which is configured by `controller.ingressClass`, is required per helm release. Ensure `controller.ingressClass` is not set to the name of the IngressClass of other releases or Ingress Controllers. Make sure your Ingress resources have the `ingressClassName` field or the `kubernetes.io/ingress.class` annotation set to the value of `controller.ingressClass`. Otherwise, the Ingress Controller will ignore them. + +- If you're using custom resources like VirtualServer and TransportServer (`controller.enableCustomResources` is set to + `true`), after you run the `helm upgrade` command, the CRDs will not be upgraded. After running the `helm upgrade` + command, run `kubectl apply -f deployments/helm-chart/crds` to upgrade the CRDs. +- For Kubernetes >= 1.18, a dedicated IngressClass resource, which is configured by `controller.ingressClass`, is + required per helm release. Ensure `controller.ingressClass` is not set to the name of the IngressClass of other + releases or Ingress Controllers. Make sure your Ingress resources have the `ingressClassName` field or the + `kubernetes.io/ingress.class` annotation set to the value of `controller.ingressClass`. Otherwise, the Ingress + Controller will ignore them. NOTES: -* When using Kubernetes >= 1.18 the Ingress Controller will only process resources that belong to its class. See [IngressClass doc](https://docs.nginx.com/nginx-ingress-controller/installation/running-multiple-ingress-controllers/#ingress-class) to learn more. -* For Kubernetes >= 1.18, a dedicated IngressClass resource, which is configured by `controller.ingressClass`, is required per helm release. When upgrading or installing releases, ensure `controller.ingressClass` is not set to the name of the IngressClass of other releases or Ingress Controllers. -### 1.8.1 +- When using Kubernetes >= 1.18 the Ingress Controller will only process resources that belong to its class. See + [IngressClass + doc](https://docs.nginx.com/nginx-ingress-controller/installation/running-multiple-ingress-controllers/#ingress-class) + to learn more. +- For Kubernetes >= 1.18, a dedicated IngressClass resource, which is configured by `controller.ingressClass`, is + required per helm release. When upgrading or installing releases, ensure `controller.ingressClass` is not set to the + name of the IngressClass of other releases or Ingress Controllers. + +## 1.8.1 CHANGES: -* Update NGINX version to 1.19.2. + +- Update NGINX version to 1.19.2. HELM CHART: -* The version of the Helm chart is now 0.6.1. + +- The version of the Helm chart is now 0.6.1. UPGRADE: -* For NGINX, use the 1.8.1 image from our DockerHub: `nginx/nginx-ingress:1.8.1`, `nginx/nginx-ingress:1.8.1-alpine` or `nginx/nginx-ingress:1.8.1-ubi` -* For NGINX Plus, please build your own image using the 1.8.1 source code. -* For Helm, use version 0.6.1 of the chart. +- For NGINX, use the 1.8.1 image from our DockerHub: `nginx/nginx-ingress:1.8.1`, `nginx/nginx-ingress:1.8.1-alpine` or + `nginx/nginx-ingress:1.8.1-ubi` +- For NGINX Plus, please build your own image using the 1.8.1 source code. +- For Helm, use version 0.6.1 of the chart. -### 1.8.0 +## 1.8.0 OVERVIEW: Release 1.8.0 includes: -* Support for NGINX App Protect Web Application Firewall. -* Support for configuration snippets and custom template for VirtualServer and VirtualServerRoute resources. -* Support for request/response header manipulation and request URI rewriting for VirtualServer/VirtualServerRoute. -* Introducing a new configuration resource - Policy - with the first policy for IP-based access control. + +- Support for NGINX App Protect Web Application Firewall. +- Support for configuration snippets and custom template for VirtualServer and VirtualServerRoute resources. +- Support for request/response header manipulation and request URI rewriting for VirtualServer/VirtualServerRoute. +- Introducing a new configuration resource - Policy - with the first policy for IP-based access control. You will find the complete changelog for release 1.8.0, including bug fixes, improvements, and changes below. FEATURES FOR VIRTUALSERVER AND VIRTUALSERVERROUTE RESOURCES: -* [1036](https://github.com/nginxinc/kubernetes-ingress/pull/1036): Add VirtualServer custom template support. -* [1028](https://github.com/nginxinc/kubernetes-ingress/pull/1028): Add access control policy. -* [1019](https://github.com/nginxinc/kubernetes-ingress/pull/1019): Add VirtualServer/VirtualServerRoute snippets support. -* [1006](https://github.com/nginxinc/kubernetes-ingress/pull/1006): Add request/response modifiers to VS and VSR. -* [994](https://github.com/nginxinc/kubernetes-ingress/pull/994): Support Class Field in VS/VSR. -* [973](https://github.com/nginxinc/kubernetes-ingress/pull/973): Add status to VirtualServer and VirtualServerRoute. + +- [1036](https://github.com/nginxinc/kubernetes-ingress/pull/1036): Add VirtualServer custom template support. +- [1028](https://github.com/nginxinc/kubernetes-ingress/pull/1028): Add access control policy. +- [1019](https://github.com/nginxinc/kubernetes-ingress/pull/1019): Add VirtualServer/VirtualServerRoute snippets + support. +- [1006](https://github.com/nginxinc/kubernetes-ingress/pull/1006): Add request/response modifiers to VS and VSR. +- [994](https://github.com/nginxinc/kubernetes-ingress/pull/994): Support Class Field in VS/VSR. +- [973](https://github.com/nginxinc/kubernetes-ingress/pull/973): Add status to VirtualServer and VirtualServerRoute. FEATURES: -* [1035](https://github.com/nginxinc/kubernetes-ingress/pull/1035): Support for App Protect module. -* [1029](https://github.com/nginxinc/kubernetes-ingress/pull/1029): Add readiness endpoint. + +- [1035](https://github.com/nginxinc/kubernetes-ingress/pull/1035): Support for App Protect module. +- [1029](https://github.com/nginxinc/kubernetes-ingress/pull/1029): Add readiness endpoint. IMPROVEMENTS: -* [995](https://github.com/nginxinc/kubernetes-ingress/pull/995): Emit event for orphaned VirtualServerRoutes. -* Documentation improvements: [946](https://github.com/nginxinc/kubernetes-ingress/pull/946) thanks to [谭九鼎](https://github.com/imba-tjd), [948](https://github.com/nginxinc/kubernetes-ingress/pull/948), [972](https://github.com/nginxinc/kubernetes-ingress/pull/972), [965](https://github.com/nginxinc/kubernetes-ingress/pull/965). + +- [995](https://github.com/nginxinc/kubernetes-ingress/pull/995): Emit event for orphaned VirtualServerRoutes. +- Documentation improvements: [946](https://github.com/nginxinc/kubernetes-ingress/pull/946) thanks to [谭九 + 鼎](https://github.com/imba-tjd), [948](https://github.com/nginxinc/kubernetes-ingress/pull/948), + [972](https://github.com/nginxinc/kubernetes-ingress/pull/972), + [965](https://github.com/nginxinc/kubernetes-ingress/pull/965). BUGFIXES: -* [1030](https://github.com/nginxinc/kubernetes-ingress/pull/1030): Fix port range validation in cli arguments. -* [953](https://github.com/nginxinc/kubernetes-ingress/pull/953): Fix error logging of master/minion ingresses. + +- [1030](https://github.com/nginxinc/kubernetes-ingress/pull/1030): Fix port range validation in cli arguments. +- [953](https://github.com/nginxinc/kubernetes-ingress/pull/953): Fix error logging of master/minion ingresses. HELM CHART: -* The version of the helm chart is now 0.6.0. -* Add new parameters to the Chart: `controller.appprotect.enable`, `controller.globalConfiguration.create`, `controller.globalConfiguration.spec`, `controller.readyStatus.enable`, `controller.readyStatus.port`, `controller.config.annotations`, `controller.reportIngressStatus.annotations`. Added in [1035](https://github.com/nginxinc/kubernetes-ingress/pull/1035), [1034](https://github.com/nginxinc/kubernetes-ingress/pull/1034), [1029](https://github.com/nginxinc/kubernetes-ingress/pull/1029), [1003](https://github.com/nginxinc/kubernetes-ingress/pull/1003) thanks to [RubyLangdon](https://github.com/RubyLangdon). -* [1047](https://github.com/nginxinc/kubernetes-ingress/pull/1047) and [1009](https://github.com/nginxinc/kubernetes-ingress/pull/1009): Change how Helm manages the custom resource definitions (CRDs) to support installing multiple Ingress Controller releases. **Note**: If you're using the custom resources (`controller.enableCustomResources` is set to `true`), this is a breaking change. See the HELM UPGRADE section below for the upgrade instructions. + +- The version of the helm chart is now 0.6.0. +- Add new parameters to the Chart: `controller.appprotect.enable`, `controller.globalConfiguration.create`, + `controller.globalConfiguration.spec`, `controller.readyStatus.enable`, `controller.readyStatus.port`, + `controller.config.annotations`, `controller.reportIngressStatus.annotations`. Added in + [1035](https://github.com/nginxinc/kubernetes-ingress/pull/1035), + [1034](https://github.com/nginxinc/kubernetes-ingress/pull/1034), + [1029](https://github.com/nginxinc/kubernetes-ingress/pull/1029), + [1003](https://github.com/nginxinc/kubernetes-ingress/pull/1003) thanks to + [RubyLangdon](https://github.com/RubyLangdon). +- [1047](https://github.com/nginxinc/kubernetes-ingress/pull/1047) and + [1009](https://github.com/nginxinc/kubernetes-ingress/pull/1009): Change how Helm manages the custom resource + definitions (CRDs) to support installing multiple Ingress Controller releases. **Note**: If you're using the custom + resources (`controller.enableCustomResources` is set to `true`), this is a breaking change. See the HELM UPGRADE + section below for the upgrade instructions. CHANGES: -* Update NGINX version to 1.19.1. -* Update NGINX Plus to R22. -* [1029](https://github.com/nginxinc/kubernetes-ingress/pull/1029): Add readiness endpoint. The Ingress Controller now exposes a readiness endpoint on port `8081` and the path `/nginx-ready`. The endpoint returns a `200` response after the Ingress Controller finishes the initial configuration of NGINX at the start. The pod template was updated to use that endpoint in a readiness probe. -* [980](https://github.com/nginxinc/kubernetes-ingress/pull/980): Enable leader election by default. + +- Update NGINX version to 1.19.1. +- Update NGINX Plus to R22. +- [1029](https://github.com/nginxinc/kubernetes-ingress/pull/1029): Add readiness endpoint. The Ingress Controller now + exposes a readiness endpoint on port `8081` and the path `/nginx-ready`. The endpoint returns a `200` response after + the Ingress Controller finishes the initial configuration of NGINX at the start. The pod template was updated to use + that endpoint in a readiness probe. +- [980](https://github.com/nginxinc/kubernetes-ingress/pull/980): Enable leader election by default. UPGRADE: -* For NGINX, use the 1.8.0 image from our DockerHub: `nginx/nginx-ingress:1.8.0`, `nginx/nginx-ingress:1.8.0-alpine` or `nginx-ingress:1.8.0-ubi` -* For NGINX Plus, please build your own image using the 1.8.0 source code. -* For Helm, use version 0.6.0 of the chart. + +- For NGINX, use the 1.8.0 image from our DockerHub: `nginx/nginx-ingress:1.8.0`, `nginx/nginx-ingress:1.8.0-alpine` or + `nginx-ingress:1.8.0-ubi` +- For NGINX Plus, please build your own image using the 1.8.0 source code. +- For Helm, use version 0.6.0 of the chart. HELM UPGRADE: -If you're using custom resources like VirtualServer and TransportServer (`controller.enableCustomResources` is set to `true`), after you run the `helm upgrade` command, the CRDs and the corresponding custom resources will be removed from the cluster. Before upgrading, make sure to back up the custom resources. After running the `helm upgrade` command, run `kubectl apply -f deployments/helm-chart/crds` to re-install the CRDs and then restore the custom resources. +If you're using custom resources like VirtualServer and TransportServer (`controller.enableCustomResources` is set to +`true`), after you run the `helm upgrade` command, the CRDs and the corresponding custom resources will be removed from +the cluster. Before upgrading, make sure to back up the custom resources. After running the `helm upgrade` command, run +`kubectl apply -f deployments/helm-chart/crds` to re-install the CRDs and then restore the custom resources. NOTES: -* As part of installing a release, Helm will install the CRDs unless that step is disabled (see the [corresponding doc](https://docs.nginx.com/nginx-ingress-controller/installation/installation-with-helm/)). The installed CRDs include the CRDs for all Ingress Controller features, including the ones disabled by default (like App Protect with `aplogconfs.appprotect.f5.com` and `appolicies.appprotect.f5.com` CRDs). -### 1.7.2 +- As part of installing a release, Helm will install the CRDs unless that step is disabled (see the [corresponding + doc](https://docs.nginx.com/nginx-ingress-controller/installation/installation-with-helm/)). The installed CRDs + include the CRDs for all Ingress Controller features, including the ones disabled by default (like App Protect with + `aplogconfs.appprotect.f5.com` and `appolicies.appprotect.f5.com` CRDs). + +## 1.7.2 CHANGES: -* Update NGINX Plus version to R22. + +- Update NGINX Plus version to R22. HELM CHART: -* The version of the Helm chart is now 0.5.2. + +- The version of the Helm chart is now 0.5.2. UPGRADE: -* For NGINX, use the 1.7.2 image from our DockerHub: `nginx/nginx-ingress:1.7.2`, `nginx/nginx-ingress:1.7.2-alpine` or `nginx/nginx-ingress:1.7.2-ubi` -* For NGINX Plus, please build your own image using the 1.7.2 source code. -* For Helm, use version 0.5.2 of the chart. -### 1.7.1 +- For NGINX, use the 1.7.2 image from our DockerHub: `nginx/nginx-ingress:1.7.2`, `nginx/nginx-ingress:1.7.2-alpine` or + `nginx/nginx-ingress:1.7.2-ubi` +- For NGINX Plus, please build your own image using the 1.7.2 source code. +- For Helm, use version 0.5.2 of the chart. + +## 1.7.1 CHANGES: -* Update NGINX version to 1.19.0. + +- Update NGINX version to 1.19.0. HELM CHART: -* The version of the Helm chart is now 0.5.1. + +- The version of the Helm chart is now 0.5.1. UPGRADE: -* For NGINX, use the 1.7.1 image from our DockerHub: `nginx/nginx-ingress:1.7.1`, `nginx/nginx-ingress:1.7.1-alpine` or `nginx/nginx-ingress:1.7.1-ubi` -* For NGINX Plus, please build your own image using the 1.7.1 source code. -* For Helm, use version 0.5.1 of the chart. -### 1.7.0 +- For NGINX, use the 1.7.1 image from our DockerHub: `nginx/nginx-ingress:1.7.1`, `nginx/nginx-ingress:1.7.1-alpine` or + `nginx/nginx-ingress:1.7.1-ubi` +- For NGINX Plus, please build your own image using the 1.7.1 source code. +- For Helm, use version 0.5.1 of the chart. + +## 1.7.0 OVERVIEW: Release 1.7.0 includes: -* Support for TCP, UDP, and TLS Passthrough load balancing with the new configuration resources: TransportServer and GlobalConfiguration. The resources allow users to deliver complex, non-HTTP-based applications from Kubernetes using the NGINX Ingress Controller. -* Support for error pages in VirtualServer and VirtualServerRoute resources. A user can now specify custom error responses for errors returned by backend applications or generated by NGINX, such as a 502 response. -* Improved validation of VirtualServer and VirtualServerRoute resources. kubectl and the Kubernetes API server can now detect violations of the structure of VirtualServer/VirtualServerRoute resources and return an error. -* Support for an operator which manages the lifecycle of the Ingress Controller on Kubernetes or OpenShift. See the [NGINX Ingress Operator GitHub repo](https://github.com/nginxinc/nginx-ingress-operator). -See the [1.7.0 release announcement blog post](https://www.nginx.com/blog/announcing-nginx-ingress-controller-for-kubernetes-release-1-7-0/), which includes an overview of each feature. +- Support for TCP, UDP, and TLS Passthrough load balancing with the new configuration resources: TransportServer and + GlobalConfiguration. The resources allow users to deliver complex, non-HTTP-based applications from Kubernetes using + NGINX Ingress Controller. +- Support for error pages in VirtualServer and VirtualServerRoute resources. A user can now specify custom error + responses for errors returned by backend applications or generated by NGINX, such as a 502 response. +- Improved validation of VirtualServer and VirtualServerRoute resources. kubectl and the Kubernetes API server can now + detect violations of the structure of VirtualServer/VirtualServerRoute resources and return an error. +- Support for an operator which manages the lifecycle of the Ingress Controller on Kubernetes or OpenShift. See the + [NGINX Ingress Operator GitHub repo](https://github.com/nginxinc/nginx-ingress-operator). + +See the [1.7.0 release announcement blog +post](https://www.nginx.com/blog/announcing-nginx-ingress-controller-for-kubernetes-release-1-7-0/), which includes an +overview of each feature. You will find the complete changelog for release 1.7.0, including bug fixes, improvements, and changes below. FEATURES FOR VIRTUALSERVER AND VIRTUALSERVERROUTE RESOURCES: -* [868](https://github.com/nginxinc/kubernetes-ingress/pull/868): Add OpenAPI CRD schema validation. -* [847](https://github.com/nginxinc/kubernetes-ingress/pull/847): Add support for error pages for VS/VSR. + +- [868](https://github.com/nginxinc/kubernetes-ingress/pull/868): Add OpenAPI CRD schema validation. +- [847](https://github.com/nginxinc/kubernetes-ingress/pull/847): Add support for error pages for VS/VSR. FEATURES: -* [902](https://github.com/nginxinc/kubernetes-ingress/pull/902): Add TransportServer and GlobalConfiguration Resources. -* [894](https://github.com/nginxinc/kubernetes-ingress/pull/894): Add Dockerfile for NGINX Open Source for Openshift. -* [857](https://github.com/nginxinc/kubernetes-ingress/pull/857): Add Openshift Dockerfile for NGINX Plus. -* [852](https://github.com/nginxinc/kubernetes-ingress/pull/852): Add default-server-access-log-off to configmap. -* [845](https://github.com/nginxinc/kubernetes-ingress/pull/845): Add log-format-escaping and stream-log-format-escaping configmap keys. Thanks to [Alexey Maslov](https://github.com/alxmsl). -* [827](https://github.com/nginxinc/kubernetes-ingress/pull/827): Add ingress class label to all Prometheus metrics. +- [902](https://github.com/nginxinc/kubernetes-ingress/pull/902): Add TransportServer and GlobalConfiguration Resources. +- [894](https://github.com/nginxinc/kubernetes-ingress/pull/894): Add Dockerfile for NGINX Open Source for Openshift. +- [857](https://github.com/nginxinc/kubernetes-ingress/pull/857): Add Openshift Dockerfile for NGINX Plus. +- [852](https://github.com/nginxinc/kubernetes-ingress/pull/852): Add default-server-access-log-off to configmap. +- [845](https://github.com/nginxinc/kubernetes-ingress/pull/845): Add log-format-escaping and stream-log-format-escaping + configmap keys. Thanks to [Alexey Maslov](https://github.com/alxmsl). +- [827](https://github.com/nginxinc/kubernetes-ingress/pull/827): Add ingress class label to all Prometheus metrics. IMPROVEMENTS: -* [850](https://github.com/nginxinc/kubernetes-ingress/pull/850): Extend redirect URI validation with protocol check in VS/VSR. -* [832](https://github.com/nginxinc/kubernetes-ingress/pull/832): Update the examples to run the `nginxdemos/nginx-hello:plain-text` image, that doesn't require root user. -* [825](https://github.com/nginxinc/kubernetes-ingress/pull/825): Add multi-stage docker builds. + +- [850](https://github.com/nginxinc/kubernetes-ingress/pull/850): Extend redirect URI validation with protocol check in + VS/VSR. +- [832](https://github.com/nginxinc/kubernetes-ingress/pull/832): Update the examples to run the + `nginxdemos/nginx-hello:plain-text` image, that doesn't require root user. +- [825](https://github.com/nginxinc/kubernetes-ingress/pull/825): Add multi-stage docker builds. BUGFIXES: -* [828](https://github.com/nginxinc/kubernetes-ingress/pull/828): Fix error messages for actions of the type return. + +- [828](https://github.com/nginxinc/kubernetes-ingress/pull/828): Fix error messages for actions of the type return. HELM CHART: -* The version of the helm chart is now 0.5.0. -* Add new parameters to the Chart: `controller.enableTLSPassthrough`, `controller.volumes`, `controller.volumeMounts`, `controller.priorityClassName`. Added in [921](https://github.com/nginxinc/kubernetes-ingress/pull/921), [878](https://github.com/nginxinc/kubernetes-ingress/pull/878), [807](https://github.com/nginxinc/kubernetes-ingress/pull/807) thanks to [Greg Snow](https://github.com/gsnegovskiy). + +- The version of the helm chart is now 0.5.0. +- Add new parameters to the Chart: `controller.enableTLSPassthrough`, `controller.volumes`, `controller.volumeMounts`, + `controller.priorityClassName`. Added in [921](https://github.com/nginxinc/kubernetes-ingress/pull/921), + [878](https://github.com/nginxinc/kubernetes-ingress/pull/878), + [807](https://github.com/nginxinc/kubernetes-ingress/pull/807) thanks to [Greg Snow](https://github.com/gsnegovskiy). CHANGES: -* Update NGINX version to 1.17.10. -* Update NGINX Plus to R21. -* [854](https://github.com/nginxinc/kubernetes-ingress/pull/854): Update the Debian base images for NGINX Plus to `debian:buster-slim`. -* [852](https://github.com/nginxinc/kubernetes-ingress/pull/852): Add default-server-access-log-off to configmap. The access logs for the default server are now enabled by default. -* [847](https://github.com/nginxinc/kubernetes-ingress/pull/847): Add support for error pages for VS/VSR. The PR affects how the Ingress Controller generates configuration for VirtualServer and VirtualServerRoutes. See [this comment](https://github.com/nginxinc/kubernetes-ingress/pull/847) for more details. -* [827](https://github.com/nginxinc/kubernetes-ingress/pull/827): Add ingress class label to all Prometheus metrics. Every Prometheus metric exposed by the Ingress Controller now includes the label `class` with the value of the Ingress Controller class (by default `nginx`), -* [825](https://github.com/nginxinc/kubernetes-ingress/pull/825): Add multi-stage docker builds. When building the Ingress Controller image in Docker, we now use a multi-stage docker build. + +- Update NGINX version to 1.17.10. +- Update NGINX Plus to R21. +- [854](https://github.com/nginxinc/kubernetes-ingress/pull/854): Update the Debian base images for NGINX Plus to + `debian:buster-slim`. +- [852](https://github.com/nginxinc/kubernetes-ingress/pull/852): Add default-server-access-log-off to configmap. The + access logs for the default server are now enabled by default. +- [847](https://github.com/nginxinc/kubernetes-ingress/pull/847): Add support for error pages for VS/VSR. The PR affects + how the Ingress Controller generates configuration for VirtualServer and VirtualServerRoutes. See [this + comment](https://github.com/nginxinc/kubernetes-ingress/pull/847) for more details. +- [827](https://github.com/nginxinc/kubernetes-ingress/pull/827): Add ingress class label to all Prometheus metrics. + Every Prometheus metric exposed by the Ingress Controller now includes the label `class` with the value of the Ingress + Controller class (by default `nginx`), +- [825](https://github.com/nginxinc/kubernetes-ingress/pull/825): Add multi-stage docker builds. When building the + Ingress Controller image in Docker, we now use a multi-stage docker build. UPGRADE: -* For NGINX, use the 1.7.0 image from our DockerHub: `nginx/nginx-ingress:1.7.0`, `nginx/nginx-ingress:1.7.0-alpine` or `nginx-ingress:1.7.0-ubi` -* For NGINX Plus, please build your own image using the 1.7.0 source code. -* For Helm, use version 0.5.0 of the chart. -When upgrading using the [manifests](https://docs.nginx.com/nginx-ingress-controller/installation/installation-with-manifests/), make sure to deploy the new TransportServer CRD (`common/ts-definition.yaml`), as it is required by the Ingress Controller. Otherwise, you will get error messages in the Ingress Controller logs. +- For NGINX, use the 1.7.0 image from our DockerHub: `nginx/nginx-ingress:1.7.0`, `nginx/nginx-ingress:1.7.0-alpine` or + `nginx-ingress:1.7.0-ubi` +- For NGINX Plus, please build your own image using the 1.7.0 source code. +- For Helm, use version 0.5.0 of the chart. -### 1.6.3 +When upgrading using the +[manifests](https://docs.nginx.com/nginx-ingress-controller/installation/installation-with-manifests/), make sure to +deploy the new TransportServer CRD (`common/ts-definition.yaml`), as it is required by the Ingress Controller. +Otherwise, you will get error messages in the Ingress Controller logs. + +## 1.6.3 CHANGES: -* Update NGINX version to 1.17.9. + +- Update NGINX version to 1.17.9. HELM CHART: -* The version of the Helm chart is now 0.4.3. + +- The version of the Helm chart is now 0.4.3. UPGRADE: -* For NGINX, use the 1.6.3 image from our DockerHub: `nginx/nginx-ingress:1.6.3` or `nginx/nginx-ingress:1.6.3-alpine` -* For NGINX Plus, please build your own image using the 1.6.3 source code. -* For Helm, use version 0.4.3 of the chart. -### 1.6.2 +- For NGINX, use the 1.6.3 image from our DockerHub: `nginx/nginx-ingress:1.6.3` or `nginx/nginx-ingress:1.6.3-alpine` +- For NGINX Plus, please build your own image using the 1.6.3 source code. +- For Helm, use version 0.4.3 of the chart. + +## 1.6.2 CHANGES: -* Update NGINX version to 1.17.8. + +- Update NGINX version to 1.17.8. HELM CHART: -* The version of the Helm chart is now 0.4.2. + +- The version of the Helm chart is now 0.4.2. UPGRADE: -* For NGINX, use the 1.6.2 image from our DockerHub: `nginx/nginx-ingress:1.6.2` or `nginx/nginx-ingress:1.6.2-alpine` -* For NGINX Plus, please build your own image using the 1.6.2 source code. -* For Helm, use version 0.4.2 of the chart. -### 1.6.1 +- For NGINX, use the 1.6.2 image from our DockerHub: `nginx/nginx-ingress:1.6.2` or `nginx/nginx-ingress:1.6.2-alpine` +- For NGINX Plus, please build your own image using the 1.6.2 source code. +- For Helm, use version 0.4.2 of the chart. + +## 1.6.1 CHANGES: -* Update NGINX version to 1.17.7. + +- Update NGINX version to 1.17.7. HELM CHART: -* The version of the Helm chart is now 0.4.1. + +- The version of the Helm chart is now 0.4.1. UPGRADE: -* For NGINX, use the 1.6.1 image from our DockerHub: `nginx/nginx-ingress:1.6.1` or `nginx/nginx-ingress:1.6.1-alpine` -* For NGINX Plus, please build your own image using the 1.6.1 source code. -* For Helm, use version 0.4.1 of the chart. -### 1.6.0 +- For NGINX, use the 1.6.1 image from our DockerHub: `nginx/nginx-ingress:1.6.1` or `nginx/nginx-ingress:1.6.1-alpine` +- For NGINX Plus, please build your own image using the 1.6.1 source code. +- For Helm, use version 0.4.1 of the chart. + +## 1.6.0 OVERVIEW: Release 1.6.0 includes: -* Improvements to VirtualServer and VirtualServerRoute resources, adding support for richer load balancing behavior, more sophisticated request routing, redirects, direct responses, and blue-green and circuit breaker patterns. The VirtualServer and VirtualServerRoute resources are enabled by default and are ready for production use. -* Support for OpenTracing, helping you to monitor and debug complex transactions. -* An improved security posture, with support to run the Ingress Controller as a non-root user. -The release announcement blog post includes the overview for each feature. See https://www.nginx.com/blog/announcing-nginx-ingress-controller-for-kubernetes-release-1-6-0/ +- Improvements to VirtualServer and VirtualServerRoute resources, adding support for richer load balancing behavior, + more sophisticated request routing, redirects, direct responses, and blue-green and circuit breaker patterns. The + VirtualServer and VirtualServerRoute resources are enabled by default and are ready for production use. +- Support for OpenTracing, helping you to monitor and debug complex transactions. +- An improved security posture, with support to run the Ingress Controller as a non-root user. + +The release announcement blog post includes the overview for each feature. See + You will find the complete changelog for release 1.6.0, including bug fixes, improvements, and changes below. FEATURES FOR VIRTUALSERVER AND VIRTUALSERVERROUTE RESOURCES: -* [780](https://github.com/nginxinc/kubernetes-ingress/pull/780): Add support for canned responses to VS/VSR. -* [778](https://github.com/nginxinc/kubernetes-ingress/pull/778): Add redirect support in VS/VSR. -* [766](https://github.com/nginxinc/kubernetes-ingress/pull/766): Add exact matches and regex support to location paths in VS/VSR. -* [748](https://github.com/nginxinc/kubernetes-ingress/pull/748): Add TLS redirect support in Virtualserver. -* [745](https://github.com/nginxinc/kubernetes-ingress/pull/745): Improve routing rules in VS/VSR -* [728](https://github.com/nginxinc/kubernetes-ingress/pull/728): Add session persistence in VS/VSR. -* [724](https://github.com/nginxinc/kubernetes-ingress/pull/724): Add VS/VSR Prometheus metrics. -* [712](https://github.com/nginxinc/kubernetes-ingress/pull/712): Add service subselector support in vs/vsr. -* [707](https://github.com/nginxinc/kubernetes-ingress/pull/707): Emit warning events in VS/VSR. -* [701](https://github.com/nginxinc/kubernetes-ingress/pull/701): Add support queue in upstreams for plus in VS/VSR. -* [693](https://github.com/nginxinc/kubernetes-ingress/pull/693): Add ServerStatusZones support in vs/vsr. -* [670](https://github.com/nginxinc/kubernetes-ingress/pull/670): Add buffering support for vs/vsr. -* [660](https://github.com/nginxinc/kubernetes-ingress/pull/660): Add ClientBodyMaxSize support in vs/vsr. -* [659](https://github.com/nginxinc/kubernetes-ingress/pull/659): Support configuring upstream zone sizes in VS/VSR. -* [655](https://github.com/nginxinc/kubernetes-ingress/pull/655): Add slow-start support in vs/vsr. -* [653](https://github.com/nginxinc/kubernetes-ingress/pull/653): Add websockets support for vs/vsr upstreams. -* [641](https://github.com/nginxinc/kubernetes-ingress/pull/641): Add support for ExternalName Services for vs/vsr. -* [635](https://github.com/nginxinc/kubernetes-ingress/pull/635): Add HealthChecks support for vs/vsr. -* [634](https://github.com/nginxinc/kubernetes-ingress/pull/634): Add Active Connections support to vs/vsr. -* [628](https://github.com/nginxinc/kubernetes-ingress/pull/628): Add retries support for vs/vsr. -* [621](https://github.com/nginxinc/kubernetes-ingress/pull/621): Add TLS support for vs/vsr upstreams. -* [617](https://github.com/nginxinc/kubernetes-ingress/pull/617): Add keepalive support to vs/vsr. -* [612](https://github.com/nginxinc/kubernetes-ingress/pull/612): Add timeouts support to vs/vsr. -* [607](https://github.com/nginxinc/kubernetes-ingress/pull/607): Add fail-timeout and max-fails support to vs/vsr. -* [596](https://github.com/nginxinc/kubernetes-ingress/pull/596): Add lb-method support in vs and vsr. + +- [780](https://github.com/nginxinc/kubernetes-ingress/pull/780): Add support for canned responses to VS/VSR. +- [778](https://github.com/nginxinc/kubernetes-ingress/pull/778): Add redirect support in VS/VSR. +- [766](https://github.com/nginxinc/kubernetes-ingress/pull/766): Add exact matches and regex support to location paths + in VS/VSR. +- [748](https://github.com/nginxinc/kubernetes-ingress/pull/748): Add TLS redirect support in Virtualserver. +- [745](https://github.com/nginxinc/kubernetes-ingress/pull/745): Improve routing rules in VS/VSR +- [728](https://github.com/nginxinc/kubernetes-ingress/pull/728): Add session persistence in VS/VSR. +- [724](https://github.com/nginxinc/kubernetes-ingress/pull/724): Add VS/VSR Prometheus metrics. +- [712](https://github.com/nginxinc/kubernetes-ingress/pull/712): Add service subselector support in vs/vsr. +- [707](https://github.com/nginxinc/kubernetes-ingress/pull/707): Emit warning events in VS/VSR. +- [701](https://github.com/nginxinc/kubernetes-ingress/pull/701): Add support queue in upstreams for plus in VS/VSR. +- [693](https://github.com/nginxinc/kubernetes-ingress/pull/693): Add ServerStatusZones support in vs/vsr. +- [670](https://github.com/nginxinc/kubernetes-ingress/pull/670): Add buffering support for vs/vsr. +- [660](https://github.com/nginxinc/kubernetes-ingress/pull/660): Add ClientBodyMaxSize support in vs/vsr. +- [659](https://github.com/nginxinc/kubernetes-ingress/pull/659): Support configuring upstream zone sizes in VS/VSR. +- [655](https://github.com/nginxinc/kubernetes-ingress/pull/655): Add slow-start support in vs/vsr. +- [653](https://github.com/nginxinc/kubernetes-ingress/pull/653): Add websockets support for vs/vsr upstreams. +- [641](https://github.com/nginxinc/kubernetes-ingress/pull/641): Add support for ExternalName Services for vs/vsr. +- [635](https://github.com/nginxinc/kubernetes-ingress/pull/635): Add HealthChecks support for vs/vsr. +- [634](https://github.com/nginxinc/kubernetes-ingress/pull/634): Add Active Connections support to vs/vsr. +- [628](https://github.com/nginxinc/kubernetes-ingress/pull/628): Add retries support for vs/vsr. +- [621](https://github.com/nginxinc/kubernetes-ingress/pull/621): Add TLS support for vs/vsr upstreams. +- [617](https://github.com/nginxinc/kubernetes-ingress/pull/617): Add keepalive support to vs/vsr. +- [612](https://github.com/nginxinc/kubernetes-ingress/pull/612): Add timeouts support to vs/vsr. +- [607](https://github.com/nginxinc/kubernetes-ingress/pull/607): Add fail-timeout and max-fails support to vs/vsr. +- [596](https://github.com/nginxinc/kubernetes-ingress/pull/596): Add lb-method support in vs and vsr. FEATURES: -* [750](https://github.com/nginxinc/kubernetes-ingress/pull/750): Add support for health status uri customisation. -* [691](https://github.com/nginxinc/kubernetes-ingress/pull/691): Helper Functions for custom annotations. -* [631](https://github.com/nginxinc/kubernetes-ingress/pull/631): Add max_conns support for NGINX plus. -* [629](https://github.com/nginxinc/kubernetes-ingress/pull/629): Added upstream zone directive annotation. Thanks to [Victor Regalado](https://github.com/vrrs). -* [616](https://github.com/nginxinc/kubernetes-ingress/pull/616): Add proxy-send-timeout to configmap key and annotation. -* [615](https://github.com/nginxinc/kubernetes-ingress/pull/615): Add support for Opentracing. -* [614](https://github.com/nginxinc/kubernetes-ingress/pull/614): Add max-conns annotation. Thanks to [Victor Regalado](https://github.com/vrrs). +- [750](https://github.com/nginxinc/kubernetes-ingress/pull/750): Add support for health status uri customisation. +- [691](https://github.com/nginxinc/kubernetes-ingress/pull/691): Helper Functions for custom annotations. +- [631](https://github.com/nginxinc/kubernetes-ingress/pull/631): Add max_conns support for NGINX plus. +- [629](https://github.com/nginxinc/kubernetes-ingress/pull/629): Added upstream zone directive annotation. Thanks to + [Victor Regalado](https://github.com/vrrs). +- [616](https://github.com/nginxinc/kubernetes-ingress/pull/616): Add proxy-send-timeout to configmap key and + annotation. +- [615](https://github.com/nginxinc/kubernetes-ingress/pull/615): Add support for Opentracing. +- [614](https://github.com/nginxinc/kubernetes-ingress/pull/614): Add max-conns annotation. Thanks to [Victor + Regalado](https://github.com/vrrs). IMPROVEMENTS: -* [678](https://github.com/nginxinc/kubernetes-ingress/pull/678): Increase defaults for server-names-hash-max-size and servers-names-hash-bucket-size ConfigMap keys. -* [694](https://github.com/nginxinc/kubernetes-ingress/pull/694): Reject VS/VSR resources with enabled plus features for OSS. -* Documentation improvements: [713](https://github.com/nginxinc/kubernetes-ingress/pull/713) thanks to [Matthew Wahner](https://github.com/mattwahner). + +- [678](https://github.com/nginxinc/kubernetes-ingress/pull/678): Increase defaults for server-names-hash-max-size and + servers-names-hash-bucket-size ConfigMap keys. +- [694](https://github.com/nginxinc/kubernetes-ingress/pull/694): Reject VS/VSR resources with enabled plus features for + OSS. +- Documentation improvements: [713](https://github.com/nginxinc/kubernetes-ingress/pull/713) thanks to [Matthew + Wahner](https://github.com/mattwahner). BUGFIXES: -* [788](https://github.com/nginxinc/kubernetes-ingress/pull/788): Fix VSR updates when namespace is set implicitly. -* [736](https://github.com/nginxinc/kubernetes-ingress/pull/736): Init Ingress labeled metrics on start. -* [686](https://github.com/nginxinc/kubernetes-ingress/pull/686): Check if config map created for leader-election. -* [664](https://github.com/nginxinc/kubernetes-ingress/pull/664): Fix reporting events for Ingress minions. -* [632](https://github.com/nginxinc/kubernetes-ingress/pull/632): Fix hsts support when not using SSL. Thanks to [Martín Fernández](https://github.com/bilby91). + +- [788](https://github.com/nginxinc/kubernetes-ingress/pull/788): Fix VSR updates when namespace is set implicitly. +- [736](https://github.com/nginxinc/kubernetes-ingress/pull/736): Init Ingress labeled metrics on start. +- [686](https://github.com/nginxinc/kubernetes-ingress/pull/686): Check if config map created for leader-election. +- [664](https://github.com/nginxinc/kubernetes-ingress/pull/664): Fix reporting events for Ingress minions. +- [632](https://github.com/nginxinc/kubernetes-ingress/pull/632): Fix hsts support when not using SSL. Thanks to [Martín + Fernández](https://github.com/bilby91). HELM CHART: -* The version of the helm chart is now 0.4.0. -* Add new parameters to the Chart: `controller.healthCheckURI`, `controller.resources`, `controller.logLevel`, `controller.customPorts`, `controller.service.customPorts`. Added in [750](https://github.com/nginxinc/kubernetes-ingress/pull/750), [636](https://github.com/nginxinc/kubernetes-ingress/pull/636) thanks to [Guilherme Oki](https://github.com/guilhermeoki), [600](https://github.com/nginxinc/kubernetes-ingress/pull/600), [581](https://github.com/nginxinc/kubernetes-ingress/pull/581) thanks to [Alex Meijer](https://github.com/ameijer-corsha). -* [722](https://github.com/nginxinc/kubernetes-ingress/pull/722): Fix trailing leader election cm when using helm. This change might lead to a failed upgrade. See the helm upgrade instruction below. -* [573](https://github.com/nginxinc/kubernetes-ingress/pull/573): Use Controller name value for app selectors. + +- The version of the helm chart is now 0.4.0. +- Add new parameters to the Chart: `controller.healthCheckURI`, `controller.resources`, `controller.logLevel`, + `controller.customPorts`, `controller.service.customPorts`. Added in + [750](https://github.com/nginxinc/kubernetes-ingress/pull/750), + [636](https://github.com/nginxinc/kubernetes-ingress/pull/636) thanks to [Guilherme + Oki](https://github.com/guilhermeoki), [600](https://github.com/nginxinc/kubernetes-ingress/pull/600), + [581](https://github.com/nginxinc/kubernetes-ingress/pull/581) thanks to [Alex + Meijer](https://github.com/ameijer-corsha). +- [722](https://github.com/nginxinc/kubernetes-ingress/pull/722): Fix trailing leader election cm when using helm. This + change might lead to a failed upgrade. See the helm upgrade instruction below. +- [573](https://github.com/nginxinc/kubernetes-ingress/pull/573): Use Controller name value for app selectors. CHANGES: -* Update NGINX versions to 1.17.6. -* Update NGINX Plus version to R20. -* [799](https://github.com/nginxinc/kubernetes-ingress/pull/779): Enable CRDs by default. VirtualServer and VirtualServerRoute resources are now enabled by default. -* [772](https://github.com/nginxinc/kubernetes-ingress/pull/772): Update VS/VSR version from v1alpha1 to v1. Make sure to update the `apiVersion` of your VirtualServer and VirtualServerRoute resources. -* [748](https://github.com/nginxinc/kubernetes-ingress/pull/748): Add TLS redirect support in VirtualServer. The `redirect-to-https` and `ssl-redirect` ConfigMap keys no longer have any effect on generated configs for VirtualServer resources. -* [745](https://github.com/nginxinc/kubernetes-ingress/pull/745): Improve routing rules. Update the spec of VirtualServer and VirtualServerRoute accordingly. See YAML examples of the changes [here](https://github.com/nginxinc/kubernetes-ingress/pull/745). -* [710](https://github.com/nginxinc/kubernetes-ingress/pull/710): Run IC as non-root. Make sure to use the updated manifests to install/upgrade the Ingress Controller. -* [603](https://github.com/nginxinc/kubernetes-ingress/pull/603): Update apiVersion in Deployments and DaemonSets to apps/v1. + +- Update NGINX versions to 1.17.6. +- Update NGINX Plus version to R20. +- [799](https://github.com/nginxinc/kubernetes-ingress/pull/779): Enable CRDs by default. VirtualServer and + VirtualServerRoute resources are now enabled by default. +- [772](https://github.com/nginxinc/kubernetes-ingress/pull/772): Update VS/VSR version from v1alpha1 to v1. Make sure + to update the `apiVersion` of your VirtualServer and VirtualServerRoute resources. +- [748](https://github.com/nginxinc/kubernetes-ingress/pull/748): Add TLS redirect support in VirtualServer. The + `redirect-to-https` and `ssl-redirect` ConfigMap keys no longer have any effect on generated configs for VirtualServer + resources. +- [745](https://github.com/nginxinc/kubernetes-ingress/pull/745): Improve routing rules. Update the spec of + VirtualServer and VirtualServerRoute accordingly. See YAML examples of the changes + [here](https://github.com/nginxinc/kubernetes-ingress/pull/745). +- [710](https://github.com/nginxinc/kubernetes-ingress/pull/710): Run IC as non-root. Make sure to use the updated + manifests to install/upgrade the Ingress Controller. +- [603](https://github.com/nginxinc/kubernetes-ingress/pull/603): Update apiVersion in Deployments and DaemonSets to + apps/v1. UPGRADE: -* For NGINX, use the 1.6.0 image from our DockerHub: `nginx/nginx-ingress:1.6.0` or `nginx/nginx-ingress:1.6.0-alpine` -* For NGINX Plus, please build your own image using the 1.6.0 source code. -* For Helm, use version 0.4.0 of the chart. + +- For NGINX, use the 1.6.0 image from our DockerHub: `nginx/nginx-ingress:1.6.0` or `nginx/nginx-ingress:1.6.0-alpine` +- For NGINX Plus, please build your own image using the 1.6.0 source code. +- For Helm, use version 0.4.0 of the chart. HELM UPGRADE: -If leader election (the `controller.reportIngressStatus.enableLeaderElection` parameter) is enabled, when upgrading to the new version of the Helm chart: -1. Make sure to specify a new ConfigMap lock name (`controller.reportIngressStatus.leaderElectionLockName`) different from the one that was created by the current version. To find out the current name, check ConfigMap resources in the namespace where the Ingress Controller is running. +If leader election (the `controller.reportIngressStatus.enableLeaderElection` parameter) is enabled, when upgrading to +the new version of the Helm chart: + +1. Make sure to specify a new ConfigMap lock name (`controller.reportIngressStatus.leaderElectionLockName`) different + from the one that was created by the current version. To find out the current name, check ConfigMap resources in the + namespace where the Ingress Controller is running. 1. After the upgrade, delete the old ConfigMap. Otherwise, the helm upgrade will not succeed. -### 1.5.8 +## 1.5.8 CHANGES: -* Update NGINX version to 1.17.6. -* Update deployment and daemonset manifests to apps/v1. + +- Update NGINX version to 1.17.6. +- Update deployment and daemonset manifests to apps/v1. HELM CHART: -* The version of the Helm chart is now 0.3.8. + +- The version of the Helm chart is now 0.3.8. UPGRADE: -* For NGINX, use the 1.5.8 image from our DockerHub: `nginx/nginx-ingress:1.5.8` or `nginx/nginx-ingress:1.5.8-alpine` -* For NGINX Plus, please build your own image using the 1.5.8 source code. -* For Helm, use version 0.3.8 of the chart. -### 1.5.7 +- For NGINX, use the 1.5.8 image from our DockerHub: `nginx/nginx-ingress:1.5.8` or `nginx/nginx-ingress:1.5.8-alpine` +- For NGINX Plus, please build your own image using the 1.5.8 source code. +- For Helm, use version 0.3.8 of the chart. + +## 1.5.7 CHANGES: -* Update NGINX version to 1.17.5. + +- Update NGINX version to 1.17.5. HELM CHART: -* The version of the Helm chart is now 0.3.7. + +- The version of the Helm chart is now 0.3.7. UPGRADE: -* For NGINX, use the 1.5.7 image from our DockerHub: `nginx/nginx-ingress:1.5.7` or `nginx/nginx-ingress:1.5.7-alpine` -* For NGINX Plus, please build your own image using the 1.5.7 source code. -* For Helm, use version 0.3.7 of the chart. -### 1.5.6 +- For NGINX, use the 1.5.7 image from our DockerHub: `nginx/nginx-ingress:1.5.7` or `nginx/nginx-ingress:1.5.7-alpine` +- For NGINX Plus, please build your own image using the 1.5.7 source code. +- For Helm, use version 0.3.7 of the chart. + +## 1.5.6 CHANGES: -* Update NGINX version to 1.17.4. + +- Update NGINX version to 1.17.4. HELM CHART: -* The version of the Helm chart is now 0.3.6. + +- The version of the Helm chart is now 0.3.6. UPGRADE: -* For NGINX, use the 1.5.6 image from our DockerHub: `nginx/nginx-ingress:1.5.6` or `nginx/nginx-ingress:1.5.6-alpine` -* For NGINX Plus, please build your own image using the 1.5.6 source code. -* For Helm, use version 0.3.6 of the chart. -### 1.5.5 +- For NGINX, use the 1.5.6 image from our DockerHub: `nginx/nginx-ingress:1.5.6` or `nginx/nginx-ingress:1.5.6-alpine` +- For NGINX Plus, please build your own image using the 1.5.6 source code. +- For Helm, use version 0.3.6 of the chart. + +## 1.5.5 CHANGES: -* Update NGINX Plus version to R19. + +- Update NGINX Plus version to R19. HELM CHART: -* The version of the Helm chart is now 0.3.5. + +- The version of the Helm chart is now 0.3.5. UPGRADE: -* For NGINX, use the 1.5.5 image from our DockerHub: `nginx/nginx-ingress:1.5.5` or `nginx/nginx-ingress:1.5.5-alpine` -* For NGINX Plus, please build your own image using the 1.5.5 source code. -* For Helm, use version 0.3.5 of the chart. -### 1.5.4 +- For NGINX, use the 1.5.5 image from our DockerHub: `nginx/nginx-ingress:1.5.5` or `nginx/nginx-ingress:1.5.5-alpine` +- For NGINX Plus, please build your own image using the 1.5.5 source code. +- For Helm, use version 0.3.5 of the chart. + +## 1.5.4 CHANGES: -* Update NGINX version to 1.17.3. + +- Update NGINX version to 1.17.3. HELM CHART: -* The version of the Helm chart is now 0.3.4. + +- The version of the Helm chart is now 0.3.4. UPGRADE: -* For NGINX, use the 1.5.4 image from our DockerHub: `nginx/nginx-ingress:1.5.4` or `nginx/nginx-ingress:1.5.4-alpine` -* For NGINX Plus, please build your own image using the 1.5.4 source code. -* For Helm, use version 0.3.4 of the chart. -### 1.5.3 +- For NGINX, use the 1.5.4 image from our DockerHub: `nginx/nginx-ingress:1.5.4` or `nginx/nginx-ingress:1.5.4-alpine` +- For NGINX Plus, please build your own image using the 1.5.4 source code. +- For Helm, use version 0.3.4 of the chart. + +## 1.5.3 CHANGES: -* Update NGINX Plus version to R18p1. + +- Update NGINX Plus version to R18p1. HELM CHART: -* The version of the Helm chart is now 0.3.3. + +- The version of the Helm chart is now 0.3.3. UPGRADE: -* For NGINX, use the 1.5.3 image from our DockerHub: `nginx/nginx-ingress:1.5.3` or `nginx/nginx-ingress:1.5.3-alpine` -* For NGINX Plus, please build your own image using the 1.5.3 source code. -* For Helm, use version 0.3.3 of the chart. -### 1.5.2 +- For NGINX, use the 1.5.3 image from our DockerHub: `nginx/nginx-ingress:1.5.3` or `nginx/nginx-ingress:1.5.3-alpine` +- For NGINX Plus, please build your own image using the 1.5.3 source code. +- For Helm, use version 0.3.3 of the chart. + +## 1.5.2 CHANGES: -* Update NGINX version to 1.17.2. + +- Update NGINX version to 1.17.2. HELM CHART: -* The version of the Helm chart is now 0.3.2. + +- The version of the Helm chart is now 0.3.2. UPGRADE: -* For NGINX, use the 1.5.2 image from our DockerHub: `nginx/nginx-ingress:1.5.2` or `nginx/nginx-ingress:1.5.2-alpine` -* For NGINX Plus, please build your own image using the 1.5.2 source code. -* For Helm, use version 0.3.2 of the chart. -### 1.5.1 +- For NGINX, use the 1.5.2 image from our DockerHub: `nginx/nginx-ingress:1.5.2` or `nginx/nginx-ingress:1.5.2-alpine` +- For NGINX Plus, please build your own image using the 1.5.2 source code. +- For Helm, use version 0.3.2 of the chart. + +## 1.5.1 CHANGES: -* Update NGINX version to 1.17.1. + +- Update NGINX version to 1.17.1. HELM CHART: -* The version of the Helm chart is now 0.3.1. -* [593](https://github.com/nginxinc/kubernetes-ingress/pull/593): Fix the selector in the Ingress Controller service when the `controller.name` parameter is set. This introduces a change, see the HELM UPGRADE section. + +- The version of the Helm chart is now 0.3.1. +- [593](https://github.com/nginxinc/kubernetes-ingress/pull/593): Fix the selector in the Ingress Controller service + when the `controller.name` parameter is set. This introduces a change, see the HELM UPGRADE section. UPGRADE: -* For NGINX, use the 1.5.1 image from our DockerHub: `nginx/nginx-ingress:1.5.1` or `nginx/nginx-ingress:1.5.1-alpine` -* For NGINX Plus, please build your own image using the 1.5.1 source code. -* For Helm, use version 0.3.1 of the chart. + +- For NGINX, use the 1.5.1 image from our DockerHub: `nginx/nginx-ingress:1.5.1` or `nginx/nginx-ingress:1.5.1-alpine` +- For NGINX Plus, please build your own image using the 1.5.1 source code. +- For Helm, use version 0.3.1 of the chart. HELM UPGRADE: -In the changelog of Release 1.5.0, we advised not to upgrade the helm chart from `0.2.1` to `0.3.0` unless the mentioned in the changelog problems were acceptable. This release we provide mitigation instructions on how to upgrade from `0.2.1` to `0.3.1` without disruptions. +In the changelog of Release 1.5.0, we advised not to upgrade the helm chart from `0.2.1` to `0.3.0` unless the mentioned +in the changelog problems were acceptable. This release we provide mitigation instructions on how to upgrade from +`0.2.1` to `0.3.1` without disruptions. When you upgrade from `0.2.1` to `0.3.1`, make sure to configure the following parameters: -* `controller.name` is set to `nginx-ingress` or the previously used value in case you customized it. This ensures the Deployment/Daemonset will not be recreated. -* `controller.service.name` is set to `nginx-ingress`. This ensures the service will not be recreated. -* `controller.config.name` is set to `nginx-config`. This ensures the ConfigMap will not be recreated. -Upgrading from `0.3.0` to `0.3.1`: Upgrading is not affected unless you customized `controller.name`. In that case, because of the fix [593](https://github.com/nginxinc/kubernetes-ingress/pull/593), the upgraded service will have a new selector, and the upgraded pod spec will have a new label. As a result, during an upgrade, the old pods will be immediately excluded from the service. Also, for the Deployment, the old pods will not terminate but continue to run. To terminate the old pods, manually remove the corresponding ReplicaSet. +- `controller.name` is set to `nginx-ingress` or the previously used value in case you customized it. This ensures the + Deployment/Daemonset will not be recreated. +- `controller.service.name` is set to `nginx-ingress`. This ensures the service will not be recreated. +- `controller.config.name` is set to `nginx-config`. This ensures the ConfigMap will not be recreated. + +Upgrading from `0.3.0` to `0.3.1`: Upgrading is not affected unless you customized `controller.name`. In that case, +because of the fix [593](https://github.com/nginxinc/kubernetes-ingress/pull/593), the upgraded service will have a new +selector, and the upgraded pod spec will have a new label. As a result, during an upgrade, the old pods will be +immediately excluded from the service. Also, for the Deployment, the old pods will not terminate but continue to run. To +terminate the old pods, manually remove the corresponding ReplicaSet. -### 1.5.0 +## 1.5.0 FEATURES: -* [560](https://github.com/nginxinc/kubernetes-ingress/pull/560): Add new configuration resources -- VirtualServer and VirtualServerRoute. -* [554](https://github.com/nginxinc/kubernetes-ingress/pull/554): Add new Prometheus metrics related to the Ingress Controller's operation (as opposed to NGINX/NGINX Plus metrics). -* [496](https://github.com/nginxinc/kubernetes-ingress/pull/496): Support a wildcard TLS certificate for TLS-enabled Ingress resources. -* [485](https://github.com/nginxinc/kubernetes-ingress/pull/485): Support ExternalName services in Ingress backends. + +- [560](https://github.com/nginxinc/kubernetes-ingress/pull/560): Add new configuration resources -- VirtualServer and + VirtualServerRoute. +- [554](https://github.com/nginxinc/kubernetes-ingress/pull/554): Add new Prometheus metrics related to the Ingress + Controller's operation (as opposed to NGINX/NGINX Plus metrics). +- [496](https://github.com/nginxinc/kubernetes-ingress/pull/496): Support a wildcard TLS certificate for TLS-enabled + Ingress resources. +- [485](https://github.com/nginxinc/kubernetes-ingress/pull/485): Support ExternalName services in Ingress backends. IMPROVEMENTS: -* Add new ConfigMap keys: `keepalive-timeout`, `keepalive-requests`, `access-log-off`, `variables-hash-bucket-size`, `variables-hash-max-size`. Added in [565](https://github.com/nginxinc/kubernetes-ingress/pull/565), [511](https://github.com/nginxinc/kubernetes-ingress/pull/511). -* [504](https://github.com/nginxinc/kubernetes-ingress/pull/504): Run the Prometheus exporter inside the Ingress Controller process instead of a sidecar container. + +- Add new ConfigMap keys: `keepalive-timeout`, `keepalive-requests`, `access-log-off`, `variables-hash-bucket-size`, + `variables-hash-max-size`. Added in [565](https://github.com/nginxinc/kubernetes-ingress/pull/565), + [511](https://github.com/nginxinc/kubernetes-ingress/pull/511). +- [504](https://github.com/nginxinc/kubernetes-ingress/pull/504): Run the Prometheus exporter inside the Ingress + Controller process instead of a sidecar container. BUGFIXES: -* [520](https://github.com/nginxinc/kubernetes-ingress/pull/520): Fix the type of the Prometheus port annotation in manifests. -* [481](https://github.com/nginxinc/kubernetes-ingress/pull/481): Fix the HSTS support. -* [439](https://github.com/nginxinc/kubernetes-ingress/pull/439): Fix the validation of the `lb-method` ConfigMap key and `nginx.org/lb-method` annotation. + +- [520](https://github.com/nginxinc/kubernetes-ingress/pull/520): Fix the type of the Prometheus port annotation in + manifests. +- [481](https://github.com/nginxinc/kubernetes-ingress/pull/481): Fix the HSTS support. +- [439](https://github.com/nginxinc/kubernetes-ingress/pull/439): Fix the validation of the `lb-method` ConfigMap key + and `nginx.org/lb-method` annotation. HELM CHART: -* The version of the helm chart is now 0.3.0. -* The helm chart is now available in our helm chart repo `helm.nginx.com/stable`. -* Add new parameters to the Chart: `controller.service.httpPort.targetPort`, `controller.service.httpsPort.targetPort`, `controller.service.name`, `controller.pod.annotations`, `controller.config.name`, `controller.reportIngressStatus.leaderElectionLockName`, `controller.service.httpPort`, `controller.service.httpsPort`, `controller.service.loadBalancerIP`, `controller.service.loadBalancerSourceRanges`, `controller.tolerations`, `controller.affinity`. Added in [562](https://github.com/nginxinc/kubernetes-ingress/pull/562), [561](https://github.com/nginxinc/kubernetes-ingress/pull/561), [553](https://github.com/nginxinc/kubernetes-ingress/pull/553), [534](https://github.com/nginxinc/kubernetes-ingress/pull/534) thanks to [Paulo Ribeiro](https://github.com/paigr), [479](https://github.com/nginxinc/kubernetes-ingress/pull/479) thanks to [Alejandro Llanes](https://github.com/sombralibre), [468](https://github.com/nginxinc/kubernetes-ingress/pull/468), [456](https://github.com/nginxinc/kubernetes-ingress/pull/456). -* [546](https://github.com/nginxinc/kubernetes-ingress/pull/546): Support deploying multiple Ingress Controllers in a cluster. **Note**: The generated resources have new names that are unique for each Ingress Controller. As a consequence, the name change affects the upgrade. See the HELM UPGRADE section for more information. -* [542](https://github.com/nginxinc/kubernetes-ingress/pull/542): Reduce the required privileges in the RBAC manifests. + +- The version of the helm chart is now 0.3.0. +- The helm chart is now available in our helm chart repo `helm.nginx.com/stable`. +- Add new parameters to the Chart: `controller.service.httpPort.targetPort`, `controller.service.httpsPort.targetPort`, + `controller.service.name`, `controller.pod.annotations`, `controller.config.name`, + `controller.reportIngressStatus.leaderElectionLockName`, `controller.service.httpPort`, + `controller.service.httpsPort`, `controller.service.loadBalancerIP`, `controller.service.loadBalancerSourceRanges`, + `controller.tolerations`, `controller.affinity`. Added in + [562](https://github.com/nginxinc/kubernetes-ingress/pull/562), + [561](https://github.com/nginxinc/kubernetes-ingress/pull/561), + [553](https://github.com/nginxinc/kubernetes-ingress/pull/553), + [534](https://github.com/nginxinc/kubernetes-ingress/pull/534) thanks to [Paulo Ribeiro](https://github.com/paigr), + [479](https://github.com/nginxinc/kubernetes-ingress/pull/479) thanks to [Alejandro + Llanes](https://github.com/sombralibre), [468](https://github.com/nginxinc/kubernetes-ingress/pull/468), + [456](https://github.com/nginxinc/kubernetes-ingress/pull/456). +- [546](https://github.com/nginxinc/kubernetes-ingress/pull/546): Support deploying multiple Ingress Controllers in a + cluster. **Note**: The generated resources have new names that are unique for each Ingress Controller. As a + consequence, the name change affects the upgrade. See the HELM UPGRADE section for more information. +- [542](https://github.com/nginxinc/kubernetes-ingress/pull/542): Reduce the required privileges in the RBAC manifests. CHANGES: -* Update NGINX version to 1.15.12. -* Prometheus metrics for NGINX/NGINX Plus have new namespace `nginx_ingress`. Examples: `nginx_http_requests_total` -> `nginx_ingress_http_requests_total`, `nginxplus_http_requests_total` -> `nginx_ingress_nginxplus_http_requests_total`. + +- Update NGINX version to 1.15.12. +- Prometheus metrics for NGINX/NGINX Plus have new namespace `nginx_ingress`. Examples: `nginx_http_requests_total` -> + `nginx_ingress_http_requests_total`, `nginxplus_http_requests_total` -> `nginx_ingress_nginxplus_http_requests_total`. UPGRADE: -* For NGINX, use the 1.5.0 image from our DockerHub: `nginx/nginx-ingress:1.5.0` or `nginx/nginx-ingress:1.5.0-alpine` -* For NGINX Plus, please build your own image using the 1.5.0 source code. -* For Helm, use version 0.3.0 of the chart. + +- For NGINX, use the 1.5.0 image from our DockerHub: `nginx/nginx-ingress:1.5.0` or `nginx/nginx-ingress:1.5.0-alpine` +- For NGINX Plus, please build your own image using the 1.5.0 source code. +- For Helm, use version 0.3.0 of the chart. HELM UPGRADE: -The new version of the helm chart uses different names for the generated resources. This makes it possible to deploy multiple Ingress Controllers in a cluster. However, as a side effect, during the upgrade from the previous version, helm will recreate the resources, instead of updating the existing ones. This, in turn, might cause problems for the following resources: -* Service: If the service was created with the type LoadBalancer, the public IP of the new service might change. Additionally, helm updates the selector of the service, so that the old pods will be immediately excluded from the service. -* Deployment/DaemonSet: Because the resource is recreated, the old pods will be removed and the new ones will be launched, instead of the default Deployment/Daemonset upgrade strategy. -* ConfigMap: After the helm removes the resource, the old Ingress Controller pods will be immediately reconfigured to use the default values of the ConfigMap keys. During a small window between the reconfiguration and the shutdown of the old pods, NGINX will use the configuration with the default values. +The new version of the helm chart uses different names for the generated resources. This makes it possible to deploy +multiple Ingress Controllers in a cluster. However, as a side effect, during the upgrade from the previous version, helm +will recreate the resources, instead of updating the existing ones. This, in turn, might cause problems for the +following resources: + +- Service: If the service was created with the type LoadBalancer, the public IP of the new service might change. + Additionally, helm updates the selector of the service, so that the old pods will be immediately excluded from the + service. +- Deployment/DaemonSet: Because the resource is recreated, the old pods will be removed and the new ones will be + launched, instead of the default Deployment/Daemonset upgrade strategy. +- ConfigMap: After the helm removes the resource, the old Ingress Controller pods will be immediately reconfigured to + use the default values of the ConfigMap keys. During a small window between the reconfiguration and the shutdown of + the old pods, NGINX will use the configuration with the default values. -We advise not to upgrade to the new version of the helm chart unless the mentioned problems are acceptable for your case. We will provide special upgrade instructions for helm that mitigate the problems for the next minor release of the Ingress Controller (1.5.1). +We advise not to upgrade to the new version of the helm chart unless the mentioned problems are acceptable for your +case. We will provide special upgrade instructions for helm that mitigate the problems for the next minor release of the +Ingress Controller (1.5.1). -### 1.4.6 +## 1.4.6 CHANGES: -* Update NGINX version to 1.15.11. -* Update NGINX Plus version to R18. + +- Update NGINX version to 1.15.11. +- Update NGINX Plus version to R18. HELM CHART: -* The version of the Helm chart is now 0.2.1. + +- The version of the Helm chart is now 0.2.1. UPGRADE: -* For NGINX, use the 1.4.6 image from our DockerHub: `nginx/nginx-ingress:1.4.6` or `nginx/nginx-ingress:1.4.6-alpine` -* For NGINX Plus, please build your own image using the 1.4.6 source code. -* For Helm, use version 0.2.1 of the chart. -### 1.4.5 +- For NGINX, use the 1.4.6 image from our DockerHub: `nginx/nginx-ingress:1.4.6` or `nginx/nginx-ingress:1.4.6-alpine` +- For NGINX Plus, please build your own image using the 1.4.6 source code. +- For Helm, use version 0.2.1 of the chart. + +## 1.4.5 CHANGES: -* Update NGINX version to 1.15.10. + +- Update NGINX version to 1.15.10. UPGRADE: -* For NGINX, use the 1.4.5 image from our DockerHub: `nginx/nginx-ingress:1.4.5` or `nginx/nginx-ingress:1.4.5-alpine` -* For NGINX Plus, please build your own image using the 1.4.5 source code. -### 1.4.4 +- For NGINX, use the 1.4.5 image from our DockerHub: `nginx/nginx-ingress:1.4.5` or `nginx/nginx-ingress:1.4.5-alpine` +- For NGINX Plus, please build your own image using the 1.4.5 source code. + +## 1.4.4 CHANGES: -* Update NGINX version to 1.15.9. + +- Update NGINX version to 1.15.9. UPGRADE: -* For NGINX, use the 1.4.4 image from our DockerHub: `nginx/nginx-ingress:1.4.4` or `nginx/nginx-ingress:1.4.4-alpine` -* For NGINX Plus, please build your own image using the 1.4.4 source code. -### 1.4.3 +- For NGINX, use the 1.4.4 image from our DockerHub: `nginx/nginx-ingress:1.4.4` or `nginx/nginx-ingress:1.4.4-alpine` +- For NGINX Plus, please build your own image using the 1.4.4 source code. + +## 1.4.3 CHANGES: -* Update NGINX version to 1.15.8. + +- Update NGINX version to 1.15.8. UPGRADE: -* For NGINX, use the 1.4.3 image from our DockerHub: `nginx/nginx-ingress:1.4.3` or `nginx/nginx-ingress:1.4.3-alpine` -* For NGINX Plus, please build your own image using the 1.4.3 source code. -### 1.4.2 +- For NGINX, use the 1.4.3 image from our DockerHub: `nginx/nginx-ingress:1.4.3` or `nginx/nginx-ingress:1.4.3-alpine` +- For NGINX Plus, please build your own image using the 1.4.3 source code. + +## 1.4.2 CHANGES: -* Update NGINX Plus version to R17. + +- Update NGINX Plus version to R17. UPGRADE: -* For NGINX, use the 1.4.2 image from our DockerHub: `nginx/nginx-ingress:1.4.2` or `nginx/nginx-ingress:1.4.2-alpine` -* For NGINX Plus, please build your own image using the 1.4.2 source code. -### 1.4.1 +- For NGINX, use the 1.4.2 image from our DockerHub: `nginx/nginx-ingress:1.4.2` or `nginx/nginx-ingress:1.4.2-alpine` +- For NGINX Plus, please build your own image using the 1.4.2 source code. + +## 1.4.1 CHANGES: -* Update NGINX version to 1.15.7. + +- Update NGINX version to 1.15.7. UPGRADE: -* For NGINX, use the 1.4.1 image from our DockerHub: `nginx/nginx-ingress:1.4.1` or `nginx/nginx-ingress:1.4.1-alpine` -* For NGINX Plus, please build your own image using the 1.4.1 source code. -### 1.4.0 +- For NGINX, use the 1.4.1 image from our DockerHub: `nginx/nginx-ingress:1.4.1` or `nginx/nginx-ingress:1.4.1-alpine` +- For NGINX Plus, please build your own image using the 1.4.1 source code. + +## 1.4.0 FEATURES: -* [401](https://github.com/nginxinc/kubernetes-ingress/pull/401): Add the `-nginx-debug` flag for enabling debugging of NGINX using the `nginx-debug` binary. -* [387](https://github.com/nginxinc/kubernetes-ingress/pull/387): Add the `-nginx-status-allow-cidrs` command-line argument for white listing IPv4 IP/CIDR blocks to allow access to NGINX stub_status or the NGINX Plus API. Thanks to [Jasmine Hegman](https://github.com/r4j4h). -* [376](https://github.com/nginxinc/kubernetes-ingress/pull/376): Support the [random](http://nginx.org/en/docs/http/ngx_http_upstream_module.html#random) load balancing method. -* [375](https://github.com/nginxinc/kubernetes-ingress/pull/375): Support custom annotations. -* [346](https://github.com/nginxinc/kubernetes-ingress/pull/346): Support the Prometheus exporter for NGINX (the stub_status metrics). -* [344](https://github.com/nginxinc/kubernetes-ingress/pull/344): Expose NGINX Plus API/NGINX stub_status on a custom port via the `-nginx-status-port` command-line argument. See also the CHANGES section. -* [342](https://github.com/nginxinc/kubernetes-ingress/pull/342): Add the `error-log-level` configmap key. Thanks to [boran seref](https://github.com/boranx). -* [320](https://github.com/nginxinc/kubernetes-ingress/pull/340): Support TCP/UDP load balancing via the `stream-snippets` configmap key. + +- [401](https://github.com/nginxinc/kubernetes-ingress/pull/401): Add the `-nginx-debug` flag for enabling debugging of + NGINX using the `nginx-debug` binary. +- [387](https://github.com/nginxinc/kubernetes-ingress/pull/387): Add the `-nginx-status-allow-cidrs` command-line + argument for white listing IPv4 IP/CIDR blocks to allow access to NGINX stub_status or the NGINX Plus API. Thanks to + [Jasmine Hegman](https://github.com/r4j4h). +- [376](https://github.com/nginxinc/kubernetes-ingress/pull/376): Support the + [random](http://nginx.org/en/docs/http/ngx_http_upstream_module.html#random) load balancing method. +- [375](https://github.com/nginxinc/kubernetes-ingress/pull/375): Support custom annotations. +- [346](https://github.com/nginxinc/kubernetes-ingress/pull/346): Support the Prometheus exporter for NGINX (the + stub_status metrics). +- [344](https://github.com/nginxinc/kubernetes-ingress/pull/344): Expose NGINX Plus API/NGINX stub_status on a custom + port via the `-nginx-status-port` command-line argument. See also the CHANGES section. +- [342](https://github.com/nginxinc/kubernetes-ingress/pull/342): Add the `error-log-level` configmap key. Thanks to + [boran seref](https://github.com/boranx). +- [320](https://github.com/nginxinc/kubernetes-ingress/pull/340): Support TCP/UDP load balancing via the + `stream-snippets` configmap key. IMPROVEMENTS: -* [434](https://github.com/nginxinc/kubernetes-ingress/pull/434): Improve consistency of templates. -* [432](https://github.com/nginxinc/kubernetes-ingress/pull/432): Fix cli-docs and Improve main test. -* [419](https://github.com/nginxinc/kubernetes-ingress/pull/419): Refactor config writing. Thanks to [feifeiiiiiiiiii](https://github.com/feifeiiiiiiiiiii). -* [403](https://github.com/nginxinc/kubernetes-ingress/pull/403): Improve NGINX start. -* [400](https://github.com/nginxinc/kubernetes-ingress/pull/400): Fix error message in internal/controller/controller.go. Thanks to [Alex O Regan](https://github.com/aaaaaaaalex). -* [399](https://github.com/nginxinc/kubernetes-ingress/pull/399): Improve secret handling. See also the CHANGES section. -* [391](https://github.com/nginxinc/kubernetes-ingress/pull/391): Update default lb-method to be random two least_conn. See also the CHANGES section. -* [389](https://github.com/nginxinc/kubernetes-ingress/pull/389): Improve parsing nginx.org/rewrites annotation. -* [380](https://github.com/nginxinc/kubernetes-ingress/pull/380): Verify reloads & cache secrets. -* [362](https://github.com/nginxinc/kubernetes-ingress/pull/362): Reduce reloads. -* [357](https://github.com/nginxinc/kubernetes-ingress/pull/357): Improve Project Layout and Refactor Controller Package. See also the CHANGES section. -* [351](https://github.com/nginxinc/kubernetes-ingress/pull/351): Make socket address obvious. + +- [434](https://github.com/nginxinc/kubernetes-ingress/pull/434): Improve consistency of templates. +- [432](https://github.com/nginxinc/kubernetes-ingress/pull/432): Fix cli-docs and Improve main test. +- [419](https://github.com/nginxinc/kubernetes-ingress/pull/419): Refactor config writing. Thanks to + [feifeiiiiiiiiii](https://github.com/feifeiiiiiiiiiii). +- [403](https://github.com/nginxinc/kubernetes-ingress/pull/403): Improve NGINX start. +- [400](https://github.com/nginxinc/kubernetes-ingress/pull/400): Fix error message in + internal/controller/controller.go. Thanks to [Alex O Regan](https://github.com/aaaaaaaalex). +- [399](https://github.com/nginxinc/kubernetes-ingress/pull/399): Improve secret handling. See also the CHANGES section. +- [391](https://github.com/nginxinc/kubernetes-ingress/pull/391): Update default lb-method to be random two least_conn. + See also the CHANGES section. +- [389](https://github.com/nginxinc/kubernetes-ingress/pull/389): Improve parsing nginx.org/rewrites annotation. +- [380](https://github.com/nginxinc/kubernetes-ingress/pull/380): Verify reloads & cache secrets. +- [362](https://github.com/nginxinc/kubernetes-ingress/pull/362): Reduce reloads. +- [357](https://github.com/nginxinc/kubernetes-ingress/pull/357): Improve Project Layout and Refactor Controller + Package. See also the CHANGES section. +- [351](https://github.com/nginxinc/kubernetes-ingress/pull/351): Make socket address obvious. BUGFIXES: -* [429](https://github.com/nginxinc/kubernetes-ingress/pull/429): Fix panic with health checks. -* [386](https://github.com/nginxinc/kubernetes-ingress/pull/386): Fix Configmap/Mergeable Ingress Add/Update event logging. -* [379](https://github.com/nginxinc/kubernetes-ingress/pull/379): Fix configmap update. -* [365](https://github.com/nginxinc/kubernetes-ingress/pull/365): Don't enqueue ingress for some service changes. -* [348](https://github.com/nginxinc/kubernetes-ingress/pull/348): Fix Configurator error check. + +- [429](https://github.com/nginxinc/kubernetes-ingress/pull/429): Fix panic with health checks. +- [386](https://github.com/nginxinc/kubernetes-ingress/pull/386): Fix Configmap/Mergeable Ingress Add/Update event + logging. +- [379](https://github.com/nginxinc/kubernetes-ingress/pull/379): Fix configmap update. +- [365](https://github.com/nginxinc/kubernetes-ingress/pull/365): Don't enqueue ingress for some service changes. +- [348](https://github.com/nginxinc/kubernetes-ingress/pull/348): Fix Configurator error check. HELM CHART: -* [430](https://github.com/nginxinc/kubernetes-ingress/pull/430): Add the `controller.serviceAccount.imagePullSecrets` parameter to the helm chart. See also the CHANGES section. -* [420](https://github.com/nginxinc/kubernetes-ingress/pull/420): Simplify values files for Helm Chart. -* [398](https://github.com/nginxinc/kubernetes-ingress/pull/398): Add the `controller.nginxStatus.allowCidrs` and `controller.service.externalIPs` parameters to helm chart. -* [393](https://github.com/nginxinc/kubernetes-ingress/pull/393): Refactor Helm Chart templates. -* [390](https://github.com/nginxinc/kubernetes-ingress/pull/390): Add the `controller.service.loadBalancerIP` parameter to the helm chat. -* [377](https://github.com/nginxinc/kubernetes-ingress/pull/377): Add the `controller.nginxStatus` parameters to the helm chart. -* [335](https://github.com/nginxinc/kubernetes-ingress/pull/335): Add the `controller.reportIngressStatus` parameters to the helm chart. -* The version of the Helm chart is now 0.2.0. + +- [430](https://github.com/nginxinc/kubernetes-ingress/pull/430): Add the `controller.serviceAccount.imagePullSecrets` + parameter to the helm chart. See also the CHANGES section. +- [420](https://github.com/nginxinc/kubernetes-ingress/pull/420): Simplify values files for Helm Chart. +- [398](https://github.com/nginxinc/kubernetes-ingress/pull/398): Add the `controller.nginxStatus.allowCidrs` and + `controller.service.externalIPs` parameters to helm chart. +- [393](https://github.com/nginxinc/kubernetes-ingress/pull/393): Refactor Helm Chart templates. +- [390](https://github.com/nginxinc/kubernetes-ingress/pull/390): Add the `controller.service.loadBalancerIP` parameter + to the helm chat. +- [377](https://github.com/nginxinc/kubernetes-ingress/pull/377): Add the `controller.nginxStatus` parameters to the + helm chart. +- [335](https://github.com/nginxinc/kubernetes-ingress/pull/335): Add the `controller.reportIngressStatus` parameters to + the helm chart. +- The version of the Helm chart is now 0.2.0. CHANGES: -* Update NGINX version to 1.15.6. -* Update NGINX Plus version to R16p1. -* Update NGINX Prometheus Exporter to 0.2.0. -* [430](https://github.com/nginxinc/kubernetes-ingress/pull/430): Add the `controller.serviceAccount.imagePullSecrets` parameter to the helm chart. **Note**: the `controller.serviceAccountName` parameter has been changed to `controller.serviceAccount.name`. -* [399](https://github.com/nginxinc/kubernetes-ingress/pull/399): Improve secret handling. **Note**: the PR changed how the Ingress Controller processes Ingress resources with TLS termination enabled but without any referenced (or with invalid) secrets and Ingress resources with JWT validation enabled but without any referenced (or with invalid) JWK. Please read [here](https://github.com/nginxinc/kubernetes-ingress/pull/399) for more details. -* [357](https://github.com/nginxinc/kubernetes-ingress/pull/357): Improve Project Layout and Refactor Controller Package. **Note**: the PR significantly changed the layout of the project to follow best practices. -* [347](https://github.com/nginxinc/kubernetes-ingress/pull/347): Use edge version in manifests and Helm chart. **Note**: the manifests and the helm chart in the master branch now reference the edge version of the Ingress Controller instead of the latest stable version used previously. -* [391](https://github.com/nginxinc/kubernetes-ingress/pull/391): Update default lb-method to be random two least_conn. **Note**: the default load balancing method is now the power of two choices as it better suits the Ingress Controller use case. Please read the [blog post](https://www.nginx.com/blog/nginx-power-of-two-choices-load-balancing-algorithm/) about the method for more details. -* [344](https://github.com/nginxinc/kubernetes-ingress/pull/344): Expose NGINX Plus API/NGINX stub_status on a custom port via the `-nginx-status-port` command-line argument. **Note**: For NGINX the stub_status is now exposed on port 8080 at the /stub_status URL by default. Previously, the stub_status was not exposed on any port. The stub_status can be disabled via the `-nginx-status` flag. - -DOC AND EXAMPLES FIXES/IMPROVEMENTS: [435](https://github.com/nginxinc/kubernetes-ingress/pull/435), [433](https://github.com/nginxinc/kubernetes-ingress/pull/433), [432](https://github.com/nginxinc/kubernetes-ingress/pull/432), [418](https://github.com/nginxinc/kubernetes-ingress/pull/418) (Thanks to [Hal Deadman](https://github.com/hdeadman)), [406](https://github.com/nginxinc/kubernetes-ingress/pull/406), [381](https://github.com/nginxinc/kubernetes-ingress/pull/381), [349](https://github.com/nginxinc/kubernetes-ingress/pull/349) (Thanks to [Artur Geraschenko](https://github.com/arturgspb)), [343](https://github.com/nginxinc/kubernetes-ingress/pull/343) + +- Update NGINX version to 1.15.6. +- Update NGINX Plus version to R16p1. +- Update NGINX Prometheus Exporter to 0.2.0. +- [430](https://github.com/nginxinc/kubernetes-ingress/pull/430): Add the `controller.serviceAccount.imagePullSecrets` + parameter to the helm chart. **Note**: the `controller.serviceAccountName` parameter has been changed to + `controller.serviceAccount.name`. +- [399](https://github.com/nginxinc/kubernetes-ingress/pull/399): Improve secret handling. **Note**: the PR changed how + the Ingress Controller processes Ingress resources with TLS termination enabled but without any referenced (or with + invalid) secrets and Ingress resources with JWT validation enabled but without any referenced (or with invalid) JWK. + Please read [here](https://github.com/nginxinc/kubernetes-ingress/pull/399) for more details. +- [357](https://github.com/nginxinc/kubernetes-ingress/pull/357): Improve Project Layout and Refactor Controller + Package. **Note**: the PR significantly changed the layout of the project to follow best practices. +- [347](https://github.com/nginxinc/kubernetes-ingress/pull/347): Use edge version in manifests and Helm chart. + **Note**: the manifests and the helm chart in the master branch now reference the edge version of the Ingress + Controller instead of the latest stable version used previously. +- [391](https://github.com/nginxinc/kubernetes-ingress/pull/391): Update default lb-method to be random two least_conn. + **Note**: the default load balancing method is now the power of two choices as it better suits the Ingress Controller + use case. Please read the [blog post](https://www.nginx.com/blog/nginx-power-of-two-choices-load-balancing-algorithm/) + about the method for more details. +- [344](https://github.com/nginxinc/kubernetes-ingress/pull/344): Expose NGINX Plus API/NGINX stub_status on a custom + port via the `-nginx-status-port` command-line argument. **Note**: For NGINX the stub_status is now exposed on port + 8080 at the /stub_status URL by default. Previously, the stub_status was not exposed on any port. The stub_status can + be disabled via the `-nginx-status` flag. + +DOC AND EXAMPLES FIXES/IMPROVEMENTS: [435](https://github.com/nginxinc/kubernetes-ingress/pull/435), +[433](https://github.com/nginxinc/kubernetes-ingress/pull/433), +[432](https://github.com/nginxinc/kubernetes-ingress/pull/432), +[418](https://github.com/nginxinc/kubernetes-ingress/pull/418) (Thanks to [Hal Deadman](https://github.com/hdeadman)), +[406](https://github.com/nginxinc/kubernetes-ingress/pull/406), +[381](https://github.com/nginxinc/kubernetes-ingress/pull/381), +[349](https://github.com/nginxinc/kubernetes-ingress/pull/349) (Thanks to [Artur +Geraschenko](https://github.com/arturgspb)), [343](https://github.com/nginxinc/kubernetes-ingress/pull/343) UPGRADE: -* For NGINX, use the 1.4.0 image from our DockerHub: `nginx/nginx-ingress:1.4.0` or `nginx/nginx-ingress:1.4.0-alpine` -* For NGINX Plus, please build your own image using the 1.4.0 source code. -### 1.3.2 +- For NGINX, use the 1.4.0 image from our DockerHub: `nginx/nginx-ingress:1.4.0` or `nginx/nginx-ingress:1.4.0-alpine` +- For NGINX Plus, please build your own image using the 1.4.0 source code. + +## 1.3.2 CHANGES: -* Update NGINX version to 1.15.6. + +- Update NGINX version to 1.15.6. UPGRADE: -* For NGINX, use the 1.3.2 image from our DockerHub: `nginx/nginx-ingress:1.3.2` or `nginx/nginx-ingress:1.3.2-alpine` -* For NGINX Plus, please build your own image using the 1.3.2 source code. -### 1.3.1 +- For NGINX, use the 1.3.2 image from our DockerHub: `nginx/nginx-ingress:1.3.2` or `nginx/nginx-ingress:1.3.2-alpine` +- For NGINX Plus, please build your own image using the 1.3.2 source code. + +## 1.3.1 CHANGES: -* Update NGINX Plus version to R15p2. + +- Update NGINX Plus version to R15p2. UPGRADE: -* For NGINX, use the 1.3.1 image from our DockerHub: `nginx/nginx-ingress:1.3.1` or `nginx/nginx-ingress:1.3.1-alpine` -* For NGINX Plus, please build your own image using the 1.3.1 source code. -### 1.3.0 +- For NGINX, use the 1.3.1 image from our DockerHub: `nginx/nginx-ingress:1.3.1` or `nginx/nginx-ingress:1.3.1-alpine` +- For NGINX Plus, please build your own image using the 1.3.1 source code. + +## 1.3.0 IMPROVEMENTS: -* [325](https://github.com/nginxinc/kubernetes-ingress/pull/325): Report ingress status. -* [311](https://github.com/nginxinc/kubernetes-ingress/pull/311): Support JWT auth in mergeable minions. -* [310](https://github.com/nginxinc/kubernetes-ingress/pull/310): NGINX configuration template custom path support. -* [308](https://github.com/nginxinc/kubernetes-ingress/pull/308): Add prometheus exporter support to helm chart. -* [303](https://github.com/nginxinc/kubernetes-ingress/pull/303): Add fetch custom NGINX template from ConfigMap. -* [301](https://github.com/nginxinc/kubernetes-ingress/pull/301): Update prometheus exporter image for Plus. -* [298](https://github.com/nginxinc/kubernetes-ingress/pull/298): Prefetch ConfigMap before initial NGINX Config generation. -* [296](https://github.com/nginxinc/kubernetes-ingress/pull/296): Improve Helm Chart. -* [295](https://github.com/nginxinc/kubernetes-ingress/pull/295): Report version information. -* [294](https://github.com/nginxinc/kubernetes-ingress/pull/294): Support dynamic reconfiguration in mergeable ingresses for Plus. -* [287](https://github.com/nginxinc/kubernetes-ingress/pull/287): Support slow-start for Plus. -* [286](https://github.com/nginxinc/kubernetes-ingress/pull/286): Add support for active health checks for Plus. + +- [325](https://github.com/nginxinc/kubernetes-ingress/pull/325): Report ingress status. +- [311](https://github.com/nginxinc/kubernetes-ingress/pull/311): Support JWT auth in mergeable minions. +- [310](https://github.com/nginxinc/kubernetes-ingress/pull/310): NGINX configuration template custom path support. +- [308](https://github.com/nginxinc/kubernetes-ingress/pull/308): Add prometheus exporter support to helm chart. +- [303](https://github.com/nginxinc/kubernetes-ingress/pull/303): Add fetch custom NGINX template from ConfigMap. +- [301](https://github.com/nginxinc/kubernetes-ingress/pull/301): Update prometheus exporter image for Plus. +- [298](https://github.com/nginxinc/kubernetes-ingress/pull/298): Prefetch ConfigMap before initial NGINX Config + generation. +- [296](https://github.com/nginxinc/kubernetes-ingress/pull/296): Improve Helm Chart. +- [295](https://github.com/nginxinc/kubernetes-ingress/pull/295): Report version information. +- [294](https://github.com/nginxinc/kubernetes-ingress/pull/294): Support dynamic reconfiguration in mergeable ingresses + for Plus. +- [287](https://github.com/nginxinc/kubernetes-ingress/pull/287): Support slow-start for Plus. +- [286](https://github.com/nginxinc/kubernetes-ingress/pull/286): Add support for active health checks for Plus. CHANGES: -* [330](https://github.com/nginxinc/kubernetes-ingress/pull/330): Update NGINX version to 1.15.2. -* [329](https://github.com/nginxinc/kubernetes-ingress/pull/329): Enforce annotations inheritance in minions. + +- [330](https://github.com/nginxinc/kubernetes-ingress/pull/330): Update NGINX version to 1.15.2. +- [329](https://github.com/nginxinc/kubernetes-ingress/pull/329): Enforce annotations inheritance in minions. BUGFIXES: -* [326](https://github.com/nginxinc/kubernetes-ingress/pull/326): Fix find ingress for secret ns bug. -* [284](https://github.com/nginxinc/kubernetes-ingress/pull/284): Correct Logs for Mergeable Types with Duplicate Location. Thanks to [Fernando Diaz](https://github.com/diazjf). +- [326](https://github.com/nginxinc/kubernetes-ingress/pull/326): Fix find ingress for secret ns bug. +- [284](https://github.com/nginxinc/kubernetes-ingress/pull/284): Correct Logs for Mergeable Types with Duplicate + Location. Thanks to [Fernando Diaz](https://github.com/diazjf). UPGRADE: -* For NGINX, use the 1.3.0 image from our DockerHub: `nginx/nginx-ingress:1.3.0` -* For NGINX Plus, please build your own image using the 1.3.0 source code. - -### 1.2.0 - -* [279](https://github.com/nginxinc/kubernetes-ingress/pull/279): Update dependencies. -* [278](https://github.com/nginxinc/kubernetes-ingress/pull/278): Fix mergeable Ingress types. -* [277](https://github.com/nginxinc/kubernetes-ingress/pull/277): Support grpc error responses. -* [276](https://github.com/nginxinc/kubernetes-ingress/pull/276): Add gRPC support. -* [274](https://github.com/nginxinc/kubernetes-ingress/pull/274): Change the default load balancing method to least_conn. -* [272](https://github.com/nginxinc/kubernetes-ingress/pull/272): Move nginx-ingress image to the official nginx DockerHub. -* [268](https://github.com/nginxinc/kubernetes-ingress/pull/268): Correct Mergeable Types misspelling and optimize blacklists. Thanks to [Fernando Diaz](https://github.com/diazjf). -* [266](https://github.com/nginxinc/kubernetes-ingress/pull/266): Add support for passive health checks. -* [261](https://github.com/nginxinc/kubernetes-ingress/pull/261): Update Customization Example. -* [258](https://github.com/nginxinc/kubernetes-ingress/pull/258): Handle annotations and conflicting paths for MergeableTypes. Thanks to [Fernando Diaz](https://github.com/diazjf). -* [256](https://github.com/nginxinc/kubernetes-ingress/pull/256): Add helm chart support. -* [249](https://github.com/nginxinc/kubernetes-ingress/pull/249): Add support for prometheus for Plus. -* [241](https://github.com/nginxinc/kubernetes-ingress/pull/241): Update the doc about building the Docker image. -* [240](https://github.com/nginxinc/kubernetes-ingress/pull/240): Use new NGINX Plus API. -* [239](https://github.com/nginxinc/kubernetes-ingress/pull/239): Fix a typo in a variable name. Thanks to [Tony Li](https://github.com/mysterytony). -* [238](https://github.com/nginxinc/kubernetes-ingress/pull/238): Remove apt-get upgrade from Plus Dockerfile. -* [237](https://github.com/nginxinc/kubernetes-ingress/pull/237): Add unit test for ingress-class handling. -* [236](https://github.com/nginxinc/kubernetes-ingress/pull/236): Always respect `-ingress-class` option. Thanks to [Nick Novitski](https://github.com/nicknovitski). -* [235](https://github.com/nginxinc/kubernetes-ingress/pull/235): Change the base image to Debian Stretch for Plus controller. -* [234](https://github.com/nginxinc/kubernetes-ingress/pull/234): Update installation manifests and instructions. -* [233](https://github.com/nginxinc/kubernetes-ingress/pull/233): Add docker build options to Makefile. -* [231](https://github.com/nginxinc/kubernetes-ingress/pull/231): Prevent a possible failure of building Plus image. -* Documentation Fixes: [248](https://github.com/nginxinc/kubernetes-ingress/pull/248), thanks to [zariye](https://github.com/zariye). [252](https://github.com/nginxinc/kubernetes-ingress/pull/252). [270](https://github.com/nginxinc/kubernetes-ingress/pull/270). -* Update NGINX version to 1.13.12. -* Update NGINX Plus version to R15 P1. - - -### 1.1.1 - -* [228](https://github.com/nginxinc/kubernetes-ingress/pull/228): Add worker-rlimit-nofile configmap key. Thanks to [Aleksandr Lysenko](https://github.com/Sarga). -* [223](https://github.com/nginxinc/kubernetes-ingress/pull/223): Add worker-connections configmap key. Thanks to [Aleksandr Lysenko](https://github.com/Sarga). -* Update NGINX version to 1.13.8. - -### 1.1.0 - -* [221](https://github.com/nginxinc/kubernetes-ingress/pull/221): Add git commit info to the IC log. -* [220](https://github.com/nginxinc/kubernetes-ingress/pull/220): Update dependencies. -* [213](https://github.com/nginxinc/kubernetes-ingress/pull/213): Add main snippets to allow Main context customization. Thanks to [Dewen Kong](https://github.com/kongdewen). -* [211](https://github.com/nginxinc/kubernetes-ingress/pull/211): Minimize the number of configuration reloads when the Ingress Controller starts; fix a problem with endpoints updates for Plus. -* [208](https://github.com/nginxinc/kubernetes-ingress/pull/208): Add worker-shutdown-timeout configmap key. Thanks to [Aleksandr Lysenko](https://github.com/Sarga). -* [199](https://github.com/nginxinc/kubernetes-ingress/pull/199): Add support for Kubernetes ssl-redirect annotation. Thanks to [Luke Seelenbinder](https://github.com/lseelenbinder). -* [194](https://github.com/nginxinc/kubernetes-ingress/pull/194) Add keepalive configmap key and annotation. -* [193](https://github.com/nginxinc/kubernetes-ingress/pull/193): Add worker-cpu-affinity configmap key. -* [192](https://github.com/nginxinc/kubernetes-ingress/pull/192): Add worker-processes configmap key. -* [186](https://github.com/nginxinc/kubernetes-ingress/pull/186): Fix hardcoded controller class. Thanks to [Serhii M](https://github.com/SiriusRed). -* [184](https://github.com/nginxinc/kubernetes-ingress/pull/184): Return a meaningful error when there is no cert and key for the default server. -* Update NGINX version to 1.13.7. -* Makefile updates: golang container was updated to 1.9. - -### 1.0.0 - -* [175](https://github.com/nginxinc/kubernetes-ingress/pull/175): Add support for JWT for NGINX Plus. -* [171](https://github.com/nginxinc/kubernetes-ingress/pull/171): Allow NGINX to listen on non-standard ports. Thanks to [Stanislav Seletskiy](https://github.com/seletskiy). -* [170](https://github.com/nginxinc/kubernetes-ingress/pull/170): Add the default server. **Note**: The Ingress Controller will fail to start if there are no cert and key for the default server. You can pass a TLS Secret for the default server as an argument to the Ingress Controller or add a cert and a key to the Docker image. -* [169](https://github.com/nginxinc/kubernetes-ingress/pull/169): Ignore Ingress resources with empty hostnames. -* [168](https://github.com/nginxinc/kubernetes-ingress/pull/168): Add the `nginx.org/lb-method` annotation. Thanks to [Sajal Kayan](https://github.com/sajal). -* [166](https://github.com/nginxinc/kubernetes-ingress/pull/166): Watch Secret resources for updates. **Note**: If a Secret referenced by one or more Ingress resources becomes invalid or gets removed, the configuration for those Ingress resources will be disabled until there is a valid Secret. -* [160](https://github.com/nginxinc/kubernetes-ingress/pull/160): Add support for events. See the details [here](https://github.com/nginxinc/kubernetes-ingress/pull/160). -* [157](https://github.com/nginxinc/kubernetes-ingress/pull/157): Add graceful termination - when the Ingress Controller receives `SIGTERM`, it shutdowns itself as well as NGINX, using `nginx -s quit`. - -### 0.9.0 - -* [156](https://github.com/nginxinc/kubernetes-ingress/pull/156): Write a pem file with an SSL certificate and key atomically. -* [155](https://github.com/nginxinc/kubernetes-ingress/pull/155): Remove http2 annotation (http/2 can be enabled globally in the ConfigMap). -* [154](https://github.com/nginxinc/kubernetes-ingress/pull/154): Merge NGINX and NGINX Plus Ingress Controller implementations. -* [151](https://github.com/nginxinc/kubernetes-ingress/pull/151): Use k8s.io/client-go. -* [146](https://github.com/nginxinc/kubernetes-ingress/pull/146): Fix health status. -* [141](https://github.com/nginxinc/kubernetes-ingress/pull/141): Set `worker_processes` to `auto` in NGINX configuration. Thanks to [Andreas Krüger](https://github.com/woopstar). -* [140](https://github.com/nginxinc/kubernetes-ingress/pull/140): Fix an error message. Thanks to [Andreas Krüger](https://github.com/woopstar). -* Update NGINX to version 1.13.3. - -### 0.8.1 - -* Update NGINX version to 1.13.0. - -### 0.8.0 - -* [117](https://github.com/nginxinc/kubernetes-ingress/pull/117): Add a customization option: location-snippets, server-snippets and http-snippets. Thanks to [rchicoli](https://github.com/rchicoli). -* [116](https://github.com/nginxinc/kubernetes-ingress/pull/116): Add support for the 301 redirect to https based on the `http_x_forwarded_proto` header. Thanks to [Chris](https://github.com/cwhenderson20). -* Update NGINX version to 1.11.13. -* Makefile updates: gcloud docker push command; golang container was updated to 1.8. -* Documentation fixes: [113](https://github.com/nginxinc/kubernetes-ingress/pull/113). Thanks to [Linus Lewandowski](https://github.com/LEW21). - -### 0.7.0 - -* [108](https://github.com/nginxinc/kubernetes-ingress/pull/108): Support for the `server_tokens` directive via the annotation and in the configmap. Thanks to [David Radcliffe](https://github.com/dwradcliffe). -* [103](https://github.com/nginxinc/kubernetes-ingress/pull/103): Improve error reporting when NGINX fails to start. -* [100](https://github.com/nginxinc/kubernetes-ingress/pull/100): Add the health check location. Thanks to [Julian](https://github.com/jmastr). -* [95](https://github.com/nginxinc/kubernetes-ingress/pull/95): Fix the runtime.TypeAssertionError issue, which sometimes occurred when deleting resources. Thanks to [Tang Le](https://github.com/tangle329). -* [93](https://github.com/nginxinc/kubernetes-ingress/pull/93): Fix overwriting of Secrets with the same name from different namespaces. -* [92](https://github.com/nginxinc/kubernetes-ingress/pull/92/files): Add overwriting of the HSTS header. Previously, when HSTS was enabled, if a backend issued the HSTS header, the controller would add the second HSTS header. Now the controller overwrites the HSTS header, if a backend also issues it. -* [91](https://github.com/nginxinc/kubernetes-ingress/pull/91): -Fix the issue with single service Ingress resources without any Ingress rules: the controller didn't pick up any updates of the endpoints of the service of such an Ingress resource. Thanks to [Tang Le](https://github.com/tangle329). -* [88](https://github.com/nginxinc/kubernetes-ingress/pull/88): Support for the `proxy_hide_header` and the `proxy_pass_header` directives via annotations and in the configmap. Thanks to [Nico Schieder](https://github.com/thetechnick). -* [85](https://github.com/nginxinc/kubernetes-ingress/pull/85): Add the configmap settings to support perfect forward secrecy. Thanks to [Nico Schieder](https://github.com/thetechnick). -* [84](https://github.com/nginxinc/kubernetes-ingress/pull/84): Secret retry: If a certificate Secret referenced in an Ingress object is not found, -the Ingress Controller will reject the Ingress object. but retries every 5s. Thanks to [Nico Schieder](https://github.com/thetechnick). -* [81](https://github.com/nginxinc/kubernetes-ingress/pull/81): Add configmap options to turn on the PROXY protocol. Thanks to [Nico Schieder](https://github.com/thetechnick). -* Update NGINX version to 1.11.8. -* Documentation fixes: [104](https://github.com/nginxinc/kubernetes-ingress/pull/104/files) and [97](https://github.com/nginxinc/kubernetes-ingress/pull/97/files). Thanks to [Ruilin Huang](https://github.com/hrl) and [Justin Garrison](https://github.com/rothgar). - -### 0.6.0 - -* [75](https://github.com/nginxinc/kubernetes-ingress/pull/75): Add the HSTS settings in the configmap and annotations. Thanks to [Nico Schieder](https://github.com/thetechnick). -* [74](https://github.com/nginxinc/kubernetes-ingress/pull/74): Fix the issue of the `kubernetes.io/ingress.class` annotation handling. Thanks to [Tang Le](https://github.com/tangle329). -* [70](https://github.com/nginxinc/kubernetes-ingress/pull/70): Add support for the alpine-based image for the NGINX controller. -* [68](https://github.com/nginxinc/kubernetes-ingress/pull/68): Support for proxy-buffering settings in the configmap and annotations. Thanks to [Mark Daniel Reidel](https://github.com/df-mreidel). -* [66](https://github.com/nginxinc/kubernetes-ingress/pull/66): Support for custom log-format in the configmap. Thanks to [Mark Daniel Reidel](https://github.com/df-mreidel). -* [65](https://github.com/nginxinc/kubernetes-ingress/pull/65): Add HTTP/2 as an option in the configmap and annotations. Thanks to [Nico Schieder](https://github.com/thetechnick). -* The NGINX Plus controller image is now based on Ubuntu Xenial. - -### 0.5.0 - -* Update NGINX version to 1.11.5. -* [64](https://github.com/nginxinc/kubernetes-ingress/pull/64): Add the `nginx.org/rewrites` annotation, which allows to rewrite the URI of a request before sending it to the application. Thanks to [Julian](https://github.com/jmastr). -* [62](https://github.com/nginxinc/kubernetes-ingress/pull/62): Add the `nginx.org/ssl-services` annotation, which allows load balancing of HTTPS applications. Thanks to [Julian](https://github.com/jmastr). - -### 0.4.0 - -* [54](https://github.com/nginxinc/kubernetes-ingress/pull/54): Previously, when specifying the port of a service in an Ingress rule, you had to use the value of the target port of that port of the service, which was incorrect. Now you must use the port value or the name of the port of the service instead of the target port value. **Note**: Please make necessary changes to your Ingress resources, if ports of your services have different values of the port and the target port fields. -* [55](https://github.com/nginxinc/kubernetes-ingress/pull/55): Add support for the `kubernetes.io/ingress.class` annotation in Ingress resources. -* [58](https://github.com/nginxinc/kubernetes-ingress/pull/58): Add the version information to the controller. For each version of the NGINX controller, you can find a corresponding image on [DockerHub](https://hub.docker.com/r/nginxdemos/nginx-ingress/tags/) with a tag equal to the version. The latest version is available through the `latest` tag. -The previous version was 0.3 +- For NGINX, use the 1.3.0 image from our DockerHub: `nginx/nginx-ingress:1.3.0` +- For NGINX Plus, please build your own image using the 1.3.0 source code. + +## 1.2.0 + +- [279](https://github.com/nginxinc/kubernetes-ingress/pull/279): Update dependencies. +- [278](https://github.com/nginxinc/kubernetes-ingress/pull/278): Fix mergeable Ingress types. +- [277](https://github.com/nginxinc/kubernetes-ingress/pull/277): Support grpc error responses. +- [276](https://github.com/nginxinc/kubernetes-ingress/pull/276): Add gRPC support. +- [274](https://github.com/nginxinc/kubernetes-ingress/pull/274): Change the default load balancing method to + least_conn. +- [272](https://github.com/nginxinc/kubernetes-ingress/pull/272): Move nginx-ingress image to the official nginx + DockerHub. +- [268](https://github.com/nginxinc/kubernetes-ingress/pull/268): Correct Mergeable Types misspelling and optimize + blacklists. Thanks to [Fernando Diaz](https://github.com/diazjf). +- [266](https://github.com/nginxinc/kubernetes-ingress/pull/266): Add support for passive health checks. +- [261](https://github.com/nginxinc/kubernetes-ingress/pull/261): Update Customization Example. +- [258](https://github.com/nginxinc/kubernetes-ingress/pull/258): Handle annotations and conflicting paths for + MergeableTypes. Thanks to [Fernando Diaz](https://github.com/diazjf). +- [256](https://github.com/nginxinc/kubernetes-ingress/pull/256): Add helm chart support. +- [249](https://github.com/nginxinc/kubernetes-ingress/pull/249): Add support for prometheus for Plus. +- [241](https://github.com/nginxinc/kubernetes-ingress/pull/241): Update the doc about building the Docker image. +- [240](https://github.com/nginxinc/kubernetes-ingress/pull/240): Use new NGINX Plus API. +- [239](https://github.com/nginxinc/kubernetes-ingress/pull/239): Fix a typo in a variable name. Thanks to [Tony + Li](https://github.com/mysterytony). +- [238](https://github.com/nginxinc/kubernetes-ingress/pull/238): Remove apt-get upgrade from Plus Dockerfile. +- [237](https://github.com/nginxinc/kubernetes-ingress/pull/237): Add unit test for ingress-class handling. +- [236](https://github.com/nginxinc/kubernetes-ingress/pull/236): Always respect `-ingress-class` option. Thanks to + [Nick Novitski](https://github.com/nicknovitski). +- [235](https://github.com/nginxinc/kubernetes-ingress/pull/235): Change the base image to Debian Stretch for Plus + controller. +- [234](https://github.com/nginxinc/kubernetes-ingress/pull/234): Update installation manifests and instructions. +- [233](https://github.com/nginxinc/kubernetes-ingress/pull/233): Add docker build options to Makefile. +- [231](https://github.com/nginxinc/kubernetes-ingress/pull/231): Prevent a possible failure of building Plus image. +- Documentation Fixes: [248](https://github.com/nginxinc/kubernetes-ingress/pull/248), thanks to + [zariye](https://github.com/zariye). [252](https://github.com/nginxinc/kubernetes-ingress/pull/252). + [270](https://github.com/nginxinc/kubernetes-ingress/pull/270). +- Update NGINX version to 1.13.12. +- Update NGINX Plus version to R15 P1. + +## 1.1.1 + +- [228](https://github.com/nginxinc/kubernetes-ingress/pull/228): Add worker-rlimit-nofile configmap key. Thanks to + [Aleksandr Lysenko](https://github.com/Sarga). +- [223](https://github.com/nginxinc/kubernetes-ingress/pull/223): Add worker-connections configmap key. Thanks to + [Aleksandr Lysenko](https://github.com/Sarga). +- Update NGINX version to 1.13.8. + +## 1.1.0 + +- [221](https://github.com/nginxinc/kubernetes-ingress/pull/221): Add git commit info to the IC log. +- [220](https://github.com/nginxinc/kubernetes-ingress/pull/220): Update dependencies. +- [213](https://github.com/nginxinc/kubernetes-ingress/pull/213): Add main snippets to allow Main context customization. + Thanks to [Dewen Kong](https://github.com/kongdewen). +- [211](https://github.com/nginxinc/kubernetes-ingress/pull/211): Minimize the number of configuration reloads when the + Ingress Controller starts; fix a problem with endpoints updates for Plus. +- [208](https://github.com/nginxinc/kubernetes-ingress/pull/208): Add worker-shutdown-timeout configmap key. Thanks to + [Aleksandr Lysenko](https://github.com/Sarga). +- [199](https://github.com/nginxinc/kubernetes-ingress/pull/199): Add support for Kubernetes ssl-redirect annotation. + Thanks to [Luke Seelenbinder](https://github.com/lseelenbinder). +- [194](https://github.com/nginxinc/kubernetes-ingress/pull/194) Add keepalive configmap key and annotation. +- [193](https://github.com/nginxinc/kubernetes-ingress/pull/193): Add worker-cpu-affinity configmap key. +- [192](https://github.com/nginxinc/kubernetes-ingress/pull/192): Add worker-processes configmap key. +- [186](https://github.com/nginxinc/kubernetes-ingress/pull/186): Fix hardcoded controller class. Thanks to [Serhii + M](https://github.com/SiriusRed). +- [184](https://github.com/nginxinc/kubernetes-ingress/pull/184): Return a meaningful error when there is no cert and + key for the default server. +- Update NGINX version to 1.13.7. +- Makefile updates: golang container was updated to 1.9. + +## 1.0.0 + +- [175](https://github.com/nginxinc/kubernetes-ingress/pull/175): Add support for JWT for NGINX Plus. +- [171](https://github.com/nginxinc/kubernetes-ingress/pull/171): Allow NGINX to listen on non-standard ports. Thanks to + [Stanislav Seletskiy](https://github.com/seletskiy). +- [170](https://github.com/nginxinc/kubernetes-ingress/pull/170): Add the default server. **Note**: The Ingress + Controller will fail to start if there are no cert and key for the default server. You can pass a TLS Secret for the + default server as an argument to the Ingress Controller or add a cert and a key to the Docker image. +- [169](https://github.com/nginxinc/kubernetes-ingress/pull/169): Ignore Ingress resources with empty hostnames. +- [168](https://github.com/nginxinc/kubernetes-ingress/pull/168): Add the `nginx.org/lb-method` annotation. Thanks to + [Sajal Kayan](https://github.com/sajal). +- [166](https://github.com/nginxinc/kubernetes-ingress/pull/166): Watch Secret resources for updates. **Note**: If a + Secret referenced by one or more Ingress resources becomes invalid or gets removed, the configuration for those + Ingress resources will be disabled until there is a valid Secret. +- [160](https://github.com/nginxinc/kubernetes-ingress/pull/160): Add support for events. See the details + [here](https://github.com/nginxinc/kubernetes-ingress/pull/160). +- [157](https://github.com/nginxinc/kubernetes-ingress/pull/157): Add graceful termination - when the Ingress Controller + receives `SIGTERM`, it shutdowns itself as well as NGINX, using `nginx -s quit`. + +## 0.9.0 + +- [156](https://github.com/nginxinc/kubernetes-ingress/pull/156): Write a pem file with an SSL certificate and key + atomically. +- [155](https://github.com/nginxinc/kubernetes-ingress/pull/155): Remove http2 annotation (http/2 can be enabled + globally in the ConfigMap). +- [154](https://github.com/nginxinc/kubernetes-ingress/pull/154): Merge NGINX and NGINX Plus Ingress Controller + implementations. +- [151](https://github.com/nginxinc/kubernetes-ingress/pull/151): Use k8s.io/client-go. +- [146](https://github.com/nginxinc/kubernetes-ingress/pull/146): Fix health status. +- [141](https://github.com/nginxinc/kubernetes-ingress/pull/141): Set `worker_processes` to `auto` in NGINX + configuration. Thanks to [Andreas Krüger](https://github.com/woopstar). +- [140](https://github.com/nginxinc/kubernetes-ingress/pull/140): Fix an error message. Thanks to [Andreas + Krüger](https://github.com/woopstar). +- Update NGINX to version 1.13.3. + +## 0.8.1 + +- Update NGINX version to 1.13.0. + +## 0.8.0 + +- [117](https://github.com/nginxinc/kubernetes-ingress/pull/117): Add a customization option: location-snippets, + server-snippets and http-snippets. Thanks to [rchicoli](https://github.com/rchicoli). +- [116](https://github.com/nginxinc/kubernetes-ingress/pull/116): Add support for the 301 redirect to https based on the + `http_x_forwarded_proto` header. Thanks to [Chris](https://github.com/cwhenderson20). +- Update NGINX version to 1.11.13. +- Makefile updates: gcloud docker push command; golang container was updated to 1.8. +- Documentation fixes: [113](https://github.com/nginxinc/kubernetes-ingress/pull/113). Thanks to [Linus + Lewandowski](https://github.com/LEW21). + +## 0.7.0 + +- [108](https://github.com/nginxinc/kubernetes-ingress/pull/108): Support for the `server_tokens` directive via the + annotation and in the configmap. Thanks to [David Radcliffe](https://github.com/dwradcliffe). +- [103](https://github.com/nginxinc/kubernetes-ingress/pull/103): Improve error reporting when NGINX fails to start. +- [100](https://github.com/nginxinc/kubernetes-ingress/pull/100): Add the health check location. Thanks to + [Julian](https://github.com/jmastr). +- [95](https://github.com/nginxinc/kubernetes-ingress/pull/95): Fix the runtime.TypeAssertionError issue, which + sometimes occurred when deleting resources. Thanks to [Tang Le](https://github.com/tangle329). +- [93](https://github.com/nginxinc/kubernetes-ingress/pull/93): Fix overwriting of Secrets with the same name from + different namespaces. +- [92](https://github.com/nginxinc/kubernetes-ingress/pull/92/files): Add overwriting of the HSTS header. Previously, + when HSTS was enabled, if a backend issued the HSTS header, the controller would add the second HSTS header. Now the + controller overwrites the HSTS header, if a backend also issues it. +- [91](https://github.com/nginxinc/kubernetes-ingress/pull/91): Fix the issue with single service Ingress resources +without any Ingress rules: the controller didn't pick up any updates of the endpoints of the service of such an Ingress +resource. Thanks to [Tang Le](https://github.com/tangle329). +- [88](https://github.com/nginxinc/kubernetes-ingress/pull/88): Support for the `proxy_hide_header` and the + `proxy_pass_header` directives via annotations and in the configmap. Thanks to [Nico + Schieder](https://github.com/thetechnick). +- [85](https://github.com/nginxinc/kubernetes-ingress/pull/85): Add the configmap settings to support perfect forward + secrecy. Thanks to [Nico Schieder](https://github.com/thetechnick). +- [84](https://github.com/nginxinc/kubernetes-ingress/pull/84): Secret retry: If a certificate Secret referenced in an +Ingress object is not found, the Ingress Controller will reject the Ingress object. but retries every 5s. Thanks to +[Nico Schieder](https://github.com/thetechnick). +- [81](https://github.com/nginxinc/kubernetes-ingress/pull/81): Add configmap options to turn on the PROXY protocol. + Thanks to [Nico Schieder](https://github.com/thetechnick). +- Update NGINX version to 1.11.8. +- Documentation fixes: [104](https://github.com/nginxinc/kubernetes-ingress/pull/104/files) and + [97](https://github.com/nginxinc/kubernetes-ingress/pull/97/files). Thanks to [Ruilin Huang](https://github.com/hrl) + and [Justin Garrison](https://github.com/rothgar). + +## 0.6.0 + +- [75](https://github.com/nginxinc/kubernetes-ingress/pull/75): Add the HSTS settings in the configmap and annotations. + Thanks to [Nico Schieder](https://github.com/thetechnick). +- [74](https://github.com/nginxinc/kubernetes-ingress/pull/74): Fix the issue of the `kubernetes.io/ingress.class` + annotation handling. Thanks to [Tang Le](https://github.com/tangle329). +- [70](https://github.com/nginxinc/kubernetes-ingress/pull/70): Add support for the alpine-based image for the NGINX + controller. +- [68](https://github.com/nginxinc/kubernetes-ingress/pull/68): Support for proxy-buffering settings in the configmap + and annotations. Thanks to [Mark Daniel Reidel](https://github.com/df-mreidel). +- [66](https://github.com/nginxinc/kubernetes-ingress/pull/66): Support for custom log-format in the configmap. Thanks + to [Mark Daniel Reidel](https://github.com/df-mreidel). +- [65](https://github.com/nginxinc/kubernetes-ingress/pull/65): Add HTTP/2 as an option in the configmap and + annotations. Thanks to [Nico Schieder](https://github.com/thetechnick). +- The NGINX Plus controller image is now based on Ubuntu Xenial. + +## 0.5.0 + +- Update NGINX version to 1.11.5. +- [64](https://github.com/nginxinc/kubernetes-ingress/pull/64): Add the `nginx.org/rewrites` annotation, which allows to + rewrite the URI of a request before sending it to the application. Thanks to [Julian](https://github.com/jmastr). +- [62](https://github.com/nginxinc/kubernetes-ingress/pull/62): Add the `nginx.org/ssl-services` annotation, which + allows load balancing of HTTPS applications. Thanks to [Julian](https://github.com/jmastr). + +## 0.4.0 + +- [54](https://github.com/nginxinc/kubernetes-ingress/pull/54): Previously, when specifying the port of a service in an + Ingress rule, you had to use the value of the target port of that port of the service, which was incorrect. Now you + must use the port value or the name of the port of the service instead of the target port value. **Note**: Please make + necessary changes to your Ingress resources, if ports of your services have different values of the port and the + target port fields. +- [55](https://github.com/nginxinc/kubernetes-ingress/pull/55): Add support for the `kubernetes.io/ingress.class` + annotation in Ingress resources. +- [58](https://github.com/nginxinc/kubernetes-ingress/pull/58): Add the version information to the controller. For each + version of the NGINX controller, you can find a corresponding image on + [DockerHub](https://hub.docker.com/r/nginxdemos/nginx-ingress/tags/) with a tag equal to the version. The latest + version is available through the `latest` tag. +The previous version was 0.3 -### Notes +## Notes -* Except when mentioned otherwise, the controller refers both to the NGINX and the NGINX Plus Ingress Controllers. +- Except when mentioned otherwise, the controller refers both to the NGINX and the NGINX Plus Ingress Controllers. diff --git a/CODEOWNERS b/CODEOWNERS index 9934150467..c9779a164e 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -1 +1,2 @@ * @nginxinc/kic +/site/ @nginxinc/kic @nginxinc/nginx-docs diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index bc3c7d3617..4deb7b91de 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -16,21 +16,21 @@ appearance, race, religion, or sexual identity and orientation. Examples of behavior that contributes to creating a positive environment include: -* Using welcoming and inclusive language -* Being respectful of differing viewpoints and experiences -* Gracefully accepting constructive criticism -* Focusing on what is best for the community -* Showing empathy towards other community members +- Using welcoming and inclusive language +- Being respectful of differing viewpoints and experiences +- Gracefully accepting constructive criticism +- Focusing on what is best for the community +- Showing empathy towards other community members Examples of unacceptable behavior by participants include: -* The use of sexualized language or imagery and unwelcome sexual attention or +- The use of sexualized language or imagery and unwelcome sexual attention or advances -* Trolling, insulting/derogatory comments, and personal or political attacks -* Public or private harassment -* Publishing others' private information, such as a physical or electronic +- Trolling, insulting/derogatory comments, and personal or political attacks +- Public or private harassment +- Publishing others' private information, such as a physical or electronic address, without explicit permission -* Other conduct which could reasonably be considered inappropriate in a +- Other conduct which could reasonably be considered inappropriate in a professional setting ## Our Responsibilities @@ -70,6 +70,6 @@ members of the project's leadership. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, -available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html +available at [homepage]: https://www.contributor-covenant.org diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index bc53f6fb9b..aa6882d465 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,8 +1,9 @@ # Contributing Guidelines -The following is a set of guidelines for contributing to the NGINX Ingress Controller. We really appreciate that you are considering contributing! +The following is a set of guidelines for contributing to NGINX Ingress Controller. We really appreciate that you are +considering contributing! -#### Table Of Contents +## Table Of Contents [Ask a Question](#ask-a-question) @@ -11,64 +12,97 @@ The following is a set of guidelines for contributing to the NGINX Ingress Contr [Contributing](#contributing) [Style Guides](#style-guides) - * [Git Style Guide](#git-style-guide) - * [Go Style Guide](#go-style-guide) -[Code of Conduct](https://github.com/nginxinc/kubernetes-ingress/blob/main/CODE_OF_CONDUCT.md) +- [Git Style Guide](#git-style-guide) +- [Go Style Guide](#go-style-guide) + +[Code of Conduct](CODE_OF_CONDUCT.md) ## Ask a Question -We will have a public forum soon where you can come and ask questions and have a discussion. For now please open an Issue on GitHub with the label `question`. +To ask a question please use [Github Discussions](https://github.com/nginxinc/kubernetes-ingress/discussions). + +You can also join our [Community Slack](https://community.nginx.org/joinslack) which has a wider NGINX audience. +Please reserve GitHub issues for feature requests and bugs rather than general questions. ## Getting Started -Follow our [Installation Guide](https://github.com/nginxinc/kubernetes-ingress/blob/main/docs/content/installation) to get the NGINX Ingress Controller up and running. +Follow our [Installation Guide](https://docs.nginx.com/nginx-ingress-controller/installation/) to +get NGINX Ingress Controller up and running. -Read the [documentation](https://github.com/nginxinc/kubernetes-ingress/tree/main/docs) and [configuration](https://github.com/nginxinc/kubernetes-ingress/tree/main/examples) examples +Read the [documentation](https://github.com/nginxinc/kubernetes-ingress/tree/main/site) and +[configuration](https://github.com/nginxinc/kubernetes-ingress/tree/main/examples) examples ### Project Structure -* This Ingress Controller is written in Go and supports both the open source NGINX software and NGINX Plus. -* The project follows a standard Go project layout - * The main code is found at `cmd/nginx-ingress/` - * The internal code is found at `internal/` - * Build files for Docker and CI are found under `build/` - * Deployment yaml files, and Helm files are found at `deployments/` - * The project dependencies are found at `vendor/`. We use [Go Modules](https://github.com/golang/go/wiki/Modules) for managing dependencies. +- NGINX Ingress Controller is written in Go and supports both the open source NGINX software and NGINX Plus. +- The project follows a standard Go project layout + - The main code is found at `cmd/nginx-ingress/` + - The internal code is found at `internal/` + - Build files for Docker are found at `build/` + - CI files are found at `.github/workflows/` + - Deployment yaml files, and Helm files are found at `deployments/` + - We use [Go modules](https://github.com/golang/go/wiki/Modules) for managing dependencies. ## Contributing ### Report a Bug -To report a bug, open an issue on GitHub with the label `bug` using the available bug report issue template. Please ensure the issue has not already been reported. +To report a bug, open an issue on GitHub and choose the type 'Bug report'. Please ensure the issue has not already been +reported, and that you fill in the template as provided, as this can reduce turnaround time. -### Suggest an Enhancement +### Suggest a new feature or other improvement -To suggest an enhancement, please create an issue on GitHub with the label `enhancement` using the available feature issue template. +To suggest an new feature or other improvement, create an issue on Github and choose the type 'Feature request'. Please +fill in the template as provided. ### Open a Pull Request -* Fork the repo, create a branch, submit a PR when your changes are tested and ready for review -* Fill in [our pull request template](https://github.com/nginxinc/kubernetes-ingress/blob/main/.github/PULL_REQUEST_TEMPLATE.md) +- Before working on a possible pull request, first open an associated issue describing the proposed change. This allows + the core development team to discuss the potential pull request with you before you do the work. +- Fork the repo, create a branch, submit a PR when your changes are tested and ready for review +- Fill in [our pull request template](.github/PULL_REQUEST_TEMPLATE.md) + +> **Note** +> +> Remember to create a feature request / bug report issue first to start a discussion about the proposed change. + +### Issue lifecycle + +- When an issue or PR is created, it will be triaged by the core development team and assigned a label to indicate the + type of issue it is (bug, feature request, etc) and to determine the milestone. Please see the [Issue + Lifecycle](ISSUE_LIFECYCLE.md) document for more information. + +### F5 Contributor License Agreement (CLA) + +F5 requires all external contributors to agree to the terms of the F5 CLA (available [here](https://github.com/f5/.github/blob/main/CLA/cla-markdown.md)) +before any of their changes can be incorporated into an F5 Open Source repository. -Note: if you’d like to implement a new feature, please consider creating a feature request issue first to start a discussion about the feature. +If you have not yet agreed to the F5 CLA terms and submit a PR to this repository, a bot will prompt you to view and +agree to the F5 CLA. You will have to agree to the F5 CLA terms through a comment in the PR before any of your changes +can be merged. Your agreement signature will be safely stored by F5 and no longer be required in future PRs. ## Style Guides ### Git Style Guide -* Keep a clean, concise and meaningful git commit history on your branch, rebasing locally and squashing before submitting a PR -* Follow the guidelines of writing a good commit message as described here https://chris.beams.io/posts/git-commit/ and summarised in the next few points - * In the subject line, use the present tense ("Add feature" not "Added feature") - * In the subject line, use the imperative mood ("Move cursor to..." not "Moves cursor to...") - * Limit the subject line to 72 characters or less - * Reference issues and pull requests liberally after the subject line - * Add more detailed description in the body of the git message (`git commit -a` to give you more space and time in your text editor to write a good message instead of `git commit -am`) +- Keep a clean, concise and meaningful git commit history on your branch, rebasing locally and squashing before + submitting a PR +- Follow the guidelines of writing a good commit message as described here + and summarized in the next few points + - In the subject line, use the present tense ("Add feature" not "Added feature") + - In the subject line, use the imperative mood ("Move cursor to..." not "Moves cursor to...") + - Limit the subject line to 72 characters or less + - Reference issues and pull requests liberally after the subject line + - Add more detailed description in the body of the git message (`git commit -a` to give you more space and time in + your text editor to write a good message instead of `git commit -am`) ### Go Style Guide -* Run `gofmt` over your code to automatically resolve a lot of style issues. Most editors support this running automatically when saving a code file. -* Run `go lint` and `go vet` on your code too to catch any other issues. -* Follow this guide on some good practice and idioms for Go - https://github.com/golang/go/wiki/CodeReviewComments -* To check for extra issues, install [golangci-lint](https://github.com/golangci/golangci-lint) and run `make lint` or `golangci-lint run` +- Run `gofmt` over your code to automatically resolve a lot of style issues. Most editors support this running + automatically when saving a code file. +- Run `go lint` and `go vet` on your code too to catch any other issues. +- Follow this guide on some good practice and idioms for Go - +- To check for extra issues, install [golangci-lint](https://github.com/golangci/golangci-lint) and run `make lint` or + `golangci-lint run` diff --git a/ISSUE_LIFECYCLE.md b/ISSUE_LIFECYCLE.md new file mode 100644 index 0000000000..545af55fa2 --- /dev/null +++ b/ISSUE_LIFECYCLE.md @@ -0,0 +1,53 @@ +# Issue Lifecycle + +To ensure a balance between work carried out by the NGINX engineering team while encouraging community involvement on +this project, we use the following issue lifecycle. (Note: The issue *creator* refers to the community member that +created the issue. The issue *owner* refers to the NGINX team member that is responsible for managing the issue +lifecycle.) + +1. New issue created by community member. + +2. Assign issue owner: All new issues are assigned an owner on the NGINX engineering team. This owner shepherds the + issue through the subsequent stages in the issue lifecycle. + +3. Determine issue type: This is done with automation where possible, and manually by the owner where necessary. The + associated label is applied to the issue. + + Possible Issue Types: + + - `needs more info`: The owner should use the issue to request information from the creator. If we don't receive the + needed information within 7 days, automation closes the issue. + + - `bug`: The implementation of a feature is not correct. + + - `proposal`: Request for a change. This can be a new feature, tackling technical debt, documentation changes, or + improving existing features. + + - `question`: The owner converts the issue to a github discussion and engages the creator. + +4. Determine milestone: The owner, in collaboration with the wider team (PM & engineering), determines what milestone to + attach to an issue. Generally, milestones correspond to product releases - however there are two 'magic' milestones + with special meanings (not tied to a specific release): + + - Issues assigned to backlog: Our team is in favour of implementing the feature request/fixing the issue, however the + implementation is not yet assigned to a concrete release. If and when a `backlog` issue aligns well with our + roadmap, it will be scheduled for a concrete iteration. We review and update our roadmap at least once every + quarter. The `backlog` list helps us shape our roadmap, but it is not the only source of input. Therefore, some + `backlog` items may eventually be closed as `out of scope`, or relabelled as `backlog candidate` once it becomes + clear that they do not align with our evolving roadmap. + + - Issues assigned to `backlog candidate`: Our team does not intend to implement the feature/fix request described in + the issue and wants the community to weigh in before we make our final decision. + + `backlog` issues can be labeled by the owner as `help wanted` and/or `good first issue` as appropriate. + +5. Promotion of `backlog candidate` issue to `backlog` issue: If an issue labelled `backlog candidate` receives more + than 30 upvotes within 60 days, we promote the issue by applying the `backlog` label. While issues promoted in this + manner have not been committed to a particular release, we welcome PRs from the community on them. + + If an issue does not make our roadmap and has not been moved to a discussion, it is closed with the label `out of + scope`. The goal is to get every issue in the issues list to one of the following end states: + + - An assigned release. + - The `backlog` label. + - Closed as `out of scope`. diff --git a/Makefile b/Makefile index 6dbc96981d..6eeced5ff5 100644 --- a/Makefile +++ b/Makefile @@ -1,19 +1,37 @@ # variables that should not be overridden by the user -GIT_COMMIT = $(shell git rev-parse HEAD || echo unknown) -GIT_COMMIT_SHORT = $(shell echo ${GIT_COMMIT} | cut -c1-7) -GIT_TAG = $(shell git describe --tags --abbrev=0 || echo untagged) -VERSION = $(GIT_TAG)-SNAPSHOT-$(GIT_COMMIT_SHORT) +VER = $(shell grep IC_VERSION .github/data/version.txt | cut -d '=' -f 2) +GIT_TAG = $(shell git describe --exact-match --tags || echo untagged) +VERSION = $(VER)-SNAPSHOT PLUS_ARGS = --secret id=nginx-repo.crt,src=nginx-repo.crt --secret id=nginx-repo.key,src=nginx-repo.key -# variables that can be overridden by the user -PREFIX ?= nginx/nginx-ingress## The name of the image. For example, nginx/nginx-ingress -TAG ?= $(VERSION:v%=%)## The tag of the image. For example, 2.0.0 -TARGET ?= local## The target of the build. Possible values: local, container and download -override DOCKER_BUILD_OPTIONS += --build-arg IC_VERSION=$(VERSION) --build-arg GIT_COMMIT=$(GIT_COMMIT)## The options for the docker build command. For example, --pull. -ARCH ?= amd64## The architecture of the image or binary. For example: amd64, arm64, ppc64le, s390x. Not all architectures are supported for all targets. +# Variables that can be overridden +REGISTRY ?= ## The registry where the image is located. +PREFIX ?= nginx/nginx-ingress ## The name of the image. For example, nginx/nginx-ingress +TAG ?= $(VERSION:v%=%) ## The tag of the image. For example, 2.0.0 +TARGET ?= local ## The target of the build. Possible values: local, container and download +PLUS_REPO ?= "pkgs.nginx.com" ## The package repo to install nginx-plus from +override DOCKER_BUILD_OPTIONS += --build-arg IC_VERSION=$(VERSION) --build-arg PACKAGE_REPO=$(PLUS_REPO) ## The options for the docker build command. For example, --pull +ARCH ?= amd64 ## The architecture of the image or binary. For example: amd64, arm64, ppc64le, s390x. Not all architectures are supported for all targets +GOOS ?= linux ## The OS of the binary. For example linux, darwin +NGINX_AGENT ?= true +TELEMETRY_ENDPOINT ?= oss.edge.df.f5.com:443 + +# Additional flags added here can be accessed in main.go. +# e.g. `main.version` maps to `var version` in main.go +GO_LINKER_FLAGS_VARS = -X main.version=${VERSION} -X main.telemetryEndpoint=${TELEMETRY_ENDPOINT} +GO_LINKER_FLAGS_OPTIONS = -s -w +GO_LINKER_FLAGS = $(GO_LINKER_FLAGS_OPTIONS) $(GO_LINKER_FLAGS_VARS) +DEBUG_GO_LINKER_FLAGS = $(GO_LINKER_FLAGS_VARS) +DEBUG_GO_GC_FLAGS = all=-N -l + +ifeq (${REGISTRY},) +BUILD_IMAGE := $(strip $(PREFIX)):$(strip $(TAG)) +else +BUILD_IMAGE := $(strip $(REGISTRY))/$(strip $(PREFIX)):$(strip $(TAG)) +endif # final docker build command -DOCKER_CMD = docker build --platform linux/$(ARCH) $(strip $(DOCKER_BUILD_OPTIONS)) --target $(strip $(TARGET)) -f build/Dockerfile -t $(strip $(PREFIX)):$(strip $(TAG)) . +DOCKER_CMD = docker build --platform linux/$(strip $(ARCH)) $(strip $(DOCKER_BUILD_OPTIONS)) --target $(strip $(TARGET)) -f build/Dockerfile -t $(BUILD_IMAGE) . export DOCKER_BUILDKIT = 1 @@ -21,8 +39,8 @@ export DOCKER_BUILDKIT = 1 .PHONY: help help: Makefile ## Display this help - @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "; printf "Usage:\n\n make \033[36m\033[0m [VARIABLE=value...]\n\nTargets:\n\n"}; {printf " \033[36m%-30s\033[0m %s\n", $$1, $$2}' - @grep -E '^(override )?[a-zA-Z_-]+ \??\+?= .*?## .*$$' $< | sort | awk 'BEGIN {FS = " \\??\\+?= .*?## "; printf "\nVariables:\n\n"}; {gsub(/override /, "", $$1); printf " \033[36m%-30s\033[0m %s\n", $$1, $$2}' + @grep -E '^[a-zA-Z0-9_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "; printf "Usage:\n\n make \033[36m\033[0m [VARIABLE=value...]\n\nTargets:\n\n"}; {printf " \033[36m%-30s\033[0m %s\n", $$1, $$2}' + @grep -E '^(override )?[a-zA-Z0-9_-]+ \??\+?= .*? ## .*$$' $< | sort | awk 'BEGIN {FS = " \\??\\+?= .*? ## "; printf "\nVariables:\n\n"}; {gsub(/override /, "", $$1); printf " \033[36m%-30s\033[0m %s\n", $$1, $$2}' .PHONY: all all: test lint verify-codegen update-crds debian-image @@ -39,21 +57,30 @@ lint-python: ## Run linter for python tests @isort . @black . +.PHONY: format +format: ## Run goimports & gofmt + @go install golang.org/x/tools/cmd/goimports + @go install mvdan.cc/gofumpt@latest + @goimports -l -w . + @gofumpt -l -w . + .PHONY: staticcheck staticcheck: ## Run staticcheck linter @staticcheck -version >/dev/null 2>&1 || go install honnef.co/go/tools/cmd/staticcheck@2022.1.3; staticcheck ./... .PHONY: test -test: ## Run tests - go test -tags=aws -shuffle=on -race ./... +test: ## Run GoLang tests + go test -tags=aws,helmunit -shuffle=on -race -coverprofile=coverage.txt -covermode=atomic ./... -cover: ## Generate coverage report - @./hack/test-cover.sh +.PHONY: test-update-snaps +test-update-snaps: + UPDATE_SNAPS=true go test -tags=aws,helmunit -shuffle=on -race ./... -cover-html: ## Generate and show coverage report in HTML format - go test -tags=aws -shuffle=on -race ./... -count=1 -cover -covermode=atomic -coverprofile=coverage.out - go tool cover -html coverage.out +cover: test ## Generate coverage report + +cover-html: test ## Generate and show coverage report in HTML format + go tool cover -html coverage.txt .PHONY: verify-codegen verify-codegen: ## Verify code generation @@ -65,27 +92,34 @@ update-codegen: ## Generate code .PHONY: update-crds update-crds: ## Update CRDs - go run sigs.k8s.io/controller-tools/cmd/controller-gen crd:crdVersions=v1 schemapatch:manifests=./deployments/common/crds/ paths=./pkg/apis/... output:dir=./deployments/common/crds - @cp -Rp deployments/common/crds/* deployments/helm-chart/crds/ + go run sigs.k8s.io/controller-tools/cmd/controller-gen crd paths=./pkg/apis/... output:crd:artifacts:config=config/crd/bases + @kustomize version || (code=$$?; printf "\033[0;31mError\033[0m: there was a problem with kustomize, use 'brew install kustomize' to install it\n"; exit $$code) + kustomize build config/crd >deploy/crds.yaml + kustomize build config/crd/app-protect-dos --load-restrictor='LoadRestrictionsNone' >deploy/crds-nap-dos.yaml + kustomize build config/crd/app-protect-waf --load-restrictor='LoadRestrictionsNone' >deploy/crds-nap-waf.yaml -.PHONY: certificate-and-key -certificate-and-key: ## Create default cert and key - ./build/generate_default_cert_and_key.sh +.PHONY: telemetry-schema +telemetry-schema: ## Generate the telemetry Schema + go generate internal/telemetry/exporter.go + gofumpt -w internal/telemetry/*_generated.go .PHONY: build build: ## Build Ingress Controller binary @docker -v || (code=$$?; printf "\033[0;31mError\033[0m: there was a problem with Docker\n"; exit $$code) -ifeq (${TARGET},local) +ifeq ($(strip $(TARGET)),local) @go version || (code=$$?; printf "\033[0;31mError\033[0m: unable to build locally, try using the parameter TARGET=container or TARGET=download\n"; exit $$code) - CGO_ENABLED=0 GOOS=linux GOARCH=$(ARCH) go build -trimpath -ldflags "-s -w -X main.version=${VERSION}" -o nginx-ingress github.com/nginxinc/kubernetes-ingress/cmd/nginx-ingress -else ifeq (${TARGET},download) + CGO_ENABLED=0 GOOS=$(strip $(GOOS)) GOARCH=$(strip $(ARCH)) go build -trimpath -ldflags "$(GO_LINKER_FLAGS)" -o nginx-ingress github.com/nginxinc/kubernetes-ingress/cmd/nginx-ingress +else ifeq ($(strip $(TARGET)),download) @$(MAKE) download-binary-docker +else ifeq ($(strip $(TARGET)),debug) + @go version || (code=$$?; printf "\033[0;31mError\033[0m: unable to build locally, try using the parameter TARGET=container or TARGET=download\n"; exit $$code) + CGO_ENABLED=0 GOOS=$(strip $(GOOS)) GOARCH=$(strip $(ARCH)) go build -ldflags "$(DEBUG_GO_LINKER_FLAGS)" -gcflags "$(DEBUG_GO_GC_FLAGS)" -o nginx-ingress github.com/nginxinc/kubernetes-ingress/cmd/nginx-ingress endif .PHONY: download-binary-docker download-binary-docker: ## Download Docker image from which to extract Ingress Controller binary, TARGET=download is required -ifeq (${TARGET},download) -DOWNLOAD_TAG := $(shell ./hack/docker.sh $(GIT_COMMIT) $(GIT_TAG)) +ifeq ($(strip $(TARGET)),download) +DOWNLOAD_TAG := $(shell ./hack/docker.sh $(GIT_TAG)) ifeq ($(DOWNLOAD_TAG),fail) $(error unable to build with TARGET=download, this function is only available when building from a git tag or from the latest commit matching the edge image) endif @@ -95,7 +129,7 @@ endif .PHONY: build-goreleaser build-goreleaser: ## Build Ingress Controller binary using GoReleaser @goreleaser -v || (code=$$?; printf "\033[0;31mError\033[0m: there was a problem with GoReleaser. Follow the docs to install it https://goreleaser.com/install\n"; exit $$code) - GOOS=linux GOPATH=$(shell go env GOPATH) GOARCH=$(ARCH) goreleaser build --rm-dist --debug --snapshot --id kubernetes-ingress --single-target + GOOS=linux GOPATH=$(shell go env GOPATH) GOARCH=$(strip $(ARCH)) goreleaser build --clean --debug --snapshot --id kubernetes-ingress --single-target .PHONY: debian-image debian-image: build ## Create Docker image for Ingress Controller (Debian) @@ -109,21 +143,44 @@ alpine-image: build ## Create Docker image for Ingress Controller (Alpine) alpine-image-plus: build ## Create Docker image for Ingress Controller (Alpine with NGINX Plus) $(DOCKER_CMD) $(PLUS_ARGS) --build-arg BUILD_OS=alpine-plus +.PHONY: alpine-image-plus-fips +alpine-image-plus-fips: build ## Create Docker image for Ingress Controller (Alpine with NGINX Plus and FIPS) + $(DOCKER_CMD) $(PLUS_ARGS) --build-arg BUILD_OS=alpine-plus-fips + +.PHONY: alpine-image-nap-plus-fips +alpine-image-nap-plus-fips: build ## Create Docker image for Ingress Controller (Alpine with NGINX Plus, NGINX App Protect WAF and FIPS) + $(DOCKER_CMD) $(PLUS_ARGS) --build-arg BUILD_OS=alpine-plus-nap-fips --build-arg NGINX_AGENT=$(NGINX_AGENT) + +.PHONY: alpine-image-nap-v5-plus-fips +alpine-image-nap-v5-plus-fips: build ## Create Docker image for Ingress Controller (Alpine with NGINX Plus, NGINX App Protect WAFv5 and FIPS) + $(DOCKER_CMD) $(PLUS_ARGS) \ + --build-arg BUILD_OS=alpine-plus-nap-v5-fips \ + --build-arg NGINX_AGENT=$(NGINX_AGENT) \ + --build-arg WAF_VERSION=v5 + .PHONY: debian-image-plus debian-image-plus: build ## Create Docker image for Ingress Controller (Debian with NGINX Plus) $(DOCKER_CMD) $(PLUS_ARGS) --build-arg BUILD_OS=debian-plus .PHONY: debian-image-nap-plus -debian-image-nap-plus: build ## Create Docker image for Ingress Controller (Debian with NGINX Plus and App Protect WAF) - $(DOCKER_CMD) $(PLUS_ARGS) --build-arg BUILD_OS=debian-plus-nap --build-arg DEBIAN_VERSION=buster-slim --build-arg NAP_MODULES=waf +debian-image-nap-plus: build ## Create Docker image for Ingress Controller (Debian with NGINX Plus and NGINX App Protect WAF) + $(DOCKER_CMD) $(PLUS_ARGS) --build-arg BUILD_OS=debian-plus-nap --build-arg NAP_MODULES=waf --build-arg NGINX_AGENT=$(NGINX_AGENT) + +.PHONY: debian-image-nap-v5-plus +debian-image-nap-v5-plus: build ## Create Docker image for Ingress Controller (Debian with NGINX Plus and NGINX App Protect WAFv5) + $(DOCKER_CMD) $(PLUS_ARGS) \ + --build-arg BUILD_OS=debian-plus-nap-v5 \ + --build-arg NAP_MODULES=waf \ + --build-arg NGINX_AGENT=$(NGINX_AGENT) \ + --build-arg WAF_VERSION=v5 .PHONY: debian-image-dos-plus -debian-image-dos-plus: build ## Create Docker image for Ingress Controller (Debian with NGINX Plus and App Protect DoS) +debian-image-dos-plus: build ## Create Docker image for Ingress Controller (Debian with NGINX Plus and NGINX App Protect DoS) $(DOCKER_CMD) $(PLUS_ARGS) --build-arg BUILD_OS=debian-plus-nap --build-arg NAP_MODULES=dos .PHONY: debian-image-nap-dos-plus -debian-image-nap-dos-plus: build ## Create Docker image for Ingress Controller (Debian with NGINX Plus, App Protect WAF and DoS) - $(DOCKER_CMD) $(PLUS_ARGS) --build-arg BUILD_OS=debian-plus-nap --build-arg DEBIAN_VERSION=buster-slim --build-arg NAP_MODULES=waf,dos +debian-image-nap-dos-plus: build ## Create Docker image for Ingress Controller (Debian with NGINX Plus, NGINX App Protect WAF and DoS) + $(DOCKER_CMD) $(PLUS_ARGS) --build-arg BUILD_OS=debian-plus-nap --build-arg NAP_MODULES=waf,dos --build-arg NGINX_AGENT=$(NGINX_AGENT) .PHONY: ubi-image ubi-image: build ## Create Docker image for Ingress Controller (UBI) @@ -131,44 +188,43 @@ ubi-image: build ## Create Docker image for Ingress Controller (UBI) .PHONY: ubi-image-plus ubi-image-plus: build ## Create Docker image for Ingress Controller (UBI with NGINX Plus) - $(DOCKER_CMD) $(PLUS_ARGS) --build-arg BUILD_OS=ubi-plus + $(DOCKER_CMD) $(PLUS_ARGS) --build-arg BUILD_OS=ubi-9-plus .PHONY: ubi-image-nap-plus -ubi-image-nap-plus: build ## Create Docker image for Ingress Controller (UBI with NGINX Plus and App Protect WAF) - $(DOCKER_CMD) $(PLUS_ARGS) --secret id=rhel_license,src=rhel_license --build-arg BUILD_OS=ubi-plus-nap --build-arg NAP_MODULES=waf +ubi-image-nap-plus: build ## Create Docker image for Ingress Controller (UBI with NGINX Plus and NGINX App Protect WAF) + $(DOCKER_CMD) $(PLUS_ARGS) --secret id=rhel_license,src=rhel_license --build-arg BUILD_OS=ubi-9-plus-nap --build-arg NAP_MODULES=waf --build-arg NGINX_AGENT=$(NGINX_AGENT) + +.PHONY: ubi-image-nap-v5-plus +ubi-image-nap-v5-plus: build ## Create Docker image for Ingress Controller (UBI with NGINX Plus and NGINX App Protect WAFv5) + $(DOCKER_CMD) $(PLUS_ARGS) --secret id=rhel_license,src=rhel_license \ + --build-arg BUILD_OS=ubi-9-plus-nap-v5 \ + --build-arg NAP_MODULES=waf \ + --build-arg NGINX_AGENT=$(NGINX_AGENT) \ + --build-arg WAF_VERSION=v5 .PHONY: ubi-image-dos-plus -ubi-image-dos-plus: build ## Create Docker image for Ingress Controller (UBI with NGINX Plus and App Protect DoS) - $(DOCKER_CMD) $(PLUS_ARGS) --secret id=rhel_license,src=rhel_license --build-arg BUILD_OS=ubi-plus-nap --build-arg NAP_MODULES=dos +ubi-image-dos-plus: build ## Create Docker image for Ingress Controller (UBI with NGINX Plus and NGINX App Protect DoS) + $(DOCKER_CMD) $(PLUS_ARGS) --secret id=rhel_license,src=rhel_license --build-arg BUILD_OS=ubi-9-plus-nap --build-arg NAP_MODULES=dos .PHONY: ubi-image-nap-dos-plus -ubi-image-nap-dos-plus: build ## Create Docker image for Ingress Controller (UBI with NGINX Plus, App Protect WAF and DoS) - $(DOCKER_CMD) $(PLUS_ARGS) --secret id=rhel_license,src=rhel_license --build-arg BUILD_OS=ubi-plus-nap --build-arg NAP_MODULES=waf,dos - -.PHONY: openshift-image openshift-image-plus openshift-image-nap-plus openshift-image-dos-plus openshift-image-nap-dos-plus -openshift-image openshift-image-plus openshift-image-nap-plus openshift-image-dos-plus openshift-image-nap-dos-plus: - @printf "\033[0;31mWarning\033[0m: The target $(filter openshift-%,$(MAKECMDGOALS)) was renamed to $(subst openshift,ubi,$(filter openshift-%,$(MAKECMDGOALS))) and will be removed in a future release.\n" - @$(MAKE) $(subst openshift,ubi,$(MAKECMDGOALS)) $(MAKEFLAGS) - -.PHONY: alpine-image-opentracing -alpine-image-opentracing: - @echo "OpenTracing is now included in all Alpine based images" - -.PHONY: debian-image-opentracing debian-image-opentracing-plus -debian-image-opentracing debian-image-opentracing-plus: - @echo "OpenTracing is now included in all Debian based images" +ubi-image-nap-dos-plus: build ## Create Docker image for Ingress Controller (UBI with NGINX Plus, NGINX App Protect WAF and DoS) + $(DOCKER_CMD) $(PLUS_ARGS) --secret id=rhel_license,src=rhel_license --build-arg BUILD_OS=ubi-9-plus-nap --build-arg NAP_MODULES=waf,dos --build-arg NGINX_AGENT=$(NGINX_AGENT) .PHONY: all-images ## Create all the Docker images for Ingress Controller -all-images: alpine-image alpine-image-plus debian-image debian-image-plus debian-image-nap-plus debian-image-dos-plus debian-image-nap-dos-plus ubi-image ubi-image-plus ubi-image-nap-plus ubi-image-dos-plus ubi-image-nap-dos-plus +all-images: alpine-image alpine-image-plus alpine-image-plus-fips alpine-image-nap-plus-fips debian-image debian-image-plus debian-image-nap-plus debian-image-dos-plus debian-image-nap-dos-plus ubi-image ubi-image-plus ubi-image-nap-plus ubi-image-dos-plus ubi-image-nap-dos-plus + +.PHONY: patch-os +patch-os: ## Patch supplied image + $(DOCKER_CMD) --build-arg IMAGE_NAME=$(IMAGE) .PHONY: push push: ## Docker push to PREFIX and TAG - docker push $(PREFIX):$(TAG) + docker push $(strip $(PREFIX)):$(strip $(TAG)) .PHONY: clean clean: ## Remove nginx-ingress binary - -rm nginx-ingress - -rm -r dist + -rm -f nginx-ingress + -rm -rf dist .PHONY: deps deps: ## Add missing and remove unused modules, verify deps and download them to local cache @@ -177,3 +233,8 @@ deps: ## Add missing and remove unused modules, verify deps and download them to .PHONY: clean-cache clean-cache: ## Clean go cache @go clean -modcache + +.PHONY: rebuild-test-img ## Rebuild the python e2e test image +rebuild-test-img: + cd tests && \ + make build diff --git a/README.md b/README.md index 30d191d7c3..2d13c24e6b 100644 --- a/README.md +++ b/README.md @@ -1,80 +1,182 @@ + +[![OpenSSFScorecard](https://api.securityscorecards.dev/projects/github.com/nginxinc/kubernetes-ingress/badge)](https://api.securityscorecards.dev/projects/github.com/nginxinc/kubernetes-ingress) +[![Regression](https://github.com/nginxinc/kubernetes-ingress/actions/workflows/regression.yml/badge.svg?event=schedule)](https://github.com/nginxinc/kubernetes-ingress/actions/workflows/regression.yml?query=event%3Aschedule) +[![FOSSA Status](https://app.fossa.com/api/projects/custom%2B5618%2Fgithub.com%2Fnginxinc%2Fkubernetes-ingress.svg?type=shield)](https://app.fossa.com/projects/custom%2B5618%2Fgithub.com%2Fnginxinc%2Fkubernetes-ingress?ref=badge_shield) +[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) +[![Go Report Card](https://goreportcard.com/badge/github.com/nginxinc/kubernetes-ingress)](https://goreportcard.com/report/github.com/nginxinc/kubernetes-ingress) +[![codecov](https://codecov.io/gh/nginxinc/kubernetes-ingress/branch/main/graph/badge.svg?token=snCn7Y0zC7)](https://codecov.io/gh/nginxinc/kubernetes-ingress) +[![GitHub release (latest SemVer)](https://img.shields.io/github/v/release/nginxinc/kubernetes-ingress?logo=github&sort=semver)](https://github.com/nginxinc/kubernetes-ingress/releases/latest) +![GitHub go.mod Go version](https://img.shields.io/github/go-mod/go-version/nginxinc/kubernetes-ingress?logo=go) +[![Docker Pulls](https://img.shields.io/docker/pulls/nginx/nginx-ingress?logo=docker&logoColor=white)](https://hub.docker.com/r/nginx/nginx-ingress) +![Docker Image Size (latest semver)](https://img.shields.io/docker/image-size/nginx/nginx-ingress?logo=docker&logoColor=white&sort=semver) +[![Artifact Hub](https://img.shields.io/endpoint?url=https://artifacthub.io/badge/repository/nginx-ingress)](https://artifacthub.io/packages/container/nginx-ingress/kubernetes-ingress) +[![Slack](https://img.shields.io/badge/slack-%23nginx--ingress--controller-green?logo=slack)](https://nginxcommunity.slack.com/channels/nginx-ingress-controller) +[![Project Status: Active – The project has reached a stable, usable state and is being actively developed.](https://www.repostatus.org/badges/latest/active.svg)](https://www.repostatus.org/#active) +![Commercial Support](https://badgen.net/badge/support/commercial/green?icon=awesome) -[![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/nginxinc/kubernetes-ingress/badge)](https://api.securityscorecards.dev/projects/github.com/nginxinc/kubernetes-ingress) [![CI](https://github.com/nginxinc/kubernetes-ingress/actions/workflows/ci.yml/badge.svg)](https://github.com/nginxinc/kubernetes-ingress/actions/workflows/ci.yml) [![FOSSA Status](https://app.fossa.com/api/projects/custom%2B5618%2Fgithub.com%2Fnginxinc%2Fkubernetes-ingress.svg?type=shield)](https://app.fossa.com/projects/custom%2B5618%2Fgithub.com%2Fnginxinc%2Fkubernetes-ingress?ref=badge_shield) [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) [![Go Report Card](https://goreportcard.com/badge/github.com/nginxinc/kubernetes-ingress)](https://goreportcard.com/report/github.com/nginxinc/kubernetes-ingress) [![codecov](https://codecov.io/gh/nginxinc/kubernetes-ingress/branch/main/graph/badge.svg?token=snCn7Y0zC7)](https://codecov.io/gh/nginxinc/kubernetes-ingress) [![GitHub release (latest SemVer)](https://img.shields.io/github/v/release/nginxinc/kubernetes-ingress?logo=github&sort=semver)](https://github.com/nginxinc/kubernetes-ingress/releases/latest) ![GitHub go.mod Go version](https://img.shields.io/github/go-mod/go-version/nginxinc/kubernetes-ingress?logo=go) [![Docker Pulls](https://img.shields.io/docker/pulls/nginx/nginx-ingress?logo=docker&logoColor=white)](https://hub.docker.com/r/nginx/nginx-ingress) ![Docker Image Size (latest semver)](https://img.shields.io/docker/image-size/nginx/nginx-ingress?logo=docker&logoColor=white&sort=semver) [![Artifact Hub](https://img.shields.io/endpoint?url=https://artifacthub.io/badge/repository/nginx-ingress)](https://artifacthub.io/packages/container/nginx-ingress/kubernetes-ingress) [![Slack](https://img.shields.io/badge/slack-%23nginx--ingress--controller-green?logo=slack)](https://nginxcommunity.slack.com/channels/nginx-ingress-controller) +# NGINX Ingress Controller -# 🚀 *Help make the NGINX Ingress Controller better by participating in our [survey](https://forms.office.com/Pages/ResponsePage.aspx?id=L_093Ttq0UCb4L-DJ9gcUKLQ7uTJaE1PitM_37KR881UMEs0Rk5PMkYzMTJTWVA0V1hUVTRLUUMyNS4u)!* 🚀 +This repo provides an implementation of an Ingress Controller for NGINX and NGINX Plus from the people behind NGINX. -# NGINX Ingress Controller +--- -This repo provides an implementation of an Ingress Controller for NGINX and NGINX Plus. +## Join The Next Community Call -**Note**: this project is different from the NGINX Ingress Controller in [kubernetes/ingress-nginx](https://github.com/kubernetes/ingress-nginx) repo. See [this doc](https://docs.nginx.com/nginx-ingress-controller/intro/nginx-ingress-controllers) to find out about the key differences. +We value community input and would love to see you at the next community call. At these calls, we discuss PRs by community members as well as issues, discussions and feature requests. -## What is the Ingress? +**Microsoft Teams Link**: [NIC - GitHub Issues Triage](https://teams.microsoft.com/l/meetup-join/19%3ameeting_OTRhZjFhMDMtZTQwOC00NDA4LWJiOGItZjhhMmE5NzgyMDY0%40thread.v2/0?context=%7b%22Tid%22%3a%22dd3dfd2f-6a3b-40d1-9be0-bf8327d81c50%22%2c%22Oid%22%3a%22ea616cee-2e02-45f5-8e4c-c24967346491%22%7d) -The Ingress is a Kubernetes resource that lets you configure an HTTP load balancer for applications running on Kubernetes, represented by one or more [Services](https://kubernetes.io/docs/concepts/services-networking/service/). Such a load balancer is necessary to deliver those applications to clients outside of the Kubernetes cluster. +**Meeting ID:** `298 140 979 789` -The Ingress resource supports the following features: -* **Content-based routing**: - * *Host-based routing*. For example, routing requests with the host header `foo.example.com` to one group of services and the host header `bar.example.com` to another group. - * *Path-based routing*. For example, routing requests with the URI that starts with `/serviceA` to service A and requests with the URI that starts with `/serviceB` to service B. -* **TLS/SSL termination** for each hostname, such as `foo.example.com`. +**Passcode:** `jpx5TM` -See the [Ingress User Guide](https://kubernetes.io/docs/user-guide/ingress/) to learn more about the Ingress resource. +**Slack**: Join our channel `#nginx-ingress-controller` on the [NGINX Community Slack](https://nginxcommunity.slack.com/channels/nginx-ingress-controller) for updates and discussions. +**When**: 15:00 GMT / [Convert to your timezone](https://dateful.com/convert/gmt?t=15), every other Monday. -## What is the Ingress Controller? +| **Community Call Dates** | +| ------------------------ | +| **2025-01-13** | +| **2025-01-27** | +| **2025-02-10** | +| **2025-02-24** | +| **2025-03-11** | +| **2025-03-24** | -The Ingress Controller is an application that runs in a cluster and configures an HTTP load balancer according to Ingress resources. The load balancer can be a software load balancer running in the cluster or a hardware or cloud load balancer running externally. Different load balancers require different Ingress Controller implementations. +--- -In the case of NGINX, the Ingress Controller is deployed in a pod along with the load balancer. +NGINX Ingress Controller works with both NGINX and NGINX Plus and supports the standard Ingress features - content-based +routing and TLS/SSL termination. -## NGINX Ingress Controller +Additionally, several NGINX and NGINX Plus features are available as extensions to the Ingress resource via annotations +and the ConfigMap resource. In addition to HTTP, NGINX Ingress Controller supports load balancing Websocket, gRPC, TCP +and UDP applications. See +[ConfigMap](https://docs.nginx.com/nginx-ingress-controller/configuration/global-configuration/configmap-resource/) and +[Annotations](https://docs.nginx.com/nginx-ingress-controller/configuration/ingress-resources/advanced-configuration-with-annotations/) +docs to learn more about the supported features and customization options. -NGINX Ingress Controller works with both NGINX and NGINX Plus and supports the standard Ingress features - content-based routing and TLS/SSL termination. +As an alternative to the Ingress, NGINX Ingress Controller supports the VirtualServer and VirtualServerRoute resources. +They enable use cases not supported with the Ingress resource, such as traffic splitting and advanced content-based +routing. See [VirtualServer and VirtualServerRoute resources +doc](https://docs.nginx.com/nginx-ingress-controller/configuration/virtualserver-and-virtualserverroute-resources/). -Additionally, several NGINX and NGINX Plus features are available as extensions to the Ingress resource via annotations and the ConfigMap resource. In addition to HTTP, NGINX Ingress Controller supports load balancing Websocket, gRPC, TCP and UDP applications. See [ConfigMap](https://docs.nginx.com/nginx-ingress-controller/configuration/global-configuration/configmap-resource/) and [Annotations](https://docs.nginx.com/nginx-ingress-controller/configuration/ingress-resources/advanced-configuration-with-annotations/) docs to learn more about the supported features and customization options. +TCP, UDP and TLS Passthrough load balancing is also supported. See the [TransportServer resource +doc](https://docs.nginx.com/nginx-ingress-controller/configuration/transportserver-resource/). -As an alternative to the Ingress, NGINX Ingress Controller supports the VirtualServer and VirtualServerRoute resources. They enable use cases not supported with the Ingress resource, such as traffic splitting and advanced content-based routing. See [VirtualServer and VirtualServerRoute resources doc](https://docs.nginx.com/nginx-ingress-controller/configuration/virtualserver-and-virtualserverroute-resources/). +Read [this doc](https://docs.nginx.com/nginx-ingress-controller/intro/nginx-plus) to learn more about NGINX Ingress +Controller with NGINX Plus. -TCP, UDP and TLS Passthrough load balancing is also supported. See the [TransportServer resource doc](https://docs.nginx.com/nginx-ingress-controller/configuration/transportserver-resource/). +> **Note** +> +> This project is different from the NGINX Ingress Controller in +[kubernetes/ingress-nginx](https://github.com/kubernetes/ingress-nginx) repo. See [this +doc](https://docs.nginx.com/nginx-ingress-controller/intro/nginx-ingress-controllers) to find out about the key +differences. -Read [this doc](https://docs.nginx.com/nginx-ingress-controller/intro/nginx-plus) to learn more about NGINX Ingress Controller with NGINX Plus. +## Ingress and Ingress Controller + +### What is the Ingress? + +The Ingress is a Kubernetes resource that lets you configure an HTTP load balancer for applications running on +Kubernetes, represented by one or more [Services](https://kubernetes.io/docs/concepts/services-networking/service/). +Such a load balancer is necessary to deliver those applications to clients outside of the Kubernetes cluster. + +The Ingress resource supports the following features: + +- **Content-based routing**: + - *Host-based routing*. For example, routing requests with the host header `foo.example.com` to one group of services + and the host header `bar.example.com` to another group. + - *Path-based routing*. For example, routing requests with the URI that starts with `/serviceA` to service A and + requests with the URI that starts with `/serviceB` to service B. +- **TLS/SSL termination** for each hostname, such as `foo.example.com`. + +See the [Ingress User Guide](https://kubernetes.io/docs/concepts/services-networking/ingress/) to learn more about the +Ingress resource. + +### What is the Ingress Controller? + +The Ingress Controller is an application that runs in a cluster and configures an HTTP load balancer according to +Ingress resources. The load balancer can be a software load balancer running in the cluster or a hardware or cloud load +balancer running externally. Different load balancers require different Ingress Controller implementations. + +In the case of NGINX, the Ingress Controller is deployed in a pod along with the load balancer. ## Getting Started -1. Install the NGINX Ingress Controller using the Kubernetes [manifests](https://docs.nginx.com/nginx-ingress-controller/installation/installation-with-manifests/) or the [helm chart](https://docs.nginx.com/nginx-ingress-controller/installation/installation-with-helm/). +> **Note** +> +> All documentation should only be used with the latest stable release, indicated on [the releases +> page](https://github.com/nginxinc/kubernetes-ingress/releases) of the GitHub repository. + +1. Install NGINX Ingress Controller using the [Helm + chart](https://docs.nginx.com/nginx-ingress-controller/installation/installing-nic/installation-with-helm/) or the Kubernetes + [manifests](https://docs.nginx.com/nginx-ingress-controller/installation/installing-nic/installation-with-manifests/). 1. Configure load balancing for a simple web application: - * Use the Ingress resource. See the [Cafe example](https://github.com/nginxinc/kubernetes-ingress/tree/main/examples/ingress-resources/complete-example). - * Or the VirtualServer resource. See the [Basic configuration](https://github.com/nginxinc/kubernetes-ingress/tree/main/examples/custom-resources/basic-configuration) example. + - Use the Ingress resource. See the [Cafe + example](https://github.com/nginxinc/kubernetes-ingress/tree/main/examples/ingress-resources/complete-example). + - Or the VirtualServer resource. See the [Basic + configuration](https://github.com/nginxinc/kubernetes-ingress/tree/main/examples/custom-resources/basic-configuration) + example. 1. See additional configuration [examples](https://github.com/nginxinc/kubernetes-ingress/tree/main/examples). -1. Learn more about all available configuration and customization in the [docs](https://docs.nginx.com/nginx-ingress-controller/). - +1. Learn more about all available configuration and customization in the + [docs](https://docs.nginx.com/nginx-ingress-controller/). ## NGINX Ingress Controller Releases -We publish Ingress Controller releases on GitHub. See our [releases page](https://github.com/nginxinc/kubernetes-ingress/releases). +We publish NGINX Ingress Controller releases on GitHub. See our [releases +page](https://github.com/nginxinc/kubernetes-ingress/releases). -The latest stable release is [2.4.1](https://github.com/nginxinc/kubernetes-ingress/releases/tag/v2.4.1). For production use, we recommend that you choose the latest stable release. +The latest stable release is [4.0.0](https://github.com/nginxinc/kubernetes-ingress/releases/tag/v4.0.0). For production +use, we recommend that you choose the latest stable release. -The edge version is useful for experimenting with new features that are not yet published in a stable release. To use, choose the *edge* version built from the [latest commit](https://github.com/nginxinc/kubernetes-ingress/commits/main) from the main branch. +The edge version is useful for experimenting with new features that are not yet published in a stable release. To use +it, choose the *edge* version built from the [latest +commit](https://github.com/nginxinc/kubernetes-ingress/commits/main) from the main branch. -To use the Ingress Controller, you need to have access to: -* An Ingress Controller image. -* Installation manifests or a Helm chart. -* Documentation and examples. +To use NGINX Ingress Controller, you need to have access to: + +- An NGINX Ingress Controller image. +- Installation manifests or a Helm chart. +- Documentation and examples. It is important that the versions of those things above match. -The table below summarizes the options regarding the images, manifests, helm chart, documentation and examples and gives your links to the correct versions: +The table below summarizes the options regarding the images, Helm chart, manifests, documentation and examples and gives +your links to the correct versions: | Version | Description | Image for NGINX | Image for NGINX Plus | Installation Manifests and Helm Chart | Documentation and Examples | | ------- | ----------- | --------------- | -------------------- | ---------------------------------------| -------------------------- | -| Latest stable release | For production use | Use the 2.4.1 images from [DockerHub](https://hub.docker.com/r/nginx/nginx-ingress/), [GitHub Container](https://github.com/nginxinc/kubernetes-ingress/pkgs/container/kubernetes-ingress), [Amazon ECR Public Gallery](https://gallery.ecr.aws/nginx/nginx-ingress) or [Quay.io](https://quay.io/repository/nginx/nginx-ingress) or [build your own image](https://docs.nginx.com/nginx-ingress-controller/installation/building-ingress-controller-image/). | Use the 2.4.1 images from the [F5 Container Registry](https://docs.nginx.com/nginx-ingress-controller/installation/pulling-ingress-controller-image/) or the [AWS Marketplace](https://aws.amazon.com/marketplace/search/?CREATOR=741df81b-dfdc-4d36-b8da-945ea66b522c&FULFILLMENT_OPTION_TYPE=CONTAINER&filters=CREATOR%2CFULFILLMENT_OPTION_TYPE) or [Build your own image](https://docs.nginx.com/nginx-ingress-controller/installation/building-ingress-controller-image/). | [Manifests](https://github.com/nginxinc/kubernetes-ingress/tree/v2.4.1/deployments). [Helm chart](https://github.com/nginxinc/kubernetes-ingress/tree/v2.4.1/deployments/helm-chart). | [Documentation](https://docs.nginx.com/nginx-ingress-controller/). [Examples](https://docs.nginx.com/nginx-ingress-controller/configuration/configuration-examples/). | -| Edge/Nightly | For testing and experimenting | Use the edge or nightly images from [DockerHub](https://hub.docker.com/r/nginx/nginx-ingress/), [GitHub Container](https://github.com/nginxinc/kubernetes-ingress/pkgs/container/kubernetes-ingress), [Amazon ECR Public Gallery](https://gallery.ecr.aws/nginx/nginx-ingress) or [Quay.io](https://quay.io/repository/nginx/nginx-ingress) or [build your own image](https://github.com/nginxinc/kubernetes-ingress/tree/main/docs/content/installation/building-ingress-controller-image.md). | [Build your own image](https://github.com/nginxinc/kubernetes-ingress/tree/main/docs/content/installation/building-ingress-controller-image.md). | [Manifests](https://github.com/nginxinc/kubernetes-ingress/tree/main/deployments). [Helm chart](https://github.com/nginxinc/kubernetes-ingress/tree/main/deployments/helm-chart). | [Documentation](https://github.com/nginxinc/kubernetes-ingress/tree/main/docs/content). [Examples](https://github.com/nginxinc/kubernetes-ingress/tree/main/examples). | +| Latest stable release | For production use | Use the 4.0.0 images from [DockerHub](https://hub.docker.com/r/nginx/nginx-ingress/), [GitHub Container](https://github.com/nginxinc/kubernetes-ingress/pkgs/container/kubernetes-ingress), [Amazon ECR Public Gallery](https://gallery.ecr.aws/nginx/nginx-ingress) or [Quay.io](https://quay.io/repository/nginx/nginx-ingress) or [build your own image](https://docs.nginx.com/nginx-ingress-controller/installation/build-ingress-controller-image/). | Use the 4.0.0 images from the [F5 Container Registry](https://docs.nginx.com/nginx-ingress-controller/installation/pulling-ingress-controller-image/) or [Build your own image](https://docs.nginx.com/nginx-ingress-controller/installation/build-nginx-ingress-controller/). | [Manifests](https://github.com/nginxinc/kubernetes-ingress/tree/v4.0.0/deployments). [Helm chart](https://github.com/nginxinc/kubernetes-ingress/tree/v4.0.0/charts/nginx-ingress). | [Documentation](https://docs.nginx.com/nginx-ingress-controller/). [Examples](https://docs.nginx.com/nginx-ingress-controller/configuration/configuration-examples/). | +| Edge/Nightly | For testing and experimenting | Use the edge or nightly images from [DockerHub](https://hub.docker.com/r/nginx/nginx-ingress/), [GitHub Container](https://github.com/nginxinc/kubernetes-ingress/pkgs/container/kubernetes-ingress), [Amazon ECR Public Gallery](https://gallery.ecr.aws/nginx/nginx-ingress) or [Quay.io](https://quay.io/repository/nginx/nginx-ingress) or [build your own image](https://docs.nginx.com/nginx-ingress-controller/installation/build-nginx-ingress-controller/). | [Build your own image](https://docs.nginx.com/nginx-ingress-controller/installation/build-nginx-ingress-controller/). | [Manifests](https://github.com/nginxinc/kubernetes-ingress/tree/main/deployments). [Helm chart](https://github.com/nginxinc/kubernetes-ingress/tree/main/charts/nginx-ingress). | [Documentation](https://github.com/nginxinc/kubernetes-ingress/tree/main/site/content). [Examples](https://github.com/nginxinc/kubernetes-ingress/tree/main/examples). | + +## SBOM (Software Bill of Materials) + +We generate SBOMs for the binaries and the Docker images. + +### Binaries + +The SBOMs for the binaries are available in the releases page. The SBOMs are generated using +[syft](https://github.com/anchore/syft) and are available in SPDX format. + +### Docker Images + +The SBOMs for the Docker images are available in the [DockerHub](https://hub.docker.com/r/nginx/nginx-ingress/), [GitHub +Container](https://github.com/nginxinc/kubernetes-ingress/pkgs/container/kubernetes-ingress), [Amazon ECR Public +Gallery](https://gallery.ecr.aws/nginx/nginx-ingress) or [Quay.io](https://quay.io/repository/nginx/nginx-ingress) +repositories. The SBOMs are generated using [syft](https://github.com/anchore/syft) and stored as an attestation in the +image manifest. + +For example to retrieve the SBOM for `linux/amd64` from Docker Hub and analyze it using +[grype](https://github.com/anchore/grype) you can run the following command: + +```console +docker buildx imagetools inspect nginx/nginx-ingress:edge --format '{{ json (index .SBOM "linux/amd64").SPDX }}' | grype +``` ## Contacts -We’d like to hear your feedback! If you have any suggestions or experience issues with our Ingress Controller, please create an issue or send a pull request on GitHub. -You can contact us directly via [kubernetes@nginx.com](mailto:kubernetes@nginx.com) or on the [NGINX Community Slack](https://nginxcommunity.slack.com/channels/nginx-ingress-controller). +We’d like to hear your feedback! If you have any suggestions or experience issues with our Ingress Controller, please +create an issue or send a pull request on GitHub. You can contact us directly via [NGINX Community +Slack](https://nginxcommunity.slack.com/channels/nginx-ingress-controller). ## Contributing @@ -82,5 +184,4 @@ If you'd like to contribute to the project, please read our [Contributing guide] ## Support -For NGINX Plus customers NGINX Ingress Controller (when used with NGINX Plus) is covered -by the support contract. +For NGINX Plus customers NGINX Ingress Controller (when used with NGINX Plus) is covered by the support contract. diff --git a/SECURITY.md b/SECURITY.md index 04bd826f70..13b5079085 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -2,15 +2,20 @@ ## Supported Versions -We advise users to run the most recent release of the NGINX Ingress Controller, and we issue software updates to the most recent release. We provide technical support for F5 customers who are using the most recent version of the NGINX Ingress Controller, and any version released within two years of the current release. +We advise users to run the most recent release of NGINX Ingress Controller, and we issue software updates to the +most recent release. We provide technical support for F5 customers who are using the most recent version of the NGINX +Ingress Controller, and any version released within two years of the current release. -For more information visit https://docs.nginx.com/nginx-ingress-controller/technical-specifications/ +For more information visit ## Reporting a Vulnerability -The F5 Security Incident Response Team (F5 SIRT) has an email alias that makes it easy to report potential security vulnerabilities. +The F5 Security Incident Response Team (F5 SIRT) has an email alias that makes it easy to report potential security +vulnerabilities. -- If you’re an F5 customer with an active support contract, please contact [F5 Technical Support](https://www.f5.com/services/support). -- If you aren’t an F5 customer, please report any potential or current instances of security vulnerabilities with any F5 product to the F5 Security Incident Response Team at F5SIRT@f5.com +- If you’re an F5 customer with an active support contract, please contact [F5 Technical + Support](https://www.f5.com/services/support). +- If you aren’t an F5 customer, please report any potential or current instances of security vulnerabilities with any F5 + product to the F5 Security Incident Response Team at -For more information visit https://www.f5.com/services/support/report-a-vulnerability +For more information visit diff --git a/build/Dockerfile b/build/Dockerfile index dd8b94f240..3baac02e5f 100644 --- a/build/Dockerfile +++ b/build/Dockerfile @@ -1,110 +1,317 @@ -# syntax=docker/dockerfile:1.4 +# syntax=docker/dockerfile:1.6 ARG BUILD_OS=debian -ARG NGINX_PLUS_VERSION=R27 +ARG NGINX_PLUS_VERSION=R33 ARG DOWNLOAD_TAG=edge -ARG DEBIAN_VERSION=bullseye-slim +ARG DEBIAN_FRONTEND=noninteractive +ARG PREBUILT_BASE_IMG=nginx/nginx-ingress:${DOWNLOAD_TAG} +ARG NGINX_AGENT=false +ARG IMAGE_NAME=nginx/nginx-ingress +ARG WAF_VERSION=v4 +ARG PACKAGE_REPO=pkgs.nginx.com -############################################# Base images containing libs for Opentracing ############################################# -FROM opentracing/nginx-opentracing:nginx-1.23.2 as opentracing-lib -FROM opentracing/nginx-opentracing:nginx-1.23.2-alpine as alpine-opentracing-lib +############################################# Base images containing libs for Opentracing and FIPS ############################################# +FROM ghcr.io/nginxinc/dependencies/nginx-ot:nginx-1.27.3@sha256:a09090e9f424f206a79a816d37321db2eed349ae3bc20d16bc4cbba32eedfc17 AS opentracing-lib +FROM ghcr.io/nginxinc/dependencies/nginx-ot:nginx-1.27.3-alpine@sha256:339c91471fa9159987aa45ab81f00f147d49709819e207ccc0bc4d434ece2db9 AS alpine-opentracing-lib +FROM ghcr.io/nginxinc/dependencies/nginx-ubi-ppc64le:nginx-1.27.3@sha256:4cda07664f09f16d780d1e803b9748c31489ea21c463bbcca50d9dcf26081a6f AS ubi-ppc64le +FROM ghcr.io/nginxinc/alpine-fips:0.2.3-alpine3.17@sha256:67b69b49aff96e185be841e2b2ff2d8236551ea5c18002bffa4344798d803fd8 AS alpine-fips-3.17 +FROM ghcr.io/nginxinc/alpine-fips:0.2.3-alpine3.20@sha256:4c29e5c50b122354d9d4ba6b97cdf64647468e788b965fc0240ead541653454a AS alpine-fips-3.20 +FROM redhat/ubi9-minimal:9.5@sha256:daa61d6103e98bccf40d7a69a0d4f8786ec390e2204fd94f7cc49053e9949360 AS ubi-minimal +FROM golang:1.23-alpine@sha256:13aaa4b92fd4dc81683816b4b62041442e9f685deeb848897ce78c5e2fb03af7 AS golang-builder + + +############################################# Base image for Alpine ############################################# +FROM nginx:1.27.3-alpine@sha256:4efa432b751239898e576a2178702fb156fc483f6d456e0ad5899b3bf5c0445a AS alpine + +RUN --mount=type=bind,from=alpine-opentracing-lib,target=/tmp/ot/ \ + apk add --no-cache libcap libstdc++ \ + && cp -av /tmp/ot/usr/local/lib/libopentracing.so* /tmp/ot/usr/local/lib/libjaegertracing*so* /tmp/ot/usr/local/lib/libzipkin*so* /tmp/ot/usr/local/lib/libdd*so* /tmp/ot/usr/local/lib/libyaml*so* /usr/local/lib/ \ + && cp -av /tmp/ot/usr/lib/nginx/modules/ngx_http_opentracing_module.so /usr/lib/nginx/modules/ \ + && ldconfig /usr/local/lib/ ############################################# Base image for Debian ############################################# -FROM nginx:1.23.2 AS debian +FROM nginx:1.27.3@sha256:42e917aaa1b5bb40dd0f6f7f4f857490ac7747d7ef73b391c774a41a8b994f15 AS debian RUN --mount=type=bind,from=opentracing-lib,target=/tmp/ot/ \ apt-get update \ && apt-get install --no-install-recommends --no-install-suggests -y libcap2-bin \ - && rm -rf /var/lib/apt/lists/* \ && cp -av /tmp/ot/usr/local/lib/libopentracing.so* /tmp/ot/usr/local/lib/libjaegertracing*so* /tmp/ot/usr/local/lib/libzipkin*so* /tmp/ot/usr/local/lib/libdd*so* /tmp/ot/usr/local/lib/libyaml*so* /usr/local/lib/ \ && cp -av /tmp/ot/usr/lib/nginx/modules/ngx_http_opentracing_module.so /usr/lib/nginx/modules/ \ && ldconfig -############################################# Base image for Alpine ############################################# -FROM nginx:1.23.2-alpine AS alpine +############################################# NGINX files ############################################# +FROM scratch AS nginx-files +ARG IC_VERSION +ARG BUILD_OS +ARG NGINX_PLUS_VERSION +ARG PACKAGE_REPO + +# the following links can be replaced with local files if needed, i.e. ADD --chown=101:0 +ADD --link --chown=101:0 https://cs.nginx.com/static/files/90pkgs-nginx 90pkgs-nginx +ADD --link --chown=101:0 https://cs.nginx.com/static/keys/nginx_signing.key nginx_signing.key +ADD --link --chown=101:0 https://cs.nginx.com/static/keys/nginx_signing.rsa.pub nginx_signing.rsa.pub +ADD --link --chown=101:0 https://cs.nginx.com/static/keys/app-protect-security-updates.key app-protect-security-updates.key +ADD --link --chown=101:0 https://cs.nginx.com/static/keys/app-protect-security-updates.rsa.pub app-protect-security-updates.rsa.pub +ADD --link --chown=101:0 https://cs.nginx.com/static/files/nginx-plus-8.repo nginx-plus-8.repo +ADD --link --chown=101:0 https://cs.nginx.com/static/files/plus-9.repo nginx-plus-9.repo +ADD --link --chown=101:0 https://cs.nginx.com/static/files/app-protect-8.repo app-protect-8.repo +ADD --link --chown=101:0 https://cs.nginx.com/static/files/app-protect-9.repo app-protect-9.repo +ADD --link --chown=101:0 https://raw.githubusercontent.com/nginxinc/k8s-common/main/files/nap-waf-v5-ubi-8.repo app-protect-v5-8.repo +ADD --link --chown=101:0 https://raw.githubusercontent.com/nginxinc/k8s-common/main/files/nap-waf-v5-ubi-9.repo app-protect-v5-9.repo +ADD --link --chown=101:0 https://cs.nginx.com/static/files/app-protect-dos-9.repo app-protect-dos-9.repo +ADD --link --chown=101:0 https://raw.githubusercontent.com/nginxinc/k8s-common/main/files/plus-debian-12.repo debian-plus-12.sources +ADD --link --chown=101:0 https://raw.githubusercontent.com/nginxinc/k8s-common/main/files/nap-waf-debian-12.repo nap-waf-12.sources +ADD --link --chown=101:0 https://raw.githubusercontent.com/nginxinc/k8s-common/main/files/nap-dos-debian-12.repo nap-dos-12.sources +ADD --link --chown=101:0 https://raw.githubusercontent.com/nginxinc/k8s-common/main/files/nap-waf-v5-debian-12.repo nap-waf-v5-12.sources +ADD --link --chown=101:0 https://raw.githubusercontent.com/nginxinc/k8s-common/main/files/agent-debian-12.repo debian-agent-12.sources +ADD --link --chown=101:0 https://cs.nginx.com/static/files/nginx-agent.repo nginx-agent.repo + +RUN --mount=from=busybox:musl,src=/bin/,dst=/bin/ printf "%s\n" "Acquire::https::pkgs.nginx.com::User-Agent k8s-ic-$IC_VERSION${BUILD_OS##debian-plus}-apt;" >> 90pkgs-nginx \ + && if ! grep -q "${PACKAGE_REPO}" 90pkgs-nginx ; then cat 90pkgs-nginx | sed -e "s/pkgs.nginx.com/${PACKAGE_REPO}/g" >> 90pkgs-nginx; fi \ + && printf "%s\n" "user_agent=k8s-ic-$IC_VERSION${BUILD_OS##ubi*plus}-dnf" | tee -a nginx-plus-*.repo \ + && sed -i -e "s;%VERSION%;${NGINX_PLUS_VERSION};g" -e "s;pkgs.nginx.com;${PACKAGE_REPO};g" -e "s;${PACKAGE_REPO}/app-protect-security-updates;pkgs.nginx.com/app-protect-security-updates;g" *.sources \ + && sed -i -e "y/0/1/" app-protect-v5-*.repo \ + && sed -i -e "y/0/1/" -e "1,8s;/centos;/${NGINX_PLUS_VERSION}/centos;" -e "s;pkgs.nginx.com;${PACKAGE_REPO};g" -e "s;${PACKAGE_REPO}/app-protect-security-updates;pkgs.nginx.com/app-protect-security-updates;g" nginx-plus-*.repo app-protect-?.repo app-protect-dos-9.repo \ + && sed -i -e "y/0/1/" -e "s;pkgs.nginx.com;${PACKAGE_REPO};g" nginx-agent.repo app-protect-v5-?.repo \ + && echo HTTP_USER_AGENT="k8s-ic-$IC_VERSION${BUILD_OS##alpine-plus}-apk" > user_agent + +ADD --link --chown=101:0 --chmod=0755 https://raw.githubusercontent.com/nginxinc/k8s-common/main/files/patch-os.sh patch-os.sh +ADD --link --chown=101:0 --chmod=0755 build/scripts/common.sh common.sh +ADD --link --chown=101:0 --chmod=0755 build/scripts/nap-waf.sh nap-waf.sh +ADD --link --chown=101:0 --chmod=0755 build/scripts/nap-dos.sh nap-dos.sh +ADD --link --chown=101:0 --chmod=0755 build/scripts/agent.sh agent.sh +ADD --link --chown=101:0 --chmod=0755 build/scripts/ubi-setup.sh ubi-setup.sh +ADD --link --chown=101:0 --chmod=0755 build/scripts/ubi-clean.sh ubi-clean.sh + + +############################################# Patch Image ############################################# +FROM ${IMAGE_NAME} AS patched +ARG IMAGE_NAME +ARG IC_VERSION -RUN --mount=type=bind,from=alpine-opentracing-lib,target=/tmp/ot/ \ - apk add --no-cache libcap libstdc++ \ - && cp -av /tmp/ot/usr/local/lib/libopentracing.so* /tmp/ot/usr/local/lib/libjaegertracing*so* /tmp/ot/usr/local/lib/libzipkin*so* /tmp/ot/usr/local/lib/libdd*so* /tmp/ot/usr/local/lib/libyaml*so* /usr/local/lib/ \ - && cp -av /tmp/ot/usr/lib/nginx/modules/ngx_http_opentracing_module.so /usr/lib/nginx/modules/ \ - && ldconfig /usr/local/lib/ +LABEL version="${IC_VERSION}" \ + org.opencontainers.image.version="${IC_VERSION}" +USER 0 +RUN --mount=type=bind,from=nginx-files,src=patch-os.sh,target=/usr/local/bin/patch-os.sh \ + if [ -f /etc/apk/repositories ]; then sed -i -e '/nginx.com/d' /etc/apk/repositories; fi \ + && patch-os.sh +USER 101 -############################################# Base image for Alpine with NGINX Plus ############################################# -FROM alpine:3.16 as alpine-plus + +############################################# Base image for Alpine with NGINX Plus ############################################## +FROM alpine:3.20@sha256:780405de0f7cf99f985dd5a4f04dfc5aae71509d89505c1ba48a88d95a0ceb7f AS alpine-plus ARG NGINX_PLUS_VERSION +ARG PACKAGE_REPO + +ENV NGINX_VERSION=${NGINX_PLUS_VERSION} RUN --mount=type=secret,id=nginx-repo.crt,dst=/etc/apk/cert.pem,mode=0644 \ --mount=type=secret,id=nginx-repo.key,dst=/etc/apk/cert.key,mode=0644 \ --mount=type=bind,from=alpine-opentracing-lib,target=/tmp/ot/ \ - wget -nv -O /etc/apk/keys/nginx_signing.rsa.pub https://cs.nginx.com/static/keys/nginx_signing.rsa.pub \ - && printf "%s\n" "https://pkgs.nginx.com/plus/${NGINX_PLUS_VERSION}/alpine/v$(grep -E -o '^[0-9]+\.[0-9]+' /etc/alpine-release)/main" >> /etc/apk/repositories \ - && apk add --no-cache libcap nginx-plus nginx-plus-module-njs nginx-plus-module-opentracing libcurl \ + --mount=type=bind,from=nginx-files,src=nginx_signing.rsa.pub,target=/etc/apk/keys/nginx_signing.rsa.pub \ + --mount=type=bind,from=nginx-files,src=user_agent,target=/tmp/user_agent \ + export $(cat /tmp/user_agent) \ + && printf "%s\n" "https://${PACKAGE_REPO}/plus/${NGINX_PLUS_VERSION}/alpine/v$(grep -E -o '^[0-9]+\.[0-9]+' /etc/alpine-release)/main" >> /etc/apk/repositories \ + && apk add --no-cache nginx-plus nginx-plus-module-njs nginx-plus-module-opentracing nginx-plus-module-fips-check libcap libcurl \ && cp -av /tmp/ot/usr/local/lib/libjaegertracing*so* /tmp/ot/usr/local/lib/libzipkin*so* /tmp/ot/usr/local/lib/libdd*so* /tmp/ot/usr/local/lib/libyaml*so* /usr/local/lib/ \ - && ldconfig /usr/local/lib/ + && ldconfig /usr/local/lib/ \ + && sed -i -e '/nginx.com/d' /etc/apk/repositories + + +############################################# Base image for Alpine with NGINX Plus and FIPS ############################################# +FROM alpine-plus AS alpine-plus-fips +ARG NGINX_PLUS_VERSION + +ENV NGINX_VERSION=${NGINX_PLUS_VERSION} + +RUN --mount=type=bind,from=alpine-fips-3.20,target=/tmp/fips/ \ + mkdir -p /usr/ssl \ + && cp -av /tmp/fips/usr/lib/ossl-modules/fips.so /usr/lib/ossl-modules/fips.so \ + && cp -av /tmp/fips/usr/ssl/fipsmodule.cnf /usr/ssl/fipsmodule.cnf \ + && cp -av /tmp/fips/etc/ssl/openssl.cnf /etc/ssl/openssl.cnf + + +############################################# Base image for Alpine with NGINX Plus, App Protect WAF and FIPS ############################################# +FROM alpine:3.17@sha256:8fc3dacfb6d69da8d44e42390de777e48577085db99aa4e4af35f483eb08b989 AS alpine-plus-nap-fips +ARG NGINX_PLUS_VERSION +ARG NGINX_AGENT +ARG NGINX_PLUS_VERSION +ARG PACKAGE_REPO + +ENV NGINX_VERSION=${NGINX_PLUS_VERSION} + +RUN --mount=type=bind,from=alpine-fips-3.17,target=/tmp/fips/ \ + --mount=type=secret,id=nginx-repo.crt,dst=/etc/apk/cert.pem,mode=0644 \ + --mount=type=secret,id=nginx-repo.key,dst=/etc/apk/cert.key,mode=0644 \ + --mount=type=bind,from=alpine-opentracing-lib,target=/tmp/ot/ \ + --mount=type=bind,from=nginx-files,src=app-protect-security-updates.rsa.pub,target=/etc/apk/keys/app-protect-security-updates.rsa.pub \ + --mount=type=bind,from=nginx-files,src=nginx_signing.rsa.pub,target=/etc/apk/keys/nginx_signing.rsa.pub \ + --mount=type=bind,from=nginx-files,src=agent.sh,target=/usr/local/bin/agent.sh \ + --mount=type=bind,from=nginx-files,src=nap-waf.sh,target=/usr/local/bin/nap-waf.sh \ + printf "%s\n" "https://${PACKAGE_REPO}/plus/${NGINX_PLUS_VERSION}/alpine/v$(grep -E -o '^[0-9]+\.[0-9]+' /etc/alpine-release)/main" >> /etc/apk/repositories \ + && printf "%s\n" "https://${PACKAGE_REPO}/app-protect/${NGINX_PLUS_VERSION}/alpine/v$(grep -E -o '^[0-9]+\.[0-9]+' /etc/alpine-release)/main" >> /etc/apk/repositories \ + && printf "%s\n" "https://pkgs.nginx.com/app-protect-security-updates/alpine/v$(grep -E -o '^[0-9]+\.[0-9]+' /etc/alpine-release)/main" >> /etc/apk/repositories \ + && printf "%s\n" "https://${PACKAGE_REPO}/nginx-agent/alpine/v$(grep -E -o '^[0-9]+\.[0-9]+' /etc/alpine-release)/main" >> /etc/apk/repositories \ + && apk add --no-cache libcap-utils libcurl nginx-plus nginx-plus-module-njs nginx-plus-module-opentracing nginx-plus-module-fips-check \ + && if [ "${NGINX_AGENT}" = "true" ]; then apk add --no-cache nginx-agent; fi \ + && mkdir -p /usr/ssl \ + && cp -av /tmp/fips/usr/lib/ossl-modules/fips.so /usr/lib/ossl-modules/fips.so \ + && cp -av /tmp/fips/usr/ssl/fipsmodule.cnf /usr/ssl/fipsmodule.cnf \ + && cp -av /tmp/fips/etc/ssl/openssl.cnf /etc/ssl/openssl.cnf \ + && cp -av /tmp/ot/usr/local/lib/libjaegertracing*so* /tmp/ot/usr/local/lib/libzipkin*so* /tmp/ot/usr/local/lib/libdd*so* /tmp/ot/usr/local/lib/libyaml*so* /usr/local/lib/ \ + && ldconfig /usr/local/lib/ \ + && apk add --no-cache app-protect app-protect-attack-signatures app-protect-threat-campaigns \ + && sed -i -e '/nginx.com/d' /etc/apk/repositories \ + && nap-waf.sh \ + && if [ "${NGINX_AGENT}" = "true" ]; then \ + agent.sh \ + ; fi + + +############################################# Base image for Alpine with NGINX Plus, App Protect WAFv5 and FIPS ############################################# +FROM alpine:3.17@sha256:8fc3dacfb6d69da8d44e42390de777e48577085db99aa4e4af35f483eb08b989 AS alpine-plus-nap-v5-fips +ARG NGINX_PLUS_VERSION +ARG NGINX_AGENT +ARG NGINX_PLUS_VERSION +ARG PACKAGE_REPO + +ENV NGINX_VERSION=${NGINX_PLUS_VERSION} + +RUN --mount=type=bind,from=alpine-fips-3.17,target=/tmp/fips/ \ + --mount=type=secret,id=nginx-repo.crt,dst=/etc/apk/cert.pem,mode=0644 \ + --mount=type=secret,id=nginx-repo.key,dst=/etc/apk/cert.key,mode=0644 \ + --mount=type=bind,from=alpine-opentracing-lib,target=/tmp/ot/ \ + --mount=type=bind,from=nginx-files,src=nginx_signing.rsa.pub,target=/etc/apk/keys/nginx_signing.rsa.pub \ + --mount=type=bind,from=nginx-files,src=agent.sh,target=/usr/local/bin/agent.sh \ + --mount=type=bind,from=nginx-files,src=nap-waf.sh,target=/usr/local/bin/nap-waf.sh \ + printf "%s\n" "https://${PACKAGE_REPO}/plus/${NGINX_PLUS_VERSION}/alpine/v$(grep -E -o '^[0-9]+\.[0-9]+' /etc/alpine-release)/main" >> /etc/apk/repositories \ + && printf "%s\n" "https://${PACKAGE_REPO}/app-protect-x-plus/alpine/v$(grep -E -o '^[0-9]+\.[0-9]+' /etc/alpine-release)/main" >> /etc/apk/repositories \ + && printf "%s\n" "https://${PACKAGE_REPO}/nginx-agent/alpine/v$(grep -E -o '^[0-9]+\.[0-9]+' /etc/alpine-release)/main" >> /etc/apk/repositories \ + && apk add --no-cache libcap-utils libcurl nginx-plus nginx-plus-module-njs nginx-plus-module-opentracing nginx-plus-module-fips-check \ + && if [ "${NGINX_AGENT}" = "true" ]; then apk add --no-cache nginx-agent; fi \ + && mkdir -p /usr/ssl \ + && cp -av /tmp/fips/usr/lib/ossl-modules/fips.so /usr/lib/ossl-modules/fips.so \ + && cp -av /tmp/fips/usr/ssl/fipsmodule.cnf /usr/ssl/fipsmodule.cnf \ + && cp -av /tmp/fips/etc/ssl/openssl.cnf /etc/ssl/openssl.cnf \ + && cp -av /tmp/ot/usr/local/lib/libjaegertracing*so* /tmp/ot/usr/local/lib/libzipkin*so* /tmp/ot/usr/local/lib/libdd*so* /tmp/ot/usr/local/lib/libyaml*so* /usr/local/lib/ \ + && ldconfig /usr/local/lib/ \ + && apk add --no-cache app-protect-module-plus~=33.5.210 \ + && sed -i -e '/nginx.com/d' /etc/apk/repositories \ + && nap-waf.sh \ + && if [ "${NGINX_AGENT}" = "true" ]; then \ + agent.sh \ + ; fi ############################################# Base image for Debian with NGINX Plus ############################################# -FROM debian:${DEBIAN_VERSION} AS debian-plus -ARG IC_VERSION +FROM debian:12-slim@sha256:d365f4920711a9074c4bcd178e8f457ee59250426441ab2a5f8106ed8fe948eb AS debian-plus ARG NGINX_PLUS_VERSION -ARG BUILD_OS + +ENV NGINX_VERSION=${NGINX_PLUS_VERSION} SHELL ["/bin/bash", "-o", "pipefail", "-c"] RUN --mount=type=secret,id=nginx-repo.crt,dst=/etc/ssl/nginx/nginx-repo.crt,mode=0644 \ --mount=type=secret,id=nginx-repo.key,dst=/etc/ssl/nginx/nginx-repo.key,mode=0644 \ --mount=type=bind,from=opentracing-lib,target=/tmp/ot/ \ + --mount=type=bind,from=nginx-files,src=nginx_signing.key,target=/tmp/nginx_signing.key \ + --mount=type=bind,from=nginx-files,src=app-protect-security-updates.key,target=/tmp/app-protect-security-updates.key \ + --mount=type=bind,from=nginx-files,src=90pkgs-nginx,target=/etc/apt/apt.conf.d/90pkgs-nginx \ + --mount=type=bind,from=nginx-files,src=debian-plus-12.sources,target=/tmp/nginx-plus.sources \ apt-get update \ - && apt-get install --no-install-recommends --no-install-suggests -y ca-certificates gnupg curl apt-transport-https libcap2-bin \ - && curl -fsSL https://cs.nginx.com/static/keys/nginx_signing.key | gpg --dearmor > /etc/apt/trusted.gpg.d/nginx_signing.gpg \ - && curl -fsSL -o /etc/apt/apt.conf.d/90pkgs-nginx https://cs.nginx.com/static/files/90pkgs-nginx \ - && DEBIAN_VERSION=$(awk -F '=' '/^VERSION_CODENAME=/ {print $2}' /etc/os-release) \ - && printf "%s\n" "Acquire::https::pkgs.nginx.com::User-Agent \"k8s-ic-$IC_VERSION${BUILD_OS##debian-plus}-apt\";" >> /etc/apt/apt.conf.d/90pkgs-nginx \ - && printf "%s\n" "deb https://pkgs.nginx.com/plus/${NGINX_PLUS_VERSION}/debian ${DEBIAN_VERSION} nginx-plus" > /etc/apt/sources.list.d/nginx-plus.list \ + && apt-get install --no-install-recommends --no-install-suggests -y gpg ca-certificates libcap2-bin libcurl4 \ + && groupadd --system --gid 101 nginx \ + && useradd --system --gid nginx --no-create-home --home-dir /nonexistent --comment "nginx user" --shell /bin/false --uid 101 nginx \ + && gpg --dearmor -o /usr/share/keyrings/nginx-archive-keyring.gpg /tmp/nginx_signing.key \ + && gpg --dearmor -o /usr/share/keyrings/app-protect-archive-keyring.gpg /tmp/app-protect-security-updates.key \ + && cp /tmp/nginx-plus.sources /etc/apt/sources.list.d/nginx-plus.sources \ && apt-get update \ - && apt-get install --no-install-recommends --no-install-suggests -y nginx-plus nginx-plus-module-njs nginx-plus-module-opentracing libcurl4 \ - && apt-get purge --auto-remove -y apt-transport-https gnupg curl \ + && apt-get install --no-install-recommends --no-install-suggests -y nginx-plus nginx-plus-module-njs nginx-plus-module-opentracing nginx-plus-module-fips-check \ + && apt-get purge --auto-remove -y gpg \ && cp -av /tmp/ot/usr/local/lib/libjaegertracing*so* /tmp/ot/usr/local/lib/libzipkin*so* /tmp/ot/usr/local/lib/libdd*so* /tmp/ot/usr/local/lib/libyaml*so* /usr/local/lib/ \ && ldconfig \ - && rm -rf /var/lib/apt/lists/* + && rm -rf /var/lib/apt/lists/* /etc/apt/sources.list.d/nginx-plus.sources ############################################# Base image for Debian with NGINX Plus and App Protect WAF/DoS ############################################# -FROM debian-plus as debian-plus-nap -ARG NGINX_PLUS_VERSION +FROM debian-plus AS debian-plus-nap ARG NAP_MODULES +ARG NGINX_AGENT +ARG NGINX_PLUS_VERSION + +ENV NGINX_VERSION=${NGINX_PLUS_VERSION} RUN --mount=type=secret,id=nginx-repo.crt,dst=/etc/ssl/nginx/nginx-repo.crt,mode=0644 \ --mount=type=secret,id=nginx-repo.key,dst=/etc/ssl/nginx/nginx-repo.key,mode=0644 \ - apt-get update \ - && apt-get install --no-install-recommends --no-install-suggests -y gnupg curl apt-transport-https \ - && DEBIAN_VERSION=$(awk -F '=' '/^VERSION_CODENAME=/ {print $2}' /etc/os-release) \ - && if [ -z "${NAP_MODULES##*waf*}" ]; then \ - curl -fsSL https://cs.nginx.com/static/keys/app-protect-security-updates.key | gpg --dearmor > /etc/apt/trusted.gpg.d/nginx_app_signing.gpg \ - && printf "%s\n" "deb https://pkgs.nginx.com/app-protect/${NGINX_PLUS_VERSION}/debian ${DEBIAN_VERSION} nginx-plus" \ - "deb https://pkgs.nginx.com/app-protect-security-updates/debian ${DEBIAN_VERSION} nginx-plus" > /etc/apt/sources.list.d/nginx-app-protect.list \ - && apt-get update \ - && apt-get install --no-install-recommends --no-install-suggests -y app-protect app-protect-attack-signatures app-protect-threat-campaigns \ - && apt-get purge --auto-remove -y curl; \ + --mount=type=bind,from=opentracing-lib,target=/tmp/ot/ \ + --mount=type=bind,from=nginx-files,src=nginx_signing.key,target=/tmp/nginx_signing.key \ + --mount=type=bind,from=nginx-files,src=90pkgs-nginx,target=/etc/apt/apt.conf.d/90pkgs-nginx \ + --mount=type=bind,from=nginx-files,src=nap-waf-12.sources,target=/tmp/app-protect.sources \ + --mount=type=bind,from=nginx-files,src=nap-dos-12.sources,target=/tmp/app-protect-dos.sources \ + --mount=type=bind,from=nginx-files,src=debian-agent-12.sources,target=/etc/apt/sources.list.d/nginx-agent.sources \ + --mount=type=bind,from=nginx-files,src=agent.sh,target=/usr/local/bin/agent.sh \ + --mount=type=bind,from=nginx-files,src=nap-waf.sh,target=/usr/local/bin/nap-waf.sh \ + --mount=type=bind,from=nginx-files,src=nap-dos.sh,target=/usr/local/bin/nap-dos.sh \ + if [ -z "${NAP_MODULES##*waf*}" ]; then \ + cp /tmp/app-protect.sources /etc/apt/sources.list.d/app-protect.sources; \ fi \ && if [ -z "${NAP_MODULES##*dos*}" ]; then \ - printf "%s\n" "deb https://pkgs.nginx.com/app-protect-dos/${NGINX_PLUS_VERSION}/debian ${DEBIAN_VERSION} nginx-plus" > /etc/apt/sources.list.d/nginx-app-protect-dos.list \ + cp /tmp/app-protect-dos.sources /etc/apt/sources.list.d/app-protect-dos.sources; \ + fi \ && apt-get update \ - && apt-get install --no-install-recommends --no-install-suggests -y app-protect-dos; \ + && if [ "${NGINX_AGENT}" = "true" ]; then apt-get install --no-install-recommends --no-install-suggests -y nginx-agent; fi \ + && if [ -z "${NAP_MODULES##*waf*}" ]; then \ + apt-get install --no-install-recommends --no-install-suggests -y app-protect app-protect-attack-signatures app-protect-threat-campaigns; \ + fi \ + && if [ -z "${NAP_MODULES##*dos*}" ]; then \ + apt-get install --no-install-recommends --no-install-suggests -y app-protect-dos; \ + fi \ + && if [ -z "${NAP_MODULES##*waf*}" ]; then \ + rm -f /etc/apt/sources.list.d/app-protect.sources; \ + fi \ + && if [ -z "${NAP_MODULES##*dos*}" ]; then \ + rm -f /etc/apt/sources.list.d/app-protect-dos.sources; \ fi \ - && apt-get purge --auto-remove -y apt-transport-https gnupg \ && rm -rf /var/lib/apt/lists/* \ - && rm /etc/apt/sources.list.d/nginx-app-protect*.list + && if [ -z "${NAP_MODULES##*waf*}" ]; then nap-waf.sh; fi \ + && if [ "${NGINX_AGENT}" = "true" ]; then agent.sh; fi \ + && if [ -z "${NAP_MODULES##*dos*}" ]; then nap-dos.sh; fi -# Uncomment the lines below if you want to install a custom CA certificate -# COPY build/*.crt /usr/local/share/ca-certificates/ -# RUN update-ca-certificates +############################################# Base image for Debian with NGINX Plus and App Protect WAFv5 ############################################# +FROM debian-plus AS debian-plus-nap-v5 +ARG NAP_MODULES +ARG NGINX_AGENT +ARG NGINX_PLUS_VERSION + +ENV NGINX_VERSION=${NGINX_PLUS_VERSION} + +RUN --mount=type=secret,id=nginx-repo.crt,dst=/etc/ssl/nginx/nginx-repo.crt,mode=0644 \ + --mount=type=secret,id=nginx-repo.key,dst=/etc/ssl/nginx/nginx-repo.key,mode=0644 \ + --mount=type=bind,from=nginx-files,src=90pkgs-nginx,target=/etc/apt/apt.conf.d/90pkgs-nginx \ + --mount=type=bind,from=nginx-files,src=nap-waf-v5-12.sources,target=/tmp/app-protect.sources \ + --mount=type=bind,from=nginx-files,src=agent.sh,target=/usr/local/bin/agent.sh \ + --mount=type=bind,from=nginx-files,src=nap-waf.sh,target=/usr/local/bin/nap-waf.sh \ + --mount=type=bind,from=nginx-files,src=debian-agent-12.sources,target=/etc/apt/sources.list.d/nginx-agent.sources \ + if [ -z "${NAP_MODULES##*waf*}" ]; then \ + cp /tmp/app-protect.sources /etc/apt/sources.list.d/app-protect.sources; \ + fi \ + && apt-get update \ + && if [ "${NGINX_AGENT}" = "true" ]; then apt-get install --no-install-recommends --no-install-suggests -y nginx-agent; fi \ + && if [ -z "${NAP_MODULES##*waf*}" ]; then \ + apt-get install --no-install-recommends --no-install-suggests -y app-protect-module-plus=33+5.210*; \ + rm -f /etc/apt/sources.list.d/app-protect.sources; \ + nap-waf.sh; \ + fi \ + && apt-get purge --auto-remove -y gpg \ + && if [ "${NGINX_AGENT}" = "true" ]; then \ + agent.sh; \ + fi ############################################# Base image for UBI ############################################# -FROM nginxcontrib/nginx:1.23.2-ubi AS ubi +FROM ubi-minimal AS ubi ARG IC_VERSION LABEL name="NGINX Ingress Controller" \ @@ -114,63 +321,211 @@ LABEL name="NGINX Ingress Controller" \ release="1" \ summary="The Ingress Controller is an application that runs in a cluster and configures an HTTP load balancer according to Ingress resources." \ description="The Ingress Controller is an application that runs in a cluster and configures an HTTP load balancer according to Ingress resources." \ - io.k8s.description="The NGINX Ingress Controller is an application that runs in a cluster and configures an HTTP load balancer according to Ingress resources." \ + io.k8s.description="NGINX Ingress Controller is an application that runs in a cluster and configures an HTTP load balancer according to Ingress resources." \ io.openshift.tags="nginx,ingress-controller,ingress,controller,kubernetes,openshift" COPY --link --chown=101:0 LICENSE /licenses/ -# temp fix for CVE-2022-27404, CVE-2022-33099 and CVE-2022-37434 -RUN microdnf --nodocs upgrade -y freetype lua-libs zlib +SHELL ["/bin/bash", "-o", "pipefail", "-c"] +RUN --mount=type=bind,from=nginx-files,src=nginx_signing.key,target=/tmp/nginx_signing.key \ + --mount=type=bind,from=nginx-files,src=ubi-setup.sh,target=/usr/local/bin/ubi-setup.sh \ + --mount=type=bind,from=nginx-files,src=ubi-clean.sh,target=/usr/local/bin/ubi-clean.sh \ + --mount=type=bind,from=ubi-ppc64le,src=/,target=/ubi-bin/ \ + ubi-setup.sh; \ + if [ $(uname -p) = ppc64le ] || [ $(uname -p) = s390x ]; then \ + rpm -qa --queryformat "%{NAME}\n" | sort > pkgs-installed \ + && microdnf --nodocs --setopt=install_weak_deps=0 install -y diffutils dnf \ + && rpm -qa --queryformat "%{NAME}\n" | sort > pkgs-new \ + && dnf install -y /ubi-bin/*.rpm \ + && dnf -q repoquery --resolve --requires --recursive --whatrequires nginx --queryformat "%{NAME}" > pkgs-nginx \ + && dnf --setopt=protected_packages= remove -y $(comm -13 pkgs-installed pkgs-new | comm -13 pkgs-nginx -) \ + && rm pkgs-installed pkgs-new pkgs-nginx; \ + else \ + printf "%s\n" "[nginx]" "name=nginx repo" \ + "baseurl=https://nginx.org/packages/mainline/centos/9/\$basearch/" \ + "gpgcheck=1" "enabled=1" "module_hotfixes=true" > /etc/yum.repos.d/nginx.repo \ + && microdnf --nodocs install -y nginx nginx-module-njs nginx-module-image-filter nginx-module-xslt \ + && rm /etc/yum.repos.d/nginx.repo; \ + fi \ + && ubi-clean.sh ############################################# Base image for UBI with NGINX Plus ############################################# -FROM redhat/ubi8:8.6-990 AS ubi-plus +FROM ubi-minimal AS ubi-9-plus ARG NGINX_PLUS_VERSION +ENV NGINX_VERSION=${NGINX_PLUS_VERSION} + SHELL ["/bin/bash", "-o", "pipefail", "-c"] RUN --mount=type=secret,id=nginx-repo.crt,dst=/etc/ssl/nginx/nginx-repo.crt,mode=0644 \ --mount=type=secret,id=nginx-repo.key,dst=/etc/ssl/nginx/nginx-repo.key,mode=0644 \ - dnf --nodocs install -y shadow-utils ca-certificates \ - # temp fix for CVE-2022-1304 and CVE-2016-3709 - && dnf --nodocs install -y libcom_err libxml2 \ - && groupadd --system --gid 101 nginx \ - && useradd --system --gid nginx --no-create-home --home-dir /nonexistent --comment "nginx user" --shell /bin/false --uid 101 nginx \ - && rpm --import https://cs.nginx.com/static/keys/nginx_signing.key \ - && curl -fsSL "https://cs.nginx.com/static/files/nginx-plus-$(grep -E -o '[0-9]+\.[0-9]+' /etc/redhat-release | cut -d"." -f1).repo" | tr 0 1 > /etc/yum.repos.d/nginx-plus.repo \ - && sed -i "0,/centos/s;;${NGINX_PLUS_VERSION}/centos;" /etc/yum.repos.d/nginx-plus.repo \ - && dnf --nodocs install -y nginx-plus nginx-plus-module-njs + --mount=type=bind,from=nginx-files,src=nginx_signing.key,target=/tmp/nginx_signing.key \ + --mount=type=bind,from=nginx-files,src=nginx-plus-9.repo,target=/etc/yum.repos.d/nginx-plus.repo \ + --mount=type=bind,from=nginx-files,src=ubi-setup.sh,target=/usr/local/bin/ubi-setup.sh \ + --mount=type=bind,from=nginx-files,src=ubi-clean.sh,target=/usr/local/bin/ubi-clean.sh \ + ubi-setup.sh \ + && microdnf --nodocs install -y nginx-plus nginx-plus-module-njs nginx-plus-module-fips-check \ + && ubi-clean.sh -############################################# Base image for UBI with NGINX Plus and App Protect WAF/DoS ############################################# -FROM ubi-plus as ubi-plus-nap -ARG NGINX_PLUS_VERSION +############################################# Base image for UBI with NGINX Plus and App Protect WAF & DoS ############################################# +FROM ubi-9-plus AS ubi-9-plus-nap ARG NAP_MODULES +ARG NGINX_AGENT RUN --mount=type=secret,id=nginx-repo.crt,dst=/etc/ssl/nginx/nginx-repo.crt,mode=0644 \ --mount=type=secret,id=nginx-repo.key,dst=/etc/ssl/nginx/nginx-repo.key,mode=0644 \ --mount=type=secret,id=rhel_license,dst=/tmp/rhel_license,mode=0644 \ + --mount=type=bind,from=nginx-files,src=nginx_signing.key,target=/tmp/nginx_signing.key \ + --mount=type=bind,from=nginx-files,src=nginx-agent.repo,target=/etc/yum.repos.d/nginx-agent.repo,rw \ + --mount=type=bind,from=nginx-files,src=app-protect-security-updates.key,target=/tmp/app-protect-security-updates.key \ + --mount=type=bind,from=nginx-files,src=app-protect-9.repo,target=/tmp/app-protect-9.repo \ + --mount=type=bind,from=nginx-files,src=app-protect-dos-9.repo,target=/tmp/app-protect-dos-9.repo \ + --mount=type=bind,from=nginx-files,src=agent.sh,target=/usr/local/bin/agent.sh \ + --mount=type=bind,from=nginx-files,src=nap-waf.sh,target=/usr/local/bin/nap-waf.sh \ + --mount=type=bind,from=nginx-files,src=nap-dos.sh,target=/usr/local/bin/nap-dos.sh \ + --mount=type=bind,from=nginx-files,src=ubi-clean.sh,target=/usr/local/bin/ubi-clean.sh \ source /tmp/rhel_license \ + && rpm -ivh https://dl.fedoraproject.org/pub/epel/epel-release-latest-9.noarch.rpm \ + && microdnf --nodocs install -y ca-certificates shadow-utils subscription-manager \ + && if [ "${NGINX_AGENT}" = "true" ]; then microdnf --nodocs install -y nginx-agent; fi \ + && subscription-manager register --org=${RHEL_ORGANIZATION} --activationkey=${RHEL_ACTIVATION_KEY} || true \ + && subscription-manager attach \ + && rpm --import /tmp/app-protect-security-updates.key \ + && if [ -z "${NAP_MODULES##*waf*}" ]; then \ + cp /tmp/app-protect-9.repo /etc/yum.repos.d/app-protect-9.repo \ + && microdnf --enablerepo=codeready-builder-for-rhel-9-x86_64-rpms --nodocs install -y \ + app-protect app-protect-attack-signatures app-protect-threat-campaigns \ + && rm -f /etc/yum.repos.d/app-protect-9.repo \ + && nap-waf.sh; \ + fi \ + && if [ -z "${NAP_MODULES##*dos*}" ]; then \ + cp /tmp/app-protect-dos-9.repo /etc/yum.repos.d/app-protect-dos-9.repo \ + && microdnf --nodocs install -y app-protect-dos \ + && rm -f /etc/yum.repos.d/app-protect-dos-9.repo \ + && nap-dos.sh; \ + fi \ + && subscription-manager unregister \ + && ubi-clean.sh \ + && if [ "${NGINX_AGENT}" = "true" ]; then agent.sh; fi + + +############################################# Base image for UBI with NGINX Plus and App Protect WAFv5 ############################################# +FROM ubi-9-plus AS ubi-9-plus-nap-v5 +ARG NAP_MODULES +ARG NGINX_AGENT + +RUN --mount=type=secret,id=nginx-repo.crt,dst=/etc/ssl/nginx/nginx-repo.crt,mode=0644 \ + --mount=type=secret,id=nginx-repo.key,dst=/etc/ssl/nginx/nginx-repo.key,mode=0644 \ + --mount=type=secret,id=rhel_license,dst=/tmp/rhel_license,mode=0644 \ + --mount=type=bind,from=nginx-files,src=nginx_signing.key,target=/tmp/nginx_signing.key \ + --mount=type=bind,from=nginx-files,src=nginx-agent.repo,target=/etc/yum.repos.d/nginx-agent.repo,rw \ + --mount=type=bind,from=nginx-files,src=app-protect-v5-9.repo,target=/tmp/app-protect-9.repo \ + --mount=type=bind,from=nginx-files,src=agent.sh,target=/usr/local/bin/agent.sh \ + --mount=type=bind,from=nginx-files,src=nap-waf.sh,target=/usr/local/bin/nap-waf.sh \ + --mount=type=bind,from=nginx-files,src=ubi-clean.sh,target=/usr/local/bin/ubi-clean.sh \ + source /tmp/rhel_license \ + && rpm -ivh https://dl.fedoraproject.org/pub/epel/epel-release-latest-9.noarch.rpm \ + && microdnf --nodocs install -y ca-certificates shadow-utils subscription-manager \ + && if [ "${NGINX_AGENT}" = "true" ]; then microdnf --nodocs install -y nginx-agent; fi \ + && if [ -z "${NAP_MODULES##*waf*}" ]; then \ + cp /tmp/app-protect-9.repo /etc/yum.repos.d/app-protect-9.repo \ + && microdnf --nodocs install -y app-protect-module-plus-33+5.210* \ + && nap-waf.sh \ + && rm -f /etc/yum.repos.d/app-protect-9.repo; \ + fi \ + && ubi-clean.sh \ + && if [ "${NGINX_AGENT}" = "true" ]; then agent.sh; fi + + +############################################# Base image for UBI8 with NGINX Plus and App Protect WAF ############################################# +FROM redhat/ubi8@sha256:37cdac4ec130a64050d6df4e1f2ef3f53868bea55d11f623d141f139ee342bd8 AS ubi-8-plus-nap +ARG NAP_MODULES +ARG NGINX_AGENT +ARG NGINX_PLUS_VERSION + +ENV NGINX_VERSION=${NGINX_PLUS_VERSION} + +RUN --mount=type=secret,id=nginx-repo.crt,dst=/etc/ssl/nginx/nginx-repo.crt,mode=0644 \ + --mount=type=secret,id=nginx-repo.key,dst=/etc/ssl/nginx/nginx-repo.key,mode=0644 \ + --mount=type=secret,id=rhel_license,dst=/tmp/rhel_license,mode=0644 \ + --mount=type=bind,from=nginx-files,src=nginx_signing.key,target=/tmp/nginx_signing.key \ + --mount=type=bind,from=nginx-files,src=nginx-plus-8.repo,target=/etc/yum.repos.d/nginx-plus.repo,rw \ + --mount=type=bind,from=nginx-files,src=nginx-agent.repo,target=/etc/yum.repos.d/nginx-agent.repo,rw \ + --mount=type=bind,from=nginx-files,src=app-protect-security-updates.key,target=/tmp/app-protect-security-updates.key \ + --mount=type=bind,from=nginx-files,src=app-protect-8.repo,target=/tmp/app-protect-8.repo \ + --mount=type=bind,from=nginx-files,src=nap-waf.sh,target=/usr/local/bin/nap-waf.sh \ + --mount=type=bind,from=nginx-files,src=agent.sh,target=/usr/local/bin/agent.sh \ + source /tmp/rhel_license \ + && if [ -z "${NAP_MODULES##*waf*}" ]; then \ + cp /tmp/app-protect-8.repo /etc/yum.repos.d/app-protect-8.repo; \ + fi \ + && groupadd --system --gid 101 nginx \ + && useradd --system --gid nginx --no-create-home --home-dir /nonexistent --comment "nginx user" --shell /bin/false --uid 101 nginx \ + && rpm --import /tmp/nginx_signing.key \ + && dnf --nodocs install -y nginx-plus nginx-plus-module-njs nginx-plus-module-fips-check \ + && if [ "${NGINX_AGENT}" = "true" ]; then dnf --nodocs install -y nginx-agent; fi \ + && sed -i 's/\(def in_container():\)/\1\n return False/g' /usr/lib64/python*/*-packages/rhsm/config.py \ && subscription-manager register --org=${RHEL_ORGANIZATION} --activationkey=${RHEL_ACTIVATION_KEY} || true \ && subscription-manager attach \ && dnf config-manager --set-enabled codeready-builder-for-rhel-8-x86_64-rpms \ && dnf --nodocs install -y https://dl.fedoraproject.org/pub/epel/epel-release-latest-8.noarch.rpm \ + && rpm --import /tmp/app-protect-security-updates.key \ && if [ -z "${NAP_MODULES##*waf*}" ]; then \ - curl -fsSL https://cs.nginx.com/static/files/app-protect-8.repo > /etc/yum.repos.d/app-protect-8.repo; \ - sed -i "0,/centos/s;;${NGINX_PLUS_VERSION}/centos;" /etc/yum.repos.d/app-protect-8.repo; \ dnf --nodocs install -y app-protect app-protect-attack-signatures app-protect-threat-campaigns; \ fi \ - && if [ -z "${NAP_MODULES##*dos*}" ]; then \ - curl -fsSL https://cs.nginx.com/static/files/app-protect-dos-8.repo > /etc/yum.repos.d/app-protect-dos-8.repo; \ - sed -i "0,/centos/s;;${NGINX_PLUS_VERSION}/centos;" /etc/yum.repos.d/app-protect-dos-8.repo; \ - dnf --nodocs install -y app-protect-dos; \ - fi \ - && rm /etc/yum.repos.d/app-protect*.repo \ && subscription-manager unregister \ - && dnf clean all && rm -rf /var/cache/dnf + && if [ -z "${NAP_MODULES##*waf*}" ]; then \ + rm -f /etc/yum.repos.d/app-protect-8.repo \ + && nap-waf.sh; \ + fi \ + && if [ "${NGINX_AGENT}" = "true" ]; then agent.sh; fi \ + && dnf clean all + + +############################################# Base image for UBI8 with NGINX Plus and App Protect WAFv5 ############################################# +FROM redhat/ubi8@sha256:37cdac4ec130a64050d6df4e1f2ef3f53868bea55d11f623d141f139ee342bd8 AS ubi-8-plus-nap-v5 +ARG NAP_MODULES +ARG NGINX_AGENT +ARG NGINX_PLUS_VERSION -# Uncomment the lines below if you want to install a custom CA certificate -# COPY build/*.crt /etc/pki/ca-trust/source/anchors/ -# RUN update-ca-trust extract +ENV NGINX_VERSION=${NGINX_PLUS_VERSION} + +RUN --mount=type=secret,id=nginx-repo.crt,dst=/etc/ssl/nginx/nginx-repo.crt,mode=0644 \ + --mount=type=secret,id=nginx-repo.key,dst=/etc/ssl/nginx/nginx-repo.key,mode=0644 \ + --mount=type=secret,id=rhel_license,dst=/tmp/rhel_license,mode=0644 \ + --mount=type=bind,from=nginx-files,src=nginx_signing.key,target=/tmp/nginx_signing.key \ + --mount=type=bind,from=nginx-files,src=nginx-plus-8.repo,target=/etc/yum.repos.d/nginx-plus.repo,rw \ + --mount=type=bind,from=nginx-files,src=nginx-agent.repo,target=/etc/yum.repos.d/nginx-agent.repo,rw \ + --mount=type=bind,from=nginx-files,src=app-protect-v5-8.repo,target=/tmp/app-protect-8.repo \ + --mount=type=bind,from=nginx-files,src=nap-waf.sh,target=/usr/local/bin/nap-waf.sh \ + --mount=type=bind,from=nginx-files,src=agent.sh,target=/usr/local/bin/agent.sh \ + source /tmp/rhel_license \ + && if [ -z "${NAP_MODULES##*waf*}" ]; then \ + cp /tmp/app-protect-8.repo /etc/yum.repos.d/app-protect-8.repo; \ + fi \ + ## the code below is duplicated from the ubi-plus image because NAP DOS doesn't support UBI 9 and minimal versions + && groupadd --system --gid 101 nginx \ + && useradd --system --gid nginx --no-create-home --home-dir /nonexistent --comment "nginx user" --shell /bin/false --uid 101 nginx \ + && rpm --import /tmp/nginx_signing.key \ + && dnf --nodocs install -y nginx-plus nginx-plus-module-njs nginx-plus-module-fips-check \ + && if [ "${NGINX_AGENT}" = "true" ]; then dnf --nodocs install -y nginx-agent; fi \ + ## end of duplicated code + && sed -i 's/\(def in_container():\)/\1\n return False/g' /usr/lib64/python*/*-packages/rhsm/config.py \ + && subscription-manager register --org=${RHEL_ORGANIZATION} --activationkey=${RHEL_ACTIVATION_KEY} || true \ + && subscription-manager attach \ + && dnf config-manager --set-enabled codeready-builder-for-rhel-8-x86_64-rpms \ + && dnf --nodocs install -y https://dl.fedoraproject.org/pub/epel/epel-release-latest-8.noarch.rpm \ + && if [ -z "${NAP_MODULES##*waf*}" ]; then \ + dnf --nodocs install -y app-protect-module-plus-33+5.210*; \ + fi \ + && subscription-manager unregister \ + && if [ -z "${NAP_MODULES##*waf*}" ]; then \ + rm -f /etc/yum.repos.d/app-protect-8.repo \ + && nap-waf.sh; \ + fi \ + && if [ "${NGINX_AGENT}" = "true" ]; then agent.sh; fi \ + && dnf clean all ############################################# GRAAL: ModSecurity ############################################# FROM ${BUILD_OS} AS modsecurity-lib @@ -201,14 +556,15 @@ RUN curl -sS -O -L http://nginx.org/download/nginx-${MS_NGINX_VERSION}.tar.gz \ && ls -all /usr/local/lib ############################################# Create common files, permissions and setcap ############################################# -FROM ${BUILD_OS} as common +FROM ${BUILD_OS} AS common ARG BUILD_OS ARG IC_VERSION -ARG GIT_COMMIT ARG TARGETPLATFORM ARG NAP_MODULES=none +ENV BUILD_OS=${BUILD_OS} + # GRAAL: add modsecurity RUN apt-get update && apt-get install -y -q --fix-missing --no-install-recommends apt-utils autoconf automake build-essential git libcurl4-openssl-dev libgeoip-dev liblmdb-dev libpcre++-dev libtool libxml2-dev libyajl-dev pkgconf wget zlib1g-dev RUN --mount=type=bind,from=modsecurity-lib,target=/tmp/ot/ \ @@ -241,9 +597,11 @@ RUN --mount=type=bind,target=/tmp mkdir -p /var/lib/nginx /etc/nginx/secrets /et && chown -R 101:0 /etc/nginx /var/cache/nginx /var/lib/nginx /*.tmpl \ && rm -f /etc/nginx/conf.d/* /etc/apt/apt.conf.d/90pkgs-nginx /etc/apt/sources.list.d/nginx-plus.list -# Uncomment the line below if you would like to add the default.pem to the image -# and use it as a certificate and key for the default server -# ADD default.pem /etc/nginx/secrets/default +RUN --mount=type=bind,target=/code \ + --mount=type=bind,from=nginx-files,src=common.sh,target=/usr/local/bin/common.sh \ + --mount=type=bind,from=nginx-files,src=patch-os.sh,target=/usr/local/bin/patch-os.sh \ + patch-os.sh \ + && common.sh EXPOSE 80 443 @@ -252,24 +610,40 @@ ENTRYPOINT ["/nginx-ingress"] # 101 is nginx USER 101 -LABEL org.opencontainers.image.version="${IC_VERSION}" -LABEL org.opencontainers.image.revision="${GIT_COMMIT}" -LABEL org.nginx.kic.image.build.target="${TARGETPLATFORM}" -LABEL org.nginx.kic.image.build.os="${BUILD_OS}" -LABEL org.nginx.kic.image.build.nginx.version="${NGINX_PLUS_VERSION}${NGINX_VERSION}" +LABEL org.opencontainers.image.version="${IC_VERSION}" \ + org.opencontainers.image.documentation=https://docs.nginx.com/nginx-ingress-controller \ + org.opencontainers.image.vendor="NGINX Inc " \ + org.nginx.kic.image.build.target="${TARGETPLATFORM}" \ + org.nginx.kic.image.build.os="${BUILD_OS}" \ + org.nginx.kic.image.build.nginx.version="${NGINX_VERSION}" ############################################# Build nginx-ingress in golang container ############################################# -FROM golang:1.19-alpine AS builder +FROM golang-builder AS builder ARG IC_VERSION ARG TARGETARCH +WORKDIR /go/src/github.com/nginxinc/kubernetes-ingress/ +RUN apk add --no-cache git libcap +RUN --mount=type=bind,target=/go/src/github.com/nginxinc/kubernetes-ingress/ --mount=type=cache,target=/root/.cache/go-build \ + go mod download +RUN --mount=type=bind,target=/go/src/github.com/nginxinc/kubernetes-ingress/ --mount=type=cache,target=/root/.cache/go-build \ + CGO_ENABLED=0 GOOS=linux GOARCH=$TARGETARCH go build -trimpath -ldflags "-s -w -X main.version=${IC_VERSION}" \ + -o /nginx-ingress github.com/nginxinc/kubernetes-ingress/cmd/nginx-ingress \ + && setcap 'cap_net_bind_service=+ep' /nginx-ingress && setcap -v 'cap_net_bind_service=+ep' /nginx-ingress + + +############################################# Download delve ############################################# +FROM golang-builder AS debug-builder +ARG TARGETARCH + WORKDIR /go/src/github.com/nginxinc/kubernetes-ingress/ RUN apk add --no-cache git RUN --mount=type=bind,target=/go/src/github.com/nginxinc/kubernetes-ingress/ --mount=type=cache,target=/root/.cache/go-build \ go mod download RUN --mount=type=bind,target=/go/src/github.com/nginxinc/kubernetes-ingress/ --mount=type=cache,target=/root/.cache/go-build \ - CGO_ENABLED=0 GOOS=linux GOARCH=$TARGETARCH go build -trimpath -ldflags "-s -w -X main.version=${IC_VERSION}" -o /nginx-ingress github.com/nginxinc/kubernetes-ingress/cmd/nginx-ingress + CGO_ENABLED=0 GOOS=linux GOARCH=$TARGETARCH go build -gcflags "all=-N -l" -o /nginx-ingress github.com/nginxinc/kubernetes-ingress/cmd/nginx-ingress +RUN CGO_ENABLED=0 go install -ldflags "-s -w -extldflags '-static'" github.com/go-delve/delve/cmd/dlv@latest ############################################# Create image with nginx-ingress built in container ############################################# @@ -286,6 +660,85 @@ FROM common AS local LABEL org.nginx.kic.image.build.version="local" COPY --link --chown=101:0 nginx-ingress / +# root is required for `setcap` invocation +USER 0 +RUN setcap 'cap_net_bind_service=+ep' /nginx-ingress && setcap -v 'cap_net_bind_service=+ep' /nginx-ingress +# 101 is nginx, defined above +USER 101 + + +############################################# Create image with nginx-ingress built locally ############################################# +FROM common AS debug + +LABEL org.nginx.kic.image.build.version="local" + +ENV GOPATH="/work" +ENV GOROOT="/go" +ENV PATH="$PATH:${GOROOT}/bin:${GOPATH}/bin" + +COPY --link --from=debug-builder --chown=101:0 /go/bin/dlv /dlv +COPY --link --chown=101:0 nginx-ingress / +# root is required for `setcap` invocation +USER 0 +RUN setcap 'cap_net_bind_service=+ep' /nginx-ingress && setcap -v 'cap_net_bind_service=+ep' /nginx-ingress && \ + setcap 'cap_net_bind_service=+ep' /dlv && setcap -v 'cap_net_bind_service=+ep' /dlv && \ + mkdir -p /nonexistent /work /go/bin /go-build && \ + chown 101:0 /nonexistent /work /go-build +COPY --link --from=debug-builder --chown=101:0 /usr/local/go/bin/go /go/bin/go +# 101 is nginx, defined above +USER 101 +ENTRYPOINT ["/dlv"] + + +############################################# Create image with nginx-ingress built locally ############################################# +FROM common AS debug-container + +LABEL org.nginx.kic.image.build.version="local" + +ENV GOPATH="/work" +ENV GOROOT="/go" +ENV PATH="$PATH:${GOROOT}/bin:${GOPATH}/bin" + +COPY --link --from=debug-builder --chown=101:0 /go/bin/dlv /dlv +COPY --link --from=debug-builder --chown=101:0 /nginx-ingress / +# root is required for `setcap` invocation +USER 0 +RUN setcap 'cap_net_bind_service=+ep' /nginx-ingress && setcap -v 'cap_net_bind_service=+ep' /nginx-ingress && \ + setcap 'cap_net_bind_service=+ep' /dlv && setcap -v 'cap_net_bind_service=+ep' /dlv && \ + mkdir -p /nonexistent /work /go/bin /go-build && \ + chown 101:0 /nonexistent /work /go-build +COPY --link --from=debug-builder --chown=101:0 /usr/local/go/bin/go /go/bin/go +# 101 is nginx, defined above +USER 101 +ENTRYPOINT ["/dlv"] + + +############################################# Create image with nginx-ingress built locally & using prebuilt base image ############################################# +FROM ${PREBUILT_BASE_IMG} AS local-prebuilt +ARG BUILD_OS + +LABEL org.nginx.kic.image.build.version="local" + +COPY --link --chown=101:0 nginx-ingress / +# root is required for `setcap` invocation +USER 0 +RUN --mount=type=bind,target=/tmp [ -z "${BUILD_OS##*plus*}" ] && PLUS=-plus; cp -a /tmp/internal/configs/version1/nginx$PLUS.ingress.tmpl /tmp/internal/configs/version1/nginx$PLUS.tmpl \ + /tmp/internal/configs/version2/nginx$PLUS.virtualserver.tmpl /tmp/internal/configs/version2/nginx$PLUS.transportserver.tmpl / \ + && chown -R 101:0 /*.tmpl \ + && chmod -R g=u /*.tmpl \ + && setcap 'cap_net_bind_service=+ep' /nginx-ingress && setcap -v 'cap_net_bind_service=+ep' /nginx-ingress +# 101 is nginx, defined above +USER 101 + + +############################################# Builder style stage to avoid duplicate layers for ingress and ingress with setcap ############################################# +# Builder image for goreleaser +FROM common AS goreleaser-setcap +ARG TARGETARCH + +COPY --link --chown=101:0 dist/kubernetes-ingress_linux_${TARGETARCH}*/nginx-ingress / +USER 0 +RUN setcap 'cap_net_bind_service=+ep' /nginx-ingress && setcap -v 'cap_net_bind_service=+ep' /nginx-ingress ############################################# Create image with nginx-ingress built by GoReleaser ############################################# @@ -294,7 +747,45 @@ ARG TARGETARCH LABEL org.nginx.kic.image.build.version="goreleaser" +COPY --link --chown=101:0 --from=goreleaser-setcap /nginx-ingress / + + +############################################# Builder style stage to avoid duplicate layers for ingress and ingress with setcap ############################################# +# Builder image for goreleaser-prebuilt +FROM ${PREBUILT_BASE_IMG} AS goreleaser-setcap-prebuilt +ARG TARGETARCH + COPY --link --chown=101:0 dist/kubernetes-ingress_linux_${TARGETARCH}*/nginx-ingress / +USER 0 +RUN setcap 'cap_net_bind_service=+ep' /nginx-ingress && setcap -v 'cap_net_bind_service=+ep' /nginx-ingress + + +############################################# Create image with nginx-ingress built by GoReleaser & using prebuilt base image ############################################# +FROM ${PREBUILT_BASE_IMG} AS goreleaser-prebuilt +ARG TARGETARCH +ARG BUILD_OS + +LABEL org.nginx.kic.image.build.version="goreleaser" + +COPY --link --chown=101:0 --from=goreleaser-setcap-prebuilt /nginx-ingress / +# root is required for `setcap` invocation +USER 0 +RUN --mount=type=bind,target=/tmp [ -z "${BUILD_OS##*plus*}" ] && PLUS=-plus; cp -a /tmp/internal/configs/version1/nginx$PLUS.ingress.tmpl /tmp/internal/configs/version1/nginx$PLUS.tmpl \ + /tmp/internal/configs/version2/nginx$PLUS.virtualserver.tmpl /tmp/internal/configs/version2/nginx$PLUS.transportserver.tmpl / \ + && chown -R 101:0 /*.tmpl \ + && chmod -R g=u /*.tmpl +USER 101 + + +############################################# Builder style stage to avoid duplicate layers for ingress and ingress with setcap ############################################# +# Builder image for aws +FROM common AS aws-setcap +ARG TARGETARCH +ARG NAP_MODULES_AWS + +COPY --link --chown=101:0 dist/aws*${NAP_MODULES_AWS}_linux_${TARGETARCH}*/nginx-ingress / +USER 0 +RUN setcap 'cap_net_bind_service=+ep' /nginx-ingress && setcap -v 'cap_net_bind_service=+ep' /nginx-ingress ############################################# Create image with nginx-ingress built by GoReleaser for AWS Marketplace ############################################# @@ -304,13 +795,41 @@ ARG NAP_MODULES_AWS LABEL org.nginx.kic.image.build.version="aws" +COPY --link --chown=101:0 --from=aws-setcap /nginx-ingress / + + +############################################# Builder style stage to avoid duplicate layers for ingress and ingress with setcap ############################################# +# Builder image for aws-prebuilt +FROM ${PREBUILT_BASE_IMG} AS aws-setcap-prebuilt +ARG TARGETARCH +ARG NAP_MODULES_AWS + COPY --link --chown=101:0 dist/aws*${NAP_MODULES_AWS}_linux_${TARGETARCH}*/nginx-ingress / +USER 0 +RUN setcap 'cap_net_bind_service=+ep' /nginx-ingress && setcap -v 'cap_net_bind_service=+ep' /nginx-ingress + + +############################################# Create image with nginx-ingress built by GoReleaser for AWS Marketplace ############################################# +FROM ${PREBUILT_BASE_IMG} AS aws-prebuilt +ARG TARGETARCH +ARG NAP_MODULES_AWS +ARG BUILD_OS + +LABEL org.nginx.kic.image.build.version="aws" + +COPY --link --chown=101:0 --from=aws-setcap-prebuilt /nginx-ingress / +USER 0 +RUN --mount=type=bind,target=/tmp [ -z "${BUILD_OS##*plus*}" ] && PLUS=-plus; cp -a /tmp/internal/configs/version1/nginx$PLUS.ingress.tmpl /tmp/internal/configs/version1/nginx$PLUS.tmpl \ + /tmp/internal/configs/version2/nginx$PLUS.virtualserver.tmpl /tmp/internal/configs/version2/nginx$PLUS.transportserver.tmpl / \ + && chown -R 101:0 /*.tmpl \ + && chmod -R g=u /*.tmpl +USER 101 ############################################# Create image with nginx-ingress extracted from image on Docker Hub ############################################# -FROM nginx/nginx-ingress:${DOWNLOAD_TAG} as kic +FROM nginx/nginx-ingress:${DOWNLOAD_TAG} AS kic -FROM common as download +FROM common AS download LABEL org.nginx.kic.image.build.version="binaries" diff --git a/build/README.md b/build/README.md index 8bc31e68ab..53da4ed119 100644 --- a/build/README.md +++ b/build/README.md @@ -1,3 +1,3 @@ # NGINX Ingress Controller -This doc is now available at https://docs.nginx.com/nginx-ingress-controller/installation/building-ingress-controller-image/ +This doc is now available at diff --git a/build/dependencies/Dockerfile.ubi b/build/dependencies/Dockerfile.ubi new file mode 100644 index 0000000000..2fb265c3fa --- /dev/null +++ b/build/dependencies/Dockerfile.ubi @@ -0,0 +1,36 @@ +# syntax=docker/dockerfile:1.8 +FROM nginx:1.27.3@sha256:42e917aaa1b5bb40dd0f6f7f4f857490ac7747d7ef73b391c774a41a8b994f15 AS nginx + +FROM redhat/ubi9:9.4@sha256:ee0b908e958a1822afc57e5d386d1ea128eebe492cb2e01b6903ee19c133ea75 AS rpm-build +ARG NGINX +ARG NJS +ENV NGINX_VERSION ${NGINX} +ENV NJS_VERSION ${NJS} + + +RUN mkdir -p /nginx/; \ + # only build for ppc64le but make multiarch image for mounting + [ $(uname -p) = x86_64 ] && exit 0; \ + [ $(uname -p) = aarch64 ] && exit 0; \ + rpm --import https://nginx.org/keys/nginx_signing.key \ + && MINOR_VERSION=$(echo ${NGINX_VERSION} | cut -d '.' -f 2) \ + && if [ $(( $MINOR_VERSION % 2)) -eq 0 ]; then echo mainline=""; else mainline="mainline/"; fi \ + && printf "%s\n" "[nginx]" "name=nginx src repo" \ + "baseurl=https://nginx.org/packages/${mainline}centos/9/SRPMS" \ + "gpgcheck=1" "enabled=1" "module_hotfixes=true" >> /etc/yum.repos.d/nginx.repo \ + && dnf install rpm-build gcc make dnf-plugins-core which -y \ + && dnf -y install https://dl.fedoraproject.org/pub/epel/epel-release-latest-9.noarch.rpm \ + && nginxPackages=" \ + nginx-${NGINX_VERSION} \ + nginx-module-xslt-${NGINX_VERSION} \ + nginx-module-image-filter-${NGINX_VERSION} \ + nginx-module-njs-${NGINX_VERSION}+${NJS_VERSION} \ + " \ + && dnf config-manager --set-enabled ubi-9-codeready-builder \ + && dnf download --source ${nginxPackages} \ + && dnf builddep -y --srpm nginx*.rpm \ + && rpmbuild --rebuild --nodebuginfo nginx*.rpm \ + && cp /root/rpmbuild/RPMS/$(arch)/* /nginx/ + +FROM scratch AS final +COPY --link --from=rpm-build /nginx / diff --git a/build/generate_default_cert_and_key.sh b/build/generate_default_cert_and_key.sh deleted file mode 100755 index c7d50663f8..0000000000 --- a/build/generate_default_cert_and_key.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/bash - -openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout default.key -out default.crt -subj "/CN=NGINXIngressController" -cat default.key default.crt > default.pem -rm default.key default.crt diff --git a/build/log-default.json b/build/log-default.json deleted file mode 100644 index c68b96e0fd..0000000000 --- a/build/log-default.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "filter": { - "request_type": "all" - }, - "content": { - "format": "default", - "max_request_size": "any", - "max_message_size": "5k" - } -} diff --git a/build/scripts/agent.sh b/build/scripts/agent.sh new file mode 100755 index 0000000000..0cb6a07ffd --- /dev/null +++ b/build/scripts/agent.sh @@ -0,0 +1,14 @@ +#!/bin/sh + +set -e + + +if [ -z "${WAF_VERSION##*v4*}" ]; then + NAP_VERSION=$(cat /opt/app_protect/VERSION) + + mkdir -p /etc/ssl/nms /opt/nms-nap-compiler + chown -R 101:0 /etc/ssl/nms /opt/nms-nap-compiler + chmod -R g=u /etc/ssl/nms /opt/nms-nap-compiler + + ln -s /opt/app_protect "/opt/nms-nap-compiler/app_protect-${NAP_VERSION}" +fi diff --git a/build/scripts/common.sh b/build/scripts/common.sh new file mode 100755 index 0000000000..0a59f628c2 --- /dev/null +++ b/build/scripts/common.sh @@ -0,0 +1,28 @@ +#!/bin/sh + +set -e + +PLUS="" +if [ -z "${BUILD_OS##*plus*}" ]; then + mkdir -p /etc/nginx/oidc/ + cp -a /code/internal/configs/oidc/* /etc/nginx/oidc/ + mkdir -p /etc/nginx/state_files/ + mkdir -p /etc/nginx/reporting/ + mkdir -p /etc/nginx/secrets/mgmt/ + PLUS=-plus +fi + +mkdir -p /etc/nginx/njs/ && cp -a /code/internal/configs/njs/* /etc/nginx/njs/ +mkdir -p /var/lib/nginx /etc/nginx/secrets /etc/nginx/stream-conf.d +setcap 'cap_net_bind_service=+eip' /usr/sbin/nginx 'cap_net_bind_service=+eip' /usr/sbin/nginx-debug +setcap -v 'cap_net_bind_service=+eip' /usr/sbin/nginx 'cap_net_bind_service=+eip' /usr/sbin/nginx-debug + +cp -a /code/internal/configs/version1/nginx$PLUS.ingress.tmpl \ + /code/internal/configs/version1/nginx$PLUS.tmpl \ + /code/internal/configs/version2/nginx$PLUS.virtualserver.tmpl \ + /code/internal/configs/version2/nginx$PLUS.transportserver.tmpl \ + / + +chown -R 101:0 /etc/nginx /var/cache/nginx /var/lib/nginx /var/log/nginx /*.tmpl +chmod -R g=u /etc/nginx /var/cache/nginx /var/lib/nginx /var/log/nginx /*.tmpl +rm -f /etc/nginx/conf.d/* diff --git a/build/scripts/nap-dos.sh b/build/scripts/nap-dos.sh new file mode 100755 index 0000000000..3e906d26c1 --- /dev/null +++ b/build/scripts/nap-dos.sh @@ -0,0 +1,6 @@ +#!/bin/sh + +set -e + +mkdir -p /root/app_protect_dos /etc/nginx/dos/policies /etc/nginx/dos/logconfs /etc/nginx/dos/allowlist /shared/cores /var/log/adm /var/run/adm +chmod 777 /shared/cores /var/log/adm /var/run/adm /etc/app_protect_dos diff --git a/build/scripts/nap-waf.sh b/build/scripts/nap-waf.sh new file mode 100755 index 0000000000..cf736732d7 --- /dev/null +++ b/build/scripts/nap-waf.sh @@ -0,0 +1,13 @@ +#!/bin/sh + +set -e + +for i in /etc/nginx/waf/nac-policies /etc/nginx/waf/nac-logconfs /etc/nginx/waf/nac-usersigs /etc/app_protect /usr/share/ts /var/log/app_protect/ /opt/app_protect/; do + if [ ! -d ${i} ]; then + mkdir -p ${i} + fi + chown -R 101:0 ${i} + chmod -R g=u ${i} +done + +touch /etc/nginx/waf/nac-usersigs/index.conf diff --git a/build/scripts/ubi-clean.sh b/build/scripts/ubi-clean.sh new file mode 100755 index 0000000000..725f22dac2 --- /dev/null +++ b/build/scripts/ubi-clean.sh @@ -0,0 +1,6 @@ +#!/bin/sh + +set -e + +microdnf remove -y shadow-utils subscription-manager python3-requests python3-cloud-what python3-subscription-manager-rhsm python3-setuptools python3-inotify python3-requests python3-urllib3 python3-idna +microdnf clean all && rm -rf /var/cache/dnf diff --git a/build/scripts/ubi-setup.sh b/build/scripts/ubi-setup.sh new file mode 100755 index 0000000000..72e3716369 --- /dev/null +++ b/build/scripts/ubi-setup.sh @@ -0,0 +1,8 @@ +#!/bin/sh + +set -e + +microdnf --nodocs install -y shadow-utils subscription-manager +groupadd --system --gid 101 nginx +useradd --system --gid nginx --no-create-home --home-dir /nonexistent --comment "nginx user" --shell /bin/false --uid 101 nginx +rpm --import /tmp/nginx_signing.key diff --git a/deployments/helm-chart-dos-arbitrator/.helmignore b/charts/nginx-ingress/.helmignore similarity index 100% rename from deployments/helm-chart-dos-arbitrator/.helmignore rename to charts/nginx-ingress/.helmignore diff --git a/charts/nginx-ingress/Chart.yaml b/charts/nginx-ingress/Chart.yaml new file mode 100644 index 0000000000..c7c1bfbe8e --- /dev/null +++ b/charts/nginx-ingress/Chart.yaml @@ -0,0 +1,17 @@ +apiVersion: v2 +name: nginx-ingress +version: 2.1.0 +appVersion: 4.1.0 +kubeVersion: ">= 1.23.0-0" +type: application +description: NGINX Ingress Controller +icon: https://raw.githubusercontent.com/nginxinc/kubernetes-ingress/v4.0.0/charts/nginx-ingress/chart-icon.png +home: https://github.com/nginxinc/kubernetes-ingress +sources: + - https://github.com/nginxinc/kubernetes-ingress/tree/v4.0.0/charts/nginx-ingress +keywords: + - ingress + - nginx +maintainers: + - name: nginxinc + email: kubernetes@nginx.com diff --git a/charts/nginx-ingress/README.md b/charts/nginx-ingress/README.md new file mode 100644 index 0000000000..ff68d539d2 --- /dev/null +++ b/charts/nginx-ingress/README.md @@ -0,0 +1,3 @@ +# Helm Documentation + +Please refer to the [Installation with Helm](https://docs.nginx.com/nginx-ingress-controller/installation/installing-nic/installation-with-helm/) guide in the NGINX Ingress Controller documentation site. diff --git a/deployments/helm-chart-dos-arbitrator/chart-icon.png b/charts/nginx-ingress/chart-icon.png similarity index 100% rename from deployments/helm-chart-dos-arbitrator/chart-icon.png rename to charts/nginx-ingress/chart-icon.png diff --git a/charts/nginx-ingress/crds b/charts/nginx-ingress/crds new file mode 120000 index 0000000000..5188fdaabe --- /dev/null +++ b/charts/nginx-ingress/crds @@ -0,0 +1 @@ +../../config/crd/bases/ \ No newline at end of file diff --git a/charts/nginx-ingress/templates/NOTES.txt b/charts/nginx-ingress/templates/NOTES.txt new file mode 100644 index 0000000000..c9a2739771 --- /dev/null +++ b/charts/nginx-ingress/templates/NOTES.txt @@ -0,0 +1,13 @@ +NGINX Ingress Controller {{ .Chart.AppVersion }} has been installed. + +For release notes for this version please see: https://docs.nginx.com/nginx-ingress-controller/releases/ + +Installation and upgrade instructions: https://docs.nginx.com/nginx-ingress-controller/installation/installing-nic/installation-with-helm/ + +{{ if .Release.IsUpgrade -}} +If you are upgrading from a version of the chart that uses older Custom Resource Definitions (CRD) it is necessary to manually upgrade the CRDs as this is not managed by Helm. +To update to the latest version of the CRDs: + $ kubectl apply -f https://raw.githubusercontent.com/nginxinc/kubernetes-ingress/v{{ .Chart.AppVersion }}/deploy/crds.yaml + +More details on upgrading the CRDs: https://docs.nginx.com/nginx-ingress-controller/installation/installing-nic/installation-with-helm/#upgrading-the-crds +{{- end -}} diff --git a/charts/nginx-ingress/templates/_helpers.tpl b/charts/nginx-ingress/templates/_helpers.tpl new file mode 100644 index 0000000000..7fe436cca0 --- /dev/null +++ b/charts/nginx-ingress/templates/_helpers.tpl @@ -0,0 +1,512 @@ +{{/* vim: set filetype=mustache: */}} + +{{/* +Expand the name of the chart. +*/}} +{{- define "nginx-ingress.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "nginx-ingress.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create a default fully qualified controller name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +*/}} +{{- define "nginx-ingress.controller.fullname" -}} +{{- printf "%s-%s" (include "nginx-ingress.fullname" .) .Values.controller.name | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Create a default fully qualified controller service name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +*/}} +{{- define "nginx-ingress.controller.service.name" -}} +{{- default (include "nginx-ingress.controller.fullname" .) .Values.serviceNameOverride | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "nginx-ingress.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "nginx-ingress.labels" -}} +helm.sh/chart: {{ include "nginx-ingress.chart" . }} +{{ include "nginx-ingress.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Pod labels +*/}} +{{- define "nginx-ingress.podLabels" -}} +{{- include "nginx-ingress.selectorLabels" . }} +{{- if .Values.nginxServiceMesh.enable }} +nsm.nginx.com/enable-ingress: "true" +nsm.nginx.com/enable-egress: "{{ .Values.nginxServiceMesh.enableEgress }}" +nsm.nginx.com/{{ .Values.controller.kind }}: {{ include "nginx-ingress.controller.fullname" . }} +{{- end }} +{{- if and .Values.nginxAgent.enable (eq (.Values.nginxAgent.customConfigMap | default "") "") }} +agent-configuration-revision-hash: {{ include "nginx-ingress.agentConfiguration" . | sha1sum | trunc 8 | quote }} +{{- end }} +{{- if .Values.controller.pod.extraLabels }} +{{ toYaml .Values.controller.pod.extraLabels }} +{{- end }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "nginx-ingress.selectorLabels" -}} +{{- if .Values.controller.selectorLabels -}} +{{ toYaml .Values.controller.selectorLabels }} +{{- else -}} +app.kubernetes.io/name: {{ include "nginx-ingress.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end -}} +{{- end -}} + +{{/* +Expand the name of the configmap. +*/}} +{{- define "nginx-ingress.configName" -}} +{{- if .Values.controller.customConfigMap -}} +{{ .Values.controller.customConfigMap }} +{{- else -}} +{{- default (include "nginx-ingress.fullname" .) .Values.controller.config.name -}} +{{- end -}} +{{- end -}} + +{{/* +Expand the name of the configmap used for NGINX Agent. +*/}} +{{- define "nginx-ingress.agentConfigName" -}} +{{- if ne (.Values.nginxAgent.customConfigMap | default "") "" -}} +{{ .Values.nginxAgent.customConfigMap }} +{{- else -}} +{{- printf "%s-agent-config" (include "nginx-ingress.fullname" . | trunc 49 | trimSuffix "-") -}} +{{- end -}} +{{- end -}} + +{{/* +Expand the name of the mgmt configmap. +*/}} +{{- define "nginx-ingress.mgmtConfigName" -}} +{{- if .Values.controller.mgmt.configMapName -}} +{{ .Values.controller.mgmt.configMapName }} +{{- else -}} +{{- default (printf "%s-mgmt" (include "nginx-ingress.fullname" .)) -}} +{{- end -}} +{{- end -}} + +{{/* +Expand license token secret name. +*/}} +{{- define "nginx-ingress.licenseTokenSecretName" -}} +{{- if hasKey .Values.controller.mgmt "licenseTokenSecretName" -}} +{{- .Values.controller.mgmt.licenseTokenSecretName -}} +{{- else }} +{{- fail "Error: When using Nginx Plus, 'controller.mgmt.licenseTokenSecretName' must be set." }} +{{- end -}} +{{- end -}} + +{{/* +Expand leader election lock name. +*/}} +{{- define "nginx-ingress.leaderElectionName" -}} +{{- if .Values.controller.reportIngressStatus.leaderElectionLockName -}} +{{ .Values.controller.reportIngressStatus.leaderElectionLockName }} +{{- else -}} +{{- printf "%s-%s" (include "nginx-ingress.fullname" .) "leader-election" -}} +{{- end -}} +{{- end -}} + +{{/* +Expand service account name. +*/}} +{{- define "nginx-ingress.serviceAccountName" -}} +{{- default (include "nginx-ingress.fullname" .) .Values.controller.serviceAccount.name -}} +{{- end -}} + +{{/* +Expand default TLS name. +*/}} +{{- define "nginx-ingress.defaultTLSName" -}} +{{- printf "%s-%s" (include "nginx-ingress.fullname" .) "default-server-tls" -}} +{{- end -}} + +{{/* +Expand wildcard TLS name. +*/}} +{{- define "nginx-ingress.wildcardTLSName" -}} +{{- printf "%s-%s" (include "nginx-ingress.fullname" .) "wildcard-tls" -}} +{{- end -}} + +{{- define "nginx-ingress.tag" -}} +{{- default .Chart.AppVersion .Values.controller.image.tag -}} +{{- end -}} + +{{/* +Expand image name. +*/}} +{{- define "nginx-ingress.image" -}} +{{ include "nginx-ingress.image-digest-or-tag" (dict "image" .Values.controller.image "default" .Chart.AppVersion ) }} +{{- end -}} + +{{- define "nap-enforcer.image" -}} +{{ include "nginx-ingress.image-digest-or-tag" (dict "image" .Values.controller.appprotect.enforcer.image "default" .Chart.AppVersion ) }} +{{- end -}} + +{{- define "nap-config-manager.image" -}} +{{ include "nginx-ingress.image-digest-or-tag" (dict "image" .Values.controller.appprotect.configManager.image "default" .Chart.AppVersion ) }} +{{- end -}} + +{{/* +Accepts an image struct like .Values.controller.image along with a default value to use +if the digest or tag is not set. Can be called like: +include "nginx-ingress.image-digest-or-tag" (dict "image" .Values.controller.image "default" .Chart.AppVersion +*/}} +{{- define "nginx-ingress.image-digest-or-tag" -}} +{{- if .image.digest -}} +{{- printf "%s@%s" .image.repository .image.digest -}} +{{- else -}} +{{- printf "%s:%s" .image.repository (default .default .image.tag) -}} +{{- end -}} +{{- end -}} + +{{- define "nginx-ingress.prometheus.serviceName" -}} +{{- printf "%s-%s" (include "nginx-ingress.fullname" .) "prometheus-service" -}} +{{- end -}} + +{{/* +return if readOnlyRootFilesystem is enabled or not. +*/}} +{{- define "nginx-ingress.readOnlyRootFilesystem" -}} +{{- if or .Values.controller.readOnlyRootFilesystem (and .Values.controller.securityContext .Values.controller.securityContext.readOnlyRootFilesystem) -}} +true +{{- else -}} +false +{{- end -}} +{{- end -}} + +{{/* +Build the args for the service binary. +*/}} +{{- define "nginx-ingress.args" -}} +{{- if and .Values.controller.debug .Values.controller.debug.enable }} +- --listen=:2345 +- --headless=true +- --log=true +- --log-output=debugger,debuglineerr,gdbwire,lldbout,rpc,dap,fncall,minidump,stack +- --accept-multiclient +- --api-version=2 +- exec +- ./nginx-ingress +{{- if .Values.controller.debug.continue }} +- --continue +{{- end }} +- -- +{{- end -}} +- -nginx-plus={{ .Values.controller.nginxplus }} +- -nginx-reload-timeout={{ .Values.controller.nginxReloadTimeout }} +- -enable-app-protect={{ .Values.controller.appprotect.enable }} +{{- if and .Values.controller.appprotect.enable .Values.controller.appprotect.logLevel }} +- -app-protect-log-level={{ .Values.controller.appprotect.logLevel }} +{{ end }} +{{- if and .Values.controller.appprotect.enable .Values.controller.appprotect.v5 }} +- -app-protect-enforcer-address="{{ .Values.controller.appprotect.enforcer.host | default "127.0.0.1" }}:{{ .Values.controller.appprotect.enforcer.port | default 50000 }}" +{{- end }} +- -enable-app-protect-dos={{ .Values.controller.appprotectdos.enable }} +{{- if .Values.controller.appprotectdos.enable }} +- -app-protect-dos-debug={{ .Values.controller.appprotectdos.debug }} +- -app-protect-dos-max-daemons={{ .Values.controller.appprotectdos.maxDaemons }} +- -app-protect-dos-max-workers={{ .Values.controller.appprotectdos.maxWorkers }} +- -app-protect-dos-memory={{ .Values.controller.appprotectdos.memory }} +{{ end }} +- -nginx-configmaps=$(POD_NAMESPACE)/{{ include "nginx-ingress.configName" . }} +{{- if .Values.controller.nginxplus }} +- -mgmt-configmap=$(POD_NAMESPACE)/{{ include "nginx-ingress.mgmtConfigName" . }} +{{- end }} +{{- if .Values.controller.defaultTLS.secret }} +- -default-server-tls-secret={{ .Values.controller.defaultTLS.secret }} +{{ else if and (.Values.controller.defaultTLS.cert) (.Values.controller.defaultTLS.key) }} +- -default-server-tls-secret=$(POD_NAMESPACE)/{{ include "nginx-ingress.defaultTLSName" . }} +{{- end }} +- -ingress-class={{ .Values.controller.ingressClass.name }} +{{- if .Values.controller.watchNamespace }} +- -watch-namespace={{ .Values.controller.watchNamespace }} +{{- end }} +{{- if .Values.controller.watchNamespaceLabel }} +- -watch-namespace-label={{ .Values.controller.watchNamespaceLabel }} +{{- end }} +{{- if .Values.controller.watchSecretNamespace }} +- -watch-secret-namespace={{ .Values.controller.watchSecretNamespace }} +{{- end }} +- -health-status={{ .Values.controller.healthStatus }} +- -health-status-uri={{ .Values.controller.healthStatusURI }} +- -nginx-debug={{ .Values.controller.nginxDebug }} +- -log-level={{ .Values.controller.logLevel }} +- -log-format={{ .Values.controller.logFormat }} +- -nginx-status={{ .Values.controller.nginxStatus.enable }} +{{- if .Values.controller.nginxStatus.enable }} +- -nginx-status-port={{ .Values.controller.nginxStatus.port }} +- -nginx-status-allow-cidrs={{ .Values.controller.nginxStatus.allowCidrs }} +{{- end }} +{{- if .Values.controller.reportIngressStatus.enable }} +- -report-ingress-status +{{- if .Values.controller.reportIngressStatus.ingressLink }} +- -ingresslink={{ .Values.controller.reportIngressStatus.ingressLink }} +{{- else if .Values.controller.reportIngressStatus.externalService }} +- -external-service={{ .Values.controller.reportIngressStatus.externalService }} +{{- else if and (.Values.controller.service.create) (eq .Values.controller.service.type "LoadBalancer") }} +- -external-service={{ include "nginx-ingress.controller.service.name" . }} +{{- end }} +{{- end }} +- -enable-leader-election={{ .Values.controller.reportIngressStatus.enableLeaderElection }} +{{- if .Values.controller.reportIngressStatus.enableLeaderElection }} +- -leader-election-lock-name={{ include "nginx-ingress.leaderElectionName" . }} +{{- end }} +{{- if .Values.controller.wildcardTLS.secret }} +- -wildcard-tls-secret={{ .Values.controller.wildcardTLS.secret }} +{{- else if and .Values.controller.wildcardTLS.cert .Values.controller.wildcardTLS.key }} +- -wildcard-tls-secret=$(POD_NAMESPACE)/{{ include "nginx-ingress.wildcardTLSName" . }} +{{- end }} +- -enable-prometheus-metrics={{ .Values.prometheus.create }} +- -prometheus-metrics-listen-port={{ .Values.prometheus.port }} +- -prometheus-tls-secret={{ .Values.prometheus.secret }} +- -enable-service-insight={{ .Values.serviceInsight.create }} +- -service-insight-listen-port={{ .Values.serviceInsight.port }} +- -service-insight-tls-secret={{ .Values.serviceInsight.secret }} +- -enable-custom-resources={{ .Values.controller.enableCustomResources }} +- -enable-snippets={{ .Values.controller.enableSnippets }} +- -disable-ipv6={{ .Values.controller.disableIPV6 }} +{{- if .Values.controller.enableCustomResources }} +- -enable-tls-passthrough={{ .Values.controller.enableTLSPassthrough }} +{{- if .Values.controller.enableTLSPassthrough }} +- -tls-passthrough-port={{ .Values.controller.tlsPassthroughPort }} +{{- end }} +- -enable-cert-manager={{ .Values.controller.enableCertManager }} +- -enable-oidc={{ .Values.controller.enableOIDC }} +- -enable-external-dns={{ .Values.controller.enableExternalDNS }} +- -default-http-listener-port={{ .Values.controller.defaultHTTPListenerPort}} +- -default-https-listener-port={{ .Values.controller.defaultHTTPSListenerPort}} +{{- if .Values.controller.globalConfiguration.create }} +- -global-configuration=$(POD_NAMESPACE)/{{ include "nginx-ingress.controller.fullname" . }} +{{- end }} +{{- end }} +- -ready-status={{ .Values.controller.readyStatus.enable }} +- -ready-status-port={{ .Values.controller.readyStatus.port }} +- -enable-latency-metrics={{ .Values.controller.enableLatencyMetrics }} +- -ssl-dynamic-reload={{ .Values.controller.enableSSLDynamicReload }} +- -enable-telemetry-reporting={{ .Values.controller.telemetryReporting.enable}} +- -weight-changes-dynamic-reload={{ .Values.controller.enableWeightChangesDynamicReload}} +{{- if .Values.nginxAgent.enable }} +- -agent=true +- -agent-instance-group={{ default (include "nginx-ingress.controller.fullname" .) .Values.nginxAgent.instanceGroup }} +{{- end }} +{{- end -}} + +{{/* +Volumes for controller. +*/}} +{{- define "nginx-ingress.volumes" -}} +{{- $volumesSet := "false" }} +volumes: +{{- if eq (include "nginx-ingress.volumeEntries" .) "" -}} +{{ toYaml list | printf " %s" }} +{{- else }} +{{ include "nginx-ingress.volumeEntries" . }} +{{- end -}} +{{- end -}} + +{{/* +List of volumes for controller. +*/}} +{{- define "nginx-ingress.volumeEntries" -}} +{{- if eq (include "nginx-ingress.readOnlyRootFilesystem" .) "true" }} +- name: nginx-etc + emptyDir: {} +- name: nginx-cache + emptyDir: {} +- name: nginx-lib + emptyDir: {} +- name: nginx-log + emptyDir: {} +{{- end }} +{{- if .Values.controller.appprotect.v5 }} +{{ toYaml .Values.controller.appprotect.volumes }} +{{- end }} +{{- if .Values.controller.volumes }} +{{ toYaml .Values.controller.volumes }} +{{- end }} +{{- if .Values.nginxAgent.enable }} +- name: agent-conf + configMap: + name: {{ include "nginx-ingress.agentConfigName" . }} +- name: agent-dynamic + emptyDir: {} +{{- if and .Values.nginxAgent.instanceManager.tls (or (ne (.Values.nginxAgent.instanceManager.tls.secret | default "") "") (ne (.Values.nginxAgent.instanceManager.tls.caSecret | default "") "")) }} +- name: nginx-agent-tls + projected: + sources: +{{- if ne .Values.nginxAgent.instanceManager.tls.secret "" }} + - secret: + name: {{ .Values.nginxAgent.instanceManager.tls.secret }} +{{- end }} +{{- if ne .Values.nginxAgent.instanceManager.tls.caSecret "" }} + - secret: + name: {{ .Values.nginxAgent.instanceManager.tls.caSecret }} +{{- end }} +{{- end }} +{{- end -}} +{{- end -}} + +{{/* +Volume mounts for controller. +*/}} +{{- define "nginx-ingress.volumeMounts" -}} +{{- $volumesSet := "false" }} +volumeMounts: +{{- if eq (include "nginx-ingress.volumeMountEntries" .) "" -}} +{{ toYaml list | printf " %s" }} +{{- else }} +{{ include "nginx-ingress.volumeMountEntries" . }} +{{- end -}} +{{- end -}} +{{- define "nginx-ingress.volumeMountEntries" -}} +{{- if eq (include "nginx-ingress.readOnlyRootFilesystem" .) "true" }} +- mountPath: /etc/nginx + name: nginx-etc +- mountPath: /var/cache/nginx + name: nginx-cache +- mountPath: /var/lib/nginx + name: nginx-lib +- mountPath: /var/log/nginx + name: nginx-log +{{- end }} +{{- if .Values.controller.appprotect.v5 }} +- name: app-protect-bd-config + mountPath: /opt/app_protect/bd_config +- name: app-protect-config + mountPath: /opt/app_protect/config + # app-protect-bundles is mounted so that Ingress Controller + # can verify that referenced bundles are present +- name: app-protect-bundles + mountPath: /etc/app_protect/bundles +{{- end }} +{{- if .Values.controller.volumeMounts }} +{{ toYaml .Values.controller.volumeMounts }} +{{- end }} +{{- if .Values.nginxAgent.enable }} +- name: agent-conf + mountPath: /etc/nginx-agent/nginx-agent.conf + subPath: nginx-agent.conf +- name: agent-dynamic + mountPath: /var/lib/nginx-agent +{{- if and .Values.nginxAgent.instanceManager.tls (or (ne (.Values.nginxAgent.instanceManager.tls.secret | default "") "") (ne (.Values.nginxAgent.instanceManager.tls.caSecret | default "") "")) }} +- name: nginx-agent-tls + mountPath: /etc/ssl/nms + readOnly: true +{{- end }} +{{- end -}} +{{- end -}} + +{{- define "nginx-ingress.appprotect.v5" -}} +{{- if .Values.controller.appprotect.v5}} +- name: waf-enforcer + image: {{ include "nap-enforcer.image" . }} + imagePullPolicy: "{{ .Values.controller.appprotect.enforcer.image.pullPolicy }}" +{{- if .Values.controller.appprotect.enforcer.securityContext }} + securityContext: +{{ toYaml .Values.controller.appprotect.enforcer.securityContext | nindent 6 }} +{{- end }} + env: + - name: ENFORCER_PORT + value: "{{ .Values.controller.appprotect.enforcer.port | default 50000 }}" + - name: ENFORCER_CONFIG_TIMEOUT + value: "0" + volumeMounts: + - name: app-protect-bd-config + mountPath: /opt/app_protect/bd_config +- name: waf-config-mgr + image: {{ include "nap-config-manager.image" . }} + imagePullPolicy: "{{ .Values.controller.appprotect.configManager.image.pullPolicy }}" +{{- if .Values.controller.appprotect.configManager.securityContext }} + securityContext: +{{ toYaml .Values.controller.appprotect.configManager.securityContext | nindent 6 }} +{{- end }} + volumeMounts: + - name: app-protect-bd-config + mountPath: /opt/app_protect/bd_config + - name: app-protect-config + mountPath: /opt/app_protect/config + - name: app-protect-bundles + mountPath: /etc/app_protect/bundles +{{- end}} +{{- end -}} + +{{- define "nginx-ingress.agentConfiguration" -}} +log: + level: {{ .Values.nginxAgent.logLevel }} + path: "" +server: + host: {{ required ".Values.nginxAgent.instanceManager.host is required when setting .Values.nginxAgent.enable to true" .Values.nginxAgent.instanceManager.host }} + grpcPort: {{ .Values.nginxAgent.instanceManager.grpcPort }} +{{- if ne (.Values.nginxAgent.instanceManager.sni | default "") "" }} + metrics: {{ .Values.nginxAgent.instanceManager.sni }} + command: {{ .Values.nginxAgent.instanceManager.sni }} +{{- end }} +{{- if .Values.nginxAgent.instanceManager.tls }} +tls: + enable: {{ .Values.nginxAgent.instanceManager.tls.enable | default true }} + skip_verify: {{ .Values.nginxAgent.instanceManager.tls.skipVerify | default false }} + {{- if ne .Values.nginxAgent.instanceManager.tls.caSecret "" }} + ca: "/etc/ssl/nms/ca.crt" + {{- end }} + {{- if ne .Values.nginxAgent.instanceManager.tls.secret "" }} + cert: "/etc/ssl/nms/tls.crt" + key: "/etc/ssl/nms/tls.key" + {{- end }} +{{- end }} +features: + - registration + - nginx-counting + - metrics-sender + - dataplane-status +extensions: + - nginx-app-protect + - nap-monitoring +nginx_app_protect: + report_interval: 15s + precompiled_publication: true +nap_monitoring: + collector_buffer_size: {{ .Values.nginxAgent.napMonitoring.collectorBufferSize }} + processor_buffer_size: {{ .Values.nginxAgent.napMonitoring.processorBufferSize }} + syslog_ip: {{ .Values.nginxAgent.syslog.host }} + syslog_port: {{ .Values.nginxAgent.syslog.port }} + +{{ end -}} diff --git a/charts/nginx-ingress/templates/clusterrole.yaml b/charts/nginx-ingress/templates/clusterrole.yaml new file mode 100644 index 0000000000..42566f9e47 --- /dev/null +++ b/charts/nginx-ingress/templates/clusterrole.yaml @@ -0,0 +1,169 @@ +{{- if and .Values.rbac.create .Values.rbac.clusterrole.create }} +kind: ClusterRole +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: {{ include "nginx-ingress.fullname" . }} + labels: + {{- include "nginx-ingress.labels" . | nindent 4 }} +rules: +- apiGroups: + - "" + resources: + - configmaps + - namespaces + - pods + - secrets + - services + verbs: + - get + - list + - watch +- apiGroups: + - "" + resources: + - events + verbs: + - create + - patch + - list +- apiGroups: + - coordination.k8s.io + resources: + - leases + verbs: + - list + - watch +- apiGroups: + - discovery.k8s.io + resources: + - endpointslices + verbs: + - get + - list + - watch +- apiGroups: + - networking.k8s.io + resources: + - ingresses + verbs: + - get + - list + - watch +- apiGroups: + - "" + resources: + - nodes + verbs: + - list +- apiGroups: + - "apps" + resources: + - replicasets + - daemonsets + verbs: + - get +- apiGroups: + - networking.k8s.io + resources: + - ingressclasses + verbs: + - get + - list +{{- if .Values.controller.reportIngressStatus.enable }} +- apiGroups: + - networking.k8s.io + resources: + - ingresses/status + verbs: + - update +{{- end }} +{{- if .Values.controller.appprotect.enable }} +- apiGroups: + - appprotect.f5.com + resources: + - appolicies + - aplogconfs + - apusersigs + verbs: + - get + - watch + - list +{{- end }} +{{- if .Values.controller.appprotectdos.enable }} +- apiGroups: + - appprotectdos.f5.com + resources: + - apdospolicies + - apdoslogconfs + - dosprotectedresources + verbs: + - get + - watch + - list +{{- end }} +{{- if .Values.controller.enableCustomResources }} +- apiGroups: + - k8s.nginx.org + resources: + - virtualservers + - virtualserverroutes + - globalconfigurations + - transportservers + - policies + verbs: + - list + - watch + - get +- apiGroups: + - k8s.nginx.org + resources: + - virtualservers/status + - virtualserverroutes/status + - policies/status + - transportservers/status + verbs: + - update +{{- end }} +{{- if .Values.controller.reportIngressStatus.ingressLink }} +- apiGroups: + - cis.f5.com + resources: + - ingresslinks + verbs: + - list + - watch + - get +{{- end }} +{{- if .Values.controller.enableCertManager }} +- apiGroups: + - cert-manager.io + resources: + - certificates + verbs: + - list + - watch + - get + - update + - create + - delete +{{- end }} +{{- if .Values.controller.enableExternalDNS }} +- apiGroups: + - externaldns.nginx.org + resources: + - dnsendpoints + verbs: + - list + - watch + - get + - update + - create + - delete +- apiGroups: + - externaldns.nginx.org + resources: + - dnsendpoints/status + verbs: + - update +{{- end }} +{{- end}} diff --git a/charts/nginx-ingress/templates/clusterrolebinding.yaml b/charts/nginx-ingress/templates/clusterrolebinding.yaml new file mode 100644 index 0000000000..ed06c48ccb --- /dev/null +++ b/charts/nginx-ingress/templates/clusterrolebinding.yaml @@ -0,0 +1,16 @@ +{{- if .Values.rbac.create }} +kind: ClusterRoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: {{ include "nginx-ingress.fullname" . }} + labels: + {{- include "nginx-ingress.labels" . | nindent 4 }} +subjects: +- kind: ServiceAccount + name: {{ include "nginx-ingress.serviceAccountName" . }} + namespace: {{ .Release.Namespace }} +roleRef: + kind: ClusterRole + name: {{ include "nginx-ingress.fullname" . }} + apiGroup: rbac.authorization.k8s.io +{{- end }} diff --git a/charts/nginx-ingress/templates/controller-configmap.yaml b/charts/nginx-ingress/templates/controller-configmap.yaml new file mode 100644 index 0000000000..ac07a721a5 --- /dev/null +++ b/charts/nginx-ingress/templates/controller-configmap.yaml @@ -0,0 +1,79 @@ +{{- if not .Values.controller.customConfigMap -}} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "nginx-ingress.configName" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "nginx-ingress.labels" . | nindent 4 }} +{{- if .Values.controller.config.annotations }} + annotations: +{{ toYaml .Values.controller.config.annotations | indent 4 }} +{{- end }} +data: +{{ toYaml (default dict .Values.controller.config.entries) | indent 2 }} +{{- end }} +--- +{{- if and .Values.nginxAgent.enable (eq (.Values.nginxAgent.customConfigMap | default "") "") }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "nginx-ingress.agentConfigName" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "nginx-ingress.labels" . | nindent 4 }} +{{- if .Values.controller.config.annotations }} + annotations: +{{ toYaml .Values.controller.config.annotations | indent 4 }} +{{- end }} +data: + nginx-agent.conf: |- +{{ include "nginx-ingress.agentConfiguration" . | indent 4 }} +{{- end }} +--- +{{- if .Values.controller.nginxplus }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "nginx-ingress.mgmtConfigName" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "nginx-ingress.labels" . | nindent 4 }} +{{- if .Values.controller.config.annotations }} + annotations: +{{ toYaml .Values.controller.config.annotations | indent 4 }} +{{- end }} +data: + license-token-secret-name: {{ required "When using Nginx Plus, 'controller.mgmt.licenseTokenSecretName' cannot be empty " (include "nginx-ingress.licenseTokenSecretName" . ) }} +{{- if hasKey .Values.controller.mgmt "sslVerify" }} + ssl-verify: {{ quote .Values.controller.mgmt.sslVerify }} +{{- end }} +{{- if hasKey .Values.controller.mgmt "enforceInitialReport" }} + enforce-initial-report: {{ quote .Values.controller.mgmt.enforceInitialReport }} +{{- end }} +{{- if hasKey .Values.controller.mgmt "usageReport" }} +{{- if hasKey .Values.controller.mgmt.usageReport "endpoint" }} + usage-report-endpoint: {{ quote .Values.controller.mgmt.usageReport.endpoint }} +{{- end }} +{{- if hasKey .Values.controller.mgmt.usageReport "interval" }} + usage-report-interval: {{ quote .Values.controller.mgmt.usageReport.interval }} +{{- end }} +{{- end }} +{{- if hasKey .Values.controller.mgmt "sslTrustedCertificateSecretName" }} + ssl-trusted-certificate-secret-name: {{ quote .Values.controller.mgmt.sslTrustedCertificateSecretName }} +{{- end }} +{{- if hasKey .Values.controller.mgmt "sslCertificateSecretName" }} + ssl-certificate-secret-name: {{ quote .Values.controller.mgmt.sslCertificateSecretName}} +{{- end }} +{{- if hasKey .Values.controller.mgmt "resolver" }} +{{- if hasKey .Values.controller.mgmt.resolver "addresses" }} + resolver-addresses: {{ join "," .Values.controller.mgmt.resolver.addresses | quote }} +{{- end }} +{{- if hasKey .Values.controller.mgmt.resolver "ipv6" }} + resolver-ipv6: {{ quote .Values.controller.mgmt.resolver.ipv6 }} +{{- end }} +{{- if hasKey .Values.controller.mgmt.resolver "valid" }} + resolver-valid: {{ quote .Values.controller.mgmt.resolver.valid }} +{{- end }} +{{- end }} +{{- end }} diff --git a/charts/nginx-ingress/templates/controller-daemonset.yaml b/charts/nginx-ingress/templates/controller-daemonset.yaml new file mode 100644 index 0000000000..268f127f85 --- /dev/null +++ b/charts/nginx-ingress/templates/controller-daemonset.yaml @@ -0,0 +1,179 @@ +{{- if eq .Values.controller.kind "daemonset" }} +apiVersion: apps/v1 +kind: DaemonSet +metadata: + name: {{ include "nginx-ingress.controller.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "nginx-ingress.labels" . | nindent 4 }} +{{- if .Values.controller.annotations }} + annotations: {{ toYaml .Values.controller.annotations | nindent 4 }} +{{- end }} +spec: + selector: + matchLabels: + {{- include "nginx-ingress.selectorLabels" . | nindent 6 }} + template: + metadata: + labels: + {{- include "nginx-ingress.podLabels" . | nindent 8 }} +{{- if or .Values.prometheus.create .Values.controller.pod.annotations }} + annotations: +{{- if .Values.prometheus.create }} + prometheus.io/scrape: "true" + prometheus.io/port: "{{ .Values.prometheus.port }}" + prometheus.io/scheme: "{{ .Values.prometheus.scheme }}" +{{- end }} +{{- if .Values.controller.pod.annotations }} +{{ toYaml .Values.controller.pod.annotations | indent 8 }} +{{- end }} +{{- end }} + spec: + serviceAccountName: {{ include "nginx-ingress.serviceAccountName" . }} + automountServiceAccountToken: true + securityContext: +{{ toYaml .Values.controller.podSecurityContext | indent 8 }} + terminationGracePeriodSeconds: {{ .Values.controller.terminationGracePeriodSeconds }} +{{- if .Values.controller.nodeSelector }} + nodeSelector: +{{ toYaml .Values.controller.nodeSelector | indent 8 }} +{{- end }} +{{- if .Values.controller.tolerations }} + tolerations: +{{ toYaml .Values.controller.tolerations | indent 6 }} +{{- end }} +{{- if .Values.controller.affinity }} + affinity: +{{ toYaml .Values.controller.affinity | indent 8 }} +{{- end }} +{{- include "nginx-ingress.volumes" . | indent 6 }} +{{- if .Values.controller.priorityClassName }} + priorityClassName: {{ .Values.controller.priorityClassName }} +{{- end }} + hostNetwork: {{ .Values.controller.hostNetwork }} + dnsPolicy: {{ .Values.controller.dnsPolicy }} + {{- if .Values.controller.shareProcessNamespace }} + shareProcessNamespace: true + {{- end }} + containers: + - name: {{ include "nginx-ingress.name" . }} + image: {{ include "nginx-ingress.image" . }} + imagePullPolicy: "{{ .Values.controller.image.pullPolicy }}" +{{- if .Values.controller.lifecycle }} + lifecycle: +{{ toYaml .Values.controller.lifecycle | indent 10 }} +{{- end }} + ports: +{{- range $key, $value := .Values.controller.containerPort }} + - name: {{ $key }} + containerPort: {{ $value }} + protocol: TCP + {{- if and $.Values.controller.hostPort.enable (index $.Values.controller.hostPort $key) }} + hostPort: {{ index $.Values.controller.hostPort $key }} + {{- end }} +{{- end }} +{{ if .Values.controller.customPorts }} +{{ toYaml .Values.controller.customPorts | indent 8 }} +{{ end }} +{{- if .Values.prometheus.create }} + - name: prometheus + containerPort: {{ .Values.prometheus.port }} +{{- end }} +{{- if .Values.serviceInsight.create }} + - name: service-insight + containerPort: {{ .Values.serviceInsight.port }} +{{- end }} +{{- if .Values.controller.readyStatus.enable }} + - name: readiness-port + containerPort: {{ .Values.controller.readyStatus.port }} + readinessProbe: + httpGet: + path: /nginx-ready + port: readiness-port + periodSeconds: 1 + initialDelaySeconds: {{ .Values.controller.readyStatus.initialDelaySeconds }} +{{- end }} +{{- if .Values.controller.securityContext }} + securityContext: +{{ toYaml .Values.controller.securityContext | indent 10 }} +{{- else }} + securityContext: + allowPrivilegeEscalation: false + readOnlyRootFilesystem: {{ .Values.controller.readOnlyRootFilesystem }} + runAsUser: 101 #nginx + runAsNonRoot: true + capabilities: + drop: + - ALL + add: + - NET_BIND_SERVICE +{{- end }} +{{- include "nginx-ingress.volumeMounts" . | indent 8 }} + env: + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name +{{- if .Values.controller.env }} +{{ toYaml .Values.controller.env | indent 8 }} +{{- end }} +{{- if .Values.nginxServiceMesh.enable }} + - name: POD_SERVICEACCOUNT + valueFrom: + fieldRef: + fieldPath: spec.serviceAccountName +{{- end }} + resources: +{{ toYaml .Values.controller.resources | indent 10 }} + args: +{{- include "nginx-ingress.args" . | nindent 10 }} +{{- if .Values.controller.extraContainers }} + {{ toYaml .Values.controller.extraContainers | nindent 6 }} +{{- end }} + +{{- include "nginx-ingress.appprotect.v5" . | nindent 6 }} + +{{- if or (eq (include "nginx-ingress.readOnlyRootFilesystem" .) "true" ) .Values.controller.initContainers }} + initContainers: +{{- end }} +{{- if eq (include "nginx-ingress.readOnlyRootFilesystem" .) "true" }} + - name: init-{{ include "nginx-ingress.name" . }} + image: {{ include "nginx-ingress.image" . }} + imagePullPolicy: "{{ .Values.controller.image.pullPolicy }}" + command: ['cp', '-vdR', '/etc/nginx/.', '/mnt/etc'] +{{- if .Values.controller.initContainerResources }} + resources: +{{ toYaml .Values.controller.initContainerResources | indent 10 }} +{{- end }} +{{- if .Values.controller.initContainerSecurityContext }} + securityContext: +{{ toYaml .Values.controller.initContainerSecurityContext | indent 10 }} +{{- else }} + securityContext: + allowPrivilegeEscalation: false + readOnlyRootFilesystem: true + runAsUser: 101 #nginx + runAsNonRoot: true + capabilities: + drop: + - ALL +{{- end }} + volumeMounts: + - mountPath: /mnt/etc + name: nginx-etc +{{- end }} +{{- if .Values.controller.initContainers }} +{{ toYaml .Values.controller.initContainers | indent 6 }} +{{- end }} +{{- if .Values.controller.strategy }} + updateStrategy: +{{ toYaml .Values.controller.strategy | indent 4 }} +{{- end }} +{{- if .Values.controller.minReadySeconds }} + minReadySeconds: {{ .Values.controller.minReadySeconds }} +{{- end }} +{{- end }} diff --git a/charts/nginx-ingress/templates/controller-deployment.yaml b/charts/nginx-ingress/templates/controller-deployment.yaml new file mode 100644 index 0000000000..95bf3bb165 --- /dev/null +++ b/charts/nginx-ingress/templates/controller-deployment.yaml @@ -0,0 +1,186 @@ +{{- if eq .Values.controller.kind "deployment" }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "nginx-ingress.controller.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "nginx-ingress.labels" . | nindent 4 }} +{{- if .Values.controller.annotations }} + annotations: {{ toYaml .Values.controller.annotations | nindent 4 }} +{{- end }} +spec: + {{- if not .Values.controller.autoscaling.enabled }} + replicas: {{ .Values.controller.replicaCount }} + {{- end }} + selector: + matchLabels: + {{- include "nginx-ingress.selectorLabels" . | nindent 6 }} + template: + metadata: + labels: + {{- include "nginx-ingress.podLabels" . | nindent 8 }} +{{- if or .Values.prometheus.create .Values.controller.pod.annotations }} + annotations: +{{- if .Values.prometheus.create }} + prometheus.io/scrape: "true" + prometheus.io/port: "{{ .Values.prometheus.port }}" + prometheus.io/scheme: "{{ .Values.prometheus.scheme }}" +{{- end }} +{{- if .Values.controller.pod.annotations }} +{{ toYaml .Values.controller.pod.annotations | indent 8 }} +{{- end }} +{{- end }} + spec: +{{- if .Values.controller.nodeSelector }} + nodeSelector: +{{ toYaml .Values.controller.nodeSelector | indent 8 }} +{{- end }} +{{- if .Values.controller.tolerations }} + tolerations: +{{ toYaml .Values.controller.tolerations | indent 6 }} +{{- end }} +{{- if .Values.controller.affinity }} + affinity: +{{ toYaml .Values.controller.affinity | indent 8 }} +{{- end }} +{{- if .Values.controller.topologySpreadConstraints }} + topologySpreadConstraints: +{{ toYaml .Values.controller.topologySpreadConstraints | indent 8 }} +{{- end }} +{{- include "nginx-ingress.volumes" . | indent 6 }} +{{- if .Values.controller.priorityClassName }} + priorityClassName: {{ .Values.controller.priorityClassName }} +{{- end }} + serviceAccountName: {{ include "nginx-ingress.serviceAccountName" . }} + automountServiceAccountToken: true + securityContext: +{{ toYaml .Values.controller.podSecurityContext | indent 8 }} + terminationGracePeriodSeconds: {{ .Values.controller.terminationGracePeriodSeconds }} + hostNetwork: {{ .Values.controller.hostNetwork }} + dnsPolicy: {{ .Values.controller.dnsPolicy }} + {{- if .Values.controller.shareProcessNamespace }} + shareProcessNamespace: true + {{- end }} + containers: + - image: {{ include "nginx-ingress.image" . }} + name: {{ include "nginx-ingress.name" . }} + imagePullPolicy: "{{ .Values.controller.image.pullPolicy }}" +{{- if .Values.controller.lifecycle }} + lifecycle: +{{ toYaml .Values.controller.lifecycle | indent 10 }} +{{- end }} + ports: +{{- range $key, $value := .Values.controller.containerPort }} + - name: {{ $key }} + containerPort: {{ $value }} + protocol: TCP + {{- if and $.Values.controller.hostPort.enable (index $.Values.controller.hostPort $key) }} + hostPort: {{ index $.Values.controller.hostPort $key }} + {{- end }} +{{- end }} +{{- if .Values.controller.customPorts }} +{{ toYaml .Values.controller.customPorts | indent 8 }} +{{- end }} +{{- if .Values.prometheus.create }} + - name: prometheus + containerPort: {{ .Values.prometheus.port }} +{{- end }} +{{- if .Values.serviceInsight.create }} + - name: service-insight + containerPort: {{ .Values.serviceInsight.port }} +{{- end }} +{{- if .Values.controller.readyStatus.enable }} + - name: readiness-port + containerPort: {{ .Values.controller.readyStatus.port }} + readinessProbe: + httpGet: + path: /nginx-ready + port: readiness-port + periodSeconds: 1 + initialDelaySeconds: {{ .Values.controller.readyStatus.initialDelaySeconds }} +{{- end }} + resources: +{{ toYaml .Values.controller.resources | indent 10 }} +{{- if .Values.controller.securityContext }} + securityContext: +{{ toYaml .Values.controller.securityContext | indent 10 }} +{{- else }} + securityContext: + allowPrivilegeEscalation: false + readOnlyRootFilesystem: {{ .Values.controller.readOnlyRootFilesystem }} + runAsUser: 101 #nginx + runAsNonRoot: true + capabilities: + drop: + - ALL + add: + - NET_BIND_SERVICE +{{- end }} +{{- include "nginx-ingress.volumeMounts" . | indent 8 }} + env: + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name +{{- if .Values.controller.env }} +{{ toYaml .Values.controller.env | indent 8 }} +{{- end }} +{{- if .Values.nginxServiceMesh.enable }} + - name: POD_SERVICEACCOUNT + valueFrom: + fieldRef: + fieldPath: spec.serviceAccountName +{{- end }} + args: +{{- include "nginx-ingress.args" . | nindent 10 }} +{{- if .Values.controller.extraContainers }} + {{ toYaml .Values.controller.extraContainers | nindent 6 }} +{{- end }} + +{{- include "nginx-ingress.appprotect.v5" . | nindent 6 }} + +{{- if or ( eq (include "nginx-ingress.readOnlyRootFilesystem" .) "true" ) .Values.controller.initContainers }} + initContainers: +{{- end }} +{{- if eq (include "nginx-ingress.readOnlyRootFilesystem" .) "true" }} + - name: init-{{ include "nginx-ingress.name" . }} + image: {{ include "nginx-ingress.image" . }} + imagePullPolicy: "{{ .Values.controller.image.pullPolicy }}" + command: ['cp', '-vdR', '/etc/nginx/.', '/mnt/etc'] +{{- if .Values.controller.initContainerResources }} + resources: +{{ toYaml .Values.controller.initContainerResources | indent 10 }} +{{- end }} +{{- if .Values.controller.initContainerSecurityContext }} + securityContext: +{{ toYaml .Values.controller.initContainerSecurityContext | indent 10 }} +{{- else }} + securityContext: + allowPrivilegeEscalation: false + readOnlyRootFilesystem: true + runAsUser: 101 #nginx + runAsNonRoot: true + capabilities: + drop: + - ALL +{{- end }} + volumeMounts: + - mountPath: /mnt/etc + name: nginx-etc +{{- end }} +{{- if .Values.controller.initContainers }} +{{ toYaml .Values.controller.initContainers | indent 6 }} +{{- end }} +{{- if .Values.controller.strategy }} + strategy: +{{ toYaml .Values.controller.strategy | indent 4 }} +{{- end }} +{{- if .Values.controller.minReadySeconds }} + minReadySeconds: {{ .Values.controller.minReadySeconds }} +{{- end }} +{{- end }} diff --git a/deployments/helm-chart/templates/controller-globalconfiguration.yaml b/charts/nginx-ingress/templates/controller-globalconfiguration.yaml similarity index 75% rename from deployments/helm-chart/templates/controller-globalconfiguration.yaml rename to charts/nginx-ingress/templates/controller-globalconfiguration.yaml index b0bba48704..939923f2e0 100644 --- a/deployments/helm-chart/templates/controller-globalconfiguration.yaml +++ b/charts/nginx-ingress/templates/controller-globalconfiguration.yaml @@ -1,8 +1,8 @@ {{ if .Values.controller.globalConfiguration.create }} -apiVersion: k8s.nginx.org/v1alpha1 +apiVersion: k8s.nginx.org/v1 kind: GlobalConfiguration metadata: - name: {{ include "nginx-ingress.name" . }} + name: {{ include "nginx-ingress.controller.fullname" . }} namespace: {{ .Release.Namespace }} labels: {{- include "nginx-ingress.labels" . | nindent 4 }} diff --git a/charts/nginx-ingress/templates/controller-hpa.yaml b/charts/nginx-ingress/templates/controller-hpa.yaml new file mode 100644 index 0000000000..971aca90d3 --- /dev/null +++ b/charts/nginx-ingress/templates/controller-hpa.yaml @@ -0,0 +1,41 @@ +{{- if and .Values.controller.autoscaling.enabled (eq .Values.controller.kind "deployment") (.Capabilities.APIVersions.Has "autoscaling/v2") -}} +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: {{ include "nginx-ingress.controller.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "nginx-ingress.labels" . | nindent 4 }} +{{- if .Values.controller.autoscaling.annotations }} + annotations: +{{ toYaml .Values.controller.autoscaling.annotations | indent 4 }} +{{- end }} +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: {{ include "nginx-ingress.controller.fullname" . }} + minReplicas: {{ .Values.controller.autoscaling.minReplicas }} + maxReplicas: {{ .Values.controller.autoscaling.maxReplicas }} +{{- if .Values.controller.autoscaling.behavior }} + behavior: +{{ toYaml .Values.controller.autoscaling.behavior | indent 4 }} +{{- end }} + metrics: + {{- if .Values.controller.autoscaling.targetMemoryUtilizationPercentage }} + - type: Resource + resource: + name: memory + target: + type: Utilization + averageUtilization: {{ .Values.controller.autoscaling.targetMemoryUtilizationPercentage }} + {{- end }} + {{- if .Values.controller.autoscaling.targetCPUUtilizationPercentage }} + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: {{ .Values.controller.autoscaling.targetCPUUtilizationPercentage }} + {{- end }} +{{- end }} diff --git a/charts/nginx-ingress/templates/controller-ingress-class.yaml b/charts/nginx-ingress/templates/controller-ingress-class.yaml new file mode 100644 index 0000000000..a351d697c6 --- /dev/null +++ b/charts/nginx-ingress/templates/controller-ingress-class.yaml @@ -0,0 +1,14 @@ +{{ if .Values.controller.ingressClass.create }} +apiVersion: networking.k8s.io/v1 +kind: IngressClass +metadata: + name: {{ .Values.controller.ingressClass.name }} + labels: + {{- include "nginx-ingress.labels" . | nindent 4 }} +{{- if .Values.controller.ingressClass.setAsDefaultIngress }} + annotations: + ingressclass.kubernetes.io/is-default-class: "true" +{{- end }} +spec: + controller: nginx.org/ingress-controller +{{ end }} diff --git a/deployments/helm-chart/templates/controller-leader-election-configmap.yaml b/charts/nginx-ingress/templates/controller-leader-election-configmap.yaml similarity index 81% rename from deployments/helm-chart/templates/controller-leader-election-configmap.yaml rename to charts/nginx-ingress/templates/controller-leader-election-configmap.yaml index ef6f79a581..440914eb3e 100644 --- a/deployments/helm-chart/templates/controller-leader-election-configmap.yaml +++ b/charts/nginx-ingress/templates/controller-leader-election-configmap.yaml @@ -1,3 +1,4 @@ +{{- if .Values.controller.reportIngressStatus.enableLeaderElection }} apiVersion: v1 kind: ConfigMap metadata: @@ -9,3 +10,4 @@ metadata: annotations: {{ toYaml .Values.controller.reportIngressStatus.annotations | indent 4 }} {{- end }} +{{- end }} diff --git a/charts/nginx-ingress/templates/controller-lease.yaml b/charts/nginx-ingress/templates/controller-lease.yaml new file mode 100644 index 0000000000..960f61cea0 --- /dev/null +++ b/charts/nginx-ingress/templates/controller-lease.yaml @@ -0,0 +1,13 @@ +{{ if .Values.controller.reportIngressStatus.enableLeaderElection }} +apiVersion: coordination.k8s.io/v1 +kind: Lease +metadata: + name: {{ include "nginx-ingress.leaderElectionName" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "nginx-ingress.labels" . | nindent 4 }} +{{- if .Values.controller.reportIngressStatus.annotations }} + annotations +{{ toYaml .Values.controller.reportIngressStatus.annotations | indent 4 }} +{{- end }} +{{- end }} diff --git a/charts/nginx-ingress/templates/controller-pdb.yaml b/charts/nginx-ingress/templates/controller-pdb.yaml new file mode 100644 index 0000000000..1c3ddc8ae5 --- /dev/null +++ b/charts/nginx-ingress/templates/controller-pdb.yaml @@ -0,0 +1,23 @@ +{{- if .Values.controller.podDisruptionBudget.enabled -}} +apiVersion: policy/v1 +kind: PodDisruptionBudget +metadata: + name: {{ include "nginx-ingress.controller.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "nginx-ingress.labels" . | nindent 4 }} +{{- if .Values.controller.podDisruptionBudget.annotations }} + annotations: +{{ toYaml .Values.controller.podDisruptionBudget.annotations | indent 4 }} +{{- end }} +spec: + selector: + matchLabels: + {{- include "nginx-ingress.selectorLabels" . | nindent 6 }} +{{- if .Values.controller.podDisruptionBudget.minAvailable }} + minAvailable: {{ .Values.controller.podDisruptionBudget.minAvailable }} +{{- end }} +{{- if .Values.controller.podDisruptionBudget.maxUnavailable }} + maxUnavailable: {{ .Values.controller.podDisruptionBudget.maxUnavailable }} +{{- end }} +{{- end }} diff --git a/charts/nginx-ingress/templates/controller-prometheus-service.yaml b/charts/nginx-ingress/templates/controller-prometheus-service.yaml new file mode 100644 index 0000000000..d36514284b --- /dev/null +++ b/charts/nginx-ingress/templates/controller-prometheus-service.yaml @@ -0,0 +1,21 @@ +{{- if and .Values.prometheus.create .Values.prometheus.service.create}} +apiVersion: v1 +kind: Service +metadata: + name: {{ include "nginx-ingress.prometheus.serviceName" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "nginx-ingress.labels" . | nindent 4 }} + {{- if .Values.prometheus.service.labels -}} + {{- toYaml .Values.prometheus.service.labels | nindent 4 }} + {{- end }} +spec: + clusterIP: None + ports: + - name: prometheus + protocol: TCP + port: {{ .Values.prometheus.port }} + targetPort: {{ .Values.prometheus.port }} + selector: + {{- include "nginx-ingress.selectorLabels" . | nindent 4 }} +{{- end }} diff --git a/charts/nginx-ingress/templates/controller-role.yaml b/charts/nginx-ingress/templates/controller-role.yaml new file mode 100644 index 0000000000..cb75d99cc3 --- /dev/null +++ b/charts/nginx-ingress/templates/controller-role.yaml @@ -0,0 +1,56 @@ +{{- if .Values.rbac.create }} +kind: Role +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: {{ include "nginx-ingress.fullname" . }} + labels: + {{- include "nginx-ingress.labels" . | nindent 4 }} + namespace: {{ .Release.Namespace }} +rules: +- apiGroups: + - "" + resources: + - configmaps + - pods + - secrets + - services + verbs: + - get + - list + - watch +- apiGroups: + - "" + resources: + - namespaces + verbs: + - get +- apiGroups: + - "" + resources: + - pods + verbs: + - update +- apiGroups: + - "" + resources: + - events + verbs: + - create + - patch + - list +- apiGroups: + - coordination.k8s.io + resources: + - leases + resourceNames: + - {{ include "nginx-ingress.leaderElectionName" . }} + verbs: + - get + - update +- apiGroups: + - coordination.k8s.io + resources: + - leases + verbs: + - create +{{- end }} diff --git a/charts/nginx-ingress/templates/controller-rolebinding.yaml b/charts/nginx-ingress/templates/controller-rolebinding.yaml new file mode 100644 index 0000000000..51ee528da3 --- /dev/null +++ b/charts/nginx-ingress/templates/controller-rolebinding.yaml @@ -0,0 +1,17 @@ +{{- if .Values.rbac.create }} +kind: RoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: {{ include "nginx-ingress.fullname" . }} + labels: + {{- include "nginx-ingress.labels" . | nindent 4 }} + namespace: {{ .Release.Namespace }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: {{ include "nginx-ingress.fullname" . }} +subjects: +- kind: ServiceAccount + name: {{ include "nginx-ingress.serviceAccountName" . }} + namespace: {{ .Release.Namespace }} +{{- end }} diff --git a/deployments/helm-chart/templates/controller-secret.yaml b/charts/nginx-ingress/templates/controller-secret.yaml similarity index 100% rename from deployments/helm-chart/templates/controller-secret.yaml rename to charts/nginx-ingress/templates/controller-secret.yaml diff --git a/charts/nginx-ingress/templates/controller-service.yaml b/charts/nginx-ingress/templates/controller-service.yaml new file mode 100644 index 0000000000..405aa0e24f --- /dev/null +++ b/charts/nginx-ingress/templates/controller-service.yaml @@ -0,0 +1,72 @@ +{{- if .Values.controller.service.create }} +apiVersion: v1 +kind: Service +metadata: + name: {{ include "nginx-ingress.controller.service.name" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "nginx-ingress.labels" . | nindent 4 }} +{{- if .Values.controller.service.extraLabels }} +{{ toYaml .Values.controller.service.extraLabels | indent 4 }} +{{- end }} +{{- if .Values.controller.service.annotations }} + annotations: +{{ toYaml .Values.controller.service.annotations | indent 4 }} +{{- end }} +spec: +{{- if .Values.controller.service.clusterIP }} + clusterIP: {{ .Values.controller.service.clusterIP }} +{{- end }} +{{- if or (eq .Values.controller.service.type "LoadBalancer") (eq .Values.controller.service.type "NodePort") }} + {{- if .Values.controller.service.externalTrafficPolicy }} + externalTrafficPolicy: {{ .Values.controller.service.externalTrafficPolicy }} + {{- end }} +{{- end }} +{{- if eq .Values.controller.service.type "LoadBalancer" }} + {{- if hasKey .Values.controller.service "allocateLoadBalancerNodePorts" }} + allocateLoadBalancerNodePorts: {{ .Values.controller.service.allocateLoadBalancerNodePorts }} + {{- end }} + {{- if .Values.controller.service.loadBalancerIP }} + loadBalancerIP: {{ .Values.controller.service.loadBalancerIP }} + {{- end }} + {{- if .Values.controller.service.loadBalancerSourceRanges }} + loadBalancerSourceRanges: +{{ toYaml .Values.controller.service.loadBalancerSourceRanges | indent 4 }} + {{- end }} +{{- end }} + type: {{ .Values.controller.service.type }} + {{- if .Values.controller.service.ipFamilyPolicy }} + ipFamilyPolicy: {{ .Values.controller.service.ipFamilyPolicy }} + {{- end }} + {{- if .Values.controller.service.ipFamilies }} + ipFamilies: {{ .Values.controller.service.ipFamilies }} + {{- end }} + ports: +{{- if .Values.controller.service.customPorts }} +{{ toYaml .Values.controller.service.customPorts | indent 2 }} +{{ end }} +{{- if .Values.controller.service.httpPort.enable }} + - port: {{ .Values.controller.service.httpPort.port }} + targetPort: {{ .Values.controller.service.httpPort.targetPort }} + protocol: TCP + name: http + {{- if or (eq .Values.controller.service.type "LoadBalancer") (eq .Values.controller.service.type "NodePort") }} + nodePort: {{ .Values.controller.service.httpPort.nodePort }} + {{- end }} +{{- end }} +{{- if .Values.controller.service.httpsPort.enable }} + - port: {{ .Values.controller.service.httpsPort.port }} + targetPort: {{ .Values.controller.service.httpsPort.targetPort }} + protocol: TCP + name: https + {{- if or (eq .Values.controller.service.type "LoadBalancer") (eq .Values.controller.service.type "NodePort") }} + nodePort: {{ .Values.controller.service.httpsPort.nodePort }} + {{- end }} +{{- end }} + selector: + {{- include "nginx-ingress.selectorLabels" . | nindent 4 }} + {{- if .Values.controller.service.externalIPs }} + externalIPs: +{{ toYaml .Values.controller.service.externalIPs | indent 4 }} + {{- end }} +{{- end }} diff --git a/charts/nginx-ingress/templates/controller-serviceaccount.yaml b/charts/nginx-ingress/templates/controller-serviceaccount.yaml new file mode 100644 index 0000000000..8cde4f5b01 --- /dev/null +++ b/charts/nginx-ingress/templates/controller-serviceaccount.yaml @@ -0,0 +1,25 @@ +{{- if .Values.rbac.create }} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "nginx-ingress.serviceAccountName" . }} +{{- if .Values.controller.serviceAccount.annotations }} + annotations: {{- toYaml .Values.controller.serviceAccount.annotations | nindent 4 }} +{{- end }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "nginx-ingress.labels" . | nindent 4 }} +{{- if or .Values.controller.serviceAccount.imagePullSecretName .Values.controller.serviceAccount.imagePullSecretsNames }} +imagePullSecrets: +{{- end }} + +{{- if .Values.controller.serviceAccount.imagePullSecretName }} +- name: {{ .Values.controller.serviceAccount.imagePullSecretName}} +{{- end }} + +{{- if .Values.controller.serviceAccount.imagePullSecretsNames }} +{{- range .Values.controller.serviceAccount.imagePullSecretsNames }} +- name: {{ . }} +{{- end }} +{{- end }} +{{- end }} diff --git a/charts/nginx-ingress/templates/controller-servicemonitor.yaml b/charts/nginx-ingress/templates/controller-servicemonitor.yaml new file mode 100644 index 0000000000..e1a4268f95 --- /dev/null +++ b/charts/nginx-ingress/templates/controller-servicemonitor.yaml @@ -0,0 +1,21 @@ +{{- if .Values.prometheus.serviceMonitor.create }} +apiVersion: monitoring.coreos.com/v1 +kind: ServiceMonitor +metadata: + name: {{ include "nginx-ingress.controller.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "nginx-ingress.labels" . | nindent 4 }} + {{- if .Values.prometheus.serviceMonitor.labels -}} + {{- toYaml .Values.prometheus.serviceMonitor.labels | nindent 4 }} + {{- end }} +spec: + selector: + matchLabels: + {{- if .Values.prometheus.serviceMonitor.selectorMatchLabels -}} + {{- toYaml .Values.prometheus.serviceMonitor.selectorMatchLabels | nindent 6 }} + {{- end }} + {{- include "nginx-ingress.selectorLabels" . | nindent 6 }} + endpoints: + {{- toYaml .Values.prometheus.serviceMonitor.endpoints | nindent 4 }} +{{- end }} diff --git a/deployments/helm-chart/templates/controller-wildcard-secret.yaml b/charts/nginx-ingress/templates/controller-wildcard-secret.yaml similarity index 100% rename from deployments/helm-chart/templates/controller-wildcard-secret.yaml rename to charts/nginx-ingress/templates/controller-wildcard-secret.yaml diff --git a/deployments/helm-chart/values-icp.yaml b/charts/nginx-ingress/values-icp.yaml similarity index 91% rename from deployments/helm-chart/values-icp.yaml rename to charts/nginx-ingress/values-icp.yaml index 1d0a6e7012..c07addffd0 100644 --- a/deployments/helm-chart/values-icp.yaml +++ b/charts/nginx-ingress/values-icp.yaml @@ -1,9 +1,10 @@ controller: + name: controller kind: daemonset nginxplus: true image: repository: mycluster.icp:8500/kube-system/nginx-plus-ingress - tag: "2.4.1" + tag: "4.0.0" nodeSelector: beta.kubernetes.io/arch: "amd64" proxy: true diff --git a/charts/nginx-ingress/values-nsm.yaml b/charts/nginx-ingress/values-nsm.yaml new file mode 100644 index 0000000000..47d11e0571 --- /dev/null +++ b/charts/nginx-ingress/values-nsm.yaml @@ -0,0 +1,6 @@ +controller: + name: controller + enableLatencyMetrics: true +nginxServiceMesh: + enable: true + enableEgress: true diff --git a/charts/nginx-ingress/values-plus.yaml b/charts/nginx-ingress/values-plus.yaml new file mode 100644 index 0000000000..91e3493750 --- /dev/null +++ b/charts/nginx-ingress/values-plus.yaml @@ -0,0 +1,6 @@ +controller: + name: controller + nginxplus: true + image: + repository: nginx-plus-ingress + tag: "4.0.0" diff --git a/charts/nginx-ingress/values.schema.json b/charts/nginx-ingress/values.schema.json new file mode 100644 index 0000000000..1fe301a7e6 --- /dev/null +++ b/charts/nginx-ingress/values.schema.json @@ -0,0 +1,2702 @@ +{ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "type": "object", + "default": {}, + "title": "Root Schema", + "required": [ + "controller", + "rbac", + "prometheus", + "serviceInsight", + "nginxServiceMesh" + ], + "properties": { + "controller": { + "type": "object", + "default": {}, + "title": "The Ingress Controller Helm Schema", + "required": [ + "name", + "kind", + "image" + ], + "properties": { + "name": { + "type": "string", + "default": "", + "title": "The name of the Ingress Controller", + "examples": [ + "controller" + ] + }, + "kind": { + "type": "string", + "default": "", + "title": "The kind of the Ingress Controller", + "enum": [ + "deployment", + "daemonset" + ], + "examples": [ + "deployment", + "daemonset" + ] + }, + "selectorLabels": { + "type": "object", + "default": {}, + "title": "The selectorLabels Schema", + "$ref": "https://raw.githubusercontent.com/nginxinc/kubernetes-json-schema/master/v1.32.0/_definitions.json#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelector/properties/matchLabels" + }, + "annotations": { + "type": "object", + "default": {}, + "title": "The annotations Schema", + "$ref": "https://raw.githubusercontent.com/nginxinc/kubernetes-json-schema/master/v1.32.0/_definitions.json#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta/properties/annotations" + }, + "nginxplus": { + "type": "boolean", + "default": false, + "title": "Deploys the Ingress Controller for NGINX Plus", + "examples": [ + false, + true + ] + }, + "debug": { + "type": "object", + "default": {}, + "title": "Runs the container with Delve, expects a version of the IC container with dlv as the entrypoint", + "properties": { + "enable": { + "type": "boolean", + "default": false, + "title": "Runs the container with Delve, expects a version of the IC container with dlv as the entrypoint", + "examples": [ + false, + true + ] + }, + "continue": { + "type": "boolean", + "default": true, + "title": "Starts Delve with --continue which means that IC will not wait for a debugger attach to start", + "examples": [ + false, + true + ] + } + }, + "examples": [ + { + "enable": true, + "continue": "fatal" + } + ] + }, + "mgmt": { + "type": "object", + "default": {}, + "title": "The mgmt block Schema", + "properties": { + "licenseTokenSecretName": { + "type": "string", + "default": "", + "title": "The licenseTokenSecretName Schema", + "examples": [ + "nginx-plus-secret", + "license-token", + "license" + ] + }, + "sslCertificateSecretName": { + "type": "string", + "default": "", + "title": "The sslCertificateSecretName Schema", + "examples": [ + "ssl-certificate" + ] + }, + "usageReport": { + "type": "object", + "default": {}, + "title": "The usageReport Schema", + "properties": { + "endpoint": { + "type": "string", + "title": "The endpoint of the usageReport", + "default": "", + "examples": [ + "", + "product.connect.nginx.com", + "nginx-mgmt.local" + ] + }, + "interval": { + "type": "string", + "pattern": "^[0-9]+[mhd]$", + "default": "1h", + "title": "The usage report interval Schema", + "examples": [ + "1m", + "1h", + "24h" + ] + } + } + }, + "enforceInitialReport": { + "type": "boolean", + "default": false, + "title": "The enforceInitialReport Schema", + "examples": [ + true, + false + ] + }, + "sslVerify": { + "type": "boolean", + "default": true, + "title": "The sslVerify Schema", + "examples": [ + true, + false + ] + }, + "resolver": { + "type": "object", + "default": {}, + "title": "The resolver Schema", + "properties": { + "addresses": { + "type": "array", + "default": [], + "title": "List of resolver addresses/fqdns" + }, + "valid": { + "type": "string", + "pattern": "^[0-9]+[smhdwMy]$", + "title": "A valid nginx time", + "examples": [ + "1m", + "5d" + ] + }, + "ipv6": { + "type": "boolean", + "title": "turn on or off ipv6 support", + "default": false + } + } + }, + "sslTrustedCertificateSecretName": { + "type": "string", + "default": "ssl-trusted-cert", + "title": "The sslTrustedCertificateSecretName Schema", + "examples": [ + "ssl-trusted-cert", + "certificate-secret" + ] + }, + "configMapName": { + "type": "string", + "default": "", + "title": "The configMap Schema", + "examples": [ + "" + ] + } + }, + "examples": [ + { + "licenseTokenSecretName": "license-token" + } + ] + }, + "nginxReloadTimeout": { + "type": "integer", + "default": 0, + "title": "Timeout in milliseconds which the Ingress Controller will wait for a successful NGINX reload after a change or at the initial start", + "examples": [ + 60000 + ] + }, + "appprotect": { + "type": "object", + "default": {}, + "title": "The App Protect WAF Schema", + "required": [ + "enable" + ], + "properties": { + "enable": { + "type": "boolean", + "default": false, + "title": "Enable the App Protect WAF module in the Ingress Controller", + "examples": [ + false, + true + ] + }, + "v5": { + "type": "boolean", + "default": false, + "title": "Enables App Protect WAF v5.", + "examples": [ + false, + true + ] + }, + "logLevel": { + "type": "string", + "default": "", + "title": "The logLevel for App Protect WAF", + "enum": [ + "fatal", + "error", + "warn", + "info", + "debug", + "trace" + ], + "examples": [ + "fatal", + "error", + "warn", + "info", + "debug", + "trace" + ] + }, + "volumes": { + "type": "array", + "default": [ + { + "name": "app-protect-bd-config", + "emptyDir": {} + }, + { + "name": "app-protect-config", + "emptyDir": {} + }, + { + "name": "app-protect-bundles", + "emptyDir": {} + } + ], + "title": "Volumes for App Protect WAF v5", + "items": { + "type": "object", + "$ref": "https://raw.githubusercontent.com/nginxinc/kubernetes-json-schema/master/v1.32.0/_definitions.json#/definitions/io.k8s.api.core.v1.Volume" + } + }, + "enforcer": { + "type": "object", + "properties": { + "host": { + "type": "string", + "default": "127.0.0.1", + "title": "Port which the App Protect WAF v5 Enforcer process runs on", + "examples": [ + "127.0.0.1" + ] + }, + "port": { + "type": "integer", + "default": 50000, + "title": "Port which the App Protect WAF v5 Enforcer process runs on", + "examples": [ + 50000 + ] + }, + "image": { + "type": "object", + "default": {}, + "title": "The image Schema", + "required": [ + "repository" + ], + "properties": { + "repository": { + "type": "string", + "default": "private-registry.nginx.com/nap/waf-enforcer", + "title": "The repository of the App Protect WAF v5 Enforcer image", + "examples": [ + "private-registry.nginx.com/nap/waf-enforcer" + ] + }, + "tag": { + "type": "string", + "default": "5.4.0", + "title": "The tag of the App Protect WAF v5 Enforcer image", + "examples": [ + "5.4.0" + ] + }, + "digest": { + "type": "string", + "default": "", + "title": "The digest of the App Protect WAF v5 Enforcer image", + "examples": [ + "sha256:2710c264e8eaeb663cee63db37b75a1ac1709f63a130fb091c843a6c3a4dc572" + ] + }, + "pullPolicy": { + "type": "string", + "default": "IfNotPresent", + "title": "The pullPolicy for the App Protect WAF v5 Enforcer image", + "allOf": [ + { + "$ref": "https://raw.githubusercontent.com/nginxinc/kubernetes-json-schema/master/v1.32.0/_definitions.json#/definitions/io.k8s.api.core.v1.Container/properties/imagePullPolicy" + }, + { + "enum": [ + "Always", + "IfNotPresent", + "Never" + ] + } + ], + "examples": [ + "Always", + "IfNotPresent", + "Never" + ] + } + }, + "examples": [ + { + "repository": "private-registry.nginx.com/nap/waf-enforcer", + "tag": "5.4.0", + "pullPolicy": "IfNotPresent" + } + ] + }, + "securityContext": { + "type": "object", + "default": {}, + "title": "The securityContext Schema", + "$ref": "https://raw.githubusercontent.com/nginxinc/kubernetes-json-schema/master/v1.32.0/_definitions.json#/definitions/io.k8s.api.core.v1.SecurityContext" + } + } + }, + "configManager": { + "type": "object", + "properties": { + "image": { + "type": "object", + "default": {}, + "title": "The image Schema", + "required": [ + "repository" + ], + "properties": { + "repository": { + "type": "string", + "default": "private-registry.nginx.com/nap/waf-config-mgr", + "title": "The repository of the App Protect WAF v5 Config Manager image", + "examples": [ + "private-registry.nginx.com/nap/waf-config-mgr" + ] + }, + "tag": { + "type": "string", + "default": "5.4.0", + "title": "The tag of the App Protect WAF v5 Config Manager image", + "examples": [ + "5.4.0" + ] + }, + "digest": { + "type": "string", + "default": "", + "title": "The digest of the App Protect WAF v5 Config Manager image", + "examples": [ + "sha256:2710c264e8eaeb663cee63db37b75a1ac1709f63a130fb091c843a6c3a4dc572" + ] + }, + "pullPolicy": { + "type": "string", + "default": "IfNotPresent", + "title": "The pullPolicy for the App Protect WAF v5 Config Manager image", + "allOf": [ + { + "$ref": "https://raw.githubusercontent.com/nginxinc/kubernetes-json-schema/master/v1.32.0/_definitions.json#/definitions/io.k8s.api.core.v1.Container/properties/imagePullPolicy" + }, + { + "enum": [ + "Always", + "IfNotPresent", + "Never" + ] + } + ], + "examples": [ + "Always", + "IfNotPresent", + "Never" + ] + } + }, + "examples": [ + { + "repository": "private-registry.nginx.com/nap/waf-config-mgr", + "tag": "5.4.0", + "pullPolicy": "IfNotPresent" + } + ] + }, + "securityContext": { + "type": "object", + "default": { + "allowPrivilegeEscalation": false, + "runAsUser": 101, + "runAsNonRoot": true, + "capabilities": { + "drop": [ + "all" + ] + } + }, + "title": "The securityContext Schema", + "$ref": "https://raw.githubusercontent.com/nginxinc/kubernetes-json-schema/master/v1.32.0/_definitions.json#/definitions/io.k8s.api.core.v1.SecurityContext" + } + } + } + }, + "examples": [ + { + "enable": true, + "logLevel": "fatal" + } + ] + }, + "appprotectdos": { + "type": "object", + "default": {}, + "title": "The App Protect DoS Schema", + "required": [ + "enable" + ], + "properties": { + "enable": { + "type": "boolean", + "default": false, + "title": "Enable the App Protect DoS module in the Ingress Controller", + "examples": [ + false, + true + ] + }, + "debug": { + "type": "boolean", + "default": false, + "title": "debugging for App Protect DoS", + "examples": [ + false, + true + ] + }, + "maxWorkers": { + "type": "integer", + "default": 0, + "title": "Max number of nginx processes to support", + "examples": [ + 0 + ] + }, + "maxDaemons": { + "type": "integer", + "default": 0, + "title": "Max number of ADMD instances", + "examples": [ + 0 + ] + }, + "memory": { + "type": "integer", + "default": 0, + "title": "RAM memory size to consume in MB", + "examples": [ + 0 + ] + } + }, + "examples": [ + { + "enable": true, + "debug": false, + "maxWorkers": 0, + "maxDaemons": 0, + "memory": 0 + } + ] + }, + "hostNetwork": { + "type": "boolean", + "default": false, + "title": "The hostNetwork Schema", + "examples": [ + false, + true + ] + }, + "hostPort": { + "type": "object", + "default": {}, + "title": "The hostPort Schema", + "patternProperties": { + "^.*$": { + "anyOf": [ + { + "$ref": "https://raw.githubusercontent.com/nginxinc/kubernetes-json-schema/master/v1.32.0/_definitions.json#/definitions/io.k8s.api.core.v1.ContainerPort/properties/hostPort" + }, + { + "type": "boolean" + } + ] + } + }, + "additionalProperties": false + }, + "containerPort": { + "type": "object", + "default": {}, + "title": "The containerPort Schema", + "patternProperties": { + "^.*$": { + "$ref": "https://raw.githubusercontent.com/nginxinc/kubernetes-json-schema/master/v1.32.0/_definitions.json#/definitions/io.k8s.api.core.v1.ContainerPort/properties/containerPort" + } + }, + "additionalProperties": false + }, + "dnsPolicy": { + "type": "string", + "allOf": [ + { + "$ref": "https://raw.githubusercontent.com/nginxinc/kubernetes-json-schema/master/v1.32.0/_definitions.json#/definitions/io.k8s.api.core.v1.PodSpec/properties/dnsPolicy" + }, + { + "enum": [ + "ClusterFirstWithHostNet", + "ClusterFirst", + "Default", + "None" + ] + } + ] + }, + "nginxDebug": { + "type": "boolean", + "default": false, + "title": "Enables debugging for NGINX", + "examples": [ + false, + true + ] + }, + "shareProcessNamespace": { + "type": "boolean", + "default": false, + "title": "Enables sharing of the process namespace between pods within the Ingress Controller", + "examples": [ + false, + true + ] + }, + "logLevel": { + "type": "string", + "default": "info", + "title": "The logLevel of the Ingress Controller", + "enum": [ + "trace", + "debug", + "info", + "warning", + "error", + "fatal" + ], + "examples": [ + "info" + ] + }, + "logFormat": { + "type": "string", + "default": "glog", + "title": "The logFormat of the Ingress Controller", + "enum": [ + "glog", + "json", + "text" + ], + "examples": [ + "json" + ] + }, + "customPorts": { + "type": "array", + "default": [], + "title": "The customPorts to expose on the NGINX Ingress Controller pod", + "items": { + "type": "object", + "$ref": "https://raw.githubusercontent.com/nginxinc/kubernetes-json-schema/master/v1.32.0/_definitions.json#/definitions/io.k8s.api.core.v1.ContainerPort" + }, + "examples": [ + [ + { + "name": "http", + "containerPort": 80, + "protocol": "TCP" + }, + { + "name": "https", + "containerPort": 443, + "protocol": "TCP" + } + ] + ] + }, + "image": { + "type": "object", + "default": {}, + "title": "The image Schema", + "required": [ + "repository" + ], + "properties": { + "repository": { + "type": "string", + "default": "nginx/nginx-ingress", + "title": "The repository of the Ingress Controller", + "examples": [ + "nginx/nginx-ingress" + ] + }, + "tag": { + "type": "string", + "default": "4.0.0", + "title": "The tag of the Ingress Controller image", + "examples": [ + "4.0.0" + ] + }, + "digest": { + "type": "string", + "default": "", + "title": "The digest of the Ingress Controller image", + "examples": [ + "sha256:2710c264e8eaeb663cee63db37b75a1ac1709f63a130fb091c843a6c3a4dc572" + ] + }, + "pullPolicy": { + "type": "string", + "default": "IfNotPresent", + "title": "The pullPolicy for the Ingress Controller image", + "allOf": [ + { + "$ref": "https://raw.githubusercontent.com/nginxinc/kubernetes-json-schema/master/v1.32.0/_definitions.json#/definitions/io.k8s.api.core.v1.Container/properties/imagePullPolicy" + }, + { + "enum": [ + "Always", + "IfNotPresent", + "Never" + ] + } + ], + "examples": [ + "Always", + "IfNotPresent", + "Never" + ] + } + }, + "examples": [ + { + "repository": "nginx/nginx-ingress", + "tag": "4.0.0", + "pullPolicy": "IfNotPresent" + } + ] + }, + "lifecycle": { + "type": "object", + "default": {}, + "title": "The lifecycle Schema", + "$ref": "https://raw.githubusercontent.com/nginxinc/kubernetes-json-schema/master/v1.32.0/_definitions.json#/definitions/io.k8s.api.core.v1.Lifecycle" + }, + "customConfigMap": { + "type": "string", + "default": "", + "title": "The customConfigMap Schema", + "examples": [ + "" + ] + }, + "config": { + "type": "object", + "default": {}, + "title": "The config Schema", + "required": [], + "properties": { + "name": { + "type": "string", + "default": "", + "title": "The name Schema", + "examples": [ + "" + ] + }, + "annotations": { + "type": "object", + "default": {}, + "title": "The annotations Schema", + "$ref": "https://raw.githubusercontent.com/nginxinc/kubernetes-json-schema/master/v1.32.0/_definitions.json#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta/properties/annotations" + }, + "entries": { + "type": "object", + "default": {}, + "title": "The entries Schema", + "required": [], + "properties": {}, + "examples": [ + {} + ] + } + }, + "examples": [ + { + "name": "", + "annotations": {}, + "entries": {} + } + ] + }, + "defaultTLS": { + "type": "object", + "default": {}, + "title": "The defaultTLS Schema", + "required": [], + "properties": { + "cert": { + "type": "string", + "default": "", + "title": "The cert Schema", + "examples": [] + }, + "key": { + "type": "string", + "default": "", + "title": "The key Schema", + "examples": [] + }, + "secret": { + "type": "string", + "default": "", + "title": "The secret Schema", + "examples": [ + "" + ] + } + }, + "examples": [] + }, + "wildcardTLS": { + "type": "object", + "default": {}, + "title": "The wildcardTLS Schema", + "required": [], + "properties": { + "cert": { + "type": "string", + "default": "", + "title": "The cert Schema", + "examples": [ + "" + ] + }, + "key": { + "type": "string", + "default": "", + "title": "The key Schema", + "examples": [ + "" + ] + }, + "secret": { + "type": "string", + "default": "", + "title": "The secret Schema", + "examples": [ + "" + ] + } + }, + "examples": [] + }, + "nodeSelector": { + "type": "object", + "default": {}, + "title": "The nodeSelector Schema", + "$ref": "https://raw.githubusercontent.com/nginxinc/kubernetes-json-schema/master/v1.32.0/_definitions.json#/definitions/io.k8s.api.core.v1.PodSpec/properties/nodeSelector" + }, + "terminationGracePeriodSeconds": { + "type": "integer", + "default": 30, + "title": "The terminationGracePeriodSeconds Schema", + "$ref": "https://raw.githubusercontent.com/nginxinc/kubernetes-json-schema/master/v1.32.0/_definitions.json#/definitions/io.k8s.api.core.v1.PodSpec/properties/terminationGracePeriodSeconds" + }, + "podSecurityContext": { + "type": "object", + "default": {}, + "title": "The podSecurityContext Schema", + "$ref": "https://raw.githubusercontent.com/nginxinc/kubernetes-json-schema/master/v1.32.0/_definitions.json#/definitions/io.k8s.api.core.v1.PodSecurityContext" + }, + "securityContext": { + "type": "object", + "default": {}, + "title": "The securityContext Schema", + "$ref": "https://raw.githubusercontent.com/nginxinc/kubernetes-json-schema/master/v1.32.0/_definitions.json#/definitions/io.k8s.api.core.v1.SecurityContext" + }, + "initContainerSecurityContext": { + "type": "object", + "default": {}, + "title": "The initContainerSecurityContext Schema", + "$ref": "https://raw.githubusercontent.com/nginxinc/kubernetes-json-schema/master/v1.32.0/_definitions.json#/definitions/io.k8s.api.core.v1.SecurityContext" + }, + "resources": { + "type": "object", + "default": {}, + "title": "The resources Schema", + "$ref": "https://raw.githubusercontent.com/nginxinc/kubernetes-json-schema/master/v1.32.0/_definitions.json#/definitions/io.k8s.api.core.v1.ResourceRequirements" + }, + "initContainerResources": { + "type": "object", + "default": {}, + "title": "The resources Schema", + "$ref": "https://raw.githubusercontent.com/nginxinc/kubernetes-json-schema/master/v1.32.0/_definitions.json#/definitions/io.k8s.api.core.v1.ResourceRequirements" + }, + "tolerations": { + "type": "array", + "default": [], + "title": "The tolerations Schema", + "items": { + "type": "object", + "$ref": "https://raw.githubusercontent.com/nginxinc/kubernetes-json-schema/master/v1.32.0/_definitions.json#/definitions/io.k8s.api.core.v1.Toleration" + } + }, + "affinity": { + "type": "object", + "default": {}, + "title": "The affinity Schema", + "$ref": "https://raw.githubusercontent.com/nginxinc/kubernetes-json-schema/master/v1.32.0/_definitions.json#/definitions/io.k8s.api.core.v1.Affinity" + }, + "topologySpreadConstraints": { + "type": "object", + "default": {}, + "title": "The topologySpreadConstraints Schema", + "$ref": "https://raw.githubusercontent.com/nginxinc/kubernetes-json-schema/master/v1.32.0/_definitions.json#/definitions/io.k8s.api.core.v1.PodSpec/properties/topologySpreadConstraints" + }, + "env": { + "type": "array", + "default": [], + "title": "The env Schema", + "items": { + "type": "object", + "$ref": "https://raw.githubusercontent.com/nginxinc/kubernetes-json-schema/master/v1.32.0/_definitions.json#/definitions/io.k8s.api.core.v1.EnvVar" + } + }, + "volumes": { + "type": "array", + "default": [], + "title": "The volumes Schema", + "items": { + "type": "object", + "$ref": "https://raw.githubusercontent.com/nginxinc/kubernetes-json-schema/master/v1.32.0/_definitions.json#/definitions/io.k8s.api.core.v1.Volume" + } + }, + "volumeMounts": { + "type": "array", + "default": [], + "title": "The volumeMounts Schema", + "items": { + "type": "object", + "$ref": "https://raw.githubusercontent.com/nginxinc/kubernetes-json-schema/master/v1.32.0/_definitions.json#/definitions/io.k8s.api.core.v1.VolumeMount" + } + }, + "initContainers": { + "type": "array", + "default": [], + "title": "The initContainers Schema", + "items": { + "type": "object", + "$ref": "https://raw.githubusercontent.com/nginxinc/kubernetes-json-schema/master/v1.32.0/_definitions.json#/definitions/io.k8s.api.core.v1.Container" + } + }, + "minReadySeconds": { + "type": "integer", + "default": 0, + "title": "The minReadySeconds Schema", + "$ref": "https://raw.githubusercontent.com/nginxinc/kubernetes-json-schema/master/v1.32.0/_definitions.json#/definitions/io.k8s.api.apps.v1.DeploymentSpec/properties/minReadySeconds" + }, + "strategy": { + "type": "object", + "default": {}, + "title": "The strategy Schema", + "allOf": [ + { + "$ref": "https://raw.githubusercontent.com/nginxinc/kubernetes-json-schema/master/v1.32.0/_definitions.json#/definitions/io.k8s.api.apps.v1.DeploymentStrategy" + }, + { + "properties": { + "type": { + "type": "string", + "enum": [ + "Recreate", + "RollingUpdate", + "OnDelete" + ] + } + } + } + ] + }, + "extraContainers": { + "type": "array", + "default": [], + "title": "The extraContainers Schema", + "items": { + "type": "object", + "$ref": "https://raw.githubusercontent.com/nginxinc/kubernetes-json-schema/master/v1.32.0/_definitions.json#/definitions/io.k8s.api.core.v1.Container" + } + }, + "replicaCount": { + "type": "integer", + "default": 1, + "title": "The replicaCount", + "examples": [ + 1 + ] + }, + "ingressClass": { + "type": "object", + "default": {}, + "title": "The ingressClass", + "required": [], + "properties": { + "create": { + "type": "boolean", + "default": true, + "title": "The create", + "examples": [ + true + ] + }, + "name": { + "type": "string", + "default": "", + "title": "The ingressClass name", + "examples": [ + "nginx" + ] + }, + "setAsDefaultIngress": { + "type": "boolean", + "default": false, + "title": "The setAsDefaultIngress", + "examples": [ + false + ] + } + } + }, + "watchNamespace": { + "type": "string", + "default": "", + "title": "The watchNamespace", + "examples": [ + "" + ] + }, + "watchSecretNamespace": { + "type": "string", + "default": "", + "title": "The watchSecretNamespace", + "examples": [ + "" + ] + }, + "enableCustomResources": { + "type": "boolean", + "default": false, + "title": "The enableCustomResources", + "examples": [ + true + ] + }, + "enableOIDC": { + "type": "boolean", + "default": false, + "title": "The enableOIDC", + "examples": [ + false + ] + }, + "enableTLSPassthrough": { + "type": "boolean", + "default": false, + "title": "The enableTLSPassthrough", + "examples": [ + false + ] + }, + "tlsPassthroughPort": { + "type": "integer", + "default": 443, + "title": "The tlsPassthroughPort", + "examples": [ + 443 + ] + }, + "enableCertManager": { + "type": "boolean", + "default": false, + "title": "The enableCertManager", + "examples": [ + false + ] + }, + "enableExternalDNS": { + "type": "boolean", + "default": false, + "title": "The enableExternalDNS", + "examples": [ + false + ] + }, + "globalConfiguration": { + "type": "object", + "default": {}, + "title": "The globalConfiguration Schema", + "required": [ + "create", + "spec" + ], + "properties": { + "create": { + "type": "boolean", + "default": false, + "title": "The create Schema", + "examples": [ + false + ] + }, + "spec": { + "type": "object", + "default": {}, + "title": "The spec Schema", + "required": [], + "properties": { + "listeners": { + "type": "array", + "default": [], + "title": "The listeners Schema", + "items": { + "type": "object", + "default": {}, + "properties": { + "port": { + "type": "integer", + "default": 0, + "title": "The port", + "examples": [ + 5353 + ] + }, + "protocol": { + "type": "string", + "default": "", + "title": "The protocol", + "examples": [ + "TCP" + ] + }, + "name": { + "type": "string", + "default": "", + "title": "The name", + "examples": [ + "dns-tcp" + ] + }, + "ipv4": { + "type": "string", + "default": "", + "title": "The ipv4 ip", + "examples": [ + "127.0.0.1" + ] + }, + "ipv6": { + "type": "string", + "default": "", + "title": "The ipv6 ip", + "examples": [ + "::1" + ] + } + } + } + } + }, + "examples": [ + {} + ] + } + }, + "examples": [ + { + "create": false, + "spec": {} + } + ] + }, + "enableSnippets": { + "type": "boolean", + "default": false, + "title": "The enableSnippets", + "examples": [ + false + ] + }, + "healthStatus": { + "type": "boolean", + "default": false, + "title": "The healthStatus", + "examples": [ + false + ] + }, + "healthStatusURI": { + "type": "string", + "format": "uri-reference", + "default": "/nginx-health", + "title": "The healthStatusURI Schema", + "examples": [ + "/nginx-health" + ] + }, + "nginxStatus": { + "type": "object", + "default": {}, + "title": "The nginxStatus Schema", + "required": [], + "properties": { + "enable": { + "type": "boolean", + "default": false, + "title": "The enable", + "examples": [ + true + ] + }, + "port": { + "type": "integer", + "default": 8080, + "title": "The port", + "examples": [ + 8080 + ] + }, + "allowCidrs": { + "type": "string", + "default": "127.0.0.1", + "title": "The allowCidrs", + "examples": [ + "127.0.0.1" + ] + } + }, + "examples": [ + { + "enable": true, + "port": 8080, + "allowCidrs": "127.0.0.1" + } + ] + }, + "service": { + "type": "object", + "default": {}, + "title": "The service Schema", + "required": [], + "properties": { + "create": { + "type": "boolean", + "default": false, + "title": "The create", + "examples": [ + true + ] + }, + "type": { + "type": "string", + "default": "", + "title": "The type", + "$ref": "https://raw.githubusercontent.com/nginxinc/kubernetes-json-schema/master/v1.32.0/_definitions.json#/definitions/io.k8s.api.core.v1.ServiceSpec/properties/type" + }, + "externalTrafficPolicy": { + "type": "string", + "default": "", + "title": "The externalTrafficPolicy", + "$ref": "https://raw.githubusercontent.com/nginxinc/kubernetes-json-schema/master/v1.32.0/_definitions.json#/definitions/io.k8s.api.core.v1.ServiceSpec/properties/externalTrafficPolicy" + }, + "annotations": { + "type": "object", + "default": {}, + "title": "The annotations", + "$ref": "https://raw.githubusercontent.com/nginxinc/kubernetes-json-schema/master/v1.32.0/_definitions.json#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta/properties/annotations" + }, + "extraLabels": { + "type": "object", + "default": {}, + "title": "The extraLabels", + "required": [], + "properties": {}, + "examples": [ + {} + ] + }, + "loadBalancerIP": { + "type": "string", + "default": "", + "title": "The loadBalancerIP", + "$ref": "https://raw.githubusercontent.com/nginxinc/kubernetes-json-schema/master/v1.32.0/_definitions.json#/definitions/io.k8s.api.core.v1.ServiceSpec/properties/loadBalancerIP" + }, + "externalIPs": { + "type": "array", + "default": [], + "title": "The externalIPs", + "$ref": "https://raw.githubusercontent.com/nginxinc/kubernetes-json-schema/master/v1.32.0/_definitions.json#/definitions/io.k8s.api.core.v1.ServiceSpec/properties/externalIPs" + }, + "loadBalancerSourceRanges": { + "type": "array", + "default": [], + "title": "The loadBalancerSourceRanges", + "items": {}, + "examples": [ + [] + ] + }, + "allocateLoadBalancerNodePorts": { + "type": "boolean", + "default": false, + "title": "The allocateLoadBalancerNodePorts Schema", + "ref": "https://raw.githubusercontent.com/nginxinc/kubernetes-json-schema/master/v1.32.0/_definitions.json#/definitions/io.k8s.api.core.v1.ServiceSpec/properties/allocateLoadBalancerNodePorts" + }, + "ipFamilyPolicy": { + "type": "string", + "default": "", + "title": "The ipFamilyPolicy Schema", + "$ref": "https://raw.githubusercontent.com/nginxinc/kubernetes-json-schema/master/v1.32.0/_definitions.json#/definitions/io.k8s.api.core.v1.ServiceSpec/properties/ipFamilyPolicy", + "examples": [ + "" + ] + }, + "ipFamilies": { + "type": "array", + "default": [], + "title": "The ipFamilies Schema", + "ref": "https://raw.githubusercontent.com/nginxinc/kubernetes-json-schema/master/v1.32.0/_definitions.json#/definitions/io.k8s.api.core.v1.ServiceSpec/properties/ipFamilies" + }, + "httpPort": { + "type": "object", + "default": {}, + "title": "The httpPort", + "required": [], + "properties": { + "enable": { + "type": "boolean", + "default": false, + "title": "The enable", + "examples": [ + true + ] + }, + "port": { + "type": "integer", + "default": 0, + "title": "The port", + "examples": [ + 80 + ] + }, + "nodePort": { + "type": "integer", + "default": 0, + "title": "The nodePort", + "examples": [ + 443 + ] + }, + "targetPort": { + "type": "integer", + "default": 0, + "title": "The targetPort", + "examples": [ + 80 + ] + } + }, + "examples": [ + { + "enable": true, + "port": 80, + "nodePort": "", + "targetPort": 80 + } + ] + }, + "httpsPort": { + "type": "object", + "default": {}, + "title": "The httpsPort", + "required": [], + "properties": { + "enable": { + "type": "boolean", + "default": false, + "title": "The enable", + "examples": [ + true + ] + }, + "port": { + "type": "integer", + "default": 0, + "title": "The port", + "examples": [ + 443 + ] + }, + "nodePort": { + "type": "integer", + "default": 0, + "title": "The nodePort", + "examples": [ + 443 + ] + }, + "targetPort": { + "type": "integer", + "default": 0, + "title": "The targetPort", + "examples": [ + 443 + ] + } + }, + "examples": [ + { + "enable": true, + "port": 443, + "nodePort": "", + "targetPort": 443 + } + ] + }, + "customPorts": { + "type": "array", + "default": [], + "title": "The customPorts", + "items": { + "type": "object", + "ref": "https://raw.githubusercontent.com/nginxinc/kubernetes-json-schema/master/v1.32.0/_definitions.json#/definitions/io.k8s.api.core.v1.ServicePort" + } + } + }, + "examples": [ + { + "create": true, + "type": "LoadBalancer", + "externalTrafficPolicy": "Local", + "annotations": {}, + "extraLabels": {}, + "loadBalancerIP": "", + "externalIPs": [], + "loadBalancerSourceRanges": [], + "name": "", + "allocateLoadBalancerNodePorts": false, + "ipFamilyPolicy": "", + "ipFamilies": [], + "httpPort": { + "enable": true, + "port": 80, + "targetPort": 80 + }, + "httpsPort": { + "enable": true, + "port": 443, + "targetPort": 443 + }, + "customPorts": [] + } + ] + }, + "serviceAccount": { + "type": "object", + "default": {}, + "title": "The serviceAccount Schema", + "required": [], + "properties": { + "annotations": { + "type": "object", + "default": {}, + "title": "The annotations Schema", + "$ref": "https://raw.githubusercontent.com/nginxinc/kubernetes-json-schema/master/v1.32.0/_definitions.json#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta/properties/annotations" + }, + "name": { + "type": "string", + "default": "", + "title": "The name Schema", + "examples": [ + "" + ] + }, + "imagePullSecretName": { + "type": "string", + "default": "", + "title": "The imagePullSecretName", + "examples": [ + "" + ] + }, + "imagePullSecretsNames": { + "type": "array", + "default": [], + "title": "The imagePullSecretName list", + "examples": [ + [] + ] + } + }, + "oneOf": [ + { + "properties": { + "imagePullSecretName": { + "maxLength": 0 + }, + "imagePullSecretsNames": { + "minItems": 1 + } + }, + "required": [ + "imagePullSecretsNames" + ] + }, + { + "properties": { + "imagePullSecretName": { + "minLength": 1 + }, + "imagePullSecretsNames": { + "maxItems": 0 + } + }, + "required": [ + "imagePullSecretName" + ] + }, + { + "properties": { + "imagePullSecretName": { + "maxLength": 0 + }, + "imagePullSecretsNames": { + "maxItems": 0 + } + }, + "required": [ + "imagePullSecretName", + "imagePullSecretsNames" + ] + }, + { + "properties": { + "imagePullSecretName": { + "maxLength": 0 + }, + "imagePullSecretsNames": { + "maxItems": 0 + } + }, + "not": { + "required": [ + "imagePullSecretName", + "imagePullSecretsNames" + ] + } + } + ], + "examples": [ + { + "name": "", + "imagePullSecretName": "", + "imagePullSecretsNames": [] + } + ] + }, + "reportIngressStatus": { + "type": "object", + "default": {}, + "title": "The reportIngressStatus Schema", + "required": [ + "enable" + ], + "properties": { + "enable": { + "type": "boolean", + "default": false, + "title": "The enable", + "examples": [ + true + ] + }, + "externalService": { + "type": "string", + "default": "", + "title": "The externalService", + "examples": [ + "" + ] + }, + "ingressLink": { + "type": "string", + "default": "", + "title": "The ingressLink", + "examples": [ + "" + ] + }, + "enableLeaderElection": { + "type": "boolean", + "default": false, + "title": "The enableLeaderElection", + "examples": [ + true + ] + }, + "leaderElectionLockName": { + "type": "string", + "default": "", + "title": "The leaderElectionLockName", + "examples": [ + "" + ] + }, + "annotations": { + "type": "object", + "default": {}, + "title": "The annotations Schema", + "$ref": "https://raw.githubusercontent.com/nginxinc/kubernetes-json-schema/master/v1.32.0/_definitions.json#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta/properties/annotations" + } + }, + "examples": [ + { + "enable": true, + "externalService": "", + "ingressLink": "", + "enableLeaderElection": true, + "leaderElectionLockName": "", + "annotations": {} + } + ] + }, + "pod": { + "type": "object", + "default": {}, + "title": "The pod Schema", + "required": [], + "properties": { + "annotations": { + "type": "object", + "default": {}, + "title": "The annotations Schema", + "$ref": "https://raw.githubusercontent.com/nginxinc/kubernetes-json-schema/master/v1.32.0/_definitions.json#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta/properties/annotations" + }, + "extraLabels": { + "type": "object", + "default": {}, + "title": "The extraLabels Schema", + "$ref": "https://raw.githubusercontent.com/nginxinc/kubernetes-json-schema/master/v1.32.0/_definitions.json#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta/properties/labels" + } + }, + "examples": [ + { + "annotations": {}, + "extraLabels": {} + } + ] + }, + "priorityClassName": { + "type": "string", + "default": "", + "title": "The priorityClassName", + "$ref": "https://raw.githubusercontent.com/nginxinc/kubernetes-json-schema/master/v1.32.0/_definitions.json#/definitions/io.k8s.api.core.v1.PodSpec/properties/priorityClassName" + }, + "podDisruptionBudget": { + "type": "object", + "default": {}, + "title": "The podDisruptionBudget Schema", + "required": [ + "enabled" + ], + "properties": { + "enabled": { + "type": "boolean" + }, + "annotations": { + "type": "object", + "default": {}, + "title": "The annotations Schema", + "$ref": "https://raw.githubusercontent.com/nginxinc/kubernetes-json-schema/master/v1.32.0/_definitions.json#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta/properties/annotations" + }, + "minAvailable": { + "$ref": "https://raw.githubusercontent.com/nginxinc/kubernetes-json-schema/master/v1.32.0/_definitions.json#/definitions/io.k8s.api.policy.v1.PodDisruptionBudgetSpec/properties/minAvailable" + }, + "maxUnavailable": { + "$ref": "https://raw.githubusercontent.com/nginxinc/kubernetes-json-schema/master/v1.32.0/_definitions.json#/definitions/io.k8s.api.policy.v1.PodDisruptionBudgetSpec/properties/maxUnavailable" + } + }, + "examples": [ + { + "enable": true, + "minAvailable": 1 + }, + { + "enable": true, + "maxUnavailable": 1 + } + ] + }, + "readyStatus": { + "type": "object", + "default": {}, + "title": "The readyStatus", + "required": [], + "properties": { + "enable": { + "type": "boolean", + "default": false, + "title": "The enable", + "examples": [ + true + ] + }, + "port": { + "type": "integer", + "default": 0, + "title": "The port", + "examples": [ + 8081 + ] + }, + "initialDelaySeconds": { + "type": "integer", + "default": 0, + "$ref": "https://raw.githubusercontent.com/nginxinc/kubernetes-json-schema/master/v1.32.0/_definitions.json#/definitions/io.k8s.api.core.v1.Probe/properties/initialDelaySeconds" + } + }, + "examples": [ + { + "enable": true, + "port": 8081, + "initialDelaySeconds": 0 + } + ] + }, + "enableLatencyMetrics": { + "type": "boolean", + "default": false, + "title": "The enableLatencyMetrics", + "examples": [ + false + ] + }, + "disableIPV6": { + "type": "boolean", + "default": false, + "title": "The disableIPV6", + "examples": [ + false + ] + }, + "defaultHTTPListenerPort": { + "type": "integer", + "default": 80, + "title": "The defaultHTTPListenerPort", + "examples": [ + 80 + ] + }, + "defaultHTTPSListenerPort": { + "type": "integer", + "default": 443, + "title": "The defaultHTTPSListenerPort", + "examples": [ + 443 + ] + }, + "readOnlyRootFilesystem": { + "type": "boolean", + "default": false, + "title": "The readOnlyRootFilesystem", + "examples": [ + false + ] + }, + "enableSSLDynamicReload": { + "type": "boolean", + "default": true, + "title": "Enable dynamic certificate reloads for NGINX Plus", + "examples": [ + true + ] + }, + "telemetryReporting": { + "type": "object", + "default": {}, + "title": "Configure telemetry reporting options", + "required": [], + "properties": { + "enable": { + "type": "boolean", + "default": true, + "title": "Enable telemetry reporting", + "examples": [ + true + ] + } + } + }, + "enableWeightChangesDynamicReload": { + "type": "boolean", + "default": false, + "title": "Enables weight changes without reloading for NGINX Plus", + "examples": [ + false + ] + } + }, + "examples": [ + { + "name": "controller", + "kind": "deployment", + "nginxplus": false, + "nginxReloadTimeout": 60000, + "appprotect": { + "enable": false, + "v5": false, + "logLevel": "fatal", + "volumes": [ + { + "name": "app-protect-bd-config", + "emptyDir": {} + }, + { + "name": "app-protect-config", + "emptyDir": {} + }, + { + "name": "app-protect-bundles", + "emptyDir": {} + } + ], + "enforcer": { + "host": "127.0.0.1", + "port": 50000, + "image": { + "repository": "private-registry.nginx.com/nap/waf-enforcer", + "tag": "5.4.0", + "pullPolicy": "IfNotPresent" + }, + "securityContext": {} + }, + "configManager": { + "image": { + "repository": "private-registry.nginx.com/nap/waf-config-mgr", + "tag": "5.4.0", + "pullPolicy": "IfNotPresent" + }, + "securityContext": { + "allowPrivilegeEscalation": false, + "runAsUser": 101, + "runAsNonRoot": true, + "capabilities": { + "drop": [ + "all" + ] + } + } + } + }, + "appprotectdos": { + "enable": false, + "debug": false, + "maxWorkers": 0, + "maxDaemons": 0, + "memory": 0 + }, + "hostNetwork": false, + "hostPort": { + "enable": false, + "http": 80, + "https": 443 + }, + "containerPort": { + "http": 80, + "https": 443 + }, + "dnsPolicy": "ClusterFirst", + "nginxDebug": false, + "shareProcessNamespace": false, + "logLevel": "info", + "logFormat": "glog", + "customPorts": [], + "image": { + "repository": "nginx/nginx-ingress", + "tag": "4.0.0", + "digest": "", + "pullPolicy": "IfNotPresent" + }, + "lifecycle": {}, + "customConfigMap": "", + "config": { + "name": "", + "annotations": {}, + "entries": {} + }, + "defaultTLS": { + "cert": "", + "key": "", + "secret": "" + }, + "wildcardTLS": { + "cert": "", + "key": "", + "secret": "" + }, + "nodeSelector": {}, + "terminationGracePeriodSeconds": 30, + "autoscaling": { + "enabled": false, + "annotations": {}, + "minReplicas": 1, + "maxReplicas": 3, + "targetCPUUtilizationPercentage": 50, + "targetMemoryUtilizationPercentage": 50, + "behavior": {} + }, + "resources": { + "requests": { + "cpu": "100m", + "memory": "128Mi" + } + }, + "podSecurityContext": { + "seccompProfile": { + "type": "RuntimeDefault" + } + }, + "securityContext": {}, + "initContainerSecurityContext": {}, + "initContainerResources": { + "requests": { + "cpu": "100m", + "memory": "128Mi" + } + }, + "tolerations": [], + "affinity": {}, + "env": [], + "volumes": [], + "volumeMounts": [], + "initContainers": [], + "minReadySeconds": 0, + "podDisruptionBudget": { + "enabled": false, + "annotations": {}, + "minAvailable": 1, + "minUnavailable": 1 + }, + "strategy": {}, + "extraContainers": [], + "replicaCount": 1, + "ingressClass": { + "name": "nginx", + "create": true, + "setAsDefaultIngress": false + }, + "watchNamespace": "", + "enableCustomResources": true, + "enableOIDC": false, + "enableTLSPassthrough": false, + "tlsPassthroughPort": 443, + "enableCertManager": false, + "enableExternalDNS": false, + "globalConfiguration": { + "create": false, + "spec": {} + }, + "enableSnippets": false, + "healthStatus": false, + "healthStatusURI": "/nginx-health", + "nginxStatus": { + "enable": true, + "port": 8080, + "allowCidrs": "127.0.0.1" + }, + "service": { + "create": true, + "type": "LoadBalancer", + "externalTrafficPolicy": "Local", + "annotations": {}, + "extraLabels": {}, + "loadBalancerIP": "", + "clusterIP": "", + "externalIPs": [], + "loadBalancerSourceRanges": [], + "allocateLoadBalancerNodePorts": false, + "ipFamilyPolicy": "", + "ipFamilies": [], + "httpPort": { + "enable": true, + "port": 80, + "nodePort": 8443, + "targetPort": 80 + }, + "httpsPort": { + "enable": true, + "port": 443, + "nodePort": 8443, + "targetPort": 443 + }, + "customPorts": [] + }, + "serviceAccount": { + "annotations": {}, + "name": "", + "imagePullSecretName": "", + "imagePullSecretsNames": [] + }, + "reportIngressStatus": { + "enable": true, + "externalService": "", + "ingressLink": "", + "enableLeaderElection": true, + "leaderElectionLockName": "", + "annotations": {} + }, + "pod": { + "annotations": {}, + "extraLabels": {} + }, + "priorityClassName": "", + "readyStatus": { + "enable": true, + "port": 8081, + "initialDelaySeconds": 0 + }, + "enableLatencyMetrics": false, + "disableIPV6": false, + "defaultHTTPListenerPort": 80, + "defaultHTTPSListenerPort": 443, + "readOnlyRootFilesystem": false, + "enableSSLDynamicReload": true, + "telemetryReporting": { + "enable": true + }, + "enableWeightChangesDynamicReload": false + } + ] + }, + "rbac": { + "type": "object", + "default": {}, + "title": "The rbac Schema", + "required": [ + "create" + ], + "properties": { + "create": { + "type": "boolean", + "default": false, + "title": "The create Schema", + "examples": [ + true + ] + } + }, + "examples": [ + { + "create": true + } + ] + }, + "prometheus": { + "type": "object", + "default": {}, + "title": "The prometheus Schema", + "required": [ + "create" + ], + "properties": { + "create": { + "type": "boolean", + "default": false, + "title": "The create", + "examples": [ + true + ] + }, + "port": { + "type": "integer", + "default": 9113, + "title": "The port", + "examples": [ + 9113 + ] + }, + "secret": { + "type": "string", + "default": "", + "title": "The secret", + "examples": [ + "" + ] + }, + "scheme": { + "type": "string", + "default": "http", + "title": "The scheme", + "examples": [ + "http" + ] + }, + "service": { + "type": "object", + "default": {}, + "properties": { + "create": { + "type": "boolean", + "default": false, + "title": "The create", + "examples": [ + true + ] + }, + "labels": { + "type": "object", + "default": {}, + "title": "The labels Schema", + "$ref": "https://raw.githubusercontent.com/nginxinc/kubernetes-json-schema/master/v1.32.0/_definitions.json#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta/properties/labels" + } + } + }, + "serviceMonitor": { + "type": "object", + "default": {}, + "title": "The serviceMonitor Schema", + "required": [], + "properties": { + "create": { + "type": "boolean", + "default": false, + "title": "The create", + "examples": [ + false + ] + }, + "labels": { + "type": "object", + "default": {}, + "title": "The labels Schema", + "$ref": "https://raw.githubusercontent.com/nginxinc/kubernetes-json-schema/master/v1.32.0/_definitions.json#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta/properties/labels" + }, + "selectorMatchLabels": { + "type": "object", + "default": {}, + "title": "The selectorMatchLabels Schema", + "$ref": "https://raw.githubusercontent.com/nginxinc/kubernetes-json-schema/master/v1.32.0/_definitions.json#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelector/properties/matchLabels" + }, + "endpoints": { + "type": "array", + "default": [], + "title": "The endpoints", + "required": [], + "items": {} + } + }, + "examples": [ + { + "create": false, + "labels": {}, + "selectorMatchLabels": {}, + "endpoints": [] + } + ] + } + }, + "examples": [ + { + "create": true, + "port": 9113, + "secret": "", + "scheme": "http" + } + ] + }, + "serviceInsight": { + "type": "object", + "default": {}, + "title": "The Service Insight Schema", + "required": [ + "create" + ], + "properties": { + "create": { + "type": "boolean", + "default": false, + "title": "The create", + "examples": [ + true + ] + }, + "port": { + "type": "integer", + "default": 9114, + "title": "The port", + "examples": [ + 9114 + ] + }, + "secret": { + "type": "string", + "default": "", + "title": "The secret", + "examples": [ + "" + ] + }, + "scheme": { + "type": "string", + "default": "http", + "title": "The scheme", + "examples": [ + "http" + ] + } + }, + "examples": [ + { + "create": true, + "port": 9114, + "secret": "", + "scheme": "http" + } + ] + }, + "nginxServiceMesh": { + "type": "object", + "default": {}, + "title": "The nginxServiceMesh Schema", + "required": [ + "enable" + ], + "properties": { + "enable": { + "type": "boolean", + "default": false, + "title": "The enable", + "examples": [ + false + ] + }, + "enableEgress": { + "type": "boolean", + "default": false, + "title": "The enableEgress", + "examples": [ + false + ] + } + }, + "examples": [ + { + "enable": false, + "enableEgress": false + } + ] + }, + "nginxAgent": { + "type": "object", + "default": { + "enable": false + }, + "title": "Configuration for NGINX Agent.", + "required": [ + "enable" + ], + "properties": { + "enable": { + "type": "boolean", + "default": false, + "title": "Enable NGINX Agent", + "examples": [ + false + ] + }, + "instanceGroup": { + "type": "string", + "default": "", + "title": "Set the --instance-group argument for NGINX Agent", + "examples": [ + "my-instance-group" + ] + }, + "logLevel": { + "type": "string", + "default": "info", + "title": "Log level for NGINX Agent", + "enum": [ + "panic", + "fatal", + "error", + "info", + "debug", + "trace" + ], + "examples": [ + "error" + ] + }, + "instanceManager": { + "type": "object", + "default": {}, + "title": "Configuration for the connection to NGINX Instance Manager", + "examples": [], + "required": [ + "host" + ], + "properties": { + "host": { + "type": "string", + "title": "FQDN or IP for connecting to NGINX Instance Manager", + "examples": [ + "nim.example.com" + ] + }, + "grpcPort": { + "type": "integer", + "title": "Port for connecting to NGINX Instance Manager", + "default": 443, + "examples": [ + 443 + ] + }, + "sni": { + "type": "string", + "title": "Server Name Indication for NGINX Instance Manager", + "default": "", + "examples": [ + "nim.example.com" + ] + }, + "tls": { + "type": "object", + "default": {}, + "title": "TLS configuration for connection between NGINX Agent and NGINX Instance Manager", + "properties": { + "enable": { + "type": "boolean", + "default": "true", + "title": "enable TLS for NGINX Instance Manager connection" + }, + "secret": { + "type": "string", + "default": "", + "title": "kubernetes.io/tls secret with a TLS certificate and key for using mTLS between NGINX Agent and NGINX Instance Manager" + }, + "caSecret": { + "type": "string", + "default": "", + "title": "nginx.org/ca secret for verification of Instance Manager TLS" + }, + "skipVerify": { + "type": "boolean", + "default": "false", + "title": "skip certificate verification" + } + } + } + } + }, + "syslog": { + "type": "object", + "default": { + "host": "127.0.0.1", + "port": 1514 + }, + "title": "Syslog listener which NGINX Agent uses to accept messages from App Protect WAF", + "properties": { + "host": { + "type": "string", + "title": "Address for NGINX Agent to run syslog listener", + "default": "127.0.0.1", + "examples": [ + "127.0.0.1" + ] + }, + "port": { + "type": "integer", + "title": "Port for NGINX Agent to run syslog listener", + "default": 1514, + "examples": [ + 1514 + ] + } + } + }, + "napMonitoring": { + "type": "object", + "default": {}, + "title": "NGINX App Protect Monitoring config", + "properties": { + "collectorBufferSize": { + "type": "integer", + "default": 50000, + "title": "Buffer size for collector. Will contain log lines and parsed log lines", + "examples": [ + 50000 + ] + }, + "processorBufferSize": { + "type": "integer", + "default": 50000, + "title": "Buffer size for processor. Will contain log lines and parsed log lines", + "examples": [ + 50000 + ] + } + } + }, + "customConfigMap": { + "type": "string", + "title": "The name of a custom ConfigMap to use instead of the one provided by default", + "default": "", + "examples": [ + "my-custom-configmap" + ] + } + } + } + }, + "examples": [ + { + "controller": { + "name": "controller", + "kind": "deployment", + "nginxplus": false, + "nginxReloadTimeout": 60000, + "appprotect": { + "enable": false, + "v5": false, + "logLevel": "fatal", + "volumes": [ + { + "name": "app-protect-bd-config", + "emptyDir": {} + }, + { + "name": "app-protect-config", + "emptyDir": {} + }, + { + "name": "app-protect-bundles", + "emptyDir": {} + } + ], + "enforcer": { + "host": "127.0.0.1", + "port": 50000, + "image": { + "repository": "private-registry.nginx.com/nap/waf-enforcer", + "tag": "5.4.0", + "pullPolicy": "IfNotPresent" + }, + "securityContext": {} + }, + "configManager": { + "image": { + "repository": "private-registry.nginx.com/nap/waf-config-mgr", + "tag": "5.4.0", + "pullPolicy": "IfNotPresent" + }, + "securityContext": { + "allowPrivilegeEscalation": false, + "runAsUser": 101, + "runAsNonRoot": true, + "capabilities": { + "drop": [ + "all" + ] + } + } + } + }, + "appprotectdos": { + "enable": false, + "debug": false, + "maxWorkers": 0, + "maxDaemons": 0, + "memory": 0 + }, + "hostNetwork": false, + "hostPort": { + "enable": false, + "http": 80, + "https": 443 + }, + "containerPort": { + "http": 80, + "https": 443 + }, + "dnsPolicy": "ClusterFirst", + "nginxDebug": false, + "shareProcessNamespace": false, + "logLevel": "info", + "logFormat": "glog", + "customPorts": [], + "image": { + "repository": "nginx/nginx-ingress", + "tag": "4.0.0", + "digest": "", + "pullPolicy": "IfNotPresent" + }, + "lifecycle": {}, + "customConfigMap": "", + "config": { + "name": "", + "annotations": {}, + "entries": {} + }, + "defaultTLS": { + "cert": "", + "key": "", + "secret": "" + }, + "wildcardTLS": { + "cert": "", + "key": "", + "secret": "" + }, + "nodeSelector": {}, + "terminationGracePeriodSeconds": 30, + "autoscaling": { + "enabled": false, + "annotations": {}, + "minReplicas": 1, + "maxReplicas": 3, + "targetCPUUtilizationPercentage": 50, + "targetMemoryUtilizationPercentage": 50, + "behavior": {} + }, + "resources": { + "requests": { + "cpu": "100m", + "memory": "128Mi" + } + }, + "podSecurityContext": { + "seccompProfile": { + "type": "RuntimeDefault" + } + }, + "securityContext": {}, + "initContainerSecurityContext": {}, + "initContainerResources": { + "requests": { + "cpu": "100m", + "memory": "128Mi" + } + }, + "tolerations": [], + "affinity": {}, + "env": [], + "volumes": [], + "volumeMounts": [], + "initContainers": [], + "minReadySeconds": 0, + "podDisruptionBudget": { + "enabled": false, + "annotations": {}, + "minAvailable": 1, + "minUnavailable": 1 + }, + "strategy": {}, + "extraContainers": [], + "replicaCount": 1, + "ingressClass": { + "name": "nginx", + "create": true, + "setAsDefaultIngress": false + }, + "watchNamespace": "", + "enableCustomResources": true, + "enableOIDC": false, + "enableTLSPassthrough": false, + "tlsPassthroughPort": 443, + "enableCertManager": false, + "enableExternalDNS": false, + "globalConfiguration": { + "create": false, + "spec": {} + }, + "enableSnippets": false, + "healthStatus": false, + "healthStatusURI": "/nginx-health", + "nginxStatus": { + "enable": true, + "port": 8080, + "allowCidrs": "127.0.0.1" + }, + "service": { + "create": true, + "type": "LoadBalancer", + "externalTrafficPolicy": "Local", + "annotations": {}, + "extraLabels": {}, + "loadBalancerIP": "", + "clusterIP": "", + "externalIPs": [], + "loadBalancerSourceRanges": [], + "allocateLoadBalancerNodePorts": false, + "ipFamilyPolicy": "", + "ipFamilies": [], + "httpPort": { + "enable": true, + "port": 80, + "nodePort": 8080, + "targetPort": 80 + }, + "httpsPort": { + "enable": true, + "port": 443, + "nodePort": 8443, + "targetPort": 443 + }, + "customPorts": [] + }, + "serviceAccount": { + "annotations": {}, + "name": "", + "imagePullSecretName": "", + "imagePullSecretsNames": [] + }, + "reportIngressStatus": { + "enable": true, + "externalService": "", + "ingressLink": "", + "enableLeaderElection": true, + "leaderElectionLockName": "", + "annotations": {} + }, + "pod": { + "annotations": {}, + "extraLabels": {} + }, + "priorityClassName": "", + "readyStatus": { + "enable": true, + "port": 8081, + "initialDelaySeconds": 0 + }, + "enableLatencyMetrics": false, + "disableIPV6": false, + "defaultHTTPListenerPort": 80, + "defaultHTTPSListenerPort": 443, + "readOnlyRootFilesystem": false, + "enableSSLDynamicReload": true, + "telemetryReporting": { + "enable": true + }, + "enableWeightChangesDynamicReload": false + }, + "rbac": { + "create": true, + "clusterrole": { + "create": true + } + }, + "prometheus": { + "create": true, + "port": 9113, + "secret": "", + "scheme": "http", + "service": { + "create": false, + "labels": { + "service": "nginx-ingress-prometheus-service" + } + }, + "serviceMonitor": { + "create": false, + "labels": {}, + "selectorMatchLabels": { + "service": "nginx-ingress-prometheus-service" + }, + "endpoints": [ + { + "port": "prometheus" + } + ] + } + }, + "serviceInsight": { + "create": false, + "port": 9114, + "secret": "", + "scheme": "http" + }, + "nginxServiceMesh": { + "enable": false, + "enableEgress": false + }, + "nginxAgent": { + "enable": false, + "instanceGroup": "", + "logLevel": "error", + "syslog": { + "host": "127.0.0.1", + "port": 1514 + }, + "napMonitoring": { + "collectorBufferSize": 50000, + "processorBufferSize": 50000 + }, + "instanceManager": { + "host": "", + "grpcPort": 443, + "sni": "", + "tls": { + "enabled": true, + "skipVerify": false, + "secret": "", + "caSecret": "" + } + }, + "customConfigMap": "" + } + } + ] +} diff --git a/charts/nginx-ingress/values.yaml b/charts/nginx-ingress/values.yaml new file mode 100644 index 0000000000..09f89f295f --- /dev/null +++ b/charts/nginx-ingress/values.yaml @@ -0,0 +1,683 @@ +controller: + ## The name of the Ingress Controller daemonset or deployment. + name: controller + + ## The kind of the Ingress Controller installation - deployment or daemonset. + kind: deployment + + ## The selectorLabels used to override the default values. + selectorLabels: {} + + ## Annotations for deployments and daemonsets + annotations: {} + + ## Deploys the Ingress Controller for NGINX Plus. + nginxplus: false + + ## Configures NGINX mgmt block for NGINX Plus + mgmt: + ## Secret name of license token for NGINX Plus + licenseTokenSecretName: "license-token" # required for NGINX Plus + + ## Enables the 180-day grace period for sending the initial usage report + # enforceInitialReport: false + + # usageReport: + # endpoint: "product.connect.nginx.com" # Endpoint for usage report + # interval: 1h + + ## Configures the ssl_verify directive in the mgmt block + # sslVerify: true + + ## Configures the resolver directive in the mgmt block + # resolver: + # ipv6: true + # valid: 1s + # addresses: + # - kube-dns.kube-system.svc.cluster.local + + ## Secret containing TLS client certificate + # sslCertificateSecretName: ssl-certificate # kubernetes.io/tls secret type + + ## Secret containing trusted CA certificate + # sslTrustedCertificateSecretName: ssl-trusted-cert + + # configMapName allows changing the name of the MGMT config map + # the name should not include a namespace + # configMapName: "" + + + ## Timeout in milliseconds which the Ingress Controller will wait for a successful NGINX reload after a change or at the initial start. + nginxReloadTimeout: 60000 + + ## Support for App Protect WAF + appprotect: + ## Enable the App Protect WAF module in the Ingress Controller. + enable: false + ## Enables App Protect WAF v5. + v5: false + ## Sets log level for App Protect WAF. Allowed values: fatal, error, warn, info, debug, trace + # logLevel: fatal + + # Volumes for App Protect WAF v5 + # Required volumes are: app-protect-bd-config, app-protect-config, and app-protect-bundles + volumes: + - name: app-protect-bd-config + emptyDir: {} + - name: app-protect-config + emptyDir: {} + - name: app-protect-bundles + emptyDir: {} + + ## Configuration for App Protect WAF v5 Enforcer + enforcer: + # Host that the App Protect WAF v5 Enforcer runs on. + # This will normally be "127.0.0.1" as the Enforcer container + # will run in the same pod as the Ingress Controller container. + host: "127.0.0.1" + # Port that the App Protect WAF v5 Enforcer runs on. + port: 50000 + image: + ## The image repository of the App Protect WAF v5 Enforcer. + repository: private-registry.nginx.com/nap/waf-enforcer + + ## The tag of the App Protect WAF v5 Enforcer image. + tag: "5.4.0" + ## The digest of the App Protect WAF v5 Enforcer image. + ## If digest is specified it has precedence over tag and will be used instead + # digest: "sha256:CHANGEME" + + ## The pull policy for the App Protect WAF v5 Enforcer image. + pullPolicy: IfNotPresent + securityContext: {} + + ## Configuration for App Protect WAF v5 Configuration Manager + configManager: + image: + ## The image repository of the App Protect WAF v5 Configuration Manager. + repository: private-registry.nginx.com/nap/waf-config-mgr + + ## The tag of the App Protect WAF v5 Configuration Manager image. + tag: "5.4.0" + ## The digest of the App Protect WAF v5 Configuration Manager image. + ## If digest is specified it has precedence over tag and will be used instead + # digest: "sha256:CHANGEME" + + ## The pull policy for the App Protect WAF v5 Configuration Manager image. + pullPolicy: IfNotPresent + securityContext: + allowPrivilegeEscalation: false + runAsUser: 101 #nginx + runAsNonRoot: true + capabilities: + drop: + - all + + ## Support for App Protect DoS + appprotectdos: + ## Enable the App Protect DoS module in the Ingress Controller. + enable: false + ## Enable debugging for App Protect DoS. + debug: false + ## Max number of nginx processes to support. + maxWorkers: 0 + ## Max number of ADMD instances. + maxDaemons: 0 + ## RAM memory size to consume in MB. + memory: 0 + + ## Enables the Ingress Controller pods to use the host's network namespace. + hostNetwork: false + + ## The hostPort configuration for the Ingress Controller pods. + hostPort: + ## Enables hostPort for the Ingress Controller pods. + enable: false + + ## The HTTP hostPort configuration for the Ingress Controller pods. + http: 80 + + ## The HTTPS hostPort configuration for the Ingress Controller pods. + https: 443 + + containerPort: + ## The HTTP containerPort configuration for the Ingress Controller pods. + http: 80 + + ## The HTTPS containerPort configuration for the Ingress Controller pods. + https: 443 + + ## DNS policy for the Ingress Controller pods + dnsPolicy: ClusterFirst + + ## Enables debugging for NGINX. Uses the nginx-debug binary. Requires error-log-level: debug in the ConfigMap via `controller.config.entries`. + nginxDebug: false + + ## Share process namespace between containers in the Ingress Controller pod. + shareProcessNamespace: false + + ## The log level of the Ingress Controller. Options include: trace, debug, info, warning, error, fatal + logLevel: info + + ## Sets the log format of Ingress Controller. Options include: glog, json, text + logFormat: glog + + ## A list of custom ports to expose on the NGINX Ingress Controller pod. Follows the conventional Kubernetes yaml syntax for container ports. + customPorts: [] + + image: + ## The image repository of the Ingress Controller. + repository: nginx/nginx-ingress + + ## The tag of the Ingress Controller image. If not specified the appVersion from Chart.yaml is used as a tag. + # tag: "4.0.0" + ## The digest of the Ingress Controller image. + ## If digest is specified it has precedence over tag and will be used instead + # digest: "sha256:CHANGEME" + + ## The pull policy for the Ingress Controller image. + pullPolicy: IfNotPresent + + ## The lifecycle of the Ingress Controller pods. + lifecycle: {} + + ## The custom ConfigMap to use instead of the one provided by default + customConfigMap: "" + + config: + ## The name of the ConfigMap used by the Ingress Controller. + ## Autogenerated if not set or set to "". + # name: nginx-config + + ## The annotations of the Ingress Controller configmap. + annotations: {} + + ## The entries of the ConfigMap for customizing NGINX configuration. + entries: {} + + ## It is recommended to use your own TLS certificates and keys + defaultTLS: + ## The base64-encoded TLS certificate for the default HTTPS server. + ## Note: It is recommended that you specify your own certificate. Alternatively, omitting the default server secret completely will configure NGINX to reject TLS connections to the default server. + cert: "" + + ## The base64-encoded TLS key for the default HTTPS server. + ## Note: It is recommended that you specify your own key. Alternatively, omitting the default server secret completely will configure NGINX to reject TLS connections to the default server. + key: "" + + ## The secret with a TLS certificate and key for the default HTTPS server. + ## The value must follow the following format: `/`. + ## Used as an alternative to specifying a certificate and key using `controller.defaultTLS.cert` and `controller.defaultTLS.key` parameters. + ## Note: Alternatively, omitting the default server secret completely will configure NGINX to reject TLS connections to the default server. + ## Format: / + secret: "" + + wildcardTLS: + ## The base64-encoded TLS certificate for every Ingress/VirtualServer host that has TLS enabled but no secret specified. + ## If the parameter is not set, for such Ingress/VirtualServer hosts NGINX will break any attempt to establish a TLS connection. + cert: "" + + ## The base64-encoded TLS key for every Ingress/VirtualServer host that has TLS enabled but no secret specified. + ## If the parameter is not set, for such Ingress/VirtualServer hosts NGINX will break any attempt to establish a TLS connection. + key: "" + + ## The secret with a TLS certificate and key for every Ingress/VirtualServer host that has TLS enabled but no secret specified. + ## The value must follow the following format: `/`. + ## Used as an alternative to specifying a certificate and key using `controller.wildcardTLS.cert` and `controller.wildcardTLS.key` parameters. + ## Format: / + secret: "" + + ## The node selector for pod assignment for the Ingress Controller pods. + # nodeSelector: {} + + ## The termination grace period of the Ingress Controller pod. + terminationGracePeriodSeconds: 30 + + ## HorizontalPodAutoscaling (HPA) + autoscaling: + ## Enables HorizontalPodAutoscaling. + enabled: false + ## The annotations of the Ingress Controller HorizontalPodAutoscaler. + annotations: {} + ## Minimum number of replicas for the HPA. + minReplicas: 1 + ## Maximum number of replicas for the HPA. + maxReplicas: 3 + ## The target cpu utilization percentage. + targetCPUUtilizationPercentage: 50 + ## The target memory utilization percentage. + targetMemoryUtilizationPercentage: 50 + ## Custom behavior policies + behavior: {} + + ## The resources of the Ingress Controller pods. + resources: + requests: + cpu: 100m + memory: 128Mi + # limits: + # cpu: 1 + # memory: 1Gi + + ## The security context for the Ingress Controller pods. + podSecurityContext: + seccompProfile: + type: RuntimeDefault + + ## The security context for the Ingress Controller containers. + securityContext: + {} # Remove curly brackets before adding values + # allowPrivilegeEscalation: true + # readOnlyRootFilesystem: true + # runAsUser: 101 #nginx + # runAsNonRoot: true + # capabilities: + # drop: + # - ALL + # add: + # - NET_BIND_SERVICE + + ## The security context for the Ingress Controller init container which is used when readOnlyRootFilesystem is set to true. + initContainerSecurityContext: {} + + ## The resources for the Ingress Controller init container which is used when readOnlyRootFilesystem is set to true. + initContainerResources: + requests: + cpu: 100m + memory: 128Mi + # limits: + # cpu: 1 + # memory: 1Gi + + ## The tolerations of the Ingress Controller pods. + tolerations: [] + + ## The affinity of the Ingress Controller pods. + affinity: {} + + ## The topology spread constraints of the Ingress controller pods. + # topologySpreadConstraints: {} + + ## The additional environment variables to be set on the Ingress Controller pods. + env: [] + # - name: MY_VAR + # value: myvalue + + ## The volumes of the Ingress Controller pods. + volumes: [] + # - name: extra-conf + # configMap: + # name: extra-conf + + ## The volumeMounts of the Ingress Controller pods. + volumeMounts: [] + # - name: extra-conf + # mountPath: /etc/nginx/conf.d/extra.conf + # subPath: extra.conf + + ## InitContainers for the Ingress Controller pods. + initContainers: [] + # - name: init-container + # image: busybox:1.34 + # command: ['sh', '-c', 'echo this is initial setup!'] + + ## The minimum number of seconds for which a newly created Pod should be ready without any of its containers crashing, for it to be considered available. + minReadySeconds: 0 + + ## Pod disruption budget for the Ingress Controller pods. + podDisruptionBudget: + ## Enables PodDisruptionBudget. + enabled: false + ## The annotations of the Ingress Controller pod disruption budget. + annotations: {} + ## The number of Ingress Controller pods that should be available. This is a mutually exclusive setting with "maxUnavailable". + # minAvailable: 1 + ## The number of Ingress Controller pods that can be unavailable. This is a mutually exclusive setting with "minAvailable". + # maxUnavailable: 1 + + ## Strategy used to replace old Pods by new ones. .spec.strategy.type can be "Recreate" or "RollingUpdate" for Deployments, and "OnDelete" or "RollingUpdate" for Daemonsets. "RollingUpdate" is the default value. + strategy: {} + + ## Extra containers for the Ingress Controller pods. + extraContainers: [] + # - name: container + # image: busybox:1.34 + # command: ['sh', '-c', 'echo this is a sidecar!'] + + ## The number of replicas of the Ingress Controller deployment. + replicaCount: 1 + + ## Configures the ingress class the Ingress Controller uses. + ingressClass: + ## A class of the Ingress Controller. + + ## IngressClass resource with the name equal to the class must be deployed. Otherwise, + ## the Ingress Controller will fail to start. + ## The Ingress Controller only processes resources that belong to its class - i.e. have the "ingressClassName" field resource equal to the class. + + ## The Ingress Controller processes all the resources that do not have the "ingressClassName" field for all versions of kubernetes. + name: nginx + + ## Creates a new IngressClass object with the name "controller.ingressClass.name". To use an existing IngressClass with the same name, set this value to false. If you use helm upgrade, do not change the values from the previous release as helm will delete IngressClass objects managed by helm. If you are upgrading from a release earlier than 3.3.0, do not set the value to false. + create: true + + ## New Ingresses without an ingressClassName field specified will be assigned the class specified in `controller.ingressClass`. Requires "controller.ingressClass.create". + setAsDefaultIngress: false + + ## Comma separated list of namespaces to watch for Ingress resources. By default, the Ingress Controller watches all namespaces. Mutually exclusive with "controller.watchNamespaceLabel". + watchNamespace: "" + + ## Configures the Ingress Controller to watch only those namespaces with label foo=bar. By default, the Ingress Controller watches all namespaces. Mutually exclusive with "controller.watchNamespace". + watchNamespaceLabel: "" + + ## Comma separated list of namespaces to watch for Secret resources. By default, the Ingress Controller watches all namespaces. + watchSecretNamespace: "" + + ## Enable the custom resources. + enableCustomResources: true + + ## Enable OIDC policies. + enableOIDC: false + + ## Enable TLS Passthrough on port 443. Requires controller.enableCustomResources. + enableTLSPassthrough: false + + ## Set the port for TLS Passthrough. Requires controller.enableCustomResources and controller.enableTLSPassthrough. + tlsPassthroughPort: 443 + + ## Enable cert manager for Virtual Server resources. Requires controller.enableCustomResources. + enableCertManager: false + + ## Enable external DNS for Virtual Server resources. Requires controller.enableCustomResources. + enableExternalDNS: false + + globalConfiguration: + ## Creates the GlobalConfiguration custom resource. Requires controller.enableCustomResources. + create: false + + ## The spec of the GlobalConfiguration for defining the global configuration parameters of the Ingress Controller. + spec: {} ## Ensure both curly brackets are removed when adding listeners in YAML format. + # listeners: + # - name: dns-udp + # port: 5353 + # protocol: UDP + # - name: dns-tcp + # port: 5353 + # protocol: TCP + + ## Enable custom NGINX configuration snippets in Ingress, VirtualServer, VirtualServerRoute and TransportServer resources. + enableSnippets: false + + ## Add a location based on the value of health-status-uri to the default server. The location responds with the 200 status code for any request. + ## Useful for external health-checking of the Ingress Controller. + healthStatus: false + + ## Sets the URI of health status location in the default server. Requires controller.healthStatus. + healthStatusURI: "/nginx-health" + + nginxStatus: + ## Enable the NGINX stub_status, or the NGINX Plus API. + enable: true + + ## Set the port where the NGINX stub_status or the NGINX Plus API is exposed. + port: 8080 + + ## Add IPv4 IP/CIDR blocks to the allow list for NGINX stub_status or the NGINX Plus API. Separate multiple IP/CIDR by commas. + allowCidrs: "127.0.0.1" + + service: + ## Creates a service to expose the Ingress Controller pods. + create: true + + ## The type of service to create for the Ingress Controller. + type: LoadBalancer + + ## The externalTrafficPolicy of the service. The value Local preserves the client source IP. + externalTrafficPolicy: Local + + ## The annotations of the Ingress Controller service. + annotations: {} + + ## The extra labels of the service. + extraLabels: {} + + ## The static IP address for the load balancer. Requires controller.service.type set to LoadBalancer. The cloud provider must support this feature. + loadBalancerIP: "" + + ## The ClusterIP for the Ingress Controller service, autoassigned if not specified. + clusterIP: "" + + ## The list of external IPs for the Ingress Controller service. + externalIPs: [] + + ## The IP ranges (CIDR) that are allowed to access the load balancer. Requires controller.service.type set to LoadBalancer. The cloud provider must support this feature. + loadBalancerSourceRanges: [] + + ## Whether to automatically allocate NodePorts (only for LoadBalancers). + # allocateLoadBalancerNodePorts: false + + ## Dual stack preference. + ## Valid values: SingleStack, PreferDualStack, RequireDualStack + # ipFamilyPolicy: SingleStack + + ## List of IP families assigned to this service. + ## Valid values: IPv4, IPv6 + # ipFamilies: + # - IPv6 + + httpPort: + ## Enables the HTTP port for the Ingress Controller service. + enable: true + + ## The HTTP port of the Ingress Controller service. + port: 80 + + ## The custom NodePort for the HTTP port. Requires controller.service.type set to NodePort or LoadBalancer. + # nodePort: 80 + + ## The HTTP port on the POD where the Ingress Controller service is running. + targetPort: 80 + + httpsPort: + ## Enables the HTTPS port for the Ingress Controller service. + enable: true + + ## The HTTPS port of the Ingress Controller service. + port: 443 + + ## The custom NodePort for the HTTPS port. Requires controller.service.type set to NodePort or LoadBalancer. + # nodePort: 443 + + ## The HTTPS port on the POD where the Ingress Controller service is running. + targetPort: 443 + + ## A list of custom ports to expose through the Ingress Controller service. Follows the conventional Kubernetes yaml syntax for service ports. + customPorts: [] + + serviceAccount: + ## The annotations of the service account of the Ingress Controller pods. + annotations: {} + + ## The name of the service account of the Ingress Controller pods. Used for RBAC. + ## Autogenerated if not set or set to "". + # name: nginx-ingress + + ## The name of the secret containing docker registry credentials. + ## Secret must exist in the same namespace as the helm release. + imagePullSecretName: "" + + ## A list of secret names containing docker registry credentials. + ## Secrets must exist in the same namespace as the helm release. + imagePullSecretsNames: [] + + reportIngressStatus: + ## Updates the address field in the status of Ingress resources with an external address of the Ingress Controller. + ## You must also specify the source of the external address either through an external service via controller.reportIngressStatus.externalService, + ## controller.reportIngressStatus.ingressLink or the external-status-address entry in the ConfigMap via controller.config.entries. + ## Note: controller.config.entries.external-status-address takes precedence over the others. + enable: true + + ## Specifies the name of the service with the type LoadBalancer through which the Ingress Controller is exposed externally. + ## The external address of the service is used when reporting the status of Ingress, VirtualServer and VirtualServerRoute resources. + ## controller.reportIngressStatus.enable must be set to true. + ## The default is autogenerated and matches the created service (see controller.service.create). + # externalService: nginx-ingress + + ## Specifies the name of the IngressLink resource, which exposes the Ingress Controller pods via a BIG-IP system. + ## The IP of the BIG-IP system is used when reporting the status of Ingress, VirtualServer and VirtualServerRoute resources. + ## controller.reportIngressStatus.enable must be set to true. + ingressLink: "" + + ## Enable Leader election to avoid multiple replicas of the controller reporting the status of Ingress resources. controller.reportIngressStatus.enable must be set to true. + enableLeaderElection: true + + ## Specifies the name to be used as the lock for leader election. controller.reportIngressStatus.enableLeaderElection must be set to true. + ## The default is autogenerated. + leaderElectionLockName: "" + + ## The annotations of the leader election configmap. + annotations: {} + + pod: + ## The annotations of the Ingress Controller pod. + annotations: {} + + ## The additional extra labels of the Ingress Controller pod. + extraLabels: {} + + ## The PriorityClass of the Ingress Controller pods. + # priorityClassName: "" + + readyStatus: + ## Enables readiness endpoint "/nginx-ready". The endpoint returns a success code when NGINX has loaded all the config after startup. + enable: true + + ## Set the port where the readiness endpoint is exposed. + port: 8081 + + ## The number of seconds after the Ingress Controller pod has started before readiness probes are initiated. + initialDelaySeconds: 0 + + ## Enable collection of latency metrics for upstreams. Requires prometheus.create. + enableLatencyMetrics: false + + ## Disable IPV6 listeners explicitly for nodes that do not support the IPV6 stack. + disableIPV6: false + + ## Sets the port for the HTTP `default_server` listener. + defaultHTTPListenerPort: 80 + + ## Sets the port for the HTTPS `default_server` listener. + defaultHTTPSListenerPort: 443 + + ## Configure root filesystem as read-only and add volumes for temporary data. + ## Three major releases after 3.5.x this argument will be moved to the `securityContext` section. + ## This value will not be used if `controller.securityContext` is set + readOnlyRootFilesystem: false + + ## Enable dynamic reloading of certificates + enableSSLDynamicReload: true + + ## Configure telemetry reporting options + telemetryReporting: + ## Enable telemetry reporting + enable: true + + ## Allows weight adjustments without reloading the NGINX Configuration for two-way splits in NGINX Plus. + ## May require increasing map_hash_bucket_size, map_hash_max_size, + ## variable_hash_bucket_size, and variable_hash_max_size in the ConfigMap based on the number of two-way splits. + enableWeightChangesDynamicReload: false + +rbac: + ## Configures RBAC. + create: true + + clusterrole: + ## Create ClusterRole + create: true + +prometheus: + ## Expose NGINX or NGINX Plus metrics in the Prometheus format. + create: true + + ## Configures the port to scrape the metrics. + port: 9113 + + ## Specifies the namespace/name of a Kubernetes TLS Secret which will be used to protect the Prometheus endpoint. + secret: "" + + ## Configures the HTTP scheme used. + scheme: http + + service: + ## Creates a ClusterIP Service to expose Prometheus metrics internally + ## Requires prometheus.create=true + create: false + + labels: + service: "nginx-ingress-prometheus-service" + + serviceMonitor: + ## Creates a serviceMonitor to expose statistics on the kubernetes pods. + create: false + + ## Kubernetes object labels to attach to the serviceMonitor object. + labels: {} + + ## A set of labels to allow the selection of endpoints for the ServiceMonitor. + selectorMatchLabels: + service: "nginx-ingress-prometheus-service" + + ## A list of endpoints allowed as part of this ServiceMonitor. + ## Matches on the name of a Service port. + endpoints: + - port: prometheus + +serviceInsight: + ## Expose NGINX Plus Service Insight endpoint. + create: false + + ## Configures the port to expose endpoint. + port: 9114 + + ## Specifies the namespace/name of a Kubernetes TLS Secret which will be used to protect the Service Insight endpoint. + secret: "" + + ## Configures the HTTP scheme used. + scheme: http + +nginxServiceMesh: + ## Enables integration with NGINX Service Mesh. + enable: false + + ## Enables NGINX Service Mesh workload to route egress traffic through the Ingress Controller. + ## Requires nginxServiceMesh.enable + enableEgress: false + +nginxAgent: + ## Enables NGINX Agent. + enable: false + ## If nginxAgent.instanceGroup is not set the value of nginx-ingress.controller.fullname will be used + instanceGroup: "" + logLevel: "error" + ## Syslog listener which NGINX Agent uses to accept messages from App Protect WAF + syslog: + host: "127.0.0.1" + port: 1514 + napMonitoring: + collectorBufferSize: 50000 + processorBufferSize: 50000 + instanceManager: + # FQDN or IP for connecting to NGINX Instance Manager, e.g. nim.example.com + host: "" + grpcPort: 443 + sni: "" + tls: + enabled: true + skipVerify: false + ## kubernetes.io/tls secret with a TLS certificate and key for using mTLS between NGINX Agent and Instance Manager + secret: "" + ## nginx.org/ca secret for verification of Instance Manager TLS + caSecret: "" + ## The name of a custom ConfigMap to use instead of the one provided by default + customConfigMap: "" diff --git a/charts/tests/__snapshots__/helmunit_test.snap b/charts/tests/__snapshots__/helmunit_test.snap new file mode 100755 index 0000000000..fe211af857 --- /dev/null +++ b/charts/tests/__snapshots__/helmunit_test.snap @@ -0,0 +1,4445 @@ + +[TestHelmNICTemplate/appProtectDOS - 1] +/-/-/-/ +# Source: nginx-ingress/templates/controller-serviceaccount.yaml +apiVersion: v1 +kind: ServiceAccount +metadata: + name: appprotect-dos-nginx-ingress + namespace: appprotect-dos + labels: + helm.sh/chart: nginx-ingress-2.1.0 + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: appprotect-dos + app.kubernetes.io/version: "4.1.0" + app.kubernetes.io/managed-by: Helm +/-/-/-/ +# Source: nginx-ingress/templates/controller-configmap.yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: appprotect-dos-nginx-ingress + namespace: appprotect-dos + labels: + helm.sh/chart: nginx-ingress-2.1.0 + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: appprotect-dos + app.kubernetes.io/version: "4.1.0" + app.kubernetes.io/managed-by: Helm +data: + {} +/-/-/-/ +# Source: nginx-ingress/templates/controller-configmap.yaml +/-/-/-/ +apiVersion: v1 +kind: ConfigMap +metadata: + name: appprotect-dos-nginx-ingress-mgmt + namespace: appprotect-dos + labels: + helm.sh/chart: nginx-ingress-2.1.0 + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: appprotect-dos + app.kubernetes.io/version: "4.1.0" + app.kubernetes.io/managed-by: Helm +data: + license-token-secret-name: license-token +/-/-/-/ +# Source: nginx-ingress/templates/controller-leader-election-configmap.yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: appprotect-dos-nginx-ingress-leader-election + namespace: appprotect-dos + labels: + helm.sh/chart: nginx-ingress-2.1.0 + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: appprotect-dos + app.kubernetes.io/version: "4.1.0" + app.kubernetes.io/managed-by: Helm +/-/-/-/ +# Source: nginx-ingress/templates/clusterrole.yaml +kind: ClusterRole +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: appprotect-dos-nginx-ingress + labels: + helm.sh/chart: nginx-ingress-2.1.0 + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: appprotect-dos + app.kubernetes.io/version: "4.1.0" + app.kubernetes.io/managed-by: Helm +rules: +- apiGroups: + - "" + resources: + - configmaps + - namespaces + - pods + - secrets + - services + verbs: + - get + - list + - watch +- apiGroups: + - "" + resources: + - events + verbs: + - create + - patch + - list +- apiGroups: + - coordination.k8s.io + resources: + - leases + verbs: + - list + - watch +- apiGroups: + - discovery.k8s.io + resources: + - endpointslices + verbs: + - get + - list + - watch +- apiGroups: + - networking.k8s.io + resources: + - ingresses + verbs: + - get + - list + - watch +- apiGroups: + - "" + resources: + - nodes + verbs: + - list +- apiGroups: + - "apps" + resources: + - replicasets + - daemonsets + verbs: + - get +- apiGroups: + - networking.k8s.io + resources: + - ingressclasses + verbs: + - get + - list +- apiGroups: + - networking.k8s.io + resources: + - ingresses/status + verbs: + - update +- apiGroups: + - appprotectdos.f5.com + resources: + - apdospolicies + - apdoslogconfs + - dosprotectedresources + verbs: + - get + - watch + - list +- apiGroups: + - k8s.nginx.org + resources: + - virtualservers + - virtualserverroutes + - globalconfigurations + - transportservers + - policies + verbs: + - list + - watch + - get +- apiGroups: + - k8s.nginx.org + resources: + - virtualservers/status + - virtualserverroutes/status + - policies/status + - transportservers/status + verbs: + - update +/-/-/-/ +# Source: nginx-ingress/templates/clusterrolebinding.yaml +kind: ClusterRoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: appprotect-dos-nginx-ingress + labels: + helm.sh/chart: nginx-ingress-2.1.0 + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: appprotect-dos + app.kubernetes.io/version: "4.1.0" + app.kubernetes.io/managed-by: Helm +subjects: +- kind: ServiceAccount + name: appprotect-dos-nginx-ingress + namespace: appprotect-dos +roleRef: + kind: ClusterRole + name: appprotect-dos-nginx-ingress + apiGroup: rbac.authorization.k8s.io +/-/-/-/ +# Source: nginx-ingress/templates/controller-role.yaml +kind: Role +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: appprotect-dos-nginx-ingress + labels: + helm.sh/chart: nginx-ingress-2.1.0 + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: appprotect-dos + app.kubernetes.io/version: "4.1.0" + app.kubernetes.io/managed-by: Helm + namespace: appprotect-dos +rules: +- apiGroups: + - "" + resources: + - configmaps + - pods + - secrets + - services + verbs: + - get + - list + - watch +- apiGroups: + - "" + resources: + - namespaces + verbs: + - get +- apiGroups: + - "" + resources: + - pods + verbs: + - update +- apiGroups: + - "" + resources: + - events + verbs: + - create + - patch + - list +- apiGroups: + - coordination.k8s.io + resources: + - leases + resourceNames: + - appprotect-dos-nginx-ingress-leader-election + verbs: + - get + - update +- apiGroups: + - coordination.k8s.io + resources: + - leases + verbs: + - create +/-/-/-/ +# Source: nginx-ingress/templates/controller-rolebinding.yaml +kind: RoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: appprotect-dos-nginx-ingress + labels: + helm.sh/chart: nginx-ingress-2.1.0 + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: appprotect-dos + app.kubernetes.io/version: "4.1.0" + app.kubernetes.io/managed-by: Helm + namespace: appprotect-dos +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: appprotect-dos-nginx-ingress +subjects: +- kind: ServiceAccount + name: appprotect-dos-nginx-ingress + namespace: appprotect-dos +/-/-/-/ +# Source: nginx-ingress/templates/controller-service.yaml +apiVersion: v1 +kind: Service +metadata: + name: appprotect-dos-nginx-ingress-controller + namespace: appprotect-dos + labels: + helm.sh/chart: nginx-ingress-2.1.0 + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: appprotect-dos + app.kubernetes.io/version: "4.1.0" + app.kubernetes.io/managed-by: Helm +spec: + externalTrafficPolicy: Local + type: LoadBalancer + ports: + - port: 80 + targetPort: 80 + protocol: TCP + name: http + nodePort: + - port: 443 + targetPort: 443 + protocol: TCP + name: https + nodePort: + selector: + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: appprotect-dos +/-/-/-/ +# Source: nginx-ingress/templates/controller-deployment.yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: appprotect-dos-nginx-ingress-controller + namespace: appprotect-dos + labels: + helm.sh/chart: nginx-ingress-2.1.0 + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: appprotect-dos + app.kubernetes.io/version: "4.1.0" + app.kubernetes.io/managed-by: Helm +spec: + replicas: 1 + selector: + matchLabels: + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: appprotect-dos + template: + metadata: + labels: + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: appprotect-dos + annotations: + prometheus.io/scrape: "true" + prometheus.io/port: "9113" + prometheus.io/scheme: "http" + spec: + volumes: [] + serviceAccountName: appprotect-dos-nginx-ingress + automountServiceAccountToken: true + securityContext: + seccompProfile: + type: RuntimeDefault + terminationGracePeriodSeconds: 30 + hostNetwork: false + dnsPolicy: ClusterFirst + containers: + - image: nginx/nginx-ingress:4.1.0 + name: nginx-ingress + imagePullPolicy: "IfNotPresent" + ports: + - name: http + containerPort: 80 + protocol: TCP + - name: https + containerPort: 443 + protocol: TCP + - name: prometheus + containerPort: 9113 + - name: readiness-port + containerPort: 8081 + readinessProbe: + httpGet: + path: /nginx-ready + port: readiness-port + periodSeconds: 1 + initialDelaySeconds: 0 + resources: + requests: + cpu: 100m + memory: 128Mi + securityContext: + allowPrivilegeEscalation: false + readOnlyRootFilesystem: false + runAsUser: 101 #nginx + runAsNonRoot: true + capabilities: + drop: + - ALL + add: + - NET_BIND_SERVICE + volumeMounts: [] + env: + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + args: + - -nginx-plus=true + - -nginx-reload-timeout=60000 + - -enable-app-protect=false + - -enable-app-protect-dos=true + - -app-protect-dos-debug=true + - -app-protect-dos-max-daemons=10 + - -app-protect-dos-max-workers=5 + - -app-protect-dos-memory=1024 + + - -nginx-configmaps=$(POD_NAMESPACE)/appprotect-dos-nginx-ingress + - -mgmt-configmap=$(POD_NAMESPACE)/appprotect-dos-nginx-ingress-mgmt + - -ingress-class=nginx + - -health-status=false + - -health-status-uri=/nginx-health + - -nginx-debug=false + - -log-level=info + - -log-format=glog + - -nginx-status=true + - -nginx-status-port=8080 + - -nginx-status-allow-cidrs=127.0.0.1 + - -report-ingress-status + - -external-service=appprotect-dos-nginx-ingress-controller + - -enable-leader-election=true + - -leader-election-lock-name=appprotect-dos-nginx-ingress-leader-election + - -enable-prometheus-metrics=true + - -prometheus-metrics-listen-port=9113 + - -prometheus-tls-secret= + - -enable-service-insight=false + - -service-insight-listen-port=9114 + - -service-insight-tls-secret= + - -enable-custom-resources=true + - -enable-snippets=false + - -disable-ipv6=false + - -enable-tls-passthrough=false + - -enable-cert-manager=false + - -enable-oidc=false + - -enable-external-dns=false + - -default-http-listener-port=80 + - -default-https-listener-port=443 + - -ready-status=true + - -ready-status-port=8081 + - -enable-latency-metrics=false + - -ssl-dynamic-reload=true + - -enable-telemetry-reporting=true + - -weight-changes-dynamic-reload=false +/-/-/-/ +# Source: nginx-ingress/templates/controller-ingress-class.yaml +apiVersion: networking.k8s.io/v1 +kind: IngressClass +metadata: + name: nginx + labels: + helm.sh/chart: nginx-ingress-2.1.0 + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: appprotect-dos + app.kubernetes.io/version: "4.1.0" + app.kubernetes.io/managed-by: Helm +spec: + controller: nginx.org/ingress-controller +/-/-/-/ +# Source: nginx-ingress/templates/controller-lease.yaml +apiVersion: coordination.k8s.io/v1 +kind: Lease +metadata: + name: appprotect-dos-nginx-ingress-leader-election + namespace: appprotect-dos + labels: + helm.sh/chart: nginx-ingress-2.1.0 + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: appprotect-dos + app.kubernetes.io/version: "4.1.0" + app.kubernetes.io/managed-by: Helm +--- + +[TestHelmNICTemplate/appProtectWAF - 1] +/-/-/-/ +# Source: nginx-ingress/templates/controller-serviceaccount.yaml +apiVersion: v1 +kind: ServiceAccount +metadata: + name: appprotect-waf-nginx-ingress + namespace: appprotect-waf + labels: + helm.sh/chart: nginx-ingress-2.1.0 + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: appprotect-waf + app.kubernetes.io/version: "4.1.0" + app.kubernetes.io/managed-by: Helm +/-/-/-/ +# Source: nginx-ingress/templates/controller-configmap.yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: appprotect-waf-nginx-ingress + namespace: appprotect-waf + labels: + helm.sh/chart: nginx-ingress-2.1.0 + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: appprotect-waf + app.kubernetes.io/version: "4.1.0" + app.kubernetes.io/managed-by: Helm +data: + {} +/-/-/-/ +# Source: nginx-ingress/templates/controller-configmap.yaml +/-/-/-/ +apiVersion: v1 +kind: ConfigMap +metadata: + name: appprotect-waf-nginx-ingress-mgmt + namespace: appprotect-waf + labels: + helm.sh/chart: nginx-ingress-2.1.0 + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: appprotect-waf + app.kubernetes.io/version: "4.1.0" + app.kubernetes.io/managed-by: Helm +data: + license-token-secret-name: license-token +/-/-/-/ +# Source: nginx-ingress/templates/controller-leader-election-configmap.yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: appprotect-waf-nginx-ingress-leader-election + namespace: appprotect-waf + labels: + helm.sh/chart: nginx-ingress-2.1.0 + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: appprotect-waf + app.kubernetes.io/version: "4.1.0" + app.kubernetes.io/managed-by: Helm +/-/-/-/ +# Source: nginx-ingress/templates/clusterrole.yaml +kind: ClusterRole +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: appprotect-waf-nginx-ingress + labels: + helm.sh/chart: nginx-ingress-2.1.0 + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: appprotect-waf + app.kubernetes.io/version: "4.1.0" + app.kubernetes.io/managed-by: Helm +rules: +- apiGroups: + - "" + resources: + - configmaps + - namespaces + - pods + - secrets + - services + verbs: + - get + - list + - watch +- apiGroups: + - "" + resources: + - events + verbs: + - create + - patch + - list +- apiGroups: + - coordination.k8s.io + resources: + - leases + verbs: + - list + - watch +- apiGroups: + - discovery.k8s.io + resources: + - endpointslices + verbs: + - get + - list + - watch +- apiGroups: + - networking.k8s.io + resources: + - ingresses + verbs: + - get + - list + - watch +- apiGroups: + - "" + resources: + - nodes + verbs: + - list +- apiGroups: + - "apps" + resources: + - replicasets + - daemonsets + verbs: + - get +- apiGroups: + - networking.k8s.io + resources: + - ingressclasses + verbs: + - get + - list +- apiGroups: + - networking.k8s.io + resources: + - ingresses/status + verbs: + - update +- apiGroups: + - appprotect.f5.com + resources: + - appolicies + - aplogconfs + - apusersigs + verbs: + - get + - watch + - list +- apiGroups: + - k8s.nginx.org + resources: + - virtualservers + - virtualserverroutes + - globalconfigurations + - transportservers + - policies + verbs: + - list + - watch + - get +- apiGroups: + - k8s.nginx.org + resources: + - virtualservers/status + - virtualserverroutes/status + - policies/status + - transportservers/status + verbs: + - update +/-/-/-/ +# Source: nginx-ingress/templates/clusterrolebinding.yaml +kind: ClusterRoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: appprotect-waf-nginx-ingress + labels: + helm.sh/chart: nginx-ingress-2.1.0 + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: appprotect-waf + app.kubernetes.io/version: "4.1.0" + app.kubernetes.io/managed-by: Helm +subjects: +- kind: ServiceAccount + name: appprotect-waf-nginx-ingress + namespace: appprotect-waf +roleRef: + kind: ClusterRole + name: appprotect-waf-nginx-ingress + apiGroup: rbac.authorization.k8s.io +/-/-/-/ +# Source: nginx-ingress/templates/controller-role.yaml +kind: Role +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: appprotect-waf-nginx-ingress + labels: + helm.sh/chart: nginx-ingress-2.1.0 + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: appprotect-waf + app.kubernetes.io/version: "4.1.0" + app.kubernetes.io/managed-by: Helm + namespace: appprotect-waf +rules: +- apiGroups: + - "" + resources: + - configmaps + - pods + - secrets + - services + verbs: + - get + - list + - watch +- apiGroups: + - "" + resources: + - namespaces + verbs: + - get +- apiGroups: + - "" + resources: + - pods + verbs: + - update +- apiGroups: + - "" + resources: + - events + verbs: + - create + - patch + - list +- apiGroups: + - coordination.k8s.io + resources: + - leases + resourceNames: + - appprotect-waf-nginx-ingress-leader-election + verbs: + - get + - update +- apiGroups: + - coordination.k8s.io + resources: + - leases + verbs: + - create +/-/-/-/ +# Source: nginx-ingress/templates/controller-rolebinding.yaml +kind: RoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: appprotect-waf-nginx-ingress + labels: + helm.sh/chart: nginx-ingress-2.1.0 + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: appprotect-waf + app.kubernetes.io/version: "4.1.0" + app.kubernetes.io/managed-by: Helm + namespace: appprotect-waf +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: appprotect-waf-nginx-ingress +subjects: +- kind: ServiceAccount + name: appprotect-waf-nginx-ingress + namespace: appprotect-waf +/-/-/-/ +# Source: nginx-ingress/templates/controller-service.yaml +apiVersion: v1 +kind: Service +metadata: + name: appprotect-waf-nginx-ingress-controller + namespace: appprotect-waf + labels: + helm.sh/chart: nginx-ingress-2.1.0 + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: appprotect-waf + app.kubernetes.io/version: "4.1.0" + app.kubernetes.io/managed-by: Helm +spec: + externalTrafficPolicy: Local + type: LoadBalancer + ports: + - port: 80 + targetPort: 80 + protocol: TCP + name: http + nodePort: + - port: 443 + targetPort: 443 + protocol: TCP + name: https + nodePort: + selector: + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: appprotect-waf +/-/-/-/ +# Source: nginx-ingress/templates/controller-deployment.yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: appprotect-waf-nginx-ingress-controller + namespace: appprotect-waf + labels: + helm.sh/chart: nginx-ingress-2.1.0 + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: appprotect-waf + app.kubernetes.io/version: "4.1.0" + app.kubernetes.io/managed-by: Helm +spec: + replicas: 1 + selector: + matchLabels: + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: appprotect-waf + template: + metadata: + labels: + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: appprotect-waf + annotations: + prometheus.io/scrape: "true" + prometheus.io/port: "9113" + prometheus.io/scheme: "http" + spec: + volumes: [] + serviceAccountName: appprotect-waf-nginx-ingress + automountServiceAccountToken: true + securityContext: + seccompProfile: + type: RuntimeDefault + terminationGracePeriodSeconds: 30 + hostNetwork: false + dnsPolicy: ClusterFirst + containers: + - image: nginx/nginx-ingress:4.1.0 + name: nginx-ingress + imagePullPolicy: "IfNotPresent" + ports: + - name: http + containerPort: 80 + protocol: TCP + - name: https + containerPort: 443 + protocol: TCP + - name: prometheus + containerPort: 9113 + - name: readiness-port + containerPort: 8081 + readinessProbe: + httpGet: + path: /nginx-ready + port: readiness-port + periodSeconds: 1 + initialDelaySeconds: 0 + resources: + requests: + cpu: 100m + memory: 128Mi + securityContext: + allowPrivilegeEscalation: false + readOnlyRootFilesystem: false + runAsUser: 101 #nginx + runAsNonRoot: true + capabilities: + drop: + - ALL + add: + - NET_BIND_SERVICE + volumeMounts: [] + env: + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + args: + - -nginx-plus=true + - -nginx-reload-timeout=60000 + - -enable-app-protect=true + - -enable-app-protect-dos=false + - -nginx-configmaps=$(POD_NAMESPACE)/appprotect-waf-nginx-ingress + - -mgmt-configmap=$(POD_NAMESPACE)/appprotect-waf-nginx-ingress-mgmt + - -ingress-class=nginx + - -health-status=false + - -health-status-uri=/nginx-health + - -nginx-debug=false + - -log-level=info + - -log-format=glog + - -nginx-status=true + - -nginx-status-port=8080 + - -nginx-status-allow-cidrs=127.0.0.1 + - -report-ingress-status + - -external-service=appprotect-waf-nginx-ingress-controller + - -enable-leader-election=true + - -leader-election-lock-name=appprotect-waf-nginx-ingress-leader-election + - -enable-prometheus-metrics=true + - -prometheus-metrics-listen-port=9113 + - -prometheus-tls-secret= + - -enable-service-insight=false + - -service-insight-listen-port=9114 + - -service-insight-tls-secret= + - -enable-custom-resources=true + - -enable-snippets=false + - -disable-ipv6=false + - -enable-tls-passthrough=false + - -enable-cert-manager=false + - -enable-oidc=false + - -enable-external-dns=false + - -default-http-listener-port=80 + - -default-https-listener-port=443 + - -ready-status=true + - -ready-status-port=8081 + - -enable-latency-metrics=false + - -ssl-dynamic-reload=true + - -enable-telemetry-reporting=true + - -weight-changes-dynamic-reload=false +/-/-/-/ +# Source: nginx-ingress/templates/controller-ingress-class.yaml +apiVersion: networking.k8s.io/v1 +kind: IngressClass +metadata: + name: nginx + labels: + helm.sh/chart: nginx-ingress-2.1.0 + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: appprotect-waf + app.kubernetes.io/version: "4.1.0" + app.kubernetes.io/managed-by: Helm +spec: + controller: nginx.org/ingress-controller +/-/-/-/ +# Source: nginx-ingress/templates/controller-lease.yaml +apiVersion: coordination.k8s.io/v1 +kind: Lease +metadata: + name: appprotect-waf-nginx-ingress-leader-election + namespace: appprotect-waf + labels: + helm.sh/chart: nginx-ingress-2.1.0 + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: appprotect-waf + app.kubernetes.io/version: "4.1.0" + app.kubernetes.io/managed-by: Helm +--- + +[TestHelmNICTemplate/appProtectWAFV5 - 1] +/-/-/-/ +# Source: nginx-ingress/templates/controller-serviceaccount.yaml +apiVersion: v1 +kind: ServiceAccount +metadata: + name: appprotect-wafv5-nginx-ingress + namespace: appprotect-wafv5 + labels: + helm.sh/chart: nginx-ingress-2.1.0 + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: appprotect-wafv5 + app.kubernetes.io/version: "4.1.0" + app.kubernetes.io/managed-by: Helm +/-/-/-/ +# Source: nginx-ingress/templates/controller-configmap.yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: appprotect-wafv5-nginx-ingress + namespace: appprotect-wafv5 + labels: + helm.sh/chart: nginx-ingress-2.1.0 + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: appprotect-wafv5 + app.kubernetes.io/version: "4.1.0" + app.kubernetes.io/managed-by: Helm +data: + {} +/-/-/-/ +# Source: nginx-ingress/templates/controller-configmap.yaml +/-/-/-/ +apiVersion: v1 +kind: ConfigMap +metadata: + name: appprotect-wafv5-nginx-ingress-mgmt + namespace: appprotect-wafv5 + labels: + helm.sh/chart: nginx-ingress-2.1.0 + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: appprotect-wafv5 + app.kubernetes.io/version: "4.1.0" + app.kubernetes.io/managed-by: Helm +data: + license-token-secret-name: license-token +/-/-/-/ +# Source: nginx-ingress/templates/controller-leader-election-configmap.yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: appprotect-wafv5-nginx-ingress-leader-election + namespace: appprotect-wafv5 + labels: + helm.sh/chart: nginx-ingress-2.1.0 + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: appprotect-wafv5 + app.kubernetes.io/version: "4.1.0" + app.kubernetes.io/managed-by: Helm +/-/-/-/ +# Source: nginx-ingress/templates/clusterrole.yaml +kind: ClusterRole +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: appprotect-wafv5-nginx-ingress + labels: + helm.sh/chart: nginx-ingress-2.1.0 + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: appprotect-wafv5 + app.kubernetes.io/version: "4.1.0" + app.kubernetes.io/managed-by: Helm +rules: +- apiGroups: + - "" + resources: + - configmaps + - namespaces + - pods + - secrets + - services + verbs: + - get + - list + - watch +- apiGroups: + - "" + resources: + - events + verbs: + - create + - patch + - list +- apiGroups: + - coordination.k8s.io + resources: + - leases + verbs: + - list + - watch +- apiGroups: + - discovery.k8s.io + resources: + - endpointslices + verbs: + - get + - list + - watch +- apiGroups: + - networking.k8s.io + resources: + - ingresses + verbs: + - get + - list + - watch +- apiGroups: + - "" + resources: + - nodes + verbs: + - list +- apiGroups: + - "apps" + resources: + - replicasets + - daemonsets + verbs: + - get +- apiGroups: + - networking.k8s.io + resources: + - ingressclasses + verbs: + - get + - list +- apiGroups: + - networking.k8s.io + resources: + - ingresses/status + verbs: + - update +- apiGroups: + - appprotect.f5.com + resources: + - appolicies + - aplogconfs + - apusersigs + verbs: + - get + - watch + - list +- apiGroups: + - k8s.nginx.org + resources: + - virtualservers + - virtualserverroutes + - globalconfigurations + - transportservers + - policies + verbs: + - list + - watch + - get +- apiGroups: + - k8s.nginx.org + resources: + - virtualservers/status + - virtualserverroutes/status + - policies/status + - transportservers/status + verbs: + - update +/-/-/-/ +# Source: nginx-ingress/templates/clusterrolebinding.yaml +kind: ClusterRoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: appprotect-wafv5-nginx-ingress + labels: + helm.sh/chart: nginx-ingress-2.1.0 + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: appprotect-wafv5 + app.kubernetes.io/version: "4.1.0" + app.kubernetes.io/managed-by: Helm +subjects: +- kind: ServiceAccount + name: appprotect-wafv5-nginx-ingress + namespace: appprotect-wafv5 +roleRef: + kind: ClusterRole + name: appprotect-wafv5-nginx-ingress + apiGroup: rbac.authorization.k8s.io +/-/-/-/ +# Source: nginx-ingress/templates/controller-role.yaml +kind: Role +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: appprotect-wafv5-nginx-ingress + labels: + helm.sh/chart: nginx-ingress-2.1.0 + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: appprotect-wafv5 + app.kubernetes.io/version: "4.1.0" + app.kubernetes.io/managed-by: Helm + namespace: appprotect-wafv5 +rules: +- apiGroups: + - "" + resources: + - configmaps + - pods + - secrets + - services + verbs: + - get + - list + - watch +- apiGroups: + - "" + resources: + - namespaces + verbs: + - get +- apiGroups: + - "" + resources: + - pods + verbs: + - update +- apiGroups: + - "" + resources: + - events + verbs: + - create + - patch + - list +- apiGroups: + - coordination.k8s.io + resources: + - leases + resourceNames: + - appprotect-wafv5-nginx-ingress-leader-election + verbs: + - get + - update +- apiGroups: + - coordination.k8s.io + resources: + - leases + verbs: + - create +/-/-/-/ +# Source: nginx-ingress/templates/controller-rolebinding.yaml +kind: RoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: appprotect-wafv5-nginx-ingress + labels: + helm.sh/chart: nginx-ingress-2.1.0 + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: appprotect-wafv5 + app.kubernetes.io/version: "4.1.0" + app.kubernetes.io/managed-by: Helm + namespace: appprotect-wafv5 +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: appprotect-wafv5-nginx-ingress +subjects: +- kind: ServiceAccount + name: appprotect-wafv5-nginx-ingress + namespace: appprotect-wafv5 +/-/-/-/ +# Source: nginx-ingress/templates/controller-service.yaml +apiVersion: v1 +kind: Service +metadata: + name: appprotect-wafv5-nginx-ingress-controller + namespace: appprotect-wafv5 + labels: + helm.sh/chart: nginx-ingress-2.1.0 + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: appprotect-wafv5 + app.kubernetes.io/version: "4.1.0" + app.kubernetes.io/managed-by: Helm +spec: + externalTrafficPolicy: Local + type: LoadBalancer + ports: + - port: 80 + targetPort: 80 + protocol: TCP + name: http + nodePort: + - port: 443 + targetPort: 443 + protocol: TCP + name: https + nodePort: + selector: + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: appprotect-wafv5 +/-/-/-/ +# Source: nginx-ingress/templates/controller-deployment.yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: appprotect-wafv5-nginx-ingress-controller + namespace: appprotect-wafv5 + labels: + helm.sh/chart: nginx-ingress-2.1.0 + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: appprotect-wafv5 + app.kubernetes.io/version: "4.1.0" + app.kubernetes.io/managed-by: Helm +spec: + replicas: 1 + selector: + matchLabels: + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: appprotect-wafv5 + template: + metadata: + labels: + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: appprotect-wafv5 + annotations: + prometheus.io/scrape: "true" + prometheus.io/port: "9113" + prometheus.io/scheme: "http" + spec: + volumes: + + - emptyDir: {} + name: app-protect-bd-config + - emptyDir: {} + name: app-protect-config + - emptyDir: {} + name: app-protect-bundles + serviceAccountName: appprotect-wafv5-nginx-ingress + automountServiceAccountToken: true + securityContext: + seccompProfile: + type: RuntimeDefault + terminationGracePeriodSeconds: 30 + hostNetwork: false + dnsPolicy: ClusterFirst + containers: + - image: nginx/nginx-ingress:4.1.0 + name: nginx-ingress + imagePullPolicy: "IfNotPresent" + ports: + - name: http + containerPort: 80 + protocol: TCP + - name: https + containerPort: 443 + protocol: TCP + - name: prometheus + containerPort: 9113 + - name: readiness-port + containerPort: 8081 + readinessProbe: + httpGet: + path: /nginx-ready + port: readiness-port + periodSeconds: 1 + initialDelaySeconds: 0 + resources: + requests: + cpu: 100m + memory: 128Mi + securityContext: + allowPrivilegeEscalation: false + readOnlyRootFilesystem: false + runAsUser: 101 #nginx + runAsNonRoot: true + capabilities: + drop: + - ALL + add: + - NET_BIND_SERVICE + volumeMounts: + + - name: app-protect-bd-config + mountPath: /opt/app_protect/bd_config + - name: app-protect-config + mountPath: /opt/app_protect/config + # app-protect-bundles is mounted so that Ingress Controller + # can verify that referenced bundles are present + - name: app-protect-bundles + mountPath: /etc/app_protect/bundles + env: + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + args: + - -nginx-plus=true + - -nginx-reload-timeout=60000 + - -enable-app-protect=true + - -app-protect-enforcer-address="localhost:50001" + - -enable-app-protect-dos=false + - -nginx-configmaps=$(POD_NAMESPACE)/appprotect-wafv5-nginx-ingress + - -mgmt-configmap=$(POD_NAMESPACE)/appprotect-wafv5-nginx-ingress-mgmt + - -ingress-class=nginx + - -health-status=false + - -health-status-uri=/nginx-health + - -nginx-debug=false + - -log-level=info + - -log-format=glog + - -nginx-status=true + - -nginx-status-port=8080 + - -nginx-status-allow-cidrs=127.0.0.1 + - -report-ingress-status + - -external-service=appprotect-wafv5-nginx-ingress-controller + - -enable-leader-election=true + - -leader-election-lock-name=appprotect-wafv5-nginx-ingress-leader-election + - -enable-prometheus-metrics=true + - -prometheus-metrics-listen-port=9113 + - -prometheus-tls-secret= + - -enable-service-insight=false + - -service-insight-listen-port=9114 + - -service-insight-tls-secret= + - -enable-custom-resources=true + - -enable-snippets=false + - -disable-ipv6=false + - -enable-tls-passthrough=false + - -enable-cert-manager=false + - -enable-oidc=false + - -enable-external-dns=false + - -default-http-listener-port=80 + - -default-https-listener-port=443 + - -ready-status=true + - -ready-status-port=8081 + - -enable-latency-metrics=false + - -ssl-dynamic-reload=true + - -enable-telemetry-reporting=true + - -weight-changes-dynamic-reload=false + + - name: waf-enforcer + image: my.private.reg/nap/waf-enforcer:5.4.0 + imagePullPolicy: "IfNotPresent" + env: + - name: ENFORCER_PORT + value: "50001" + - name: ENFORCER_CONFIG_TIMEOUT + value: "0" + volumeMounts: + - name: app-protect-bd-config + mountPath: /opt/app_protect/bd_config + - name: waf-config-mgr + image: my.private.reg/nap/waf-config-mgr:5.4.0 + imagePullPolicy: "IfNotPresent" + securityContext: + + allowPrivilegeEscalation: false + capabilities: + drop: + - all + runAsNonRoot: true + runAsUser: 101 + volumeMounts: + - name: app-protect-bd-config + mountPath: /opt/app_protect/bd_config + - name: app-protect-config + mountPath: /opt/app_protect/config + - name: app-protect-bundles + mountPath: /etc/app_protect/bundles +/-/-/-/ +# Source: nginx-ingress/templates/controller-ingress-class.yaml +apiVersion: networking.k8s.io/v1 +kind: IngressClass +metadata: + name: nginx + labels: + helm.sh/chart: nginx-ingress-2.1.0 + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: appprotect-wafv5 + app.kubernetes.io/version: "4.1.0" + app.kubernetes.io/managed-by: Helm +spec: + controller: nginx.org/ingress-controller +/-/-/-/ +# Source: nginx-ingress/templates/controller-lease.yaml +apiVersion: coordination.k8s.io/v1 +kind: Lease +metadata: + name: appprotect-wafv5-nginx-ingress-leader-election + namespace: appprotect-wafv5 + labels: + helm.sh/chart: nginx-ingress-2.1.0 + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: appprotect-wafv5 + app.kubernetes.io/version: "4.1.0" + app.kubernetes.io/managed-by: Helm +--- + +[TestHelmNICTemplate/customResources - 1] +/-/-/-/ +# Source: nginx-ingress/templates/controller-serviceaccount.yaml +apiVersion: v1 +kind: ServiceAccount +metadata: + name: custom-resources-nginx-ingress + namespace: custom-resources + labels: + helm.sh/chart: nginx-ingress-2.1.0 + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: custom-resources + app.kubernetes.io/version: "4.1.0" + app.kubernetes.io/managed-by: Helm +/-/-/-/ +# Source: nginx-ingress/templates/controller-configmap.yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: custom-resources-nginx-ingress + namespace: custom-resources + labels: + helm.sh/chart: nginx-ingress-2.1.0 + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: custom-resources + app.kubernetes.io/version: "4.1.0" + app.kubernetes.io/managed-by: Helm +data: + {} +/-/-/-/ +# Source: nginx-ingress/templates/controller-leader-election-configmap.yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: custom-resources-nginx-ingress-leader-election + namespace: custom-resources + labels: + helm.sh/chart: nginx-ingress-2.1.0 + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: custom-resources + app.kubernetes.io/version: "4.1.0" + app.kubernetes.io/managed-by: Helm +/-/-/-/ +# Source: nginx-ingress/templates/clusterrole.yaml +kind: ClusterRole +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: custom-resources-nginx-ingress + labels: + helm.sh/chart: nginx-ingress-2.1.0 + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: custom-resources + app.kubernetes.io/version: "4.1.0" + app.kubernetes.io/managed-by: Helm +rules: +- apiGroups: + - "" + resources: + - configmaps + - namespaces + - pods + - secrets + - services + verbs: + - get + - list + - watch +- apiGroups: + - "" + resources: + - events + verbs: + - create + - patch + - list +- apiGroups: + - coordination.k8s.io + resources: + - leases + verbs: + - list + - watch +- apiGroups: + - discovery.k8s.io + resources: + - endpointslices + verbs: + - get + - list + - watch +- apiGroups: + - networking.k8s.io + resources: + - ingresses + verbs: + - get + - list + - watch +- apiGroups: + - "" + resources: + - nodes + verbs: + - list +- apiGroups: + - "apps" + resources: + - replicasets + - daemonsets + verbs: + - get +- apiGroups: + - networking.k8s.io + resources: + - ingressclasses + verbs: + - get + - list +- apiGroups: + - networking.k8s.io + resources: + - ingresses/status + verbs: + - update +/-/-/-/ +# Source: nginx-ingress/templates/clusterrolebinding.yaml +kind: ClusterRoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: custom-resources-nginx-ingress + labels: + helm.sh/chart: nginx-ingress-2.1.0 + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: custom-resources + app.kubernetes.io/version: "4.1.0" + app.kubernetes.io/managed-by: Helm +subjects: +- kind: ServiceAccount + name: custom-resources-nginx-ingress + namespace: custom-resources +roleRef: + kind: ClusterRole + name: custom-resources-nginx-ingress + apiGroup: rbac.authorization.k8s.io +/-/-/-/ +# Source: nginx-ingress/templates/controller-role.yaml +kind: Role +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: custom-resources-nginx-ingress + labels: + helm.sh/chart: nginx-ingress-2.1.0 + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: custom-resources + app.kubernetes.io/version: "4.1.0" + app.kubernetes.io/managed-by: Helm + namespace: custom-resources +rules: +- apiGroups: + - "" + resources: + - configmaps + - pods + - secrets + - services + verbs: + - get + - list + - watch +- apiGroups: + - "" + resources: + - namespaces + verbs: + - get +- apiGroups: + - "" + resources: + - pods + verbs: + - update +- apiGroups: + - "" + resources: + - events + verbs: + - create + - patch + - list +- apiGroups: + - coordination.k8s.io + resources: + - leases + resourceNames: + - custom-resources-nginx-ingress-leader-election + verbs: + - get + - update +- apiGroups: + - coordination.k8s.io + resources: + - leases + verbs: + - create +/-/-/-/ +# Source: nginx-ingress/templates/controller-rolebinding.yaml +kind: RoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: custom-resources-nginx-ingress + labels: + helm.sh/chart: nginx-ingress-2.1.0 + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: custom-resources + app.kubernetes.io/version: "4.1.0" + app.kubernetes.io/managed-by: Helm + namespace: custom-resources +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: custom-resources-nginx-ingress +subjects: +- kind: ServiceAccount + name: custom-resources-nginx-ingress + namespace: custom-resources +/-/-/-/ +# Source: nginx-ingress/templates/controller-service.yaml +apiVersion: v1 +kind: Service +metadata: + name: custom-resources-nginx-ingress-controller + namespace: custom-resources + labels: + helm.sh/chart: nginx-ingress-2.1.0 + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: custom-resources + app.kubernetes.io/version: "4.1.0" + app.kubernetes.io/managed-by: Helm +spec: + externalTrafficPolicy: Local + type: LoadBalancer + ports: + - port: 80 + targetPort: 80 + protocol: TCP + name: http + nodePort: + - port: 443 + targetPort: 443 + protocol: TCP + name: https + nodePort: + selector: + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: custom-resources +/-/-/-/ +# Source: nginx-ingress/templates/controller-deployment.yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: custom-resources-nginx-ingress-controller + namespace: custom-resources + labels: + helm.sh/chart: nginx-ingress-2.1.0 + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: custom-resources + app.kubernetes.io/version: "4.1.0" + app.kubernetes.io/managed-by: Helm +spec: + replicas: 1 + selector: + matchLabels: + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: custom-resources + template: + metadata: + labels: + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: custom-resources + annotations: + prometheus.io/scrape: "true" + prometheus.io/port: "9113" + prometheus.io/scheme: "http" + spec: + volumes: [] + serviceAccountName: custom-resources-nginx-ingress + automountServiceAccountToken: true + securityContext: + seccompProfile: + type: RuntimeDefault + terminationGracePeriodSeconds: 30 + hostNetwork: false + dnsPolicy: ClusterFirst + containers: + - image: nginx/nginx-ingress:4.1.0 + name: nginx-ingress + imagePullPolicy: "IfNotPresent" + ports: + - name: http + containerPort: 80 + protocol: TCP + - name: https + containerPort: 443 + protocol: TCP + - name: prometheus + containerPort: 9113 + - name: readiness-port + containerPort: 8081 + readinessProbe: + httpGet: + path: /nginx-ready + port: readiness-port + periodSeconds: 1 + initialDelaySeconds: 0 + resources: + requests: + cpu: 100m + memory: 128Mi + securityContext: + allowPrivilegeEscalation: false + readOnlyRootFilesystem: false + runAsUser: 101 #nginx + runAsNonRoot: true + capabilities: + drop: + - ALL + add: + - NET_BIND_SERVICE + volumeMounts: [] + env: + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + args: + - -nginx-plus=false + - -nginx-reload-timeout=60000 + - -enable-app-protect=false + - -enable-app-protect-dos=false + - -nginx-configmaps=$(POD_NAMESPACE)/custom-resources-nginx-ingress + - -ingress-class=nginx + - -health-status=false + - -health-status-uri=/nginx-health + - -nginx-debug=false + - -log-level=info + - -log-format=glog + - -nginx-status=true + - -nginx-status-port=8080 + - -nginx-status-allow-cidrs=127.0.0.1 + - -report-ingress-status + - -external-service=custom-resources-nginx-ingress-controller + - -enable-leader-election=true + - -leader-election-lock-name=custom-resources-nginx-ingress-leader-election + - -enable-prometheus-metrics=true + - -prometheus-metrics-listen-port=9113 + - -prometheus-tls-secret= + - -enable-service-insight=false + - -service-insight-listen-port=9114 + - -service-insight-tls-secret= + - -enable-custom-resources=false + - -enable-snippets=false + - -disable-ipv6=false + - -ready-status=true + - -ready-status-port=8081 + - -enable-latency-metrics=false + - -ssl-dynamic-reload=true + - -enable-telemetry-reporting=true + - -weight-changes-dynamic-reload=false +/-/-/-/ +# Source: nginx-ingress/templates/controller-ingress-class.yaml +apiVersion: networking.k8s.io/v1 +kind: IngressClass +metadata: + name: nginx + labels: + helm.sh/chart: nginx-ingress-2.1.0 + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: custom-resources + app.kubernetes.io/version: "4.1.0" + app.kubernetes.io/managed-by: Helm +spec: + controller: nginx.org/ingress-controller +/-/-/-/ +# Source: nginx-ingress/templates/controller-configmap.yaml +/-/-/-/ +/-/-/-/ +# Source: nginx-ingress/templates/controller-lease.yaml +apiVersion: coordination.k8s.io/v1 +kind: Lease +metadata: + name: custom-resources-nginx-ingress-leader-election + namespace: custom-resources + labels: + helm.sh/chart: nginx-ingress-2.1.0 + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: custom-resources + app.kubernetes.io/version: "4.1.0" + app.kubernetes.io/managed-by: Helm +--- + +[TestHelmNICTemplate/daemonset - 1] +/-/-/-/ +# Source: nginx-ingress/templates/controller-serviceaccount.yaml +apiVersion: v1 +kind: ServiceAccount +metadata: + name: daemonset-nginx-ingress + namespace: default + labels: + helm.sh/chart: nginx-ingress-2.1.0 + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: daemonset + app.kubernetes.io/version: "4.1.0" + app.kubernetes.io/managed-by: Helm +/-/-/-/ +# Source: nginx-ingress/templates/controller-configmap.yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: daemonset-nginx-ingress + namespace: default + labels: + helm.sh/chart: nginx-ingress-2.1.0 + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: daemonset + app.kubernetes.io/version: "4.1.0" + app.kubernetes.io/managed-by: Helm +data: + {} +/-/-/-/ +# Source: nginx-ingress/templates/controller-leader-election-configmap.yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: daemonset-nginx-ingress-leader-election + namespace: default + labels: + helm.sh/chart: nginx-ingress-2.1.0 + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: daemonset + app.kubernetes.io/version: "4.1.0" + app.kubernetes.io/managed-by: Helm +/-/-/-/ +# Source: nginx-ingress/templates/clusterrole.yaml +kind: ClusterRole +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: daemonset-nginx-ingress + labels: + helm.sh/chart: nginx-ingress-2.1.0 + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: daemonset + app.kubernetes.io/version: "4.1.0" + app.kubernetes.io/managed-by: Helm +rules: +- apiGroups: + - "" + resources: + - configmaps + - namespaces + - pods + - secrets + - services + verbs: + - get + - list + - watch +- apiGroups: + - "" + resources: + - events + verbs: + - create + - patch + - list +- apiGroups: + - coordination.k8s.io + resources: + - leases + verbs: + - list + - watch +- apiGroups: + - discovery.k8s.io + resources: + - endpointslices + verbs: + - get + - list + - watch +- apiGroups: + - networking.k8s.io + resources: + - ingresses + verbs: + - get + - list + - watch +- apiGroups: + - "" + resources: + - nodes + verbs: + - list +- apiGroups: + - "apps" + resources: + - replicasets + - daemonsets + verbs: + - get +- apiGroups: + - networking.k8s.io + resources: + - ingressclasses + verbs: + - get + - list +- apiGroups: + - networking.k8s.io + resources: + - ingresses/status + verbs: + - update +- apiGroups: + - k8s.nginx.org + resources: + - virtualservers + - virtualserverroutes + - globalconfigurations + - transportservers + - policies + verbs: + - list + - watch + - get +- apiGroups: + - k8s.nginx.org + resources: + - virtualservers/status + - virtualserverroutes/status + - policies/status + - transportservers/status + verbs: + - update +/-/-/-/ +# Source: nginx-ingress/templates/clusterrolebinding.yaml +kind: ClusterRoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: daemonset-nginx-ingress + labels: + helm.sh/chart: nginx-ingress-2.1.0 + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: daemonset + app.kubernetes.io/version: "4.1.0" + app.kubernetes.io/managed-by: Helm +subjects: +- kind: ServiceAccount + name: daemonset-nginx-ingress + namespace: default +roleRef: + kind: ClusterRole + name: daemonset-nginx-ingress + apiGroup: rbac.authorization.k8s.io +/-/-/-/ +# Source: nginx-ingress/templates/controller-role.yaml +kind: Role +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: daemonset-nginx-ingress + labels: + helm.sh/chart: nginx-ingress-2.1.0 + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: daemonset + app.kubernetes.io/version: "4.1.0" + app.kubernetes.io/managed-by: Helm + namespace: default +rules: +- apiGroups: + - "" + resources: + - configmaps + - pods + - secrets + - services + verbs: + - get + - list + - watch +- apiGroups: + - "" + resources: + - namespaces + verbs: + - get +- apiGroups: + - "" + resources: + - pods + verbs: + - update +- apiGroups: + - "" + resources: + - events + verbs: + - create + - patch + - list +- apiGroups: + - coordination.k8s.io + resources: + - leases + resourceNames: + - daemonset-nginx-ingress-leader-election + verbs: + - get + - update +- apiGroups: + - coordination.k8s.io + resources: + - leases + verbs: + - create +/-/-/-/ +# Source: nginx-ingress/templates/controller-rolebinding.yaml +kind: RoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: daemonset-nginx-ingress + labels: + helm.sh/chart: nginx-ingress-2.1.0 + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: daemonset + app.kubernetes.io/version: "4.1.0" + app.kubernetes.io/managed-by: Helm + namespace: default +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: daemonset-nginx-ingress +subjects: +- kind: ServiceAccount + name: daemonset-nginx-ingress + namespace: default +/-/-/-/ +# Source: nginx-ingress/templates/controller-service.yaml +apiVersion: v1 +kind: Service +metadata: + name: daemonset-nginx-ingress-controller + namespace: default + labels: + helm.sh/chart: nginx-ingress-2.1.0 + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: daemonset + app.kubernetes.io/version: "4.1.0" + app.kubernetes.io/managed-by: Helm +spec: + externalTrafficPolicy: Local + type: LoadBalancer + ports: + - port: 80 + targetPort: 80 + protocol: TCP + name: http + nodePort: + - port: 443 + targetPort: 443 + protocol: TCP + name: https + nodePort: + selector: + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: daemonset +/-/-/-/ +# Source: nginx-ingress/templates/controller-daemonset.yaml +apiVersion: apps/v1 +kind: DaemonSet +metadata: + name: daemonset-nginx-ingress-controller + namespace: default + labels: + helm.sh/chart: nginx-ingress-2.1.0 + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: daemonset + app.kubernetes.io/version: "4.1.0" + app.kubernetes.io/managed-by: Helm +spec: + selector: + matchLabels: + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: daemonset + template: + metadata: + labels: + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: daemonset + annotations: + prometheus.io/scrape: "true" + prometheus.io/port: "9113" + prometheus.io/scheme: "http" + spec: + serviceAccountName: daemonset-nginx-ingress + automountServiceAccountToken: true + securityContext: + seccompProfile: + type: RuntimeDefault + terminationGracePeriodSeconds: 30 + volumes: [] + hostNetwork: false + dnsPolicy: ClusterFirst + containers: + - name: nginx-ingress + image: nginx/nginx-ingress:4.1.0 + imagePullPolicy: "IfNotPresent" + ports: + - name: http + containerPort: 80 + protocol: TCP + - name: https + containerPort: 443 + protocol: TCP + + - name: prometheus + containerPort: 9113 + - name: readiness-port + containerPort: 8081 + readinessProbe: + httpGet: + path: /nginx-ready + port: readiness-port + periodSeconds: 1 + initialDelaySeconds: 0 + securityContext: + allowPrivilegeEscalation: false + readOnlyRootFilesystem: false + runAsUser: 101 #nginx + runAsNonRoot: true + capabilities: + drop: + - ALL + add: + - NET_BIND_SERVICE + volumeMounts: [] + env: + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + resources: + requests: + cpu: 100m + memory: 128Mi + args: + - -nginx-plus=false + - -nginx-reload-timeout=60000 + - -enable-app-protect=false + - -enable-app-protect-dos=false + - -nginx-configmaps=$(POD_NAMESPACE)/daemonset-nginx-ingress + - -ingress-class=nginx + - -health-status=false + - -health-status-uri=/nginx-health + - -nginx-debug=false + - -log-level=info + - -log-format=glog + - -nginx-status=true + - -nginx-status-port=8080 + - -nginx-status-allow-cidrs=127.0.0.1 + - -report-ingress-status + - -external-service=daemonset-nginx-ingress-controller + - -enable-leader-election=true + - -leader-election-lock-name=daemonset-nginx-ingress-leader-election + - -enable-prometheus-metrics=true + - -prometheus-metrics-listen-port=9113 + - -prometheus-tls-secret= + - -enable-service-insight=false + - -service-insight-listen-port=9114 + - -service-insight-tls-secret= + - -enable-custom-resources=true + - -enable-snippets=false + - -disable-ipv6=false + - -enable-tls-passthrough=false + - -enable-cert-manager=false + - -enable-oidc=false + - -enable-external-dns=false + - -default-http-listener-port=80 + - -default-https-listener-port=443 + - -ready-status=true + - -ready-status-port=8081 + - -enable-latency-metrics=false + - -ssl-dynamic-reload=true + - -enable-telemetry-reporting=true + - -weight-changes-dynamic-reload=false +/-/-/-/ +# Source: nginx-ingress/templates/controller-ingress-class.yaml +apiVersion: networking.k8s.io/v1 +kind: IngressClass +metadata: + name: nginx + labels: + helm.sh/chart: nginx-ingress-2.1.0 + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: daemonset + app.kubernetes.io/version: "4.1.0" + app.kubernetes.io/managed-by: Helm +spec: + controller: nginx.org/ingress-controller +/-/-/-/ +# Source: nginx-ingress/templates/controller-configmap.yaml +/-/-/-/ +/-/-/-/ +# Source: nginx-ingress/templates/controller-lease.yaml +apiVersion: coordination.k8s.io/v1 +kind: Lease +metadata: + name: daemonset-nginx-ingress-leader-election + namespace: default + labels: + helm.sh/chart: nginx-ingress-2.1.0 + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: daemonset + app.kubernetes.io/version: "4.1.0" + app.kubernetes.io/managed-by: Helm +--- + +[TestHelmNICTemplate/default_values_file - 1] +/-/-/-/ +# Source: nginx-ingress/templates/controller-serviceaccount.yaml +apiVersion: v1 +kind: ServiceAccount +metadata: + name: default-nginx-ingress + namespace: default + labels: + helm.sh/chart: nginx-ingress-2.1.0 + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: default + app.kubernetes.io/version: "4.1.0" + app.kubernetes.io/managed-by: Helm +/-/-/-/ +# Source: nginx-ingress/templates/controller-configmap.yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: default-nginx-ingress + namespace: default + labels: + helm.sh/chart: nginx-ingress-2.1.0 + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: default + app.kubernetes.io/version: "4.1.0" + app.kubernetes.io/managed-by: Helm +data: + {} +/-/-/-/ +# Source: nginx-ingress/templates/controller-leader-election-configmap.yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: default-nginx-ingress-leader-election + namespace: default + labels: + helm.sh/chart: nginx-ingress-2.1.0 + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: default + app.kubernetes.io/version: "4.1.0" + app.kubernetes.io/managed-by: Helm +/-/-/-/ +# Source: nginx-ingress/templates/clusterrole.yaml +kind: ClusterRole +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: default-nginx-ingress + labels: + helm.sh/chart: nginx-ingress-2.1.0 + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: default + app.kubernetes.io/version: "4.1.0" + app.kubernetes.io/managed-by: Helm +rules: +- apiGroups: + - "" + resources: + - configmaps + - namespaces + - pods + - secrets + - services + verbs: + - get + - list + - watch +- apiGroups: + - "" + resources: + - events + verbs: + - create + - patch + - list +- apiGroups: + - coordination.k8s.io + resources: + - leases + verbs: + - list + - watch +- apiGroups: + - discovery.k8s.io + resources: + - endpointslices + verbs: + - get + - list + - watch +- apiGroups: + - networking.k8s.io + resources: + - ingresses + verbs: + - get + - list + - watch +- apiGroups: + - "" + resources: + - nodes + verbs: + - list +- apiGroups: + - "apps" + resources: + - replicasets + - daemonsets + verbs: + - get +- apiGroups: + - networking.k8s.io + resources: + - ingressclasses + verbs: + - get + - list +- apiGroups: + - networking.k8s.io + resources: + - ingresses/status + verbs: + - update +- apiGroups: + - k8s.nginx.org + resources: + - virtualservers + - virtualserverroutes + - globalconfigurations + - transportservers + - policies + verbs: + - list + - watch + - get +- apiGroups: + - k8s.nginx.org + resources: + - virtualservers/status + - virtualserverroutes/status + - policies/status + - transportservers/status + verbs: + - update +/-/-/-/ +# Source: nginx-ingress/templates/clusterrolebinding.yaml +kind: ClusterRoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: default-nginx-ingress + labels: + helm.sh/chart: nginx-ingress-2.1.0 + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: default + app.kubernetes.io/version: "4.1.0" + app.kubernetes.io/managed-by: Helm +subjects: +- kind: ServiceAccount + name: default-nginx-ingress + namespace: default +roleRef: + kind: ClusterRole + name: default-nginx-ingress + apiGroup: rbac.authorization.k8s.io +/-/-/-/ +# Source: nginx-ingress/templates/controller-role.yaml +kind: Role +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: default-nginx-ingress + labels: + helm.sh/chart: nginx-ingress-2.1.0 + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: default + app.kubernetes.io/version: "4.1.0" + app.kubernetes.io/managed-by: Helm + namespace: default +rules: +- apiGroups: + - "" + resources: + - configmaps + - pods + - secrets + - services + verbs: + - get + - list + - watch +- apiGroups: + - "" + resources: + - namespaces + verbs: + - get +- apiGroups: + - "" + resources: + - pods + verbs: + - update +- apiGroups: + - "" + resources: + - events + verbs: + - create + - patch + - list +- apiGroups: + - coordination.k8s.io + resources: + - leases + resourceNames: + - default-nginx-ingress-leader-election + verbs: + - get + - update +- apiGroups: + - coordination.k8s.io + resources: + - leases + verbs: + - create +/-/-/-/ +# Source: nginx-ingress/templates/controller-rolebinding.yaml +kind: RoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: default-nginx-ingress + labels: + helm.sh/chart: nginx-ingress-2.1.0 + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: default + app.kubernetes.io/version: "4.1.0" + app.kubernetes.io/managed-by: Helm + namespace: default +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: default-nginx-ingress +subjects: +- kind: ServiceAccount + name: default-nginx-ingress + namespace: default +/-/-/-/ +# Source: nginx-ingress/templates/controller-service.yaml +apiVersion: v1 +kind: Service +metadata: + name: default-nginx-ingress-controller + namespace: default + labels: + helm.sh/chart: nginx-ingress-2.1.0 + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: default + app.kubernetes.io/version: "4.1.0" + app.kubernetes.io/managed-by: Helm +spec: + externalTrafficPolicy: Local + type: LoadBalancer + ports: + - port: 80 + targetPort: 80 + protocol: TCP + name: http + nodePort: + - port: 443 + targetPort: 443 + protocol: TCP + name: https + nodePort: + selector: + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: default +/-/-/-/ +# Source: nginx-ingress/templates/controller-deployment.yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: default-nginx-ingress-controller + namespace: default + labels: + helm.sh/chart: nginx-ingress-2.1.0 + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: default + app.kubernetes.io/version: "4.1.0" + app.kubernetes.io/managed-by: Helm +spec: + replicas: 1 + selector: + matchLabels: + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: default + template: + metadata: + labels: + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: default + annotations: + prometheus.io/scrape: "true" + prometheus.io/port: "9113" + prometheus.io/scheme: "http" + spec: + volumes: [] + serviceAccountName: default-nginx-ingress + automountServiceAccountToken: true + securityContext: + seccompProfile: + type: RuntimeDefault + terminationGracePeriodSeconds: 30 + hostNetwork: false + dnsPolicy: ClusterFirst + containers: + - image: nginx/nginx-ingress:4.1.0 + name: nginx-ingress + imagePullPolicy: "IfNotPresent" + ports: + - name: http + containerPort: 80 + protocol: TCP + - name: https + containerPort: 443 + protocol: TCP + - name: prometheus + containerPort: 9113 + - name: readiness-port + containerPort: 8081 + readinessProbe: + httpGet: + path: /nginx-ready + port: readiness-port + periodSeconds: 1 + initialDelaySeconds: 0 + resources: + requests: + cpu: 100m + memory: 128Mi + securityContext: + allowPrivilegeEscalation: false + readOnlyRootFilesystem: false + runAsUser: 101 #nginx + runAsNonRoot: true + capabilities: + drop: + - ALL + add: + - NET_BIND_SERVICE + volumeMounts: [] + env: + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + args: + - -nginx-plus=false + - -nginx-reload-timeout=60000 + - -enable-app-protect=false + - -enable-app-protect-dos=false + - -nginx-configmaps=$(POD_NAMESPACE)/default-nginx-ingress + - -ingress-class=nginx + - -health-status=false + - -health-status-uri=/nginx-health + - -nginx-debug=false + - -log-level=info + - -log-format=glog + - -nginx-status=true + - -nginx-status-port=8080 + - -nginx-status-allow-cidrs=127.0.0.1 + - -report-ingress-status + - -external-service=default-nginx-ingress-controller + - -enable-leader-election=true + - -leader-election-lock-name=default-nginx-ingress-leader-election + - -enable-prometheus-metrics=true + - -prometheus-metrics-listen-port=9113 + - -prometheus-tls-secret= + - -enable-service-insight=false + - -service-insight-listen-port=9114 + - -service-insight-tls-secret= + - -enable-custom-resources=true + - -enable-snippets=false + - -disable-ipv6=false + - -enable-tls-passthrough=false + - -enable-cert-manager=false + - -enable-oidc=false + - -enable-external-dns=false + - -default-http-listener-port=80 + - -default-https-listener-port=443 + - -ready-status=true + - -ready-status-port=8081 + - -enable-latency-metrics=false + - -ssl-dynamic-reload=true + - -enable-telemetry-reporting=true + - -weight-changes-dynamic-reload=false +/-/-/-/ +# Source: nginx-ingress/templates/controller-ingress-class.yaml +apiVersion: networking.k8s.io/v1 +kind: IngressClass +metadata: + name: nginx + labels: + helm.sh/chart: nginx-ingress-2.1.0 + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: default + app.kubernetes.io/version: "4.1.0" + app.kubernetes.io/managed-by: Helm +spec: + controller: nginx.org/ingress-controller +/-/-/-/ +# Source: nginx-ingress/templates/controller-configmap.yaml +/-/-/-/ +/-/-/-/ +# Source: nginx-ingress/templates/controller-lease.yaml +apiVersion: coordination.k8s.io/v1 +kind: Lease +metadata: + name: default-nginx-ingress-leader-election + namespace: default + labels: + helm.sh/chart: nginx-ingress-2.1.0 + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: default + app.kubernetes.io/version: "4.1.0" + app.kubernetes.io/managed-by: Helm +--- + +[TestHelmNICTemplate/globalConfig - 1] +/-/-/-/ +# Source: nginx-ingress/templates/controller-serviceaccount.yaml +apiVersion: v1 +kind: ServiceAccount +metadata: + name: global-configuration-nginx-ingress + namespace: gc + labels: + helm.sh/chart: nginx-ingress-2.1.0 + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: global-configuration + app.kubernetes.io/version: "4.1.0" + app.kubernetes.io/managed-by: Helm +/-/-/-/ +# Source: nginx-ingress/templates/controller-configmap.yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: global-configuration-nginx-ingress + namespace: gc + labels: + helm.sh/chart: nginx-ingress-2.1.0 + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: global-configuration + app.kubernetes.io/version: "4.1.0" + app.kubernetes.io/managed-by: Helm +data: + {} +/-/-/-/ +# Source: nginx-ingress/templates/controller-leader-election-configmap.yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: global-configuration-nginx-ingress-leader-election + namespace: gc + labels: + helm.sh/chart: nginx-ingress-2.1.0 + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: global-configuration + app.kubernetes.io/version: "4.1.0" + app.kubernetes.io/managed-by: Helm +/-/-/-/ +# Source: nginx-ingress/templates/clusterrole.yaml +kind: ClusterRole +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: global-configuration-nginx-ingress + labels: + helm.sh/chart: nginx-ingress-2.1.0 + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: global-configuration + app.kubernetes.io/version: "4.1.0" + app.kubernetes.io/managed-by: Helm +rules: +- apiGroups: + - "" + resources: + - configmaps + - namespaces + - pods + - secrets + - services + verbs: + - get + - list + - watch +- apiGroups: + - "" + resources: + - events + verbs: + - create + - patch + - list +- apiGroups: + - coordination.k8s.io + resources: + - leases + verbs: + - list + - watch +- apiGroups: + - discovery.k8s.io + resources: + - endpointslices + verbs: + - get + - list + - watch +- apiGroups: + - networking.k8s.io + resources: + - ingresses + verbs: + - get + - list + - watch +- apiGroups: + - "" + resources: + - nodes + verbs: + - list +- apiGroups: + - "apps" + resources: + - replicasets + - daemonsets + verbs: + - get +- apiGroups: + - networking.k8s.io + resources: + - ingressclasses + verbs: + - get + - list +- apiGroups: + - networking.k8s.io + resources: + - ingresses/status + verbs: + - update +- apiGroups: + - k8s.nginx.org + resources: + - virtualservers + - virtualserverroutes + - globalconfigurations + - transportservers + - policies + verbs: + - list + - watch + - get +- apiGroups: + - k8s.nginx.org + resources: + - virtualservers/status + - virtualserverroutes/status + - policies/status + - transportservers/status + verbs: + - update +/-/-/-/ +# Source: nginx-ingress/templates/clusterrolebinding.yaml +kind: ClusterRoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: global-configuration-nginx-ingress + labels: + helm.sh/chart: nginx-ingress-2.1.0 + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: global-configuration + app.kubernetes.io/version: "4.1.0" + app.kubernetes.io/managed-by: Helm +subjects: +- kind: ServiceAccount + name: global-configuration-nginx-ingress + namespace: gc +roleRef: + kind: ClusterRole + name: global-configuration-nginx-ingress + apiGroup: rbac.authorization.k8s.io +/-/-/-/ +# Source: nginx-ingress/templates/controller-role.yaml +kind: Role +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: global-configuration-nginx-ingress + labels: + helm.sh/chart: nginx-ingress-2.1.0 + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: global-configuration + app.kubernetes.io/version: "4.1.0" + app.kubernetes.io/managed-by: Helm + namespace: gc +rules: +- apiGroups: + - "" + resources: + - configmaps + - pods + - secrets + - services + verbs: + - get + - list + - watch +- apiGroups: + - "" + resources: + - namespaces + verbs: + - get +- apiGroups: + - "" + resources: + - pods + verbs: + - update +- apiGroups: + - "" + resources: + - events + verbs: + - create + - patch + - list +- apiGroups: + - coordination.k8s.io + resources: + - leases + resourceNames: + - global-configuration-nginx-ingress-leader-election + verbs: + - get + - update +- apiGroups: + - coordination.k8s.io + resources: + - leases + verbs: + - create +/-/-/-/ +# Source: nginx-ingress/templates/controller-rolebinding.yaml +kind: RoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: global-configuration-nginx-ingress + labels: + helm.sh/chart: nginx-ingress-2.1.0 + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: global-configuration + app.kubernetes.io/version: "4.1.0" + app.kubernetes.io/managed-by: Helm + namespace: gc +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: global-configuration-nginx-ingress +subjects: +- kind: ServiceAccount + name: global-configuration-nginx-ingress + namespace: gc +/-/-/-/ +# Source: nginx-ingress/templates/controller-service.yaml +apiVersion: v1 +kind: Service +metadata: + name: global-configuration-nginx-ingress-controller + namespace: gc + labels: + helm.sh/chart: nginx-ingress-2.1.0 + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: global-configuration + app.kubernetes.io/version: "4.1.0" + app.kubernetes.io/managed-by: Helm +spec: + externalTrafficPolicy: Local + type: LoadBalancer + ports: + - port: 80 + targetPort: 80 + protocol: TCP + name: http + nodePort: + - port: 443 + targetPort: 443 + protocol: TCP + name: https + nodePort: + selector: + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: global-configuration +/-/-/-/ +# Source: nginx-ingress/templates/controller-deployment.yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: global-configuration-nginx-ingress-controller + namespace: gc + labels: + helm.sh/chart: nginx-ingress-2.1.0 + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: global-configuration + app.kubernetes.io/version: "4.1.0" + app.kubernetes.io/managed-by: Helm +spec: + replicas: 1 + selector: + matchLabels: + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: global-configuration + template: + metadata: + labels: + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: global-configuration + annotations: + prometheus.io/scrape: "true" + prometheus.io/port: "9113" + prometheus.io/scheme: "http" + spec: + volumes: [] + serviceAccountName: global-configuration-nginx-ingress + automountServiceAccountToken: true + securityContext: + seccompProfile: + type: RuntimeDefault + terminationGracePeriodSeconds: 30 + hostNetwork: false + dnsPolicy: ClusterFirst + containers: + - image: nginx/nginx-ingress:4.1.0 + name: nginx-ingress + imagePullPolicy: "IfNotPresent" + ports: + - name: http + containerPort: 80 + protocol: TCP + - name: https + containerPort: 443 + protocol: TCP + - name: prometheus + containerPort: 9113 + - name: readiness-port + containerPort: 8081 + readinessProbe: + httpGet: + path: /nginx-ready + port: readiness-port + periodSeconds: 1 + initialDelaySeconds: 0 + resources: + requests: + cpu: 100m + memory: 128Mi + securityContext: + allowPrivilegeEscalation: false + readOnlyRootFilesystem: false + runAsUser: 101 #nginx + runAsNonRoot: true + capabilities: + drop: + - ALL + add: + - NET_BIND_SERVICE + volumeMounts: [] + env: + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + args: + - -nginx-plus=false + - -nginx-reload-timeout=60000 + - -enable-app-protect=false + - -enable-app-protect-dos=false + - -nginx-configmaps=$(POD_NAMESPACE)/global-configuration-nginx-ingress + - -ingress-class=nginx + - -health-status=false + - -health-status-uri=/nginx-health + - -nginx-debug=false + - -log-level=info + - -log-format=glog + - -nginx-status=true + - -nginx-status-port=8080 + - -nginx-status-allow-cidrs=127.0.0.1 + - -report-ingress-status + - -external-service=global-configuration-nginx-ingress-controller + - -enable-leader-election=true + - -leader-election-lock-name=global-configuration-nginx-ingress-leader-election + - -enable-prometheus-metrics=true + - -prometheus-metrics-listen-port=9113 + - -prometheus-tls-secret= + - -enable-service-insight=false + - -service-insight-listen-port=9114 + - -service-insight-tls-secret= + - -enable-custom-resources=true + - -enable-snippets=false + - -disable-ipv6=false + - -enable-tls-passthrough=false + - -enable-cert-manager=false + - -enable-oidc=false + - -enable-external-dns=false + - -default-http-listener-port=80 + - -default-https-listener-port=443 + - -global-configuration=$(POD_NAMESPACE)/global-configuration-nginx-ingress-controller + - -ready-status=true + - -ready-status-port=8081 + - -enable-latency-metrics=false + - -ssl-dynamic-reload=true + - -enable-telemetry-reporting=true + - -weight-changes-dynamic-reload=false +/-/-/-/ +# Source: nginx-ingress/templates/controller-ingress-class.yaml +apiVersion: networking.k8s.io/v1 +kind: IngressClass +metadata: + name: nginx + labels: + helm.sh/chart: nginx-ingress-2.1.0 + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: global-configuration + app.kubernetes.io/version: "4.1.0" + app.kubernetes.io/managed-by: Helm +spec: + controller: nginx.org/ingress-controller +/-/-/-/ +# Source: nginx-ingress/templates/controller-configmap.yaml +/-/-/-/ +/-/-/-/ +# Source: nginx-ingress/templates/controller-globalconfiguration.yaml +apiVersion: k8s.nginx.org/v1 +kind: GlobalConfiguration +metadata: + name: global-configuration-nginx-ingress-controller + namespace: gc + labels: + helm.sh/chart: nginx-ingress-2.1.0 + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: global-configuration + app.kubernetes.io/version: "4.1.0" + app.kubernetes.io/managed-by: Helm +spec: + listeners: + - name: dns-udp + port: 5353 + protocol: UDP + - name: dns-tcp + port: 5353 + protocol: TCP +/-/-/-/ +# Source: nginx-ingress/templates/controller-lease.yaml +apiVersion: coordination.k8s.io/v1 +kind: Lease +metadata: + name: global-configuration-nginx-ingress-leader-election + namespace: gc + labels: + helm.sh/chart: nginx-ingress-2.1.0 + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: global-configuration + app.kubernetes.io/version: "4.1.0" + app.kubernetes.io/managed-by: Helm +--- + +[TestHelmNICTemplate/ingressClass - 1] +/-/-/-/ +# Source: nginx-ingress/templates/controller-serviceaccount.yaml +apiVersion: v1 +kind: ServiceAccount +metadata: + name: ingress-class-nginx-ingress + namespace: default + labels: + helm.sh/chart: nginx-ingress-2.1.0 + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: ingress-class + app.kubernetes.io/version: "4.1.0" + app.kubernetes.io/managed-by: Helm +/-/-/-/ +# Source: nginx-ingress/templates/controller-configmap.yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: ingress-class-nginx-ingress + namespace: default + labels: + helm.sh/chart: nginx-ingress-2.1.0 + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: ingress-class + app.kubernetes.io/version: "4.1.0" + app.kubernetes.io/managed-by: Helm +data: + {} +/-/-/-/ +# Source: nginx-ingress/templates/controller-leader-election-configmap.yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: ingress-class-nginx-ingress-leader-election + namespace: default + labels: + helm.sh/chart: nginx-ingress-2.1.0 + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: ingress-class + app.kubernetes.io/version: "4.1.0" + app.kubernetes.io/managed-by: Helm +/-/-/-/ +# Source: nginx-ingress/templates/clusterrole.yaml +kind: ClusterRole +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: ingress-class-nginx-ingress + labels: + helm.sh/chart: nginx-ingress-2.1.0 + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: ingress-class + app.kubernetes.io/version: "4.1.0" + app.kubernetes.io/managed-by: Helm +rules: +- apiGroups: + - "" + resources: + - configmaps + - namespaces + - pods + - secrets + - services + verbs: + - get + - list + - watch +- apiGroups: + - "" + resources: + - events + verbs: + - create + - patch + - list +- apiGroups: + - coordination.k8s.io + resources: + - leases + verbs: + - list + - watch +- apiGroups: + - discovery.k8s.io + resources: + - endpointslices + verbs: + - get + - list + - watch +- apiGroups: + - networking.k8s.io + resources: + - ingresses + verbs: + - get + - list + - watch +- apiGroups: + - "" + resources: + - nodes + verbs: + - list +- apiGroups: + - "apps" + resources: + - replicasets + - daemonsets + verbs: + - get +- apiGroups: + - networking.k8s.io + resources: + - ingressclasses + verbs: + - get + - list +- apiGroups: + - networking.k8s.io + resources: + - ingresses/status + verbs: + - update +- apiGroups: + - k8s.nginx.org + resources: + - virtualservers + - virtualserverroutes + - globalconfigurations + - transportservers + - policies + verbs: + - list + - watch + - get +- apiGroups: + - k8s.nginx.org + resources: + - virtualservers/status + - virtualserverroutes/status + - policies/status + - transportservers/status + verbs: + - update +/-/-/-/ +# Source: nginx-ingress/templates/clusterrolebinding.yaml +kind: ClusterRoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: ingress-class-nginx-ingress + labels: + helm.sh/chart: nginx-ingress-2.1.0 + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: ingress-class + app.kubernetes.io/version: "4.1.0" + app.kubernetes.io/managed-by: Helm +subjects: +- kind: ServiceAccount + name: ingress-class-nginx-ingress + namespace: default +roleRef: + kind: ClusterRole + name: ingress-class-nginx-ingress + apiGroup: rbac.authorization.k8s.io +/-/-/-/ +# Source: nginx-ingress/templates/controller-role.yaml +kind: Role +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: ingress-class-nginx-ingress + labels: + helm.sh/chart: nginx-ingress-2.1.0 + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: ingress-class + app.kubernetes.io/version: "4.1.0" + app.kubernetes.io/managed-by: Helm + namespace: default +rules: +- apiGroups: + - "" + resources: + - configmaps + - pods + - secrets + - services + verbs: + - get + - list + - watch +- apiGroups: + - "" + resources: + - namespaces + verbs: + - get +- apiGroups: + - "" + resources: + - pods + verbs: + - update +- apiGroups: + - "" + resources: + - events + verbs: + - create + - patch + - list +- apiGroups: + - coordination.k8s.io + resources: + - leases + resourceNames: + - ingress-class-nginx-ingress-leader-election + verbs: + - get + - update +- apiGroups: + - coordination.k8s.io + resources: + - leases + verbs: + - create +/-/-/-/ +# Source: nginx-ingress/templates/controller-rolebinding.yaml +kind: RoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: ingress-class-nginx-ingress + labels: + helm.sh/chart: nginx-ingress-2.1.0 + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: ingress-class + app.kubernetes.io/version: "4.1.0" + app.kubernetes.io/managed-by: Helm + namespace: default +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: ingress-class-nginx-ingress +subjects: +- kind: ServiceAccount + name: ingress-class-nginx-ingress + namespace: default +/-/-/-/ +# Source: nginx-ingress/templates/controller-service.yaml +apiVersion: v1 +kind: Service +metadata: + name: ingress-class-nginx-ingress-controller + namespace: default + labels: + helm.sh/chart: nginx-ingress-2.1.0 + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: ingress-class + app.kubernetes.io/version: "4.1.0" + app.kubernetes.io/managed-by: Helm +spec: + externalTrafficPolicy: Local + type: LoadBalancer + ports: + - port: 80 + targetPort: 80 + protocol: TCP + name: http + nodePort: + - port: 443 + targetPort: 443 + protocol: TCP + name: https + nodePort: + selector: + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: ingress-class +/-/-/-/ +# Source: nginx-ingress/templates/controller-deployment.yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: ingress-class-nginx-ingress-controller + namespace: default + labels: + helm.sh/chart: nginx-ingress-2.1.0 + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: ingress-class + app.kubernetes.io/version: "4.1.0" + app.kubernetes.io/managed-by: Helm +spec: + replicas: 1 + selector: + matchLabels: + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: ingress-class + template: + metadata: + labels: + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: ingress-class + annotations: + prometheus.io/scrape: "true" + prometheus.io/port: "9113" + prometheus.io/scheme: "http" + spec: + volumes: [] + serviceAccountName: ingress-class-nginx-ingress + automountServiceAccountToken: true + securityContext: + seccompProfile: + type: RuntimeDefault + terminationGracePeriodSeconds: 30 + hostNetwork: false + dnsPolicy: ClusterFirst + containers: + - image: nginx/nginx-ingress:4.1.0 + name: nginx-ingress + imagePullPolicy: "IfNotPresent" + ports: + - name: http + containerPort: 80 + protocol: TCP + - name: https + containerPort: 443 + protocol: TCP + - name: prometheus + containerPort: 9113 + - name: readiness-port + containerPort: 8081 + readinessProbe: + httpGet: + path: /nginx-ready + port: readiness-port + periodSeconds: 1 + initialDelaySeconds: 0 + resources: + requests: + cpu: 100m + memory: 128Mi + securityContext: + allowPrivilegeEscalation: false + readOnlyRootFilesystem: false + runAsUser: 101 #nginx + runAsNonRoot: true + capabilities: + drop: + - ALL + add: + - NET_BIND_SERVICE + volumeMounts: [] + env: + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + args: + - -nginx-plus=false + - -nginx-reload-timeout=60000 + - -enable-app-protect=false + - -enable-app-protect-dos=false + - -nginx-configmaps=$(POD_NAMESPACE)/ingress-class-nginx-ingress + - -ingress-class=changed + - -health-status=false + - -health-status-uri=/nginx-health + - -nginx-debug=false + - -log-level=info + - -log-format=glog + - -nginx-status=true + - -nginx-status-port=8080 + - -nginx-status-allow-cidrs=127.0.0.1 + - -report-ingress-status + - -external-service=ingress-class-nginx-ingress-controller + - -enable-leader-election=true + - -leader-election-lock-name=ingress-class-nginx-ingress-leader-election + - -enable-prometheus-metrics=true + - -prometheus-metrics-listen-port=9113 + - -prometheus-tls-secret= + - -enable-service-insight=false + - -service-insight-listen-port=9114 + - -service-insight-tls-secret= + - -enable-custom-resources=true + - -enable-snippets=false + - -disable-ipv6=false + - -enable-tls-passthrough=false + - -enable-cert-manager=false + - -enable-oidc=false + - -enable-external-dns=false + - -default-http-listener-port=80 + - -default-https-listener-port=443 + - -ready-status=true + - -ready-status-port=8081 + - -enable-latency-metrics=false + - -ssl-dynamic-reload=true + - -enable-telemetry-reporting=true + - -weight-changes-dynamic-reload=false +/-/-/-/ +# Source: nginx-ingress/templates/controller-ingress-class.yaml +apiVersion: networking.k8s.io/v1 +kind: IngressClass +metadata: + name: changed + labels: + helm.sh/chart: nginx-ingress-2.1.0 + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: ingress-class + app.kubernetes.io/version: "4.1.0" + app.kubernetes.io/managed-by: Helm + annotations: + ingressclass.kubernetes.io/is-default-class: "true" +spec: + controller: nginx.org/ingress-controller +/-/-/-/ +# Source: nginx-ingress/templates/controller-configmap.yaml +/-/-/-/ +/-/-/-/ +# Source: nginx-ingress/templates/controller-lease.yaml +apiVersion: coordination.k8s.io/v1 +kind: Lease +metadata: + name: ingress-class-nginx-ingress-leader-election + namespace: default + labels: + helm.sh/chart: nginx-ingress-2.1.0 + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: ingress-class + app.kubernetes.io/version: "4.1.0" + app.kubernetes.io/managed-by: Helm +--- + +[TestHelmNICTemplate/namespace - 1] +/-/-/-/ +# Source: nginx-ingress/templates/controller-serviceaccount.yaml +apiVersion: v1 +kind: ServiceAccount +metadata: + name: namespace-nginx-ingress + namespace: nginx-ingress + labels: + helm.sh/chart: nginx-ingress-2.1.0 + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: namespace + app.kubernetes.io/version: "4.1.0" + app.kubernetes.io/managed-by: Helm +/-/-/-/ +# Source: nginx-ingress/templates/controller-configmap.yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: namespace-nginx-ingress + namespace: nginx-ingress + labels: + helm.sh/chart: nginx-ingress-2.1.0 + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: namespace + app.kubernetes.io/version: "4.1.0" + app.kubernetes.io/managed-by: Helm +data: + {} +/-/-/-/ +# Source: nginx-ingress/templates/controller-leader-election-configmap.yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: namespace-nginx-ingress-leader-election + namespace: nginx-ingress + labels: + helm.sh/chart: nginx-ingress-2.1.0 + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: namespace + app.kubernetes.io/version: "4.1.0" + app.kubernetes.io/managed-by: Helm +/-/-/-/ +# Source: nginx-ingress/templates/clusterrole.yaml +kind: ClusterRole +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: namespace-nginx-ingress + labels: + helm.sh/chart: nginx-ingress-2.1.0 + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: namespace + app.kubernetes.io/version: "4.1.0" + app.kubernetes.io/managed-by: Helm +rules: +- apiGroups: + - "" + resources: + - configmaps + - namespaces + - pods + - secrets + - services + verbs: + - get + - list + - watch +- apiGroups: + - "" + resources: + - events + verbs: + - create + - patch + - list +- apiGroups: + - coordination.k8s.io + resources: + - leases + verbs: + - list + - watch +- apiGroups: + - discovery.k8s.io + resources: + - endpointslices + verbs: + - get + - list + - watch +- apiGroups: + - networking.k8s.io + resources: + - ingresses + verbs: + - get + - list + - watch +- apiGroups: + - "" + resources: + - nodes + verbs: + - list +- apiGroups: + - "apps" + resources: + - replicasets + - daemonsets + verbs: + - get +- apiGroups: + - networking.k8s.io + resources: + - ingressclasses + verbs: + - get + - list +- apiGroups: + - networking.k8s.io + resources: + - ingresses/status + verbs: + - update +- apiGroups: + - k8s.nginx.org + resources: + - virtualservers + - virtualserverroutes + - globalconfigurations + - transportservers + - policies + verbs: + - list + - watch + - get +- apiGroups: + - k8s.nginx.org + resources: + - virtualservers/status + - virtualserverroutes/status + - policies/status + - transportservers/status + verbs: + - update +/-/-/-/ +# Source: nginx-ingress/templates/clusterrolebinding.yaml +kind: ClusterRoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: namespace-nginx-ingress + labels: + helm.sh/chart: nginx-ingress-2.1.0 + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: namespace + app.kubernetes.io/version: "4.1.0" + app.kubernetes.io/managed-by: Helm +subjects: +- kind: ServiceAccount + name: namespace-nginx-ingress + namespace: nginx-ingress +roleRef: + kind: ClusterRole + name: namespace-nginx-ingress + apiGroup: rbac.authorization.k8s.io +/-/-/-/ +# Source: nginx-ingress/templates/controller-role.yaml +kind: Role +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: namespace-nginx-ingress + labels: + helm.sh/chart: nginx-ingress-2.1.0 + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: namespace + app.kubernetes.io/version: "4.1.0" + app.kubernetes.io/managed-by: Helm + namespace: nginx-ingress +rules: +- apiGroups: + - "" + resources: + - configmaps + - pods + - secrets + - services + verbs: + - get + - list + - watch +- apiGroups: + - "" + resources: + - namespaces + verbs: + - get +- apiGroups: + - "" + resources: + - pods + verbs: + - update +- apiGroups: + - "" + resources: + - events + verbs: + - create + - patch + - list +- apiGroups: + - coordination.k8s.io + resources: + - leases + resourceNames: + - namespace-nginx-ingress-leader-election + verbs: + - get + - update +- apiGroups: + - coordination.k8s.io + resources: + - leases + verbs: + - create +/-/-/-/ +# Source: nginx-ingress/templates/controller-rolebinding.yaml +kind: RoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: namespace-nginx-ingress + labels: + helm.sh/chart: nginx-ingress-2.1.0 + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: namespace + app.kubernetes.io/version: "4.1.0" + app.kubernetes.io/managed-by: Helm + namespace: nginx-ingress +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: namespace-nginx-ingress +subjects: +- kind: ServiceAccount + name: namespace-nginx-ingress + namespace: nginx-ingress +/-/-/-/ +# Source: nginx-ingress/templates/controller-service.yaml +apiVersion: v1 +kind: Service +metadata: + name: namespace-nginx-ingress-controller + namespace: nginx-ingress + labels: + helm.sh/chart: nginx-ingress-2.1.0 + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: namespace + app.kubernetes.io/version: "4.1.0" + app.kubernetes.io/managed-by: Helm +spec: + externalTrafficPolicy: Local + type: LoadBalancer + ports: + - port: 80 + targetPort: 80 + protocol: TCP + name: http + nodePort: + - port: 443 + targetPort: 443 + protocol: TCP + name: https + nodePort: + selector: + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: namespace +/-/-/-/ +# Source: nginx-ingress/templates/controller-deployment.yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: namespace-nginx-ingress-controller + namespace: nginx-ingress + labels: + helm.sh/chart: nginx-ingress-2.1.0 + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: namespace + app.kubernetes.io/version: "4.1.0" + app.kubernetes.io/managed-by: Helm +spec: + replicas: 1 + selector: + matchLabels: + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: namespace + template: + metadata: + labels: + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: namespace + annotations: + prometheus.io/scrape: "true" + prometheus.io/port: "9113" + prometheus.io/scheme: "http" + spec: + volumes: [] + serviceAccountName: namespace-nginx-ingress + automountServiceAccountToken: true + securityContext: + seccompProfile: + type: RuntimeDefault + terminationGracePeriodSeconds: 30 + hostNetwork: false + dnsPolicy: ClusterFirst + containers: + - image: nginx/nginx-ingress:4.1.0 + name: nginx-ingress + imagePullPolicy: "IfNotPresent" + ports: + - name: http + containerPort: 80 + protocol: TCP + - name: https + containerPort: 443 + protocol: TCP + - name: prometheus + containerPort: 9113 + - name: readiness-port + containerPort: 8081 + readinessProbe: + httpGet: + path: /nginx-ready + port: readiness-port + periodSeconds: 1 + initialDelaySeconds: 0 + resources: + requests: + cpu: 100m + memory: 128Mi + securityContext: + allowPrivilegeEscalation: false + readOnlyRootFilesystem: false + runAsUser: 101 #nginx + runAsNonRoot: true + capabilities: + drop: + - ALL + add: + - NET_BIND_SERVICE + volumeMounts: [] + env: + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + args: + - -nginx-plus=false + - -nginx-reload-timeout=60000 + - -enable-app-protect=false + - -enable-app-protect-dos=false + - -nginx-configmaps=$(POD_NAMESPACE)/namespace-nginx-ingress + - -ingress-class=nginx + - -health-status=false + - -health-status-uri=/nginx-health + - -nginx-debug=false + - -log-level=info + - -log-format=glog + - -nginx-status=true + - -nginx-status-port=8080 + - -nginx-status-allow-cidrs=127.0.0.1 + - -report-ingress-status + - -external-service=namespace-nginx-ingress-controller + - -enable-leader-election=true + - -leader-election-lock-name=namespace-nginx-ingress-leader-election + - -enable-prometheus-metrics=true + - -prometheus-metrics-listen-port=9113 + - -prometheus-tls-secret= + - -enable-service-insight=false + - -service-insight-listen-port=9114 + - -service-insight-tls-secret= + - -enable-custom-resources=true + - -enable-snippets=false + - -disable-ipv6=false + - -enable-tls-passthrough=false + - -enable-cert-manager=false + - -enable-oidc=false + - -enable-external-dns=false + - -default-http-listener-port=80 + - -default-https-listener-port=443 + - -ready-status=true + - -ready-status-port=8081 + - -enable-latency-metrics=false + - -ssl-dynamic-reload=true + - -enable-telemetry-reporting=true + - -weight-changes-dynamic-reload=false +/-/-/-/ +# Source: nginx-ingress/templates/controller-ingress-class.yaml +apiVersion: networking.k8s.io/v1 +kind: IngressClass +metadata: + name: nginx + labels: + helm.sh/chart: nginx-ingress-2.1.0 + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: namespace + app.kubernetes.io/version: "4.1.0" + app.kubernetes.io/managed-by: Helm +spec: + controller: nginx.org/ingress-controller +/-/-/-/ +# Source: nginx-ingress/templates/controller-configmap.yaml +/-/-/-/ +/-/-/-/ +# Source: nginx-ingress/templates/controller-lease.yaml +apiVersion: coordination.k8s.io/v1 +kind: Lease +metadata: + name: namespace-nginx-ingress-leader-election + namespace: nginx-ingress + labels: + helm.sh/chart: nginx-ingress-2.1.0 + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: namespace + app.kubernetes.io/version: "4.1.0" + app.kubernetes.io/managed-by: Helm +--- + +[TestHelmNICTemplate/plus - 1] +/-/-/-/ +# Source: nginx-ingress/templates/controller-serviceaccount.yaml +apiVersion: v1 +kind: ServiceAccount +metadata: + name: plus-nginx-ingress + namespace: default + labels: + helm.sh/chart: nginx-ingress-2.1.0 + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: plus + app.kubernetes.io/version: "4.1.0" + app.kubernetes.io/managed-by: Helm +/-/-/-/ +# Source: nginx-ingress/templates/controller-configmap.yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: plus-nginx-ingress + namespace: default + labels: + helm.sh/chart: nginx-ingress-2.1.0 + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: plus + app.kubernetes.io/version: "4.1.0" + app.kubernetes.io/managed-by: Helm +data: + {} +/-/-/-/ +# Source: nginx-ingress/templates/controller-configmap.yaml +/-/-/-/ +apiVersion: v1 +kind: ConfigMap +metadata: + name: plus-nginx-ingress-mgmt + namespace: default + labels: + helm.sh/chart: nginx-ingress-2.1.0 + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: plus + app.kubernetes.io/version: "4.1.0" + app.kubernetes.io/managed-by: Helm +data: + license-token-secret-name: license-token +/-/-/-/ +# Source: nginx-ingress/templates/controller-leader-election-configmap.yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: plus-nginx-ingress-leader-election + namespace: default + labels: + helm.sh/chart: nginx-ingress-2.1.0 + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: plus + app.kubernetes.io/version: "4.1.0" + app.kubernetes.io/managed-by: Helm +/-/-/-/ +# Source: nginx-ingress/templates/clusterrole.yaml +kind: ClusterRole +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: plus-nginx-ingress + labels: + helm.sh/chart: nginx-ingress-2.1.0 + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: plus + app.kubernetes.io/version: "4.1.0" + app.kubernetes.io/managed-by: Helm +rules: +- apiGroups: + - "" + resources: + - configmaps + - namespaces + - pods + - secrets + - services + verbs: + - get + - list + - watch +- apiGroups: + - "" + resources: + - events + verbs: + - create + - patch + - list +- apiGroups: + - coordination.k8s.io + resources: + - leases + verbs: + - list + - watch +- apiGroups: + - discovery.k8s.io + resources: + - endpointslices + verbs: + - get + - list + - watch +- apiGroups: + - networking.k8s.io + resources: + - ingresses + verbs: + - get + - list + - watch +- apiGroups: + - "" + resources: + - nodes + verbs: + - list +- apiGroups: + - "apps" + resources: + - replicasets + - daemonsets + verbs: + - get +- apiGroups: + - networking.k8s.io + resources: + - ingressclasses + verbs: + - get + - list +- apiGroups: + - networking.k8s.io + resources: + - ingresses/status + verbs: + - update +- apiGroups: + - k8s.nginx.org + resources: + - virtualservers + - virtualserverroutes + - globalconfigurations + - transportservers + - policies + verbs: + - list + - watch + - get +- apiGroups: + - k8s.nginx.org + resources: + - virtualservers/status + - virtualserverroutes/status + - policies/status + - transportservers/status + verbs: + - update +/-/-/-/ +# Source: nginx-ingress/templates/clusterrolebinding.yaml +kind: ClusterRoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: plus-nginx-ingress + labels: + helm.sh/chart: nginx-ingress-2.1.0 + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: plus + app.kubernetes.io/version: "4.1.0" + app.kubernetes.io/managed-by: Helm +subjects: +- kind: ServiceAccount + name: plus-nginx-ingress + namespace: default +roleRef: + kind: ClusterRole + name: plus-nginx-ingress + apiGroup: rbac.authorization.k8s.io +/-/-/-/ +# Source: nginx-ingress/templates/controller-role.yaml +kind: Role +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: plus-nginx-ingress + labels: + helm.sh/chart: nginx-ingress-2.1.0 + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: plus + app.kubernetes.io/version: "4.1.0" + app.kubernetes.io/managed-by: Helm + namespace: default +rules: +- apiGroups: + - "" + resources: + - configmaps + - pods + - secrets + - services + verbs: + - get + - list + - watch +- apiGroups: + - "" + resources: + - namespaces + verbs: + - get +- apiGroups: + - "" + resources: + - pods + verbs: + - update +- apiGroups: + - "" + resources: + - events + verbs: + - create + - patch + - list +- apiGroups: + - coordination.k8s.io + resources: + - leases + resourceNames: + - plus-nginx-ingress-leader-election + verbs: + - get + - update +- apiGroups: + - coordination.k8s.io + resources: + - leases + verbs: + - create +/-/-/-/ +# Source: nginx-ingress/templates/controller-rolebinding.yaml +kind: RoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: plus-nginx-ingress + labels: + helm.sh/chart: nginx-ingress-2.1.0 + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: plus + app.kubernetes.io/version: "4.1.0" + app.kubernetes.io/managed-by: Helm + namespace: default +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: plus-nginx-ingress +subjects: +- kind: ServiceAccount + name: plus-nginx-ingress + namespace: default +/-/-/-/ +# Source: nginx-ingress/templates/controller-service.yaml +apiVersion: v1 +kind: Service +metadata: + name: plus-nginx-ingress-controller + namespace: default + labels: + helm.sh/chart: nginx-ingress-2.1.0 + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: plus + app.kubernetes.io/version: "4.1.0" + app.kubernetes.io/managed-by: Helm +spec: + externalTrafficPolicy: Local + type: LoadBalancer + ports: + - port: 80 + targetPort: 80 + protocol: TCP + name: http + nodePort: + - port: 443 + targetPort: 443 + protocol: TCP + name: https + nodePort: + selector: + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: plus +/-/-/-/ +# Source: nginx-ingress/templates/controller-deployment.yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: plus-nginx-ingress-controller + namespace: default + labels: + helm.sh/chart: nginx-ingress-2.1.0 + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: plus + app.kubernetes.io/version: "4.1.0" + app.kubernetes.io/managed-by: Helm +spec: + replicas: 1 + selector: + matchLabels: + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: plus + template: + metadata: + labels: + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: plus + annotations: + prometheus.io/scrape: "true" + prometheus.io/port: "9113" + prometheus.io/scheme: "http" + spec: + volumes: [] + serviceAccountName: plus-nginx-ingress + automountServiceAccountToken: true + securityContext: + seccompProfile: + type: RuntimeDefault + terminationGracePeriodSeconds: 30 + hostNetwork: false + dnsPolicy: ClusterFirst + containers: + - image: nginx/nginx-ingress:4.1.0 + name: nginx-ingress + imagePullPolicy: "IfNotPresent" + ports: + - name: http + containerPort: 80 + protocol: TCP + - name: https + containerPort: 443 + protocol: TCP + - name: prometheus + containerPort: 9113 + - name: readiness-port + containerPort: 8081 + readinessProbe: + httpGet: + path: /nginx-ready + port: readiness-port + periodSeconds: 1 + initialDelaySeconds: 0 + resources: + requests: + cpu: 100m + memory: 128Mi + securityContext: + allowPrivilegeEscalation: false + readOnlyRootFilesystem: false + runAsUser: 101 #nginx + runAsNonRoot: true + capabilities: + drop: + - ALL + add: + - NET_BIND_SERVICE + volumeMounts: [] + env: + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + args: + - -nginx-plus=true + - -nginx-reload-timeout=60000 + - -enable-app-protect=false + - -enable-app-protect-dos=false + - -nginx-configmaps=$(POD_NAMESPACE)/plus-nginx-ingress + - -mgmt-configmap=$(POD_NAMESPACE)/plus-nginx-ingress-mgmt + - -ingress-class=nginx + - -health-status=false + - -health-status-uri=/nginx-health + - -nginx-debug=false + - -log-level=info + - -log-format=glog + - -nginx-status=true + - -nginx-status-port=8080 + - -nginx-status-allow-cidrs=127.0.0.1 + - -report-ingress-status + - -external-service=plus-nginx-ingress-controller + - -enable-leader-election=true + - -leader-election-lock-name=plus-nginx-ingress-leader-election + - -enable-prometheus-metrics=true + - -prometheus-metrics-listen-port=9113 + - -prometheus-tls-secret= + - -enable-service-insight=false + - -service-insight-listen-port=9114 + - -service-insight-tls-secret= + - -enable-custom-resources=true + - -enable-snippets=false + - -disable-ipv6=false + - -enable-tls-passthrough=false + - -enable-cert-manager=false + - -enable-oidc=false + - -enable-external-dns=false + - -default-http-listener-port=80 + - -default-https-listener-port=443 + - -ready-status=true + - -ready-status-port=8081 + - -enable-latency-metrics=false + - -ssl-dynamic-reload=true + - -enable-telemetry-reporting=true + - -weight-changes-dynamic-reload=false +/-/-/-/ +# Source: nginx-ingress/templates/controller-ingress-class.yaml +apiVersion: networking.k8s.io/v1 +kind: IngressClass +metadata: + name: nginx + labels: + helm.sh/chart: nginx-ingress-2.1.0 + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: plus + app.kubernetes.io/version: "4.1.0" + app.kubernetes.io/managed-by: Helm +spec: + controller: nginx.org/ingress-controller +/-/-/-/ +# Source: nginx-ingress/templates/controller-lease.yaml +apiVersion: coordination.k8s.io/v1 +kind: Lease +metadata: + name: plus-nginx-ingress-leader-election + namespace: default + labels: + helm.sh/chart: nginx-ingress-2.1.0 + app.kubernetes.io/name: nginx-ingress + app.kubernetes.io/instance: plus + app.kubernetes.io/version: "4.1.0" + app.kubernetes.io/managed-by: Helm +--- diff --git a/charts/tests/helmunit_test.go b/charts/tests/helmunit_test.go new file mode 100644 index 0000000000..0e9752fe6e --- /dev/null +++ b/charts/tests/helmunit_test.go @@ -0,0 +1,107 @@ +//go:build helmunit + +package test + +import ( + "os" + "path/filepath" + "testing" + + "github.com/gkampitakis/go-snaps/snaps" + "github.com/gruntwork-io/terratest/modules/helm" + "github.com/gruntwork-io/terratest/modules/k8s" +) + +func TestMain(m *testing.M) { + code := m.Run() + + // After all tests have run `go-snaps` will sort snapshots + snaps.Clean(m, snaps.CleanOpts{Sort: true}) + + os.Exit(code) +} + +// An example of how to verify the rendered template object of a Helm Chart given various inputs. +func TestHelmNICTemplate(t *testing.T) { + t.Parallel() + + tests := map[string]struct { + valuesFile string + releaseName string + namespace string + }{ + "default values file": { + valuesFile: "", + releaseName: "default", + namespace: "default", + }, + "daemonset": { + valuesFile: "testdata/daemonset.yaml", + releaseName: "daemonset", + namespace: "default", + }, + "namespace": { + valuesFile: "", + releaseName: "namespace", + namespace: "nginx-ingress", + }, + "plus": { + valuesFile: "testdata/plus.yaml", + releaseName: "plus", + namespace: "default", + }, + "ingressClass": { + valuesFile: "testdata/ingress-class.yaml", + releaseName: "ingress-class", + namespace: "default", + }, + "globalConfig": { + valuesFile: "testdata/global-configuration.yaml", + releaseName: "global-configuration", + namespace: "gc", + }, + "customResources": { + valuesFile: "testdata/custom-resources.yaml", + releaseName: "custom-resources", + namespace: "custom-resources", + }, + "appProtectWAF": { + valuesFile: "testdata/app-protect-waf.yaml", + releaseName: "appprotect-waf", + namespace: "appprotect-waf", + }, + "appProtectWAFV5": { + valuesFile: "testdata/app-protect-wafv5.yaml", + releaseName: "appprotect-wafv5", + namespace: "appprotect-wafv5", + }, + "appProtectDOS": { + valuesFile: "testdata/app-protect-dos.yaml", + releaseName: "appprotect-dos", + namespace: "appprotect-dos", + }, + } + + // Path to the helm chart we will test + helmChartPath, err := filepath.Abs("../nginx-ingress") + if err != nil { + t.Fatal("Failed to open helm chart path ../nginx-ingress") + } + + for testName, tc := range tests { + t.Run(testName, func(t *testing.T) { + options := &helm.Options{ + KubectlOptions: k8s.NewKubectlOptions("", "", tc.namespace), + } + + if tc.valuesFile != "" { + options.ValuesFiles = []string{tc.valuesFile} + } + + output := helm.RenderTemplate(t, options, helmChartPath, tc.releaseName, make([]string, 0)) + + snaps.MatchSnapshot(t, output) + t.Log(output) + }) + } +} diff --git a/charts/tests/testdata/app-protect-dos.yaml b/charts/tests/testdata/app-protect-dos.yaml new file mode 100644 index 0000000000..8bd1603e94 --- /dev/null +++ b/charts/tests/testdata/app-protect-dos.yaml @@ -0,0 +1,13 @@ +controller: + nginxplus: true + appprotectdos: + ## Enable the App Protect DoS module in the Ingress Controller. + enable: true + ## Enable debugging for App Protect DoS. + debug: true + ## Max number of nginx processes to support. + maxWorkers: 5 + ## Max number of ADMD instances. + maxDaemons: 10 + ## RAM memory size to consume in MB. + memory: 1024 diff --git a/charts/tests/testdata/app-protect-waf.yaml b/charts/tests/testdata/app-protect-waf.yaml new file mode 100644 index 0000000000..152559ad63 --- /dev/null +++ b/charts/tests/testdata/app-protect-waf.yaml @@ -0,0 +1,4 @@ +controller: + nginxplus: true + appprotect: + enable: true diff --git a/charts/tests/testdata/app-protect-wafv5.yaml b/charts/tests/testdata/app-protect-wafv5.yaml new file mode 100644 index 0000000000..7b2808bf2e --- /dev/null +++ b/charts/tests/testdata/app-protect-wafv5.yaml @@ -0,0 +1,21 @@ +controller: + nginxplus: true + appprotect: + enable: true + v5: true + volumes: + - name: app-protect-bd-config + emptyDir: {} + - name: app-protect-config + emptyDir: {} + - name: app-protect-bundles + emptyDir: {} + + enforcer: + host: "localhost" + port: 50001 + image: + repository: my.private.reg/nap/waf-enforcer + configManager: + image: + repository: my.private.reg/nap/waf-config-mgr diff --git a/charts/tests/testdata/custom-resources.yaml b/charts/tests/testdata/custom-resources.yaml new file mode 100644 index 0000000000..356385776b --- /dev/null +++ b/charts/tests/testdata/custom-resources.yaml @@ -0,0 +1,2 @@ +controller: + enableCustomResources: false diff --git a/charts/tests/testdata/daemonset.yaml b/charts/tests/testdata/daemonset.yaml new file mode 100644 index 0000000000..3e78846b9d --- /dev/null +++ b/charts/tests/testdata/daemonset.yaml @@ -0,0 +1,2 @@ +controller: + kind: daemonset diff --git a/charts/tests/testdata/global-configuration.yaml b/charts/tests/testdata/global-configuration.yaml new file mode 100644 index 0000000000..500724f480 --- /dev/null +++ b/charts/tests/testdata/global-configuration.yaml @@ -0,0 +1,11 @@ +controller: + globalConfiguration: + create: true + spec: + listeners: + - name: dns-udp + port: 5353 + protocol: UDP + - name: dns-tcp + port: 5353 + protocol: TCP diff --git a/charts/tests/testdata/ingress-class.yaml b/charts/tests/testdata/ingress-class.yaml new file mode 100644 index 0000000000..a60fb0fe50 --- /dev/null +++ b/charts/tests/testdata/ingress-class.yaml @@ -0,0 +1,5 @@ +controller: + ingressClass: + name: changed + create: true + setAsDefaultIngress: true diff --git a/charts/tests/testdata/plus.yaml b/charts/tests/testdata/plus.yaml new file mode 100644 index 0000000000..60f7dc92af --- /dev/null +++ b/charts/tests/testdata/plus.yaml @@ -0,0 +1,2 @@ +controller: + nginxplus: true diff --git a/cmd/nginx-ingress/flags.go b/cmd/nginx-ingress/flags.go index c4bf503e76..0804ac796c 100644 --- a/cmd/nginx-ingress/flags.go +++ b/cmd/nginx-ingress/flags.go @@ -1,17 +1,29 @@ package main import ( + "context" "flag" "fmt" "net" "os" "regexp" - "strconv" "strings" - "github.com/golang/glog" + internalValidation "github.com/nginxinc/kubernetes-ingress/internal/validation" api_v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/util/validation" + + nl "github.com/nginxinc/kubernetes-ingress/internal/logger" +) + +const ( + dynamicSSLReloadParam = "ssl-dynamic-reload" + dynamicWeightChangesParam = "weight-changes-dynamic-reload" + appProtectLogLevelDefault = "fatal" + appProtectEnforcerAddrDefault = "127.0.0.1:50000" + logLevelDefault = "info" + logFormatDefault = "glog" ) var ( @@ -27,20 +39,28 @@ var ( The Ingress Controller does not start NGINX and does not write any generated NGINX configuration files to disk`) watchNamespace = flag.String("watch-namespace", api_v1.NamespaceAll, - `Comma separated list of namespaces the Ingress Controller should watch for resources. By default the Ingress Controller watches all namespaces`) + `Comma separated list of namespaces the Ingress Controller should watch for resources. By default the Ingress Controller watches all namespaces. Mutually exclusive with "watch-namespace-label".`) watchNamespaces []string watchSecretNamespace = flag.String("watch-secret-namespace", "", - `Comma separated list of namespaces the Ingress Controller should watch for secrets. If this arg is not configured, the Ingress Controller watches the same namespaces for all resources. See "watch-namespace". `) + `Comma separated list of namespaces the Ingress Controller should watch for secrets. If this arg is not configured, the Ingress Controller watches the same namespaces for all resources. See "watch-namespace" and "watch-namespace-label". `) watchSecretNamespaces []string + watchNamespaceLabel = flag.String("watch-namespace-label", "", + `Configures the Ingress Controller to watch only those namespaces with label foo=bar. By default the Ingress Controller watches all namespaces. Mutually exclusive with "watch-namespace". `) + nginxConfigMaps = flag.String("nginx-configmaps", "", `A ConfigMap resource for customizing NGINX configuration. If a ConfigMap is set, but the Ingress Controller is not able to fetch it from Kubernetes API, the Ingress Controller will fail to start. Format: /`) + mgmtConfigMap = flag.String("mgmt-configmap", "", + `A ConfigMap resource for customizing NGINX configuration. If a ConfigMap is set, + but the Ingress Controller is not able to fetch it from Kubernetes API, the Ingress Controller will fail to start. + Format: /`) + nginxPlus = flag.Bool("nginx-plus", false, "Enable support for NGINX Plus") appProtect = flag.Bool("enable-app-protect", false, "Enable support for NGINX App Protect. Requires -nginx-plus.") @@ -56,6 +76,12 @@ var ( appProtectDosMaxWorkers = flag.Int("app-protect-dos-max-workers", 0, "Max number of nginx processes to support. Requires -nginx-plus and -enable-app-protect-dos.") appProtectDosMemory = flag.Int("app-protect-dos-memory", 0, "RAM memory size to consume in MB. Requires -nginx-plus and -enable-app-protect-dos.") + appProtectEnforcerAddress = flag.String("app-protect-enforcer-address", appProtectEnforcerAddrDefault, + `Sets address for App Protect v5 Enforcer. Requires -nginx-plus and -enable-app-protect.`) + + agent = flag.Bool("agent", false, "Enable NGINX Agent") + agentInstanceGroup = flag.String("agent-instance-group", "nginx-ingress-controller", "Grouping used to associate NGINX Ingress Controller instances") + ingressClass = flag.String("ingress-class", "nginx", `A class of the Ingress Controller. @@ -135,12 +161,18 @@ var ( prometheusMetricsListenPort = flag.Int("prometheus-metrics-listen-port", 9113, "Set the port where the Prometheus metrics are exposed. [1024 - 65535]") + enableServiceInsight = flag.Bool("enable-service-insight", false, + `Enable service insight for external load balancers. Requires -nginx-plus`) + + serviceInsightTLSSecretName = flag.String("service-insight-tls-secret", "", + `A Secret with a TLS certificate and key for TLS termination of the service insight.`) + + serviceInsightListenPort = flag.Int("service-insight-listen-port", 9114, + "Set the port where the Service Insight stats are exposed. Requires -nginx-plus. [1024 - 65535]") + enableCustomResources = flag.Bool("enable-custom-resources", true, "Enable custom resources") - enablePreviewPolicies = flag.Bool("enable-preview-policies", false, - "Enable preview policies. This flag is deprecated. To enable OIDC Policies please use -enable-oidc instead.") - enableOIDC = flag.Bool("enable-oidc", false, "Enable OIDC Policies.") @@ -151,7 +183,9 @@ var ( `The namespace/name of the GlobalConfiguration resource for global configuration of the Ingress Controller. Requires -enable-custom-resources. Format: /`) enableTLSPassthrough = flag.Bool("enable-tls-passthrough", false, - "Enable TLS Passthrough on port 443. Requires -enable-custom-resources") + "Enable TLS Passthrough on default port 443. Requires -enable-custom-resources") + + tlsPassthroughPort = flag.Int("tls-passthrough-port", 443, "Set custom port for TLS Passthrough. [1024 - 65535]") spireAgentAddress = flag.String("spire-agent-address", "", `Specifies the address of the running Spire agent. Requires -nginx-plus and is for use with NGINX Service Mesh only. If the flag is set, @@ -173,12 +207,24 @@ var ( enableExternalDNS = flag.Bool("enable-external-dns", false, "Enable external-dns controller for VirtualServer resources. Requires -enable-custom-resources") - includeYearInLogs = flag.Bool("include-year", false, - "Option to include the year in the log header") - disableIPV6 = flag.Bool("disable-ipv6", false, `Disable IPV6 listeners explicitly for nodes that do not support the IPV6 stack`) + defaultHTTPListenerPort = flag.Int("default-http-listener-port", 80, "Sets a custom port for the HTTP NGINX `default_server`. [1024 - 65535]") + + defaultHTTPSListenerPort = flag.Int("default-https-listener-port", 443, "Sets a custom port for the HTTPS `default_server`. [1024 - 65535]") + + enableDynamicSSLReload = flag.Bool(dynamicSSLReloadParam, true, "Enable reloading of SSL Certificates without restarting the NGINX process.") + + enableTelemetryReporting = flag.Bool("enable-telemetry-reporting", true, "Enable gathering and reporting of product related telemetry.") + + logFormat = flag.String("log-format", logFormatDefault, "Set log format to either glog, text, or json.") + + logLevel = flag.String("log-level", logLevelDefault, + `Sets log level for Ingress Controller. Allowed values: fatal, error, warning, info, debug, trace.`) + + enableDynamicWeightChangesReload = flag.Bool(dynamicWeightChangesParam, false, "Enable changing weights of split clients without reloading NGINX. Requires -nginx-plus") + startupCheckFn func() error ) @@ -186,163 +232,207 @@ var ( func parseFlags() { flag.Parse() - if *versionFlag { + if *versionFlag { // printed in main os.Exit(0) } +} - initialChecks() +func initValidate(ctx context.Context) { + l := nl.LoggerFromContext(ctx) + logFormatValidationError := validateLogFormat(*logFormat) + if logFormatValidationError != nil { + nl.Warnf(l, "Invalid log format: %s. Valid options are: glog, text, json. Falling back to default: %s", *logFormat, logFormatDefault) + } - watchNamespaces = strings.Split(*watchNamespace, ",") - glog.Infof("Namespaces watched: %v", watchNamespaces) + logLevelValidationError := validateLogLevel(*logLevel) + if logLevelValidationError != nil { + nl.Warnf(l, "Invalid log level: %s. Valid options are: trace, debug, info, warning, error, fatal. Falling back to default: %s", *logLevel, logLevelDefault) + } - if len(*watchSecretNamespace) > 0 { - watchSecretNamespaces = strings.Split(*watchSecretNamespace, ",") - } else { - // empty => default to watched namespaces - watchSecretNamespaces = watchNamespaces + if *enableLatencyMetrics && !*enablePrometheusMetrics { + nl.Warn(l, "enable-latency-metrics flag requires enable-prometheus-metrics, latency metrics will not be collected") + *enableLatencyMetrics = false } - glog.Infof("Namespaces watched for secrets: %v", watchSecretNamespaces) + if *enableServiceInsight && !*nginxPlus { + nl.Warn(l, "enable-service-insight flag support is for NGINX Plus, service insight endpoint will not be exposed") + *enableServiceInsight = false + } - validationChecks() + if *enableDynamicWeightChangesReload && !*nginxPlus { + nl.Warn(l, "weight-changes-dynamic-reload flag support is for NGINX Plus, Dynamic Weight Changes will not be enabled") + *enableDynamicWeightChangesReload = false + } - if *enableTLSPassthrough && !*enableCustomResources { - glog.Fatal("enable-tls-passthrough flag requires -enable-custom-resources") + if *mgmtConfigMap != "" && !*nginxPlus { + nl.Warn(l, "mgmt-configmap flag requires -nginx-plus, mgmt configmap will not be used") + *mgmtConfigMap = "" } - if *enablePreviewPolicies { - glog.Warning("enable-preview-policies is universally deprecated. To enable OIDC Policies please use -enable-oidc instead.") + mustValidateInitialChecks(ctx) + mustValidateWatchedNamespaces(ctx) + mustValidateFlags(ctx) +} + +func mustValidateInitialChecks(ctx context.Context) { + l := nl.LoggerFromContext(ctx) + + if startupCheckFn != nil { + err := startupCheckFn() + if err != nil { + nl.Fatalf(l, "Failed startup check: %v", err) + } + l.Info("AWS startup check passed") } - *enableOIDC = *enablePreviewPolicies || *enableOIDC - if *appProtect && !*nginxPlus { - glog.Fatal("NGINX App Protect support is for NGINX Plus only") + l.Info(fmt.Sprintf("Starting with flags: %+q", os.Args[1:])) + + unparsed := flag.Args() + if len(unparsed) > 0 { + nl.Warnf(l, "Ignoring unhandled arguments: %+q", unparsed) } +} - if *appProtectLogLevel != appProtectLogLevelDefault && !*appProtect && !*nginxPlus { - glog.Fatal("app-protect-log-level support is for NGINX Plus only and App Protect is enable") +// mustValidateWatchedNamespaces calls internally os.Exit if it can't validate namespaces. +func mustValidateWatchedNamespaces(ctx context.Context) { + l := nl.LoggerFromContext(ctx) + if *watchNamespace != "" && *watchNamespaceLabel != "" { + nl.Fatal(l, "watch-namespace and -watch-namespace-label are mutually exclusive") } - if *appProtectDos && !*nginxPlus { - glog.Fatal("NGINX App Protect Dos support is for NGINX Plus only") + watchNamespaces = strings.Split(*watchNamespace, ",") + + if *watchNamespace != "" { + l.Info(fmt.Sprintf("Namespaces watched: %v", watchNamespaces)) + namespacesNameValidationError := validateNamespaceNames(watchNamespaces) + if namespacesNameValidationError != nil { + nl.Fatalf(l, "Invalid values for namespaces: %v", namespacesNameValidationError) + } } - if *appProtectDosDebug && !*appProtectDos && !*nginxPlus { - glog.Fatal("NGINX App Protect Dos debug support is for NGINX Plus only and App Protect Dos is enable") + if len(*watchSecretNamespace) > 0 { + watchSecretNamespaces = strings.Split(*watchSecretNamespace, ",") + l.Debug(fmt.Sprintf("Namespaces watched for secrets: %v", watchSecretNamespaces)) + namespacesNameValidationError := validateNamespaceNames(watchSecretNamespaces) + if namespacesNameValidationError != nil { + nl.Fatalf(l, "Invalid values for secret namespaces: %v", namespacesNameValidationError) + } + } else { + // empty => default to watched namespaces + watchSecretNamespaces = watchNamespaces } - if *appProtectDosMaxDaemons != 0 && !*appProtectDos && !*nginxPlus { - glog.Fatal("NGINX App Protect Dos max daemons support is for NGINX Plus only and App Protect Dos is enable") + if *watchNamespaceLabel != "" { + var err error + _, err = labels.Parse(*watchNamespaceLabel) + if err != nil { + nl.Fatalf(l, "Unable to parse label %v for watch namespace label: %v", *watchNamespaceLabel, err) + } } +} - if *appProtectDosMaxWorkers != 0 && !*appProtectDos && !*nginxPlus { - glog.Fatal("NGINX App Protect Dos max workers support is for NGINX Plus and App Protect Dos is enable") +// mustValidateFlags checks the values for various flags +// and calls os.Exit if any of the flags is invalid. +// nolint:gocyclo +func mustValidateFlags(ctx context.Context) { + l := nl.LoggerFromContext(ctx) + healthStatusURIValidationError := validateLocation(*healthStatusURI) + if healthStatusURIValidationError != nil { + nl.Fatalf(l, "Invalid value for health-status-uri: %v", healthStatusURIValidationError) } - if *appProtectDosMemory != 0 && !*appProtectDos && !*nginxPlus { - glog.Fatal("NGINX App Protect Dos memory support is for NGINX Plus and App Protect Dos is enable") + statusLockNameValidationError := validateResourceName(*leaderElectionLockName) + if statusLockNameValidationError != nil { + nl.Fatalf(l, "Invalid value for leader-election-lock-name: %v", statusLockNameValidationError) } - if *spireAgentAddress != "" && !*nginxPlus { - glog.Fatal("spire-agent-address support is for NGINX Plus only") + statusPortValidationError := internalValidation.ValidateUnprivilegedPort(*nginxStatusPort) + if statusPortValidationError != nil { + nl.Fatalf(l, "Invalid value for nginx-status-port: %v", statusPortValidationError) } - if *enableInternalRoutes && *spireAgentAddress == "" { - glog.Fatal("enable-internal-routes flag requires spire-agent-address") + metricsPortValidationError := internalValidation.ValidateUnprivilegedPort(*prometheusMetricsListenPort) + if metricsPortValidationError != nil { + nl.Fatalf(l, "Invalid value for prometheus-metrics-listen-port: %v", metricsPortValidationError) } - if *enableLatencyMetrics && !*enablePrometheusMetrics { - glog.Warning("enable-latency-metrics flag requires enable-prometheus-metrics, latency metrics will not be collected") - *enableLatencyMetrics = false + readyStatusPortValidationError := internalValidation.ValidateUnprivilegedPort(*readyStatusPort) + if readyStatusPortValidationError != nil { + nl.Fatalf(l, "Invalid value for ready-status-port: %v", readyStatusPortValidationError) } - if *enableCertManager && !*enableCustomResources { - glog.Fatal("enable-cert-manager flag requires -enable-custom-resources") + healthProbePortValidationError := internalValidation.ValidateUnprivilegedPort(*serviceInsightListenPort) + if healthProbePortValidationError != nil { + nl.Fatalf(l, "Invalid value for service-insight-listen-port: %v", metricsPortValidationError) } - if *enableExternalDNS && !*enableCustomResources { - glog.Fatal("enable-external-dns flag requires -enable-custom-resources") + var err error + allowedCIDRs, err = parseNginxStatusAllowCIDRs(*nginxStatusAllowCIDRs) + if err != nil { + nl.Fatalf(l, "Invalid value for nginx-status-allow-cidrs: %v", err) } - if *ingressLink != "" && *externalService != "" { - glog.Fatal("ingresslink and external-service cannot both be set") + if *appProtectLogLevel != appProtectLogLevelDefault && *appProtect && *nginxPlus { + appProtectlogLevelValidationError := validateLogLevel(*appProtectLogLevel) + if appProtectlogLevelValidationError != nil { + nl.Fatalf(l, "Invalid value for app-protect-log-level: %v", *appProtectLogLevel) + } } -} -func initialChecks() { - err := flag.Lookup("logtostderr").Value.Set("true") - if err != nil { - glog.Fatalf("Error setting logtostderr to true: %v", err) + if *enableTLSPassthrough && !*enableCustomResources { + nl.Fatal(l, "enable-tls-passthrough flag requires -enable-custom-resources") } - err = flag.Lookup("include_year").Value.Set(strconv.FormatBool(*includeYearInLogs)) - if err != nil { - glog.Fatalf("Error setting include_year flag: %v", err) + if *appProtect && !*nginxPlus { + nl.Fatal(l, "NGINX App Protect support is for NGINX Plus only") } - if startupCheckFn != nil { - err := startupCheckFn() - if err != nil { - glog.Fatalf("Failed startup check: %v", err) - } + if *appProtectLogLevel != appProtectLogLevelDefault && !*appProtect && !*nginxPlus { + nl.Fatal(l, "app-protect-log-level support is for NGINX Plus only and App Protect is enable") } - glog.Infof("Starting with flags: %+q", os.Args[1:]) + if *appProtectDos && !*nginxPlus { + nl.Fatal(l, "NGINX App Protect Dos support is for NGINX Plus only") + } - unparsed := flag.Args() - if len(unparsed) > 0 { - glog.Warningf("Ignoring unhandled arguments: %+q", unparsed) + if *appProtectDosDebug && !*appProtectDos && !*nginxPlus { + nl.Fatal(l, "NGINX App Protect Dos debug support is for NGINX Plus only and App Protect Dos is enable") } -} -// validationChecks checks the values for various flags -func validationChecks() { - healthStatusURIValidationError := validateLocation(*healthStatusURI) - if healthStatusURIValidationError != nil { - glog.Fatalf("Invalid value for health-status-uri: %v", healthStatusURIValidationError) + if *appProtectDosMaxDaemons != 0 && !*appProtectDos && !*nginxPlus { + nl.Fatal(l, "NGINX App Protect Dos max daemons support is for NGINX Plus only and App Protect Dos is enable") } - statusLockNameValidationError := validateResourceName(*leaderElectionLockName) - if statusLockNameValidationError != nil { - glog.Fatalf("Invalid value for leader-election-lock-name: %v", statusLockNameValidationError) + if *appProtectDosMaxWorkers != 0 && !*appProtectDos && !*nginxPlus { + nl.Fatal(l, "NGINX App Protect Dos max workers support is for NGINX Plus and App Protect Dos is enable") } - namespacesNameValidationError := validateNamespaceNames(watchNamespaces) - if namespacesNameValidationError != nil { - glog.Fatalf("Invalid values for namespaces: %v", namespacesNameValidationError) + if *appProtectDosMemory != 0 && !*appProtectDos && !*nginxPlus { + nl.Fatal(l, "NGINX App Protect Dos memory support is for NGINX Plus and App Protect Dos is enable") } - namespacesNameValidationError = validateNamespaceNames(watchSecretNamespaces) - if namespacesNameValidationError != nil { - glog.Fatalf("Invalid values for secret namespaces: %v", namespacesNameValidationError) + if *enableInternalRoutes && *spireAgentAddress == "" { + nl.Fatal(l, "enable-internal-routes flag requires spire-agent-address") } - statusPortValidationError := validatePort(*nginxStatusPort) - if statusPortValidationError != nil { - glog.Fatalf("Invalid value for nginx-status-port: %v", statusPortValidationError) + if *enableCertManager && !*enableCustomResources { + nl.Fatal(l, "enable-cert-manager flag requires -enable-custom-resources") } - metricsPortValidationError := validatePort(*prometheusMetricsListenPort) - if metricsPortValidationError != nil { - glog.Fatalf("Invalid value for prometheus-metrics-listen-port: %v", metricsPortValidationError) + if *enableExternalDNS && !*enableCustomResources { + nl.Fatal(l, "enable-external-dns flag requires -enable-custom-resources") } - readyStatusPortValidationError := validatePort(*readyStatusPort) - if readyStatusPortValidationError != nil { - glog.Fatalf("Invalid value for ready-status-port: %v", readyStatusPortValidationError) + if *ingressLink != "" && *externalService != "" { + nl.Fatal(l, "ingresslink and external-service cannot both be set") } - var err error - allowedCIDRs, err = parseNginxStatusAllowCIDRs(*nginxStatusAllowCIDRs) - if err != nil { - glog.Fatalf(`Invalid value for nginx-status-allow-cidrs: %v`, err) + if *agent && !*appProtect { + nl.Fatal(l, "NGINX Agent is used to enable the Security Monitoring dashboard and requires NGINX App Protect to be enabled") } - if *appProtectLogLevel != appProtectLogLevelDefault && *appProtect && *nginxPlus { - logLevelValidationError := validateAppProtectLogLevel(*appProtectLogLevel) - if logLevelValidationError != nil { - glog.Fatalf("Invalid value for app-protect-log-level: %v", *appProtectLogLevel) - } + if *nginxPlus && *mgmtConfigMap == "" { + nl.Fatal(l, "NGINX Plus requires a mgmt ConfigMap to be set") } } @@ -375,18 +465,8 @@ func validateResourceName(name string) error { return nil } -// validatePort makes sure a given port is inside the valid port range for its usage -func validatePort(port int) error { - if port < 1024 || port > 65535 { - return fmt.Errorf("port outside of valid port range [1024 - 65535]: %v", port) - } - return nil -} - -const appProtectLogLevelDefault = "fatal" - -// validateAppProtectLogLevel makes sure a given logLevel is one of the allowed values -func validateAppProtectLogLevel(logLevel string) error { +// validateLogLevel makes sure a given logLevel is one of the allowed values +func validateLogLevel(logLevel string) error { switch strings.ToLower(logLevel) { case "fatal", @@ -397,7 +477,16 @@ func validateAppProtectLogLevel(logLevel string) error { "trace": return nil } - return fmt.Errorf("invalid App Protect log level: %v", logLevel) + return fmt.Errorf("invalid log level: %v", logLevel) +} + +// validateLogFormat makes sure a given logFormat is one of the allowed values +func validateLogFormat(logFormat string) error { + switch strings.ToLower(logFormat) { + case "glog", "json", "text": + return nil + } + return fmt.Errorf("invalid log format: %v", logFormat) } // parseNginxStatusAllowCIDRs converts a comma separated CIDR/IP address string into an array of CIDR/IP addresses. diff --git a/cmd/nginx-ingress/flags_test.go b/cmd/nginx-ingress/flags_test.go new file mode 100644 index 0000000000..6021cea8b0 --- /dev/null +++ b/cmd/nginx-ingress/flags_test.go @@ -0,0 +1,183 @@ +package main + +import ( + "errors" + "reflect" + "strings" + "testing" +) + +func TestParseNginxStatusAllowCIDRs(t *testing.T) { + badCIDRs := []struct { + input string + expectedError error + }{ + { + "earth, ,,furball", + errors.New("invalid IP address: earth"), + }, + { + "127.0.0.1,10.0.1.0/24, ,,furball", + errors.New("invalid CIDR address: an empty string is an invalid CIDR block or IP address"), + }, + { + "false", + errors.New("invalid IP address: false"), + }, + } + for _, badCIDR := range badCIDRs { + _, err := parseNginxStatusAllowCIDRs(badCIDR.input) + if err == nil { + t.Errorf("parseNginxStatusAllowCIDRs(%q) returned no error when it should have returned error %q", badCIDR.input, badCIDR.expectedError) + } else if err.Error() != badCIDR.expectedError.Error() { + t.Errorf("parseNginxStatusAllowCIDRs(%q) returned error %q when it should have returned error %q", badCIDR.input, err, badCIDR.expectedError) + } + } + + goodCIDRs := []struct { + input string + expected []string + }{ + { + "127.0.0.1", + []string{"127.0.0.1"}, + }, + { + "10.0.1.0/24", + []string{"10.0.1.0/24"}, + }, + { + "127.0.0.1,10.0.1.0/24,68.121.233.214 , 24.24.24.24/32", + []string{"127.0.0.1", "10.0.1.0/24", "68.121.233.214", "24.24.24.24/32"}, + }, + } + for _, goodCIDR := range goodCIDRs { + result, err := parseNginxStatusAllowCIDRs(goodCIDR.input) + if err != nil { + t.Errorf("parseNginxStatusAllowCIDRs(%q) returned an error when it should have returned no error: %q", goodCIDR.input, err) + } + + if !reflect.DeepEqual(result, goodCIDR.expected) { + t.Errorf("parseNginxStatusAllowCIDRs(%q) returned %v expected %v: ", goodCIDR.input, result, goodCIDR.expected) + } + } +} + +func TestValidateCIDRorIP(t *testing.T) { + badCIDRs := []string{"localhost", "thing", "~", "!!!", "", " ", "-1"} + for _, badCIDR := range badCIDRs { + err := validateCIDRorIP(badCIDR) + if err == nil { + t.Errorf(`Expected error for invalid CIDR "%v"\n`, badCIDR) + } + } + + goodCIDRs := []string{"0.0.0.0/32", "0.0.0.0/0", "127.0.0.1/32", "127.0.0.0/24", "23.232.65.42"} + for _, goodCIDR := range goodCIDRs { + err := validateCIDRorIP(goodCIDR) + if err != nil { + t.Errorf("Error for valid CIDR: %v err: %v\n", goodCIDR, err) + } + } +} + +func TestValidateLocation(t *testing.T) { + badLocations := []string{ + "", + "/", + " /test", + "/bad;", + } + for _, badLocation := range badLocations { + err := validateLocation(badLocation) + if err == nil { + t.Errorf("validateLocation(%v) returned no error when it should have returned an error", badLocation) + } + } + + goodLocations := []string{ + "/test", + "/test/subtest", + } + for _, goodLocation := range goodLocations { + err := validateLocation(goodLocation) + if err != nil { + t.Errorf("validateLocation(%v) returned an error when it should have returned no error: %v", goodLocation, err) + } + } +} + +func TestValidateLogLevel(t *testing.T) { + badLogLevels := []string{ + "", + "critical", + "none", + "info;", + } + for _, badLogLevel := range badLogLevels { + err := validateLogLevel(badLogLevel) + if err == nil { + t.Errorf("validateLogLevel(%v) returned no error when it should have returned an error", badLogLevel) + } + } + + goodLogLevels := []string{ + "fatal", + "Error", + "WARN", + "info", + "debug", + "trace", + } + for _, goodLogLevel := range goodLogLevels { + err := validateLogLevel(goodLogLevel) + if err != nil { + t.Errorf("validateLogLevel(%v) returned an error when it should have returned no error: %v", goodLogLevel, err) + } + } +} + +func TestValidateNamespaces(t *testing.T) { + badNamespaces := []string{"watchns1, watchns2, watchns%$", "watchns1,watchns2,watchns%$"} + for _, badNs := range badNamespaces { + err := validateNamespaceNames(strings.Split(badNs, ",")) + if err == nil { + t.Errorf("Expected error for invalid namespace %v\n", badNs) + } + } + + goodNamespaces := []string{"watched-namespace", "watched-namespace,", "watched-namespace1,watched-namespace2", "watched-namespace1, watched-namespace2"} + for _, goodNs := range goodNamespaces { + err := validateNamespaceNames(strings.Split(goodNs, ",")) + if err != nil { + t.Errorf("Error for valid namespace: %v err: %v\n", goodNs, err) + } + } +} + +func TestValidateLogFormat(t *testing.T) { + badLogFormats := []string{ + "", + "jason", + "txt", + "gloog", + } + for _, badLogFormat := range badLogFormats { + err := validateLogFormat(badLogFormat) + if err == nil { + t.Errorf("validateLogFormat(%v) returned no error when it should have returned an error", badLogFormat) + } + } + + goodLogFormats := []string{ + "json", + "text", + "glog", + } + for _, goodLogFormat := range goodLogFormats { + err := validateLogFormat(goodLogFormat) + if err != nil { + t.Errorf("validateLogFormat(%v) returned an error when it should have returned no error: %v", goodLogFormat, err) + } + } +} diff --git a/cmd/nginx-ingress/main.go b/cmd/nginx-ingress/main.go index 68ed5345ae..850920eee5 100644 --- a/cmd/nginx-ingress/main.go +++ b/cmd/nginx-ingress/main.go @@ -1,89 +1,195 @@ package main import ( + "bytes" "context" "fmt" + "io" + "log/slog" "net" "net/http" "os" "os/signal" + "regexp" "runtime" "strings" "syscall" "time" - "github.com/golang/glog" "github.com/nginxinc/kubernetes-ingress/internal/configs" "github.com/nginxinc/kubernetes-ingress/internal/configs/version1" "github.com/nginxinc/kubernetes-ingress/internal/configs/version2" + "github.com/nginxinc/kubernetes-ingress/internal/healthcheck" "github.com/nginxinc/kubernetes-ingress/internal/k8s" "github.com/nginxinc/kubernetes-ingress/internal/k8s/secrets" + license_reporting "github.com/nginxinc/kubernetes-ingress/internal/license_reporting" "github.com/nginxinc/kubernetes-ingress/internal/metrics" "github.com/nginxinc/kubernetes-ingress/internal/metrics/collectors" "github.com/nginxinc/kubernetes-ingress/internal/nginx" cr_validation "github.com/nginxinc/kubernetes-ingress/pkg/apis/configuration/validation" k8s_nginx "github.com/nginxinc/kubernetes-ingress/pkg/client/clientset/versioned" conf_scheme "github.com/nginxinc/kubernetes-ingress/pkg/client/clientset/versioned/scheme" - "github.com/nginxinc/nginx-plus-go-client/client" + "github.com/nginxinc/nginx-plus-go-client/v2/client" nginxCollector "github.com/nginxinc/nginx-prometheus-exporter/collector" "github.com/prometheus/client_golang/prometheus" api_v1 "k8s.io/api/core/v1" meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + pkg_runtime "k8s.io/apimachinery/pkg/runtime" util_version "k8s.io/apimachinery/pkg/util/version" "k8s.io/client-go/dynamic" "k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes/scheme" + core_v1 "k8s.io/client-go/kubernetes/typed/core/v1" "k8s.io/client-go/rest" "k8s.io/client-go/tools/clientcmd" clientcmdapi "k8s.io/client-go/tools/clientcmd/api" + "k8s.io/client-go/tools/record" + + nl "github.com/nginxinc/kubernetes-ingress/internal/logger" + nic_glog "github.com/nginxinc/kubernetes-ingress/internal/logger/glog" + "github.com/nginxinc/kubernetes-ingress/internal/logger/levels" ) // Injected during build -var version string +var ( + version string + telemetryEndpoint string + logLevels = map[string]slog.Level{ + "trace": levels.LevelTrace, + "debug": levels.LevelDebug, + "info": levels.LevelInfo, + "warning": levels.LevelWarning, + "error": levels.LevelError, + "fatal": levels.LevelFatal, + } +) + +const ( + nginxVersionLabel = "app.nginx.org/version" + versionLabel = "app.kubernetes.io/version" + appProtectVersionLabel = "appprotect.f5.com/version" + agentVersionLabel = "app.nginx.org/agent-version" + appProtectVersionPath = "/opt/app_protect/RELEASE" + appProtectv4BundleFolder = "/etc/nginx/waf/bundles/" + appProtectv5BundleFolder = "/etc/app_protect/bundles/" + fatalEventFlushTime = 200 * time.Millisecond + secretErrorReason = "SecretError" + configMapErrorReason = "ConfigMapError" +) func main() { commitHash, commitTime, dirtyBuild := getBuildInfo() fmt.Printf("NGINX Ingress Controller Version=%v Commit=%v Date=%v DirtyState=%v Arch=%v/%v Go=%v\n", version, commitHash, commitTime, dirtyBuild, runtime.GOOS, runtime.GOARCH, runtime.Version()) - parseFlags() + ctx := initLogger(*logFormat, logLevels[*logLevel], os.Stdout) + l := nl.LoggerFromContext(ctx) - config, kubeClient := createConfigAndKubeClient() + initValidate(ctx) + parsedFlags := os.Args[1:] - kubernetesVersionInfo(kubeClient) + buildOS := os.Getenv("BUILD_OS") + controllerNamespace := os.Getenv("POD_NAMESPACE") + podName := os.Getenv("POD_NAME") - validateIngressClass(kubeClient) + config, kubeClient := mustCreateConfigAndKubeClient(ctx) + if err := validateKubernetesVersionInfo(ctx, kubeClient); err != nil { + nl.Fatal(l, err) + } + pod, err := kubeClient.CoreV1().Pods(controllerNamespace).Get(context.TODO(), podName, meta_v1.GetOptions{}) + if err != nil { + nl.Fatalf(l, "Failed to get pod: %v", err) + } + eventBroadcaster := record.NewBroadcaster() + eventBroadcaster.StartLogging(func(format string, args ...interface{}) { + nl.Infof(l, format, args...) + }) + eventBroadcaster.StartRecordingToSink(&core_v1.EventSinkImpl{ + Interface: core_v1.New(kubeClient.CoreV1().RESTClient()).Events(""), + }) + eventRecorder := eventBroadcaster.NewRecorder(scheme.Scheme, + api_v1.EventSource{Component: "nginx-ingress-controller"}) + defer eventBroadcaster.Shutdown() + mustValidateIngressClass(ctx, kubeClient) + + checkNamespaces(ctx, kubeClient) + + dynClient, confClient := createCustomClients(ctx, config) - checkNamespaceExists(kubeClient, watchNamespaces) + constLabels := map[string]string{"class": *ingressClass} - checkNamespaceExists(kubeClient, watchSecretNamespaces) + managerCollector, controllerCollector, registry := createManagerAndControllerCollectors(ctx, constLabels) - dynClient, confClient := createCustomClients(config) + var licenseReporter *license_reporting.LicenseReporter - constLabels := map[string]string{"class": *ingressClass} + if *nginxPlus { + licenseReporter = license_reporting.NewLicenseReporter(kubeClient, eventRecorder, pod) + } + + nginxManager, useFakeNginxManager := createNginxManager(ctx, managerCollector, licenseReporter) + + nginxVersion := getNginxVersionInfo(ctx, nginxManager) + + var appProtectVersion string + var appProtectV5 bool + appProtectBundlePath := appProtectv4BundleFolder + if *appProtect { + appProtectVersion = getAppProtectVersionInfo(ctx) + + r := regexp.MustCompile("^5.*") + if r.MatchString(appProtectVersion) { + appProtectV5 = true + appProtectBundlePath = appProtectv5BundleFolder + } + } - managerCollector, controllerCollector, registry := createManagerAndControllerCollectors(constLabels) + var agentVersion string + if *agent { + agentVersion = getAgentVersionInfo(nginxManager) + } - nginxManager, useFakeNginxManager := createNginxManager(managerCollector) + go updateSelfWithVersionInfo(ctx, eventRecorder, kubeClient, version, appProtectVersion, agentVersion, nginxVersion, 10, time.Second*5) - getNginxVersionInfo(nginxManager) + var mgmtCfgParams *configs.MGMTConfigParams + if *nginxPlus { + mgmtCfgParams = processMGMTConfigMap(kubeClient, configs.NewDefaultMGMTConfigParams(ctx), eventRecorder, pod) + if err := processLicenseSecret(kubeClient, nginxManager, mgmtCfgParams, controllerNamespace); err != nil { + logEventAndExit(ctx, eventRecorder, pod, secretErrorReason, err) + } - templateExecutor, templateExecutorV2 := createTemplateExecutors() + if err := processTrustedCertSecret(kubeClient, nginxManager, mgmtCfgParams, controllerNamespace); err != nil { + logEventAndExit(ctx, eventRecorder, pod, secretErrorReason, err) + } - aPPluginDone, aPAgentDone, aPPDosAgentDone := startApAgentsAndPlugins(nginxManager) + if err := processClientAuthSecret(kubeClient, nginxManager, mgmtCfgParams, controllerNamespace); err != nil { + logEventAndExit(ctx, eventRecorder, pod, secretErrorReason, err) + } - sslRejectHandshake := processDefaultServerSecret(kubeClient, nginxManager) + } - isWildcardEnabled := processWildcardSecret(kubeClient, nginxManager) + templateExecutor, templateExecutorV2 := createTemplateExecutors(ctx) + sslRejectHandshake, err := processDefaultServerSecret(kubeClient, nginxManager) + if err != nil { + logEventAndExit(ctx, eventRecorder, pod, secretErrorReason, err) + } + + staticSSLPath := nginxManager.GetSecretsDir() + + isWildcardEnabled, err := processWildcardSecret(kubeClient, nginxManager) + if err != nil { + logEventAndExit(ctx, eventRecorder, pod, secretErrorReason, err) + } globalConfigurationValidator := createGlobalConfigurationValidator() - processGlobalConfiguration() + mustProcessGlobalConfiguration(ctx) - cfgParams := configs.NewDefaultConfigParams(*nginxPlus) - cfgParams = processConfigMaps(kubeClient, cfgParams, nginxManager, templateExecutor) + cfgParams := configs.NewDefaultConfigParams(ctx, *nginxPlus) + cfgParams = processConfigMaps(kubeClient, cfgParams, nginxManager, templateExecutor, eventRecorder) staticCfgParams := &configs.StaticConfigParams{ DisableIPV6: *disableIPV6, + DefaultHTTPListenerPort: *defaultHTTPListenerPort, + DefaultHTTPSListenerPort: *defaultHTTPSListenerPort, HealthStatus: *healthStatus, HealthStatusURI: *healthStatusURI, NginxStatus: *nginxStatus, @@ -91,59 +197,96 @@ func main() { NginxStatusPort: *nginxStatusPort, StubStatusOverUnixSocketForOSS: *enablePrometheusMetrics, TLSPassthrough: *enableTLSPassthrough, + TLSPassthroughPort: *tlsPassthroughPort, EnableSnippets: *enableSnippets, NginxServiceMesh: *spireAgentAddress != "", MainAppProtectLoadModule: *appProtect, + MainAppProtectV5LoadModule: appProtectV5, MainAppProtectDosLoadModule: *appProtectDos, + MainAppProtectV5EnforcerAddr: *appProtectEnforcerAddress, EnableLatencyMetrics: *enableLatencyMetrics, EnableOIDC: *enableOIDC, SSLRejectHandshake: sslRejectHandshake, EnableCertManager: *enableCertManager, + DynamicSSLReload: *enableDynamicSSLReload, + DynamicWeightChangesReload: *enableDynamicWeightChangesReload, + StaticSSLPath: staticSSLPath, + NginxVersion: nginxVersion, + AppProtectBundlePath: appProtectBundlePath, } - processNginxConfig(staticCfgParams, cfgParams, templateExecutor, nginxManager) + mustWriteNginxMainConfig(staticCfgParams, cfgParams, mgmtCfgParams, templateExecutor, nginxManager) if *enableTLSPassthrough { var emptyFile []byte nginxManager.CreateTLSPassthroughHostsConfig(emptyFile) } - nginxDone := make(chan error, 1) - nginxManager.Start(nginxDone) + process := startChildProcesses(nginxManager, appProtectV5) - plusClient := createPlusClient(*nginxPlus, useFakeNginxManager, nginxManager) - - plusCollector, syslogListener, latencyCollector := createPlusAndLatencyCollectors(registry, constLabels, kubeClient, plusClient, staticCfgParams.NginxServiceMesh) + plusClient := createPlusClient(ctx, *nginxPlus, useFakeNginxManager, nginxManager) + if *nginxPlus { + licenseReporter.Config.PlusClient = plusClient + } - cnf := configs.NewConfigurator(nginxManager, staticCfgParams, cfgParams, templateExecutor, - templateExecutorV2, *nginxPlus, isWildcardEnabled, plusCollector, *enablePrometheusMetrics, latencyCollector, *enableLatencyMetrics) - controllerNamespace := os.Getenv("POD_NAMESPACE") + plusCollector, syslogListener, latencyCollector := createPlusAndLatencyCollectors(ctx, registry, constLabels, kubeClient, plusClient, staticCfgParams.NginxServiceMesh) + cnf := configs.NewConfigurator(configs.ConfiguratorParams{ + NginxManager: nginxManager, + StaticCfgParams: staticCfgParams, + Config: cfgParams, + MGMTCfgParams: mgmtCfgParams, + TemplateExecutor: templateExecutor, + TemplateExecutorV2: templateExecutorV2, + LatencyCollector: latencyCollector, + LabelUpdater: plusCollector, + IsPlus: *nginxPlus, + IsWildcardEnabled: isWildcardEnabled, + IsPrometheusEnabled: *enablePrometheusMetrics, + IsLatencyMetricsEnabled: *enableLatencyMetrics, + IsDynamicSSLReloadEnabled: *enableDynamicSSLReload, + IsDynamicWeightChangesReloadEnabled: *enableDynamicWeightChangesReload, + NginxVersion: nginxVersion, + }) transportServerValidator := cr_validation.NewTransportServerValidator(*enableTLSPassthrough, *enableSnippets, *nginxPlus) - virtualServerValidator := cr_validation.NewVirtualServerValidator(cr_validation.IsPlus(*nginxPlus), cr_validation.IsDosEnabled(*appProtectDos), cr_validation.IsCertManagerEnabled(*enableCertManager), cr_validation.IsExternalDNSEnabled(*enableExternalDNS)) + virtualServerValidator := cr_validation.NewVirtualServerValidator( + cr_validation.IsPlus(*nginxPlus), + cr_validation.IsDosEnabled(*appProtectDos), + cr_validation.IsCertManagerEnabled(*enableCertManager), + cr_validation.IsExternalDNSEnabled(*enableExternalDNS), + ) + + if *enableServiceInsight { + createHealthProbeEndpoint(kubeClient, plusClient, cnf) + } lbcInput := k8s.NewLoadBalancerControllerInput{ KubeClient: kubeClient, ConfClient: confClient, DynClient: dynClient, RestConfig: config, + Recorder: eventRecorder, ResyncPeriod: 30 * time.Second, + LoggerContext: ctx, Namespace: watchNamespaces, SecretNamespace: watchSecretNamespaces, NginxConfigurator: cnf, DefaultServerSecret: *defaultServerSecret, AppProtectEnabled: *appProtect, AppProtectDosEnabled: *appProtectDos, + AppProtectVersion: appProtectVersion, IsNginxPlus: *nginxPlus, IngressClass: *ingressClass, ExternalServiceName: *externalService, IngressLink: *ingressLink, ControllerNamespace: controllerNamespace, + Pod: pod, ReportIngressStatus: *reportIngressStatus, IsLeaderElectionEnabled: *leaderElectionEnabled, LeaderElectionLockName: *leaderElectionLockName, WildcardTLSSecret: *wildcardTLSSecret, ConfigMaps: *nginxConfigMaps, + MGMTConfigMap: *mgmtConfigMap, GlobalConfiguration: *globalConfiguration, AreCustomResourcesEnabled: *enableCustomResources, EnableOIDC: *enableOIDC, @@ -156,10 +299,18 @@ func main() { IsPrometheusEnabled: *enablePrometheusMetrics, IsLatencyMetricsEnabled: *enableLatencyMetrics, IsTLSPassthroughEnabled: *enableTLSPassthrough, + TLSPassthroughPort: *tlsPassthroughPort, SnippetsEnabled: *enableSnippets, CertManagerEnabled: *enableCertManager, ExternalDNSEnabled: *enableExternalDNS, IsIPV6Disabled: *disableIPV6, + WatchNamespaceLabel: *watchNamespaceLabel, + EnableTelemetryReporting: *enableTelemetryReporting, + TelemetryReportingEndpoint: telemetryEndpoint, + BuildOS: buildOS, + NICVersion: version, + DynamicWeightChangesReload: *enableDynamicWeightChangesReload, + InstallationFlags: parsedFlags, } lbc := k8s.NewLoadBalancerController(lbcInput) @@ -169,25 +320,60 @@ func main() { port := fmt.Sprintf(":%v", *readyStatusPort) s := http.NewServeMux() s.HandleFunc("/nginx-ready", ready(lbc)) - glog.Fatal(http.ListenAndServe(port, s)) + nl.Fatal(l, http.ListenAndServe(port, s)) // nolint:gosec }() } - if *appProtect || *appProtectDos { - go handleTerminationWithAppProtect(lbc, nginxManager, syslogListener, nginxDone, aPAgentDone, aPPluginDone, aPPDosAgentDone, *appProtect, *appProtectDos) - } else { - go handleTermination(lbc, nginxManager, syslogListener, nginxDone) - } + go handleTermination(lbc, nginxManager, syslogListener, process) lbc.Run() for { - glog.Info("Waiting for the controller to exit...") + nl.Info(l, "Waiting for the controller to exit...") time.Sleep(30 * time.Second) } } -func createConfigAndKubeClient() (*rest.Config, *kubernetes.Clientset) { +func processClientAuthSecret(kubeClient *kubernetes.Clientset, nginxManager nginx.Manager, mgmtCfgParams *configs.MGMTConfigParams, controllerNamespace string) error { + if mgmtCfgParams.Secrets.ClientAuth == "" { + return nil + } + + clientAuthSecretNsName := controllerNamespace + "/" + mgmtCfgParams.Secrets.ClientAuth + + secret, err := getAndValidateSecret(kubeClient, clientAuthSecretNsName, api_v1.SecretTypeTLS) + if err != nil { + return fmt.Errorf("error trying to get the client auth secret %v: %w", clientAuthSecretNsName, err) + } + + bytes := configs.GenerateCertAndKeyFileContent(secret) + nginxManager.CreateSecret(fmt.Sprintf("mgmt/%s", configs.ClientAuthCertSecretFileName), bytes, nginx.ReadWriteOnlyFileMode) + return nil +} + +func processTrustedCertSecret(kubeClient *kubernetes.Clientset, nginxManager nginx.Manager, mgmtCfgParams *configs.MGMTConfigParams, controllerNamespace string) error { + if mgmtCfgParams.Secrets.TrustedCert == "" { + return nil + } + + trustedCertSecretNsName := controllerNamespace + "/" + mgmtCfgParams.Secrets.TrustedCert + + secret, err := getAndValidateSecret(kubeClient, trustedCertSecretNsName, secrets.SecretTypeCA) + if err != nil { + return fmt.Errorf("error trying to get the trusted cert secret %v: %w", trustedCertSecretNsName, err) + } + + caBytes, crlBytes := configs.GenerateCAFileContent(secret) + nginxManager.CreateSecret(fmt.Sprintf("mgmt/%s", configs.CACrtKey), caBytes, nginx.ReadWriteOnlyFileMode) + if _, hasCRL := secret.Data[configs.CACrlKey]; hasCRL { + mgmtCfgParams.Secrets.TrustedCRL = secret.Name + nginxManager.CreateSecret(fmt.Sprintf("mgmt/%s", configs.CACrlKey), crlBytes, nginx.ReadWriteOnlyFileMode) + } + return nil +} + +func mustCreateConfigAndKubeClient(ctx context.Context) (*rest.Config, *kubernetes.Clientset) { + l := nl.LoggerFromContext(ctx) var config *rest.Config var err error if *proxyURL != "" { @@ -199,102 +385,133 @@ func createConfigAndKubeClient() (*rest.Config, *kubernetes.Clientset) { }, }).ClientConfig() if err != nil { - glog.Fatalf("error creating client configuration: %v", err) + nl.Fatalf(l, "error creating client configuration: %v", err) } } else { if config, err = rest.InClusterConfig(); err != nil { - glog.Fatalf("error creating client configuration: %v", err) + nl.Fatalf(l, "error creating client configuration: %v", err) } } kubeClient, err := kubernetes.NewForConfig(config) if err != nil { - glog.Fatalf("Failed to create client: %v.", err) + nl.Fatalf(l, "Failed to create client: %v.", err) } return config, kubeClient } -func kubernetesVersionInfo(kubeClient kubernetes.Interface) { +// validateKubernetesVersionInfo returns an Error if +// the k8s version can not be retrieved or the version is not supported. +func validateKubernetesVersionInfo(ctx context.Context, kubeClient kubernetes.Interface) error { + l := nl.LoggerFromContext(ctx) k8sVersion, err := k8s.GetK8sVersion(kubeClient) if err != nil { - glog.Fatalf("error retrieving k8s version: %v", err) + return fmt.Errorf("error retrieving k8s version: %w", err) } - glog.Infof("Kubernetes version: %v", k8sVersion) + nl.Infof(l, "Kubernetes version: %v", k8sVersion) - minK8sVersion, err := util_version.ParseGeneric("1.21.0") + minK8sVersion, err := util_version.ParseGeneric("1.22.0") if err != nil { - glog.Fatalf("unexpected error parsing minimum supported version: %v", err) + return fmt.Errorf("unexpected error parsing minimum supported version: %w", err) } if !k8sVersion.AtLeast(minK8sVersion) { - glog.Fatalf("Versions of Kubernetes < %v are not supported, please refer to the documentation for details on supported versions and legacy controller support.", minK8sVersion) + return fmt.Errorf("versions of kubernetes < %v are not supported, please refer to the documentation for details on supported versions and legacy controller support", minK8sVersion) } + return nil } -func validateIngressClass(kubeClient kubernetes.Interface) { +// mustValidateIngressClass calls internally os.Exit +// and terminates the program if the ingress class is not valid. +func mustValidateIngressClass(ctx context.Context, kubeClient kubernetes.Interface) { + l := nl.LoggerFromContext(ctx) ingressClassRes, err := kubeClient.NetworkingV1().IngressClasses().Get(context.TODO(), *ingressClass, meta_v1.GetOptions{}) if err != nil { - glog.Fatalf("Error when getting IngressClass %v: %v", *ingressClass, err) + nl.Fatalf(l, "Error when getting IngressClass %v: %v", *ingressClass, err) } if ingressClassRes.Spec.Controller != k8s.IngressControllerName { - glog.Fatalf("IngressClass with name %v has an invalid Spec.Controller %v; expected %v", ingressClassRes.Name, ingressClassRes.Spec.Controller, k8s.IngressControllerName) + nl.Fatalf(l, "IngressClass with name %v has an invalid Spec.Controller %v; expected %v", ingressClassRes.Name, ingressClassRes.Spec.Controller, k8s.IngressControllerName) + } +} + +func checkNamespaces(ctx context.Context, kubeClient kubernetes.Interface) { + l := nl.LoggerFromContext(ctx) + if *watchNamespaceLabel != "" { + // bootstrap the watched namespace list + var newWatchNamespaces []string + nsList, err := kubeClient.CoreV1().Namespaces().List(context.TODO(), meta_v1.ListOptions{LabelSelector: *watchNamespaceLabel}) + if err != nil { + nl.Errorf(l, "error when getting Namespaces with the label selector %v: %v", watchNamespaceLabel, err) + } + for _, ns := range nsList.Items { + newWatchNamespaces = append(newWatchNamespaces, ns.Name) + } + watchNamespaces = newWatchNamespaces + nl.Infof(l, "Namespaces watched using label %v: %v", *watchNamespaceLabel, watchNamespaces) + } else { + checkNamespaceExists(ctx, kubeClient, watchNamespaces) } + checkNamespaceExists(ctx, kubeClient, watchSecretNamespaces) } -func checkNamespaceExists(kubeClient kubernetes.Interface, namespaces []string) { +func checkNamespaceExists(ctx context.Context, kubeClient kubernetes.Interface, namespaces []string) { + l := nl.LoggerFromContext(ctx) for _, ns := range namespaces { if ns != "" { _, err := kubeClient.CoreV1().Namespaces().Get(context.TODO(), ns, meta_v1.GetOptions{}) if err != nil { - glog.Warningf("Error when getting Namespace %v: %v", ns, err) + nl.Warnf(l, "Error when getting Namespace %v: %v", ns, err) } } } } -func createCustomClients(config *rest.Config) (dynamic.Interface, k8s_nginx.Interface) { +func createCustomClients(ctx context.Context, config *rest.Config) (dynamic.Interface, k8s_nginx.Interface) { + l := nl.LoggerFromContext(ctx) var dynClient dynamic.Interface var err error if *appProtectDos || *appProtect || *ingressLink != "" { dynClient, err = dynamic.NewForConfig(config) if err != nil { - glog.Fatalf("Failed to create dynamic client: %v.", err) + nl.Fatalf(l, "Failed to create dynamic client: %v.", err) } } var confClient k8s_nginx.Interface if *enableCustomResources { confClient, err = k8s_nginx.NewForConfig(config) if err != nil { - glog.Fatalf("Failed to create a conf client: %v", err) + nl.Fatalf(l, "Failed to create a conf client: %v", err) } // required for emitting Events for VirtualServer err = conf_scheme.AddToScheme(scheme.Scheme) if err != nil { - glog.Fatalf("Failed to add configuration types to the scheme: %v", err) + nl.Fatalf(l, "Failed to add configuration types to the scheme: %v", err) } } return dynClient, confClient } -func createPlusClient(nginxPlus bool, useFakeNginxManager bool, nginxManager nginx.Manager) *client.NginxClient { +func createPlusClient(ctx context.Context, nginxPlus bool, useFakeNginxManager bool, nginxManager nginx.Manager) *client.NginxClient { + l := nl.LoggerFromContext(ctx) var plusClient *client.NginxClient var err error if nginxPlus && !useFakeNginxManager { httpClient := getSocketClient("/var/lib/nginx/nginx-plus-api.sock") - plusClient, err = client.NewNginxClient(httpClient, "http://nginx-plus-api/api") + plusClient, err = client.NewNginxClient("http://nginx-plus-api/api", client.WithHTTPClient(httpClient)) if err != nil { - glog.Fatalf("Failed to create NginxClient for Plus: %v", err) + nl.Fatalf(l, "Failed to create NginxClient for Plus: %v", err) } nginxManager.SetPlusClients(plusClient, httpClient) } return plusClient } -func createTemplateExecutors() (*version1.TemplateExecutor, *version2.TemplateExecutor) { +func createTemplateExecutors(ctx context.Context) (*version1.TemplateExecutor, *version2.TemplateExecutor) { + l := nl.LoggerFromContext(ctx) nginxConfTemplatePath := "nginx.tmpl" nginxIngressTemplatePath := "nginx.ingress.tmpl" nginxVirtualServerTemplatePath := "nginx.virtualserver.tmpl" @@ -321,51 +538,76 @@ func createTemplateExecutors() (*version1.TemplateExecutor, *version2.TemplateEx templateExecutor, err := version1.NewTemplateExecutor(nginxConfTemplatePath, nginxIngressTemplatePath) if err != nil { - glog.Fatalf("Error creating TemplateExecutor: %v", err) + nl.Fatalf(l, "Error creating TemplateExecutor: %v", err) } templateExecutorV2, err := version2.NewTemplateExecutor(nginxVirtualServerTemplatePath, nginxTransportServerTemplatePath) if err != nil { - glog.Fatalf("Error creating TemplateExecutorV2: %v", err) + nl.Fatalf(l, "Error creating TemplateExecutorV2: %v", err) } return templateExecutor, templateExecutorV2 } -func createNginxManager(managerCollector collectors.ManagerCollector) (nginx.Manager, bool) { +func createNginxManager(ctx context.Context, managerCollector collectors.ManagerCollector, licenseReporter *license_reporting.LicenseReporter) (nginx.Manager, bool) { useFakeNginxManager := *proxyURL != "" var nginxManager nginx.Manager if useFakeNginxManager { nginxManager = nginx.NewFakeManager("/etc/nginx") } else { timeout := time.Duration(*nginxReloadTimeout) * time.Millisecond - nginxManager = nginx.NewLocalManager("/etc/nginx/", *nginxDebug, managerCollector, timeout) + nginxManager = nginx.NewLocalManager(ctx, "/etc/nginx/", *nginxDebug, managerCollector, licenseReporter, timeout, *nginxPlus) } return nginxManager, useFakeNginxManager } -func getNginxVersionInfo(nginxManager nginx.Manager) { - nginxVersion := nginxManager.Version() - isPlus := strings.Contains(nginxVersion, "plus") - glog.Infof("Using %s", nginxVersion) +func getNginxVersionInfo(ctx context.Context, nginxManager nginx.Manager) nginx.Version { + l := nl.LoggerFromContext(ctx) + nginxInfo := nginxManager.Version() + nl.Infof(l, "Using %s", nginxInfo.String()) - if *nginxPlus && !isPlus { - glog.Fatal("NGINX Plus flag enabled (-nginx-plus) without NGINX Plus binary") - } else if !*nginxPlus && isPlus { - glog.Fatal("NGINX Plus binary found without NGINX Plus flag (-nginx-plus)") + if *nginxPlus && !nginxInfo.IsPlus { + nl.Fatalf(l, "NGINX Plus flag enabled (-nginx-plus) without NGINX Plus binary") + } else if !*nginxPlus && nginxInfo.IsPlus { + nl.Fatalf(l, "NGINX Plus binary found without NGINX Plus flag (-nginx-plus)") } + return nginxInfo +} + +func getAppProtectVersionInfo(ctx context.Context) string { + l := nl.LoggerFromContext(ctx) + v, err := os.ReadFile(appProtectVersionPath) + if err != nil { + nl.Fatalf(l, "Cannot detect the AppProtect version, %s", err.Error()) + } + version := strings.TrimSpace(string(v)) + nl.Infof(l, "Using AppProtect Version %s", version) + return version +} + +func getAgentVersionInfo(nginxManager nginx.Manager) string { + return nginxManager.AgentVersion() +} + +type childProcesses struct { + nginxDone chan error + aPPluginEnable bool + aPPluginDone chan error + aPDosEnable bool + aPDosDone chan error + agentEnable bool + agentDone chan error } -func startApAgentsAndPlugins(nginxManager nginx.Manager) (chan error, chan error, chan error) { +// newChildProcesses starts the several child processes based on flags set. +// AppProtect. AppProtectDos, Agent. +func startChildProcesses(nginxManager nginx.Manager, appProtectV5 bool) childProcesses { var aPPluginDone chan error - var aPAgentDone chan error - if *appProtect { + // Do not start AppProtect Plugins when using v5. + if *appProtect && !appProtectV5 { aPPluginDone = make(chan error, 1) - aPAgentDone = make(chan error, 1) - - nginxManager.AppProtectAgentStart(aPAgentDone, *appProtectLogLevel) - nginxManager.AppProtectPluginStart(aPPluginDone) + nginxManager.AppProtectPluginStart(aPPluginDone, *appProtectLogLevel) } var aPPDosAgentDone chan error @@ -374,20 +616,38 @@ func startApAgentsAndPlugins(nginxManager nginx.Manager) (chan error, chan error aPPDosAgentDone = make(chan error, 1) nginxManager.AppProtectDosAgentStart(aPPDosAgentDone, *appProtectDosDebug, *appProtectDosMaxDaemons, *appProtectDosMaxWorkers, *appProtectDosMemory) } - return aPPluginDone, aPAgentDone, aPPDosAgentDone + + nginxDone := make(chan error, 1) + nginxManager.Start(nginxDone) + + var agentDone chan error + if *agent { + agentDone = make(chan error, 1) + nginxManager.AgentStart(agentDone, *agentInstanceGroup) + } + + return childProcesses{ + nginxDone: nginxDone, + aPPluginEnable: *appProtect, + aPPluginDone: aPPluginDone, + aPDosEnable: *appProtectDos, + aPDosDone: aPPDosAgentDone, + agentEnable: *agent, + agentDone: agentDone, + } } -func processDefaultServerSecret(kubeClient *kubernetes.Clientset, nginxManager nginx.Manager) bool { +func processDefaultServerSecret(kubeClient *kubernetes.Clientset, nginxManager nginx.Manager) (bool, error) { var sslRejectHandshake bool if *defaultServerSecret != "" { - secret, err := getAndValidateSecret(kubeClient, *defaultServerSecret) + secret, err := getAndValidateSecret(kubeClient, *defaultServerSecret, api_v1.SecretTypeTLS) if err != nil { - glog.Fatalf("Error trying to get the default server TLS secret %v: %v", *defaultServerSecret, err) + return sslRejectHandshake, fmt.Errorf("error trying to get the default server TLS secret %v: %w", *defaultServerSecret, err) } bytes := configs.GenerateCertAndKeyFileContent(secret) - nginxManager.CreateSecret(configs.DefaultServerSecretName, bytes, nginx.TLSSecretFileMode) + nginxManager.CreateSecret(configs.DefaultServerSecretFileName, bytes, nginx.ReadWriteOnlyFileMode) } else { _, err := os.Stat(configs.DefaultServerSecretPath) if err != nil { @@ -395,24 +655,41 @@ func processDefaultServerSecret(kubeClient *kubernetes.Clientset, nginxManager n // file doesn't exist - it is OK! we will reject TLS connections in the default server sslRejectHandshake = true } else { - glog.Fatalf("Error checking the default server TLS cert and key in %s: %v", configs.DefaultServerSecretPath, err) + return sslRejectHandshake, fmt.Errorf("error checking the default server TLS cert and key in %s: %w", configs.DefaultServerSecretPath, err) } } } - return sslRejectHandshake + return sslRejectHandshake, nil } -func processWildcardSecret(kubeClient *kubernetes.Clientset, nginxManager nginx.Manager) bool { - if *wildcardTLSSecret != "" { - secret, err := getAndValidateSecret(kubeClient, *wildcardTLSSecret) +func processWildcardSecret(kubeClient *kubernetes.Clientset, nginxManager nginx.Manager) (bool, error) { + isWildcardEnabled := *wildcardTLSSecret != "" + if isWildcardEnabled { + secret, err := getAndValidateSecret(kubeClient, *wildcardTLSSecret, api_v1.SecretTypeTLS) if err != nil { - glog.Fatalf("Error trying to get the wildcard TLS secret %v: %v", *wildcardTLSSecret, err) + return false, fmt.Errorf("error trying to get the wildcard TLS secret %v: %w", *wildcardTLSSecret, err) } bytes := configs.GenerateCertAndKeyFileContent(secret) - nginxManager.CreateSecret(configs.WildcardSecretName, bytes, nginx.TLSSecretFileMode) + nginxManager.CreateSecret(configs.WildcardSecretFileName, bytes, nginx.ReadWriteOnlyFileMode) + } + return isWildcardEnabled, nil +} + +func processLicenseSecret(kubeClient *kubernetes.Clientset, nginxManager nginx.Manager, mgmtCfgParams *configs.MGMTConfigParams, controllerNamespace string) error { + licenseSecretNsName := controllerNamespace + "/" + mgmtCfgParams.Secrets.License + + secret, err := getAndValidateSecret(kubeClient, licenseSecretNsName, secrets.SecretTypeLicense) + if err != nil { + return fmt.Errorf("license secret: %w", err) } - return *wildcardTLSSecret != "" + + bytes, err := configs.GenerateLicenseSecret(secret) + if err != nil { + return err + } + nginxManager.CreateSecret(configs.LicenseSecretFileName, bytes, nginx.ReadWriteOnlyFileMode) + return nil } func createGlobalConfigurationValidator() *cr_validation.GlobalConfigurationValidator { @@ -428,14 +705,25 @@ func createGlobalConfigurationValidator() *cr_validation.GlobalConfigurationVali forbiddenListenerPorts[*prometheusMetricsListenPort] = true } + if *enableServiceInsight { + forbiddenListenerPorts[*serviceInsightListenPort] = true + } + + if *enableTLSPassthrough { + forbiddenListenerPorts[*tlsPassthroughPort] = true + } + return cr_validation.NewGlobalConfigurationValidator(forbiddenListenerPorts) } -func processNginxConfig(staticCfgParams *configs.StaticConfigParams, cfgParams *configs.ConfigParams, templateExecutor *version1.TemplateExecutor, nginxManager nginx.Manager) { - ngxConfig := configs.GenerateNginxMainConfig(staticCfgParams, cfgParams) +// mustWriteNginxMainConfig calls internally os.Exit +// if can't generate a valid NGINX config. +func mustWriteNginxMainConfig(staticCfgParams *configs.StaticConfigParams, cfgParams *configs.ConfigParams, mgmtCfgParams *configs.MGMTConfigParams, templateExecutor *version1.TemplateExecutor, nginxManager nginx.Manager) { + l := nl.LoggerFromContext(cfgParams.Context) + ngxConfig := configs.GenerateNginxMainConfig(staticCfgParams, cfgParams, mgmtCfgParams) content, err := templateExecutor.ExecuteMainConfigTemplate(ngxConfig) if err != nil { - glog.Fatalf("Error generating NGINX main config: %v", err) + nl.Fatalf(l, "Error generating NGINX main config: %v", err) } nginxManager.CreateMainConfig(content) @@ -446,46 +734,12 @@ func processNginxConfig(staticCfgParams *configs.StaticConfigParams, cfgParams * if ngxConfig.OpenTracingLoadModule { err := nginxManager.CreateOpenTracingTracerConfig(cfgParams.MainOpenTracingTracerConfig) if err != nil { - glog.Fatalf("Error creating OpenTracing tracer config file: %v", err) - } - } -} - -func handleTermination(lbc *k8s.LoadBalancerController, nginxManager nginx.Manager, listener metrics.SyslogListener, nginxDone chan error) { - signalChan := make(chan os.Signal, 1) - signal.Notify(signalChan, syscall.SIGTERM) - - exitStatus := 0 - exited := false - - select { - case err := <-nginxDone: - if err != nil { - glog.Errorf("nginx command exited with an error: %v", err) - exitStatus = 1 - } else { - glog.Info("nginx command exited successfully") + nl.Fatalf(l, "Error creating OpenTracing tracer config file: %v", err) } - exited = true - case <-signalChan: - glog.Info("Received SIGTERM, shutting down") } - - glog.Info("Shutting down the controller") - lbc.Stop() - - if !exited { - glog.Info("Shutting down NGINX") - nginxManager.Quit() - <-nginxDone - } - listener.Stop() - - glog.Infof("Exiting with a status: %v", exitStatus) - os.Exit(exitStatus) } -// getSocketClient gets an http.Client with the a unix socket transport. +// getSocketClient gets a http.Client with a unix socket transport. func getSocketClient(sockPath string) *http.Client { return &http.Client{ Transport: &http.Transport{ @@ -497,53 +751,67 @@ func getSocketClient(sockPath string) *http.Client { } // getAndValidateSecret gets and validates a secret. -func getAndValidateSecret(kubeClient *kubernetes.Clientset, secretNsName string) (secret *api_v1.Secret, err error) { +func getAndValidateSecret(kubeClient *kubernetes.Clientset, secretNsName string, secretType api_v1.SecretType) (secret *api_v1.Secret, err error) { ns, name, err := k8s.ParseNamespaceName(secretNsName) if err != nil { return nil, fmt.Errorf("could not parse the %v argument: %w", secretNsName, err) } secret, err = kubeClient.CoreV1().Secrets(ns).Get(context.TODO(), name, meta_v1.GetOptions{}) if err != nil { - return nil, fmt.Errorf("could not get %v: %w", secretNsName, err) + return nil, fmt.Errorf("could not find %v: %w", secretNsName, err) } - err = secrets.ValidateTLSSecret(secret) - if err != nil { - return nil, fmt.Errorf("%v is invalid: %w", secretNsName, err) + switch secretType { + case api_v1.SecretTypeTLS: + err = secrets.ValidateTLSSecret(secret) + if err != nil { + return nil, fmt.Errorf("%v is invalid: %w", secretNsName, err) + } + case secrets.SecretTypeLicense: + err = secrets.ValidateLicenseSecret(secret) + if err != nil { + return nil, err + } + case secrets.SecretTypeCA: + err = secrets.ValidateCASecret(secret) + if err != nil { + return nil, err + } } + return secret, nil } -func handleTerminationWithAppProtect(lbc *k8s.LoadBalancerController, nginxManager nginx.Manager, listener metrics.SyslogListener, nginxDone, agentDone, pluginDone, agentDosDone chan error, appProtectEnabled, appProtectDosEnabled bool) { +func handleTermination(lbc *k8s.LoadBalancerController, nginxManager nginx.Manager, listener metrics.SyslogListener, cpcfg childProcesses) { signalChan := make(chan os.Signal, 1) signal.Notify(signalChan, syscall.SIGTERM) select { - case err := <-nginxDone: - glog.Fatalf("nginx command exited unexpectedly with status: %v", err) - case err := <-pluginDone: - glog.Fatalf("AppProtectPlugin command exited unexpectedly with status: %v", err) - case err := <-agentDone: - glog.Fatalf("AppProtectAgent command exited unexpectedly with status: %v", err) - case err := <-agentDosDone: - glog.Fatalf("AppProtectDosAgent command exited unexpectedly with status: %v", err) + case err := <-cpcfg.nginxDone: + if err != nil { + nl.Fatalf(lbc.Logger, "nginx command exited unexpectedly with status: %v", err) + } else { + nl.Info(lbc.Logger, "nginx command exited successfully") + } + case err := <-cpcfg.aPPluginDone: + nl.Fatalf(lbc.Logger, "AppProtectPlugin command exited unexpectedly with status: %v", err) + case err := <-cpcfg.aPDosDone: + nl.Fatalf(lbc.Logger, "AppProtectDosAgent command exited unexpectedly with status: %v", err) case <-signalChan: - glog.Infof("Received SIGTERM, shutting down") + nl.Info(lbc.Logger, "Received SIGTERM, shutting down") lbc.Stop() nginxManager.Quit() - <-nginxDone - if appProtectEnabled { + <-cpcfg.nginxDone + if cpcfg.aPPluginEnable { nginxManager.AppProtectPluginQuit() - <-pluginDone - nginxManager.AppProtectAgentQuit() - <-agentDone + <-cpcfg.aPPluginDone } - if appProtectDosEnabled { + if cpcfg.aPDosEnable { nginxManager.AppProtectDosAgentQuit() - <-agentDosDone + <-cpcfg.aPDosDone } listener.Stop() } - glog.Info("Exiting successfully") + nl.Info(lbc.Logger, "Exiting successfully") os.Exit(0) } @@ -558,7 +826,8 @@ func ready(lbc *k8s.LoadBalancerController) http.HandlerFunc { } } -func createManagerAndControllerCollectors(constLabels map[string]string) (collectors.ManagerCollector, collectors.ControllerCollector, *prometheus.Registry) { +func createManagerAndControllerCollectors(ctx context.Context, constLabels map[string]string) (collectors.ManagerCollector, collectors.ControllerCollector, *prometheus.Registry) { + l := nl.LoggerFromContext(ctx) var err error var registry *prometheus.Registry @@ -571,39 +840,41 @@ func createManagerAndControllerCollectors(constLabels map[string]string) (collec registry = prometheus.NewRegistry() mc = collectors.NewLocalManagerMetricsCollector(constLabels) cc = collectors.NewControllerMetricsCollector(*enableCustomResources, constLabels) - processCollector := collectors.NewNginxProcessesMetricsCollector(constLabels) + processCollector := collectors.NewNginxProcessesMetricsCollector(ctx, constLabels) workQueueCollector := collectors.NewWorkQueueMetricsCollector(constLabels) err = mc.Register(registry) if err != nil { - glog.Errorf("Error registering Manager Prometheus metrics: %v", err) + nl.Errorf(l, "Error registering Manager Prometheus metrics: %v", err) } err = cc.Register(registry) if err != nil { - glog.Errorf("Error registering Controller Prometheus metrics: %v", err) + nl.Errorf(l, "Error registering Controller Prometheus metrics: %v", err) } err = processCollector.Register(registry) if err != nil { - glog.Errorf("Error registering NginxProcess Prometheus metrics: %v", err) + nl.Errorf(l, "Error registering NginxProcess Prometheus metrics: %v", err) } err = workQueueCollector.Register(registry) if err != nil { - glog.Errorf("Error registering WorkQueue Prometheus metrics: %v", err) + nl.Errorf(l, "Error registering WorkQueue Prometheus metrics: %v", err) } } return mc, cc, registry } func createPlusAndLatencyCollectors( + ctx context.Context, registry *prometheus.Registry, constLabels map[string]string, kubeClient *kubernetes.Clientset, plusClient *client.NginxClient, isMesh bool, ) (*nginxCollector.NginxPlusCollector, metrics.SyslogListener, collectors.LatencyCollector) { + l := nl.LoggerFromContext(ctx) var prometheusSecret *api_v1.Secret var err error var lc collectors.LatencyCollector @@ -612,9 +883,9 @@ func createPlusAndLatencyCollectors( syslogListener = metrics.NewSyslogFakeServer() if *prometheusTLSSecretName != "" { - prometheusSecret, err = getAndValidateSecret(kubeClient, *prometheusTLSSecretName) + prometheusSecret, err = getAndValidateSecret(kubeClient, *prometheusTLSSecretName, api_v1.SecretTypeTLS) if err != nil { - glog.Fatalf("Error trying to get the prometheus TLS secret %v: %v", *prometheusTLSSecretName, err) + nl.Fatalf(l, "Error trying to get the prometheus TLS secret %v: %v", *prometheusTLSSecretName, err) } } @@ -632,23 +903,20 @@ func createPlusAndLatencyCollectors( serverZoneVariableLabels := []string{"resource_type", "resource_name", "resource_namespace"} streamServerZoneVariableLabels := []string{"resource_type", "resource_name", "resource_namespace"} variableLabelNames := nginxCollector.NewVariableLabelNames(upstreamServerVariableLabels, serverZoneVariableLabels, upstreamServerPeerVariableLabelNames, - streamUpstreamServerVariableLabels, streamServerZoneVariableLabels, streamUpstreamServerPeerVariableLabelNames) - plusCollector = nginxCollector.NewNginxPlusCollector(plusClient, "nginx_ingress_nginxplus", variableLabelNames, constLabels) - go metrics.RunPrometheusListenerForNginxPlus(*prometheusMetricsListenPort, plusCollector, registry, prometheusSecret) + streamUpstreamServerVariableLabels, streamServerZoneVariableLabels, streamUpstreamServerPeerVariableLabelNames, nil) + plusCollector = nginxCollector.NewNginxPlusCollector(plusClient, "nginx_ingress_nginxplus", variableLabelNames, constLabels, l) + go metrics.RunPrometheusListenerForNginxPlus(ctx, *prometheusMetricsListenPort, plusCollector, registry, prometheusSecret) } else { httpClient := getSocketClient("/var/lib/nginx/nginx-status.sock") - client, err := metrics.NewNginxMetricsClient(httpClient) - if err != nil { - glog.Errorf("Error creating the Nginx client for Prometheus metrics: %v", err) - } - go metrics.RunPrometheusListenerForNginx(*prometheusMetricsListenPort, client, registry, constLabels, prometheusSecret) + client := metrics.NewNginxMetricsClient(httpClient) + go metrics.RunPrometheusListenerForNginx(ctx, *prometheusMetricsListenPort, client, registry, constLabels, prometheusSecret) } if *enableLatencyMetrics { - lc = collectors.NewLatencyMetricsCollector(constLabels, upstreamServerVariableLabels, upstreamServerPeerVariableLabelNames) + lc = collectors.NewLatencyMetricsCollector(ctx, constLabels, upstreamServerVariableLabels, upstreamServerPeerVariableLabelNames) if err := lc.Register(registry); err != nil { - glog.Errorf("Error registering Latency Prometheus metrics: %v", err) + nl.Errorf(l, "Error registering Latency Prometheus metrics: %v", err) } - syslogListener = metrics.NewLatencyMetricsListener("/var/lib/nginx/nginx-syslog.sock", lc) + syslogListener = metrics.NewLatencyMetricsListener(ctx, "/var/lib/nginx/nginx-syslog.sock", lc) go syslogListener.Run() } } @@ -656,34 +924,55 @@ func createPlusAndLatencyCollectors( return plusCollector, syslogListener, lc } -func processGlobalConfiguration() { +func createHealthProbeEndpoint(kubeClient *kubernetes.Clientset, plusClient *client.NginxClient, cnf *configs.Configurator) { + l := nl.LoggerFromContext(cnf.CfgParams.Context) + if !*enableServiceInsight { + return + } + var serviceInsightSecret *api_v1.Secret + var err error + + if *serviceInsightTLSSecretName != "" { + serviceInsightSecret, err = getAndValidateSecret(kubeClient, *serviceInsightTLSSecretName, api_v1.SecretTypeTLS) + if err != nil { + nl.Fatalf(l, "Error trying to get the service insight TLS secret %v: %v", *serviceInsightTLSSecretName, err) + } + } + go healthcheck.RunHealthCheck(*serviceInsightListenPort, plusClient, cnf, serviceInsightSecret) +} + +// mustProcessGlobalConfiguration calls internally os.Exit +// if unable to parse provided global configuration. +func mustProcessGlobalConfiguration(ctx context.Context) { + l := nl.LoggerFromContext(ctx) if *globalConfiguration != "" { _, _, err := k8s.ParseNamespaceName(*globalConfiguration) if err != nil { - glog.Fatalf("Error parsing the global-configuration argument: %v", err) + nl.Fatalf(l, "Error parsing the global-configuration argument: %v", err) } if !*enableCustomResources { - glog.Fatal("global-configuration flag requires -enable-custom-resources") + nl.Fatalf(l, "global-configuration flag requires -enable-custom-resources") } } } -func processConfigMaps(kubeClient *kubernetes.Clientset, cfgParams *configs.ConfigParams, nginxManager nginx.Manager, templateExecutor *version1.TemplateExecutor) *configs.ConfigParams { +func processConfigMaps(kubeClient *kubernetes.Clientset, cfgParams *configs.ConfigParams, nginxManager nginx.Manager, templateExecutor *version1.TemplateExecutor, eventLog record.EventRecorder) *configs.ConfigParams { + l := nl.LoggerFromContext(cfgParams.Context) if *nginxConfigMaps != "" { ns, name, err := k8s.ParseNamespaceName(*nginxConfigMaps) if err != nil { - glog.Fatalf("Error parsing the nginx-configmaps argument: %v", err) + nl.Fatalf(l, "Error parsing the nginx-configmaps argument: %v", err) } cfm, err := kubeClient.CoreV1().ConfigMaps(ns).Get(context.TODO(), name, meta_v1.GetOptions{}) if err != nil { - glog.Fatalf("Error when getting %v: %v", *nginxConfigMaps, err) + nl.Fatalf(l, "Error when getting %v: %v", *nginxConfigMaps, err) } - cfgParams = configs.ParseConfigMap(cfm, *nginxPlus, *appProtect, *appProtectDos, *enableTLSPassthrough) + cfgParams, _ = configs.ParseConfigMap(cfgParams.Context, cfm, *nginxPlus, *appProtect, *appProtectDos, *enableTLSPassthrough, eventLog) if cfgParams.MainServerSSLDHParamFileContent != nil { fileName, err := nginxManager.CreateDHParam(*cfgParams.MainServerSSLDHParamFileContent) if err != nil { - glog.Fatalf("Configmap %s/%s: Could not update dhparams: %v", ns, name, err) + nl.Fatalf(l, "Configmap %s/%s: Could not update dhparams: %v", ns, name, err) } else { cfgParams.MainServerSSLDHParam = fileName } @@ -691,15 +980,113 @@ func processConfigMaps(kubeClient *kubernetes.Clientset, cfgParams *configs.Conf if cfgParams.MainTemplate != nil { err = templateExecutor.UpdateMainTemplate(cfgParams.MainTemplate) if err != nil { - glog.Fatalf("Error updating NGINX main template: %v", err) + nl.Fatalf(l, "Error updating NGINX main template: %v", err) } } if cfgParams.IngressTemplate != nil { err = templateExecutor.UpdateIngressTemplate(cfgParams.IngressTemplate) if err != nil { - glog.Fatalf("Error updating ingress template: %v", err) + nl.Fatalf(l, "Error updating ingress template: %v", err) } } } return cfgParams } + +func processMGMTConfigMap(kubeClient *kubernetes.Clientset, mgmtCfgParams *configs.MGMTConfigParams, eventLog record.EventRecorder, pod *api_v1.Pod) *configs.MGMTConfigParams { + ctx := mgmtCfgParams.Context + var fatalErr error + + ns, name, err := k8s.ParseNamespaceName(*mgmtConfigMap) + if err != nil { + logEventAndExit(ctx, eventLog, pod, configMapErrorReason, fmt.Errorf("error parsing the mgmt-configmap argument: %w", err)) + } + cfm, err := kubeClient.CoreV1().ConfigMaps(ns).Get(context.TODO(), name, meta_v1.GetOptions{}) + if err != nil { + logEventAndExit(ctx, eventLog, cfm, configMapErrorReason, fmt.Errorf("error when getting mgmt-configmap [%v]: %w", *mgmtConfigMap, err)) + } + if mgmtCfgParams, _, fatalErr = configs.ParseMGMTConfigMap(ctx, cfm, eventLog); fatalErr != nil { + logEventAndExit(ctx, eventLog, cfm, secretErrorReason, fatalErr) + } + return mgmtCfgParams +} + +func updateSelfWithVersionInfo(ctx context.Context, eventLog record.EventRecorder, kubeClient *kubernetes.Clientset, version, appProtectVersion, agentVersion string, nginxVersion nginx.Version, maxRetries int, waitTime time.Duration) { + l := nl.LoggerFromContext(ctx) + podUpdated := false + + for i := 0; (i < maxRetries || maxRetries == 0) && !podUpdated; i++ { + if i > 0 { + time.Sleep(waitTime) + } + pod, err := kubeClient.CoreV1().Pods(os.Getenv("POD_NAMESPACE")).Get(context.TODO(), os.Getenv("POD_NAME"), meta_v1.GetOptions{}) + if err != nil { + nl.Errorf(l, "Error getting pod on attempt %d of %d: %v", i+1, maxRetries, err) + continue + } + + // Copy pod and update the labels. + newPod := pod.DeepCopy() + labels := newPod.ObjectMeta.Labels + if labels == nil { + labels = make(map[string]string) + } + + labels[nginxVersionLabel] = nginxVersion.Format() + labels[versionLabel] = strings.TrimPrefix(version, "v") + if appProtectVersion != "" { + labels[appProtectVersionLabel] = appProtectVersion + } + if agentVersion != "" { + labels[agentVersionLabel] = agentVersion + } + newPod.ObjectMeta.Labels = labels + + _, err = kubeClient.CoreV1().Pods(newPod.ObjectMeta.Namespace).Update(context.TODO(), newPod, meta_v1.UpdateOptions{}) + if err != nil { + nl.Errorf(l, "Error updating pod with labels on attempt %d of %d: %v", i+1, maxRetries, err) + continue + } + + labelsString := new(bytes.Buffer) + for key, value := range labels { + fmt.Fprintf(labelsString, "%s=\"%s\", ", key, value) + } + eventLog.Eventf(newPod, api_v1.EventTypeNormal, "UpdatePodLabel", "Successfully added version labels, %s", strings.TrimRight(labelsString.String(), ", ")) + nl.Infof(l, "Pod label updated: %s", pod.ObjectMeta.Name) + podUpdated = true + } + + if !podUpdated { + nl.Errorf(l, "Failed to update pod labels after %d attempts", maxRetries) + } +} + +func logEventAndExit(ctx context.Context, eventLog record.EventRecorder, obj pkg_runtime.Object, reason string, err error) { + l := nl.LoggerFromContext(ctx) + eventLog.Eventf(obj, api_v1.EventTypeWarning, reason, err.Error()) + time.Sleep(fatalEventFlushTime) // wait for the event to be flushed + nl.Fatal(l, err.Error()) +} + +func initLogger(logFormat string, level slog.Level, out io.Writer) context.Context { + programLevel := new(slog.LevelVar) // Info by default + var h slog.Handler + switch { + case logFormat == "glog": + h = nic_glog.New(out, &nic_glog.Options{Level: programLevel}) + case logFormat == "json": + h = slog.NewJSONHandler(out, &slog.HandlerOptions{Level: programLevel}) + case logFormat == "text": + h = slog.NewTextHandler(out, &slog.HandlerOptions{Level: programLevel}) + default: + h = nic_glog.New(out, &nic_glog.Options{Level: programLevel}) + } + l := slog.New(h) + slog.SetDefault(l) + c := context.Background() + + programLevel.Set(level) + + return nl.ContextWithLogger(c, l) +} diff --git a/cmd/nginx-ingress/main_test.go b/cmd/nginx-ingress/main_test.go index 1715d2a3e8..8e62435911 100644 --- a/cmd/nginx-ingress/main_test.go +++ b/cmd/nginx-ingress/main_test.go @@ -1,174 +1,133 @@ package main import ( - "errors" - "reflect" - "strings" + "bytes" + "context" + "io" + "log/slog" + "regexp" "testing" -) - -func TestValidatePort(t *testing.T) { - badPorts := []int{80, 443, 1, 1023, 65536} - for _, badPort := range badPorts { - err := validatePort(badPort) - if err == nil { - t.Errorf("Expected error for port %v\n", badPort) - } - } - goodPorts := []int{8080, 8081, 8082, 1024, 65535} - for _, goodPort := range goodPorts { - err := validatePort(goodPort) - if err != nil { - t.Errorf("Error for valid port: %v err: %v\n", goodPort, err) - } - } -} + nl "github.com/nginxinc/kubernetes-ingress/internal/logger" + nic_glog "github.com/nginxinc/kubernetes-ingress/internal/logger/glog" + "github.com/nginxinc/kubernetes-ingress/internal/logger/levels" + pkgversion "k8s.io/apimachinery/pkg/version" + fakediscovery "k8s.io/client-go/discovery/fake" + "k8s.io/client-go/kubernetes/fake" +) -func TestParseNginxStatusAllowCIDRs(t *testing.T) { - badCIDRs := []struct { - input string - expectedError error +func TestLogFormats(t *testing.T) { + testCases := []struct { + name string + format string + wantre string }{ { - "earth, ,,furball", - errors.New("invalid IP address: earth"), + name: "glog format message", + format: "glog", + wantre: `^I\d{8}\s\d+:\d+:\d+.\d{6}\s+\d+\s\w+\.go:\d+\]\s.*\s$`, }, { - "127.0.0.1,10.0.1.0/24, ,,furball", - errors.New("invalid CIDR address: an empty string is an invalid CIDR block or IP address"), + name: "json format message", + format: "json", + wantre: `^{"time":"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d+.*","level":"INFO","msg":".*}`, }, { - "false", - errors.New("invalid IP address: false"), + name: "text format message", + format: "text", + wantre: `^time=\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d+.*level=\w+\smsg=\w+`, }, } - for _, badCIDR := range badCIDRs { - _, err := parseNginxStatusAllowCIDRs(badCIDR.input) - if err == nil { - t.Errorf("parseNginxStatusAllowCIDRs(%q) returned no error when it should have returned error %q", badCIDR.input, badCIDR.expectedError) - } else if err.Error() != badCIDR.expectedError.Error() { - t.Errorf("parseNginxStatusAllowCIDRs(%q) returned error %q when it should have returned error %q", badCIDR.input, err, badCIDR.expectedError) - } + t.Parallel() + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + var buf bytes.Buffer + ctx := initLogger(tc.format, levels.LevelInfo, &buf) + l := nl.LoggerFromContext(ctx) + l.Log(ctx, levels.LevelInfo, "test") + got := buf.String() + re := regexp.MustCompile(tc.wantre) + if !re.MatchString(got) { + t.Errorf("\ngot:\n%q\nwant:\n%q", got, tc.wantre) + } + }) } +} - goodCIDRs := []struct { - input string - expected []string +func TestK8sVersionValidation(t *testing.T) { + testCases := []struct { + name string + kubeVersion string }{ { - "127.0.0.1", - []string{"127.0.0.1"}, + name: "Earliest version 1.22.0", + kubeVersion: "1.22.0", }, { - "10.0.1.0/24", - []string{"10.0.1.0/24"}, + name: "Minor version 1.22.5", + kubeVersion: "1.22.5", }, { - "127.0.0.1,10.0.1.0/24,68.121.233.214 , 24.24.24.24/32", - []string{"127.0.0.1", "10.0.1.0/24", "68.121.233.214", "24.24.24.24/32"}, + name: "Close to current 1.32.0", + kubeVersion: "1.32.0", }, } - for _, goodCIDR := range goodCIDRs { - result, err := parseNginxStatusAllowCIDRs(goodCIDR.input) - if err != nil { - t.Errorf("parseNginxStatusAllowCIDRs(%q) returned an error when it should have returned no error: %q", goodCIDR.input, err) - } - - if !reflect.DeepEqual(result, goodCIDR.expected) { - t.Errorf("parseNginxStatusAllowCIDRs(%q) returned %v expected %v: ", goodCIDR.input, result, goodCIDR.expected) - } - } -} + t.Parallel() + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + // setup logger + l := slog.New(nic_glog.New(io.Discard, &nic_glog.Options{Level: levels.LevelInfo})) + ctx := nl.ContextWithLogger(context.Background(), l) -func TestValidateCIDRorIP(t *testing.T) { - badCIDRs := []string{"localhost", "thing", "~", "!!!", "", " ", "-1"} - for _, badCIDR := range badCIDRs { - err := validateCIDRorIP(badCIDR) - if err == nil { - t.Errorf(`Expected error for invalid CIDR "%v"\n`, badCIDR) - } - } + // setup kube client with version + clientset := fake.NewSimpleClientset() + fakeDiscovery, _ := clientset.Discovery().(*fakediscovery.FakeDiscovery) + fakeDiscovery.FakedServerVersion = &pkgversion.Info{GitVersion: tc.kubeVersion} - goodCIDRs := []string{"0.0.0.0/32", "0.0.0.0/0", "127.0.0.1/32", "127.0.0.0/24", "23.232.65.42"} - for _, goodCIDR := range goodCIDRs { - err := validateCIDRorIP(goodCIDR) - if err != nil { - t.Errorf("Error for valid CIDR: %v err: %v\n", goodCIDR, err) - } + // run test + err := validateKubernetesVersionInfo(ctx, clientset) + if err != nil { + t.Errorf("%v", err) + } + }) } } -func TestValidateLocation(t *testing.T) { - badLocations := []string{ - "", - "/", - " /test", - "/bad;", - } - for _, badLocation := range badLocations { - err := validateLocation(badLocation) - if err == nil { - t.Errorf("validateLocation(%v) returned no error when it should have returned an error", badLocation) - } - } - - goodLocations := []string{ - "/test", - "/test/subtest", - } - for _, goodLocation := range goodLocations { - err := validateLocation(goodLocation) - if err != nil { - t.Errorf("validateLocation(%v) returned an error when it should have returned no error: %v", goodLocation, err) - } - } -} - -func TestValidateAppProtectLogLevel(t *testing.T) { - badLogLevels := []string{ - "", - "critical", - "none", - "info;", - } - for _, badLogLevel := range badLogLevels { - err := validateAppProtectLogLevel(badLogLevel) - if err == nil { - t.Errorf("validateAppProtectLogLevel(%v) returned no error when it should have returned an error", badLogLevel) - } - } - - goodLogLevels := []string{ - "fatal", - "Error", - "WARN", - "info", - "debug", - "trace", - } - for _, goodLogLevel := range goodLogLevels { - err := validateAppProtectLogLevel(goodLogLevel) - if err != nil { - t.Errorf("validateAppProtectLogLevel(%v) returned an error when it should have returned no error: %v", goodLogLevel, err) - } +func TestK8sVersionValidationBad(t *testing.T) { + testCases := []struct { + name string + kubeVersion string + }{ + { + name: "Before earliest version 1.21.0", + kubeVersion: "1.21.0", + }, + { + name: "Empty version", + kubeVersion: "", + }, + { + name: "Garbage", + kubeVersion: "xyzabc", + }, } -} + t.Parallel() + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + // setup logger + l := slog.New(nic_glog.New(io.Discard, &nic_glog.Options{Level: levels.LevelInfo})) + ctx := nl.ContextWithLogger(context.Background(), l) -func TestValidateNamespaces(t *testing.T) { - badNamespaces := []string{"watchns1, watchns2, watchns%$", "watchns1,watchns2,watchns%$"} - for _, badNs := range badNamespaces { - err := validateNamespaceNames(strings.Split(badNs, ",")) - if err == nil { - t.Errorf("Expected error for invalid namespace %v\n", badNs) - } - } + // setup kube client with version + clientset := fake.NewSimpleClientset() + fakeDiscovery, _ := clientset.Discovery().(*fakediscovery.FakeDiscovery) + fakeDiscovery.FakedServerVersion = &pkgversion.Info{GitVersion: tc.kubeVersion} - goodNamespaces := []string{"watched-namespace", "watched-namespace,", "watched-namespace1,watched-namespace2", "watched-namespace1, watched-namespace2"} - for _, goodNs := range goodNamespaces { - err := validateNamespaceNames(strings.Split(goodNs, ",")) - if err != nil { - t.Errorf("Error for valid namespace: %v err: %v\n", goodNs, err) - } + // run test + err := validateKubernetesVersionInfo(ctx, clientset) + if err == nil { + t.Error("Wanted an error here") + } + }) } } diff --git a/config/crd/app-protect-dos/kustomization.yaml b/config/crd/app-protect-dos/kustomization.yaml new file mode 100644 index 0000000000..f8dcb8ae0a --- /dev/null +++ b/config/crd/app-protect-dos/kustomization.yaml @@ -0,0 +1,6 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: +- ../bases/appprotectdos.f5.com_apdoslogconfs.yaml +- ../bases/appprotectdos.f5.com_apdospolicy.yaml +- ../bases/appprotectdos.f5.com_dosprotectedresources.yaml diff --git a/config/crd/app-protect-waf/kustomization.yaml b/config/crd/app-protect-waf/kustomization.yaml new file mode 100644 index 0000000000..1ee28ecbd4 --- /dev/null +++ b/config/crd/app-protect-waf/kustomization.yaml @@ -0,0 +1,6 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: +- ../bases/appprotect.f5.com_aplogconfs.yaml +- ../bases/appprotect.f5.com_appolicies.yaml +- ../bases/appprotect.f5.com_apusersigs.yaml diff --git a/config/crd/bases/appprotect.f5.com_aplogconfs.yaml b/config/crd/bases/appprotect.f5.com_aplogconfs.yaml new file mode 100644 index 0000000000..8aacce99c3 --- /dev/null +++ b/config/crd/bases/appprotect.f5.com_aplogconfs.yaml @@ -0,0 +1,83 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.13.0 + name: aplogconfs.appprotect.f5.com +spec: + group: appprotect.f5.com + names: + kind: APLogConf + listKind: APLogConfList + plural: aplogconfs + singular: aplogconf + preserveUnknownFields: false + scope: Namespaced + versions: + - name: v1beta1 + schema: + openAPIV3Schema: + description: APLogConf is the Schema for the APLogConfs API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: APLogConfSpec defines the desired state of APLogConf + properties: + content: + properties: + escaping_characters: + items: + properties: + from: + type: string + to: + type: string + type: object + type: array + format: + enum: + - splunk + - arcsight + - default + - user-defined + - grpc + type: string + format_string: + type: string + list_delimiter: + type: string + list_prefix: + type: string + list_suffix: + type: string + max_message_size: + pattern: ^([1-9]|[1-5][0-9]|6[0-4])k$ + type: string + max_request_size: + pattern: ^([1-9]|[1-9][0-9]|[1-9][0-9]{2}|[1-9][0-9]{3}|10[0-2][0-9][0-9]|[1-9]k|10k|any)$ + type: string + type: object + filter: + properties: + request_type: + enum: + - all + - illegal + - blocked + type: string + type: object + type: object + type: object + served: true + storage: true diff --git a/config/crd/bases/appprotect.f5.com_appolicies.yaml b/config/crd/bases/appprotect.f5.com_appolicies.yaml new file mode 100644 index 0000000000..4929c96247 --- /dev/null +++ b/config/crd/bases/appprotect.f5.com_appolicies.yaml @@ -0,0 +1,2172 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.13.0 + name: appolicies.appprotect.f5.com +spec: + group: appprotect.f5.com + names: + kind: APPolicy + listKind: APPolicyList + plural: appolicies + singular: appolicy + preserveUnknownFields: false + scope: Namespaced + versions: + - name: v1beta1 + schema: + openAPIV3Schema: + description: APPolicyConfig is the Schema for the APPolicyconfigs API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: APPolicySpec defines the desired state of APPolicy + properties: + modifications: + items: + properties: + action: + type: string + description: + type: string + entity: + properties: + name: + type: string + type: object + entityChanges: + properties: + type: + type: string + type: object + type: object + x-kubernetes-preserve-unknown-fields: true + type: array + modificationsReference: + properties: + link: + pattern: ^http + type: string + type: object + policy: + description: Defines the App Protect policy + properties: + applicationLanguage: + enum: + - iso-8859-10 + - iso-8859-6 + - windows-1255 + - auto-detect + - koi8-r + - gb18030 + - iso-8859-8 + - windows-1250 + - iso-8859-9 + - windows-1252 + - iso-8859-16 + - gb2312 + - iso-8859-2 + - iso-8859-5 + - windows-1257 + - windows-1256 + - iso-8859-13 + - windows-874 + - windows-1253 + - iso-8859-3 + - euc-jp + - utf-8 + - gbk + - windows-1251 + - big5 + - iso-8859-1 + - shift_jis + - euc-kr + - iso-8859-4 + - iso-8859-7 + - iso-8859-15 + type: string + blocking-settings: + properties: + evasions: + items: + properties: + description: + enum: + - '%u decoding' + - Apache whitespace + - Bad unescape + - Bare byte decoding + - Directory traversals + - IIS backslashes + - IIS Unicode codepoints + - Multiple decoding + - Multiple slashes + - Semicolon path parameters + - Trailing dot + - Trailing slash + type: string + enabled: + type: boolean + maxDecodingPasses: + type: integer + type: object + type: array + http-protocols: + items: + properties: + description: + enum: + - Unescaped space in URL + - Unparsable request content + - Several Content-Length headers + - 'POST request with Content-Length: 0' + - Null in request + - No Host header in HTTP/1.1 request + - Multiple host headers + - Host header contains IP address + - High ASCII characters in headers + - Header name with no header value + - CRLF characters before request start + - Content length should be a positive number + - Chunked request with Content-Length header + - Check maximum number of cookies + - Check maximum number of parameters + - Check maximum number of headers + - Body in GET or HEAD requests + - Bad multipart/form-data request parsing + - Bad multipart parameters parsing + - Bad HTTP version + - Bad host header value + type: string + enabled: + type: boolean + maxCookies: + maximum: 100 + minimum: 1 + type: integer + maxHeaders: + maximum: 150 + minimum: 1 + type: integer + maxParams: + maximum: 5000 + minimum: 1 + type: integer + type: object + type: array + violations: + items: + properties: + alarm: + type: boolean + block: + type: boolean + description: + type: string + name: + enum: + - VIOL_ACCESS_INVALID + - VIOL_ACCESS_MALFORMED + - VIOL_ACCESS_MISSING + - VIOL_ACCESS_UNAUTHORIZED + - VIOL_ASM_COOKIE_HIJACKING + - VIOL_ASM_COOKIE_MODIFIED + - VIOL_BLACKLISTED_IP + - VIOL_COOKIE_EXPIRED + - VIOL_COOKIE_LENGTH + - VIOL_COOKIE_MALFORMED + - VIOL_COOKIE_MODIFIED + - VIOL_CSRF + - VIOL_DATA_GUARD + - VIOL_ENCODING + - VIOL_EVASION + - VIOL_FILE_UPLOAD + - VIOL_FILE_UPLOAD_IN_BODY + - VIOL_FILETYPE + - VIOL_GRAPHQL_ERROR_RESPONSE + - VIOL_GRAPHQL_FORMAT + - VIOL_GRAPHQL_INTROSPECTION_QUERY + - VIOL_GRAPHQL_MALFORMED + - VIOL_GRPC_FORMAT + - VIOL_GRPC_MALFORMED + - VIOL_GRPC_METHOD + - VIOL_HEADER_LENGTH + - VIOL_HEADER_METACHAR + - VIOL_HEADER_REPEATED + - VIOL_HTTP_PROTOCOL + - VIOL_HTTP_RESPONSE_STATUS + - VIOL_JSON_FORMAT + - VIOL_JSON_MALFORMED + - VIOL_JSON_SCHEMA + - VIOL_MANDATORY_HEADER + - VIOL_MANDATORY_PARAMETER + - VIOL_MANDATORY_REQUEST_BODY + - VIOL_METHOD + - VIOL_PARAMETER + - VIOL_PARAMETER_ARRAY_VALUE + - VIOL_PARAMETER_DATA_TYPE + - VIOL_PARAMETER_EMPTY_VALUE + - VIOL_PARAMETER_LOCATION + - VIOL_PARAMETER_MULTIPART_NULL_VALUE + - VIOL_PARAMETER_NAME_METACHAR + - VIOL_PARAMETER_NUMERIC_VALUE + - VIOL_PARAMETER_REPEATED + - VIOL_PARAMETER_STATIC_VALUE + - VIOL_PARAMETER_VALUE_BASE64 + - VIOL_PARAMETER_VALUE_LENGTH + - VIOL_PARAMETER_VALUE_METACHAR + - VIOL_PARAMETER_VALUE_REGEXP + - VIOL_POST_DATA_LENGTH + - VIOL_QUERY_STRING_LENGTH + - VIOL_RATING_NEED_EXAMINATION + - VIOL_RATING_THREAT + - VIOL_REQUEST_LENGTH + - VIOL_REQUEST_MAX_LENGTH + - VIOL_THREAT_CAMPAIGN + - VIOL_URL + - VIOL_URL_CONTENT_TYPE + - VIOL_URL_LENGTH + - VIOL_URL_METACHAR + - VIOL_XML_FORMAT + - VIOL_XML_MALFORMED + type: string + type: object + type: array + type: object + blockingSettingReference: + properties: + link: + pattern: ^http + type: string + type: object + bot-defense: + properties: + mitigations: + properties: + anomalies: + items: + properties: + $action: + enum: + - delete + type: string + action: + enum: + - alarm + - block + - default + - detect + - ignore + type: string + name: + type: string + scoreThreshold: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object + type: array + browsers: + items: + properties: + $action: + enum: + - delete + type: string + action: + enum: + - alarm + - block + - detect + type: string + maxVersion: + maximum: 2147483647 + minimum: 0 + type: integer + minVersion: + maximum: 2147483647 + minimum: 0 + type: integer + name: + type: string + type: object + type: array + classes: + items: + properties: + action: + enum: + - alarm + - block + - detect + - ignore + type: string + name: + enum: + - browser + - malicious-bot + - suspicious-browser + - trusted-bot + - unknown + - untrusted-bot + type: string + type: object + type: array + signatures: + items: + properties: + $action: + enum: + - delete + type: string + action: + enum: + - alarm + - block + - detect + - ignore + type: string + name: + type: string + type: object + type: array + type: object + settings: + properties: + caseSensitiveHttpHeaders: + type: boolean + isEnabled: + type: boolean + type: object + type: object + browser-definitions: + items: + properties: + $action: + enum: + - delete + type: string + isUserDefined: + type: boolean + matchRegex: + type: string + matchString: + type: string + name: + type: string + type: object + type: array + caseInsensitive: + type: boolean + character-sets: + items: + properties: + characterSet: + items: + properties: + isAllowed: + type: boolean + metachar: + type: string + type: object + type: array + characterSetType: + enum: + - gwt-content + - header + - json-content + - parameter-name + - parameter-value + - plain-text-content + - url + - xml-content + type: string + type: object + type: array + characterSetReference: + properties: + link: + pattern: ^http + type: string + type: object + cookie-settings: + properties: + maximumCookieHeaderLength: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object + cookieReference: + properties: + link: + pattern: ^http + type: string + type: object + cookieSettingsReference: + properties: + link: + pattern: ^http + type: string + type: object + cookies: + items: + properties: + $action: + enum: + - delete + type: string + accessibleOnlyThroughTheHttpProtocol: + type: boolean + attackSignaturesCheck: + type: boolean + decodeValueAsBase64: + enum: + - enabled + - disabled + - required + type: string + enforcementType: + type: string + insertSameSiteAttribute: + enum: + - lax + - none + - none-value + - strict + type: string + maskValueInLogs: + type: boolean + name: + type: string + securedOverHttpsConnection: + type: boolean + signatureOverrides: + items: + properties: + enabled: + type: boolean + name: + type: string + signatureId: + type: integer + tag: + type: string + type: object + type: array + type: + enum: + - explicit + - wildcard + type: string + wildcardOrder: + type: integer + type: object + type: array + csrf-protection: + properties: + enabled: + type: boolean + expirationTimeInSeconds: + pattern: disabled|\d+ + type: string + sslOnly: + type: boolean + type: object + csrf-urls: + items: + properties: + $action: + enum: + - delete + type: string + enforcementAction: + enum: + - verify-origin + - none + type: string + method: + enum: + - GET + - POST + - any + type: string + url: + type: string + wildcardOrder: + type: integer + type: object + type: array + data-guard: + properties: + creditCardNumbers: + type: boolean + customPatterns: + type: boolean + customPatternsList: + items: + type: string + type: array + enabled: + type: boolean + enforcementMode: + enum: + - ignore-urls-in-list + - enforce-urls-in-list + type: string + enforcementUrls: + items: + type: string + type: array + firstCustomCharactersToExpose: + type: integer + lastCcnDigitsToExpose: + type: integer + lastCustomCharactersToExpose: + type: integer + lastSsnDigitsToExpose: + type: integer + maskData: + type: boolean + usSocialSecurityNumbers: + type: boolean + type: object + dataGuardReference: + properties: + link: + pattern: ^http + type: string + type: object + description: + type: string + enablePassiveMode: + type: boolean + enforcementMode: + enum: + - transparent + - blocking + type: string + enforcer-settings: + properties: + enforcerStateCookies: + properties: + httpOnlyAttribute: + type: boolean + sameSiteAttribute: + enum: + - lax + - none + - none-value + - strict + type: string + secureAttribute: + enum: + - always + - never + type: string + type: object + type: object + filetypeReference: + properties: + link: + pattern: ^http + type: string + type: object + filetypes: + items: + properties: + $action: + enum: + - delete + type: string + allowed: + type: boolean + checkPostDataLength: + type: boolean + checkQueryStringLength: + type: boolean + checkRequestLength: + type: boolean + checkUrlLength: + type: boolean + name: + type: string + postDataLength: + type: integer + queryStringLength: + type: integer + requestLength: + type: integer + responseCheck: + type: boolean + type: + enum: + - explicit + - wildcard + type: string + urlLength: + type: integer + wildcardOrder: + type: integer + type: object + type: array + fullPath: + type: string + general: + properties: + allowedResponseCodes: + items: + format: int32 + maximum: 999 + minimum: 100 + type: integer + type: array + customXffHeaders: + items: + type: string + type: array + maskCreditCardNumbersInRequest: + type: boolean + trustXff: + type: boolean + type: object + generalReference: + properties: + link: + pattern: ^http + type: string + type: object + graphql-profiles: + items: + properties: + $action: + enum: + - delete + type: string + attackSignaturesCheck: + type: boolean + defenseAttributes: + properties: + allowIntrospectionQueries: + type: boolean + maximumBatchedQueries: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + maximumQueryCost: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + maximumStructureDepth: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + maximumTotalLength: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + maximumValueLength: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + tolerateParsingWarnings: + type: boolean + type: object + description: + type: string + metacharElementCheck: + type: boolean + metacharOverrides: + items: + properties: + isAllowed: + type: boolean + metachar: + type: string + type: object + type: array + name: + type: string + responseEnforcement: + properties: + blockDisallowedPatterns: + type: boolean + disallowedPatterns: + items: + type: string + type: array + type: object + sensitiveData: + items: + properties: + parameterName: + type: string + type: object + type: array + signatureOverrides: + items: + properties: + enabled: + type: boolean + name: + type: string + signatureId: + type: integer + tag: + type: string + type: object + type: array + type: object + type: array + grpc-profiles: + items: + properties: + $action: + enum: + - delete + type: string + associateUrls: + type: boolean + attackSignaturesCheck: + type: boolean + decodeStringValuesAsBase64: + enum: + - disabled + - enabled + type: string + defenseAttributes: + properties: + allowUnknownFields: + type: boolean + maximumDataLength: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object + description: + type: string + hasIdlFiles: + type: boolean + idlFiles: + items: + properties: + idlFile: + properties: + contents: + type: string + fileName: + type: string + isBase64: + type: boolean + type: object + importUrl: + type: string + isPrimary: + type: boolean + primaryIdlFileName: + type: string + type: object + type: array + metacharCheck: + type: boolean + metacharElementCheck: + type: boolean + name: + type: string + signatureOverrides: + items: + properties: + enabled: + type: boolean + name: + type: string + signatureId: + type: integer + tag: + type: string + type: object + type: array + type: object + type: array + header-settings: + properties: + maximumHttpHeaderLength: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object + headerReference: + properties: + link: + pattern: ^http + type: string + type: object + headerSettingsReference: + properties: + link: + pattern: ^http + type: string + type: object + headers: + items: + properties: + $action: + enum: + - delete + type: string + allowRepeatedOccurrences: + type: boolean + base64Decoding: + type: boolean + checkSignatures: + type: boolean + decodeValueAsBase64: + enum: + - enabled + - disabled + - required + type: string + htmlNormalization: + type: boolean + mandatory: + type: boolean + maskValueInLogs: + type: boolean + name: + type: string + normalizationViolations: + type: boolean + percentDecoding: + type: boolean + signatureOverrides: + items: + properties: + enabled: + type: boolean + name: + type: string + signatureId: + type: integer + tag: + type: string + type: object + type: array + type: + enum: + - explicit + - wildcard + type: string + urlNormalization: + type: boolean + wildcardOrder: + type: integer + type: object + type: array + host-names: + items: + properties: + $action: + enum: + - delete + type: string + includeSubdomains: + type: boolean + name: + type: string + type: object + type: array + idl-files: + items: + properties: + contents: + type: string + fileName: + type: string + isBase64: + type: boolean + type: object + type: array + json-profiles: + items: + properties: + $action: + enum: + - delete + type: string + attackSignaturesCheck: + type: boolean + defenseAttributes: + properties: + maximumArrayLength: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + maximumStructureDepth: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + maximumTotalLengthOfJSONData: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + maximumValueLength: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + tolerateJSONParsingWarnings: + type: boolean + type: object + description: + type: string + handleJsonValuesAsParameters: + type: boolean + hasValidationFiles: + type: boolean + metacharOverrides: + items: + properties: + isAllowed: + type: boolean + metachar: + type: string + type: object + type: array + name: + type: string + signatureOverrides: + items: + properties: + enabled: + type: boolean + name: + type: string + signatureId: + type: integer + tag: + type: string + type: object + type: array + validationFiles: + items: + properties: + importUrl: + type: string + isPrimary: + type: boolean + jsonValidationFile: + properties: + $action: + enum: + - delete + type: string + contents: + type: string + fileName: + type: string + isBase64: + type: boolean + type: object + type: object + type: array + type: object + type: array + json-validation-files: + items: + properties: + $action: + enum: + - delete + type: string + contents: + type: string + fileName: + type: string + isBase64: + type: boolean + type: object + type: array + jsonProfileReference: + properties: + link: + pattern: ^http + type: string + type: object + jsonValidationFileReference: + properties: + link: + pattern: ^http + type: string + type: object + methodReference: + properties: + link: + pattern: ^http + type: string + type: object + methods: + items: + properties: + $action: + enum: + - delete + type: string + name: + type: string + type: object + type: array + name: + type: string + open-api-files: + items: + properties: + link: + pattern: ^http + type: string + type: object + type: array + parameterReference: + properties: + link: + pattern: ^http + type: string + type: object + parameters: + items: + properties: + $action: + enum: + - delete + type: string + allowEmptyValue: + type: boolean + allowRepeatedParameterName: + type: boolean + arraySerializationFormat: + enum: + - csv + - form + - label + - matrix + - multi + - multipart + - pipe + - ssv + - tsv + type: string + attackSignaturesCheck: + type: boolean + checkMaxValue: + type: boolean + checkMaxValueLength: + type: boolean + checkMetachars: + type: boolean + checkMinValue: + type: boolean + checkMinValueLength: + type: boolean + checkMultipleOfValue: + type: boolean + contentProfile: + properties: + name: + type: string + type: object + dataType: + enum: + - alpha-numeric + - binary + - boolean + - decimal + - email + - integer + - none + - phone + type: string + decodeValueAsBase64: + enum: + - enabled + - disabled + - required + type: string + disallowFileUploadOfExecutables: + type: boolean + enableRegularExpression: + type: boolean + exclusiveMax: + type: boolean + exclusiveMin: + type: boolean + isBase64: + type: boolean + isCookie: + type: boolean + isHeader: + type: boolean + level: + enum: + - global + - url + type: string + mandatory: + type: boolean + maximumLength: + type: integer + maximumValue: + type: integer + metacharsOnParameterValueCheck: + type: boolean + minimumLength: + type: integer + minimumValue: + type: integer + multipleOf: + type: integer + name: + type: string + nameMetacharOverrides: + items: + properties: + isAllowed: + type: boolean + metachar: + type: string + type: object + type: array + objectSerializationStyle: + type: string + parameterEnumValues: + items: + type: string + type: array + parameterLocation: + enum: + - any + - cookie + - form-data + - header + - path + - query + type: string + regularExpression: + type: string + sensitiveParameter: + type: boolean + signatureOverrides: + items: + properties: + enabled: + type: boolean + name: + type: string + signatureId: + type: integer + tag: + type: string + type: object + type: array + staticValues: + type: string + type: + enum: + - explicit + - wildcard + type: string + url: + properties: + method: + enum: + - ACL + - BCOPY + - BDELETE + - BMOVE + - BPROPFIND + - BPROPPATCH + - CHECKIN + - CHECKOUT + - CONNECT + - COPY + - DELETE + - GET + - HEAD + - LINK + - LOCK + - MERGE + - MKCOL + - MKWORKSPACE + - MOVE + - NOTIFY + - OPTIONS + - PATCH + - POLL + - POST + - PROPFIND + - PROPPATCH + - PUT + - REPORT + - RPC_IN_DATA + - RPC_OUT_DATA + - SEARCH + - SUBSCRIBE + - TRACE + - TRACK + - UNLINK + - UNLOCK + - UNSUBSCRIBE + - VERSION_CONTROL + - X-MS-ENUMATTS + - '*' + type: string + name: + type: string + protocol: + enum: + - http + - https + type: string + type: + enum: + - explicit + - wildcard + type: string + type: object + valueMetacharOverrides: + items: + properties: + isAllowed: + type: boolean + metachar: + type: string + type: object + type: array + valueType: + enum: + - array + - auto-detect + - dynamic-content + - dynamic-parameter-name + - ignore + - json + - object + - openapi-array + - static-content + - user-input + - xml + type: string + wildcardOrder: + type: integer + type: object + type: array + response-pages: + items: + properties: + ajaxActionType: + enum: + - alert-popup + - custom + - redirect + type: string + ajaxCustomContent: + type: string + ajaxEnabled: + type: boolean + ajaxPopupMessage: + type: string + ajaxRedirectUrl: + type: string + grpcStatusCode: + pattern: ABORTED|ALREADY_EXISTS|CANCELLED|DATA_LOSS|DEADLINE_EXCEEDED|FAILED_PRECONDITION|INTERNAL|INVALID_ARGUMENT|NOT_FOUND|OK|OUT_OF_RANGE|PERMISSION_DENIED|RESOURCE_EXHAUSTED|UNAUTHENTICATED|UNAVAILABLE|UNIMPLEMENTED|UNKNOWN|d+ + type: string + grpcStatusMessage: + type: string + responseActionType: + enum: + - custom + - default + - erase-cookies + - redirect + - soap-fault + type: string + responseContent: + type: string + responseHeader: + type: string + responsePageType: + enum: + - ajax + - ajax-login + - captcha + - captcha-fail + - default + - failed-login-honeypot + - failed-login-honeypot-ajax + - hijack + - leaked-credentials + - leaked-credentials-ajax + - mobile + - persistent-flow + - xml + - grpc + type: string + responseRedirectUrl: + type: string + type: object + type: array + responsePageReference: + properties: + link: + pattern: ^http + type: string + type: object + sensitive-parameters: + items: + properties: + $action: + enum: + - delete + type: string + name: + type: string + type: object + type: array + sensitiveParameterReference: + properties: + link: + pattern: ^http + type: string + type: object + server-technologies: + items: + properties: + $action: + enum: + - delete + type: string + serverTechnologyName: + enum: + - Jenkins + - SharePoint + - Oracle Application Server + - Python + - Oracle Identity Manager + - Spring Boot + - CouchDB + - SQLite + - Handlebars + - Mustache + - Prototype + - Zend + - Redis + - Underscore.js + - Ember.js + - ZURB Foundation + - ef.js + - Vue.js + - UIKit + - TYPO3 CMS + - RequireJS + - React + - MooTools + - Laravel + - GraphQL + - Google Web Toolkit + - Express.js + - CodeIgniter + - Backbone.js + - AngularJS + - JavaScript + - Nginx + - Jetty + - Joomla + - JavaServer Faces (JSF) + - Ruby + - MongoDB + - Django + - Node.js + - Citrix + - JBoss + - Elasticsearch + - Apache Struts + - XML + - PostgreSQL + - IBM DB2 + - Sybase/ASE + - CGI + - Proxy Servers + - SSI (Server Side Includes) + - Cisco + - Novell + - Macromedia JRun + - BEA Systems WebLogic Server + - Lotus Domino + - MySQL + - Oracle + - Microsoft SQL Server + - PHP + - Outlook Web Access + - Apache/NCSA HTTP Server + - Apache Tomcat + - WordPress + - Macromedia ColdFusion + - Unix/Linux + - Microsoft Windows + - ASP.NET + - Front Page Server Extensions (FPSE) + - IIS + - WebDAV + - ASP + - Java Servlets/JSP + - jQuery + type: string + type: object + type: array + serverTechnologyReference: + properties: + link: + pattern: ^http + type: string + type: object + signature-requirements: + items: + properties: + $action: + enum: + - delete + type: string + tag: + type: string + type: object + type: array + signature-sets: + items: + properties: + $action: + enum: + - delete + type: string + alarm: + type: boolean + block: + type: boolean + name: + type: string + type: object + x-kubernetes-preserve-unknown-fields: true + type: array + signature-settings: + properties: + attackSignatureFalsePositiveMode: + enum: + - detect + - detect-and-allow + - disabled + type: string + minimumAccuracyForAutoAddedSignatures: + enum: + - high + - low + - medium + type: string + type: object + signatureReference: + properties: + link: + pattern: ^http + type: string + type: object + signatureSetReference: + properties: + link: + pattern: ^http + type: string + type: object + signatureSettingReference: + properties: + link: + pattern: ^http + type: string + type: object + signatures: + items: + properties: + enabled: + type: boolean + name: + type: string + signatureId: + type: integer + tag: + type: string + type: object + type: array + softwareVersion: + type: string + template: + properties: + name: + type: string + type: object + threat-campaigns: + items: + properties: + isEnabled: + type: boolean + name: + type: string + type: object + type: array + threatCampaignReference: + properties: + link: + pattern: ^http + type: string + type: object + urlReference: + properties: + link: + pattern: ^http + type: string + type: object + urls: + items: + properties: + $action: + enum: + - delete + type: string + allowRenderingInFrames: + enum: + - never + - only-same + type: string + allowRenderingInFramesOnlyFrom: + type: string + attackSignaturesCheck: + type: boolean + clickjackingProtection: + type: boolean + description: + type: string + disallowFileUploadOfExecutables: + type: boolean + html5CrossOriginRequestsEnforcement: + properties: + allowOriginsEnforcementMode: + enum: + - replace-with + - unmodified + type: string + checkAllowedMethods: + type: boolean + crossDomainAllowedOrigin: + items: + properties: + includeSubDomains: + type: boolean + originName: + type: string + originPort: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + originProtocol: + enum: + - http + - http/https + - https + type: string + type: object + type: array + enforcementMode: + enum: + - disabled + - enforce + type: string + type: object + isAllowed: + type: boolean + mandatoryBody: + type: boolean + metacharOverrides: + items: + properties: + isAllowed: + type: boolean + metachar: + type: string + type: object + type: array + metacharsOnUrlCheck: + type: boolean + method: + enum: + - ACL + - BCOPY + - BDELETE + - BMOVE + - BPROPFIND + - BPROPPATCH + - CHECKIN + - CHECKOUT + - CONNECT + - COPY + - DELETE + - GET + - HEAD + - LINK + - LOCK + - MERGE + - MKCOL + - MKWORKSPACE + - MOVE + - NOTIFY + - OPTIONS + - PATCH + - POLL + - POST + - PROPFIND + - PROPPATCH + - PUT + - REPORT + - RPC_IN_DATA + - RPC_OUT_DATA + - SEARCH + - SUBSCRIBE + - TRACE + - TRACK + - UNLINK + - UNLOCK + - UNSUBSCRIBE + - VERSION_CONTROL + - X-MS-ENUMATTS + - '*' + type: string + methodOverrides: + items: + properties: + allowed: + type: boolean + method: + enum: + - ACL + - BCOPY + - BDELETE + - BMOVE + - BPROPFIND + - BPROPPATCH + - CHECKIN + - CHECKOUT + - CONNECT + - COPY + - DELETE + - GET + - HEAD + - LINK + - LOCK + - MERGE + - MKCOL + - MKWORKSPACE + - MOVE + - NOTIFY + - OPTIONS + - PATCH + - POLL + - POST + - PROPFIND + - PROPPATCH + - PUT + - REPORT + - RPC_IN_DATA + - RPC_OUT_DATA + - SEARCH + - SUBSCRIBE + - TRACE + - TRACK + - UNLINK + - UNLOCK + - UNSUBSCRIBE + - VERSION_CONTROL + - X-MS-ENUMATTS + type: string + type: object + type: array + methodsOverrideOnUrlCheck: + type: boolean + name: + type: string + operationId: + type: string + positionalParameters: + items: + properties: + parameter: + properties: + $action: + enum: + - delete + type: string + allowEmptyValue: + type: boolean + allowRepeatedParameterName: + type: boolean + arraySerializationFormat: + enum: + - csv + - form + - label + - matrix + - multi + - multipart + - pipe + - ssv + - tsv + type: string + attackSignaturesCheck: + type: boolean + checkMaxValue: + type: boolean + checkMaxValueLength: + type: boolean + checkMetachars: + type: boolean + checkMinValue: + type: boolean + checkMinValueLength: + type: boolean + checkMultipleOfValue: + type: boolean + contentProfile: + properties: + name: + type: string + type: object + dataType: + enum: + - alpha-numeric + - binary + - boolean + - decimal + - email + - integer + - none + - phone + type: string + decodeValueAsBase64: + enum: + - enabled + - disabled + - required + type: string + disallowFileUploadOfExecutables: + type: boolean + enableRegularExpression: + type: boolean + exclusiveMax: + type: boolean + exclusiveMin: + type: boolean + isBase64: + type: boolean + isCookie: + type: boolean + isHeader: + type: boolean + level: + enum: + - global + - url + type: string + mandatory: + type: boolean + maximumLength: + type: integer + maximumValue: + type: integer + metacharsOnParameterValueCheck: + type: boolean + minimumLength: + type: integer + minimumValue: + type: integer + multipleOf: + type: integer + name: + type: string + nameMetacharOverrides: + items: + properties: + isAllowed: + type: boolean + metachar: + type: string + type: object + type: array + objectSerializationStyle: + type: string + parameterEnumValues: + items: + type: string + type: array + parameterLocation: + enum: + - any + - cookie + - form-data + - header + - path + - query + type: string + regularExpression: + type: string + sensitiveParameter: + type: boolean + signatureOverrides: + items: + properties: + enabled: + type: boolean + name: + type: string + signatureId: + type: integer + tag: + type: string + type: object + type: array + staticValues: + type: string + type: + enum: + - explicit + - wildcard + type: string + url: + properties: + method: + enum: + - ACL + - BCOPY + - BDELETE + - BMOVE + - BPROPFIND + - BPROPPATCH + - CHECKIN + - CHECKOUT + - CONNECT + - COPY + - DELETE + - GET + - HEAD + - LINK + - LOCK + - MERGE + - MKCOL + - MKWORKSPACE + - MOVE + - NOTIFY + - OPTIONS + - PATCH + - POLL + - POST + - PROPFIND + - PROPPATCH + - PUT + - REPORT + - RPC_IN_DATA + - RPC_OUT_DATA + - SEARCH + - SUBSCRIBE + - TRACE + - TRACK + - UNLINK + - UNLOCK + - UNSUBSCRIBE + - VERSION_CONTROL + - X-MS-ENUMATTS + - '*' + type: string + name: + type: string + protocol: + enum: + - http + - https + type: string + type: + enum: + - explicit + - wildcard + type: string + type: object + valueMetacharOverrides: + items: + properties: + isAllowed: + type: boolean + metachar: + type: string + type: object + type: array + valueType: + enum: + - array + - auto-detect + - dynamic-content + - dynamic-parameter-name + - ignore + - json + - object + - openapi-array + - static-content + - user-input + - xml + type: string + wildcardOrder: + type: integer + type: object + urlSegmentIndex: + type: integer + type: object + type: array + protocol: + enum: + - http + - https + type: string + signatureOverrides: + items: + properties: + enabled: + type: boolean + name: + type: string + signatureId: + type: integer + tag: + type: string + type: object + type: array + type: + enum: + - explicit + - wildcard + type: string + urlContentProfiles: + items: + properties: + contentProfile: + properties: + name: + type: string + type: object + headerName: + type: string + headerOrder: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + headerValue: + type: string + name: + type: string + type: + enum: + - apply-content-signatures + - apply-value-and-content-signatures + - disallow + - do-nothing + - form-data + - gwt + - json + - xml + - grpc + type: string + type: object + type: array + wildcardOrder: + type: integer + type: object + type: array + whitelist-ips: + items: + properties: + $action: + enum: + - delete + type: string + blockRequests: + enum: + - always + - never + - policy-default + type: string + ipAddress: + pattern: '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}' + type: string + ipMask: + pattern: '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}' + type: string + neverLogRequests: + type: boolean + type: object + type: array + whitelistIpReference: + properties: + link: + pattern: ^http + type: string + type: object + xml-profiles: + items: + properties: + $action: + enum: + - delete + type: string + attackSignaturesCheck: + type: boolean + defenseAttributes: + properties: + allowCDATA: + type: boolean + allowDTDs: + type: boolean + allowExternalReferences: + type: boolean + allowProcessingInstructions: + type: boolean + maximumAttributeValueLength: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + maximumAttributesPerElement: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + maximumChildrenPerElement: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + maximumDocumentDepth: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + maximumDocumentSize: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + maximumElements: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + maximumNSDeclarations: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + maximumNameLength: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + maximumNamespaceLength: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + tolerateCloseTagShorthand: + type: boolean + tolerateLeadingWhiteSpace: + type: boolean + tolerateNumericNames: + type: boolean + type: object + description: + type: string + enableWss: + type: boolean + followSchemaLinks: + type: boolean + name: + type: string + signatureOverrides: + items: + properties: + enabled: + type: boolean + name: + type: string + signatureId: + type: integer + tag: + type: string + type: object + type: array + useXmlResponsePage: + type: boolean + type: object + type: array + xml-validation-files: + items: + properties: + $action: + enum: + - delete + type: string + contents: + type: string + fileName: + type: string + isBase64: + type: boolean + type: object + type: array + xmlProfileReference: + properties: + link: + pattern: ^http + type: string + type: object + xmlValidationFileReference: + properties: + link: + pattern: ^http + type: string + type: object + type: object + type: object + type: object + served: true + storage: true diff --git a/config/crd/bases/appprotect.f5.com_apusersigs.yaml b/config/crd/bases/appprotect.f5.com_apusersigs.yaml new file mode 100644 index 0000000000..6d71ed6336 --- /dev/null +++ b/config/crd/bases/appprotect.f5.com_apusersigs.yaml @@ -0,0 +1,98 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.13.0 + name: apusersigs.appprotect.f5.com +spec: + group: appprotect.f5.com + names: + kind: APUserSig + listKind: APUserSigList + plural: apusersigs + singular: apusersig + preserveUnknownFields: false + scope: Namespaced + versions: + - name: v1beta1 + schema: + openAPIV3Schema: + description: APUserSig is the Schema for the apusersigs API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: APUserSigSpec defines the desired state of APUserSig + properties: + properties: + type: string + signatures: + items: + properties: + accuracy: + enum: + - high + - medium + - low + type: string + attackType: + properties: + name: + type: string + type: object + description: + type: string + name: + type: string + references: + properties: + type: + enum: + - bugtraq + - cve + - nessus + - url + type: string + value: + type: string + type: object + risk: + enum: + - high + - medium + - low + type: string + rule: + type: string + signatureType: + enum: + - request + - response + type: string + systems: + items: + properties: + name: + type: string + type: object + type: array + type: object + type: array + softwareVersion: + type: string + tag: + type: string + type: object + type: object + served: true + storage: true diff --git a/deployments/common/crds/appprotectdos.f5.com_apdoslogconfs.yaml b/config/crd/bases/appprotectdos.f5.com_apdoslogconfs.yaml similarity index 100% rename from deployments/common/crds/appprotectdos.f5.com_apdoslogconfs.yaml rename to config/crd/bases/appprotectdos.f5.com_apdoslogconfs.yaml diff --git a/deployments/common/crds/appprotectdos.f5.com_apdospolicy.yaml b/config/crd/bases/appprotectdos.f5.com_apdospolicy.yaml similarity index 100% rename from deployments/common/crds/appprotectdos.f5.com_apdospolicy.yaml rename to config/crd/bases/appprotectdos.f5.com_apdospolicy.yaml diff --git a/config/crd/bases/appprotectdos.f5.com_dosprotectedresources.yaml b/config/crd/bases/appprotectdos.f5.com_dosprotectedresources.yaml new file mode 100644 index 0000000000..7e6b5fef6c --- /dev/null +++ b/config/crd/bases/appprotectdos.f5.com_dosprotectedresources.yaml @@ -0,0 +1,113 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.17.0 + name: dosprotectedresources.appprotectdos.f5.com +spec: + group: appprotectdos.f5.com + names: + kind: DosProtectedResource + listKind: DosProtectedResourceList + plural: dosprotectedresources + shortNames: + - pr + singular: dosprotectedresource + scope: Namespaced + versions: + - name: v1beta1 + schema: + openAPIV3Schema: + description: DosProtectedResource defines a Dos protected resource. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: DosProtectedResourceSpec defines the properties and values + a DosProtectedResource can have. + properties: + allowList: + description: AllowList is a list of allowed IPs and subnet masks + items: + description: AllowListEntry represents an IP address and a subnet + mask. + properties: + ipWithMask: + type: string + type: object + type: array + apDosMonitor: + description: 'ApDosMonitor is how NGINX App Protect DoS monitors the + stress level of the protected object. The monitor requests are sent + from localhost (127.0.0.1). Default value: URI - None, protocol + - http1, timeout - NGINX App Protect DoS default.' + properties: + protocol: + description: Protocol determines if the server listens on http1 + / http2 / grpc / websocket. The default is http1. + enum: + - http1 + - http2 + - grpc + - websocket + type: string + timeout: + description: Timeout determines how long (in seconds) should NGINX + App Protect DoS wait for a response. Default is 10 seconds for + http1/http2 and 5 seconds for grpc. + format: int64 + type: integer + uri: + description: 'URI is the destination to the desired protected + object in the nginx.conf:' + type: string + type: object + apDosPolicy: + description: ApDosPolicy is the namespace/name of a ApDosPolicy resource + type: string + dosAccessLogDest: + description: DosAccessLogDest is the network address for the access + logs + type: string + dosSecurityLog: + description: DosSecurityLog defines the security log of the DosProtectedResource. + properties: + apDosLogConf: + description: ApDosLogConf is the namespace/name of a APDosLogConf + resource + type: string + dosLogDest: + description: DosLogDest is the network address of a logging service, + can be either IP or DNS name. + type: string + enable: + description: Enable enables the security logging feature if set + to true + type: boolean + type: object + enable: + description: Enable enables the DOS feature if set to true + type: boolean + name: + description: Name is the name of protected object, max of 63 characters. + type: string + type: object + type: object + served: true + storage: true diff --git a/config/crd/bases/externaldns.nginx.org_dnsendpoints.yaml b/config/crd/bases/externaldns.nginx.org_dnsendpoints.yaml new file mode 100644 index 0000000000..6a2f3a625a --- /dev/null +++ b/config/crd/bases/externaldns.nginx.org_dnsendpoints.yaml @@ -0,0 +1,97 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.17.0 + name: dnsendpoints.externaldns.nginx.org +spec: + group: externaldns.nginx.org + names: + kind: DNSEndpoint + listKind: DNSEndpointList + plural: dnsendpoints + singular: dnsendpoint + scope: Namespaced + versions: + - name: v1 + schema: + openAPIV3Schema: + description: DNSEndpoint is the CRD wrapper for Endpoint + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: DNSEndpointSpec holds information about endpoints. + properties: + endpoints: + items: + description: Endpoint describes DNS Endpoint. + properties: + dnsName: + description: The hostname for the DNS record + type: string + labels: + additionalProperties: + type: string + description: Labels stores labels defined for the Endpoint + type: object + providerSpecific: + description: ProviderSpecific stores provider specific config + items: + description: ProviderSpecificProperty represents provider + specific config property. + properties: + name: + description: Name of the property + type: string + value: + description: Value of the property + type: string + type: object + type: array + recordTTL: + description: TTL for the record + format: int64 + type: integer + recordType: + description: RecordType type of record, e.g. CNAME, A, SRV, + TXT, MX + type: string + targets: + description: The targets the DNS service points to + items: + type: string + type: array + type: object + type: array + type: object + status: + description: DNSEndpointStatus represents generation observed by the external + dns controller. + properties: + observedGeneration: + description: The generation observed by by the external-dns controller. + format: int64 + type: integer + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crd/bases/k8s.nginx.org_globalconfigurations.yaml b/config/crd/bases/k8s.nginx.org_globalconfigurations.yaml new file mode 100644 index 0000000000..feef849af0 --- /dev/null +++ b/config/crd/bases/k8s.nginx.org_globalconfigurations.yaml @@ -0,0 +1,66 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.17.0 + name: globalconfigurations.k8s.nginx.org +spec: + group: k8s.nginx.org + names: + kind: GlobalConfiguration + listKind: GlobalConfigurationList + plural: globalconfigurations + shortNames: + - gc + singular: globalconfiguration + scope: Namespaced + versions: + - name: v1 + schema: + openAPIV3Schema: + description: GlobalConfiguration defines the GlobalConfiguration resource. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: GlobalConfigurationSpec is the spec of the GlobalConfiguration + resource. + properties: + listeners: + items: + description: Listener defines a listener. + properties: + ipv4: + type: string + ipv6: + type: string + name: + type: string + port: + type: integer + protocol: + type: string + ssl: + type: boolean + type: object + type: array + type: object + type: object + served: true + storage: true diff --git a/config/crd/bases/k8s.nginx.org_policies.yaml b/config/crd/bases/k8s.nginx.org_policies.yaml new file mode 100644 index 0000000000..600c5a7ee2 --- /dev/null +++ b/config/crd/bases/k8s.nginx.org_policies.yaml @@ -0,0 +1,252 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.17.0 + name: policies.k8s.nginx.org +spec: + group: k8s.nginx.org + names: + kind: Policy + listKind: PolicyList + plural: policies + shortNames: + - pol + singular: policy + scope: Namespaced + versions: + - additionalPrinterColumns: + - description: Current state of the Policy. If the resource has a valid status, + it means it has been validated and accepted by the Ingress Controller. + jsonPath: .status.state + name: State + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1 + schema: + openAPIV3Schema: + description: Policy defines a Policy for VirtualServer and VirtualServerRoute + resources. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: |- + PolicySpec is the spec of the Policy resource. + The spec includes multiple fields, where each field represents a different policy. + Only one policy (field) is allowed. + properties: + accessControl: + description: AccessControl defines an access policy based on the source + IP of a request. + properties: + allow: + items: + type: string + type: array + deny: + items: + type: string + type: array + type: object + apiKey: + description: APIKey defines an API Key policy. + properties: + clientSecret: + type: string + suppliedIn: + description: SuppliedIn defines the locations API Key should be + supplied in. + properties: + header: + items: + type: string + type: array + query: + items: + type: string + type: array + type: object + type: object + basicAuth: + description: BasicAuth holds HTTP Basic authentication configuration + properties: + realm: + type: string + secret: + type: string + type: object + egressMTLS: + description: EgressMTLS defines an Egress MTLS policy. + properties: + ciphers: + type: string + protocols: + type: string + serverName: + type: boolean + sessionReuse: + type: boolean + sslName: + type: string + tlsSecret: + type: string + trustedCertSecret: + type: string + verifyDepth: + type: integer + verifyServer: + type: boolean + type: object + ingressClassName: + type: string + ingressMTLS: + description: IngressMTLS defines an Ingress MTLS policy. + properties: + clientCertSecret: + type: string + crlFileName: + type: string + verifyClient: + type: string + verifyDepth: + type: integer + type: object + jwt: + description: JWTAuth holds JWT authentication configuration. + properties: + jwksURI: + type: string + keyCache: + type: string + realm: + type: string + secret: + type: string + token: + type: string + type: object + oidc: + description: OIDC defines an Open ID Connect policy. + properties: + accessTokenEnable: + type: boolean + authEndpoint: + type: string + authExtraArgs: + items: + type: string + type: array + clientID: + type: string + clientSecret: + type: string + endSessionEndpoint: + type: string + jwksURI: + type: string + postLogoutRedirectURI: + type: string + redirectURI: + type: string + scope: + type: string + tokenEndpoint: + type: string + zoneSyncLeeway: + type: integer + type: object + rateLimit: + description: RateLimit defines a rate limit policy. + properties: + burst: + type: integer + delay: + type: integer + dryRun: + type: boolean + key: + type: string + logLevel: + type: string + noDelay: + type: boolean + rate: + type: string + rejectCode: + type: integer + scale: + type: boolean + zoneSize: + type: string + type: object + waf: + description: WAF defines an WAF policy. + properties: + apBundle: + type: string + apPolicy: + type: string + enable: + type: boolean + securityLog: + description: SecurityLog defines the security log of a WAF policy. + properties: + apLogBundle: + type: string + apLogConf: + type: string + enable: + type: boolean + logDest: + type: string + type: object + securityLogs: + items: + description: SecurityLog defines the security log of a WAF policy. + properties: + apLogBundle: + type: string + apLogConf: + type: string + enable: + type: boolean + logDest: + type: string + type: object + type: array + type: object + type: object + status: + description: PolicyStatus is the status of the policy resource + properties: + message: + type: string + reason: + type: string + state: + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crd/bases/k8s.nginx.org_transportservers.yaml b/config/crd/bases/k8s.nginx.org_transportservers.yaml new file mode 100644 index 0000000000..e3ce300b5b --- /dev/null +++ b/config/crd/bases/k8s.nginx.org_transportservers.yaml @@ -0,0 +1,175 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.17.0 + name: transportservers.k8s.nginx.org +spec: + group: k8s.nginx.org + names: + kind: TransportServer + listKind: TransportServerList + plural: transportservers + shortNames: + - ts + singular: transportserver + scope: Namespaced + versions: + - additionalPrinterColumns: + - description: Current state of the TransportServer. If the resource has a valid + status, it means it has been validated and accepted by the Ingress Controller. + jsonPath: .status.state + name: State + type: string + - jsonPath: .status.reason + name: Reason + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1 + schema: + openAPIV3Schema: + description: TransportServer defines the TransportServer resource. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: TransportServerSpec is the spec of the TransportServer resource. + properties: + action: + description: TransportServerAction defines an action. + properties: + pass: + type: string + type: object + host: + type: string + ingressClassName: + type: string + listener: + description: TransportServerListener defines a listener for a TransportServer. + properties: + name: + type: string + protocol: + type: string + type: object + serverSnippets: + type: string + sessionParameters: + description: SessionParameters defines session parameters. + properties: + timeout: + type: string + type: object + streamSnippets: + type: string + tls: + description: TransportServerTLS defines TransportServerTLS configuration + for a TransportServer. + properties: + secret: + type: string + type: object + upstreamParameters: + description: UpstreamParameters defines parameters for an upstream. + properties: + connectTimeout: + type: string + nextUpstream: + type: boolean + nextUpstreamTimeout: + type: string + nextUpstreamTries: + type: integer + udpRequests: + type: integer + udpResponses: + type: integer + type: object + upstreams: + items: + description: TransportServerUpstream defines an upstream. + properties: + backup: + type: string + backupPort: + type: integer + failTimeout: + type: string + healthCheck: + description: TransportServerHealthCheck defines the parameters + for active Upstream HealthChecks. + properties: + enable: + type: boolean + fails: + type: integer + interval: + type: string + jitter: + type: string + match: + description: TransportServerMatch defines the parameters + of a custom health check. + properties: + expect: + type: string + send: + type: string + type: object + passes: + type: integer + port: + type: integer + timeout: + type: string + type: object + loadBalancingMethod: + type: string + maxConns: + type: integer + maxFails: + type: integer + name: + type: string + port: + type: integer + service: + type: string + type: object + type: array + type: object + status: + description: TransportServerStatus defines the status for the TransportServer + resource. + properties: + message: + type: string + reason: + type: string + state: + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crd/bases/k8s.nginx.org_virtualserverroutes.yaml b/config/crd/bases/k8s.nginx.org_virtualserverroutes.yaml new file mode 100644 index 0000000000..192b7f3f4e --- /dev/null +++ b/config/crd/bases/k8s.nginx.org_virtualserverroutes.yaml @@ -0,0 +1,729 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.17.0 + name: virtualserverroutes.k8s.nginx.org +spec: + group: k8s.nginx.org + names: + kind: VirtualServerRoute + listKind: VirtualServerRouteList + plural: virtualserverroutes + shortNames: + - vsr + singular: virtualserverroute + scope: Namespaced + versions: + - additionalPrinterColumns: + - description: Current state of the VirtualServerRoute. If the resource has a + valid status, it means it has been validated and accepted by the Ingress Controller. + jsonPath: .status.state + name: State + type: string + - jsonPath: .spec.host + name: Host + type: string + - jsonPath: .status.externalEndpoints[*].ip + name: IP + type: string + - jsonPath: .status.externalEndpoints[*].hostname + name: ExternalHostname + priority: 1 + type: string + - jsonPath: .status.externalEndpoints[*].ports + name: Ports + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1 + schema: + openAPIV3Schema: + description: VirtualServerRoute defines the VirtualServerRoute resource. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: VirtualServerRouteSpec is the spec of the VirtualServerRoute + resource. + properties: + host: + type: string + ingressClassName: + type: string + subroutes: + items: + description: Route defines a route. + properties: + action: + description: Action defines an action. + properties: + pass: + type: string + proxy: + description: ActionProxy defines a proxy in an Action. + properties: + requestHeaders: + description: ProxyRequestHeaders defines the request + headers manipulation in an ActionProxy. + properties: + pass: + type: boolean + set: + items: + description: Header defines an HTTP Header. + properties: + name: + type: string + value: + type: string + type: object + type: array + type: object + responseHeaders: + description: ProxyResponseHeaders defines the response + headers manipulation in an ActionProxy. + properties: + add: + items: + description: AddHeader defines an HTTP Header + with an optional Always field to use with the + add_header NGINX directive. + properties: + always: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + hide: + items: + type: string + type: array + ignore: + items: + type: string + type: array + pass: + items: + type: string + type: array + type: object + rewritePath: + type: string + upstream: + type: string + type: object + redirect: + description: ActionRedirect defines a redirect in an Action. + properties: + code: + type: integer + url: + type: string + type: object + return: + description: ActionReturn defines a return in an Action. + properties: + body: + type: string + code: + type: integer + headers: + items: + description: Header defines an HTTP Header. + properties: + name: + type: string + value: + type: string + type: object + type: array + type: + type: string + type: object + type: object + dos: + type: string + errorPages: + items: + description: ErrorPage defines an ErrorPage in a Route. + properties: + codes: + items: + type: integer + type: array + redirect: + description: ErrorPageRedirect defines a redirect for + an ErrorPage. + properties: + code: + type: integer + url: + type: string + type: object + return: + description: ErrorPageReturn defines a return for an ErrorPage. + properties: + body: + type: string + code: + type: integer + headers: + items: + description: Header defines an HTTP Header. + properties: + name: + type: string + value: + type: string + type: object + type: array + type: + type: string + type: object + type: object + type: array + location-snippets: + type: string + matches: + items: + description: Match defines a match. + properties: + action: + description: Action defines an action. + properties: + pass: + type: string + proxy: + description: ActionProxy defines a proxy in an Action. + properties: + requestHeaders: + description: ProxyRequestHeaders defines the request + headers manipulation in an ActionProxy. + properties: + pass: + type: boolean + set: + items: + description: Header defines an HTTP Header. + properties: + name: + type: string + value: + type: string + type: object + type: array + type: object + responseHeaders: + description: ProxyResponseHeaders defines the + response headers manipulation in an ActionProxy. + properties: + add: + items: + description: AddHeader defines an HTTP Header + with an optional Always field to use with + the add_header NGINX directive. + properties: + always: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + hide: + items: + type: string + type: array + ignore: + items: + type: string + type: array + pass: + items: + type: string + type: array + type: object + rewritePath: + type: string + upstream: + type: string + type: object + redirect: + description: ActionRedirect defines a redirect in + an Action. + properties: + code: + type: integer + url: + type: string + type: object + return: + description: ActionReturn defines a return in an Action. + properties: + body: + type: string + code: + type: integer + headers: + items: + description: Header defines an HTTP Header. + properties: + name: + type: string + value: + type: string + type: object + type: array + type: + type: string + type: object + type: object + conditions: + items: + description: Condition defines a condition in a MatchRule. + properties: + argument: + type: string + cookie: + type: string + header: + type: string + value: + type: string + variable: + type: string + type: object + type: array + splits: + items: + description: Split defines a split. + properties: + action: + description: Action defines an action. + properties: + pass: + type: string + proxy: + description: ActionProxy defines a proxy in + an Action. + properties: + requestHeaders: + description: ProxyRequestHeaders defines + the request headers manipulation in an + ActionProxy. + properties: + pass: + type: boolean + set: + items: + description: Header defines an HTTP + Header. + properties: + name: + type: string + value: + type: string + type: object + type: array + type: object + responseHeaders: + description: ProxyResponseHeaders defines + the response headers manipulation in an + ActionProxy. + properties: + add: + items: + description: AddHeader defines an + HTTP Header with an optional Always + field to use with the add_header + NGINX directive. + properties: + always: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + hide: + items: + type: string + type: array + ignore: + items: + type: string + type: array + pass: + items: + type: string + type: array + type: object + rewritePath: + type: string + upstream: + type: string + type: object + redirect: + description: ActionRedirect defines a redirect + in an Action. + properties: + code: + type: integer + url: + type: string + type: object + return: + description: ActionReturn defines a return in + an Action. + properties: + body: + type: string + code: + type: integer + headers: + items: + description: Header defines an HTTP Header. + properties: + name: + type: string + value: + type: string + type: object + type: array + type: + type: string + type: object + type: object + weight: + type: integer + type: object + type: array + type: object + type: array + path: + type: string + policies: + items: + description: PolicyReference references a policy by name and + an optional namespace. + properties: + name: + type: string + namespace: + type: string + type: object + type: array + route: + type: string + splits: + items: + description: Split defines a split. + properties: + action: + description: Action defines an action. + properties: + pass: + type: string + proxy: + description: ActionProxy defines a proxy in an Action. + properties: + requestHeaders: + description: ProxyRequestHeaders defines the request + headers manipulation in an ActionProxy. + properties: + pass: + type: boolean + set: + items: + description: Header defines an HTTP Header. + properties: + name: + type: string + value: + type: string + type: object + type: array + type: object + responseHeaders: + description: ProxyResponseHeaders defines the + response headers manipulation in an ActionProxy. + properties: + add: + items: + description: AddHeader defines an HTTP Header + with an optional Always field to use with + the add_header NGINX directive. + properties: + always: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + hide: + items: + type: string + type: array + ignore: + items: + type: string + type: array + pass: + items: + type: string + type: array + type: object + rewritePath: + type: string + upstream: + type: string + type: object + redirect: + description: ActionRedirect defines a redirect in + an Action. + properties: + code: + type: integer + url: + type: string + type: object + return: + description: ActionReturn defines a return in an Action. + properties: + body: + type: string + code: + type: integer + headers: + items: + description: Header defines an HTTP Header. + properties: + name: + type: string + value: + type: string + type: object + type: array + type: + type: string + type: object + type: object + weight: + type: integer + type: object + type: array + type: object + type: array + upstreams: + items: + description: Upstream defines an upstream. + properties: + backup: + type: string + backupPort: + type: integer + buffer-size: + type: string + buffering: + type: boolean + buffers: + description: UpstreamBuffers defines Buffer Configuration for + an Upstream. + properties: + number: + type: integer + size: + type: string + type: object + client-max-body-size: + type: string + connect-timeout: + type: string + fail-timeout: + type: string + healthCheck: + description: HealthCheck defines the parameters for active Upstream + HealthChecks. + properties: + connect-timeout: + type: string + enable: + type: boolean + fails: + type: integer + grpcService: + type: string + grpcStatus: + type: integer + headers: + items: + description: Header defines an HTTP Header. + properties: + name: + type: string + value: + type: string + type: object + type: array + interval: + type: string + jitter: + type: string + keepalive-time: + type: string + mandatory: + type: boolean + passes: + type: integer + path: + type: string + persistent: + type: boolean + port: + type: integer + read-timeout: + type: string + send-timeout: + type: string + statusMatch: + type: string + tls: + description: UpstreamTLS defines a TLS configuration for + an Upstream. + properties: + enable: + type: boolean + type: object + type: object + keepalive: + type: integer + lb-method: + type: string + max-conns: + type: integer + max-fails: + type: integer + name: + type: string + next-upstream: + type: string + next-upstream-timeout: + type: string + next-upstream-tries: + type: integer + ntlm: + type: boolean + port: + type: integer + queue: + description: UpstreamQueue defines Queue Configuration for an + Upstream. + properties: + size: + type: integer + timeout: + type: string + type: object + read-timeout: + type: string + send-timeout: + type: string + service: + type: string + sessionCookie: + description: SessionCookie defines the parameters for session + persistence. + properties: + domain: + type: string + enable: + type: boolean + expires: + type: string + httpOnly: + type: boolean + name: + type: string + path: + type: string + samesite: + type: string + secure: + type: boolean + type: object + slow-start: + type: string + subselector: + additionalProperties: + type: string + type: object + tls: + description: UpstreamTLS defines a TLS configuration for an + Upstream. + properties: + enable: + type: boolean + type: object + type: + type: string + use-cluster-ip: + type: boolean + type: object + type: array + type: object + status: + description: VirtualServerRouteStatus defines the status for the VirtualServerRoute + resource. + properties: + externalEndpoints: + items: + description: ExternalEndpoint defines the IP/ Hostname and ports + used to connect to this resource. + properties: + hostname: + type: string + ip: + type: string + ports: + type: string + type: object + type: array + message: + type: string + reason: + type: string + referencedBy: + type: string + state: + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crd/bases/k8s.nginx.org_virtualservers.yaml b/config/crd/bases/k8s.nginx.org_virtualservers.yaml new file mode 100644 index 0000000000..2acc5cf292 --- /dev/null +++ b/config/crd/bases/k8s.nginx.org_virtualservers.yaml @@ -0,0 +1,829 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.17.0 + name: virtualservers.k8s.nginx.org +spec: + group: k8s.nginx.org + names: + kind: VirtualServer + listKind: VirtualServerList + plural: virtualservers + shortNames: + - vs + singular: virtualserver + scope: Namespaced + versions: + - additionalPrinterColumns: + - description: Current state of the VirtualServer. If the resource has a valid + status, it means it has been validated and accepted by the Ingress Controller. + jsonPath: .status.state + name: State + type: string + - jsonPath: .spec.host + name: Host + type: string + - jsonPath: .status.externalEndpoints[*].ip + name: IP + type: string + - jsonPath: .status.externalEndpoints[*].hostname + name: ExternalHostname + priority: 1 + type: string + - jsonPath: .status.externalEndpoints[*].ports + name: Ports + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1 + schema: + openAPIV3Schema: + description: VirtualServer defines the VirtualServer resource. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: VirtualServerSpec is the spec of the VirtualServer resource. + properties: + dos: + type: string + externalDNS: + description: ExternalDNS defines externaldns sub-resource of a virtual + server. + properties: + enable: + type: boolean + labels: + additionalProperties: + type: string + description: Labels stores labels defined for the Endpoint + type: object + providerSpecific: + description: ProviderSpecific stores provider specific config + items: + description: |- + ProviderSpecificProperty defines specific property + for using with ExternalDNS sub-resource. + properties: + name: + description: Name of the property + type: string + value: + description: Value of the property + type: string + type: object + type: array + recordTTL: + description: TTL for the record + format: int64 + type: integer + recordType: + type: string + type: object + gunzip: + type: boolean + host: + type: string + http-snippets: + type: string + ingressClassName: + type: string + internalRoute: + description: InternalRoute allows for the configuration of internal + routing. + type: boolean + listener: + description: VirtualServerListener references a custom http and/or + https listener defined in GlobalConfiguration. + properties: + http: + type: string + https: + type: string + type: object + policies: + items: + description: PolicyReference references a policy by name and an + optional namespace. + properties: + name: + type: string + namespace: + type: string + type: object + type: array + routes: + items: + description: Route defines a route. + properties: + action: + description: Action defines an action. + properties: + pass: + type: string + proxy: + description: ActionProxy defines a proxy in an Action. + properties: + requestHeaders: + description: ProxyRequestHeaders defines the request + headers manipulation in an ActionProxy. + properties: + pass: + type: boolean + set: + items: + description: Header defines an HTTP Header. + properties: + name: + type: string + value: + type: string + type: object + type: array + type: object + responseHeaders: + description: ProxyResponseHeaders defines the response + headers manipulation in an ActionProxy. + properties: + add: + items: + description: AddHeader defines an HTTP Header + with an optional Always field to use with the + add_header NGINX directive. + properties: + always: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + hide: + items: + type: string + type: array + ignore: + items: + type: string + type: array + pass: + items: + type: string + type: array + type: object + rewritePath: + type: string + upstream: + type: string + type: object + redirect: + description: ActionRedirect defines a redirect in an Action. + properties: + code: + type: integer + url: + type: string + type: object + return: + description: ActionReturn defines a return in an Action. + properties: + body: + type: string + code: + type: integer + headers: + items: + description: Header defines an HTTP Header. + properties: + name: + type: string + value: + type: string + type: object + type: array + type: + type: string + type: object + type: object + dos: + type: string + errorPages: + items: + description: ErrorPage defines an ErrorPage in a Route. + properties: + codes: + items: + type: integer + type: array + redirect: + description: ErrorPageRedirect defines a redirect for + an ErrorPage. + properties: + code: + type: integer + url: + type: string + type: object + return: + description: ErrorPageReturn defines a return for an ErrorPage. + properties: + body: + type: string + code: + type: integer + headers: + items: + description: Header defines an HTTP Header. + properties: + name: + type: string + value: + type: string + type: object + type: array + type: + type: string + type: object + type: object + type: array + location-snippets: + type: string + matches: + items: + description: Match defines a match. + properties: + action: + description: Action defines an action. + properties: + pass: + type: string + proxy: + description: ActionProxy defines a proxy in an Action. + properties: + requestHeaders: + description: ProxyRequestHeaders defines the request + headers manipulation in an ActionProxy. + properties: + pass: + type: boolean + set: + items: + description: Header defines an HTTP Header. + properties: + name: + type: string + value: + type: string + type: object + type: array + type: object + responseHeaders: + description: ProxyResponseHeaders defines the + response headers manipulation in an ActionProxy. + properties: + add: + items: + description: AddHeader defines an HTTP Header + with an optional Always field to use with + the add_header NGINX directive. + properties: + always: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + hide: + items: + type: string + type: array + ignore: + items: + type: string + type: array + pass: + items: + type: string + type: array + type: object + rewritePath: + type: string + upstream: + type: string + type: object + redirect: + description: ActionRedirect defines a redirect in + an Action. + properties: + code: + type: integer + url: + type: string + type: object + return: + description: ActionReturn defines a return in an Action. + properties: + body: + type: string + code: + type: integer + headers: + items: + description: Header defines an HTTP Header. + properties: + name: + type: string + value: + type: string + type: object + type: array + type: + type: string + type: object + type: object + conditions: + items: + description: Condition defines a condition in a MatchRule. + properties: + argument: + type: string + cookie: + type: string + header: + type: string + value: + type: string + variable: + type: string + type: object + type: array + splits: + items: + description: Split defines a split. + properties: + action: + description: Action defines an action. + properties: + pass: + type: string + proxy: + description: ActionProxy defines a proxy in + an Action. + properties: + requestHeaders: + description: ProxyRequestHeaders defines + the request headers manipulation in an + ActionProxy. + properties: + pass: + type: boolean + set: + items: + description: Header defines an HTTP + Header. + properties: + name: + type: string + value: + type: string + type: object + type: array + type: object + responseHeaders: + description: ProxyResponseHeaders defines + the response headers manipulation in an + ActionProxy. + properties: + add: + items: + description: AddHeader defines an + HTTP Header with an optional Always + field to use with the add_header + NGINX directive. + properties: + always: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + hide: + items: + type: string + type: array + ignore: + items: + type: string + type: array + pass: + items: + type: string + type: array + type: object + rewritePath: + type: string + upstream: + type: string + type: object + redirect: + description: ActionRedirect defines a redirect + in an Action. + properties: + code: + type: integer + url: + type: string + type: object + return: + description: ActionReturn defines a return in + an Action. + properties: + body: + type: string + code: + type: integer + headers: + items: + description: Header defines an HTTP Header. + properties: + name: + type: string + value: + type: string + type: object + type: array + type: + type: string + type: object + type: object + weight: + type: integer + type: object + type: array + type: object + type: array + path: + type: string + policies: + items: + description: PolicyReference references a policy by name and + an optional namespace. + properties: + name: + type: string + namespace: + type: string + type: object + type: array + route: + type: string + splits: + items: + description: Split defines a split. + properties: + action: + description: Action defines an action. + properties: + pass: + type: string + proxy: + description: ActionProxy defines a proxy in an Action. + properties: + requestHeaders: + description: ProxyRequestHeaders defines the request + headers manipulation in an ActionProxy. + properties: + pass: + type: boolean + set: + items: + description: Header defines an HTTP Header. + properties: + name: + type: string + value: + type: string + type: object + type: array + type: object + responseHeaders: + description: ProxyResponseHeaders defines the + response headers manipulation in an ActionProxy. + properties: + add: + items: + description: AddHeader defines an HTTP Header + with an optional Always field to use with + the add_header NGINX directive. + properties: + always: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + hide: + items: + type: string + type: array + ignore: + items: + type: string + type: array + pass: + items: + type: string + type: array + type: object + rewritePath: + type: string + upstream: + type: string + type: object + redirect: + description: ActionRedirect defines a redirect in + an Action. + properties: + code: + type: integer + url: + type: string + type: object + return: + description: ActionReturn defines a return in an Action. + properties: + body: + type: string + code: + type: integer + headers: + items: + description: Header defines an HTTP Header. + properties: + name: + type: string + value: + type: string + type: object + type: array + type: + type: string + type: object + type: object + weight: + type: integer + type: object + type: array + type: object + type: array + server-snippets: + type: string + tls: + description: TLS defines TLS configuration for a VirtualServer. + properties: + cert-manager: + description: CertManager defines a cert manager config for a TLS. + properties: + cluster-issuer: + type: string + common-name: + type: string + duration: + type: string + issue-temp-cert: + type: boolean + issuer: + type: string + issuer-group: + type: string + issuer-kind: + type: string + renew-before: + type: string + usages: + type: string + type: object + redirect: + description: TLSRedirect defines a redirect for a TLS. + properties: + basedOn: + type: string + code: + type: integer + enable: + type: boolean + type: object + secret: + type: string + type: object + upstreams: + items: + description: Upstream defines an upstream. + properties: + backup: + type: string + backupPort: + type: integer + buffer-size: + type: string + buffering: + type: boolean + buffers: + description: UpstreamBuffers defines Buffer Configuration for + an Upstream. + properties: + number: + type: integer + size: + type: string + type: object + client-max-body-size: + type: string + connect-timeout: + type: string + fail-timeout: + type: string + healthCheck: + description: HealthCheck defines the parameters for active Upstream + HealthChecks. + properties: + connect-timeout: + type: string + enable: + type: boolean + fails: + type: integer + grpcService: + type: string + grpcStatus: + type: integer + headers: + items: + description: Header defines an HTTP Header. + properties: + name: + type: string + value: + type: string + type: object + type: array + interval: + type: string + jitter: + type: string + keepalive-time: + type: string + mandatory: + type: boolean + passes: + type: integer + path: + type: string + persistent: + type: boolean + port: + type: integer + read-timeout: + type: string + send-timeout: + type: string + statusMatch: + type: string + tls: + description: UpstreamTLS defines a TLS configuration for + an Upstream. + properties: + enable: + type: boolean + type: object + type: object + keepalive: + type: integer + lb-method: + type: string + max-conns: + type: integer + max-fails: + type: integer + name: + type: string + next-upstream: + type: string + next-upstream-timeout: + type: string + next-upstream-tries: + type: integer + ntlm: + type: boolean + port: + type: integer + queue: + description: UpstreamQueue defines Queue Configuration for an + Upstream. + properties: + size: + type: integer + timeout: + type: string + type: object + read-timeout: + type: string + send-timeout: + type: string + service: + type: string + sessionCookie: + description: SessionCookie defines the parameters for session + persistence. + properties: + domain: + type: string + enable: + type: boolean + expires: + type: string + httpOnly: + type: boolean + name: + type: string + path: + type: string + samesite: + type: string + secure: + type: boolean + type: object + slow-start: + type: string + subselector: + additionalProperties: + type: string + type: object + tls: + description: UpstreamTLS defines a TLS configuration for an + Upstream. + properties: + enable: + type: boolean + type: object + type: + type: string + use-cluster-ip: + type: boolean + type: object + type: array + type: object + status: + description: VirtualServerStatus defines the status for the VirtualServer + resource. + properties: + externalEndpoints: + items: + description: ExternalEndpoint defines the IP/ Hostname and ports + used to connect to this resource. + properties: + hostname: + type: string + ip: + type: string + ports: + type: string + type: object + type: array + message: + type: string + reason: + type: string + state: + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crd/kustomization.yaml b/config/crd/kustomization.yaml new file mode 100644 index 0000000000..7336f9687f --- /dev/null +++ b/config/crd/kustomization.yaml @@ -0,0 +1,9 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: +- bases/externaldns.nginx.org_dnsendpoints.yaml +- bases/k8s.nginx.org_globalconfigurations.yaml +- bases/k8s.nginx.org_policies.yaml +- bases/k8s.nginx.org_transportservers.yaml +- bases/k8s.nginx.org_virtualserverroutes.yaml +- bases/k8s.nginx.org_virtualservers.yaml diff --git a/deploy/crds-nap-dos.yaml b/deploy/crds-nap-dos.yaml new file mode 100644 index 0000000000..bb9d09840b --- /dev/null +++ b/deploy/crds-nap-dos.yaml @@ -0,0 +1,258 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.9.2 + creationTimestamp: null + name: apdoslogconfs.appprotectdos.f5.com +spec: + group: appprotectdos.f5.com + names: + kind: APDosLogConf + listKind: APDosLogConfList + plural: apdoslogconfs + singular: apdoslogconf + preserveUnknownFields: false + scope: Namespaced + versions: + - name: v1beta1 + schema: + openAPIV3Schema: + description: APDosLogConf is the Schema for the APDosLogConfs API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: APDosLogConfSpec defines the desired state of APDosLogConf + properties: + content: + properties: + format: + enum: + - splunk + - arcsight + - user-defined + type: string + format_string: + type: string + max_message_size: + pattern: ^([1-9]|[1-5][0-9]|6[0-4])k$ + type: string + type: object + filter: + properties: + attack-signatures: + default: top 10 + pattern: ^(none|all|top ([1-9]|[1-9][0-9]|[1-9][0-9]{2,4}|100000))$ + type: string + bad-actors: + default: top 10 + pattern: ^(none|all|top ([1-9]|[1-9][0-9]|[1-9][0-9]{2,4}|100000))$ + type: string + traffic-mitigation-stats: + default: all + enum: + - none + - all + type: string + type: object + type: object + type: object + served: true + storage: true +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.9.2 + creationTimestamp: null + name: apdospolicies.appprotectdos.f5.com +spec: + group: appprotectdos.f5.com + names: + kind: APDosPolicy + listKind: APDosPoliciesList + plural: apdospolicies + singular: apdospolicy + preserveUnknownFields: false + scope: Namespaced + versions: + - name: v1beta1 + schema: + openAPIV3Schema: + description: APDosPolicy is the Schema for the APDosPolicy API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: APDosPolicySpec defines the desired state of APDosPolicy + properties: + automation_tools_detection: + default: "on" + enum: + - "on" + - "off" + type: string + bad_actors: + default: "on" + enum: + - "on" + - "off" + type: string + mitigation_mode: + default: standard + enum: + - standard + - conservative + - none + type: string + signatures: + default: "on" + enum: + - "on" + - "off" + type: string + tls_fingerprint: + default: "on" + enum: + - "on" + - "off" + type: string + type: object + type: object + served: true + storage: true +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.17.0 + name: dosprotectedresources.appprotectdos.f5.com +spec: + group: appprotectdos.f5.com + names: + kind: DosProtectedResource + listKind: DosProtectedResourceList + plural: dosprotectedresources + shortNames: + - pr + singular: dosprotectedresource + scope: Namespaced + versions: + - name: v1beta1 + schema: + openAPIV3Schema: + description: DosProtectedResource defines a Dos protected resource. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: DosProtectedResourceSpec defines the properties and values + a DosProtectedResource can have. + properties: + allowList: + description: AllowList is a list of allowed IPs and subnet masks + items: + description: AllowListEntry represents an IP address and a subnet + mask. + properties: + ipWithMask: + type: string + type: object + type: array + apDosMonitor: + description: 'ApDosMonitor is how NGINX App Protect DoS monitors the + stress level of the protected object. The monitor requests are sent + from localhost (127.0.0.1). Default value: URI - None, protocol + - http1, timeout - NGINX App Protect DoS default.' + properties: + protocol: + description: Protocol determines if the server listens on http1 + / http2 / grpc / websocket. The default is http1. + enum: + - http1 + - http2 + - grpc + - websocket + type: string + timeout: + description: Timeout determines how long (in seconds) should NGINX + App Protect DoS wait for a response. Default is 10 seconds for + http1/http2 and 5 seconds for grpc. + format: int64 + type: integer + uri: + description: 'URI is the destination to the desired protected + object in the nginx.conf:' + type: string + type: object + apDosPolicy: + description: ApDosPolicy is the namespace/name of a ApDosPolicy resource + type: string + dosAccessLogDest: + description: DosAccessLogDest is the network address for the access + logs + type: string + dosSecurityLog: + description: DosSecurityLog defines the security log of the DosProtectedResource. + properties: + apDosLogConf: + description: ApDosLogConf is the namespace/name of a APDosLogConf + resource + type: string + dosLogDest: + description: DosLogDest is the network address of a logging service, + can be either IP or DNS name. + type: string + enable: + description: Enable enables the security logging feature if set + to true + type: boolean + type: object + enable: + description: Enable enables the DOS feature if set to true + type: boolean + name: + description: Name is the name of protected object, max of 63 characters. + type: string + type: object + type: object + served: true + storage: true diff --git a/deploy/crds-nap-waf.yaml b/deploy/crds-nap-waf.yaml new file mode 100644 index 0000000000..2548a69ce4 --- /dev/null +++ b/deploy/crds-nap-waf.yaml @@ -0,0 +1,2355 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.13.0 + name: aplogconfs.appprotect.f5.com +spec: + group: appprotect.f5.com + names: + kind: APLogConf + listKind: APLogConfList + plural: aplogconfs + singular: aplogconf + preserveUnknownFields: false + scope: Namespaced + versions: + - name: v1beta1 + schema: + openAPIV3Schema: + description: APLogConf is the Schema for the APLogConfs API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: APLogConfSpec defines the desired state of APLogConf + properties: + content: + properties: + escaping_characters: + items: + properties: + from: + type: string + to: + type: string + type: object + type: array + format: + enum: + - splunk + - arcsight + - default + - user-defined + - grpc + type: string + format_string: + type: string + list_delimiter: + type: string + list_prefix: + type: string + list_suffix: + type: string + max_message_size: + pattern: ^([1-9]|[1-5][0-9]|6[0-4])k$ + type: string + max_request_size: + pattern: ^([1-9]|[1-9][0-9]|[1-9][0-9]{2}|[1-9][0-9]{3}|10[0-2][0-9][0-9]|[1-9]k|10k|any)$ + type: string + type: object + filter: + properties: + request_type: + enum: + - all + - illegal + - blocked + type: string + type: object + type: object + type: object + served: true + storage: true +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.13.0 + name: appolicies.appprotect.f5.com +spec: + group: appprotect.f5.com + names: + kind: APPolicy + listKind: APPolicyList + plural: appolicies + singular: appolicy + preserveUnknownFields: false + scope: Namespaced + versions: + - name: v1beta1 + schema: + openAPIV3Schema: + description: APPolicyConfig is the Schema for the APPolicyconfigs API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: APPolicySpec defines the desired state of APPolicy + properties: + modifications: + items: + properties: + action: + type: string + description: + type: string + entity: + properties: + name: + type: string + type: object + entityChanges: + properties: + type: + type: string + type: object + type: object + x-kubernetes-preserve-unknown-fields: true + type: array + modificationsReference: + properties: + link: + pattern: ^http + type: string + type: object + policy: + description: Defines the App Protect policy + properties: + applicationLanguage: + enum: + - iso-8859-10 + - iso-8859-6 + - windows-1255 + - auto-detect + - koi8-r + - gb18030 + - iso-8859-8 + - windows-1250 + - iso-8859-9 + - windows-1252 + - iso-8859-16 + - gb2312 + - iso-8859-2 + - iso-8859-5 + - windows-1257 + - windows-1256 + - iso-8859-13 + - windows-874 + - windows-1253 + - iso-8859-3 + - euc-jp + - utf-8 + - gbk + - windows-1251 + - big5 + - iso-8859-1 + - shift_jis + - euc-kr + - iso-8859-4 + - iso-8859-7 + - iso-8859-15 + type: string + blocking-settings: + properties: + evasions: + items: + properties: + description: + enum: + - '%u decoding' + - Apache whitespace + - Bad unescape + - Bare byte decoding + - Directory traversals + - IIS backslashes + - IIS Unicode codepoints + - Multiple decoding + - Multiple slashes + - Semicolon path parameters + - Trailing dot + - Trailing slash + type: string + enabled: + type: boolean + maxDecodingPasses: + type: integer + type: object + type: array + http-protocols: + items: + properties: + description: + enum: + - Unescaped space in URL + - Unparsable request content + - Several Content-Length headers + - 'POST request with Content-Length: 0' + - Null in request + - No Host header in HTTP/1.1 request + - Multiple host headers + - Host header contains IP address + - High ASCII characters in headers + - Header name with no header value + - CRLF characters before request start + - Content length should be a positive number + - Chunked request with Content-Length header + - Check maximum number of cookies + - Check maximum number of parameters + - Check maximum number of headers + - Body in GET or HEAD requests + - Bad multipart/form-data request parsing + - Bad multipart parameters parsing + - Bad HTTP version + - Bad host header value + type: string + enabled: + type: boolean + maxCookies: + maximum: 100 + minimum: 1 + type: integer + maxHeaders: + maximum: 150 + minimum: 1 + type: integer + maxParams: + maximum: 5000 + minimum: 1 + type: integer + type: object + type: array + violations: + items: + properties: + alarm: + type: boolean + block: + type: boolean + description: + type: string + name: + enum: + - VIOL_ACCESS_INVALID + - VIOL_ACCESS_MALFORMED + - VIOL_ACCESS_MISSING + - VIOL_ACCESS_UNAUTHORIZED + - VIOL_ASM_COOKIE_HIJACKING + - VIOL_ASM_COOKIE_MODIFIED + - VIOL_BLACKLISTED_IP + - VIOL_COOKIE_EXPIRED + - VIOL_COOKIE_LENGTH + - VIOL_COOKIE_MALFORMED + - VIOL_COOKIE_MODIFIED + - VIOL_CSRF + - VIOL_DATA_GUARD + - VIOL_ENCODING + - VIOL_EVASION + - VIOL_FILE_UPLOAD + - VIOL_FILE_UPLOAD_IN_BODY + - VIOL_FILETYPE + - VIOL_GRAPHQL_ERROR_RESPONSE + - VIOL_GRAPHQL_FORMAT + - VIOL_GRAPHQL_INTROSPECTION_QUERY + - VIOL_GRAPHQL_MALFORMED + - VIOL_GRPC_FORMAT + - VIOL_GRPC_MALFORMED + - VIOL_GRPC_METHOD + - VIOL_HEADER_LENGTH + - VIOL_HEADER_METACHAR + - VIOL_HEADER_REPEATED + - VIOL_HTTP_PROTOCOL + - VIOL_HTTP_RESPONSE_STATUS + - VIOL_JSON_FORMAT + - VIOL_JSON_MALFORMED + - VIOL_JSON_SCHEMA + - VIOL_MANDATORY_HEADER + - VIOL_MANDATORY_PARAMETER + - VIOL_MANDATORY_REQUEST_BODY + - VIOL_METHOD + - VIOL_PARAMETER + - VIOL_PARAMETER_ARRAY_VALUE + - VIOL_PARAMETER_DATA_TYPE + - VIOL_PARAMETER_EMPTY_VALUE + - VIOL_PARAMETER_LOCATION + - VIOL_PARAMETER_MULTIPART_NULL_VALUE + - VIOL_PARAMETER_NAME_METACHAR + - VIOL_PARAMETER_NUMERIC_VALUE + - VIOL_PARAMETER_REPEATED + - VIOL_PARAMETER_STATIC_VALUE + - VIOL_PARAMETER_VALUE_BASE64 + - VIOL_PARAMETER_VALUE_LENGTH + - VIOL_PARAMETER_VALUE_METACHAR + - VIOL_PARAMETER_VALUE_REGEXP + - VIOL_POST_DATA_LENGTH + - VIOL_QUERY_STRING_LENGTH + - VIOL_RATING_NEED_EXAMINATION + - VIOL_RATING_THREAT + - VIOL_REQUEST_LENGTH + - VIOL_REQUEST_MAX_LENGTH + - VIOL_THREAT_CAMPAIGN + - VIOL_URL + - VIOL_URL_CONTENT_TYPE + - VIOL_URL_LENGTH + - VIOL_URL_METACHAR + - VIOL_XML_FORMAT + - VIOL_XML_MALFORMED + type: string + type: object + type: array + type: object + blockingSettingReference: + properties: + link: + pattern: ^http + type: string + type: object + bot-defense: + properties: + mitigations: + properties: + anomalies: + items: + properties: + $action: + enum: + - delete + type: string + action: + enum: + - alarm + - block + - default + - detect + - ignore + type: string + name: + type: string + scoreThreshold: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object + type: array + browsers: + items: + properties: + $action: + enum: + - delete + type: string + action: + enum: + - alarm + - block + - detect + type: string + maxVersion: + maximum: 2147483647 + minimum: 0 + type: integer + minVersion: + maximum: 2147483647 + minimum: 0 + type: integer + name: + type: string + type: object + type: array + classes: + items: + properties: + action: + enum: + - alarm + - block + - detect + - ignore + type: string + name: + enum: + - browser + - malicious-bot + - suspicious-browser + - trusted-bot + - unknown + - untrusted-bot + type: string + type: object + type: array + signatures: + items: + properties: + $action: + enum: + - delete + type: string + action: + enum: + - alarm + - block + - detect + - ignore + type: string + name: + type: string + type: object + type: array + type: object + settings: + properties: + caseSensitiveHttpHeaders: + type: boolean + isEnabled: + type: boolean + type: object + type: object + browser-definitions: + items: + properties: + $action: + enum: + - delete + type: string + isUserDefined: + type: boolean + matchRegex: + type: string + matchString: + type: string + name: + type: string + type: object + type: array + caseInsensitive: + type: boolean + character-sets: + items: + properties: + characterSet: + items: + properties: + isAllowed: + type: boolean + metachar: + type: string + type: object + type: array + characterSetType: + enum: + - gwt-content + - header + - json-content + - parameter-name + - parameter-value + - plain-text-content + - url + - xml-content + type: string + type: object + type: array + characterSetReference: + properties: + link: + pattern: ^http + type: string + type: object + cookie-settings: + properties: + maximumCookieHeaderLength: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object + cookieReference: + properties: + link: + pattern: ^http + type: string + type: object + cookieSettingsReference: + properties: + link: + pattern: ^http + type: string + type: object + cookies: + items: + properties: + $action: + enum: + - delete + type: string + accessibleOnlyThroughTheHttpProtocol: + type: boolean + attackSignaturesCheck: + type: boolean + decodeValueAsBase64: + enum: + - enabled + - disabled + - required + type: string + enforcementType: + type: string + insertSameSiteAttribute: + enum: + - lax + - none + - none-value + - strict + type: string + maskValueInLogs: + type: boolean + name: + type: string + securedOverHttpsConnection: + type: boolean + signatureOverrides: + items: + properties: + enabled: + type: boolean + name: + type: string + signatureId: + type: integer + tag: + type: string + type: object + type: array + type: + enum: + - explicit + - wildcard + type: string + wildcardOrder: + type: integer + type: object + type: array + csrf-protection: + properties: + enabled: + type: boolean + expirationTimeInSeconds: + pattern: disabled|\d+ + type: string + sslOnly: + type: boolean + type: object + csrf-urls: + items: + properties: + $action: + enum: + - delete + type: string + enforcementAction: + enum: + - verify-origin + - none + type: string + method: + enum: + - GET + - POST + - any + type: string + url: + type: string + wildcardOrder: + type: integer + type: object + type: array + data-guard: + properties: + creditCardNumbers: + type: boolean + customPatterns: + type: boolean + customPatternsList: + items: + type: string + type: array + enabled: + type: boolean + enforcementMode: + enum: + - ignore-urls-in-list + - enforce-urls-in-list + type: string + enforcementUrls: + items: + type: string + type: array + firstCustomCharactersToExpose: + type: integer + lastCcnDigitsToExpose: + type: integer + lastCustomCharactersToExpose: + type: integer + lastSsnDigitsToExpose: + type: integer + maskData: + type: boolean + usSocialSecurityNumbers: + type: boolean + type: object + dataGuardReference: + properties: + link: + pattern: ^http + type: string + type: object + description: + type: string + enablePassiveMode: + type: boolean + enforcementMode: + enum: + - transparent + - blocking + type: string + enforcer-settings: + properties: + enforcerStateCookies: + properties: + httpOnlyAttribute: + type: boolean + sameSiteAttribute: + enum: + - lax + - none + - none-value + - strict + type: string + secureAttribute: + enum: + - always + - never + type: string + type: object + type: object + filetypeReference: + properties: + link: + pattern: ^http + type: string + type: object + filetypes: + items: + properties: + $action: + enum: + - delete + type: string + allowed: + type: boolean + checkPostDataLength: + type: boolean + checkQueryStringLength: + type: boolean + checkRequestLength: + type: boolean + checkUrlLength: + type: boolean + name: + type: string + postDataLength: + type: integer + queryStringLength: + type: integer + requestLength: + type: integer + responseCheck: + type: boolean + type: + enum: + - explicit + - wildcard + type: string + urlLength: + type: integer + wildcardOrder: + type: integer + type: object + type: array + fullPath: + type: string + general: + properties: + allowedResponseCodes: + items: + format: int32 + maximum: 999 + minimum: 100 + type: integer + type: array + customXffHeaders: + items: + type: string + type: array + maskCreditCardNumbersInRequest: + type: boolean + trustXff: + type: boolean + type: object + generalReference: + properties: + link: + pattern: ^http + type: string + type: object + graphql-profiles: + items: + properties: + $action: + enum: + - delete + type: string + attackSignaturesCheck: + type: boolean + defenseAttributes: + properties: + allowIntrospectionQueries: + type: boolean + maximumBatchedQueries: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + maximumQueryCost: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + maximumStructureDepth: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + maximumTotalLength: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + maximumValueLength: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + tolerateParsingWarnings: + type: boolean + type: object + description: + type: string + metacharElementCheck: + type: boolean + metacharOverrides: + items: + properties: + isAllowed: + type: boolean + metachar: + type: string + type: object + type: array + name: + type: string + responseEnforcement: + properties: + blockDisallowedPatterns: + type: boolean + disallowedPatterns: + items: + type: string + type: array + type: object + sensitiveData: + items: + properties: + parameterName: + type: string + type: object + type: array + signatureOverrides: + items: + properties: + enabled: + type: boolean + name: + type: string + signatureId: + type: integer + tag: + type: string + type: object + type: array + type: object + type: array + grpc-profiles: + items: + properties: + $action: + enum: + - delete + type: string + associateUrls: + type: boolean + attackSignaturesCheck: + type: boolean + decodeStringValuesAsBase64: + enum: + - disabled + - enabled + type: string + defenseAttributes: + properties: + allowUnknownFields: + type: boolean + maximumDataLength: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object + description: + type: string + hasIdlFiles: + type: boolean + idlFiles: + items: + properties: + idlFile: + properties: + contents: + type: string + fileName: + type: string + isBase64: + type: boolean + type: object + importUrl: + type: string + isPrimary: + type: boolean + primaryIdlFileName: + type: string + type: object + type: array + metacharCheck: + type: boolean + metacharElementCheck: + type: boolean + name: + type: string + signatureOverrides: + items: + properties: + enabled: + type: boolean + name: + type: string + signatureId: + type: integer + tag: + type: string + type: object + type: array + type: object + type: array + header-settings: + properties: + maximumHttpHeaderLength: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object + headerReference: + properties: + link: + pattern: ^http + type: string + type: object + headerSettingsReference: + properties: + link: + pattern: ^http + type: string + type: object + headers: + items: + properties: + $action: + enum: + - delete + type: string + allowRepeatedOccurrences: + type: boolean + base64Decoding: + type: boolean + checkSignatures: + type: boolean + decodeValueAsBase64: + enum: + - enabled + - disabled + - required + type: string + htmlNormalization: + type: boolean + mandatory: + type: boolean + maskValueInLogs: + type: boolean + name: + type: string + normalizationViolations: + type: boolean + percentDecoding: + type: boolean + signatureOverrides: + items: + properties: + enabled: + type: boolean + name: + type: string + signatureId: + type: integer + tag: + type: string + type: object + type: array + type: + enum: + - explicit + - wildcard + type: string + urlNormalization: + type: boolean + wildcardOrder: + type: integer + type: object + type: array + host-names: + items: + properties: + $action: + enum: + - delete + type: string + includeSubdomains: + type: boolean + name: + type: string + type: object + type: array + idl-files: + items: + properties: + contents: + type: string + fileName: + type: string + isBase64: + type: boolean + type: object + type: array + json-profiles: + items: + properties: + $action: + enum: + - delete + type: string + attackSignaturesCheck: + type: boolean + defenseAttributes: + properties: + maximumArrayLength: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + maximumStructureDepth: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + maximumTotalLengthOfJSONData: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + maximumValueLength: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + tolerateJSONParsingWarnings: + type: boolean + type: object + description: + type: string + handleJsonValuesAsParameters: + type: boolean + hasValidationFiles: + type: boolean + metacharOverrides: + items: + properties: + isAllowed: + type: boolean + metachar: + type: string + type: object + type: array + name: + type: string + signatureOverrides: + items: + properties: + enabled: + type: boolean + name: + type: string + signatureId: + type: integer + tag: + type: string + type: object + type: array + validationFiles: + items: + properties: + importUrl: + type: string + isPrimary: + type: boolean + jsonValidationFile: + properties: + $action: + enum: + - delete + type: string + contents: + type: string + fileName: + type: string + isBase64: + type: boolean + type: object + type: object + type: array + type: object + type: array + json-validation-files: + items: + properties: + $action: + enum: + - delete + type: string + contents: + type: string + fileName: + type: string + isBase64: + type: boolean + type: object + type: array + jsonProfileReference: + properties: + link: + pattern: ^http + type: string + type: object + jsonValidationFileReference: + properties: + link: + pattern: ^http + type: string + type: object + methodReference: + properties: + link: + pattern: ^http + type: string + type: object + methods: + items: + properties: + $action: + enum: + - delete + type: string + name: + type: string + type: object + type: array + name: + type: string + open-api-files: + items: + properties: + link: + pattern: ^http + type: string + type: object + type: array + parameterReference: + properties: + link: + pattern: ^http + type: string + type: object + parameters: + items: + properties: + $action: + enum: + - delete + type: string + allowEmptyValue: + type: boolean + allowRepeatedParameterName: + type: boolean + arraySerializationFormat: + enum: + - csv + - form + - label + - matrix + - multi + - multipart + - pipe + - ssv + - tsv + type: string + attackSignaturesCheck: + type: boolean + checkMaxValue: + type: boolean + checkMaxValueLength: + type: boolean + checkMetachars: + type: boolean + checkMinValue: + type: boolean + checkMinValueLength: + type: boolean + checkMultipleOfValue: + type: boolean + contentProfile: + properties: + name: + type: string + type: object + dataType: + enum: + - alpha-numeric + - binary + - boolean + - decimal + - email + - integer + - none + - phone + type: string + decodeValueAsBase64: + enum: + - enabled + - disabled + - required + type: string + disallowFileUploadOfExecutables: + type: boolean + enableRegularExpression: + type: boolean + exclusiveMax: + type: boolean + exclusiveMin: + type: boolean + isBase64: + type: boolean + isCookie: + type: boolean + isHeader: + type: boolean + level: + enum: + - global + - url + type: string + mandatory: + type: boolean + maximumLength: + type: integer + maximumValue: + type: integer + metacharsOnParameterValueCheck: + type: boolean + minimumLength: + type: integer + minimumValue: + type: integer + multipleOf: + type: integer + name: + type: string + nameMetacharOverrides: + items: + properties: + isAllowed: + type: boolean + metachar: + type: string + type: object + type: array + objectSerializationStyle: + type: string + parameterEnumValues: + items: + type: string + type: array + parameterLocation: + enum: + - any + - cookie + - form-data + - header + - path + - query + type: string + regularExpression: + type: string + sensitiveParameter: + type: boolean + signatureOverrides: + items: + properties: + enabled: + type: boolean + name: + type: string + signatureId: + type: integer + tag: + type: string + type: object + type: array + staticValues: + type: string + type: + enum: + - explicit + - wildcard + type: string + url: + properties: + method: + enum: + - ACL + - BCOPY + - BDELETE + - BMOVE + - BPROPFIND + - BPROPPATCH + - CHECKIN + - CHECKOUT + - CONNECT + - COPY + - DELETE + - GET + - HEAD + - LINK + - LOCK + - MERGE + - MKCOL + - MKWORKSPACE + - MOVE + - NOTIFY + - OPTIONS + - PATCH + - POLL + - POST + - PROPFIND + - PROPPATCH + - PUT + - REPORT + - RPC_IN_DATA + - RPC_OUT_DATA + - SEARCH + - SUBSCRIBE + - TRACE + - TRACK + - UNLINK + - UNLOCK + - UNSUBSCRIBE + - VERSION_CONTROL + - X-MS-ENUMATTS + - '*' + type: string + name: + type: string + protocol: + enum: + - http + - https + type: string + type: + enum: + - explicit + - wildcard + type: string + type: object + valueMetacharOverrides: + items: + properties: + isAllowed: + type: boolean + metachar: + type: string + type: object + type: array + valueType: + enum: + - array + - auto-detect + - dynamic-content + - dynamic-parameter-name + - ignore + - json + - object + - openapi-array + - static-content + - user-input + - xml + type: string + wildcardOrder: + type: integer + type: object + type: array + response-pages: + items: + properties: + ajaxActionType: + enum: + - alert-popup + - custom + - redirect + type: string + ajaxCustomContent: + type: string + ajaxEnabled: + type: boolean + ajaxPopupMessage: + type: string + ajaxRedirectUrl: + type: string + grpcStatusCode: + pattern: ABORTED|ALREADY_EXISTS|CANCELLED|DATA_LOSS|DEADLINE_EXCEEDED|FAILED_PRECONDITION|INTERNAL|INVALID_ARGUMENT|NOT_FOUND|OK|OUT_OF_RANGE|PERMISSION_DENIED|RESOURCE_EXHAUSTED|UNAUTHENTICATED|UNAVAILABLE|UNIMPLEMENTED|UNKNOWN|d+ + type: string + grpcStatusMessage: + type: string + responseActionType: + enum: + - custom + - default + - erase-cookies + - redirect + - soap-fault + type: string + responseContent: + type: string + responseHeader: + type: string + responsePageType: + enum: + - ajax + - ajax-login + - captcha + - captcha-fail + - default + - failed-login-honeypot + - failed-login-honeypot-ajax + - hijack + - leaked-credentials + - leaked-credentials-ajax + - mobile + - persistent-flow + - xml + - grpc + type: string + responseRedirectUrl: + type: string + type: object + type: array + responsePageReference: + properties: + link: + pattern: ^http + type: string + type: object + sensitive-parameters: + items: + properties: + $action: + enum: + - delete + type: string + name: + type: string + type: object + type: array + sensitiveParameterReference: + properties: + link: + pattern: ^http + type: string + type: object + server-technologies: + items: + properties: + $action: + enum: + - delete + type: string + serverTechnologyName: + enum: + - Jenkins + - SharePoint + - Oracle Application Server + - Python + - Oracle Identity Manager + - Spring Boot + - CouchDB + - SQLite + - Handlebars + - Mustache + - Prototype + - Zend + - Redis + - Underscore.js + - Ember.js + - ZURB Foundation + - ef.js + - Vue.js + - UIKit + - TYPO3 CMS + - RequireJS + - React + - MooTools + - Laravel + - GraphQL + - Google Web Toolkit + - Express.js + - CodeIgniter + - Backbone.js + - AngularJS + - JavaScript + - Nginx + - Jetty + - Joomla + - JavaServer Faces (JSF) + - Ruby + - MongoDB + - Django + - Node.js + - Citrix + - JBoss + - Elasticsearch + - Apache Struts + - XML + - PostgreSQL + - IBM DB2 + - Sybase/ASE + - CGI + - Proxy Servers + - SSI (Server Side Includes) + - Cisco + - Novell + - Macromedia JRun + - BEA Systems WebLogic Server + - Lotus Domino + - MySQL + - Oracle + - Microsoft SQL Server + - PHP + - Outlook Web Access + - Apache/NCSA HTTP Server + - Apache Tomcat + - WordPress + - Macromedia ColdFusion + - Unix/Linux + - Microsoft Windows + - ASP.NET + - Front Page Server Extensions (FPSE) + - IIS + - WebDAV + - ASP + - Java Servlets/JSP + - jQuery + type: string + type: object + type: array + serverTechnologyReference: + properties: + link: + pattern: ^http + type: string + type: object + signature-requirements: + items: + properties: + $action: + enum: + - delete + type: string + tag: + type: string + type: object + type: array + signature-sets: + items: + properties: + $action: + enum: + - delete + type: string + alarm: + type: boolean + block: + type: boolean + name: + type: string + type: object + x-kubernetes-preserve-unknown-fields: true + type: array + signature-settings: + properties: + attackSignatureFalsePositiveMode: + enum: + - detect + - detect-and-allow + - disabled + type: string + minimumAccuracyForAutoAddedSignatures: + enum: + - high + - low + - medium + type: string + type: object + signatureReference: + properties: + link: + pattern: ^http + type: string + type: object + signatureSetReference: + properties: + link: + pattern: ^http + type: string + type: object + signatureSettingReference: + properties: + link: + pattern: ^http + type: string + type: object + signatures: + items: + properties: + enabled: + type: boolean + name: + type: string + signatureId: + type: integer + tag: + type: string + type: object + type: array + softwareVersion: + type: string + template: + properties: + name: + type: string + type: object + threat-campaigns: + items: + properties: + isEnabled: + type: boolean + name: + type: string + type: object + type: array + threatCampaignReference: + properties: + link: + pattern: ^http + type: string + type: object + urlReference: + properties: + link: + pattern: ^http + type: string + type: object + urls: + items: + properties: + $action: + enum: + - delete + type: string + allowRenderingInFrames: + enum: + - never + - only-same + type: string + allowRenderingInFramesOnlyFrom: + type: string + attackSignaturesCheck: + type: boolean + clickjackingProtection: + type: boolean + description: + type: string + disallowFileUploadOfExecutables: + type: boolean + html5CrossOriginRequestsEnforcement: + properties: + allowOriginsEnforcementMode: + enum: + - replace-with + - unmodified + type: string + checkAllowedMethods: + type: boolean + crossDomainAllowedOrigin: + items: + properties: + includeSubDomains: + type: boolean + originName: + type: string + originPort: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + originProtocol: + enum: + - http + - http/https + - https + type: string + type: object + type: array + enforcementMode: + enum: + - disabled + - enforce + type: string + type: object + isAllowed: + type: boolean + mandatoryBody: + type: boolean + metacharOverrides: + items: + properties: + isAllowed: + type: boolean + metachar: + type: string + type: object + type: array + metacharsOnUrlCheck: + type: boolean + method: + enum: + - ACL + - BCOPY + - BDELETE + - BMOVE + - BPROPFIND + - BPROPPATCH + - CHECKIN + - CHECKOUT + - CONNECT + - COPY + - DELETE + - GET + - HEAD + - LINK + - LOCK + - MERGE + - MKCOL + - MKWORKSPACE + - MOVE + - NOTIFY + - OPTIONS + - PATCH + - POLL + - POST + - PROPFIND + - PROPPATCH + - PUT + - REPORT + - RPC_IN_DATA + - RPC_OUT_DATA + - SEARCH + - SUBSCRIBE + - TRACE + - TRACK + - UNLINK + - UNLOCK + - UNSUBSCRIBE + - VERSION_CONTROL + - X-MS-ENUMATTS + - '*' + type: string + methodOverrides: + items: + properties: + allowed: + type: boolean + method: + enum: + - ACL + - BCOPY + - BDELETE + - BMOVE + - BPROPFIND + - BPROPPATCH + - CHECKIN + - CHECKOUT + - CONNECT + - COPY + - DELETE + - GET + - HEAD + - LINK + - LOCK + - MERGE + - MKCOL + - MKWORKSPACE + - MOVE + - NOTIFY + - OPTIONS + - PATCH + - POLL + - POST + - PROPFIND + - PROPPATCH + - PUT + - REPORT + - RPC_IN_DATA + - RPC_OUT_DATA + - SEARCH + - SUBSCRIBE + - TRACE + - TRACK + - UNLINK + - UNLOCK + - UNSUBSCRIBE + - VERSION_CONTROL + - X-MS-ENUMATTS + type: string + type: object + type: array + methodsOverrideOnUrlCheck: + type: boolean + name: + type: string + operationId: + type: string + positionalParameters: + items: + properties: + parameter: + properties: + $action: + enum: + - delete + type: string + allowEmptyValue: + type: boolean + allowRepeatedParameterName: + type: boolean + arraySerializationFormat: + enum: + - csv + - form + - label + - matrix + - multi + - multipart + - pipe + - ssv + - tsv + type: string + attackSignaturesCheck: + type: boolean + checkMaxValue: + type: boolean + checkMaxValueLength: + type: boolean + checkMetachars: + type: boolean + checkMinValue: + type: boolean + checkMinValueLength: + type: boolean + checkMultipleOfValue: + type: boolean + contentProfile: + properties: + name: + type: string + type: object + dataType: + enum: + - alpha-numeric + - binary + - boolean + - decimal + - email + - integer + - none + - phone + type: string + decodeValueAsBase64: + enum: + - enabled + - disabled + - required + type: string + disallowFileUploadOfExecutables: + type: boolean + enableRegularExpression: + type: boolean + exclusiveMax: + type: boolean + exclusiveMin: + type: boolean + isBase64: + type: boolean + isCookie: + type: boolean + isHeader: + type: boolean + level: + enum: + - global + - url + type: string + mandatory: + type: boolean + maximumLength: + type: integer + maximumValue: + type: integer + metacharsOnParameterValueCheck: + type: boolean + minimumLength: + type: integer + minimumValue: + type: integer + multipleOf: + type: integer + name: + type: string + nameMetacharOverrides: + items: + properties: + isAllowed: + type: boolean + metachar: + type: string + type: object + type: array + objectSerializationStyle: + type: string + parameterEnumValues: + items: + type: string + type: array + parameterLocation: + enum: + - any + - cookie + - form-data + - header + - path + - query + type: string + regularExpression: + type: string + sensitiveParameter: + type: boolean + signatureOverrides: + items: + properties: + enabled: + type: boolean + name: + type: string + signatureId: + type: integer + tag: + type: string + type: object + type: array + staticValues: + type: string + type: + enum: + - explicit + - wildcard + type: string + url: + properties: + method: + enum: + - ACL + - BCOPY + - BDELETE + - BMOVE + - BPROPFIND + - BPROPPATCH + - CHECKIN + - CHECKOUT + - CONNECT + - COPY + - DELETE + - GET + - HEAD + - LINK + - LOCK + - MERGE + - MKCOL + - MKWORKSPACE + - MOVE + - NOTIFY + - OPTIONS + - PATCH + - POLL + - POST + - PROPFIND + - PROPPATCH + - PUT + - REPORT + - RPC_IN_DATA + - RPC_OUT_DATA + - SEARCH + - SUBSCRIBE + - TRACE + - TRACK + - UNLINK + - UNLOCK + - UNSUBSCRIBE + - VERSION_CONTROL + - X-MS-ENUMATTS + - '*' + type: string + name: + type: string + protocol: + enum: + - http + - https + type: string + type: + enum: + - explicit + - wildcard + type: string + type: object + valueMetacharOverrides: + items: + properties: + isAllowed: + type: boolean + metachar: + type: string + type: object + type: array + valueType: + enum: + - array + - auto-detect + - dynamic-content + - dynamic-parameter-name + - ignore + - json + - object + - openapi-array + - static-content + - user-input + - xml + type: string + wildcardOrder: + type: integer + type: object + urlSegmentIndex: + type: integer + type: object + type: array + protocol: + enum: + - http + - https + type: string + signatureOverrides: + items: + properties: + enabled: + type: boolean + name: + type: string + signatureId: + type: integer + tag: + type: string + type: object + type: array + type: + enum: + - explicit + - wildcard + type: string + urlContentProfiles: + items: + properties: + contentProfile: + properties: + name: + type: string + type: object + headerName: + type: string + headerOrder: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + headerValue: + type: string + name: + type: string + type: + enum: + - apply-content-signatures + - apply-value-and-content-signatures + - disallow + - do-nothing + - form-data + - gwt + - json + - xml + - grpc + type: string + type: object + type: array + wildcardOrder: + type: integer + type: object + type: array + whitelist-ips: + items: + properties: + $action: + enum: + - delete + type: string + blockRequests: + enum: + - always + - never + - policy-default + type: string + ipAddress: + pattern: '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}' + type: string + ipMask: + pattern: '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}' + type: string + neverLogRequests: + type: boolean + type: object + type: array + whitelistIpReference: + properties: + link: + pattern: ^http + type: string + type: object + xml-profiles: + items: + properties: + $action: + enum: + - delete + type: string + attackSignaturesCheck: + type: boolean + defenseAttributes: + properties: + allowCDATA: + type: boolean + allowDTDs: + type: boolean + allowExternalReferences: + type: boolean + allowProcessingInstructions: + type: boolean + maximumAttributeValueLength: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + maximumAttributesPerElement: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + maximumChildrenPerElement: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + maximumDocumentDepth: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + maximumDocumentSize: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + maximumElements: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + maximumNSDeclarations: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + maximumNameLength: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + maximumNamespaceLength: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + tolerateCloseTagShorthand: + type: boolean + tolerateLeadingWhiteSpace: + type: boolean + tolerateNumericNames: + type: boolean + type: object + description: + type: string + enableWss: + type: boolean + followSchemaLinks: + type: boolean + name: + type: string + signatureOverrides: + items: + properties: + enabled: + type: boolean + name: + type: string + signatureId: + type: integer + tag: + type: string + type: object + type: array + useXmlResponsePage: + type: boolean + type: object + type: array + xml-validation-files: + items: + properties: + $action: + enum: + - delete + type: string + contents: + type: string + fileName: + type: string + isBase64: + type: boolean + type: object + type: array + xmlProfileReference: + properties: + link: + pattern: ^http + type: string + type: object + xmlValidationFileReference: + properties: + link: + pattern: ^http + type: string + type: object + type: object + type: object + type: object + served: true + storage: true +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.13.0 + name: apusersigs.appprotect.f5.com +spec: + group: appprotect.f5.com + names: + kind: APUserSig + listKind: APUserSigList + plural: apusersigs + singular: apusersig + preserveUnknownFields: false + scope: Namespaced + versions: + - name: v1beta1 + schema: + openAPIV3Schema: + description: APUserSig is the Schema for the apusersigs API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: APUserSigSpec defines the desired state of APUserSig + properties: + properties: + type: string + signatures: + items: + properties: + accuracy: + enum: + - high + - medium + - low + type: string + attackType: + properties: + name: + type: string + type: object + description: + type: string + name: + type: string + references: + properties: + type: + enum: + - bugtraq + - cve + - nessus + - url + type: string + value: + type: string + type: object + risk: + enum: + - high + - medium + - low + type: string + rule: + type: string + signatureType: + enum: + - request + - response + type: string + systems: + items: + properties: + name: + type: string + type: object + type: array + type: object + type: array + softwareVersion: + type: string + tag: + type: string + type: object + type: object + served: true + storage: true diff --git a/deploy/crds.yaml b/deploy/crds.yaml new file mode 100644 index 0000000000..018ca2684e --- /dev/null +++ b/deploy/crds.yaml @@ -0,0 +1,2147 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.17.0 + name: dnsendpoints.externaldns.nginx.org +spec: + group: externaldns.nginx.org + names: + kind: DNSEndpoint + listKind: DNSEndpointList + plural: dnsendpoints + singular: dnsendpoint + scope: Namespaced + versions: + - name: v1 + schema: + openAPIV3Schema: + description: DNSEndpoint is the CRD wrapper for Endpoint + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: DNSEndpointSpec holds information about endpoints. + properties: + endpoints: + items: + description: Endpoint describes DNS Endpoint. + properties: + dnsName: + description: The hostname for the DNS record + type: string + labels: + additionalProperties: + type: string + description: Labels stores labels defined for the Endpoint + type: object + providerSpecific: + description: ProviderSpecific stores provider specific config + items: + description: ProviderSpecificProperty represents provider + specific config property. + properties: + name: + description: Name of the property + type: string + value: + description: Value of the property + type: string + type: object + type: array + recordTTL: + description: TTL for the record + format: int64 + type: integer + recordType: + description: RecordType type of record, e.g. CNAME, A, SRV, + TXT, MX + type: string + targets: + description: The targets the DNS service points to + items: + type: string + type: array + type: object + type: array + type: object + status: + description: DNSEndpointStatus represents generation observed by the external + dns controller. + properties: + observedGeneration: + description: The generation observed by by the external-dns controller. + format: int64 + type: integer + type: object + type: object + served: true + storage: true + subresources: + status: {} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.17.0 + name: globalconfigurations.k8s.nginx.org +spec: + group: k8s.nginx.org + names: + kind: GlobalConfiguration + listKind: GlobalConfigurationList + plural: globalconfigurations + shortNames: + - gc + singular: globalconfiguration + scope: Namespaced + versions: + - name: v1 + schema: + openAPIV3Schema: + description: GlobalConfiguration defines the GlobalConfiguration resource. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: GlobalConfigurationSpec is the spec of the GlobalConfiguration + resource. + properties: + listeners: + items: + description: Listener defines a listener. + properties: + ipv4: + type: string + ipv6: + type: string + name: + type: string + port: + type: integer + protocol: + type: string + ssl: + type: boolean + type: object + type: array + type: object + type: object + served: true + storage: true +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.17.0 + name: policies.k8s.nginx.org +spec: + group: k8s.nginx.org + names: + kind: Policy + listKind: PolicyList + plural: policies + shortNames: + - pol + singular: policy + scope: Namespaced + versions: + - additionalPrinterColumns: + - description: Current state of the Policy. If the resource has a valid status, + it means it has been validated and accepted by the Ingress Controller. + jsonPath: .status.state + name: State + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1 + schema: + openAPIV3Schema: + description: Policy defines a Policy for VirtualServer and VirtualServerRoute + resources. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: |- + PolicySpec is the spec of the Policy resource. + The spec includes multiple fields, where each field represents a different policy. + Only one policy (field) is allowed. + properties: + accessControl: + description: AccessControl defines an access policy based on the source + IP of a request. + properties: + allow: + items: + type: string + type: array + deny: + items: + type: string + type: array + type: object + apiKey: + description: APIKey defines an API Key policy. + properties: + clientSecret: + type: string + suppliedIn: + description: SuppliedIn defines the locations API Key should be + supplied in. + properties: + header: + items: + type: string + type: array + query: + items: + type: string + type: array + type: object + type: object + basicAuth: + description: BasicAuth holds HTTP Basic authentication configuration + properties: + realm: + type: string + secret: + type: string + type: object + egressMTLS: + description: EgressMTLS defines an Egress MTLS policy. + properties: + ciphers: + type: string + protocols: + type: string + serverName: + type: boolean + sessionReuse: + type: boolean + sslName: + type: string + tlsSecret: + type: string + trustedCertSecret: + type: string + verifyDepth: + type: integer + verifyServer: + type: boolean + type: object + ingressClassName: + type: string + ingressMTLS: + description: IngressMTLS defines an Ingress MTLS policy. + properties: + clientCertSecret: + type: string + crlFileName: + type: string + verifyClient: + type: string + verifyDepth: + type: integer + type: object + jwt: + description: JWTAuth holds JWT authentication configuration. + properties: + jwksURI: + type: string + keyCache: + type: string + realm: + type: string + secret: + type: string + token: + type: string + type: object + oidc: + description: OIDC defines an Open ID Connect policy. + properties: + accessTokenEnable: + type: boolean + authEndpoint: + type: string + authExtraArgs: + items: + type: string + type: array + clientID: + type: string + clientSecret: + type: string + endSessionEndpoint: + type: string + jwksURI: + type: string + postLogoutRedirectURI: + type: string + redirectURI: + type: string + scope: + type: string + tokenEndpoint: + type: string + zoneSyncLeeway: + type: integer + type: object + rateLimit: + description: RateLimit defines a rate limit policy. + properties: + burst: + type: integer + delay: + type: integer + dryRun: + type: boolean + key: + type: string + logLevel: + type: string + noDelay: + type: boolean + rate: + type: string + rejectCode: + type: integer + scale: + type: boolean + zoneSize: + type: string + type: object + waf: + description: WAF defines an WAF policy. + properties: + apBundle: + type: string + apPolicy: + type: string + enable: + type: boolean + securityLog: + description: SecurityLog defines the security log of a WAF policy. + properties: + apLogBundle: + type: string + apLogConf: + type: string + enable: + type: boolean + logDest: + type: string + type: object + securityLogs: + items: + description: SecurityLog defines the security log of a WAF policy. + properties: + apLogBundle: + type: string + apLogConf: + type: string + enable: + type: boolean + logDest: + type: string + type: object + type: array + type: object + type: object + status: + description: PolicyStatus is the status of the policy resource + properties: + message: + type: string + reason: + type: string + state: + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.17.0 + name: transportservers.k8s.nginx.org +spec: + group: k8s.nginx.org + names: + kind: TransportServer + listKind: TransportServerList + plural: transportservers + shortNames: + - ts + singular: transportserver + scope: Namespaced + versions: + - additionalPrinterColumns: + - description: Current state of the TransportServer. If the resource has a valid + status, it means it has been validated and accepted by the Ingress Controller. + jsonPath: .status.state + name: State + type: string + - jsonPath: .status.reason + name: Reason + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1 + schema: + openAPIV3Schema: + description: TransportServer defines the TransportServer resource. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: TransportServerSpec is the spec of the TransportServer resource. + properties: + action: + description: TransportServerAction defines an action. + properties: + pass: + type: string + type: object + host: + type: string + ingressClassName: + type: string + listener: + description: TransportServerListener defines a listener for a TransportServer. + properties: + name: + type: string + protocol: + type: string + type: object + serverSnippets: + type: string + sessionParameters: + description: SessionParameters defines session parameters. + properties: + timeout: + type: string + type: object + streamSnippets: + type: string + tls: + description: TransportServerTLS defines TransportServerTLS configuration + for a TransportServer. + properties: + secret: + type: string + type: object + upstreamParameters: + description: UpstreamParameters defines parameters for an upstream. + properties: + connectTimeout: + type: string + nextUpstream: + type: boolean + nextUpstreamTimeout: + type: string + nextUpstreamTries: + type: integer + udpRequests: + type: integer + udpResponses: + type: integer + type: object + upstreams: + items: + description: TransportServerUpstream defines an upstream. + properties: + backup: + type: string + backupPort: + type: integer + failTimeout: + type: string + healthCheck: + description: TransportServerHealthCheck defines the parameters + for active Upstream HealthChecks. + properties: + enable: + type: boolean + fails: + type: integer + interval: + type: string + jitter: + type: string + match: + description: TransportServerMatch defines the parameters + of a custom health check. + properties: + expect: + type: string + send: + type: string + type: object + passes: + type: integer + port: + type: integer + timeout: + type: string + type: object + loadBalancingMethod: + type: string + maxConns: + type: integer + maxFails: + type: integer + name: + type: string + port: + type: integer + service: + type: string + type: object + type: array + type: object + status: + description: TransportServerStatus defines the status for the TransportServer + resource. + properties: + message: + type: string + reason: + type: string + state: + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.17.0 + name: virtualserverroutes.k8s.nginx.org +spec: + group: k8s.nginx.org + names: + kind: VirtualServerRoute + listKind: VirtualServerRouteList + plural: virtualserverroutes + shortNames: + - vsr + singular: virtualserverroute + scope: Namespaced + versions: + - additionalPrinterColumns: + - description: Current state of the VirtualServerRoute. If the resource has a + valid status, it means it has been validated and accepted by the Ingress Controller. + jsonPath: .status.state + name: State + type: string + - jsonPath: .spec.host + name: Host + type: string + - jsonPath: .status.externalEndpoints[*].ip + name: IP + type: string + - jsonPath: .status.externalEndpoints[*].hostname + name: ExternalHostname + priority: 1 + type: string + - jsonPath: .status.externalEndpoints[*].ports + name: Ports + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1 + schema: + openAPIV3Schema: + description: VirtualServerRoute defines the VirtualServerRoute resource. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: VirtualServerRouteSpec is the spec of the VirtualServerRoute + resource. + properties: + host: + type: string + ingressClassName: + type: string + subroutes: + items: + description: Route defines a route. + properties: + action: + description: Action defines an action. + properties: + pass: + type: string + proxy: + description: ActionProxy defines a proxy in an Action. + properties: + requestHeaders: + description: ProxyRequestHeaders defines the request + headers manipulation in an ActionProxy. + properties: + pass: + type: boolean + set: + items: + description: Header defines an HTTP Header. + properties: + name: + type: string + value: + type: string + type: object + type: array + type: object + responseHeaders: + description: ProxyResponseHeaders defines the response + headers manipulation in an ActionProxy. + properties: + add: + items: + description: AddHeader defines an HTTP Header + with an optional Always field to use with the + add_header NGINX directive. + properties: + always: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + hide: + items: + type: string + type: array + ignore: + items: + type: string + type: array + pass: + items: + type: string + type: array + type: object + rewritePath: + type: string + upstream: + type: string + type: object + redirect: + description: ActionRedirect defines a redirect in an Action. + properties: + code: + type: integer + url: + type: string + type: object + return: + description: ActionReturn defines a return in an Action. + properties: + body: + type: string + code: + type: integer + headers: + items: + description: Header defines an HTTP Header. + properties: + name: + type: string + value: + type: string + type: object + type: array + type: + type: string + type: object + type: object + dos: + type: string + errorPages: + items: + description: ErrorPage defines an ErrorPage in a Route. + properties: + codes: + items: + type: integer + type: array + redirect: + description: ErrorPageRedirect defines a redirect for + an ErrorPage. + properties: + code: + type: integer + url: + type: string + type: object + return: + description: ErrorPageReturn defines a return for an ErrorPage. + properties: + body: + type: string + code: + type: integer + headers: + items: + description: Header defines an HTTP Header. + properties: + name: + type: string + value: + type: string + type: object + type: array + type: + type: string + type: object + type: object + type: array + location-snippets: + type: string + matches: + items: + description: Match defines a match. + properties: + action: + description: Action defines an action. + properties: + pass: + type: string + proxy: + description: ActionProxy defines a proxy in an Action. + properties: + requestHeaders: + description: ProxyRequestHeaders defines the request + headers manipulation in an ActionProxy. + properties: + pass: + type: boolean + set: + items: + description: Header defines an HTTP Header. + properties: + name: + type: string + value: + type: string + type: object + type: array + type: object + responseHeaders: + description: ProxyResponseHeaders defines the + response headers manipulation in an ActionProxy. + properties: + add: + items: + description: AddHeader defines an HTTP Header + with an optional Always field to use with + the add_header NGINX directive. + properties: + always: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + hide: + items: + type: string + type: array + ignore: + items: + type: string + type: array + pass: + items: + type: string + type: array + type: object + rewritePath: + type: string + upstream: + type: string + type: object + redirect: + description: ActionRedirect defines a redirect in + an Action. + properties: + code: + type: integer + url: + type: string + type: object + return: + description: ActionReturn defines a return in an Action. + properties: + body: + type: string + code: + type: integer + headers: + items: + description: Header defines an HTTP Header. + properties: + name: + type: string + value: + type: string + type: object + type: array + type: + type: string + type: object + type: object + conditions: + items: + description: Condition defines a condition in a MatchRule. + properties: + argument: + type: string + cookie: + type: string + header: + type: string + value: + type: string + variable: + type: string + type: object + type: array + splits: + items: + description: Split defines a split. + properties: + action: + description: Action defines an action. + properties: + pass: + type: string + proxy: + description: ActionProxy defines a proxy in + an Action. + properties: + requestHeaders: + description: ProxyRequestHeaders defines + the request headers manipulation in an + ActionProxy. + properties: + pass: + type: boolean + set: + items: + description: Header defines an HTTP + Header. + properties: + name: + type: string + value: + type: string + type: object + type: array + type: object + responseHeaders: + description: ProxyResponseHeaders defines + the response headers manipulation in an + ActionProxy. + properties: + add: + items: + description: AddHeader defines an + HTTP Header with an optional Always + field to use with the add_header + NGINX directive. + properties: + always: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + hide: + items: + type: string + type: array + ignore: + items: + type: string + type: array + pass: + items: + type: string + type: array + type: object + rewritePath: + type: string + upstream: + type: string + type: object + redirect: + description: ActionRedirect defines a redirect + in an Action. + properties: + code: + type: integer + url: + type: string + type: object + return: + description: ActionReturn defines a return in + an Action. + properties: + body: + type: string + code: + type: integer + headers: + items: + description: Header defines an HTTP Header. + properties: + name: + type: string + value: + type: string + type: object + type: array + type: + type: string + type: object + type: object + weight: + type: integer + type: object + type: array + type: object + type: array + path: + type: string + policies: + items: + description: PolicyReference references a policy by name and + an optional namespace. + properties: + name: + type: string + namespace: + type: string + type: object + type: array + route: + type: string + splits: + items: + description: Split defines a split. + properties: + action: + description: Action defines an action. + properties: + pass: + type: string + proxy: + description: ActionProxy defines a proxy in an Action. + properties: + requestHeaders: + description: ProxyRequestHeaders defines the request + headers manipulation in an ActionProxy. + properties: + pass: + type: boolean + set: + items: + description: Header defines an HTTP Header. + properties: + name: + type: string + value: + type: string + type: object + type: array + type: object + responseHeaders: + description: ProxyResponseHeaders defines the + response headers manipulation in an ActionProxy. + properties: + add: + items: + description: AddHeader defines an HTTP Header + with an optional Always field to use with + the add_header NGINX directive. + properties: + always: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + hide: + items: + type: string + type: array + ignore: + items: + type: string + type: array + pass: + items: + type: string + type: array + type: object + rewritePath: + type: string + upstream: + type: string + type: object + redirect: + description: ActionRedirect defines a redirect in + an Action. + properties: + code: + type: integer + url: + type: string + type: object + return: + description: ActionReturn defines a return in an Action. + properties: + body: + type: string + code: + type: integer + headers: + items: + description: Header defines an HTTP Header. + properties: + name: + type: string + value: + type: string + type: object + type: array + type: + type: string + type: object + type: object + weight: + type: integer + type: object + type: array + type: object + type: array + upstreams: + items: + description: Upstream defines an upstream. + properties: + backup: + type: string + backupPort: + type: integer + buffer-size: + type: string + buffering: + type: boolean + buffers: + description: UpstreamBuffers defines Buffer Configuration for + an Upstream. + properties: + number: + type: integer + size: + type: string + type: object + client-max-body-size: + type: string + connect-timeout: + type: string + fail-timeout: + type: string + healthCheck: + description: HealthCheck defines the parameters for active Upstream + HealthChecks. + properties: + connect-timeout: + type: string + enable: + type: boolean + fails: + type: integer + grpcService: + type: string + grpcStatus: + type: integer + headers: + items: + description: Header defines an HTTP Header. + properties: + name: + type: string + value: + type: string + type: object + type: array + interval: + type: string + jitter: + type: string + keepalive-time: + type: string + mandatory: + type: boolean + passes: + type: integer + path: + type: string + persistent: + type: boolean + port: + type: integer + read-timeout: + type: string + send-timeout: + type: string + statusMatch: + type: string + tls: + description: UpstreamTLS defines a TLS configuration for + an Upstream. + properties: + enable: + type: boolean + type: object + type: object + keepalive: + type: integer + lb-method: + type: string + max-conns: + type: integer + max-fails: + type: integer + name: + type: string + next-upstream: + type: string + next-upstream-timeout: + type: string + next-upstream-tries: + type: integer + ntlm: + type: boolean + port: + type: integer + queue: + description: UpstreamQueue defines Queue Configuration for an + Upstream. + properties: + size: + type: integer + timeout: + type: string + type: object + read-timeout: + type: string + send-timeout: + type: string + service: + type: string + sessionCookie: + description: SessionCookie defines the parameters for session + persistence. + properties: + domain: + type: string + enable: + type: boolean + expires: + type: string + httpOnly: + type: boolean + name: + type: string + path: + type: string + samesite: + type: string + secure: + type: boolean + type: object + slow-start: + type: string + subselector: + additionalProperties: + type: string + type: object + tls: + description: UpstreamTLS defines a TLS configuration for an + Upstream. + properties: + enable: + type: boolean + type: object + type: + type: string + use-cluster-ip: + type: boolean + type: object + type: array + type: object + status: + description: VirtualServerRouteStatus defines the status for the VirtualServerRoute + resource. + properties: + externalEndpoints: + items: + description: ExternalEndpoint defines the IP/ Hostname and ports + used to connect to this resource. + properties: + hostname: + type: string + ip: + type: string + ports: + type: string + type: object + type: array + message: + type: string + reason: + type: string + referencedBy: + type: string + state: + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.17.0 + name: virtualservers.k8s.nginx.org +spec: + group: k8s.nginx.org + names: + kind: VirtualServer + listKind: VirtualServerList + plural: virtualservers + shortNames: + - vs + singular: virtualserver + scope: Namespaced + versions: + - additionalPrinterColumns: + - description: Current state of the VirtualServer. If the resource has a valid + status, it means it has been validated and accepted by the Ingress Controller. + jsonPath: .status.state + name: State + type: string + - jsonPath: .spec.host + name: Host + type: string + - jsonPath: .status.externalEndpoints[*].ip + name: IP + type: string + - jsonPath: .status.externalEndpoints[*].hostname + name: ExternalHostname + priority: 1 + type: string + - jsonPath: .status.externalEndpoints[*].ports + name: Ports + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1 + schema: + openAPIV3Schema: + description: VirtualServer defines the VirtualServer resource. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: VirtualServerSpec is the spec of the VirtualServer resource. + properties: + dos: + type: string + externalDNS: + description: ExternalDNS defines externaldns sub-resource of a virtual + server. + properties: + enable: + type: boolean + labels: + additionalProperties: + type: string + description: Labels stores labels defined for the Endpoint + type: object + providerSpecific: + description: ProviderSpecific stores provider specific config + items: + description: |- + ProviderSpecificProperty defines specific property + for using with ExternalDNS sub-resource. + properties: + name: + description: Name of the property + type: string + value: + description: Value of the property + type: string + type: object + type: array + recordTTL: + description: TTL for the record + format: int64 + type: integer + recordType: + type: string + type: object + gunzip: + type: boolean + host: + type: string + http-snippets: + type: string + ingressClassName: + type: string + internalRoute: + description: InternalRoute allows for the configuration of internal + routing. + type: boolean + listener: + description: VirtualServerListener references a custom http and/or + https listener defined in GlobalConfiguration. + properties: + http: + type: string + https: + type: string + type: object + policies: + items: + description: PolicyReference references a policy by name and an + optional namespace. + properties: + name: + type: string + namespace: + type: string + type: object + type: array + routes: + items: + description: Route defines a route. + properties: + action: + description: Action defines an action. + properties: + pass: + type: string + proxy: + description: ActionProxy defines a proxy in an Action. + properties: + requestHeaders: + description: ProxyRequestHeaders defines the request + headers manipulation in an ActionProxy. + properties: + pass: + type: boolean + set: + items: + description: Header defines an HTTP Header. + properties: + name: + type: string + value: + type: string + type: object + type: array + type: object + responseHeaders: + description: ProxyResponseHeaders defines the response + headers manipulation in an ActionProxy. + properties: + add: + items: + description: AddHeader defines an HTTP Header + with an optional Always field to use with the + add_header NGINX directive. + properties: + always: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + hide: + items: + type: string + type: array + ignore: + items: + type: string + type: array + pass: + items: + type: string + type: array + type: object + rewritePath: + type: string + upstream: + type: string + type: object + redirect: + description: ActionRedirect defines a redirect in an Action. + properties: + code: + type: integer + url: + type: string + type: object + return: + description: ActionReturn defines a return in an Action. + properties: + body: + type: string + code: + type: integer + headers: + items: + description: Header defines an HTTP Header. + properties: + name: + type: string + value: + type: string + type: object + type: array + type: + type: string + type: object + type: object + dos: + type: string + errorPages: + items: + description: ErrorPage defines an ErrorPage in a Route. + properties: + codes: + items: + type: integer + type: array + redirect: + description: ErrorPageRedirect defines a redirect for + an ErrorPage. + properties: + code: + type: integer + url: + type: string + type: object + return: + description: ErrorPageReturn defines a return for an ErrorPage. + properties: + body: + type: string + code: + type: integer + headers: + items: + description: Header defines an HTTP Header. + properties: + name: + type: string + value: + type: string + type: object + type: array + type: + type: string + type: object + type: object + type: array + location-snippets: + type: string + matches: + items: + description: Match defines a match. + properties: + action: + description: Action defines an action. + properties: + pass: + type: string + proxy: + description: ActionProxy defines a proxy in an Action. + properties: + requestHeaders: + description: ProxyRequestHeaders defines the request + headers manipulation in an ActionProxy. + properties: + pass: + type: boolean + set: + items: + description: Header defines an HTTP Header. + properties: + name: + type: string + value: + type: string + type: object + type: array + type: object + responseHeaders: + description: ProxyResponseHeaders defines the + response headers manipulation in an ActionProxy. + properties: + add: + items: + description: AddHeader defines an HTTP Header + with an optional Always field to use with + the add_header NGINX directive. + properties: + always: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + hide: + items: + type: string + type: array + ignore: + items: + type: string + type: array + pass: + items: + type: string + type: array + type: object + rewritePath: + type: string + upstream: + type: string + type: object + redirect: + description: ActionRedirect defines a redirect in + an Action. + properties: + code: + type: integer + url: + type: string + type: object + return: + description: ActionReturn defines a return in an Action. + properties: + body: + type: string + code: + type: integer + headers: + items: + description: Header defines an HTTP Header. + properties: + name: + type: string + value: + type: string + type: object + type: array + type: + type: string + type: object + type: object + conditions: + items: + description: Condition defines a condition in a MatchRule. + properties: + argument: + type: string + cookie: + type: string + header: + type: string + value: + type: string + variable: + type: string + type: object + type: array + splits: + items: + description: Split defines a split. + properties: + action: + description: Action defines an action. + properties: + pass: + type: string + proxy: + description: ActionProxy defines a proxy in + an Action. + properties: + requestHeaders: + description: ProxyRequestHeaders defines + the request headers manipulation in an + ActionProxy. + properties: + pass: + type: boolean + set: + items: + description: Header defines an HTTP + Header. + properties: + name: + type: string + value: + type: string + type: object + type: array + type: object + responseHeaders: + description: ProxyResponseHeaders defines + the response headers manipulation in an + ActionProxy. + properties: + add: + items: + description: AddHeader defines an + HTTP Header with an optional Always + field to use with the add_header + NGINX directive. + properties: + always: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + hide: + items: + type: string + type: array + ignore: + items: + type: string + type: array + pass: + items: + type: string + type: array + type: object + rewritePath: + type: string + upstream: + type: string + type: object + redirect: + description: ActionRedirect defines a redirect + in an Action. + properties: + code: + type: integer + url: + type: string + type: object + return: + description: ActionReturn defines a return in + an Action. + properties: + body: + type: string + code: + type: integer + headers: + items: + description: Header defines an HTTP Header. + properties: + name: + type: string + value: + type: string + type: object + type: array + type: + type: string + type: object + type: object + weight: + type: integer + type: object + type: array + type: object + type: array + path: + type: string + policies: + items: + description: PolicyReference references a policy by name and + an optional namespace. + properties: + name: + type: string + namespace: + type: string + type: object + type: array + route: + type: string + splits: + items: + description: Split defines a split. + properties: + action: + description: Action defines an action. + properties: + pass: + type: string + proxy: + description: ActionProxy defines a proxy in an Action. + properties: + requestHeaders: + description: ProxyRequestHeaders defines the request + headers manipulation in an ActionProxy. + properties: + pass: + type: boolean + set: + items: + description: Header defines an HTTP Header. + properties: + name: + type: string + value: + type: string + type: object + type: array + type: object + responseHeaders: + description: ProxyResponseHeaders defines the + response headers manipulation in an ActionProxy. + properties: + add: + items: + description: AddHeader defines an HTTP Header + with an optional Always field to use with + the add_header NGINX directive. + properties: + always: + type: boolean + name: + type: string + value: + type: string + type: object + type: array + hide: + items: + type: string + type: array + ignore: + items: + type: string + type: array + pass: + items: + type: string + type: array + type: object + rewritePath: + type: string + upstream: + type: string + type: object + redirect: + description: ActionRedirect defines a redirect in + an Action. + properties: + code: + type: integer + url: + type: string + type: object + return: + description: ActionReturn defines a return in an Action. + properties: + body: + type: string + code: + type: integer + headers: + items: + description: Header defines an HTTP Header. + properties: + name: + type: string + value: + type: string + type: object + type: array + type: + type: string + type: object + type: object + weight: + type: integer + type: object + type: array + type: object + type: array + server-snippets: + type: string + tls: + description: TLS defines TLS configuration for a VirtualServer. + properties: + cert-manager: + description: CertManager defines a cert manager config for a TLS. + properties: + cluster-issuer: + type: string + common-name: + type: string + duration: + type: string + issue-temp-cert: + type: boolean + issuer: + type: string + issuer-group: + type: string + issuer-kind: + type: string + renew-before: + type: string + usages: + type: string + type: object + redirect: + description: TLSRedirect defines a redirect for a TLS. + properties: + basedOn: + type: string + code: + type: integer + enable: + type: boolean + type: object + secret: + type: string + type: object + upstreams: + items: + description: Upstream defines an upstream. + properties: + backup: + type: string + backupPort: + type: integer + buffer-size: + type: string + buffering: + type: boolean + buffers: + description: UpstreamBuffers defines Buffer Configuration for + an Upstream. + properties: + number: + type: integer + size: + type: string + type: object + client-max-body-size: + type: string + connect-timeout: + type: string + fail-timeout: + type: string + healthCheck: + description: HealthCheck defines the parameters for active Upstream + HealthChecks. + properties: + connect-timeout: + type: string + enable: + type: boolean + fails: + type: integer + grpcService: + type: string + grpcStatus: + type: integer + headers: + items: + description: Header defines an HTTP Header. + properties: + name: + type: string + value: + type: string + type: object + type: array + interval: + type: string + jitter: + type: string + keepalive-time: + type: string + mandatory: + type: boolean + passes: + type: integer + path: + type: string + persistent: + type: boolean + port: + type: integer + read-timeout: + type: string + send-timeout: + type: string + statusMatch: + type: string + tls: + description: UpstreamTLS defines a TLS configuration for + an Upstream. + properties: + enable: + type: boolean + type: object + type: object + keepalive: + type: integer + lb-method: + type: string + max-conns: + type: integer + max-fails: + type: integer + name: + type: string + next-upstream: + type: string + next-upstream-timeout: + type: string + next-upstream-tries: + type: integer + ntlm: + type: boolean + port: + type: integer + queue: + description: UpstreamQueue defines Queue Configuration for an + Upstream. + properties: + size: + type: integer + timeout: + type: string + type: object + read-timeout: + type: string + send-timeout: + type: string + service: + type: string + sessionCookie: + description: SessionCookie defines the parameters for session + persistence. + properties: + domain: + type: string + enable: + type: boolean + expires: + type: string + httpOnly: + type: boolean + name: + type: string + path: + type: string + samesite: + type: string + secure: + type: boolean + type: object + slow-start: + type: string + subselector: + additionalProperties: + type: string + type: object + tls: + description: UpstreamTLS defines a TLS configuration for an + Upstream. + properties: + enable: + type: boolean + type: object + type: + type: string + use-cluster-ip: + type: boolean + type: object + type: array + type: object + status: + description: VirtualServerStatus defines the status for the VirtualServer + resource. + properties: + externalEndpoints: + items: + description: ExternalEndpoint defines the IP/ Hostname and ports + used to connect to this resource. + properties: + hostname: + type: string + ip: + type: string + ports: + type: string + type: object + type: array + message: + type: string + reason: + type: string + state: + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/deployments/README.md b/deployments/README.md index 813085d3b6..f552136f7e 100644 --- a/deployments/README.md +++ b/deployments/README.md @@ -1,3 +1,4 @@ # Installation -This folder includes Kubernetes manifests for installing NGINX or NGINX Plus Ingress Controller. Read the installation instructions [here](https://docs.nginx.com/nginx-ingress-controller/installation/). +This folder includes Kubernetes manifests for installing NGINX or NGINX Plus Ingress Controller. Read the installation +instructions [here](https://docs.nginx.com/nginx-ingress-controller/installation/). diff --git a/deployments/common/crds/appprotect.f5.com_aplogconfs.yaml b/deployments/common/crds/appprotect.f5.com_aplogconfs.yaml deleted file mode 100644 index 53b7fb40d7..0000000000 --- a/deployments/common/crds/appprotect.f5.com_aplogconfs.yaml +++ /dev/null @@ -1,80 +0,0 @@ -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.10.0 - creationTimestamp: null - name: aplogconfs.appprotect.f5.com -spec: - group: appprotect.f5.com - names: - kind: APLogConf - listKind: APLogConfList - plural: aplogconfs - singular: aplogconf - preserveUnknownFields: false - scope: Namespaced - versions: - - name: v1beta1 - schema: - openAPIV3Schema: - description: APLogConf is the Schema for the APLogConfs API - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: APLogConfSpec defines the desired state of APLogConf - properties: - content: - properties: - escaping_characters: - items: - properties: - from: - type: string - to: - type: string - type: object - type: array - format: - enum: - - splunk - - arcsight - - default - - user-defined - - grpc - type: string - format_string: - type: string - list_delimiter: - type: string - list_prefix: - type: string - list_suffix: - type: string - max_message_size: - pattern: ^([1-9]|[1-5][0-9]|6[0-4])k$ - type: string - max_request_size: - pattern: ^([1-9]|[1-9][0-9]|[1-9][0-9]{2}|1[0-9]{3}|20[1-3][0-9]|204[1-8]|any)$ - type: string - type: object - filter: - properties: - request_type: - enum: - - all - - illegal - - blocked - type: string - type: object - type: object - type: object - served: true - storage: true diff --git a/deployments/common/crds/appprotect.f5.com_appolicies.yaml b/deployments/common/crds/appprotect.f5.com_appolicies.yaml deleted file mode 100644 index 8c494414cb..0000000000 --- a/deployments/common/crds/appprotect.f5.com_appolicies.yaml +++ /dev/null @@ -1,1903 +0,0 @@ -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.10.0 - creationTimestamp: null - name: appolicies.appprotect.f5.com -spec: - group: appprotect.f5.com - names: - kind: APPolicy - listKind: APPolicyList - plural: appolicies - singular: appolicy - preserveUnknownFields: false - scope: Namespaced - versions: - - name: v1beta1 - schema: - openAPIV3Schema: - description: APPolicyConfig is the Schema for the APPolicyconfigs API - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: APPolicySpec defines the desired state of APPolicy - properties: - modifications: - items: - properties: - action: - type: string - description: - type: string - entity: - properties: - name: - type: string - type: object - entityChanges: - properties: - type: - type: string - type: object - type: object - x-kubernetes-preserve-unknown-fields: true - type: array - modificationsReference: - properties: - link: - pattern: ^http - type: string - type: object - policy: - description: Defines the App Protect policy - properties: - applicationLanguage: - enum: - - iso-8859-10 - - iso-8859-6 - - windows-1255 - - auto-detect - - koi8-r - - gb18030 - - iso-8859-8 - - windows-1250 - - iso-8859-9 - - windows-1252 - - iso-8859-16 - - gb2312 - - iso-8859-2 - - iso-8859-5 - - windows-1257 - - windows-1256 - - iso-8859-13 - - windows-874 - - windows-1253 - - iso-8859-3 - - euc-jp - - utf-8 - - gbk - - windows-1251 - - big5 - - iso-8859-1 - - shift_jis - - euc-kr - - iso-8859-4 - - iso-8859-7 - - iso-8859-15 - type: string - blocking-settings: - properties: - evasions: - items: - properties: - description: - enum: - - '%u decoding' - - Apache whitespace - - Bad unescape - - Bare byte decoding - - Directory traversals - - IIS backslashes - - IIS Unicode codepoints - - Multiple decoding - type: string - enabled: - type: boolean - maxDecodingPasses: - type: integer - type: object - type: array - http-protocols: - items: - properties: - description: - enum: - - Unescaped space in URL - - Unparsable request content - - Several Content-Length headers - - 'POST request with Content-Length: 0' - - Null in request - - No Host header in HTTP/1.1 request - - Multiple host headers - - Host header contains IP address - - High ASCII characters in headers - - Header name with no header value - - CRLF characters before request start - - Content length should be a positive number - - Chunked request with Content-Length header - - Check maximum number of parameters - - Check maximum number of headers - - Body in GET or HEAD requests - - Bad multipart/form-data request parsing - - Bad multipart parameters parsing - - Bad HTTP version - - Bad host header value - type: string - enabled: - type: boolean - maxHeaders: - type: integer - maxParams: - type: integer - type: object - type: array - violations: - items: - properties: - alarm: - type: boolean - block: - type: boolean - description: - type: string - name: - enum: - - VIOL_GRPC_FORMAT - - VIOL_GRPC_MALFORMED - - VIOL_GRPC_METHOD - - VIOL_PARAMETER_ARRAY_VALUE - - VIOL_PARAMETER_VALUE_REGEXP - - VIOL_CSRF - - VIOL_PARAMETER_VALUE_BASE64 - - VIOL_MANDATORY_HEADER - - VIOL_HEADER_REPEATED - - VIOL_ASM_COOKIE_MODIFIED - - VIOL_BLACKLISTED_IP - - VIOL_COOKIE_EXPIRED - - VIOL_COOKIE_LENGTH - - VIOL_COOKIE_MALFORMED - - VIOL_COOKIE_MODIFIED - - VIOL_DATA_GUARD - - VIOL_ENCODING - - VIOL_EVASION - - VIOL_FILETYPE - - VIOL_FILE_UPLOAD - - VIOL_FILE_UPLOAD_IN_BODY - - VIOL_HEADER_LENGTH - - VIOL_HEADER_METACHAR - - VIOL_HTTP_PROTOCOL - - VIOL_HTTP_RESPONSE_STATUS - - VIOL_JSON_FORMAT - - VIOL_JSON_MALFORMED - - VIOL_JSON_SCHEMA - - VIOL_MANDATORY_PARAMETER - - VIOL_MANDATORY_REQUEST_BODY - - VIOL_METHOD - - VIOL_PARAMETER - - VIOL_PARAMETER_DATA_TYPE - - VIOL_PARAMETER_EMPTY_VALUE - - VIOL_PARAMETER_LOCATION - - VIOL_PARAMETER_MULTIPART_NULL_VALUE - - VIOL_PARAMETER_NAME_METACHAR - - VIOL_PARAMETER_NUMERIC_VALUE - - VIOL_PARAMETER_REPEATED - - VIOL_PARAMETER_STATIC_VALUE - - VIOL_PARAMETER_VALUE_LENGTH - - VIOL_PARAMETER_VALUE_METACHAR - - VIOL_POST_DATA_LENGTH - - VIOL_QUERY_STRING_LENGTH - - VIOL_RATING_THREAT - - VIOL_RATING_NEED_EXAMINATION - - VIOL_REQUEST_MAX_LENGTH - - VIOL_REQUEST_LENGTH - - VIOL_THREAT_CAMPAIGN - - VIOL_URL - - VIOL_URL_CONTENT_TYPE - - VIOL_URL_LENGTH - - VIOL_URL_METACHAR - - VIOL_XML_FORMAT - - VIOL_XML_MALFORMED - type: string - type: object - type: array - type: object - blockingSettingReference: - properties: - link: - pattern: ^http - type: string - type: object - bot-defense: - properties: - mitigations: - properties: - anomalies: - items: - properties: - $action: - enum: - - delete - type: string - action: - enum: - - alarm - - block - - default - - detect - - ignore - type: string - name: - type: string - scoreThreshold: - pattern: '[0-9]|[1-9][0-9]|1[0-4][0-9]|150|default' - type: string - type: object - type: array - browsers: - items: - properties: - $action: - enum: - - delete - type: string - action: - enum: - - alarm - - block - - detect - type: string - browserDefinition: - properties: - $action: - enum: - - delete - type: string - isUserDefined: - type: boolean - matchRegex: - type: string - matchString: - type: string - name: - type: string - type: object - maxVersion: - maximum: 2147483647 - minimum: 0 - type: integer - minVersion: - maximum: 2147483647 - minimum: 0 - type: integer - name: - type: string - type: object - type: array - classes: - items: - properties: - action: - enum: - - alarm - - block - - detect - - ignore - type: string - name: - enum: - - browser - - malicious-bot - - suspicious-browser - - trusted-bot - - unknown - - untrusted-bot - type: string - type: object - type: array - signatures: - items: - properties: - $action: - enum: - - delete - type: string - action: - enum: - - alarm - - block - - detect - - ignore - type: string - name: - type: string - type: object - type: array - type: object - settings: - properties: - caseSensitiveHttpHeaders: - type: boolean - isEnabled: - type: boolean - type: object - type: object - browser-definitions: - items: - properties: - $action: - enum: - - delete - type: string - isUserDefined: - type: boolean - matchRegex: - type: string - matchString: - type: string - name: - type: string - type: object - type: array - caseInsensitive: - type: boolean - character-sets: - items: - properties: - characterSet: - items: - properties: - isAllowed: - type: boolean - metachar: - type: string - type: object - type: array - characterSetType: - enum: - - gwt-content - - header - - json-content - - parameter-name - - parameter-value - - plain-text-content - - url - - xml-content - type: string - type: object - type: array - characterSetReference: - properties: - link: - pattern: ^http - type: string - type: object - cookie-settings: - properties: - maximumCookieHeaderLength: - pattern: any|\d+ - type: string - type: object - cookieReference: - properties: - link: - pattern: ^http - type: string - type: object - cookieSettingsReference: - properties: - link: - pattern: ^http - type: string - type: object - cookies: - items: - properties: - $action: - enum: - - delete - type: string - accessibleOnlyThroughTheHttpProtocol: - type: boolean - attackSignaturesCheck: - type: boolean - decodeValueAsBase64: - enum: - - enabled - - disabled - - required - type: string - enforcementType: - type: string - insertSameSiteAttribute: - enum: - - lax - - none - - none-value - - strict - type: string - name: - type: string - securedOverHttpsConnection: - type: boolean - signatureOverrides: - items: - properties: - enabled: - type: boolean - name: - type: string - signatureId: - type: integer - tag: - type: string - type: object - type: array - type: - enum: - - explicit - - wildcard - type: string - wildcardOrder: - type: integer - type: object - type: array - csrf-protection: - properties: - enabled: - type: boolean - expirationTimeInSeconds: - pattern: disabled|\d+ - type: string - sslOnly: - type: boolean - type: object - csrf-urls: - items: - properties: - $action: - enum: - - delete - type: string - enforcementAction: - enum: - - verify-origin - - none - type: string - method: - enum: - - GET - - POST - - any - type: string - url: - type: string - wildcardOrder: - type: integer - type: object - type: array - data-guard: - properties: - creditCardNumbers: - type: boolean - enabled: - type: boolean - enforcementMode: - enum: - - ignore-urls-in-list - - enforce-urls-in-list - type: string - enforcementUrls: - items: - type: string - type: array - lastCcnDigitsToExpose: - type: integer - lastSsnDigitsToExpose: - type: integer - maskData: - type: boolean - usSocialSecurityNumbers: - type: boolean - type: object - dataGuardReference: - properties: - link: - pattern: ^http - type: string - type: object - description: - type: string - enablePassiveMode: - type: boolean - enforcementMode: - enum: - - transparent - - blocking - type: string - enforcer-settings: - properties: - enforcerStateCookies: - properties: - httpOnlyAttribute: - type: boolean - sameSiteAttribute: - enum: - - lax - - none - - none-value - - strict - type: string - secureAttribute: - enum: - - always - - never - type: string - type: object - type: object - filetypeReference: - properties: - link: - pattern: ^http - type: string - type: object - filetypes: - items: - properties: - $action: - enum: - - delete - type: string - allowed: - type: boolean - checkPostDataLength: - type: boolean - checkQueryStringLength: - type: boolean - checkRequestLength: - type: boolean - checkUrlLength: - type: boolean - name: - type: string - postDataLength: - type: integer - queryStringLength: - type: integer - requestLength: - type: integer - responseCheck: - type: boolean - type: - enum: - - explicit - - wildcard - type: string - urlLength: - type: integer - wildcardOrder: - type: integer - type: object - type: array - fullPath: - type: string - general: - properties: - allowedResponseCodes: - items: - format: int32 - maximum: 999 - minimum: 100 - type: integer - type: array - customXffHeaders: - items: - type: string - type: array - maskCreditCardNumbersInRequest: - type: boolean - trustXff: - type: boolean - type: object - generalReference: - properties: - link: - pattern: ^http - type: string - type: object - grpc-profiles: - items: - properties: - $action: - enum: - - delete - type: string - associateUrls: - type: boolean - attackSignaturesCheck: - type: boolean - defenseAttributes: - properties: - allowUnknownFields: - type: boolean - maximumDataLength: - pattern: any|\d+ - type: string - type: object - description: - type: string - hasIdlFiles: - type: boolean - idlFiles: - items: - properties: - idlFile: - properties: - contents: - type: string - fileName: - type: string - isBase64: - type: boolean - type: object - importUrl: - type: string - isPrimary: - type: boolean - primaryIdlFileName: - type: string - type: object - type: array - metacharElementCheck: - type: boolean - name: - type: string - signatureOverrides: - items: - properties: - enabled: - type: boolean - name: - type: string - signatureId: - type: integer - tag: - type: string - type: object - type: array - type: object - type: array - header-settings: - properties: - maximumHttpHeaderLength: - pattern: any|\d+ - type: string - type: object - headerReference: - properties: - link: - pattern: ^http - type: string - type: object - headerSettingsReference: - properties: - link: - pattern: ^http - type: string - type: object - headers: - items: - properties: - $action: - enum: - - delete - type: string - allowRepeatedOccurrences: - type: boolean - base64Decoding: - type: boolean - checkSignatures: - type: boolean - decodeValueAsBase64: - enum: - - enabled - - disabled - - required - type: string - htmlNormalization: - type: boolean - mandatory: - type: boolean - maskValueInLogs: - type: boolean - name: - type: string - normalizationViolations: - type: boolean - percentDecoding: - type: boolean - signatureOverrides: - items: - properties: - enabled: - type: boolean - name: - type: string - signatureId: - type: integer - tag: - type: string - type: object - type: array - type: - enum: - - explicit - - wildcard - type: string - urlNormalization: - type: boolean - wildcardOrder: - type: integer - type: object - type: array - host-names: - items: - properties: - $action: - enum: - - delete - type: string - includeSubdomains: - type: boolean - name: - type: string - type: object - type: array - idl-files: - items: - properties: - contents: - type: string - fileName: - type: string - isBase64: - type: boolean - type: object - type: array - json-profiles: - items: - properties: - $action: - enum: - - delete - type: string - attackSignaturesCheck: - type: boolean - defenseAttributes: - properties: - maximumArrayLength: - pattern: any|\d+ - type: string - maximumStructureDepth: - pattern: any|\d+ - type: string - maximumTotalLengthOfJSONData: - pattern: any|\d+ - type: string - maximumValueLength: - pattern: any|\d+ - type: string - tolerateJSONParsingWarnings: - type: boolean - type: object - description: - type: string - handleJsonValuesAsParameters: - type: boolean - hasValidationFiles: - type: boolean - metacharOverrides: - items: - properties: - isAllowed: - type: boolean - metachar: - type: string - type: object - type: array - name: - type: string - signatureOverrides: - items: - properties: - enabled: - type: boolean - name: - type: string - signatureId: - type: integer - tag: - type: string - type: object - type: array - validationFiles: - items: - properties: - importUrl: - type: string - isPrimary: - type: boolean - jsonValidationFile: - properties: - $action: - enum: - - delete - type: string - contents: - type: string - fileName: - type: string - isBase64: - type: boolean - type: object - type: object - type: array - type: object - type: array - json-validation-files: - items: - properties: - $action: - enum: - - delete - type: string - contents: - type: string - fileName: - type: string - isBase64: - type: boolean - type: object - type: array - jsonProfileReference: - properties: - link: - pattern: ^http - type: string - type: object - jsonValidationFileReference: - properties: - link: - pattern: ^http - type: string - type: object - methodReference: - properties: - link: - pattern: ^http - type: string - type: object - methods: - items: - properties: - $action: - enum: - - delete - type: string - name: - type: string - type: object - type: array - name: - type: string - open-api-files: - items: - properties: - link: - pattern: ^http - type: string - type: object - type: array - parameterReference: - properties: - link: - pattern: ^http - type: string - type: object - parameters: - items: - properties: - $action: - enum: - - delete - type: string - allowEmptyValue: - type: boolean - allowRepeatedParameterName: - type: boolean - arraySerializationFormat: - enum: - - csv - - form - - label - - matrix - - multi - - multipart - - pipe - - ssv - - tsv - type: string - attackSignaturesCheck: - type: boolean - checkMaxValue: - type: boolean - checkMaxValueLength: - type: boolean - checkMetachars: - type: boolean - checkMinValue: - type: boolean - checkMinValueLength: - type: boolean - checkMultipleOfValue: - type: boolean - contentProfile: - properties: - name: - type: string - type: object - dataType: - enum: - - alpha-numeric - - binary - - boolean - - decimal - - email - - integer - - none - - phone - type: string - decodeValueAsBase64: - enum: - - enabled - - disabled - - required - type: string - disallowFileUploadOfExecutables: - type: boolean - enableRegularExpression: - type: boolean - exclusiveMax: - type: boolean - exclusiveMin: - type: boolean - isBase64: - type: boolean - isCookie: - type: boolean - isHeader: - type: boolean - level: - enum: - - global - - url - type: string - mandatory: - type: boolean - maximumLength: - type: integer - maximumValue: - type: integer - metacharsOnParameterValueCheck: - type: boolean - minimumLength: - type: integer - minimumValue: - type: integer - multipleOf: - type: integer - name: - type: string - nameMetacharOverrides: - items: - properties: - isAllowed: - type: boolean - metachar: - type: string - type: object - type: array - objectSerializationStyle: - type: string - parameterEnumValues: - items: - type: string - type: array - parameterLocation: - enum: - - any - - cookie - - form-data - - header - - path - - query - type: string - regularExpression: - type: string - sensitiveParameter: - type: boolean - signatureOverrides: - items: - properties: - enabled: - type: boolean - name: - type: string - signatureId: - type: integer - tag: - type: string - type: object - type: array - staticValues: - type: string - type: - enum: - - explicit - - wildcard - type: string - url: - type: object - valueMetacharOverrides: - items: - properties: - isAllowed: - type: boolean - metachar: - type: string - type: object - type: array - valueType: - enum: - - array - - auto-detect - - dynamic-content - - dynamic-parameter-name - - ignore - - json - - object - - openapi-array - - static-content - - user-input - - xml - type: string - wildcardOrder: - type: integer - type: object - type: array - response-pages: - items: - properties: - ajaxActionType: - enum: - - alert-popup - - custom - - redirect - type: string - ajaxCustomContent: - type: string - ajaxEnabled: - type: boolean - ajaxPopupMessage: - type: string - ajaxRedirectUrl: - type: string - grpcStatusCode: - pattern: ABORTED|ALREADY_EXISTS|CANCELLED|DATA_LOSS|DEADLINE_EXCEEDED|FAILED_PRECONDITION|INTERNAL|INVALID_ARGUMENT|NOT_FOUND|OK|OUT_OF_RANGE|PERMISSION_DENIED|RESOURCE_EXHAUSTED|UNAUTHENTICATED|UNAVAILABLE|UNIMPLEMENTED|UNKNOWN|d+ - type: string - grpcStatusMessage: - type: string - responseActionType: - enum: - - custom - - default - - erase-cookies - - redirect - - soap-fault - type: string - responseContent: - type: string - responseHeader: - type: string - responsePageType: - enum: - - ajax - - ajax-login - - captcha - - captcha-fail - - default - - failed-login-honeypot - - failed-login-honeypot-ajax - - hijack - - leaked-credentials - - leaked-credentials-ajax - - mobile - - persistent-flow - - xml - - grpc - type: string - responseRedirectUrl: - type: string - type: object - type: array - responsePageReference: - properties: - link: - pattern: ^http - type: string - type: object - sensitive-parameters: - items: - properties: - $action: - enum: - - delete - type: string - name: - type: string - type: object - type: array - sensitiveParameterReference: - properties: - link: - pattern: ^http - type: string - type: object - server-technologies: - items: - properties: - $action: - enum: - - delete - type: string - serverTechnologyName: - enum: - - Jenkins - - SharePoint - - Oracle Application Server - - Python - - Oracle Identity Manager - - Spring Boot - - CouchDB - - SQLite - - Handlebars - - Mustache - - Prototype - - Zend - - Redis - - Underscore.js - - Ember.js - - ZURB Foundation - - ef.js - - Vue.js - - UIKit - - TYPO3 CMS - - RequireJS - - React - - MooTools - - Laravel - - GraphQL - - Google Web Toolkit - - Express.js - - CodeIgniter - - Backbone.js - - AngularJS - - JavaScript - - Nginx - - Jetty - - Joomla - - JavaServer Faces (JSF) - - Ruby - - MongoDB - - Django - - Node.js - - Citrix - - JBoss - - Elasticsearch - - Apache Struts - - XML - - PostgreSQL - - IBM DB2 - - Sybase/ASE - - CGI - - Proxy Servers - - SSI (Server Side Includes) - - Cisco - - Novell - - Macromedia JRun - - BEA Systems WebLogic Server - - Lotus Domino - - MySQL - - Oracle - - Microsoft SQL Server - - PHP - - Outlook Web Access - - Apache/NCSA HTTP Server - - Apache Tomcat - - WordPress - - Macromedia ColdFusion - - Unix/Linux - - Microsoft Windows - - ASP.NET - - Front Page Server Extensions (FPSE) - - IIS - - WebDAV - - ASP - - Java Servlets/JSP - - jQuery - type: string - type: object - type: array - serverTechnologyReference: - properties: - link: - pattern: ^http - type: string - type: object - signature-requirements: - items: - properties: - $action: - enum: - - delete - type: string - tag: - type: string - type: object - type: array - signature-sets: - items: - properties: - $action: - enum: - - delete - type: string - alarm: - type: boolean - block: - type: boolean - name: - type: string - type: object - x-kubernetes-preserve-unknown-fields: true - type: array - signature-settings: - properties: - attackSignatureFalsePositiveMode: - enum: - - detect - - detect-and-allow - - disabled - type: string - minimumAccuracyForAutoAddedSignatures: - enum: - - high - - low - - medium - type: string - type: object - signatureReference: - properties: - link: - pattern: ^http - type: string - type: object - signatureSetReference: - properties: - link: - pattern: ^http - type: string - type: object - signatureSettingReference: - properties: - link: - pattern: ^http - type: string - type: object - signatures: - items: - properties: - enabled: - type: boolean - name: - type: string - signatureId: - type: integer - tag: - type: string - type: object - type: array - softwareVersion: - type: string - template: - properties: - name: - type: string - type: object - threat-campaigns: - items: - properties: - isEnabled: - type: boolean - name: - type: string - type: object - type: array - threatCampaignReference: - properties: - link: - pattern: ^http - type: string - type: object - urlReference: - properties: - link: - pattern: ^http - type: string - type: object - urls: - items: - properties: - $action: - enum: - - delete - type: string - allowRenderingInFrames: - enum: - - never - - only-same - type: string - allowRenderingInFramesOnlyFrom: - type: string - attackSignaturesCheck: - type: boolean - clickjackingProtection: - type: boolean - description: - type: string - disallowFileUploadOfExecutables: - type: boolean - html5CrossOriginRequestsEnforcement: - properties: - allowOriginsEnforcementMode: - enum: - - replace-with - - unmodified - type: string - checkAllowedMethods: - type: boolean - crossDomainAllowedOrigin: - items: - properties: - includeSubDomains: - type: boolean - originName: - type: string - originPort: - pattern: any|\d+ - type: string - originProtocol: - enum: - - http - - http/https - - https - type: string - type: object - type: array - enforcementMode: - enum: - - disabled - - enforce - type: string - type: object - isAllowed: - type: boolean - mandatoryBody: - type: boolean - metacharOverrides: - items: - properties: - isAllowed: - type: boolean - metachar: - type: string - type: object - type: array - metacharsOnUrlCheck: - type: boolean - method: - enum: - - ACL - - BCOPY - - BDELETE - - BMOVE - - BPROPFIND - - BPROPPATCH - - CHECKIN - - CHECKOUT - - CONNECT - - COPY - - DELETE - - GET - - HEAD - - LINK - - LOCK - - MERGE - - MKCOL - - MKWORKSPACE - - MOVE - - NOTIFY - - OPTIONS - - PATCH - - POLL - - POST - - PROPFIND - - PROPPATCH - - PUT - - REPORT - - RPC_IN_DATA - - RPC_OUT_DATA - - SEARCH - - SUBSCRIBE - - TRACE - - TRACK - - UNLINK - - UNLOCK - - UNSUBSCRIBE - - VERSION_CONTROL - - X-MS-ENUMATTS - - '*' - type: string - methodOverrides: - items: - properties: - allowed: - type: boolean - method: - enum: - - ACL - - BCOPY - - BDELETE - - BMOVE - - BPROPFIND - - BPROPPATCH - - CHECKIN - - CHECKOUT - - CONNECT - - COPY - - DELETE - - GET - - HEAD - - LINK - - LOCK - - MERGE - - MKCOL - - MKWORKSPACE - - MOVE - - NOTIFY - - OPTIONS - - PATCH - - POLL - - POST - - PROPFIND - - PROPPATCH - - PUT - - REPORT - - RPC_IN_DATA - - RPC_OUT_DATA - - SEARCH - - SUBSCRIBE - - TRACE - - TRACK - - UNLINK - - UNLOCK - - UNSUBSCRIBE - - VERSION_CONTROL - - X-MS-ENUMATTS - type: string - type: object - type: array - methodsOverrideOnUrlCheck: - type: boolean - name: - type: string - operationId: - type: string - positionalParameters: - items: - properties: - parameter: - properties: - $action: - enum: - - delete - type: string - allowEmptyValue: - type: boolean - allowRepeatedParameterName: - type: boolean - arraySerializationFormat: - enum: - - csv - - form - - label - - matrix - - multi - - multipart - - pipe - - ssv - - tsv - type: string - attackSignaturesCheck: - type: boolean - checkMaxValue: - type: boolean - checkMaxValueLength: - type: boolean - checkMetachars: - type: boolean - checkMinValue: - type: boolean - checkMinValueLength: - type: boolean - checkMultipleOfValue: - type: boolean - contentProfile: - properties: - name: - type: string - type: object - dataType: - enum: - - alpha-numeric - - binary - - boolean - - decimal - - email - - integer - - none - - phone - type: string - decodeValueAsBase64: - enum: - - enabled - - disabled - - required - type: string - disallowFileUploadOfExecutables: - type: boolean - enableRegularExpression: - type: boolean - exclusiveMax: - type: boolean - exclusiveMin: - type: boolean - isBase64: - type: boolean - isCookie: - type: boolean - isHeader: - type: boolean - level: - enum: - - global - - url - type: string - mandatory: - type: boolean - maximumLength: - type: integer - maximumValue: - type: integer - metacharsOnParameterValueCheck: - type: boolean - minimumLength: - type: integer - minimumValue: - type: integer - multipleOf: - type: integer - name: - type: string - nameMetacharOverrides: - items: - properties: - isAllowed: - type: boolean - metachar: - type: string - type: object - type: array - objectSerializationStyle: - type: string - parameterEnumValues: - items: - type: string - type: array - parameterLocation: - enum: - - any - - cookie - - form-data - - header - - path - - query - type: string - regularExpression: - type: string - sensitiveParameter: - type: boolean - signatureOverrides: - items: - properties: - enabled: - type: boolean - name: - type: string - signatureId: - type: integer - tag: - type: string - type: object - type: array - staticValues: - type: string - type: - enum: - - explicit - - wildcard - type: string - url: - type: object - valueMetacharOverrides: - items: - properties: - isAllowed: - type: boolean - metachar: - type: string - type: object - type: array - valueType: - enum: - - array - - auto-detect - - dynamic-content - - dynamic-parameter-name - - ignore - - json - - object - - openapi-array - - static-content - - user-input - - xml - type: string - wildcardOrder: - type: integer - type: object - urlSegmentIndex: - type: integer - type: object - type: array - protocol: - enum: - - http - - https - type: string - signatureOverrides: - items: - properties: - enabled: - type: boolean - name: - type: string - signatureId: - type: integer - tag: - type: string - type: object - type: array - type: - enum: - - explicit - - wildcard - type: string - urlContentProfiles: - items: - properties: - contentProfile: - properties: - name: - type: string - type: object - headerName: - type: string - headerOrder: - type: string - headerValue: - type: string - name: - type: string - type: - enum: - - apply-content-signatures - - apply-value-and-content-signatures - - disallow - - do-nothing - - form-data - - gwt - - json - - xml - - grpc - type: string - type: object - type: array - wildcardOrder: - type: integer - type: object - type: array - whitelist-ips: - items: - properties: - $action: - enum: - - delete - type: string - blockRequests: - enum: - - always - - never - - policy-default - type: string - ipAddress: - pattern: '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}' - type: string - ipMask: - pattern: '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}' - type: string - neverLogRequests: - type: boolean - type: object - type: array - whitelistIpReference: - properties: - link: - pattern: ^http - type: string - type: object - xml-profiles: - items: - properties: - $action: - enum: - - delete - type: string - attackSignaturesCheck: - type: boolean - defenseAttributes: - properties: - allowCDATA: - type: boolean - allowDTDs: - type: boolean - allowExternalReferences: - type: boolean - allowProcessingInstructions: - type: boolean - maximumAttributeValueLength: - pattern: any|\d+ - type: string - maximumAttributesPerElement: - pattern: any|\d+ - type: string - maximumChildrenPerElement: - pattern: any|\d+ - type: string - maximumDocumentDepth: - pattern: any|\d+ - type: string - maximumDocumentSize: - pattern: any|\d+ - type: string - maximumElements: - pattern: any|\d+ - type: string - maximumNSDeclarations: - pattern: any|\d+ - type: string - maximumNameLength: - pattern: any|\d+ - type: string - maximumNamespaceLength: - pattern: any|\d+ - type: string - tolerateCloseTagShorthand: - type: boolean - tolerateLeadingWhiteSpace: - type: boolean - tolerateNumericNames: - type: boolean - type: object - description: - type: string - enableWss: - type: boolean - followSchemaLinks: - type: boolean - name: - type: string - signatureOverrides: - items: - properties: - enabled: - type: boolean - name: - type: string - signatureId: - type: integer - tag: - type: string - type: object - type: array - type: object - type: array - xml-validation-files: - items: - properties: - $action: - enum: - - delete - type: string - contents: - type: string - fileName: - type: string - isBase64: - type: boolean - type: object - type: array - xmlProfileReference: - properties: - link: - pattern: ^http - type: string - type: object - xmlValidationFileReference: - properties: - link: - pattern: ^http - type: string - type: object - type: object - type: object - type: object - served: true - storage: true diff --git a/deployments/common/crds/appprotect.f5.com_apusersigs.yaml b/deployments/common/crds/appprotect.f5.com_apusersigs.yaml deleted file mode 100644 index 34eb0784f4..0000000000 --- a/deployments/common/crds/appprotect.f5.com_apusersigs.yaml +++ /dev/null @@ -1,93 +0,0 @@ -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.10.0 - creationTimestamp: null - name: apusersigs.appprotect.f5.com -spec: - group: appprotect.f5.com - names: - kind: APUserSig - listKind: APUserSigList - plural: apusersigs - singular: apusersig - preserveUnknownFields: false - scope: Namespaced - versions: - - name: v1beta1 - schema: - openAPIV3Schema: - description: APUserSig is the Schema for the apusersigs API - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: APUserSigSpec defines the desired state of APUserSig - properties: - properties: - type: string - signatures: - items: - properties: - accuracy: - enum: - - high - - medium - - low - type: string - attackType: - properties: - name: - type: string - type: object - description: - type: string - name: - type: string - references: - properties: - type: - enum: - - bugtraq - - cve - - nessus - - url - type: string - value: - type: string - type: object - risk: - enum: - - high - - medium - - low - type: string - rule: - type: string - signatureType: - enum: - - request - - response - type: string - systems: - items: - properties: - name: - type: string - type: object - type: array - type: object - type: array - tag: - type: string - type: object - type: object - served: true - storage: true diff --git a/deployments/common/crds/appprotectdos.f5.com_dosprotectedresources.yaml b/deployments/common/crds/appprotectdos.f5.com_dosprotectedresources.yaml deleted file mode 100644 index c18e11737d..0000000000 --- a/deployments/common/crds/appprotectdos.f5.com_dosprotectedresources.yaml +++ /dev/null @@ -1,81 +0,0 @@ -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.10.0 - creationTimestamp: null - name: dosprotectedresources.appprotectdos.f5.com -spec: - group: appprotectdos.f5.com - names: - kind: DosProtectedResource - listKind: DosProtectedResourceList - plural: dosprotectedresources - shortNames: - - pr - singular: dosprotectedresource - scope: Namespaced - versions: - - name: v1beta1 - schema: - openAPIV3Schema: - description: DosProtectedResource defines a Dos protected resource. - type: object - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: DosProtectedResourceSpec defines the properties and values a DosProtectedResource can have. - type: object - properties: - apDosMonitor: - description: 'ApDosMonitor is how NGINX App Protect DoS monitors the stress level of the protected object. The monitor requests are sent from localhost (127.0.0.1). Default value: URI - None, protocol - http1, timeout - NGINX App Protect DoS default.' - type: object - properties: - protocol: - description: Protocol determines if the server listens on http1 / http2 / grpc. The default is http1. - type: string - enum: - - http1 - - http2 - - grpc - timeout: - description: Timeout determines how long (in seconds) should NGINX App Protect DoS wait for a response. Default is 10 seconds for http1/http2 and 5 seconds for grpc. - type: integer - format: int64 - uri: - description: 'URI is the destination to the desired protected object in the nginx.conf:' - type: string - apDosPolicy: - description: ApDosPolicy is the namespace/name of a ApDosPolicy resource - type: string - dosAccessLogDest: - description: DosAccessLogDest is the network address for the access logs - type: string - dosSecurityLog: - description: DosSecurityLog defines the security log of the DosProtectedResource. - type: object - properties: - apDosLogConf: - description: ApDosLogConf is the namespace/name of a APDosLogConf resource - type: string - dosLogDest: - description: DosLogDest is the network address of a logging service, can be either IP or DNS name. - type: string - enable: - description: Enable enables the security logging feature if set to true - type: boolean - enable: - description: Enable enables the DOS feature if set to true - type: boolean - name: - description: Name is the name of protected object, max of 63 characters. - type: string - served: true - storage: true diff --git a/deployments/common/crds/externaldns.nginx.org_dnsendpoints.yaml b/deployments/common/crds/externaldns.nginx.org_dnsendpoints.yaml deleted file mode 100644 index 206626bb59..0000000000 --- a/deployments/common/crds/externaldns.nginx.org_dnsendpoints.yaml +++ /dev/null @@ -1,85 +0,0 @@ -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.10.0 - creationTimestamp: null - name: dnsendpoints.externaldns.nginx.org -spec: - group: externaldns.nginx.org - names: - kind: DNSEndpoint - listKind: DNSEndpointList - plural: dnsendpoints - singular: dnsendpoint - scope: Namespaced - versions: - - name: v1 - schema: - openAPIV3Schema: - description: DNSEndpoint is the CRD wrapper for Endpoint - type: object - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: DNSEndpointSpec holds information about endpoints. - type: object - properties: - endpoints: - type: array - items: - description: Endpoint describes DNS Endpoint. - type: object - properties: - dnsName: - description: The hostname for the DNS record - type: string - labels: - description: Labels stores labels defined for the Endpoint - type: object - additionalProperties: - type: string - providerSpecific: - description: ProviderSpecific stores provider specific config - type: array - items: - description: ProviderSpecificProperty represents provider specific config property. - type: object - properties: - name: - description: Name of the property - type: string - value: - description: Value of the property - type: string - recordTTL: - description: TTL for the record - type: integer - format: int64 - recordType: - description: RecordType type of record, e.g. CNAME, A, SRV, TXT, MX - type: string - targets: - description: The targets the DNS service points to - type: array - items: - type: string - status: - description: DNSEndpointStatus represents generation observed by the external dns controller. - type: object - properties: - observedGeneration: - description: The generation observed by by the external-dns controller. - type: integer - format: int64 - served: true - storage: true - subresources: - status: {} diff --git a/deployments/common/crds/k8s.nginx.org_globalconfigurations.yaml b/deployments/common/crds/k8s.nginx.org_globalconfigurations.yaml deleted file mode 100644 index 5c9ef1f1f1..0000000000 --- a/deployments/common/crds/k8s.nginx.org_globalconfigurations.yaml +++ /dev/null @@ -1,50 +0,0 @@ -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.10.0 - creationTimestamp: null - name: globalconfigurations.k8s.nginx.org -spec: - group: k8s.nginx.org - names: - kind: GlobalConfiguration - listKind: GlobalConfigurationList - plural: globalconfigurations - shortNames: - - gc - singular: globalconfiguration - scope: Namespaced - versions: - - name: v1alpha1 - schema: - openAPIV3Schema: - description: GlobalConfiguration defines the GlobalConfiguration resource. - type: object - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: GlobalConfigurationSpec is the spec of the GlobalConfiguration resource. - type: object - properties: - listeners: - type: array - items: - description: Listener defines a listener. - type: object - properties: - name: - type: string - port: - type: integer - protocol: - type: string - served: true - storage: true diff --git a/deployments/common/crds/k8s.nginx.org_policies.yaml b/deployments/common/crds/k8s.nginx.org_policies.yaml deleted file mode 100644 index 802f351423..0000000000 --- a/deployments/common/crds/k8s.nginx.org_policies.yaml +++ /dev/null @@ -1,290 +0,0 @@ -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.10.0 - creationTimestamp: null - name: policies.k8s.nginx.org -spec: - group: k8s.nginx.org - names: - kind: Policy - listKind: PolicyList - plural: policies - shortNames: - - pol - singular: policy - scope: Namespaced - versions: - - additionalPrinterColumns: - - description: Current state of the Policy. If the resource has a valid status, it means it has been validated and accepted by the Ingress Controller. - jsonPath: .status.state - name: State - type: string - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v1 - schema: - openAPIV3Schema: - description: Policy defines a Policy for VirtualServer and VirtualServerRoute resources. - type: object - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: PolicySpec is the spec of the Policy resource. The spec includes multiple fields, where each field represents a different policy. Only one policy (field) is allowed. - type: object - properties: - accessControl: - description: AccessControl defines an access policy based on the source IP of a request. - type: object - properties: - allow: - type: array - items: - type: string - deny: - type: array - items: - type: string - basicAuth: - description: 'BasicAuth holds HTTP Basic authentication configuration policy status: preview' - type: object - properties: - realm: - type: string - secret: - type: string - egressMTLS: - description: EgressMTLS defines an Egress MTLS policy. - type: object - properties: - ciphers: - type: string - protocols: - type: string - serverName: - type: boolean - sessionReuse: - type: boolean - sslName: - type: string - tlsSecret: - type: string - trustedCertSecret: - type: string - verifyDepth: - type: integer - verifyServer: - type: boolean - ingressClassName: - type: string - ingressMTLS: - description: IngressMTLS defines an Ingress MTLS policy. - type: object - properties: - clientCertSecret: - type: string - verifyClient: - type: string - verifyDepth: - type: integer - jwt: - description: JWTAuth holds JWT authentication configuration. - type: object - properties: - realm: - type: string - secret: - type: string - token: - type: string - oidc: - description: OIDC defines an Open ID Connect policy. - type: object - properties: - authEndpoint: - type: string - clientID: - type: string - clientSecret: - type: string - jwksURI: - type: string - redirectURI: - type: string - scope: - type: string - tokenEndpoint: - type: string - zoneSyncLeeway: - type: integer - rateLimit: - description: RateLimit defines a rate limit policy. - type: object - properties: - burst: - type: integer - delay: - type: integer - dryRun: - type: boolean - key: - type: string - logLevel: - type: string - noDelay: - type: boolean - rate: - type: string - rejectCode: - type: integer - zoneSize: - type: string - waf: - description: WAF defines an WAF policy. - type: object - properties: - apPolicy: - type: string - enable: - type: boolean - securityLog: - description: SecurityLog defines the security log of a WAF policy. - type: object - properties: - apLogConf: - type: string - enable: - type: boolean - logDest: - type: string - securityLogs: - type: array - items: - description: SecurityLog defines the security log of a WAF policy. - type: object - properties: - apLogConf: - type: string - enable: - type: boolean - logDest: - type: string - status: - description: PolicyStatus is the status of the policy resource - type: object - properties: - message: - type: string - reason: - type: string - state: - type: string - served: true - storage: true - subresources: - status: {} - - name: v1alpha1 - schema: - openAPIV3Schema: - description: Policy defines a Policy for VirtualServer and VirtualServerRoute resources. - type: object - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: PolicySpec is the spec of the Policy resource. The spec includes multiple fields, where each field represents a different policy. Only one policy (field) is allowed. - type: object - properties: - accessControl: - description: AccessControl defines an access policy based on the source IP of a request. - type: object - properties: - allow: - type: array - items: - type: string - deny: - type: array - items: - type: string - egressMTLS: - description: EgressMTLS defines an Egress MTLS policy. - type: object - properties: - ciphers: - type: string - protocols: - type: string - serverName: - type: boolean - sessionReuse: - type: boolean - sslName: - type: string - tlsSecret: - type: string - trustedCertSecret: - type: string - verifyDepth: - type: integer - verifyServer: - type: boolean - ingressMTLS: - description: IngressMTLS defines an Ingress MTLS policy. - type: object - properties: - clientCertSecret: - type: string - verifyClient: - type: string - verifyDepth: - type: integer - jwt: - description: JWTAuth holds JWT authentication configuration. - type: object - properties: - realm: - type: string - secret: - type: string - token: - type: string - rateLimit: - description: RateLimit defines a rate limit policy. - type: object - properties: - burst: - type: integer - delay: - type: integer - dryRun: - type: boolean - key: - type: string - logLevel: - type: string - noDelay: - type: boolean - rate: - type: string - rejectCode: - type: integer - zoneSize: - type: string - served: true - storage: false diff --git a/deployments/common/crds/k8s.nginx.org_transportservers.yaml b/deployments/common/crds/k8s.nginx.org_transportservers.yaml deleted file mode 100644 index 67069a61af..0000000000 --- a/deployments/common/crds/k8s.nginx.org_transportservers.yaml +++ /dev/null @@ -1,151 +0,0 @@ -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.10.0 - creationTimestamp: null - name: transportservers.k8s.nginx.org -spec: - group: k8s.nginx.org - names: - kind: TransportServer - listKind: TransportServerList - plural: transportservers - shortNames: - - ts - singular: transportserver - scope: Namespaced - versions: - - additionalPrinterColumns: - - description: Current state of the TransportServer. If the resource has a valid status, it means it has been validated and accepted by the Ingress Controller. - jsonPath: .status.state - name: State - type: string - - jsonPath: .status.reason - name: Reason - type: string - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v1alpha1 - schema: - openAPIV3Schema: - description: TransportServer defines the TransportServer resource. - type: object - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: TransportServerSpec is the spec of the TransportServer resource. - type: object - properties: - action: - description: Action defines an action. - type: object - properties: - pass: - type: string - host: - type: string - ingressClassName: - type: string - listener: - description: TransportServerListener defines a listener for a TransportServer. - type: object - properties: - name: - type: string - protocol: - type: string - serverSnippets: - type: string - sessionParameters: - description: SessionParameters defines session parameters. - type: object - properties: - timeout: - type: string - streamSnippets: - type: string - upstreamParameters: - description: UpstreamParameters defines parameters for an upstream. - type: object - properties: - connectTimeout: - type: string - nextUpstream: - type: boolean - nextUpstreamTimeout: - type: string - nextUpstreamTries: - type: integer - udpRequests: - type: integer - udpResponses: - type: integer - upstreams: - type: array - items: - description: Upstream defines an upstream. - type: object - properties: - failTimeout: - type: string - healthCheck: - description: HealthCheck defines the parameters for active Upstream HealthChecks. - type: object - properties: - enable: - type: boolean - fails: - type: integer - interval: - type: string - jitter: - type: string - match: - description: Match defines the parameters of a custom health check. - type: object - properties: - expect: - type: string - send: - type: string - passes: - type: integer - port: - type: integer - timeout: - type: string - loadBalancingMethod: - type: string - maxConns: - type: integer - maxFails: - type: integer - name: - type: string - port: - type: integer - service: - type: string - status: - description: TransportServerStatus defines the status for the TransportServer resource. - type: object - properties: - message: - type: string - reason: - type: string - state: - type: string - served: true - storage: true - subresources: - status: {} diff --git a/deployments/common/crds/k8s.nginx.org_virtualserverroutes.yaml b/deployments/common/crds/k8s.nginx.org_virtualserverroutes.yaml deleted file mode 100644 index 91c75853b2..0000000000 --- a/deployments/common/crds/k8s.nginx.org_virtualserverroutes.yaml +++ /dev/null @@ -1,635 +0,0 @@ -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.10.0 - creationTimestamp: null - name: virtualserverroutes.k8s.nginx.org -spec: - group: k8s.nginx.org - names: - kind: VirtualServerRoute - listKind: VirtualServerRouteList - plural: virtualserverroutes - shortNames: - - vsr - singular: virtualserverroute - scope: Namespaced - versions: - - additionalPrinterColumns: - - description: Current state of the VirtualServerRoute. If the resource has a valid status, it means it has been validated and accepted by the Ingress Controller. - jsonPath: .status.state - name: State - type: string - - jsonPath: .spec.host - name: Host - type: string - - jsonPath: .status.externalEndpoints[*].ip - name: IP - type: string - - jsonPath: .status.externalEndpoints[*].hostname - name: ExternalHostname - priority: 1 - type: string - - jsonPath: .status.externalEndpoints[*].ports - name: Ports - type: string - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v1 - schema: - openAPIV3Schema: - description: VirtualServerRoute defines the VirtualServerRoute resource. - type: object - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: VirtualServerRouteSpec is the spec of the VirtualServerRoute resource. - type: object - properties: - host: - type: string - ingressClassName: - type: string - subroutes: - type: array - items: - description: Route defines a route. - type: object - properties: - action: - description: Action defines an action. - type: object - properties: - pass: - type: string - proxy: - description: ActionProxy defines a proxy in an Action. - type: object - properties: - requestHeaders: - description: ProxyRequestHeaders defines the request headers manipulation in an ActionProxy. - type: object - properties: - pass: - type: boolean - set: - type: array - items: - description: Header defines an HTTP Header. - type: object - properties: - name: - type: string - value: - type: string - responseHeaders: - description: ProxyResponseHeaders defines the response headers manipulation in an ActionProxy. - type: object - properties: - add: - type: array - items: - description: AddHeader defines an HTTP Header with an optional Always field to use with the add_header NGINX directive. - type: object - properties: - always: - type: boolean - name: - type: string - value: - type: string - hide: - type: array - items: - type: string - ignore: - type: array - items: - type: string - pass: - type: array - items: - type: string - rewritePath: - type: string - upstream: - type: string - redirect: - description: ActionRedirect defines a redirect in an Action. - type: object - properties: - code: - type: integer - url: - type: string - return: - description: ActionReturn defines a return in an Action. - type: object - properties: - body: - type: string - code: - type: integer - type: - type: string - dos: - type: string - errorPages: - type: array - items: - description: ErrorPage defines an ErrorPage in a Route. - type: object - properties: - codes: - type: array - items: - type: integer - redirect: - description: ErrorPageRedirect defines a redirect for an ErrorPage. - type: object - properties: - code: - type: integer - url: - type: string - return: - description: ErrorPageReturn defines a return for an ErrorPage. - type: object - properties: - body: - type: string - code: - type: integer - headers: - type: array - items: - description: Header defines an HTTP Header. - type: object - properties: - name: - type: string - value: - type: string - type: - type: string - location-snippets: - type: string - matches: - type: array - items: - description: Match defines a match. - type: object - properties: - action: - description: Action defines an action. - type: object - properties: - pass: - type: string - proxy: - description: ActionProxy defines a proxy in an Action. - type: object - properties: - requestHeaders: - description: ProxyRequestHeaders defines the request headers manipulation in an ActionProxy. - type: object - properties: - pass: - type: boolean - set: - type: array - items: - description: Header defines an HTTP Header. - type: object - properties: - name: - type: string - value: - type: string - responseHeaders: - description: ProxyResponseHeaders defines the response headers manipulation in an ActionProxy. - type: object - properties: - add: - type: array - items: - description: AddHeader defines an HTTP Header with an optional Always field to use with the add_header NGINX directive. - type: object - properties: - always: - type: boolean - name: - type: string - value: - type: string - hide: - type: array - items: - type: string - ignore: - type: array - items: - type: string - pass: - type: array - items: - type: string - rewritePath: - type: string - upstream: - type: string - redirect: - description: ActionRedirect defines a redirect in an Action. - type: object - properties: - code: - type: integer - url: - type: string - return: - description: ActionReturn defines a return in an Action. - type: object - properties: - body: - type: string - code: - type: integer - type: - type: string - conditions: - type: array - items: - description: Condition defines a condition in a MatchRule. - type: object - properties: - argument: - type: string - cookie: - type: string - header: - type: string - value: - type: string - variable: - type: string - splits: - type: array - items: - description: Split defines a split. - type: object - properties: - action: - description: Action defines an action. - type: object - properties: - pass: - type: string - proxy: - description: ActionProxy defines a proxy in an Action. - type: object - properties: - requestHeaders: - description: ProxyRequestHeaders defines the request headers manipulation in an ActionProxy. - type: object - properties: - pass: - type: boolean - set: - type: array - items: - description: Header defines an HTTP Header. - type: object - properties: - name: - type: string - value: - type: string - responseHeaders: - description: ProxyResponseHeaders defines the response headers manipulation in an ActionProxy. - type: object - properties: - add: - type: array - items: - description: AddHeader defines an HTTP Header with an optional Always field to use with the add_header NGINX directive. - type: object - properties: - always: - type: boolean - name: - type: string - value: - type: string - hide: - type: array - items: - type: string - ignore: - type: array - items: - type: string - pass: - type: array - items: - type: string - rewritePath: - type: string - upstream: - type: string - redirect: - description: ActionRedirect defines a redirect in an Action. - type: object - properties: - code: - type: integer - url: - type: string - return: - description: ActionReturn defines a return in an Action. - type: object - properties: - body: - type: string - code: - type: integer - type: - type: string - weight: - type: integer - path: - type: string - policies: - type: array - items: - description: PolicyReference references a policy by name and an optional namespace. - type: object - properties: - name: - type: string - namespace: - type: string - route: - type: string - splits: - type: array - items: - description: Split defines a split. - type: object - properties: - action: - description: Action defines an action. - type: object - properties: - pass: - type: string - proxy: - description: ActionProxy defines a proxy in an Action. - type: object - properties: - requestHeaders: - description: ProxyRequestHeaders defines the request headers manipulation in an ActionProxy. - type: object - properties: - pass: - type: boolean - set: - type: array - items: - description: Header defines an HTTP Header. - type: object - properties: - name: - type: string - value: - type: string - responseHeaders: - description: ProxyResponseHeaders defines the response headers manipulation in an ActionProxy. - type: object - properties: - add: - type: array - items: - description: AddHeader defines an HTTP Header with an optional Always field to use with the add_header NGINX directive. - type: object - properties: - always: - type: boolean - name: - type: string - value: - type: string - hide: - type: array - items: - type: string - ignore: - type: array - items: - type: string - pass: - type: array - items: - type: string - rewritePath: - type: string - upstream: - type: string - redirect: - description: ActionRedirect defines a redirect in an Action. - type: object - properties: - code: - type: integer - url: - type: string - return: - description: ActionReturn defines a return in an Action. - type: object - properties: - body: - type: string - code: - type: integer - type: - type: string - weight: - type: integer - upstreams: - type: array - items: - description: Upstream defines an upstream. - type: object - properties: - buffer-size: - type: string - buffering: - type: boolean - buffers: - description: UpstreamBuffers defines Buffer Configuration for an Upstream. - type: object - properties: - number: - type: integer - size: - type: string - client-max-body-size: - type: string - connect-timeout: - type: string - fail-timeout: - type: string - healthCheck: - description: HealthCheck defines the parameters for active Upstream HealthChecks. - type: object - properties: - connect-timeout: - type: string - enable: - type: boolean - fails: - type: integer - grpcService: - type: string - grpcStatus: - type: integer - headers: - type: array - items: - description: Header defines an HTTP Header. - type: object - properties: - name: - type: string - value: - type: string - interval: - type: string - jitter: - type: string - mandatory: - type: boolean - passes: - type: integer - path: - type: string - persistent: - type: boolean - port: - type: integer - read-timeout: - type: string - send-timeout: - type: string - statusMatch: - type: string - tls: - description: UpstreamTLS defines a TLS configuration for an Upstream. - type: object - properties: - enable: - type: boolean - keepalive: - type: integer - lb-method: - type: string - max-conns: - type: integer - max-fails: - type: integer - name: - type: string - next-upstream: - type: string - next-upstream-timeout: - type: string - next-upstream-tries: - type: integer - ntlm: - type: boolean - port: - type: integer - queue: - description: UpstreamQueue defines Queue Configuration for an Upstream. - type: object - properties: - size: - type: integer - timeout: - type: string - read-timeout: - type: string - send-timeout: - type: string - service: - type: string - sessionCookie: - description: SessionCookie defines the parameters for session persistence. - type: object - properties: - domain: - type: string - enable: - type: boolean - expires: - type: string - httpOnly: - type: boolean - name: - type: string - path: - type: string - secure: - type: boolean - slow-start: - type: string - subselector: - type: object - additionalProperties: - type: string - tls: - description: UpstreamTLS defines a TLS configuration for an Upstream. - type: object - properties: - enable: - type: boolean - type: - type: string - use-cluster-ip: - type: boolean - status: - description: VirtualServerRouteStatus defines the status for the VirtualServerRoute resource. - type: object - properties: - externalEndpoints: - type: array - items: - description: ExternalEndpoint defines the IP/ Hostname and ports used to connect to this resource. - type: object - properties: - hostname: - type: string - ip: - type: string - ports: - type: string - message: - type: string - reason: - type: string - referencedBy: - type: string - state: - type: string - served: true - storage: true - subresources: - status: {} diff --git a/deployments/common/crds/k8s.nginx.org_virtualservers.yaml b/deployments/common/crds/k8s.nginx.org_virtualservers.yaml deleted file mode 100644 index b247a56226..0000000000 --- a/deployments/common/crds/k8s.nginx.org_virtualservers.yaml +++ /dev/null @@ -1,715 +0,0 @@ -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.10.0 - creationTimestamp: null - name: virtualservers.k8s.nginx.org -spec: - group: k8s.nginx.org - names: - kind: VirtualServer - listKind: VirtualServerList - plural: virtualservers - shortNames: - - vs - singular: virtualserver - scope: Namespaced - versions: - - additionalPrinterColumns: - - description: Current state of the VirtualServer. If the resource has a valid status, it means it has been validated and accepted by the Ingress Controller. - jsonPath: .status.state - name: State - type: string - - jsonPath: .spec.host - name: Host - type: string - - jsonPath: .status.externalEndpoints[*].ip - name: IP - type: string - - jsonPath: .status.externalEndpoints[*].hostname - name: ExternalHostname - priority: 1 - type: string - - jsonPath: .status.externalEndpoints[*].ports - name: Ports - type: string - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v1 - schema: - openAPIV3Schema: - description: VirtualServer defines the VirtualServer resource. - type: object - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: VirtualServerSpec is the spec of the VirtualServer resource. - type: object - properties: - dos: - type: string - externalDNS: - description: ExternalDNS defines externaldns sub-resource of a virtual server. - type: object - properties: - enable: - type: boolean - labels: - description: Labels stores labels defined for the Endpoint - type: object - additionalProperties: - type: string - providerSpecific: - description: ProviderSpecific stores provider specific config - type: array - items: - description: ProviderSpecificProperty defines specific property for using with ExternalDNS sub-resource. - type: object - properties: - name: - description: Name of the property - type: string - value: - description: Value of the property - type: string - recordTTL: - description: TTL for the record - type: integer - format: int64 - recordType: - type: string - host: - type: string - http-snippets: - type: string - ingressClassName: - type: string - policies: - type: array - items: - description: PolicyReference references a policy by name and an optional namespace. - type: object - properties: - name: - type: string - namespace: - type: string - routes: - type: array - items: - description: Route defines a route. - type: object - properties: - action: - description: Action defines an action. - type: object - properties: - pass: - type: string - proxy: - description: ActionProxy defines a proxy in an Action. - type: object - properties: - requestHeaders: - description: ProxyRequestHeaders defines the request headers manipulation in an ActionProxy. - type: object - properties: - pass: - type: boolean - set: - type: array - items: - description: Header defines an HTTP Header. - type: object - properties: - name: - type: string - value: - type: string - responseHeaders: - description: ProxyResponseHeaders defines the response headers manipulation in an ActionProxy. - type: object - properties: - add: - type: array - items: - description: AddHeader defines an HTTP Header with an optional Always field to use with the add_header NGINX directive. - type: object - properties: - always: - type: boolean - name: - type: string - value: - type: string - hide: - type: array - items: - type: string - ignore: - type: array - items: - type: string - pass: - type: array - items: - type: string - rewritePath: - type: string - upstream: - type: string - redirect: - description: ActionRedirect defines a redirect in an Action. - type: object - properties: - code: - type: integer - url: - type: string - return: - description: ActionReturn defines a return in an Action. - type: object - properties: - body: - type: string - code: - type: integer - type: - type: string - dos: - type: string - errorPages: - type: array - items: - description: ErrorPage defines an ErrorPage in a Route. - type: object - properties: - codes: - type: array - items: - type: integer - redirect: - description: ErrorPageRedirect defines a redirect for an ErrorPage. - type: object - properties: - code: - type: integer - url: - type: string - return: - description: ErrorPageReturn defines a return for an ErrorPage. - type: object - properties: - body: - type: string - code: - type: integer - headers: - type: array - items: - description: Header defines an HTTP Header. - type: object - properties: - name: - type: string - value: - type: string - type: - type: string - location-snippets: - type: string - matches: - type: array - items: - description: Match defines a match. - type: object - properties: - action: - description: Action defines an action. - type: object - properties: - pass: - type: string - proxy: - description: ActionProxy defines a proxy in an Action. - type: object - properties: - requestHeaders: - description: ProxyRequestHeaders defines the request headers manipulation in an ActionProxy. - type: object - properties: - pass: - type: boolean - set: - type: array - items: - description: Header defines an HTTP Header. - type: object - properties: - name: - type: string - value: - type: string - responseHeaders: - description: ProxyResponseHeaders defines the response headers manipulation in an ActionProxy. - type: object - properties: - add: - type: array - items: - description: AddHeader defines an HTTP Header with an optional Always field to use with the add_header NGINX directive. - type: object - properties: - always: - type: boolean - name: - type: string - value: - type: string - hide: - type: array - items: - type: string - ignore: - type: array - items: - type: string - pass: - type: array - items: - type: string - rewritePath: - type: string - upstream: - type: string - redirect: - description: ActionRedirect defines a redirect in an Action. - type: object - properties: - code: - type: integer - url: - type: string - return: - description: ActionReturn defines a return in an Action. - type: object - properties: - body: - type: string - code: - type: integer - type: - type: string - conditions: - type: array - items: - description: Condition defines a condition in a MatchRule. - type: object - properties: - argument: - type: string - cookie: - type: string - header: - type: string - value: - type: string - variable: - type: string - splits: - type: array - items: - description: Split defines a split. - type: object - properties: - action: - description: Action defines an action. - type: object - properties: - pass: - type: string - proxy: - description: ActionProxy defines a proxy in an Action. - type: object - properties: - requestHeaders: - description: ProxyRequestHeaders defines the request headers manipulation in an ActionProxy. - type: object - properties: - pass: - type: boolean - set: - type: array - items: - description: Header defines an HTTP Header. - type: object - properties: - name: - type: string - value: - type: string - responseHeaders: - description: ProxyResponseHeaders defines the response headers manipulation in an ActionProxy. - type: object - properties: - add: - type: array - items: - description: AddHeader defines an HTTP Header with an optional Always field to use with the add_header NGINX directive. - type: object - properties: - always: - type: boolean - name: - type: string - value: - type: string - hide: - type: array - items: - type: string - ignore: - type: array - items: - type: string - pass: - type: array - items: - type: string - rewritePath: - type: string - upstream: - type: string - redirect: - description: ActionRedirect defines a redirect in an Action. - type: object - properties: - code: - type: integer - url: - type: string - return: - description: ActionReturn defines a return in an Action. - type: object - properties: - body: - type: string - code: - type: integer - type: - type: string - weight: - type: integer - path: - type: string - policies: - type: array - items: - description: PolicyReference references a policy by name and an optional namespace. - type: object - properties: - name: - type: string - namespace: - type: string - route: - type: string - splits: - type: array - items: - description: Split defines a split. - type: object - properties: - action: - description: Action defines an action. - type: object - properties: - pass: - type: string - proxy: - description: ActionProxy defines a proxy in an Action. - type: object - properties: - requestHeaders: - description: ProxyRequestHeaders defines the request headers manipulation in an ActionProxy. - type: object - properties: - pass: - type: boolean - set: - type: array - items: - description: Header defines an HTTP Header. - type: object - properties: - name: - type: string - value: - type: string - responseHeaders: - description: ProxyResponseHeaders defines the response headers manipulation in an ActionProxy. - type: object - properties: - add: - type: array - items: - description: AddHeader defines an HTTP Header with an optional Always field to use with the add_header NGINX directive. - type: object - properties: - always: - type: boolean - name: - type: string - value: - type: string - hide: - type: array - items: - type: string - ignore: - type: array - items: - type: string - pass: - type: array - items: - type: string - rewritePath: - type: string - upstream: - type: string - redirect: - description: ActionRedirect defines a redirect in an Action. - type: object - properties: - code: - type: integer - url: - type: string - return: - description: ActionReturn defines a return in an Action. - type: object - properties: - body: - type: string - code: - type: integer - type: - type: string - weight: - type: integer - server-snippets: - type: string - tls: - description: TLS defines TLS configuration for a VirtualServer. - type: object - properties: - cert-manager: - description: CertManager defines a cert manager config for a TLS. - type: object - properties: - cluster-issuer: - type: string - common-name: - type: string - duration: - type: string - issuer: - type: string - issuer-group: - type: string - issuer-kind: - type: string - renew-before: - type: string - usages: - type: string - redirect: - description: TLSRedirect defines a redirect for a TLS. - type: object - properties: - basedOn: - type: string - code: - type: integer - enable: - type: boolean - secret: - type: string - upstreams: - type: array - items: - description: Upstream defines an upstream. - type: object - properties: - buffer-size: - type: string - buffering: - type: boolean - buffers: - description: UpstreamBuffers defines Buffer Configuration for an Upstream. - type: object - properties: - number: - type: integer - size: - type: string - client-max-body-size: - type: string - connect-timeout: - type: string - fail-timeout: - type: string - healthCheck: - description: HealthCheck defines the parameters for active Upstream HealthChecks. - type: object - properties: - connect-timeout: - type: string - enable: - type: boolean - fails: - type: integer - grpcService: - type: string - grpcStatus: - type: integer - headers: - type: array - items: - description: Header defines an HTTP Header. - type: object - properties: - name: - type: string - value: - type: string - interval: - type: string - jitter: - type: string - mandatory: - type: boolean - passes: - type: integer - path: - type: string - persistent: - type: boolean - port: - type: integer - read-timeout: - type: string - send-timeout: - type: string - statusMatch: - type: string - tls: - description: UpstreamTLS defines a TLS configuration for an Upstream. - type: object - properties: - enable: - type: boolean - keepalive: - type: integer - lb-method: - type: string - max-conns: - type: integer - max-fails: - type: integer - name: - type: string - next-upstream: - type: string - next-upstream-timeout: - type: string - next-upstream-tries: - type: integer - ntlm: - type: boolean - port: - type: integer - queue: - description: UpstreamQueue defines Queue Configuration for an Upstream. - type: object - properties: - size: - type: integer - timeout: - type: string - read-timeout: - type: string - send-timeout: - type: string - service: - type: string - sessionCookie: - description: SessionCookie defines the parameters for session persistence. - type: object - properties: - domain: - type: string - enable: - type: boolean - expires: - type: string - httpOnly: - type: boolean - name: - type: string - path: - type: string - secure: - type: boolean - slow-start: - type: string - subselector: - type: object - additionalProperties: - type: string - tls: - description: UpstreamTLS defines a TLS configuration for an Upstream. - type: object - properties: - enable: - type: boolean - type: - type: string - use-cluster-ip: - type: boolean - status: - description: VirtualServerStatus defines the status for the VirtualServer resource. - type: object - properties: - externalEndpoints: - type: array - items: - description: ExternalEndpoint defines the IP/ Hostname and ports used to connect to this resource. - type: object - properties: - hostname: - type: string - ip: - type: string - ports: - type: string - message: - type: string - reason: - type: string - state: - type: string - served: true - storage: true - subresources: - status: {} diff --git a/deployments/common/default-server-secret.yaml b/deployments/common/default-server-secret.yaml deleted file mode 100644 index 887354075d..0000000000 --- a/deployments/common/default-server-secret.yaml +++ /dev/null @@ -1,9 +0,0 @@ -apiVersion: v1 -kind: Secret -metadata: - name: default-server-secret - namespace: nginx-ingress -type: kubernetes.io/tls -data: - tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUN2akNDQWFZQ0NRREFPRjl0THNhWFhEQU5CZ2txaGtpRzl3MEJBUXNGQURBaE1SOHdIUVlEVlFRRERCWk8KUjBsT1dFbHVaM0psYzNORGIyNTBjbTlzYkdWeU1CNFhEVEU0TURreE1qRTRNRE16TlZvWERUSXpNRGt4TVRFNApNRE16TlZvd0lURWZNQjBHQTFVRUF3d1dUa2RKVGxoSmJtZHlaWE56UTI5dWRISnZiR3hsY2pDQ0FTSXdEUVlKCktvWklodmNOQVFFQkJRQURnZ0VQQURDQ0FRb0NnZ0VCQUwvN2hIUEtFWGRMdjNyaUM3QlBrMTNpWkt5eTlyQ08KR2xZUXYyK2EzUDF0azIrS3YwVGF5aGRCbDRrcnNUcTZzZm8vWUk1Y2Vhbkw4WGM3U1pyQkVRYm9EN2REbWs1Qgo4eDZLS2xHWU5IWlg0Rm5UZ0VPaStlM2ptTFFxRlBSY1kzVnNPazFFeUZBL0JnWlJVbkNHZUtGeERSN0tQdGhyCmtqSXVuektURXUyaDU4Tlp0S21ScUJHdDEwcTNRYzhZT3ExM2FnbmovUWRjc0ZYYTJnMjB1K1lYZDdoZ3krZksKWk4vVUkxQUQ0YzZyM1lma1ZWUmVHd1lxQVp1WXN2V0RKbW1GNWRwdEMzN011cDBPRUxVTExSakZJOTZXNXIwSAo1TmdPc25NWFJNV1hYVlpiNWRxT3R0SmRtS3FhZ25TZ1JQQVpQN2MwQjFQU2FqYzZjNGZRVXpNQ0F3RUFBVEFOCkJna3Foa2lHOXcwQkFRc0ZBQU9DQVFFQWpLb2tRdGRPcEsrTzhibWVPc3lySmdJSXJycVFVY2ZOUitjb0hZVUoKdGhrYnhITFMzR3VBTWI5dm15VExPY2xxeC9aYzJPblEwMEJCLzlTb0swcitFZ1U2UlVrRWtWcitTTFA3NTdUWgozZWI4dmdPdEduMS9ienM3bzNBaS9kclkrcUI5Q2k1S3lPc3FHTG1US2xFaUtOYkcyR1ZyTWxjS0ZYQU80YTY3Cklnc1hzYktNbTQwV1U3cG9mcGltU1ZmaXFSdkV5YmN3N0NYODF6cFErUyt1eHRYK2VBZ3V0NHh3VlI5d2IyVXYKelhuZk9HbWhWNThDd1dIQnNKa0kxNXhaa2VUWXdSN0diaEFMSkZUUkk3dkhvQXprTWIzbjAxQjQyWjNrN3RXNQpJUDFmTlpIOFUvOWxiUHNoT21FRFZkdjF5ZytVRVJxbStGSis2R0oxeFJGcGZnPT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo= - tls.key: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcEFJQkFBS0NBUUVBdi91RWM4b1JkMHUvZXVJTHNFK1RYZUprckxMMnNJNGFWaEMvYjVyYy9XMlRiNHEvClJOcktGMEdYaVN1eE9ycXgrajlnamx4NXFjdnhkenRKbXNFUkJ1Z1B0ME9hVGtIekhvb3FVWmcwZGxmZ1dkT0EKUTZMNTdlT1l0Q29VOUZ4amRXdzZUVVRJVUQ4R0JsRlNjSVo0b1hFTkhzbysyR3VTTWk2Zk1wTVM3YUhudzFtMApxWkdvRWEzWFNyZEJ6eGc2clhkcUNlUDlCMXl3VmRyYURiUzc1aGQzdUdETDU4cGszOVFqVUFQaHpxdmRoK1JWClZGNGJCaW9CbTVpeTlZTW1hWVhsMm0wTGZzeTZuUTRRdFFzdEdNVWozcGJtdlFmazJBNnljeGRFeFpkZFZsdmwKMm82MjBsMllxcHFDZEtCRThCay90elFIVTlKcU56cHpoOUJUTXdJREFRQUJBb0lCQVFDZklHbXowOHhRVmorNwpLZnZJUXQwQ0YzR2MxNld6eDhVNml4MHg4Mm15d1kxUUNlL3BzWE9LZlRxT1h1SENyUlp5TnUvZ2IvUUQ4bUFOCmxOMjRZTWl0TWRJODg5TEZoTkp3QU5OODJDeTczckM5bzVvUDlkazAvYzRIbjAzSkVYNzZ5QjgzQm9rR1FvYksKMjhMNk0rdHUzUmFqNjd6Vmc2d2szaEhrU0pXSzBwV1YrSjdrUkRWYmhDYUZhNk5nMUZNRWxhTlozVDhhUUtyQgpDUDNDeEFTdjYxWTk5TEI4KzNXWVFIK3NYaTVGM01pYVNBZ1BkQUk3WEh1dXFET1lvMU5PL0JoSGt1aVg2QnRtCnorNTZud2pZMy8yUytSRmNBc3JMTnIwMDJZZi9oY0IraVlDNzVWYmcydVd6WTY3TWdOTGQ5VW9RU3BDRkYrVm4KM0cyUnhybnhBb0dCQU40U3M0ZVlPU2huMVpQQjdhTUZsY0k2RHR2S2ErTGZTTXFyY2pOZjJlSEpZNnhubmxKdgpGenpGL2RiVWVTbWxSekR0WkdlcXZXaHFISy9iTjIyeWJhOU1WMDlRQ0JFTk5jNmtWajJTVHpUWkJVbEx4QzYrCk93Z0wyZHhKendWelU0VC84ajdHalRUN05BZVpFS2FvRHFyRG5BYWkyaW5oZU1JVWZHRXFGKzJyQW9HQkFOMVAKK0tZL0lsS3RWRzRKSklQNzBjUis3RmpyeXJpY05iWCtQVzUvOXFHaWxnY2grZ3l4b25BWlBpd2NpeDN3QVpGdwpaZC96ZFB2aTBkWEppc1BSZjRMazg5b2pCUmpiRmRmc2l5UmJYbyt3TFU4NUhRU2NGMnN5aUFPaTVBRHdVU0FkCm45YWFweUNweEFkREtERHdObit3ZFhtaTZ0OHRpSFRkK3RoVDhkaVpBb0dCQUt6Wis1bG9OOTBtYlF4VVh5YUwKMjFSUm9tMGJjcndsTmVCaWNFSmlzaEhYa2xpSVVxZ3hSZklNM2hhUVRUcklKZENFaHFsV01aV0xPb2I2NTNyZgo3aFlMSXM1ZUtka3o0aFRVdnpldm9TMHVXcm9CV2xOVHlGanIrSWhKZnZUc0hpOGdsU3FkbXgySkJhZUFVWUNXCndNdlQ4NmNLclNyNkQrZG8wS05FZzFsL0FvR0FlMkFVdHVFbFNqLzBmRzgrV3hHc1RFV1JqclRNUzRSUjhRWXQKeXdjdFA4aDZxTGxKUTRCWGxQU05rMXZLTmtOUkxIb2pZT2pCQTViYjhibXNVU1BlV09NNENoaFJ4QnlHbmR2eAphYkJDRkFwY0IvbEg4d1R0alVZYlN5T294ZGt5OEp0ek90ajJhS0FiZHd6NlArWDZDODhjZmxYVFo5MWpYL3RMCjF3TmRKS2tDZ1lCbyt0UzB5TzJ2SWFmK2UwSkN5TGhzVDQ5cTN3Zis2QWVqWGx2WDJ1VnRYejN5QTZnbXo5aCsKcDNlK2JMRUxwb3B0WFhNdUFRR0xhUkcrYlNNcjR5dERYbE5ZSndUeThXczNKY3dlSTdqZVp2b0ZpbmNvVlVIMwphdmxoTUVCRGYxSjltSDB5cDBwWUNaS2ROdHNvZEZtQktzVEtQMjJhTmtsVVhCS3gyZzR6cFE9PQotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo= diff --git a/deployments/common/plus-mgmt-configmap.yaml b/deployments/common/plus-mgmt-configmap.yaml new file mode 100644 index 0000000000..8b64038ac4 --- /dev/null +++ b/deployments/common/plus-mgmt-configmap.yaml @@ -0,0 +1,7 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: nginx-config-mgmt + namespace: nginx-ingress +data: + license-token-secret-name: "license-token" diff --git a/deployments/daemon-set/nginx-ingress.yaml b/deployments/daemon-set/nginx-ingress.yaml index 7b87dd0e6b..a3b502d47b 100644 --- a/deployments/daemon-set/nginx-ingress.yaml +++ b/deployments/daemon-set/nginx-ingress.yaml @@ -11,6 +11,7 @@ spec: metadata: labels: app: nginx-ingress + app.kubernetes.io/name: nginx-ingress #annotations: #prometheus.io/scrape: "true" #prometheus.io/port: "9113" @@ -18,8 +19,20 @@ spec: spec: serviceAccountName: nginx-ingress automountServiceAccountToken: true + securityContext: + seccompProfile: + type: RuntimeDefault +# volumes: +# - name: nginx-etc +# emptyDir: {} +# - name: nginx-cache +# emptyDir: {} +# - name: nginx-lib +# emptyDir: {} +# - name: nginx-log +# emptyDir: {} containers: - - image: nginx/nginx-ingress:2.4.1 + - image: nginx/nginx-ingress:4.0.0 imagePullPolicy: IfNotPresent name: nginx-ingress ports: @@ -46,13 +59,24 @@ spec: # cpu: "1" # memory: "1Gi" securityContext: - allowPrivilegeEscalation: true + allowPrivilegeEscalation: false +# readOnlyRootFilesystem: true runAsUser: 101 #nginx + runAsNonRoot: true capabilities: drop: - ALL add: - NET_BIND_SERVICE +# volumeMounts: +# - mountPath: /etc/nginx +# name: nginx-etc +# - mountPath: /var/cache/nginx +# name: nginx-cache +# - mountPath: /var/lib/nginx +# name: nginx-lib +# - mountPath: /var/log/nginx +# name: nginx-log env: - name: POD_NAMESPACE valueFrom: @@ -64,10 +88,26 @@ spec: fieldPath: metadata.name args: - -nginx-configmaps=$(POD_NAMESPACE)/nginx-config - - -default-server-tls-secret=$(POD_NAMESPACE)/default-server-secret - #- -include-year - #- -v=3 # Enables extensive logging. Useful for troubleshooting. - #- -report-ingress-status - #- -external-service=nginx-ingress + - -report-ingress-status + - -external-service=nginx-ingress + #- -default-server-tls-secret=$(POD_NAMESPACE)/default-server-secret + #- -log-level=debug # Enables extensive logging. Useful for troubleshooting. Options include: trace, debug, info, warning, error, fatal + #- -log-format=glog # Sets the log format. Options include: glog, json, text #- -enable-prometheus-metrics #- -global-configuration=$(POD_NAMESPACE)/nginx-configuration +# initContainers: +# - image: nginx/nginx-ingress:4.0.0 +# imagePullPolicy: IfNotPresent +# name: init-nginx-ingress +# command: ['cp', '-vdR', '/etc/nginx/.', '/mnt/etc'] +# securityContext: +# allowPrivilegeEscalation: false +# readOnlyRootFilesystem: true +# runAsUser: 101 #nginx +# runAsNonRoot: true +# capabilities: +# drop: +# - ALL +# volumeMounts: +# - mountPath: /mnt/etc +# name: nginx-etc diff --git a/deployments/daemon-set/nginx-plus-ingress.yaml b/deployments/daemon-set/nginx-plus-ingress.yaml index 59af9c0d73..61f83c6e7e 100644 --- a/deployments/daemon-set/nginx-plus-ingress.yaml +++ b/deployments/daemon-set/nginx-plus-ingress.yaml @@ -11,6 +11,7 @@ spec: metadata: labels: app: nginx-ingress + app.kubernetes.io/name: nginx-ingress #annotations: #prometheus.io/scrape: "true" #prometheus.io/port: "9113" @@ -18,8 +19,20 @@ spec: spec: serviceAccountName: nginx-ingress automountServiceAccountToken: true + securityContext: + seccompProfile: + type: RuntimeDefault +# volumes: +# - name: nginx-etc +# emptyDir: {} +# - name: nginx-cache +# emptyDir: {} +# - name: nginx-lib +# emptyDir: {} +# - name: nginx-log +# emptyDir: {} containers: - - image: nginx-plus-ingress:2.4.1 + - image: nginx-plus-ingress:4.0.0 imagePullPolicy: IfNotPresent name: nginx-plus-ingress ports: @@ -46,13 +59,24 @@ spec: # cpu: "1" # memory: "1Gi" securityContext: - allowPrivilegeEscalation: true + allowPrivilegeEscalation: false +# readOnlyRootFilesystem: true runAsUser: 101 #nginx + runAsNonRoot: true capabilities: drop: - ALL add: - NET_BIND_SERVICE +# volumeMounts: +# - mountPath: /etc/nginx +# name: nginx-etc +# - mountPath: /var/cache/nginx +# name: nginx-cache +# - mountPath: /var/lib/nginx +# name: nginx-lib +# - mountPath: /var/log/nginx +# name: nginx-log env: - name: POD_NAMESPACE valueFrom: @@ -65,12 +89,29 @@ spec: args: - -nginx-plus - -nginx-configmaps=$(POD_NAMESPACE)/nginx-config - - -default-server-tls-secret=$(POD_NAMESPACE)/default-server-secret - #- -include-year + - -mgmt-configmap=$(POD_NAMESPACE)/nginx-config-mgmt + - -report-ingress-status + - -external-service=nginx-ingress + #- -default-server-tls-secret=$(POD_NAMESPACE)/default-server-secret #- -enable-app-protect #- -enable-app-protect-dos - #- -v=3 # Enables extensive logging. Useful for troubleshooting. - #- -report-ingress-status - #- -external-service=nginx-ingress + #- -log-level=debug # Enables extensive logging. Useful for troubleshooting. Options include: trace, debug, info, warning, error, fatal + #- -log-format=glog # Sets the log format. Options include: glog, json, text #- -enable-prometheus-metrics #- -global-configuration=$(POD_NAMESPACE)/nginx-configuration +# initContainers: +# - image: nginx/nginx-ingress:4.0.0 +# imagePullPolicy: IfNotPresent +# name: init-nginx-ingress +# command: ['cp', '-vdR', '/etc/nginx/.', '/mnt/etc'] +# securityContext: +# allowPrivilegeEscalation: false +# readOnlyRootFilesystem: true +# runAsUser: 101 #nginx +# runAsNonRoot: true +# capabilities: +# drop: +# - ALL +# volumeMounts: +# - mountPath: /mnt/etc +# name: nginx-etc diff --git a/deployments/deployment/appprotect-dos-arb.yaml b/deployments/deployment/appprotect-dos-arb.yaml index ebd5775156..8186592688 100644 --- a/deployments/deployment/appprotect-dos-arb.yaml +++ b/deployments/deployment/appprotect-dos-arb.yaml @@ -15,7 +15,7 @@ spec: spec: containers: - name: appprotect-dos-arb - image: docker-registry.nginx.com/nap-dos/app_protect_dos_arb:1.1.0 + image: docker-registry.nginx.com/nap-dos/app_protect_dos_arb:1.1.1 imagePullPolicy: IfNotPresent resources: limits: diff --git a/deployments/deployment/nginx-ingress.yaml b/deployments/deployment/nginx-ingress.yaml index e26248e694..4e4d268470 100644 --- a/deployments/deployment/nginx-ingress.yaml +++ b/deployments/deployment/nginx-ingress.yaml @@ -12,6 +12,7 @@ spec: metadata: labels: app: nginx-ingress + app.kubernetes.io/name: nginx-ingress #annotations: #prometheus.io/scrape: "true" #prometheus.io/port: "9113" @@ -19,8 +20,20 @@ spec: spec: serviceAccountName: nginx-ingress automountServiceAccountToken: true + securityContext: + seccompProfile: + type: RuntimeDefault +# volumes: +# - name: nginx-etc +# emptyDir: {} +# - name: nginx-cache +# emptyDir: {} +# - name: nginx-lib +# emptyDir: {} +# - name: nginx-log +# emptyDir: {} containers: - - image: nginx/nginx-ingress:2.4.1 + - image: nginx/nginx-ingress:4.0.0 imagePullPolicy: IfNotPresent name: nginx-ingress ports: @@ -45,7 +58,8 @@ spec: # cpu: "1" # memory: "1Gi" securityContext: - allowPrivilegeEscalation: true + allowPrivilegeEscalation: false +# readOnlyRootFilesystem: true runAsUser: 101 #nginx runAsNonRoot: true capabilities: @@ -53,6 +67,15 @@ spec: - ALL add: - NET_BIND_SERVICE +# volumeMounts: +# - mountPath: /etc/nginx +# name: nginx-etc +# - mountPath: /var/cache/nginx +# name: nginx-cache +# - mountPath: /var/lib/nginx +# name: nginx-lib +# - mountPath: /var/log/nginx +# name: nginx-log env: - name: POD_NAMESPACE valueFrom: @@ -64,12 +87,28 @@ spec: fieldPath: metadata.name args: - -nginx-configmaps=$(POD_NAMESPACE)/nginx-config - - -default-server-tls-secret=$(POD_NAMESPACE)/default-server-secret - #- -include-year + - -report-ingress-status + - -external-service=nginx-ingress + #- -default-server-tls-secret=$(POD_NAMESPACE)/default-server-secret #- -enable-cert-manager #- -enable-external-dns - #- -v=3 # Enables extensive logging. Useful for troubleshooting. - #- -report-ingress-status - #- -external-service=nginx-ingress + #- -log-level=debug # Enables extensive logging. Useful for troubleshooting. Options include: trace, debug, info, warning, error, fatal + #- -log-format=glog # Sets the log format. Options include: glog, json, text #- -enable-prometheus-metrics #- -global-configuration=$(POD_NAMESPACE)/nginx-configuration +# initContainers: +# - image: nginx/nginx-ingress:4.0.0 +# imagePullPolicy: IfNotPresent +# name: init-nginx-ingress +# command: ['cp', '-vdR', '/etc/nginx/.', '/mnt/etc'] +# securityContext: +# allowPrivilegeEscalation: false +# readOnlyRootFilesystem: true +# runAsUser: 101 #nginx +# runAsNonRoot: true +# capabilities: +# drop: +# - ALL +# volumeMounts: +# - mountPath: /mnt/etc +# name: nginx-etc diff --git a/deployments/deployment/nginx-plus-ingress.yaml b/deployments/deployment/nginx-plus-ingress.yaml index 97a0b08dc1..85313316ce 100644 --- a/deployments/deployment/nginx-plus-ingress.yaml +++ b/deployments/deployment/nginx-plus-ingress.yaml @@ -12,6 +12,7 @@ spec: metadata: labels: app: nginx-ingress + app.kubernetes.io/name: nginx-ingress #annotations: #prometheus.io/scrape: "true" #prometheus.io/port: "9113" @@ -19,8 +20,20 @@ spec: spec: serviceAccountName: nginx-ingress automountServiceAccountToken: true + securityContext: + seccompProfile: + type: RuntimeDefault +# volumes: +# - name: nginx-etc +# emptyDir: {} +# - name: nginx-cache +# emptyDir: {} +# - name: nginx-lib +# emptyDir: {} +# - name: nginx-log +# emptyDir: {} containers: - - image: nginx-plus-ingress:2.4.1 + - image: nginx-plus-ingress:4.0.0 imagePullPolicy: IfNotPresent name: nginx-plus-ingress ports: @@ -32,6 +45,8 @@ spec: containerPort: 8081 - name: prometheus containerPort: 9113 + - name: service-insight + containerPort: 9114 readinessProbe: httpGet: path: /nginx-ready @@ -45,7 +60,8 @@ spec: # cpu: "1" # memory: "1Gi" securityContext: - allowPrivilegeEscalation: true + allowPrivilegeEscalation: false +# readOnlyRootFilesystem: true runAsUser: 101 #nginx runAsNonRoot: true capabilities: @@ -53,6 +69,15 @@ spec: - ALL add: - NET_BIND_SERVICE +# volumeMounts: +# - mountPath: /etc/nginx +# name: nginx-etc +# - mountPath: /var/cache/nginx +# name: nginx-cache +# - mountPath: /var/lib/nginx +# name: nginx-lib +# - mountPath: /var/log/nginx +# name: nginx-log env: - name: POD_NAMESPACE valueFrom: @@ -65,14 +90,32 @@ spec: args: - -nginx-plus - -nginx-configmaps=$(POD_NAMESPACE)/nginx-config - - -default-server-tls-secret=$(POD_NAMESPACE)/default-server-secret - #- -include-year + - -mgmt-configmap=$(POD_NAMESPACE)/nginx-config-mgmt + - -report-ingress-status + - -external-service=nginx-ingress + #- -default-server-tls-secret=$(POD_NAMESPACE)/default-server-secret #- -enable-cert-manager #- -enable-external-dns #- -enable-app-protect #- -enable-app-protect-dos - #- -v=3 # Enables extensive logging. Useful for troubleshooting. - #- -report-ingress-status - #- -external-service=nginx-ingress + #- -log-level=debug # Enables extensive logging. Useful for troubleshooting. Options include: trace, debug, info, warning, error, fatal + #- -log-format=glog # Sets the log format. Options include: glog, json, text #- -enable-prometheus-metrics + #- -enable-service-insight #- -global-configuration=$(POD_NAMESPACE)/nginx-configuration +# initContainers: +# - image: nginx/nginx-ingress:4.0.0 +# imagePullPolicy: IfNotPresent +# name: init-nginx-ingress +# command: ['cp', '-vdR', '/etc/nginx/.', '/mnt/etc'] +# securityContext: +# allowPrivilegeEscalation: false +# readOnlyRootFilesystem: true +# runAsUser: 101 #nginx +# runAsNonRoot: true +# capabilities: +# drop: +# - ALL +# volumeMounts: +# - mountPath: /mnt/etc +# name: nginx-etc diff --git a/deployments/helm-chart-dos-arbitrator/Chart.yaml b/deployments/helm-chart-dos-arbitrator/Chart.yaml deleted file mode 100644 index b0cce89c2f..0000000000 --- a/deployments/helm-chart-dos-arbitrator/Chart.yaml +++ /dev/null @@ -1,17 +0,0 @@ -name: nginx-appprotect-dos-arbitrator -version: 0.1.0 -appVersion: 1.1.0 -apiVersion: v1 -kubeVersion: ">= 1.21.0-0" -description: NGINX App Protect Dos arbitrator -icon: https://raw.githubusercontent.com/nginxinc/kubernetes-ingress/v2.4.1/deployments/helm-chart-dos-arbitrator/chart-icon.png -home: https://github.com/nginxinc/kubernetes-ingress -sources: - - https://github.com/nginxinc/kubernetes-ingress/tree/v2.4.1/deployments/helm-chart-dos-arbitrator -keywords: - - appprotect-dos - - nginx - - arbitrator -maintainers: - - name: nginxinc - email: kubernetes@nginx.com diff --git a/deployments/helm-chart-dos-arbitrator/README.md b/deployments/helm-chart-dos-arbitrator/README.md deleted file mode 100644 index af51a69c9d..0000000000 --- a/deployments/helm-chart-dos-arbitrator/README.md +++ /dev/null @@ -1,95 +0,0 @@ -# NGINX App Protect DoS Arbitrator Helm Chart - -## Introduction - -This chart deploys the NGINX App Protect DoS Arbitrator in your Kubernetes cluster. - -## Prerequisites - - - A [Kubernetes Version Supported by the Ingress Controller](https://docs.nginx.com/nginx-ingress-controller/technical-specifications/#supported-kubernetes-versions) - - Helm 3.0+. - - Git. - -## Getting the Chart Sources - -This step is required if you're installing the chart using its sources. Additionally, the step is also required for managing the custom resource definitions (CRDs), which the Ingress Controller requires by default, or for upgrading/deleting the CRDs. - -1. Clone the Ingress Controller repo: - ```console - $ git clone https://github.com/nginxinc/kubernetes-ingress.git --branch v2.4.1 - ``` -2. Change your working directory to /deployments/helm-chart-dos-arbitrator: - ```console - $ cd kubernetes-ingress/deployments/helm-chart-dos-arbitrator - ``` - -## Adding the Helm Repository - -This step is required if you're installing the chart via the helm repository. - -```console -$ helm repo add nginx-stable https://helm.nginx.com/stable -$ helm repo update -``` - -## Installing the Chart - -### Installing via Helm Repository - -To install the chart with the release name my-release-dos (my-release-dos is the name that you choose): - -```console -$ helm install my-release-dos nginx-stable/nginx-appprotect-dos-arbitrator -``` - - -### Installing Using Chart Sources - -To install the chart with the release name my-release-dos (my-release-dos is the name that you choose): - -```console -$ helm install my-release-dos . -``` - -The command deploys the App Protect DoS Arbitrator in your Kubernetes cluster in the default configuration. The configuration section lists the parameters that can be configured during installation. - -## Upgrading the Chart - -### Upgrading the Release - -To upgrade the release `my-release-dos`: - -#### Upgrade Using Chart Sources: - -```console -$ helm upgrade my-release-dos . -``` - -#### Upgrade via Helm Repository: - -```console -$ helm upgrade my-release-dos nginx-stable/nginx-appprotect-dos-arbitrator -``` - -## Uninstalling the Chart - -### Uninstalling the Release - -To uninstall/delete the release `my-release-dos`: - -```console -$ helm uninstall my-release-dos -``` - -The command removes all the Kubernetes components associated with the release and deletes the release. - -## Configuration - -The following tables lists the configurable parameters of the NGINX App Protect DoS Arbitrator chart and their default values. - -Parameter | Description | Default ---- | --- | --- -`arbitrator.resources` | The resources of the Arbitrator pods. | limits:
cpu: 500m
memory: 128Mi -`arbitrator.image.repository` | The image repository of the Arbitrator image. | docker-registry.nginx.com/nap-dos/app_protect_dos_arb -`arbitrator.image.tag` | The tag of the Arbitrator image. | 1.1.0 -`arbitrator.image.pullPolicy` | The pull policy for the Arbitrator image. | IfNotPresent diff --git a/deployments/helm-chart-dos-arbitrator/templates/_helpers.tpl b/deployments/helm-chart-dos-arbitrator/templates/_helpers.tpl deleted file mode 100644 index 02714455be..0000000000 --- a/deployments/helm-chart-dos-arbitrator/templates/_helpers.tpl +++ /dev/null @@ -1,18 +0,0 @@ -{{/* vim: set filetype=mustache: */}} - -{{/* -Expand the name of the chart. -*/}} -{{- define "arbitrator.name" -}} -{{- printf "%s-%s" .Release.Name .Chart.Name | trunc 63 | trimSuffix "-" -}} -{{- end -}} - -{{/* -Create labels -*/}} -{{- define "arbitrator.labels" -}} -app.kubernetes.io/name: {{ include "arbitrator.name" . }} -helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version }} -app.kubernetes.io/managed-by: {{ .Release.Service }} -app.kubernetes.io/instance: {{ .Release.Name }} -{{- end -}} diff --git a/deployments/helm-chart-dos-arbitrator/templates/controller-deployment.yaml b/deployments/helm-chart-dos-arbitrator/templates/controller-deployment.yaml deleted file mode 100644 index 66d448a4ba..0000000000 --- a/deployments/helm-chart-dos-arbitrator/templates/controller-deployment.yaml +++ /dev/null @@ -1,30 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - name: {{ include "arbitrator.name" . }} - namespace: {{ .Release.Namespace }} - labels: {{- include "arbitrator.labels" . | nindent 4 }} -spec: - replicas: 1 - selector: - matchLabels: - app: {{ include "arbitrator.name" . }} - template: - metadata: - labels: - app: {{ include "arbitrator.name" . }} - spec: - containers: - - name: {{ include "arbitrator.name" . }} - image: "{{ .Values.arbitrator.image.repository }}:{{ .Values.arbitrator.image.tag }}" - imagePullPolicy: "{{ .Values.arbitrator.image.pullPolicy }}" - resources: -{{ toYaml .Values.arbitrator.resources | indent 12 }} - ports: - - containerPort: 3000 - securityContext: - allowPrivilegeEscalation: false - runAsUser: 1001 - capabilities: - drop: - - ALL diff --git a/deployments/helm-chart-dos-arbitrator/templates/controller-service.yaml b/deployments/helm-chart-dos-arbitrator/templates/controller-service.yaml deleted file mode 100644 index f55a9fd8d2..0000000000 --- a/deployments/helm-chart-dos-arbitrator/templates/controller-service.yaml +++ /dev/null @@ -1,15 +0,0 @@ -apiVersion: v1 -kind: Service -metadata: - name: svc-appprotect-dos-arb - namespace: {{ .Release.Namespace }} - labels: {{- include "arbitrator.labels" . | nindent 4 }} -spec: - selector: - app: {{ include "arbitrator.name" . }} - ports: - - name: arb - port: 3000 - protocol: TCP - targetPort: 3000 - clusterIP: None diff --git a/deployments/helm-chart-dos-arbitrator/values.yaml b/deployments/helm-chart-dos-arbitrator/values.yaml deleted file mode 100644 index eeacd9d3cf..0000000000 --- a/deployments/helm-chart-dos-arbitrator/values.yaml +++ /dev/null @@ -1,16 +0,0 @@ -arbitrator: - ## The resources of the Arbitrator pods. - resources: - limits: - cpu: 500m - memory: 128Mi - - image: - ## The image repository of the Arbitrator. - repository: docker-registry.nginx.com/nap-dos/app_protect_dos_arb - - ## The tag of the Arbitrator image. - tag: "1.1.0" - - ## The pull policy for the Arbitrator image. - pullPolicy: IfNotPresent diff --git a/deployments/helm-chart/.helmignore b/deployments/helm-chart/.helmignore deleted file mode 100644 index c1347c2c27..0000000000 --- a/deployments/helm-chart/.helmignore +++ /dev/null @@ -1,2 +0,0 @@ -# Patterns to ignore when building packages. -*.png diff --git a/deployments/helm-chart/Chart.yaml b/deployments/helm-chart/Chart.yaml deleted file mode 100644 index 00a42d0bfa..0000000000 --- a/deployments/helm-chart/Chart.yaml +++ /dev/null @@ -1,17 +0,0 @@ -apiVersion: v2 -name: nginx-ingress -version: 0.15.1 -appVersion: 2.4.1 -kubeVersion: ">= 1.21.0-0" -type: application -description: NGINX Ingress Controller -icon: https://raw.githubusercontent.com/nginxinc/kubernetes-ingress/v2.4.1/deployments/helm-chart/chart-icon.png -home: https://github.com/nginxinc/kubernetes-ingress -sources: - - https://github.com/nginxinc/kubernetes-ingress/tree/v2.4.1/deployments/helm-chart -keywords: - - ingress - - nginx -maintainers: - - name: nginxinc - email: kubernetes@nginx.com diff --git a/deployments/helm-chart/README.md b/deployments/helm-chart/README.md deleted file mode 100644 index 65a402e799..0000000000 --- a/deployments/helm-chart/README.md +++ /dev/null @@ -1,263 +0,0 @@ -# NGINX Ingress Controller Helm Chart - -## Introduction - -This chart deploys the NGINX Ingress Controller in your Kubernetes cluster. - -## Prerequisites - - - A [Kubernetes Version Supported by the Ingress Controller](https://docs.nginx.com/nginx-ingress-controller/technical-specifications/#supported-kubernetes-versions) - - Helm 3.0+. - - Git. - - If you’d like to use NGINX Plus: - - To pull from the F5 Container registry, configure a docker registry secret using your JWT token from the MyF5 portal by following the instructions from [here](https://docs.nginx.com/nginx-ingress-controller/installation/using-the-jwt-token-docker-secret). Make sure to specify the secret using `controller.serviceAccount.imagePullSecretName` parameter. - - Alternatively, pull an Ingress Controller image with NGINX Plus and push it to your private registry by following the instructions from [here](https://docs.nginx.com/nginx-ingress-controller/installation/pulling-ingress-controller-image). - - Alternatively, you can build an Ingress Controller image with NGINX Plus and push it to your private registry by following the instructions from [here](https://docs.nginx.com/nginx-ingress-controller/installation/building-ingress-controller-image). - - Update the `controller.image.repository` field of the `values-plus.yaml` accordingly. - - If you’d like to use App Protect DoS, please install App Protect DoS Arbitrator helm chart. Make sure to install in the same namespace as the NGINX Ingress Controller. Note that if you install multiple NGINX Ingress Controllers in the same namespace, they will need to share the same Arbitrator because it is not possible to install more than one Arbitrator in a single namespace. - - -## Getting the Chart Sources - -This step is required if you're installing the chart using its sources. Additionally, the step is also required for managing the custom resource definitions (CRDs), which the Ingress Controller requires by default, or for upgrading/deleting the CRDs. - -1. Clone the Ingress Controller repo: - ```console - $ git clone https://github.com/nginxinc/kubernetes-ingress --branch v2.4.1 - ``` - **Note**: If you want to use the experimental repository (`edge`), remove the `--branch` flag and value. - -2. Change your working directory to /deployments/helm-chart: - ```console - $ cd kubernetes-ingress/deployments/helm-chart - ``` - -## Adding the Helm Repository - -This step is required if you're installing the chart via the helm repository. - -```console -$ helm repo add nginx-stable https://helm.nginx.com/stable -$ helm repo update -``` - -**Note**: If you want to use the experimental repository, replace `stable` with `edge`. - -## Installing the Chart - -### Installing the CRDs - -By default, the Ingress Controller requires a number of custom resource definitions (CRDs) installed in the cluster. The Helm client will install those CRDs. If the CRDs are not installed, the Ingress Controller pods will not become `Ready`. - -If you do not use the custom resources that require those CRDs (which corresponds to `controller.enableCustomResources` set to `false` and `controller.appprotect.enable` set to `false` and `controller.appprotectdos.enable` set to `false`), the installation of the CRDs can be skipped by specifying `--skip-crds` for the helm install command. - -### Installing via Helm Repository - -To install the chart with the release name my-release (my-release is the name that you choose): - -For NGINX: -```console -$ helm install my-release nginx-stable/nginx-ingress -``` - -For NGINX Plus: (assuming you have pushed the Ingress Controller image `nginx-plus-ingress` to your private registry `myregistry.example.com`) -```console -$ helm install my-release nginx-stable/nginx-ingress --set controller.image.repository=myregistry.example.com/nginx-plus-ingress --set controller.nginxplus=true -``` - -**Note**: If you want to use the experimental repository, replace `stable` with `edge` and add the `--devel` flag. - -### Installing Using Chart Sources - -To install the chart with the release name my-release (my-release is the name that you choose): - -For NGINX: -```console -$ helm install my-release . -``` - -For NGINX Plus: -```console -$ helm install my-release -f values-plus.yaml . -``` - -**Note**: If you want to use the experimental repository, replace the value in the `tag` field inside the yaml files with `edge`. - -The command deploys the Ingress Controller in your Kubernetes cluster in the default configuration. The configuration section lists the parameters that can be configured during installation. - -When deploying the Ingress Controller, make sure to use your own TLS certificate and key for the default server rather than the default pre-generated ones. Read the [Configuration](#Configuration) section below to see how to configure a TLS certificate and key for the default server. Note that the default server returns the Not Found page with the 404 status code for all requests for domains for which there are no Ingress rules defined. - -## Upgrading the Chart - -### Upgrading the CRDs - -Helm does not upgrade the CRDs during a release upgrade. Before you upgrade a release, run the following command to upgrade the CRDs: - -```console -$ kubectl apply -f crds/ -``` -> **Note**: The following warning is expected and can be ignored: `Warning: kubectl apply should be used on resource created by either kubectl create --save-config or kubectl apply`. - -> **Note**: Make sure to check the [release notes](https://www.github.com/nginxinc/kubernetes-ingress/releases) for a new release for any special upgrade procedures. - -### Upgrading the Release - -To upgrade the release `my-release`: - -#### Upgrade Using Chart Sources: - -```console -$ helm upgrade my-release . -``` - -#### Upgrade via Helm Repository: - -```console -$ helm upgrade my-release nginx-stable/nginx-ingress -``` - -## Uninstalling the Chart - -### Uninstalling the Release - -To uninstall/delete the release `my-release`: - -```console -$ helm uninstall my-release -``` -The command removes all the Kubernetes components associated with the release and deletes the release. - -### Uninstalling the CRDs - -Uninstalling the release does not remove the CRDs. To remove the CRDs, run: - -```console -$ kubectl delete -f crds/ -``` -> **Note**: This command will delete all the corresponding custom resources in your cluster across all namespaces. Please ensure there are no custom resources that you want to keep and there are no other Ingress Controller releases running in the cluster. - -## Running Multiple Ingress Controllers - -If you are running multiple Ingress Controller releases in your cluster with enabled custom resources, the releases will share a single version of the CRDs. As a result, make sure that the Ingress Controller versions match the version of the CRDs. Additionally, when uninstalling a release, ensure that you don’t remove the CRDs until there are no other Ingress Controller releases running in the cluster. - -See [running multiple ingress controllers](https://docs.nginx.com/nginx-ingress-controller/installation/running-multiple-ingress-controllers/) for more details. - -## Configuration - -The following tables lists the configurable parameters of the NGINX Ingress Controller chart and their default values. - -Parameter | Description | Default ---- | --- | --- -`controller.name` | The name of the Ingress Controller daemonset or deployment. | Autogenerated -`controller.kind` | The kind of the Ingress Controller installation - deployment or daemonset. | deployment -`controller.annotations` | Allows for setting of `annotations` for deployment or daemonset. | {} -`controller.nginxplus` | Deploys the Ingress Controller for NGINX Plus. | false -`controller.nginxReloadTimeout` | The timeout in milliseconds which the Ingress Controller will wait for a successful NGINX reload after a change or at the initial start. | 60000 -`controller.hostNetwork` | Enables the Ingress Controller pods to use the host's network namespace. | false -`controller.dnsPolicy` | DNS policy for the Ingress Controller pods. | ClusterFirst -`controller.nginxDebug` | Enables debugging for NGINX. Uses the `nginx-debug` binary. Requires `error-log-level: debug` in the ConfigMap via `controller.config.entries`. | false -`controller.logLevel` | The log level of the Ingress Controller. | 1 -`controller.image.digest ` | The image digest of the Ingress Controller. | None -`controller.image.repository` | The image repository of the Ingress Controller. | nginx/nginx-ingress -`controller.image.tag` | The tag of the Ingress Controller image. | 2.4.1 -`controller.image.pullPolicy` | The pull policy for the Ingress Controller image. | IfNotPresent -`controller.lifecycle` | The lifecycle of the Ingress Controller pods. | {} -`controller.customConfigMap` | The name of the custom ConfigMap used by the Ingress Controller. If set, then the default config is ignored. | "" -`controller.config.name` | The name of the ConfigMap used by the Ingress Controller. | Autogenerated -`controller.config.annotations` | The annotations of the Ingress Controller configmap. | {} -`controller.config.entries` | The entries of the ConfigMap for customizing NGINX configuration. See [ConfigMap resource docs](https://docs.nginx.com/nginx-ingress-controller/configuration/global-configuration/configmap-resource/) for the list of supported ConfigMap keys. | {} -`controller.customPorts` | A list of custom ports to expose on the NGINX ingress controller pod. Follows the conventional Kubernetes yaml syntax for container ports. | [] -`controller.defaultTLS.cert` | The base64-encoded TLS certificate for the default HTTPS server. **Note:** By default, a pre-generated self-signed certificate is used. It is recommended that you specify your own certificate. Alternatively, omitting the default server secret completely will configure NGINX to reject TLS connections to the default server. | A pre-generated self-signed certificate. -`controller.defaultTLS.key` | The base64-encoded TLS key for the default HTTPS server. **Note:** By default, a pre-generated key is used. It is recommended that you specify your own key. Alternatively, omitting the default server secret completely will configure NGINX to reject TLS connections to the default server. | A pre-generated key. -`controller.defaultTLS.secret` | The secret with a TLS certificate and key for the default HTTPS server. The value must follow the following format: `/`. Used as an alternative to specifying a certificate and key using `controller.defaultTLS.cert` and `controller.defaultTLS.key` parameters. **Note:** Alternatively, omitting the default server secret completely will configure NGINX to reject TLS connections to the default server. | None -`controller.wildcardTLS.cert` | The base64-encoded TLS certificate for every Ingress/VirtualServer host that has TLS enabled but no secret specified. If the parameter is not set, for such Ingress/VirtualServer hosts NGINX will break any attempt to establish a TLS connection. | None -`controller.wildcardTLS.key` | The base64-encoded TLS key for every Ingress/VirtualServer host that has TLS enabled but no secret specified. If the parameter is not set, for such Ingress/VirtualServer hosts NGINX will break any attempt to establish a TLS connection. | None -`controller.wildcardTLS.secret` | The secret with a TLS certificate and key for every Ingress/VirtualServer host that has TLS enabled but no secret specified. The value must follow the following format: `/`. Used as an alternative to specifying a certificate and key using `controller.wildcardTLS.cert` and `controller.wildcardTLS.key` parameters. | None -`controller.nodeSelector` | The node selector for pod assignment for the Ingress Controller pods. | {} -`controller.terminationGracePeriodSeconds` | The termination grace period of the Ingress Controller pod. | 30 -`controller.tolerations` | The tolerations of the Ingress Controller pods. | [] -`controller.affinity` | The affinity of the Ingress Controller pods. | {} -`controller.topologySpreadConstraints` | The topology spread constraints of the Ingress controller pods. | {} -`controller.volumes` | The volumes of the Ingress Controller pods. | [] -`controller.volumeMounts` | The volumeMounts of the Ingress Controller pods. | [] -`controller.initContainers` | InitContainers for the Ingress Controller pods. | [] -`controller.extraContainers` | Extra (eg. sidecar) containers for the Ingress Controller pods. | [] -`controller.resources` | The resources of the Ingress Controller pods. | requests: cpu=100m,memory=128Mi -`controller.replicaCount` | The number of replicas of the Ingress Controller deployment. | 1 -`controller.ingressClass` | A class of the Ingress Controller. An IngressClass resource with the name equal to the class must be deployed. Otherwise, the Ingress Controller will fail to start. The Ingress Controller only processes resources that belong to its class - i.e. have the "ingressClassName" field resource equal to the class. The Ingress Controller processes all the VirtualServer/VirtualServerRoute/TransportServer resources that do not have the "ingressClassName" field for all versions of kubernetes. | nginx -`controller.setAsDefaultIngress` | New Ingresses without an `"ingressClassName"` field specified will be assigned the class specified in `controller.ingressClass`. | false -`controller.watchNamespace` | Comma separated list of namespaces the Ingress Controller should watch for resources. By default the Ingress Controller watches all namespaces. Please note that if configuring multiple namespaces using the Helm cli `--set` option, the string needs to wrapped in double quotes and the commas escaped using a backslash - e.g. `--set controller.watchNamespace="default\,nginx-ingress"`. | "" -`controller.watchSecretNamespace` | Comma separated list of namespaces the Ingress Controller should watch for resources of type Secret. If this arg is not configured, the Ingress Controller watches the same namespaces for all resources. See `watch-namespace`. Please note that if configuring multiple namespaces using the Helm cli `--set` option, the string needs to wrapped in double quotes and the commas escaped using a backslash - e.g. `--set controller.watchSecretNamespace="default\,nginx-ingress"`. | "" -`controller.enableCustomResources` | Enable the custom resources. | true -`controller.enablePreviewPolicies` | Enable preview policies. This parameter is deprecated. To enable OIDC Policies please use `controller.enableOIDC` instead. | false -`controller.enableOIDC` | Enable OIDC policies. | false -`controller.enableTLSPassthrough` | Enable TLS Passthrough on port 443. Requires `controller.enableCustomResources`. | false -`controller.enableCertManager` | Enable x509 automated certificate management for VirtualServer resources using cert-manager (cert-manager.io). Requires `controller.enableCustomResources`. | false -`controller.enableExternalDNS` | Enable integration with ExternalDNS for configuring public DNS entries for VirtualServer resources using [ExternalDNS](https://github.com/kubernetes-sigs/external-dns). Requires `controller.enableCustomResources`. | false -`controller.globalConfiguration.create` | Creates the GlobalConfiguration custom resource. Requires `controller.enableCustomResources`. | false -`controller.globalConfiguration.spec` | The spec of the GlobalConfiguration for defining the global configuration parameters of the Ingress Controller. | {} -`controller.enableSnippets` | Enable custom NGINX configuration snippets in Ingress, VirtualServer, VirtualServerRoute and TransportServer resources. | false -`controller.healthStatus` | Add a location "/nginx-health" to the default server. The location responds with the 200 status code for any request. Useful for external health-checking of the Ingress Controller. | false -`controller.healthStatusURI` | Sets the URI of health status location in the default server. Requires `controller.healthStatus`. | "/nginx-health" -`controller.nginxStatus.enable` | Enable the NGINX stub_status, or the NGINX Plus API. | true -`controller.nginxStatus.port` | Set the port where the NGINX stub_status or the NGINX Plus API is exposed. | 8080 -`controller.nginxStatus.allowCidrs` | Add IP/CIDR blocks to the allow list for NGINX stub_status or the NGINX Plus API. Separate multiple IP/CIDR by commas. | 127.0.0.1,::1 -`controller.priorityClassName` | The PriorityClass of the Ingress Controller pods. | None -`controller.service.create` | Creates a service to expose the Ingress Controller pods. | true -`controller.service.type` | The type of service to create for the Ingress Controller. | LoadBalancer -`controller.service.externalTrafficPolicy` | The externalTrafficPolicy of the service. The value Local preserves the client source IP. | Local -`controller.service.annotations` | The annotations of the Ingress Controller service. | {} -`controller.service.extraLabels` | The extra labels of the service. | {} -`controller.service.loadBalancerIP` | The static IP address for the load balancer. Requires `controller.service.type` set to `LoadBalancer`. The cloud provider must support this feature. | "" -`controller.service.externalIPs` | The list of external IPs for the Ingress Controller service. | [] -`controller.service.loadBalancerSourceRanges` | The IP ranges (CIDR) that are allowed to access the load balancer. Requires `controller.service.type` set to `LoadBalancer`. The cloud provider must support this feature. | [] -`controller.service.name` | The name of the service. | Autogenerated -`controller.service.customPorts` | A list of custom ports to expose through the Ingress Controller service. Follows the conventional Kubernetes yaml syntax for service ports. | [] -`controller.service.httpPort.enable` | Enables the HTTP port for the Ingress Controller service. | true -`controller.service.httpPort.port` | The HTTP port of the Ingress Controller service. | 80 -`controller.service.httpPort.nodePort` | The custom NodePort for the HTTP port. Requires `controller.service.type` set to `NodePort`. | "" -`controller.service.httpPort.targetPort` | The target port of the HTTP port of the Ingress Controller service. | 80 -`controller.service.httpsPort.enable` | Enables the HTTPS port for the Ingress Controller service. | true -`controller.service.httpsPort.port` | The HTTPS port of the Ingress Controller service. | 443 -`controller.service.httpsPort.nodePort` | The custom NodePort for the HTTPS port. Requires `controller.service.type` set to `NodePort`. | "" -`controller.service.httpsPort.targetPort` | The target port of the HTTPS port of the Ingress Controller service. | 443 -`controller.serviceAccount.annotations` | The annotations of the Ingress Controller service account. | {} -`controller.serviceAccount.name` | The name of the service account of the Ingress Controller pods. Used for RBAC. | Autogenerated -`controller.serviceAccount.imagePullSecretName` | The name of the secret containing docker registry credentials. Secret must exist in the same namespace as the helm release. | "" -`controller.serviceMonitor.name` | The name of the serviceMonitor. | Autogenerated -`controller.serviceMonitor.create` | Create a ServiceMonitor custom resource. | false -`controller.serviceMonitor.labels` | Kubernetes object labels to attach to the serviceMonitor object. | "" -`controller.serviceMonitor.selectorMatchLabels` | A set of labels to allow the selection of endpoints for the ServiceMonitor. | "" -`controller.serviceMonitor.endpoints` | A list of endpoints allowed as part of this ServiceMonitor. | "" -`controller.reportIngressStatus.enable` | Updates the address field in the status of Ingress resources with an external address of the Ingress Controller. You must also specify the source of the external address either through an external service via `controller.reportIngressStatus.externalService`, `controller.reportIngressStatus.ingressLink` or the `external-status-address` entry in the ConfigMap via `controller.config.entries`. **Note:** `controller.config.entries.external-status-address` takes precedence over the others. | true -`controller.reportIngressStatus.externalService` | Specifies the name of the service with the type LoadBalancer through which the Ingress Controller is exposed externally. The external address of the service is used when reporting the status of Ingress, VirtualServer and VirtualServerRoute resources. `controller.reportIngressStatus.enable` must be set to `true`. The default is autogenerated and enabled when `controller.service.create` is set to `true` and `controller.service.type` is set to `LoadBalancer`. | Autogenerated -`controller.reportIngressStatus.ingressLink` | Specifies the name of the IngressLink resource, which exposes the Ingress Controller pods via a BIG-IP system. The IP of the BIG-IP system is used when reporting the status of Ingress, VirtualServer and VirtualServerRoute resources. `controller.reportIngressStatus.enable` must be set to `true`. | "" -`controller.reportIngressStatus.enableLeaderElection` | Enable Leader election to avoid multiple replicas of the controller reporting the status of Ingress resources. `controller.reportIngressStatus.enable` must be set to `true`. | true -`controller.reportIngressStatus.leaderElectionLockName` | Specifies the name of the ConfigMap, within the same namespace as the controller, used as the lock for leader election. controller.reportIngressStatus.enableLeaderElection must be set to true. | Autogenerated -`controller.reportIngressStatus.annotations` | The annotations of the leader election configmap. | {} -`controller.pod.annotations` | The annotations of the Ingress Controller pod. | {} -`controller.pod.extraLabels` | The additional extra labels of the Ingress Controller pod. | {} -`controller.appprotect.enable` | Enables the App Protect WAF module in the Ingress Controller. | false -`controller.appprotectdos.enable` | Enables the App Protect DoS module in the Ingress Controller. | false -`controller.appprotectdos.debug` | Enable debugging for App Protect DoS. | false -`controller.appprotectdos.maxDaemons` | Max number of ADMD instances. | 1 -`controller.appprotectdos.maxWorkers` | Max number of nginx processes to support. | Number of CPU cores in the machine -`controller.appprotectdos.memory` | RAM memory size to consume in MB. | 50% of free RAM in the container or 80MB, the smaller -`controller.readyStatus.enable` | Enables the readiness endpoint `"/nginx-ready"`. The endpoint returns a success code when NGINX has loaded all the config after the startup. This also configures a readiness probe for the Ingress Controller pods that uses the readiness endpoint. | true -`controller.readyStatus.port` | The HTTP port for the readiness endpoint. | 8081 -`controller.readyStatus.initialDelaySeconds` | The number of seconds after the Ingress Controller pod has started before readiness probes are initiated. | 0 -`controller.enableLatencyMetrics` | Enable collection of latency metrics for upstreams. Requires `prometheus.create`. | false -`controller.minReadySeconds` | Specifies the minimum number of seconds for which a newly created Pod should be ready without any of its containers crashing, for it to be considered available. [docs](https://kubernetes.io/docs/concepts/workloads/controllers/deployment/#min-ready-seconds) | 0 -`controller.strategy` | Specifies the strategy used to replace old Pods by new ones. [docs](https://kubernetes.io/docs/concepts/workloads/controllers/deployment/#strategy) | {} -`controller.disableIPV6` | Disable IPV6 listeners explicitly for nodes that do not support the IPV6 stack. | false -`rbac.create` | Configures RBAC. | true -`prometheus.create` | Expose NGINX or NGINX Plus metrics in the Prometheus format. | false -`prometheus.port` | Configures the port to scrape the metrics. | 9113 -`prometheus.scheme` | Configures the HTTP scheme to use for connections to the Prometheus endpoint. | http -`prometheus.secret` | The namespace / name of a Kubernetes TLS Secret. If specified, this secret is used to secure the Prometheus endpoint with TLS connections. | "" -`nginxServiceMesh.enable` | Enable integration with NGINX Service Mesh. See the NGINX Service Mesh [docs](https://docs.nginx.com/nginx-service-mesh/tutorials/kic/deploy-with-kic/) for more details. Requires `controller.nginxplus`. | false -`nginxServiceMesh.enableEgress` | Enable NGINX Service Mesh workloads to route egress traffic through the Ingress Controller. See the NGINX Service Mesh [docs](https://docs.nginx.com/nginx-service-mesh/tutorials/kic/deploy-with-kic/#enabling-egress) for more details. Requires `nginxServiceMesh.enable`. | false - -## Notes -* The values-icp.yaml file is used for deploying the Ingress Controller on IBM Cloud Private. See the [blog post](https://www.nginx.com/blog/nginx-ingress-controller-ibm-cloud-private/) for more details. -* The values-nsm.yaml file is used for deploying the Ingress Controller with NGINX Service Mesh. See the NGINX Service Mesh [docs](https://docs.nginx.com/nginx-service-mesh/tutorials/kic/deploy-with-kic/) for more details. diff --git a/deployments/helm-chart/chart-icon.png b/deployments/helm-chart/chart-icon.png deleted file mode 100644 index 52961c9a6f..0000000000 Binary files a/deployments/helm-chart/chart-icon.png and /dev/null differ diff --git a/deployments/helm-chart/crds/appprotect.f5.com_aplogconfs.yaml b/deployments/helm-chart/crds/appprotect.f5.com_aplogconfs.yaml deleted file mode 100644 index 53b7fb40d7..0000000000 --- a/deployments/helm-chart/crds/appprotect.f5.com_aplogconfs.yaml +++ /dev/null @@ -1,80 +0,0 @@ -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.10.0 - creationTimestamp: null - name: aplogconfs.appprotect.f5.com -spec: - group: appprotect.f5.com - names: - kind: APLogConf - listKind: APLogConfList - plural: aplogconfs - singular: aplogconf - preserveUnknownFields: false - scope: Namespaced - versions: - - name: v1beta1 - schema: - openAPIV3Schema: - description: APLogConf is the Schema for the APLogConfs API - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: APLogConfSpec defines the desired state of APLogConf - properties: - content: - properties: - escaping_characters: - items: - properties: - from: - type: string - to: - type: string - type: object - type: array - format: - enum: - - splunk - - arcsight - - default - - user-defined - - grpc - type: string - format_string: - type: string - list_delimiter: - type: string - list_prefix: - type: string - list_suffix: - type: string - max_message_size: - pattern: ^([1-9]|[1-5][0-9]|6[0-4])k$ - type: string - max_request_size: - pattern: ^([1-9]|[1-9][0-9]|[1-9][0-9]{2}|1[0-9]{3}|20[1-3][0-9]|204[1-8]|any)$ - type: string - type: object - filter: - properties: - request_type: - enum: - - all - - illegal - - blocked - type: string - type: object - type: object - type: object - served: true - storage: true diff --git a/deployments/helm-chart/crds/appprotect.f5.com_appolicies.yaml b/deployments/helm-chart/crds/appprotect.f5.com_appolicies.yaml deleted file mode 100644 index 8c494414cb..0000000000 --- a/deployments/helm-chart/crds/appprotect.f5.com_appolicies.yaml +++ /dev/null @@ -1,1903 +0,0 @@ -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.10.0 - creationTimestamp: null - name: appolicies.appprotect.f5.com -spec: - group: appprotect.f5.com - names: - kind: APPolicy - listKind: APPolicyList - plural: appolicies - singular: appolicy - preserveUnknownFields: false - scope: Namespaced - versions: - - name: v1beta1 - schema: - openAPIV3Schema: - description: APPolicyConfig is the Schema for the APPolicyconfigs API - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: APPolicySpec defines the desired state of APPolicy - properties: - modifications: - items: - properties: - action: - type: string - description: - type: string - entity: - properties: - name: - type: string - type: object - entityChanges: - properties: - type: - type: string - type: object - type: object - x-kubernetes-preserve-unknown-fields: true - type: array - modificationsReference: - properties: - link: - pattern: ^http - type: string - type: object - policy: - description: Defines the App Protect policy - properties: - applicationLanguage: - enum: - - iso-8859-10 - - iso-8859-6 - - windows-1255 - - auto-detect - - koi8-r - - gb18030 - - iso-8859-8 - - windows-1250 - - iso-8859-9 - - windows-1252 - - iso-8859-16 - - gb2312 - - iso-8859-2 - - iso-8859-5 - - windows-1257 - - windows-1256 - - iso-8859-13 - - windows-874 - - windows-1253 - - iso-8859-3 - - euc-jp - - utf-8 - - gbk - - windows-1251 - - big5 - - iso-8859-1 - - shift_jis - - euc-kr - - iso-8859-4 - - iso-8859-7 - - iso-8859-15 - type: string - blocking-settings: - properties: - evasions: - items: - properties: - description: - enum: - - '%u decoding' - - Apache whitespace - - Bad unescape - - Bare byte decoding - - Directory traversals - - IIS backslashes - - IIS Unicode codepoints - - Multiple decoding - type: string - enabled: - type: boolean - maxDecodingPasses: - type: integer - type: object - type: array - http-protocols: - items: - properties: - description: - enum: - - Unescaped space in URL - - Unparsable request content - - Several Content-Length headers - - 'POST request with Content-Length: 0' - - Null in request - - No Host header in HTTP/1.1 request - - Multiple host headers - - Host header contains IP address - - High ASCII characters in headers - - Header name with no header value - - CRLF characters before request start - - Content length should be a positive number - - Chunked request with Content-Length header - - Check maximum number of parameters - - Check maximum number of headers - - Body in GET or HEAD requests - - Bad multipart/form-data request parsing - - Bad multipart parameters parsing - - Bad HTTP version - - Bad host header value - type: string - enabled: - type: boolean - maxHeaders: - type: integer - maxParams: - type: integer - type: object - type: array - violations: - items: - properties: - alarm: - type: boolean - block: - type: boolean - description: - type: string - name: - enum: - - VIOL_GRPC_FORMAT - - VIOL_GRPC_MALFORMED - - VIOL_GRPC_METHOD - - VIOL_PARAMETER_ARRAY_VALUE - - VIOL_PARAMETER_VALUE_REGEXP - - VIOL_CSRF - - VIOL_PARAMETER_VALUE_BASE64 - - VIOL_MANDATORY_HEADER - - VIOL_HEADER_REPEATED - - VIOL_ASM_COOKIE_MODIFIED - - VIOL_BLACKLISTED_IP - - VIOL_COOKIE_EXPIRED - - VIOL_COOKIE_LENGTH - - VIOL_COOKIE_MALFORMED - - VIOL_COOKIE_MODIFIED - - VIOL_DATA_GUARD - - VIOL_ENCODING - - VIOL_EVASION - - VIOL_FILETYPE - - VIOL_FILE_UPLOAD - - VIOL_FILE_UPLOAD_IN_BODY - - VIOL_HEADER_LENGTH - - VIOL_HEADER_METACHAR - - VIOL_HTTP_PROTOCOL - - VIOL_HTTP_RESPONSE_STATUS - - VIOL_JSON_FORMAT - - VIOL_JSON_MALFORMED - - VIOL_JSON_SCHEMA - - VIOL_MANDATORY_PARAMETER - - VIOL_MANDATORY_REQUEST_BODY - - VIOL_METHOD - - VIOL_PARAMETER - - VIOL_PARAMETER_DATA_TYPE - - VIOL_PARAMETER_EMPTY_VALUE - - VIOL_PARAMETER_LOCATION - - VIOL_PARAMETER_MULTIPART_NULL_VALUE - - VIOL_PARAMETER_NAME_METACHAR - - VIOL_PARAMETER_NUMERIC_VALUE - - VIOL_PARAMETER_REPEATED - - VIOL_PARAMETER_STATIC_VALUE - - VIOL_PARAMETER_VALUE_LENGTH - - VIOL_PARAMETER_VALUE_METACHAR - - VIOL_POST_DATA_LENGTH - - VIOL_QUERY_STRING_LENGTH - - VIOL_RATING_THREAT - - VIOL_RATING_NEED_EXAMINATION - - VIOL_REQUEST_MAX_LENGTH - - VIOL_REQUEST_LENGTH - - VIOL_THREAT_CAMPAIGN - - VIOL_URL - - VIOL_URL_CONTENT_TYPE - - VIOL_URL_LENGTH - - VIOL_URL_METACHAR - - VIOL_XML_FORMAT - - VIOL_XML_MALFORMED - type: string - type: object - type: array - type: object - blockingSettingReference: - properties: - link: - pattern: ^http - type: string - type: object - bot-defense: - properties: - mitigations: - properties: - anomalies: - items: - properties: - $action: - enum: - - delete - type: string - action: - enum: - - alarm - - block - - default - - detect - - ignore - type: string - name: - type: string - scoreThreshold: - pattern: '[0-9]|[1-9][0-9]|1[0-4][0-9]|150|default' - type: string - type: object - type: array - browsers: - items: - properties: - $action: - enum: - - delete - type: string - action: - enum: - - alarm - - block - - detect - type: string - browserDefinition: - properties: - $action: - enum: - - delete - type: string - isUserDefined: - type: boolean - matchRegex: - type: string - matchString: - type: string - name: - type: string - type: object - maxVersion: - maximum: 2147483647 - minimum: 0 - type: integer - minVersion: - maximum: 2147483647 - minimum: 0 - type: integer - name: - type: string - type: object - type: array - classes: - items: - properties: - action: - enum: - - alarm - - block - - detect - - ignore - type: string - name: - enum: - - browser - - malicious-bot - - suspicious-browser - - trusted-bot - - unknown - - untrusted-bot - type: string - type: object - type: array - signatures: - items: - properties: - $action: - enum: - - delete - type: string - action: - enum: - - alarm - - block - - detect - - ignore - type: string - name: - type: string - type: object - type: array - type: object - settings: - properties: - caseSensitiveHttpHeaders: - type: boolean - isEnabled: - type: boolean - type: object - type: object - browser-definitions: - items: - properties: - $action: - enum: - - delete - type: string - isUserDefined: - type: boolean - matchRegex: - type: string - matchString: - type: string - name: - type: string - type: object - type: array - caseInsensitive: - type: boolean - character-sets: - items: - properties: - characterSet: - items: - properties: - isAllowed: - type: boolean - metachar: - type: string - type: object - type: array - characterSetType: - enum: - - gwt-content - - header - - json-content - - parameter-name - - parameter-value - - plain-text-content - - url - - xml-content - type: string - type: object - type: array - characterSetReference: - properties: - link: - pattern: ^http - type: string - type: object - cookie-settings: - properties: - maximumCookieHeaderLength: - pattern: any|\d+ - type: string - type: object - cookieReference: - properties: - link: - pattern: ^http - type: string - type: object - cookieSettingsReference: - properties: - link: - pattern: ^http - type: string - type: object - cookies: - items: - properties: - $action: - enum: - - delete - type: string - accessibleOnlyThroughTheHttpProtocol: - type: boolean - attackSignaturesCheck: - type: boolean - decodeValueAsBase64: - enum: - - enabled - - disabled - - required - type: string - enforcementType: - type: string - insertSameSiteAttribute: - enum: - - lax - - none - - none-value - - strict - type: string - name: - type: string - securedOverHttpsConnection: - type: boolean - signatureOverrides: - items: - properties: - enabled: - type: boolean - name: - type: string - signatureId: - type: integer - tag: - type: string - type: object - type: array - type: - enum: - - explicit - - wildcard - type: string - wildcardOrder: - type: integer - type: object - type: array - csrf-protection: - properties: - enabled: - type: boolean - expirationTimeInSeconds: - pattern: disabled|\d+ - type: string - sslOnly: - type: boolean - type: object - csrf-urls: - items: - properties: - $action: - enum: - - delete - type: string - enforcementAction: - enum: - - verify-origin - - none - type: string - method: - enum: - - GET - - POST - - any - type: string - url: - type: string - wildcardOrder: - type: integer - type: object - type: array - data-guard: - properties: - creditCardNumbers: - type: boolean - enabled: - type: boolean - enforcementMode: - enum: - - ignore-urls-in-list - - enforce-urls-in-list - type: string - enforcementUrls: - items: - type: string - type: array - lastCcnDigitsToExpose: - type: integer - lastSsnDigitsToExpose: - type: integer - maskData: - type: boolean - usSocialSecurityNumbers: - type: boolean - type: object - dataGuardReference: - properties: - link: - pattern: ^http - type: string - type: object - description: - type: string - enablePassiveMode: - type: boolean - enforcementMode: - enum: - - transparent - - blocking - type: string - enforcer-settings: - properties: - enforcerStateCookies: - properties: - httpOnlyAttribute: - type: boolean - sameSiteAttribute: - enum: - - lax - - none - - none-value - - strict - type: string - secureAttribute: - enum: - - always - - never - type: string - type: object - type: object - filetypeReference: - properties: - link: - pattern: ^http - type: string - type: object - filetypes: - items: - properties: - $action: - enum: - - delete - type: string - allowed: - type: boolean - checkPostDataLength: - type: boolean - checkQueryStringLength: - type: boolean - checkRequestLength: - type: boolean - checkUrlLength: - type: boolean - name: - type: string - postDataLength: - type: integer - queryStringLength: - type: integer - requestLength: - type: integer - responseCheck: - type: boolean - type: - enum: - - explicit - - wildcard - type: string - urlLength: - type: integer - wildcardOrder: - type: integer - type: object - type: array - fullPath: - type: string - general: - properties: - allowedResponseCodes: - items: - format: int32 - maximum: 999 - minimum: 100 - type: integer - type: array - customXffHeaders: - items: - type: string - type: array - maskCreditCardNumbersInRequest: - type: boolean - trustXff: - type: boolean - type: object - generalReference: - properties: - link: - pattern: ^http - type: string - type: object - grpc-profiles: - items: - properties: - $action: - enum: - - delete - type: string - associateUrls: - type: boolean - attackSignaturesCheck: - type: boolean - defenseAttributes: - properties: - allowUnknownFields: - type: boolean - maximumDataLength: - pattern: any|\d+ - type: string - type: object - description: - type: string - hasIdlFiles: - type: boolean - idlFiles: - items: - properties: - idlFile: - properties: - contents: - type: string - fileName: - type: string - isBase64: - type: boolean - type: object - importUrl: - type: string - isPrimary: - type: boolean - primaryIdlFileName: - type: string - type: object - type: array - metacharElementCheck: - type: boolean - name: - type: string - signatureOverrides: - items: - properties: - enabled: - type: boolean - name: - type: string - signatureId: - type: integer - tag: - type: string - type: object - type: array - type: object - type: array - header-settings: - properties: - maximumHttpHeaderLength: - pattern: any|\d+ - type: string - type: object - headerReference: - properties: - link: - pattern: ^http - type: string - type: object - headerSettingsReference: - properties: - link: - pattern: ^http - type: string - type: object - headers: - items: - properties: - $action: - enum: - - delete - type: string - allowRepeatedOccurrences: - type: boolean - base64Decoding: - type: boolean - checkSignatures: - type: boolean - decodeValueAsBase64: - enum: - - enabled - - disabled - - required - type: string - htmlNormalization: - type: boolean - mandatory: - type: boolean - maskValueInLogs: - type: boolean - name: - type: string - normalizationViolations: - type: boolean - percentDecoding: - type: boolean - signatureOverrides: - items: - properties: - enabled: - type: boolean - name: - type: string - signatureId: - type: integer - tag: - type: string - type: object - type: array - type: - enum: - - explicit - - wildcard - type: string - urlNormalization: - type: boolean - wildcardOrder: - type: integer - type: object - type: array - host-names: - items: - properties: - $action: - enum: - - delete - type: string - includeSubdomains: - type: boolean - name: - type: string - type: object - type: array - idl-files: - items: - properties: - contents: - type: string - fileName: - type: string - isBase64: - type: boolean - type: object - type: array - json-profiles: - items: - properties: - $action: - enum: - - delete - type: string - attackSignaturesCheck: - type: boolean - defenseAttributes: - properties: - maximumArrayLength: - pattern: any|\d+ - type: string - maximumStructureDepth: - pattern: any|\d+ - type: string - maximumTotalLengthOfJSONData: - pattern: any|\d+ - type: string - maximumValueLength: - pattern: any|\d+ - type: string - tolerateJSONParsingWarnings: - type: boolean - type: object - description: - type: string - handleJsonValuesAsParameters: - type: boolean - hasValidationFiles: - type: boolean - metacharOverrides: - items: - properties: - isAllowed: - type: boolean - metachar: - type: string - type: object - type: array - name: - type: string - signatureOverrides: - items: - properties: - enabled: - type: boolean - name: - type: string - signatureId: - type: integer - tag: - type: string - type: object - type: array - validationFiles: - items: - properties: - importUrl: - type: string - isPrimary: - type: boolean - jsonValidationFile: - properties: - $action: - enum: - - delete - type: string - contents: - type: string - fileName: - type: string - isBase64: - type: boolean - type: object - type: object - type: array - type: object - type: array - json-validation-files: - items: - properties: - $action: - enum: - - delete - type: string - contents: - type: string - fileName: - type: string - isBase64: - type: boolean - type: object - type: array - jsonProfileReference: - properties: - link: - pattern: ^http - type: string - type: object - jsonValidationFileReference: - properties: - link: - pattern: ^http - type: string - type: object - methodReference: - properties: - link: - pattern: ^http - type: string - type: object - methods: - items: - properties: - $action: - enum: - - delete - type: string - name: - type: string - type: object - type: array - name: - type: string - open-api-files: - items: - properties: - link: - pattern: ^http - type: string - type: object - type: array - parameterReference: - properties: - link: - pattern: ^http - type: string - type: object - parameters: - items: - properties: - $action: - enum: - - delete - type: string - allowEmptyValue: - type: boolean - allowRepeatedParameterName: - type: boolean - arraySerializationFormat: - enum: - - csv - - form - - label - - matrix - - multi - - multipart - - pipe - - ssv - - tsv - type: string - attackSignaturesCheck: - type: boolean - checkMaxValue: - type: boolean - checkMaxValueLength: - type: boolean - checkMetachars: - type: boolean - checkMinValue: - type: boolean - checkMinValueLength: - type: boolean - checkMultipleOfValue: - type: boolean - contentProfile: - properties: - name: - type: string - type: object - dataType: - enum: - - alpha-numeric - - binary - - boolean - - decimal - - email - - integer - - none - - phone - type: string - decodeValueAsBase64: - enum: - - enabled - - disabled - - required - type: string - disallowFileUploadOfExecutables: - type: boolean - enableRegularExpression: - type: boolean - exclusiveMax: - type: boolean - exclusiveMin: - type: boolean - isBase64: - type: boolean - isCookie: - type: boolean - isHeader: - type: boolean - level: - enum: - - global - - url - type: string - mandatory: - type: boolean - maximumLength: - type: integer - maximumValue: - type: integer - metacharsOnParameterValueCheck: - type: boolean - minimumLength: - type: integer - minimumValue: - type: integer - multipleOf: - type: integer - name: - type: string - nameMetacharOverrides: - items: - properties: - isAllowed: - type: boolean - metachar: - type: string - type: object - type: array - objectSerializationStyle: - type: string - parameterEnumValues: - items: - type: string - type: array - parameterLocation: - enum: - - any - - cookie - - form-data - - header - - path - - query - type: string - regularExpression: - type: string - sensitiveParameter: - type: boolean - signatureOverrides: - items: - properties: - enabled: - type: boolean - name: - type: string - signatureId: - type: integer - tag: - type: string - type: object - type: array - staticValues: - type: string - type: - enum: - - explicit - - wildcard - type: string - url: - type: object - valueMetacharOverrides: - items: - properties: - isAllowed: - type: boolean - metachar: - type: string - type: object - type: array - valueType: - enum: - - array - - auto-detect - - dynamic-content - - dynamic-parameter-name - - ignore - - json - - object - - openapi-array - - static-content - - user-input - - xml - type: string - wildcardOrder: - type: integer - type: object - type: array - response-pages: - items: - properties: - ajaxActionType: - enum: - - alert-popup - - custom - - redirect - type: string - ajaxCustomContent: - type: string - ajaxEnabled: - type: boolean - ajaxPopupMessage: - type: string - ajaxRedirectUrl: - type: string - grpcStatusCode: - pattern: ABORTED|ALREADY_EXISTS|CANCELLED|DATA_LOSS|DEADLINE_EXCEEDED|FAILED_PRECONDITION|INTERNAL|INVALID_ARGUMENT|NOT_FOUND|OK|OUT_OF_RANGE|PERMISSION_DENIED|RESOURCE_EXHAUSTED|UNAUTHENTICATED|UNAVAILABLE|UNIMPLEMENTED|UNKNOWN|d+ - type: string - grpcStatusMessage: - type: string - responseActionType: - enum: - - custom - - default - - erase-cookies - - redirect - - soap-fault - type: string - responseContent: - type: string - responseHeader: - type: string - responsePageType: - enum: - - ajax - - ajax-login - - captcha - - captcha-fail - - default - - failed-login-honeypot - - failed-login-honeypot-ajax - - hijack - - leaked-credentials - - leaked-credentials-ajax - - mobile - - persistent-flow - - xml - - grpc - type: string - responseRedirectUrl: - type: string - type: object - type: array - responsePageReference: - properties: - link: - pattern: ^http - type: string - type: object - sensitive-parameters: - items: - properties: - $action: - enum: - - delete - type: string - name: - type: string - type: object - type: array - sensitiveParameterReference: - properties: - link: - pattern: ^http - type: string - type: object - server-technologies: - items: - properties: - $action: - enum: - - delete - type: string - serverTechnologyName: - enum: - - Jenkins - - SharePoint - - Oracle Application Server - - Python - - Oracle Identity Manager - - Spring Boot - - CouchDB - - SQLite - - Handlebars - - Mustache - - Prototype - - Zend - - Redis - - Underscore.js - - Ember.js - - ZURB Foundation - - ef.js - - Vue.js - - UIKit - - TYPO3 CMS - - RequireJS - - React - - MooTools - - Laravel - - GraphQL - - Google Web Toolkit - - Express.js - - CodeIgniter - - Backbone.js - - AngularJS - - JavaScript - - Nginx - - Jetty - - Joomla - - JavaServer Faces (JSF) - - Ruby - - MongoDB - - Django - - Node.js - - Citrix - - JBoss - - Elasticsearch - - Apache Struts - - XML - - PostgreSQL - - IBM DB2 - - Sybase/ASE - - CGI - - Proxy Servers - - SSI (Server Side Includes) - - Cisco - - Novell - - Macromedia JRun - - BEA Systems WebLogic Server - - Lotus Domino - - MySQL - - Oracle - - Microsoft SQL Server - - PHP - - Outlook Web Access - - Apache/NCSA HTTP Server - - Apache Tomcat - - WordPress - - Macromedia ColdFusion - - Unix/Linux - - Microsoft Windows - - ASP.NET - - Front Page Server Extensions (FPSE) - - IIS - - WebDAV - - ASP - - Java Servlets/JSP - - jQuery - type: string - type: object - type: array - serverTechnologyReference: - properties: - link: - pattern: ^http - type: string - type: object - signature-requirements: - items: - properties: - $action: - enum: - - delete - type: string - tag: - type: string - type: object - type: array - signature-sets: - items: - properties: - $action: - enum: - - delete - type: string - alarm: - type: boolean - block: - type: boolean - name: - type: string - type: object - x-kubernetes-preserve-unknown-fields: true - type: array - signature-settings: - properties: - attackSignatureFalsePositiveMode: - enum: - - detect - - detect-and-allow - - disabled - type: string - minimumAccuracyForAutoAddedSignatures: - enum: - - high - - low - - medium - type: string - type: object - signatureReference: - properties: - link: - pattern: ^http - type: string - type: object - signatureSetReference: - properties: - link: - pattern: ^http - type: string - type: object - signatureSettingReference: - properties: - link: - pattern: ^http - type: string - type: object - signatures: - items: - properties: - enabled: - type: boolean - name: - type: string - signatureId: - type: integer - tag: - type: string - type: object - type: array - softwareVersion: - type: string - template: - properties: - name: - type: string - type: object - threat-campaigns: - items: - properties: - isEnabled: - type: boolean - name: - type: string - type: object - type: array - threatCampaignReference: - properties: - link: - pattern: ^http - type: string - type: object - urlReference: - properties: - link: - pattern: ^http - type: string - type: object - urls: - items: - properties: - $action: - enum: - - delete - type: string - allowRenderingInFrames: - enum: - - never - - only-same - type: string - allowRenderingInFramesOnlyFrom: - type: string - attackSignaturesCheck: - type: boolean - clickjackingProtection: - type: boolean - description: - type: string - disallowFileUploadOfExecutables: - type: boolean - html5CrossOriginRequestsEnforcement: - properties: - allowOriginsEnforcementMode: - enum: - - replace-with - - unmodified - type: string - checkAllowedMethods: - type: boolean - crossDomainAllowedOrigin: - items: - properties: - includeSubDomains: - type: boolean - originName: - type: string - originPort: - pattern: any|\d+ - type: string - originProtocol: - enum: - - http - - http/https - - https - type: string - type: object - type: array - enforcementMode: - enum: - - disabled - - enforce - type: string - type: object - isAllowed: - type: boolean - mandatoryBody: - type: boolean - metacharOverrides: - items: - properties: - isAllowed: - type: boolean - metachar: - type: string - type: object - type: array - metacharsOnUrlCheck: - type: boolean - method: - enum: - - ACL - - BCOPY - - BDELETE - - BMOVE - - BPROPFIND - - BPROPPATCH - - CHECKIN - - CHECKOUT - - CONNECT - - COPY - - DELETE - - GET - - HEAD - - LINK - - LOCK - - MERGE - - MKCOL - - MKWORKSPACE - - MOVE - - NOTIFY - - OPTIONS - - PATCH - - POLL - - POST - - PROPFIND - - PROPPATCH - - PUT - - REPORT - - RPC_IN_DATA - - RPC_OUT_DATA - - SEARCH - - SUBSCRIBE - - TRACE - - TRACK - - UNLINK - - UNLOCK - - UNSUBSCRIBE - - VERSION_CONTROL - - X-MS-ENUMATTS - - '*' - type: string - methodOverrides: - items: - properties: - allowed: - type: boolean - method: - enum: - - ACL - - BCOPY - - BDELETE - - BMOVE - - BPROPFIND - - BPROPPATCH - - CHECKIN - - CHECKOUT - - CONNECT - - COPY - - DELETE - - GET - - HEAD - - LINK - - LOCK - - MERGE - - MKCOL - - MKWORKSPACE - - MOVE - - NOTIFY - - OPTIONS - - PATCH - - POLL - - POST - - PROPFIND - - PROPPATCH - - PUT - - REPORT - - RPC_IN_DATA - - RPC_OUT_DATA - - SEARCH - - SUBSCRIBE - - TRACE - - TRACK - - UNLINK - - UNLOCK - - UNSUBSCRIBE - - VERSION_CONTROL - - X-MS-ENUMATTS - type: string - type: object - type: array - methodsOverrideOnUrlCheck: - type: boolean - name: - type: string - operationId: - type: string - positionalParameters: - items: - properties: - parameter: - properties: - $action: - enum: - - delete - type: string - allowEmptyValue: - type: boolean - allowRepeatedParameterName: - type: boolean - arraySerializationFormat: - enum: - - csv - - form - - label - - matrix - - multi - - multipart - - pipe - - ssv - - tsv - type: string - attackSignaturesCheck: - type: boolean - checkMaxValue: - type: boolean - checkMaxValueLength: - type: boolean - checkMetachars: - type: boolean - checkMinValue: - type: boolean - checkMinValueLength: - type: boolean - checkMultipleOfValue: - type: boolean - contentProfile: - properties: - name: - type: string - type: object - dataType: - enum: - - alpha-numeric - - binary - - boolean - - decimal - - email - - integer - - none - - phone - type: string - decodeValueAsBase64: - enum: - - enabled - - disabled - - required - type: string - disallowFileUploadOfExecutables: - type: boolean - enableRegularExpression: - type: boolean - exclusiveMax: - type: boolean - exclusiveMin: - type: boolean - isBase64: - type: boolean - isCookie: - type: boolean - isHeader: - type: boolean - level: - enum: - - global - - url - type: string - mandatory: - type: boolean - maximumLength: - type: integer - maximumValue: - type: integer - metacharsOnParameterValueCheck: - type: boolean - minimumLength: - type: integer - minimumValue: - type: integer - multipleOf: - type: integer - name: - type: string - nameMetacharOverrides: - items: - properties: - isAllowed: - type: boolean - metachar: - type: string - type: object - type: array - objectSerializationStyle: - type: string - parameterEnumValues: - items: - type: string - type: array - parameterLocation: - enum: - - any - - cookie - - form-data - - header - - path - - query - type: string - regularExpression: - type: string - sensitiveParameter: - type: boolean - signatureOverrides: - items: - properties: - enabled: - type: boolean - name: - type: string - signatureId: - type: integer - tag: - type: string - type: object - type: array - staticValues: - type: string - type: - enum: - - explicit - - wildcard - type: string - url: - type: object - valueMetacharOverrides: - items: - properties: - isAllowed: - type: boolean - metachar: - type: string - type: object - type: array - valueType: - enum: - - array - - auto-detect - - dynamic-content - - dynamic-parameter-name - - ignore - - json - - object - - openapi-array - - static-content - - user-input - - xml - type: string - wildcardOrder: - type: integer - type: object - urlSegmentIndex: - type: integer - type: object - type: array - protocol: - enum: - - http - - https - type: string - signatureOverrides: - items: - properties: - enabled: - type: boolean - name: - type: string - signatureId: - type: integer - tag: - type: string - type: object - type: array - type: - enum: - - explicit - - wildcard - type: string - urlContentProfiles: - items: - properties: - contentProfile: - properties: - name: - type: string - type: object - headerName: - type: string - headerOrder: - type: string - headerValue: - type: string - name: - type: string - type: - enum: - - apply-content-signatures - - apply-value-and-content-signatures - - disallow - - do-nothing - - form-data - - gwt - - json - - xml - - grpc - type: string - type: object - type: array - wildcardOrder: - type: integer - type: object - type: array - whitelist-ips: - items: - properties: - $action: - enum: - - delete - type: string - blockRequests: - enum: - - always - - never - - policy-default - type: string - ipAddress: - pattern: '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}' - type: string - ipMask: - pattern: '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}' - type: string - neverLogRequests: - type: boolean - type: object - type: array - whitelistIpReference: - properties: - link: - pattern: ^http - type: string - type: object - xml-profiles: - items: - properties: - $action: - enum: - - delete - type: string - attackSignaturesCheck: - type: boolean - defenseAttributes: - properties: - allowCDATA: - type: boolean - allowDTDs: - type: boolean - allowExternalReferences: - type: boolean - allowProcessingInstructions: - type: boolean - maximumAttributeValueLength: - pattern: any|\d+ - type: string - maximumAttributesPerElement: - pattern: any|\d+ - type: string - maximumChildrenPerElement: - pattern: any|\d+ - type: string - maximumDocumentDepth: - pattern: any|\d+ - type: string - maximumDocumentSize: - pattern: any|\d+ - type: string - maximumElements: - pattern: any|\d+ - type: string - maximumNSDeclarations: - pattern: any|\d+ - type: string - maximumNameLength: - pattern: any|\d+ - type: string - maximumNamespaceLength: - pattern: any|\d+ - type: string - tolerateCloseTagShorthand: - type: boolean - tolerateLeadingWhiteSpace: - type: boolean - tolerateNumericNames: - type: boolean - type: object - description: - type: string - enableWss: - type: boolean - followSchemaLinks: - type: boolean - name: - type: string - signatureOverrides: - items: - properties: - enabled: - type: boolean - name: - type: string - signatureId: - type: integer - tag: - type: string - type: object - type: array - type: object - type: array - xml-validation-files: - items: - properties: - $action: - enum: - - delete - type: string - contents: - type: string - fileName: - type: string - isBase64: - type: boolean - type: object - type: array - xmlProfileReference: - properties: - link: - pattern: ^http - type: string - type: object - xmlValidationFileReference: - properties: - link: - pattern: ^http - type: string - type: object - type: object - type: object - type: object - served: true - storage: true diff --git a/deployments/helm-chart/crds/appprotect.f5.com_apusersigs.yaml b/deployments/helm-chart/crds/appprotect.f5.com_apusersigs.yaml deleted file mode 100644 index 34eb0784f4..0000000000 --- a/deployments/helm-chart/crds/appprotect.f5.com_apusersigs.yaml +++ /dev/null @@ -1,93 +0,0 @@ -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.10.0 - creationTimestamp: null - name: apusersigs.appprotect.f5.com -spec: - group: appprotect.f5.com - names: - kind: APUserSig - listKind: APUserSigList - plural: apusersigs - singular: apusersig - preserveUnknownFields: false - scope: Namespaced - versions: - - name: v1beta1 - schema: - openAPIV3Schema: - description: APUserSig is the Schema for the apusersigs API - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: APUserSigSpec defines the desired state of APUserSig - properties: - properties: - type: string - signatures: - items: - properties: - accuracy: - enum: - - high - - medium - - low - type: string - attackType: - properties: - name: - type: string - type: object - description: - type: string - name: - type: string - references: - properties: - type: - enum: - - bugtraq - - cve - - nessus - - url - type: string - value: - type: string - type: object - risk: - enum: - - high - - medium - - low - type: string - rule: - type: string - signatureType: - enum: - - request - - response - type: string - systems: - items: - properties: - name: - type: string - type: object - type: array - type: object - type: array - tag: - type: string - type: object - type: object - served: true - storage: true diff --git a/deployments/helm-chart/crds/appprotectdos.f5.com_apdoslogconfs.yaml b/deployments/helm-chart/crds/appprotectdos.f5.com_apdoslogconfs.yaml deleted file mode 100644 index e23e87184b..0000000000 --- a/deployments/helm-chart/crds/appprotectdos.f5.com_apdoslogconfs.yaml +++ /dev/null @@ -1,68 +0,0 @@ -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.9.2 - creationTimestamp: null - name: apdoslogconfs.appprotectdos.f5.com -spec: - group: appprotectdos.f5.com - names: - kind: APDosLogConf - listKind: APDosLogConfList - plural: apdoslogconfs - singular: apdoslogconf - preserveUnknownFields: false - scope: Namespaced - versions: - - name: v1beta1 - schema: - openAPIV3Schema: - description: APDosLogConf is the Schema for the APDosLogConfs API - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: APDosLogConfSpec defines the desired state of APDosLogConf - properties: - content: - properties: - format: - enum: - - splunk - - arcsight - - user-defined - type: string - format_string: - type: string - max_message_size: - pattern: ^([1-9]|[1-5][0-9]|6[0-4])k$ - type: string - type: object - filter: - properties: - traffic-mitigation-stats: - enum: - - none - - all - default: all - type: string - bad-actors: - pattern: ^(none|all|top ([1-9]|[1-9][0-9]|[1-9][0-9]{2,4}|100000))$ - default: top 10 - type: string - attack-signatures: - pattern: ^(none|all|top ([1-9]|[1-9][0-9]|[1-9][0-9]{2,4}|100000))$ - default: top 10 - type: string - type: object - type: object - type: object - served: true - storage: true diff --git a/deployments/helm-chart/crds/appprotectdos.f5.com_apdospolicy.yaml b/deployments/helm-chart/crds/appprotectdos.f5.com_apdospolicy.yaml deleted file mode 100644 index a16399a1a2..0000000000 --- a/deployments/helm-chart/crds/appprotectdos.f5.com_apdospolicy.yaml +++ /dev/null @@ -1,68 +0,0 @@ -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.9.2 - creationTimestamp: null - name: apdospolicies.appprotectdos.f5.com -spec: - group: appprotectdos.f5.com - names: - kind: APDosPolicy - listKind: APDosPoliciesList - plural: apdospolicies - singular: apdospolicy - preserveUnknownFields: false - scope: Namespaced - versions: - - name: v1beta1 - schema: - openAPIV3Schema: - type: object - description: APDosPolicy is the Schema for the APDosPolicy API - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - type: object - description: APDosPolicySpec defines the desired state of APDosPolicy - properties: - mitigation_mode: - enum: - - "standard" - - "conservative" - - "none" - default: "standard" - type: string - signatures: - enum: - - "on" - - "off" - default: "on" - type: string - bad_actors: - enum: - - "on" - - "off" - default: "on" - type: string - automation_tools_detection: - enum: - - "on" - - "off" - default: "on" - type: string - tls_fingerprint: - enum: - - "on" - - "off" - default: "on" - type: string - served: true - storage: true diff --git a/deployments/helm-chart/crds/appprotectdos.f5.com_dosprotectedresources.yaml b/deployments/helm-chart/crds/appprotectdos.f5.com_dosprotectedresources.yaml deleted file mode 100644 index c18e11737d..0000000000 --- a/deployments/helm-chart/crds/appprotectdos.f5.com_dosprotectedresources.yaml +++ /dev/null @@ -1,81 +0,0 @@ -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.10.0 - creationTimestamp: null - name: dosprotectedresources.appprotectdos.f5.com -spec: - group: appprotectdos.f5.com - names: - kind: DosProtectedResource - listKind: DosProtectedResourceList - plural: dosprotectedresources - shortNames: - - pr - singular: dosprotectedresource - scope: Namespaced - versions: - - name: v1beta1 - schema: - openAPIV3Schema: - description: DosProtectedResource defines a Dos protected resource. - type: object - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: DosProtectedResourceSpec defines the properties and values a DosProtectedResource can have. - type: object - properties: - apDosMonitor: - description: 'ApDosMonitor is how NGINX App Protect DoS monitors the stress level of the protected object. The monitor requests are sent from localhost (127.0.0.1). Default value: URI - None, protocol - http1, timeout - NGINX App Protect DoS default.' - type: object - properties: - protocol: - description: Protocol determines if the server listens on http1 / http2 / grpc. The default is http1. - type: string - enum: - - http1 - - http2 - - grpc - timeout: - description: Timeout determines how long (in seconds) should NGINX App Protect DoS wait for a response. Default is 10 seconds for http1/http2 and 5 seconds for grpc. - type: integer - format: int64 - uri: - description: 'URI is the destination to the desired protected object in the nginx.conf:' - type: string - apDosPolicy: - description: ApDosPolicy is the namespace/name of a ApDosPolicy resource - type: string - dosAccessLogDest: - description: DosAccessLogDest is the network address for the access logs - type: string - dosSecurityLog: - description: DosSecurityLog defines the security log of the DosProtectedResource. - type: object - properties: - apDosLogConf: - description: ApDosLogConf is the namespace/name of a APDosLogConf resource - type: string - dosLogDest: - description: DosLogDest is the network address of a logging service, can be either IP or DNS name. - type: string - enable: - description: Enable enables the security logging feature if set to true - type: boolean - enable: - description: Enable enables the DOS feature if set to true - type: boolean - name: - description: Name is the name of protected object, max of 63 characters. - type: string - served: true - storage: true diff --git a/deployments/helm-chart/crds/externaldns.nginx.org_dnsendpoints.yaml b/deployments/helm-chart/crds/externaldns.nginx.org_dnsendpoints.yaml deleted file mode 100644 index 206626bb59..0000000000 --- a/deployments/helm-chart/crds/externaldns.nginx.org_dnsendpoints.yaml +++ /dev/null @@ -1,85 +0,0 @@ -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.10.0 - creationTimestamp: null - name: dnsendpoints.externaldns.nginx.org -spec: - group: externaldns.nginx.org - names: - kind: DNSEndpoint - listKind: DNSEndpointList - plural: dnsendpoints - singular: dnsendpoint - scope: Namespaced - versions: - - name: v1 - schema: - openAPIV3Schema: - description: DNSEndpoint is the CRD wrapper for Endpoint - type: object - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: DNSEndpointSpec holds information about endpoints. - type: object - properties: - endpoints: - type: array - items: - description: Endpoint describes DNS Endpoint. - type: object - properties: - dnsName: - description: The hostname for the DNS record - type: string - labels: - description: Labels stores labels defined for the Endpoint - type: object - additionalProperties: - type: string - providerSpecific: - description: ProviderSpecific stores provider specific config - type: array - items: - description: ProviderSpecificProperty represents provider specific config property. - type: object - properties: - name: - description: Name of the property - type: string - value: - description: Value of the property - type: string - recordTTL: - description: TTL for the record - type: integer - format: int64 - recordType: - description: RecordType type of record, e.g. CNAME, A, SRV, TXT, MX - type: string - targets: - description: The targets the DNS service points to - type: array - items: - type: string - status: - description: DNSEndpointStatus represents generation observed by the external dns controller. - type: object - properties: - observedGeneration: - description: The generation observed by by the external-dns controller. - type: integer - format: int64 - served: true - storage: true - subresources: - status: {} diff --git a/deployments/helm-chart/crds/k8s.nginx.org_globalconfigurations.yaml b/deployments/helm-chart/crds/k8s.nginx.org_globalconfigurations.yaml deleted file mode 100644 index 5c9ef1f1f1..0000000000 --- a/deployments/helm-chart/crds/k8s.nginx.org_globalconfigurations.yaml +++ /dev/null @@ -1,50 +0,0 @@ -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.10.0 - creationTimestamp: null - name: globalconfigurations.k8s.nginx.org -spec: - group: k8s.nginx.org - names: - kind: GlobalConfiguration - listKind: GlobalConfigurationList - plural: globalconfigurations - shortNames: - - gc - singular: globalconfiguration - scope: Namespaced - versions: - - name: v1alpha1 - schema: - openAPIV3Schema: - description: GlobalConfiguration defines the GlobalConfiguration resource. - type: object - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: GlobalConfigurationSpec is the spec of the GlobalConfiguration resource. - type: object - properties: - listeners: - type: array - items: - description: Listener defines a listener. - type: object - properties: - name: - type: string - port: - type: integer - protocol: - type: string - served: true - storage: true diff --git a/deployments/helm-chart/crds/k8s.nginx.org_policies.yaml b/deployments/helm-chart/crds/k8s.nginx.org_policies.yaml deleted file mode 100644 index 802f351423..0000000000 --- a/deployments/helm-chart/crds/k8s.nginx.org_policies.yaml +++ /dev/null @@ -1,290 +0,0 @@ -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.10.0 - creationTimestamp: null - name: policies.k8s.nginx.org -spec: - group: k8s.nginx.org - names: - kind: Policy - listKind: PolicyList - plural: policies - shortNames: - - pol - singular: policy - scope: Namespaced - versions: - - additionalPrinterColumns: - - description: Current state of the Policy. If the resource has a valid status, it means it has been validated and accepted by the Ingress Controller. - jsonPath: .status.state - name: State - type: string - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v1 - schema: - openAPIV3Schema: - description: Policy defines a Policy for VirtualServer and VirtualServerRoute resources. - type: object - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: PolicySpec is the spec of the Policy resource. The spec includes multiple fields, where each field represents a different policy. Only one policy (field) is allowed. - type: object - properties: - accessControl: - description: AccessControl defines an access policy based on the source IP of a request. - type: object - properties: - allow: - type: array - items: - type: string - deny: - type: array - items: - type: string - basicAuth: - description: 'BasicAuth holds HTTP Basic authentication configuration policy status: preview' - type: object - properties: - realm: - type: string - secret: - type: string - egressMTLS: - description: EgressMTLS defines an Egress MTLS policy. - type: object - properties: - ciphers: - type: string - protocols: - type: string - serverName: - type: boolean - sessionReuse: - type: boolean - sslName: - type: string - tlsSecret: - type: string - trustedCertSecret: - type: string - verifyDepth: - type: integer - verifyServer: - type: boolean - ingressClassName: - type: string - ingressMTLS: - description: IngressMTLS defines an Ingress MTLS policy. - type: object - properties: - clientCertSecret: - type: string - verifyClient: - type: string - verifyDepth: - type: integer - jwt: - description: JWTAuth holds JWT authentication configuration. - type: object - properties: - realm: - type: string - secret: - type: string - token: - type: string - oidc: - description: OIDC defines an Open ID Connect policy. - type: object - properties: - authEndpoint: - type: string - clientID: - type: string - clientSecret: - type: string - jwksURI: - type: string - redirectURI: - type: string - scope: - type: string - tokenEndpoint: - type: string - zoneSyncLeeway: - type: integer - rateLimit: - description: RateLimit defines a rate limit policy. - type: object - properties: - burst: - type: integer - delay: - type: integer - dryRun: - type: boolean - key: - type: string - logLevel: - type: string - noDelay: - type: boolean - rate: - type: string - rejectCode: - type: integer - zoneSize: - type: string - waf: - description: WAF defines an WAF policy. - type: object - properties: - apPolicy: - type: string - enable: - type: boolean - securityLog: - description: SecurityLog defines the security log of a WAF policy. - type: object - properties: - apLogConf: - type: string - enable: - type: boolean - logDest: - type: string - securityLogs: - type: array - items: - description: SecurityLog defines the security log of a WAF policy. - type: object - properties: - apLogConf: - type: string - enable: - type: boolean - logDest: - type: string - status: - description: PolicyStatus is the status of the policy resource - type: object - properties: - message: - type: string - reason: - type: string - state: - type: string - served: true - storage: true - subresources: - status: {} - - name: v1alpha1 - schema: - openAPIV3Schema: - description: Policy defines a Policy for VirtualServer and VirtualServerRoute resources. - type: object - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: PolicySpec is the spec of the Policy resource. The spec includes multiple fields, where each field represents a different policy. Only one policy (field) is allowed. - type: object - properties: - accessControl: - description: AccessControl defines an access policy based on the source IP of a request. - type: object - properties: - allow: - type: array - items: - type: string - deny: - type: array - items: - type: string - egressMTLS: - description: EgressMTLS defines an Egress MTLS policy. - type: object - properties: - ciphers: - type: string - protocols: - type: string - serverName: - type: boolean - sessionReuse: - type: boolean - sslName: - type: string - tlsSecret: - type: string - trustedCertSecret: - type: string - verifyDepth: - type: integer - verifyServer: - type: boolean - ingressMTLS: - description: IngressMTLS defines an Ingress MTLS policy. - type: object - properties: - clientCertSecret: - type: string - verifyClient: - type: string - verifyDepth: - type: integer - jwt: - description: JWTAuth holds JWT authentication configuration. - type: object - properties: - realm: - type: string - secret: - type: string - token: - type: string - rateLimit: - description: RateLimit defines a rate limit policy. - type: object - properties: - burst: - type: integer - delay: - type: integer - dryRun: - type: boolean - key: - type: string - logLevel: - type: string - noDelay: - type: boolean - rate: - type: string - rejectCode: - type: integer - zoneSize: - type: string - served: true - storage: false diff --git a/deployments/helm-chart/crds/k8s.nginx.org_transportservers.yaml b/deployments/helm-chart/crds/k8s.nginx.org_transportservers.yaml deleted file mode 100644 index 67069a61af..0000000000 --- a/deployments/helm-chart/crds/k8s.nginx.org_transportservers.yaml +++ /dev/null @@ -1,151 +0,0 @@ -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.10.0 - creationTimestamp: null - name: transportservers.k8s.nginx.org -spec: - group: k8s.nginx.org - names: - kind: TransportServer - listKind: TransportServerList - plural: transportservers - shortNames: - - ts - singular: transportserver - scope: Namespaced - versions: - - additionalPrinterColumns: - - description: Current state of the TransportServer. If the resource has a valid status, it means it has been validated and accepted by the Ingress Controller. - jsonPath: .status.state - name: State - type: string - - jsonPath: .status.reason - name: Reason - type: string - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v1alpha1 - schema: - openAPIV3Schema: - description: TransportServer defines the TransportServer resource. - type: object - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: TransportServerSpec is the spec of the TransportServer resource. - type: object - properties: - action: - description: Action defines an action. - type: object - properties: - pass: - type: string - host: - type: string - ingressClassName: - type: string - listener: - description: TransportServerListener defines a listener for a TransportServer. - type: object - properties: - name: - type: string - protocol: - type: string - serverSnippets: - type: string - sessionParameters: - description: SessionParameters defines session parameters. - type: object - properties: - timeout: - type: string - streamSnippets: - type: string - upstreamParameters: - description: UpstreamParameters defines parameters for an upstream. - type: object - properties: - connectTimeout: - type: string - nextUpstream: - type: boolean - nextUpstreamTimeout: - type: string - nextUpstreamTries: - type: integer - udpRequests: - type: integer - udpResponses: - type: integer - upstreams: - type: array - items: - description: Upstream defines an upstream. - type: object - properties: - failTimeout: - type: string - healthCheck: - description: HealthCheck defines the parameters for active Upstream HealthChecks. - type: object - properties: - enable: - type: boolean - fails: - type: integer - interval: - type: string - jitter: - type: string - match: - description: Match defines the parameters of a custom health check. - type: object - properties: - expect: - type: string - send: - type: string - passes: - type: integer - port: - type: integer - timeout: - type: string - loadBalancingMethod: - type: string - maxConns: - type: integer - maxFails: - type: integer - name: - type: string - port: - type: integer - service: - type: string - status: - description: TransportServerStatus defines the status for the TransportServer resource. - type: object - properties: - message: - type: string - reason: - type: string - state: - type: string - served: true - storage: true - subresources: - status: {} diff --git a/deployments/helm-chart/crds/k8s.nginx.org_virtualserverroutes.yaml b/deployments/helm-chart/crds/k8s.nginx.org_virtualserverroutes.yaml deleted file mode 100644 index 91c75853b2..0000000000 --- a/deployments/helm-chart/crds/k8s.nginx.org_virtualserverroutes.yaml +++ /dev/null @@ -1,635 +0,0 @@ -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.10.0 - creationTimestamp: null - name: virtualserverroutes.k8s.nginx.org -spec: - group: k8s.nginx.org - names: - kind: VirtualServerRoute - listKind: VirtualServerRouteList - plural: virtualserverroutes - shortNames: - - vsr - singular: virtualserverroute - scope: Namespaced - versions: - - additionalPrinterColumns: - - description: Current state of the VirtualServerRoute. If the resource has a valid status, it means it has been validated and accepted by the Ingress Controller. - jsonPath: .status.state - name: State - type: string - - jsonPath: .spec.host - name: Host - type: string - - jsonPath: .status.externalEndpoints[*].ip - name: IP - type: string - - jsonPath: .status.externalEndpoints[*].hostname - name: ExternalHostname - priority: 1 - type: string - - jsonPath: .status.externalEndpoints[*].ports - name: Ports - type: string - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v1 - schema: - openAPIV3Schema: - description: VirtualServerRoute defines the VirtualServerRoute resource. - type: object - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: VirtualServerRouteSpec is the spec of the VirtualServerRoute resource. - type: object - properties: - host: - type: string - ingressClassName: - type: string - subroutes: - type: array - items: - description: Route defines a route. - type: object - properties: - action: - description: Action defines an action. - type: object - properties: - pass: - type: string - proxy: - description: ActionProxy defines a proxy in an Action. - type: object - properties: - requestHeaders: - description: ProxyRequestHeaders defines the request headers manipulation in an ActionProxy. - type: object - properties: - pass: - type: boolean - set: - type: array - items: - description: Header defines an HTTP Header. - type: object - properties: - name: - type: string - value: - type: string - responseHeaders: - description: ProxyResponseHeaders defines the response headers manipulation in an ActionProxy. - type: object - properties: - add: - type: array - items: - description: AddHeader defines an HTTP Header with an optional Always field to use with the add_header NGINX directive. - type: object - properties: - always: - type: boolean - name: - type: string - value: - type: string - hide: - type: array - items: - type: string - ignore: - type: array - items: - type: string - pass: - type: array - items: - type: string - rewritePath: - type: string - upstream: - type: string - redirect: - description: ActionRedirect defines a redirect in an Action. - type: object - properties: - code: - type: integer - url: - type: string - return: - description: ActionReturn defines a return in an Action. - type: object - properties: - body: - type: string - code: - type: integer - type: - type: string - dos: - type: string - errorPages: - type: array - items: - description: ErrorPage defines an ErrorPage in a Route. - type: object - properties: - codes: - type: array - items: - type: integer - redirect: - description: ErrorPageRedirect defines a redirect for an ErrorPage. - type: object - properties: - code: - type: integer - url: - type: string - return: - description: ErrorPageReturn defines a return for an ErrorPage. - type: object - properties: - body: - type: string - code: - type: integer - headers: - type: array - items: - description: Header defines an HTTP Header. - type: object - properties: - name: - type: string - value: - type: string - type: - type: string - location-snippets: - type: string - matches: - type: array - items: - description: Match defines a match. - type: object - properties: - action: - description: Action defines an action. - type: object - properties: - pass: - type: string - proxy: - description: ActionProxy defines a proxy in an Action. - type: object - properties: - requestHeaders: - description: ProxyRequestHeaders defines the request headers manipulation in an ActionProxy. - type: object - properties: - pass: - type: boolean - set: - type: array - items: - description: Header defines an HTTP Header. - type: object - properties: - name: - type: string - value: - type: string - responseHeaders: - description: ProxyResponseHeaders defines the response headers manipulation in an ActionProxy. - type: object - properties: - add: - type: array - items: - description: AddHeader defines an HTTP Header with an optional Always field to use with the add_header NGINX directive. - type: object - properties: - always: - type: boolean - name: - type: string - value: - type: string - hide: - type: array - items: - type: string - ignore: - type: array - items: - type: string - pass: - type: array - items: - type: string - rewritePath: - type: string - upstream: - type: string - redirect: - description: ActionRedirect defines a redirect in an Action. - type: object - properties: - code: - type: integer - url: - type: string - return: - description: ActionReturn defines a return in an Action. - type: object - properties: - body: - type: string - code: - type: integer - type: - type: string - conditions: - type: array - items: - description: Condition defines a condition in a MatchRule. - type: object - properties: - argument: - type: string - cookie: - type: string - header: - type: string - value: - type: string - variable: - type: string - splits: - type: array - items: - description: Split defines a split. - type: object - properties: - action: - description: Action defines an action. - type: object - properties: - pass: - type: string - proxy: - description: ActionProxy defines a proxy in an Action. - type: object - properties: - requestHeaders: - description: ProxyRequestHeaders defines the request headers manipulation in an ActionProxy. - type: object - properties: - pass: - type: boolean - set: - type: array - items: - description: Header defines an HTTP Header. - type: object - properties: - name: - type: string - value: - type: string - responseHeaders: - description: ProxyResponseHeaders defines the response headers manipulation in an ActionProxy. - type: object - properties: - add: - type: array - items: - description: AddHeader defines an HTTP Header with an optional Always field to use with the add_header NGINX directive. - type: object - properties: - always: - type: boolean - name: - type: string - value: - type: string - hide: - type: array - items: - type: string - ignore: - type: array - items: - type: string - pass: - type: array - items: - type: string - rewritePath: - type: string - upstream: - type: string - redirect: - description: ActionRedirect defines a redirect in an Action. - type: object - properties: - code: - type: integer - url: - type: string - return: - description: ActionReturn defines a return in an Action. - type: object - properties: - body: - type: string - code: - type: integer - type: - type: string - weight: - type: integer - path: - type: string - policies: - type: array - items: - description: PolicyReference references a policy by name and an optional namespace. - type: object - properties: - name: - type: string - namespace: - type: string - route: - type: string - splits: - type: array - items: - description: Split defines a split. - type: object - properties: - action: - description: Action defines an action. - type: object - properties: - pass: - type: string - proxy: - description: ActionProxy defines a proxy in an Action. - type: object - properties: - requestHeaders: - description: ProxyRequestHeaders defines the request headers manipulation in an ActionProxy. - type: object - properties: - pass: - type: boolean - set: - type: array - items: - description: Header defines an HTTP Header. - type: object - properties: - name: - type: string - value: - type: string - responseHeaders: - description: ProxyResponseHeaders defines the response headers manipulation in an ActionProxy. - type: object - properties: - add: - type: array - items: - description: AddHeader defines an HTTP Header with an optional Always field to use with the add_header NGINX directive. - type: object - properties: - always: - type: boolean - name: - type: string - value: - type: string - hide: - type: array - items: - type: string - ignore: - type: array - items: - type: string - pass: - type: array - items: - type: string - rewritePath: - type: string - upstream: - type: string - redirect: - description: ActionRedirect defines a redirect in an Action. - type: object - properties: - code: - type: integer - url: - type: string - return: - description: ActionReturn defines a return in an Action. - type: object - properties: - body: - type: string - code: - type: integer - type: - type: string - weight: - type: integer - upstreams: - type: array - items: - description: Upstream defines an upstream. - type: object - properties: - buffer-size: - type: string - buffering: - type: boolean - buffers: - description: UpstreamBuffers defines Buffer Configuration for an Upstream. - type: object - properties: - number: - type: integer - size: - type: string - client-max-body-size: - type: string - connect-timeout: - type: string - fail-timeout: - type: string - healthCheck: - description: HealthCheck defines the parameters for active Upstream HealthChecks. - type: object - properties: - connect-timeout: - type: string - enable: - type: boolean - fails: - type: integer - grpcService: - type: string - grpcStatus: - type: integer - headers: - type: array - items: - description: Header defines an HTTP Header. - type: object - properties: - name: - type: string - value: - type: string - interval: - type: string - jitter: - type: string - mandatory: - type: boolean - passes: - type: integer - path: - type: string - persistent: - type: boolean - port: - type: integer - read-timeout: - type: string - send-timeout: - type: string - statusMatch: - type: string - tls: - description: UpstreamTLS defines a TLS configuration for an Upstream. - type: object - properties: - enable: - type: boolean - keepalive: - type: integer - lb-method: - type: string - max-conns: - type: integer - max-fails: - type: integer - name: - type: string - next-upstream: - type: string - next-upstream-timeout: - type: string - next-upstream-tries: - type: integer - ntlm: - type: boolean - port: - type: integer - queue: - description: UpstreamQueue defines Queue Configuration for an Upstream. - type: object - properties: - size: - type: integer - timeout: - type: string - read-timeout: - type: string - send-timeout: - type: string - service: - type: string - sessionCookie: - description: SessionCookie defines the parameters for session persistence. - type: object - properties: - domain: - type: string - enable: - type: boolean - expires: - type: string - httpOnly: - type: boolean - name: - type: string - path: - type: string - secure: - type: boolean - slow-start: - type: string - subselector: - type: object - additionalProperties: - type: string - tls: - description: UpstreamTLS defines a TLS configuration for an Upstream. - type: object - properties: - enable: - type: boolean - type: - type: string - use-cluster-ip: - type: boolean - status: - description: VirtualServerRouteStatus defines the status for the VirtualServerRoute resource. - type: object - properties: - externalEndpoints: - type: array - items: - description: ExternalEndpoint defines the IP/ Hostname and ports used to connect to this resource. - type: object - properties: - hostname: - type: string - ip: - type: string - ports: - type: string - message: - type: string - reason: - type: string - referencedBy: - type: string - state: - type: string - served: true - storage: true - subresources: - status: {} diff --git a/deployments/helm-chart/crds/k8s.nginx.org_virtualservers.yaml b/deployments/helm-chart/crds/k8s.nginx.org_virtualservers.yaml deleted file mode 100644 index b247a56226..0000000000 --- a/deployments/helm-chart/crds/k8s.nginx.org_virtualservers.yaml +++ /dev/null @@ -1,715 +0,0 @@ -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.10.0 - creationTimestamp: null - name: virtualservers.k8s.nginx.org -spec: - group: k8s.nginx.org - names: - kind: VirtualServer - listKind: VirtualServerList - plural: virtualservers - shortNames: - - vs - singular: virtualserver - scope: Namespaced - versions: - - additionalPrinterColumns: - - description: Current state of the VirtualServer. If the resource has a valid status, it means it has been validated and accepted by the Ingress Controller. - jsonPath: .status.state - name: State - type: string - - jsonPath: .spec.host - name: Host - type: string - - jsonPath: .status.externalEndpoints[*].ip - name: IP - type: string - - jsonPath: .status.externalEndpoints[*].hostname - name: ExternalHostname - priority: 1 - type: string - - jsonPath: .status.externalEndpoints[*].ports - name: Ports - type: string - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v1 - schema: - openAPIV3Schema: - description: VirtualServer defines the VirtualServer resource. - type: object - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: VirtualServerSpec is the spec of the VirtualServer resource. - type: object - properties: - dos: - type: string - externalDNS: - description: ExternalDNS defines externaldns sub-resource of a virtual server. - type: object - properties: - enable: - type: boolean - labels: - description: Labels stores labels defined for the Endpoint - type: object - additionalProperties: - type: string - providerSpecific: - description: ProviderSpecific stores provider specific config - type: array - items: - description: ProviderSpecificProperty defines specific property for using with ExternalDNS sub-resource. - type: object - properties: - name: - description: Name of the property - type: string - value: - description: Value of the property - type: string - recordTTL: - description: TTL for the record - type: integer - format: int64 - recordType: - type: string - host: - type: string - http-snippets: - type: string - ingressClassName: - type: string - policies: - type: array - items: - description: PolicyReference references a policy by name and an optional namespace. - type: object - properties: - name: - type: string - namespace: - type: string - routes: - type: array - items: - description: Route defines a route. - type: object - properties: - action: - description: Action defines an action. - type: object - properties: - pass: - type: string - proxy: - description: ActionProxy defines a proxy in an Action. - type: object - properties: - requestHeaders: - description: ProxyRequestHeaders defines the request headers manipulation in an ActionProxy. - type: object - properties: - pass: - type: boolean - set: - type: array - items: - description: Header defines an HTTP Header. - type: object - properties: - name: - type: string - value: - type: string - responseHeaders: - description: ProxyResponseHeaders defines the response headers manipulation in an ActionProxy. - type: object - properties: - add: - type: array - items: - description: AddHeader defines an HTTP Header with an optional Always field to use with the add_header NGINX directive. - type: object - properties: - always: - type: boolean - name: - type: string - value: - type: string - hide: - type: array - items: - type: string - ignore: - type: array - items: - type: string - pass: - type: array - items: - type: string - rewritePath: - type: string - upstream: - type: string - redirect: - description: ActionRedirect defines a redirect in an Action. - type: object - properties: - code: - type: integer - url: - type: string - return: - description: ActionReturn defines a return in an Action. - type: object - properties: - body: - type: string - code: - type: integer - type: - type: string - dos: - type: string - errorPages: - type: array - items: - description: ErrorPage defines an ErrorPage in a Route. - type: object - properties: - codes: - type: array - items: - type: integer - redirect: - description: ErrorPageRedirect defines a redirect for an ErrorPage. - type: object - properties: - code: - type: integer - url: - type: string - return: - description: ErrorPageReturn defines a return for an ErrorPage. - type: object - properties: - body: - type: string - code: - type: integer - headers: - type: array - items: - description: Header defines an HTTP Header. - type: object - properties: - name: - type: string - value: - type: string - type: - type: string - location-snippets: - type: string - matches: - type: array - items: - description: Match defines a match. - type: object - properties: - action: - description: Action defines an action. - type: object - properties: - pass: - type: string - proxy: - description: ActionProxy defines a proxy in an Action. - type: object - properties: - requestHeaders: - description: ProxyRequestHeaders defines the request headers manipulation in an ActionProxy. - type: object - properties: - pass: - type: boolean - set: - type: array - items: - description: Header defines an HTTP Header. - type: object - properties: - name: - type: string - value: - type: string - responseHeaders: - description: ProxyResponseHeaders defines the response headers manipulation in an ActionProxy. - type: object - properties: - add: - type: array - items: - description: AddHeader defines an HTTP Header with an optional Always field to use with the add_header NGINX directive. - type: object - properties: - always: - type: boolean - name: - type: string - value: - type: string - hide: - type: array - items: - type: string - ignore: - type: array - items: - type: string - pass: - type: array - items: - type: string - rewritePath: - type: string - upstream: - type: string - redirect: - description: ActionRedirect defines a redirect in an Action. - type: object - properties: - code: - type: integer - url: - type: string - return: - description: ActionReturn defines a return in an Action. - type: object - properties: - body: - type: string - code: - type: integer - type: - type: string - conditions: - type: array - items: - description: Condition defines a condition in a MatchRule. - type: object - properties: - argument: - type: string - cookie: - type: string - header: - type: string - value: - type: string - variable: - type: string - splits: - type: array - items: - description: Split defines a split. - type: object - properties: - action: - description: Action defines an action. - type: object - properties: - pass: - type: string - proxy: - description: ActionProxy defines a proxy in an Action. - type: object - properties: - requestHeaders: - description: ProxyRequestHeaders defines the request headers manipulation in an ActionProxy. - type: object - properties: - pass: - type: boolean - set: - type: array - items: - description: Header defines an HTTP Header. - type: object - properties: - name: - type: string - value: - type: string - responseHeaders: - description: ProxyResponseHeaders defines the response headers manipulation in an ActionProxy. - type: object - properties: - add: - type: array - items: - description: AddHeader defines an HTTP Header with an optional Always field to use with the add_header NGINX directive. - type: object - properties: - always: - type: boolean - name: - type: string - value: - type: string - hide: - type: array - items: - type: string - ignore: - type: array - items: - type: string - pass: - type: array - items: - type: string - rewritePath: - type: string - upstream: - type: string - redirect: - description: ActionRedirect defines a redirect in an Action. - type: object - properties: - code: - type: integer - url: - type: string - return: - description: ActionReturn defines a return in an Action. - type: object - properties: - body: - type: string - code: - type: integer - type: - type: string - weight: - type: integer - path: - type: string - policies: - type: array - items: - description: PolicyReference references a policy by name and an optional namespace. - type: object - properties: - name: - type: string - namespace: - type: string - route: - type: string - splits: - type: array - items: - description: Split defines a split. - type: object - properties: - action: - description: Action defines an action. - type: object - properties: - pass: - type: string - proxy: - description: ActionProxy defines a proxy in an Action. - type: object - properties: - requestHeaders: - description: ProxyRequestHeaders defines the request headers manipulation in an ActionProxy. - type: object - properties: - pass: - type: boolean - set: - type: array - items: - description: Header defines an HTTP Header. - type: object - properties: - name: - type: string - value: - type: string - responseHeaders: - description: ProxyResponseHeaders defines the response headers manipulation in an ActionProxy. - type: object - properties: - add: - type: array - items: - description: AddHeader defines an HTTP Header with an optional Always field to use with the add_header NGINX directive. - type: object - properties: - always: - type: boolean - name: - type: string - value: - type: string - hide: - type: array - items: - type: string - ignore: - type: array - items: - type: string - pass: - type: array - items: - type: string - rewritePath: - type: string - upstream: - type: string - redirect: - description: ActionRedirect defines a redirect in an Action. - type: object - properties: - code: - type: integer - url: - type: string - return: - description: ActionReturn defines a return in an Action. - type: object - properties: - body: - type: string - code: - type: integer - type: - type: string - weight: - type: integer - server-snippets: - type: string - tls: - description: TLS defines TLS configuration for a VirtualServer. - type: object - properties: - cert-manager: - description: CertManager defines a cert manager config for a TLS. - type: object - properties: - cluster-issuer: - type: string - common-name: - type: string - duration: - type: string - issuer: - type: string - issuer-group: - type: string - issuer-kind: - type: string - renew-before: - type: string - usages: - type: string - redirect: - description: TLSRedirect defines a redirect for a TLS. - type: object - properties: - basedOn: - type: string - code: - type: integer - enable: - type: boolean - secret: - type: string - upstreams: - type: array - items: - description: Upstream defines an upstream. - type: object - properties: - buffer-size: - type: string - buffering: - type: boolean - buffers: - description: UpstreamBuffers defines Buffer Configuration for an Upstream. - type: object - properties: - number: - type: integer - size: - type: string - client-max-body-size: - type: string - connect-timeout: - type: string - fail-timeout: - type: string - healthCheck: - description: HealthCheck defines the parameters for active Upstream HealthChecks. - type: object - properties: - connect-timeout: - type: string - enable: - type: boolean - fails: - type: integer - grpcService: - type: string - grpcStatus: - type: integer - headers: - type: array - items: - description: Header defines an HTTP Header. - type: object - properties: - name: - type: string - value: - type: string - interval: - type: string - jitter: - type: string - mandatory: - type: boolean - passes: - type: integer - path: - type: string - persistent: - type: boolean - port: - type: integer - read-timeout: - type: string - send-timeout: - type: string - statusMatch: - type: string - tls: - description: UpstreamTLS defines a TLS configuration for an Upstream. - type: object - properties: - enable: - type: boolean - keepalive: - type: integer - lb-method: - type: string - max-conns: - type: integer - max-fails: - type: integer - name: - type: string - next-upstream: - type: string - next-upstream-timeout: - type: string - next-upstream-tries: - type: integer - ntlm: - type: boolean - port: - type: integer - queue: - description: UpstreamQueue defines Queue Configuration for an Upstream. - type: object - properties: - size: - type: integer - timeout: - type: string - read-timeout: - type: string - send-timeout: - type: string - service: - type: string - sessionCookie: - description: SessionCookie defines the parameters for session persistence. - type: object - properties: - domain: - type: string - enable: - type: boolean - expires: - type: string - httpOnly: - type: boolean - name: - type: string - path: - type: string - secure: - type: boolean - slow-start: - type: string - subselector: - type: object - additionalProperties: - type: string - tls: - description: UpstreamTLS defines a TLS configuration for an Upstream. - type: object - properties: - enable: - type: boolean - type: - type: string - use-cluster-ip: - type: boolean - status: - description: VirtualServerStatus defines the status for the VirtualServer resource. - type: object - properties: - externalEndpoints: - type: array - items: - description: ExternalEndpoint defines the IP/ Hostname and ports used to connect to this resource. - type: object - properties: - hostname: - type: string - ip: - type: string - ports: - type: string - message: - type: string - reason: - type: string - state: - type: string - served: true - storage: true - subresources: - status: {} diff --git a/deployments/helm-chart/templates/NOTES.txt b/deployments/helm-chart/templates/NOTES.txt deleted file mode 100644 index c5f4cdf405..0000000000 --- a/deployments/helm-chart/templates/NOTES.txt +++ /dev/null @@ -1 +0,0 @@ -The NGINX Ingress Controller has been installed. diff --git a/deployments/helm-chart/templates/_helpers.tpl b/deployments/helm-chart/templates/_helpers.tpl deleted file mode 100644 index e04d1a3557..0000000000 --- a/deployments/helm-chart/templates/_helpers.tpl +++ /dev/null @@ -1,93 +0,0 @@ -{{/* vim: set filetype=mustache: */}} - -{{/* -Expand the name of the chart. -*/}} -{{- define "nginx-ingress.name" -}} -{{- printf "%s-%s" .Release.Name .Chart.Name | trunc 63 | trimSuffix "-" -}} -{{- end -}} - -{{/* -Create labels -*/}} -{{- define "nginx-ingress.labels" -}} -app.kubernetes.io/name: {{ include "nginx-ingress.name" . }} -helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version }} -app.kubernetes.io/managed-by: {{ .Release.Service }} -app.kubernetes.io/instance: {{ .Release.Name }} -{{- end -}} - -{{/* -Expand the name of the configmap. -*/}} -{{- define "nginx-ingress.configName" -}} -{{- if .Values.controller.customConfigMap -}} -{{ .Values.controller.customConfigMap }} -{{- else -}} -{{- default (include "nginx-ingress.name" .) .Values.controller.config.name -}} -{{- end -}} -{{- end -}} - -{{/* -Expand leader election lock name. -*/}} -{{- define "nginx-ingress.leaderElectionName" -}} -{{- if .Values.controller.reportIngressStatus.leaderElectionLockName -}} -{{ .Values.controller.reportIngressStatus.leaderElectionLockName }} -{{- else -}} -{{- printf "%s-%s" (include "nginx-ingress.name" .) "leader-election" -}} -{{- end -}} -{{- end -}} - -{{/* -Expand service account name. -*/}} -{{- define "nginx-ingress.serviceAccountName" -}} -{{- default (include "nginx-ingress.name" .) .Values.controller.serviceAccount.name -}} -{{- end -}} - -{{/* -Expand service name. -*/}} -{{- define "nginx-ingress.serviceName" -}} -{{- default (include "nginx-ingress.name" .) .Values.controller.service.name }} -{{- end -}} - -{{/* -Expand serviceMonitor name. -*/}} -{{- define "nginx-ingress.serviceMonitorName" -}} -{{- default (include "nginx-ingress.name" .) .Values.controller.serviceMonitor.name }} -{{- end -}} - -{{/* -Expand default TLS name. -*/}} -{{- define "nginx-ingress.defaultTLSName" -}} -{{- printf "%s-%s" (include "nginx-ingress.name" .) "default-server-tls" -}} -{{- end -}} - -{{/* -Expand wildcard TLS name. -*/}} -{{- define "nginx-ingress.wildcardTLSName" -}} -{{- printf "%s-%s" (include "nginx-ingress.name" .) "wildcard-tls" -}} -{{- end -}} - -{{/* -Expand app name. -*/}} -{{- define "nginx-ingress.appName" -}} -{{- default (include "nginx-ingress.name" .) .Values.controller.name -}} -{{- end -}} - -{{/* -Expand image name. -*/}} -{{- define "nginx-ingress.image" -}} -{{- if .Values.controller.image.digest -}} -{{- printf "%s@%s" .Values.controller.image.repository .Values.controller.image.digest -}} -{{- else -}} -{{- printf "%s:%s" .Values.controller.image.repository .Values.controller.image.tag -}} -{{- end -}} -{{- end -}} diff --git a/deployments/helm-chart/templates/controller-configmap.yaml b/deployments/helm-chart/templates/controller-configmap.yaml deleted file mode 100644 index fd11991865..0000000000 --- a/deployments/helm-chart/templates/controller-configmap.yaml +++ /dev/null @@ -1,17 +0,0 @@ -{{- if not .Values.controller.customConfigMap -}} -apiVersion: v1 -kind: ConfigMap -metadata: - name: {{ include "nginx-ingress.configName" . }} - namespace: {{ .Release.Namespace }} - labels: - {{- include "nginx-ingress.labels" . | nindent 4 }} -{{- if .Values.controller.config.annotations }} - annotations: -{{ toYaml .Values.controller.config.annotations | indent 4 }} -{{- end }} -data: -{{- if .Values.controller.config.entries }} -{{ toYaml .Values.controller.config.entries | indent 2 }} -{{- end }} -{{- end }} diff --git a/deployments/helm-chart/templates/controller-daemonset.yaml b/deployments/helm-chart/templates/controller-daemonset.yaml deleted file mode 100644 index 7b311fa2d8..0000000000 --- a/deployments/helm-chart/templates/controller-daemonset.yaml +++ /dev/null @@ -1,233 +0,0 @@ -{{- if eq .Values.controller.kind "daemonset" }} -apiVersion: apps/v1 -kind: DaemonSet -metadata: - name: {{ default (include "nginx-ingress.name" .) .Values.controller.name }} - namespace: {{ .Release.Namespace }} - labels: - {{- include "nginx-ingress.labels" . | nindent 4 }} -{{- if .Values.controller.annotations }} - annotations: {{ toYaml .Values.controller.annotations | nindent 4 }} -{{- end }} -spec: - selector: - matchLabels: - app: {{ include "nginx-ingress.appName" . }} - template: - metadata: - labels: - app: {{ include "nginx-ingress.appName" . }} -{{- if .Values.nginxServiceMesh.enable }} - nsm.nginx.com/daemonset: {{ default (include "nginx-ingress.name" .) .Values.controller.name }} - spiffe.io/spiffeid: "true" -{{- end }} -{{- if .Values.controller.pod.extraLabels }} -{{ toYaml .Values.controller.pod.extraLabels | indent 8 }} -{{- end }} -{{- if or .Values.prometheus.create (or .Values.controller.pod.annotations .Values.nginxServiceMesh.enable) }} - annotations: -{{- if .Values.prometheus.create }} - prometheus.io/scrape: "true" - prometheus.io/port: "{{ .Values.prometheus.port }}" - prometheus.io/scheme: "{{ .Values.prometheus.scheme }}" -{{- end }} -{{- if .Values.nginxServiceMesh.enable }} - nsm.nginx.com/enable-ingress: "true" - nsm.nginx.com/enable-egress: "{{ .Values.nginxServiceMesh.enableEgress }}" -{{- end }} -{{- if .Values.controller.pod.annotations }} -{{ toYaml .Values.controller.pod.annotations | indent 8 }} -{{- end }} -{{- end }} - spec: - serviceAccountName: {{ include "nginx-ingress.serviceAccountName" . }} - automountServiceAccountToken: true - terminationGracePeriodSeconds: {{ .Values.controller.terminationGracePeriodSeconds }} -{{- if .Values.controller.nodeSelector }} - nodeSelector: -{{ toYaml .Values.controller.nodeSelector | indent 8 }} -{{- end }} -{{- if .Values.controller.tolerations }} - tolerations: -{{ toYaml .Values.controller.tolerations | indent 6 }} -{{- end }} -{{- if .Values.controller.affinity }} - affinity: -{{ toYaml .Values.controller.affinity | indent 8 }} -{{- end }} -{{- if or .Values.controller.volumes .Values.nginxServiceMesh.enable }} - volumes: -{{- end }} -{{- if .Values.nginxServiceMesh.enable }} - - hostPath: - path: /run/spire/sockets - type: DirectoryOrCreate - name: spire-agent-socket -{{- end }} -{{- if .Values.controller.volumes }} -{{ toYaml .Values.controller.volumes | indent 6 }} -{{- end }} -{{- if .Values.controller.priorityClassName }} - priorityClassName: {{ .Values.controller.priorityClassName }} -{{- end }} - hostNetwork: {{ .Values.controller.hostNetwork }} - dnsPolicy: {{ .Values.controller.dnsPolicy }} - containers: - - name: {{ include "nginx-ingress.name" . }} - image: {{ include "nginx-ingress.image" . }} - imagePullPolicy: "{{ .Values.controller.image.pullPolicy }}" -{{- if .Values.controller.lifecycle }} - lifecycle: -{{ toYaml .Values.controller.lifecycle | indent 10 }} -{{- end }} - ports: - - name: http - containerPort: 80 - hostPort: 80 - - name: https - containerPort: 443 - hostPort: 443 -{{ if .Values.controller.customPorts }} -{{ toYaml .Values.controller.customPorts | indent 8 }} -{{ end }} -{{- if .Values.prometheus.create }} - - name: prometheus - containerPort: {{ .Values.prometheus.port }} -{{- end }} -{{- if .Values.controller.readyStatus.enable }} - - name: readiness-port - containerPort: {{ .Values.controller.readyStatus.port }} - readinessProbe: - httpGet: - path: /nginx-ready - port: readiness-port - periodSeconds: 1 - initialDelaySeconds: {{ .Values.controller.readyStatus.initialDelaySeconds }} -{{- end }} - securityContext: - allowPrivilegeEscalation: true - runAsUser: 101 #nginx - runAsNonRoot: true - capabilities: - drop: - - ALL - add: - - NET_BIND_SERVICE -{{- if or .Values.controller.volumeMounts .Values.nginxServiceMesh.enable }} - volumeMounts: -{{- end }} -{{- if .Values.nginxServiceMesh.enable }} - - mountPath: /run/spire/sockets - name: spire-agent-socket -{{- end }} -{{- if .Values.controller.volumeMounts }} -{{ toYaml .Values.controller.volumeMounts | indent 8 }} -{{- end }} - env: - - name: POD_NAMESPACE - valueFrom: - fieldRef: - fieldPath: metadata.namespace - - name: POD_NAME - valueFrom: - fieldRef: - fieldPath: metadata.name -{{- if .Values.nginxServiceMesh.enable }} - - name: POD_SERVICEACCOUNT - valueFrom: - fieldRef: - fieldPath: spec.serviceAccountName -{{- end }} - resources: -{{ toYaml .Values.controller.resources | indent 10 }} - args: - - -nginx-plus={{ .Values.controller.nginxplus }} - - -nginx-reload-timeout={{ .Values.controller.nginxReloadTimeout }} - - -enable-app-protect={{ .Values.controller.appprotect.enable }} -{{- if and .Values.controller.appprotect.enable .Values.controller.appprotect.logLevel }} - - -app-protect-log-level={{ .Values.controller.appprotect.logLevel }} -{{ end }} - - -enable-app-protect-dos={{ .Values.controller.appprotectdos.enable }} - {{- if .Values.controller.appprotectdos.enable }} - - -app-protect-dos-debug={{ .Values.controller.appprotectdos.debug }} - - -app-protect-dos-max-daemons={{ .Values.controller.appprotectdos.maxWorkers }} - - -app-protect-dos-max-workers={{ .Values.controller.appprotectdos.maxDaemons }} - - -app-protect-dos-memory={{ .Values.controller.appprotectdos.memory }} - {{ end }} - - -nginx-configmaps=$(POD_NAMESPACE)/{{ include "nginx-ingress.configName" . }} -{{- if .Values.controller.defaultTLS.secret }} - - -default-server-tls-secret={{ .Values.controller.defaultTLS.secret }} -{{ else if and (.Values.controller.defaultTLS.cert) (.Values.controller.defaultTLS.key) }} - - -default-server-tls-secret=$(POD_NAMESPACE)/{{ include "nginx-ingress.defaultTLSName" . }} -{{- end }} - - -ingress-class={{ .Values.controller.ingressClass }} -{{- if .Values.controller.watchNamespace }} - - -watch-namespace={{ .Values.controller.watchNamespace }} -{{- end }} -{{- if .Values.controller.watchSecretNamespace }} - - -watch-secret-namespace={{ .Values.controller.watchSecretNamespace }} -{{- end }} - - -health-status={{ .Values.controller.healthStatus }} - - -health-status-uri={{ .Values.controller.healthStatusURI }} - - -nginx-debug={{ .Values.controller.nginxDebug }} - - -v={{ .Values.controller.logLevel }} - - -nginx-status={{ .Values.controller.nginxStatus.enable }} -{{- if .Values.controller.nginxStatus.enable }} - - -nginx-status-port={{ .Values.controller.nginxStatus.port }} - - -nginx-status-allow-cidrs={{ .Values.controller.nginxStatus.allowCidrs }} -{{- end }} -{{- if .Values.controller.reportIngressStatus.enable }} - - -report-ingress-status -{{- if .Values.controller.reportIngressStatus.ingressLink }} - - -ingresslink={{ .Values.controller.reportIngressStatus.ingressLink }} -{{- else if .Values.controller.reportIngressStatus.externalService }} - - -external-service={{ .Values.controller.reportIngressStatus.externalService }} -{{- else if and (.Values.controller.service.create) (eq .Values.controller.service.type "LoadBalancer") }} - - -external-service={{ include "nginx-ingress.serviceName" . }} -{{- end }} - - -enable-leader-election={{ .Values.controller.reportIngressStatus.enableLeaderElection }} - - -leader-election-lock-name={{ include "nginx-ingress.leaderElectionName" . }} -{{- end }} -{{- if .Values.controller.wildcardTLS.secret }} - - -wildcard-tls-secret={{ .Values.controller.wildcardTLS.secret }} -{{- else if and .Values.controller.wildcardTLS.cert .Values.controller.wildcardTLS.key }} - - -wildcard-tls-secret=$(POD_NAMESPACE)/{{ include "nginx-ingress.wildcardTLSName" . }} -{{- end }} - - -enable-prometheus-metrics={{ .Values.prometheus.create }} - - -prometheus-metrics-listen-port={{ .Values.prometheus.port }} - - -prometheus-tls-secret={{ .Values.prometheus.secret }} - - -enable-custom-resources={{ .Values.controller.enableCustomResources }} - - -enable-snippets={{ .Values.controller.enableSnippets }} - - -include-year={{ .Values.controller.includeYear }} - - -disable-ipv6={{ .Values.controller.disableIPV6 }} -{{- if .Values.controller.enableCustomResources }} - - -enable-tls-passthrough={{ .Values.controller.enableTLSPassthrough }} - - -enable-preview-policies={{ .Values.controller.enablePreviewPolicies }} - - -enable-cert-manager={{ .Values.controller.enableCertManager }} - - -enable-oidc={{ .Values.controller.enableOIDC }} - - -enable-external-dns={{ .Values.controller.enableExternalDNS }} -{{- if .Values.controller.globalConfiguration.create }} - - -global-configuration=$(POD_NAMESPACE)/{{ include "nginx-ingress.name" . }} -{{- end }} -{{- end }} - - -ready-status={{ .Values.controller.readyStatus.enable }} - - -ready-status-port={{ .Values.controller.readyStatus.port }} - - -enable-latency-metrics={{ .Values.controller.enableLatencyMetrics }} -{{- if .Values.nginxServiceMesh.enable }} - - -spire-agent-address=/run/spire/sockets/agent.sock - - -enable-internal-routes={{ .Values.nginxServiceMesh.enableEgress }} -{{- end }} -{{- if .Values.controller.extraContainers }} - {{ toYaml .Values.controller.extraContainers | nindent 6 }} -{{- end }} -{{- if .Values.controller.initContainers }} - initContainers: {{ toYaml .Values.controller.initContainers | nindent 8 }} -{{- end }} -{{- if .Values.controller.strategy }} - updateStrategy: -{{ toYaml .Values.controller.strategy | indent 4 }} -{{- end }} -{{- if .Values.controller.minReadySeconds }} - minReadySeconds: {{ .Values.controller.minReadySeconds }} -{{- end }} -{{- end }} diff --git a/deployments/helm-chart/templates/controller-deployment.yaml b/deployments/helm-chart/templates/controller-deployment.yaml deleted file mode 100644 index 9ec53bf972..0000000000 --- a/deployments/helm-chart/templates/controller-deployment.yaml +++ /dev/null @@ -1,236 +0,0 @@ -{{- if eq .Values.controller.kind "deployment" }} -apiVersion: apps/v1 -kind: Deployment -metadata: - name: {{ default (include "nginx-ingress.name" .) .Values.controller.name }} - namespace: {{ .Release.Namespace }} - labels: - {{- include "nginx-ingress.labels" . | nindent 4 }} -{{- if .Values.controller.annotations }} - annotations: {{ toYaml .Values.controller.annotations | nindent 4 }} -{{- end }} -spec: - replicas: {{ .Values.controller.replicaCount }} - selector: - matchLabels: - app: {{ include "nginx-ingress.appName" . }} - template: - metadata: - labels: - app: {{ include "nginx-ingress.appName" . }} -{{- if .Values.nginxServiceMesh.enable }} - nsm.nginx.com/deployment: {{ default (include "nginx-ingress.name" .) .Values.controller.name }} - spiffe.io/spiffeid: "true" -{{- end }} -{{- if .Values.controller.pod.extraLabels }} -{{ toYaml .Values.controller.pod.extraLabels | indent 8 }} -{{- end }} -{{- if or .Values.prometheus.create (or .Values.controller.pod.annotations .Values.nginxServiceMesh.enable) }} - annotations: -{{- if .Values.prometheus.create }} - prometheus.io/scrape: "true" - prometheus.io/port: "{{ .Values.prometheus.port }}" - prometheus.io/scheme: "{{ .Values.prometheus.scheme }}" -{{- end }} -{{- if .Values.nginxServiceMesh.enable }} - nsm.nginx.com/enable-ingress: "true" - nsm.nginx.com/enable-egress: "{{ .Values.nginxServiceMesh.enableEgress }}" -{{- end }} -{{- if .Values.controller.pod.annotations }} -{{ toYaml .Values.controller.pod.annotations | indent 8 }} -{{- end }} -{{- end }} - spec: -{{- if .Values.controller.nodeSelector }} - nodeSelector: -{{ toYaml .Values.controller.nodeSelector | indent 8 }} -{{- end }} -{{- if .Values.controller.tolerations }} - tolerations: -{{ toYaml .Values.controller.tolerations | indent 6 }} -{{- end }} -{{- if .Values.controller.affinity }} - affinity: -{{ toYaml .Values.controller.affinity | indent 8 }} -{{- end }} -{{- if .Values.controller.topologySpreadConstraints }} - topologySpreadConstraints: -{{ toYaml .Values.controller.topologySpreadConstraints | indent 8 }} -{{- end }} -{{- if or (.Values.controller.volumes) (.Values.nginxServiceMesh.enable) }} - volumes: -{{- end }} -{{- if .Values.nginxServiceMesh.enable }} - - hostPath: - path: /run/spire/sockets - type: DirectoryOrCreate - name: spire-agent-socket -{{- end }} -{{- if .Values.controller.volumes }} -{{ toYaml .Values.controller.volumes | indent 6 }} -{{- end }} -{{- if .Values.controller.priorityClassName }} - priorityClassName: {{ .Values.controller.priorityClassName }} -{{- end }} - serviceAccountName: {{ include "nginx-ingress.serviceAccountName" . }} - automountServiceAccountToken: true - terminationGracePeriodSeconds: {{ .Values.controller.terminationGracePeriodSeconds }} - hostNetwork: {{ .Values.controller.hostNetwork }} - dnsPolicy: {{ .Values.controller.dnsPolicy }} - containers: - - image: {{ include "nginx-ingress.image" . }} - name: {{ include "nginx-ingress.name" . }} - imagePullPolicy: "{{ .Values.controller.image.pullPolicy }}" -{{- if .Values.controller.lifecycle }} - lifecycle: -{{ toYaml .Values.controller.lifecycle | indent 10 }} -{{- end }} - ports: - - name: http - containerPort: 80 - - name: https - containerPort: 443 -{{ if .Values.controller.customPorts }} -{{ toYaml .Values.controller.customPorts | indent 8 }} -{{ end }} -{{- if .Values.prometheus.create }} - - name: prometheus - containerPort: {{ .Values.prometheus.port }} -{{- end }} -{{- if .Values.controller.readyStatus.enable }} - - name: readiness-port - containerPort: {{ .Values.controller.readyStatus.port }} - readinessProbe: - httpGet: - path: /nginx-ready - port: readiness-port - periodSeconds: 1 - initialDelaySeconds: {{ .Values.controller.readyStatus.initialDelaySeconds }} -{{- end }} - resources: -{{ toYaml .Values.controller.resources | indent 10 }} - securityContext: - allowPrivilegeEscalation: true - runAsUser: 101 #nginx - runAsNonRoot: true - capabilities: - drop: - - ALL - add: - - NET_BIND_SERVICE -{{- if or (.Values.controller.volumeMounts) (.Values.nginxServiceMesh.enable) }} - volumeMounts: -{{- end }} -{{- if .Values.nginxServiceMesh.enable }} - - mountPath: /run/spire/sockets - name: spire-agent-socket -{{- end }} -{{- if .Values.controller.volumeMounts}} -{{ toYaml .Values.controller.volumeMounts | indent 8 }} -{{- end }} - env: - - name: POD_NAMESPACE - valueFrom: - fieldRef: - fieldPath: metadata.namespace - - name: POD_NAME - valueFrom: - fieldRef: - fieldPath: metadata.name -{{- if .Values.nginxServiceMesh.enable }} - - name: POD_SERVICEACCOUNT - valueFrom: - fieldRef: - fieldPath: spec.serviceAccountName -{{- end }} - args: - - -nginx-plus={{ .Values.controller.nginxplus }} - - -nginx-reload-timeout={{ .Values.controller.nginxReloadTimeout }} - - -enable-app-protect={{ .Values.controller.appprotect.enable }} -{{- if and .Values.controller.appprotect.enable .Values.controller.appprotect.logLevel }} - - -app-protect-log-level={{ .Values.controller.appprotect.logLevel }} -{{ end }} - - -enable-app-protect-dos={{ .Values.controller.appprotectdos.enable }} -{{- if .Values.controller.appprotectdos.enable }} - - -app-protect-dos-debug={{ .Values.controller.appprotectdos.debug }} - - -app-protect-dos-max-daemons={{ .Values.controller.appprotectdos.maxWorkers }} - - -app-protect-dos-max-workers={{ .Values.controller.appprotectdos.maxDaemons }} - - -app-protect-dos-memory={{ .Values.controller.appprotectdos.memory }} -{{ end }} - - -nginx-configmaps=$(POD_NAMESPACE)/{{ include "nginx-ingress.configName" . }} -{{- if .Values.controller.defaultTLS.secret }} - - -default-server-tls-secret={{ .Values.controller.defaultTLS.secret }} -{{ else if and (.Values.controller.defaultTLS.cert) (.Values.controller.defaultTLS.key) }} - - -default-server-tls-secret=$(POD_NAMESPACE)/{{ include "nginx-ingress.defaultTLSName" . }} -{{- end }} - - -ingress-class={{ .Values.controller.ingressClass }} -{{- if .Values.controller.watchNamespace }} - - -watch-namespace={{ .Values.controller.watchNamespace }} -{{- end }} -{{- if .Values.controller.watchSecretNamespace }} - - -watch-secret-namespace={{ .Values.controller.watchSecretNamespace }} -{{- end }} - - -health-status={{ .Values.controller.healthStatus }} - - -health-status-uri={{ .Values.controller.healthStatusURI }} - - -nginx-debug={{ .Values.controller.nginxDebug }} - - -v={{ .Values.controller.logLevel }} - - -nginx-status={{ .Values.controller.nginxStatus.enable }} -{{- if .Values.controller.nginxStatus.enable }} - - -nginx-status-port={{ .Values.controller.nginxStatus.port }} - - -nginx-status-allow-cidrs={{ .Values.controller.nginxStatus.allowCidrs }} -{{- end }} -{{- if .Values.controller.reportIngressStatus.enable }} - - -report-ingress-status -{{- if .Values.controller.reportIngressStatus.ingressLink }} - - -ingresslink={{ .Values.controller.reportIngressStatus.ingressLink }} -{{- else if .Values.controller.reportIngressStatus.externalService }} - - -external-service={{ .Values.controller.reportIngressStatus.externalService }} -{{- else if and (.Values.controller.service.create) (eq .Values.controller.service.type "LoadBalancer") }} - - -external-service={{ include "nginx-ingress.serviceName" . }} -{{- end }} - - -enable-leader-election={{ .Values.controller.reportIngressStatus.enableLeaderElection }} - - -leader-election-lock-name={{ include "nginx-ingress.leaderElectionName" . }} -{{- end }} -{{- if .Values.controller.wildcardTLS.secret }} - - -wildcard-tls-secret={{ .Values.controller.wildcardTLS.secret }} -{{- else if and .Values.controller.wildcardTLS.cert .Values.controller.wildcardTLS.key }} - - -wildcard-tls-secret=$(POD_NAMESPACE)/{{ include "nginx-ingress.wildcardTLSName" . }} -{{- end }} - - -enable-prometheus-metrics={{ .Values.prometheus.create }} - - -prometheus-metrics-listen-port={{ .Values.prometheus.port }} - - -prometheus-tls-secret={{ .Values.prometheus.secret }} - - -enable-custom-resources={{ .Values.controller.enableCustomResources }} - - -enable-snippets={{ .Values.controller.enableSnippets }} - - -include-year={{ .Values.controller.includeYear }} - - -disable-ipv6={{ .Values.controller.disableIPV6 }} -{{- if .Values.controller.enableCustomResources }} - - -enable-tls-passthrough={{ .Values.controller.enableTLSPassthrough }} - - -enable-preview-policies={{ .Values.controller.enablePreviewPolicies }} - - -enable-cert-manager={{ .Values.controller.enableCertManager }} - - -enable-oidc={{ .Values.controller.enableOIDC }} - - -enable-external-dns={{ .Values.controller.enableExternalDNS }} -{{- if .Values.controller.globalConfiguration.create }} - - -global-configuration=$(POD_NAMESPACE)/{{ include "nginx-ingress.name" . }} -{{- end }} -{{- end }} - - -ready-status={{ .Values.controller.readyStatus.enable }} - - -ready-status-port={{ .Values.controller.readyStatus.port }} - - -enable-latency-metrics={{ .Values.controller.enableLatencyMetrics }} -{{- if .Values.nginxServiceMesh.enable }} - - -spire-agent-address=/run/spire/sockets/agent.sock - - -enable-internal-routes={{ .Values.nginxServiceMesh.enableEgress }} -{{- end }} -{{- if .Values.controller.extraContainers }} - {{ toYaml .Values.controller.extraContainers | nindent 6 }} -{{- end }} -{{- if .Values.controller.initContainers }} - initContainers: {{ toYaml .Values.controller.initContainers | nindent 8 }} -{{- end }} -{{- if .Values.controller.strategy }} - strategy: -{{ toYaml .Values.controller.strategy | indent 4 }} -{{- end }} -{{- if .Values.controller.minReadySeconds }} - minReadySeconds: {{ .Values.controller.minReadySeconds }} -{{- end }} -{{- end }} diff --git a/deployments/helm-chart/templates/controller-ingress-class.yaml b/deployments/helm-chart/templates/controller-ingress-class.yaml deleted file mode 100644 index bc071b47cf..0000000000 --- a/deployments/helm-chart/templates/controller-ingress-class.yaml +++ /dev/null @@ -1,10 +0,0 @@ -apiVersion: networking.k8s.io/v1 -kind: IngressClass -metadata: - name: {{ .Values.controller.ingressClass }} -{{- if .Values.controller.setAsDefaultIngress }} - annotations: - ingressclass.kubernetes.io/is-default-class: "true" -{{- end }} -spec: - controller: nginx.org/ingress-controller diff --git a/deployments/helm-chart/templates/controller-service.yaml b/deployments/helm-chart/templates/controller-service.yaml deleted file mode 100644 index a1340b863b..0000000000 --- a/deployments/helm-chart/templates/controller-service.yaml +++ /dev/null @@ -1,71 +0,0 @@ -{{- if .Values.controller.service.create }} -apiVersion: v1 -kind: Service -metadata: - name: {{ include "nginx-ingress.serviceName" . }} - namespace: {{ .Release.Namespace }} - labels: - {{- include "nginx-ingress.labels" . | nindent 4 }} -{{- if .Values.controller.service.extraLabels }} -{{ toYaml .Values.controller.service.extraLabels | indent 4 }} -{{- end }} -{{- if .Values.controller.service.annotations }} - annotations: -{{ toYaml .Values.controller.service.annotations | indent 4 }} -{{- end }} -spec: -{{- if or (eq .Values.controller.service.type "LoadBalancer") (eq .Values.controller.service.type "NodePort") }} - {{- if .Values.controller.service.externalTrafficPolicy }} - externalTrafficPolicy: {{ .Values.controller.service.externalTrafficPolicy }} - {{- end }} -{{- end }} -{{- if eq .Values.controller.service.type "LoadBalancer" }} - {{- if and (semverCompare ">=1.22.0-0" .Capabilities.KubeVersion.Version) (.Values.controller.service.allocateLoadBalancerNodePorts) }} - allocateLoadBalancerNodePorts: {{ .Values.controller.service.allocateLoadBalancerNodePorts }} - {{- end }} - {{- if .Values.controller.service.loadBalancerIP }} - loadBalancerIP: {{ .Values.controller.service.loadBalancerIP }} - {{- end }} - {{- if .Values.controller.service.loadBalancerSourceRanges }} - loadBalancerSourceRanges: -{{ toYaml .Values.controller.service.loadBalancerSourceRanges | indent 4 }} - {{- end }} -{{- end }} - type: {{ .Values.controller.service.type }} - {{- if semverCompare ">=1.21.0-0" .Capabilities.KubeVersion.Version }} - {{- if .Values.controller.service.ipFamilyPolicy }} - ipFamilyPolicy: {{ .Values.controller.service.ipFamilyPolicy }} - {{- end }} - {{- if .Values.controller.service.ipFamilies }} - ipFamilies: {{ .Values.controller.service.ipFamilies }} - {{- end }} - {{- end }} - ports: -{{- if .Values.controller.service.customPorts }} -{{ toYaml .Values.controller.service.customPorts | indent 2 }} -{{ end }} -{{- if .Values.controller.service.httpPort.enable }} - - port: {{ .Values.controller.service.httpPort.port }} - targetPort: {{ .Values.controller.service.httpPort.targetPort }} - protocol: TCP - name: http - {{- if eq .Values.controller.service.type "NodePort" }} - nodePort: {{ .Values.controller.service.httpPort.nodePort }} - {{- end }} -{{- end }} -{{- if .Values.controller.service.httpsPort.enable }} - - port: {{ .Values.controller.service.httpsPort.port }} - targetPort: {{ .Values.controller.service.httpsPort.targetPort }} - protocol: TCP - name: https - {{- if eq .Values.controller.service.type "NodePort" }} - nodePort: {{ .Values.controller.service.httpsPort.nodePort }} - {{- end }} -{{- end }} - selector: - app: {{ include "nginx-ingress.appName" . }} - {{- if .Values.controller.service.externalIPs }} - externalIPs: -{{ toYaml .Values.controller.service.externalIPs | indent 4 }} - {{- end }} -{{- end }} diff --git a/deployments/helm-chart/templates/controller-serviceaccount.yaml b/deployments/helm-chart/templates/controller-serviceaccount.yaml deleted file mode 100644 index e1a3b51a06..0000000000 --- a/deployments/helm-chart/templates/controller-serviceaccount.yaml +++ /dev/null @@ -1,16 +0,0 @@ -{{- if .Values.rbac.create }} -apiVersion: v1 -kind: ServiceAccount -metadata: -{{- if .Values.controller.serviceAccount.annotations }} - annotations: {{- toYaml .Values.controller.serviceAccount.annotations | nindent 4 }} -{{- end }} - name: {{ include "nginx-ingress.serviceAccountName" . }} - namespace: {{ .Release.Namespace }} - labels: - {{- include "nginx-ingress.labels" . | nindent 4 }} -{{- if .Values.controller.serviceAccount.imagePullSecretName }} -imagePullSecrets: -- name: {{ .Values.controller.serviceAccount.imagePullSecretName }} -{{- end }} -{{- end }} diff --git a/deployments/helm-chart/templates/controller-servicemonitor.yaml b/deployments/helm-chart/templates/controller-servicemonitor.yaml deleted file mode 100644 index 3638d56e09..0000000000 --- a/deployments/helm-chart/templates/controller-servicemonitor.yaml +++ /dev/null @@ -1,15 +0,0 @@ -{{- if .Values.controller.serviceMonitor.create }} -apiVersion: monitoring.coreos.com/v1 -kind: ServiceMonitor -metadata: - name: {{ include "nginx-ingress.serviceMonitorName" . }} - namespace: {{ .Release.Namespace }} - labels: - {{- toYaml .Values.controller.serviceMonitor.labels | nindent 4 }} -spec: - selector: - matchLabels: - {{- toYaml .Values.controller.serviceMonitor.selectorMatchLabels | nindent 6 }} - endpoints: - {{- toYaml .Values.controller.serviceMonitor.endpoints | nindent 4 }} -{{- end }} diff --git a/deployments/helm-chart/templates/rbac.yaml b/deployments/helm-chart/templates/rbac.yaml deleted file mode 100644 index ad95710a86..0000000000 --- a/deployments/helm-chart/templates/rbac.yaml +++ /dev/null @@ -1,197 +0,0 @@ -{{- if .Values.rbac.create }} -kind: ClusterRole -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: {{ include "nginx-ingress.name" . }} - labels: - {{- include "nginx-ingress.labels" . | nindent 4 }} -rules: -{{- if .Values.controller.appprotect.enable }} -- apiGroups: - - appprotect.f5.com - resources: - - appolicies - - aplogconfs - - apusersigs - verbs: - - get - - watch - - list -{{- end }} -{{- if .Values.controller.appprotectdos.enable }} -- apiGroups: - - appprotectdos.f5.com - resources: - - apdospolicies - - apdoslogconfs - - dosprotectedresources - verbs: - - get - - watch - - list -{{- end }} -- apiGroups: - - "" - resources: - - services - - endpoints - verbs: - - get - - list - - watch -- apiGroups: - - "" - resources: - - secrets - verbs: - - get - - list - - watch -- apiGroups: - - "" - resources: - - configmaps - verbs: - - get - - list - - watch -{{- if .Values.controller.reportIngressStatus.enableLeaderElection }} - - update - - create -{{- end }} -- apiGroups: - - "" - resources: - - pods - verbs: - - list - - watch -- apiGroups: - - "" - resources: - - namespaces - verbs: - - get - - list - - watch -- apiGroups: - - "" - resources: - - events - verbs: - - create - - patch - - list -- apiGroups: - - coordination.k8s.io - resources: - - leases - verbs: - - get - - list - - watch - - update - - create -- apiGroups: - - networking.k8s.io - resources: - - ingresses - verbs: - - get - - list - - watch -- apiGroups: - - networking.k8s.io - resources: - - ingressclasses - verbs: - - get -{{- if .Values.controller.reportIngressStatus.enable }} -- apiGroups: - - networking.k8s.io - resources: - - ingresses/status - verbs: - - update -{{- end }} -{{- if .Values.controller.enableCustomResources }} -- apiGroups: - - k8s.nginx.org - resources: - - virtualservers - - virtualserverroutes - - globalconfigurations - - transportservers - - policies - verbs: - - list - - watch - - get -- apiGroups: - - k8s.nginx.org - resources: - - virtualservers/status - - virtualserverroutes/status - - policies/status - - transportservers/status - verbs: - - update -{{- end }} -{{- if .Values.controller.reportIngressStatus.ingressLink }} -- apiGroups: - - cis.f5.com - resources: - - ingresslinks - verbs: - - list - - watch - - get -{{- end }} -{{- if .Values.controller.enableCertManager }} -- apiGroups: - - cert-manager.io - resources: - - certificates - verbs: - - list - - watch - - get - - update - - create - - delete -{{- end }} -{{- if .Values.controller.enableExternalDNS }} -- apiGroups: - - externaldns.nginx.org - resources: - - dnsendpoints - verbs: - - list - - watch - - get - - update - - create - - delete -- apiGroups: - - externaldns.nginx.org - resources: - - dnsendpoints/status - verbs: - - update -{{- end }} ---- -kind: ClusterRoleBinding -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: {{ include "nginx-ingress.name" . }} - labels: - {{- include "nginx-ingress.labels" . | nindent 4 }} -subjects: -- kind: ServiceAccount - name: {{ include "nginx-ingress.serviceAccountName" . }} - namespace: {{ .Release.Namespace }} -roleRef: - kind: ClusterRole - name: {{ include "nginx-ingress.name" . }} - apiGroup: rbac.authorization.k8s.io -{{- end }} diff --git a/deployments/helm-chart/values-nsm.yaml b/deployments/helm-chart/values-nsm.yaml deleted file mode 100644 index 153a3aeb86..0000000000 --- a/deployments/helm-chart/values-nsm.yaml +++ /dev/null @@ -1,9 +0,0 @@ -controller: - nginxplus: true - image: - repository: nginx-plus-ingress - tag: "2.4.1" - enableLatencyMetrics: true -nginxServiceMesh: - enable: true - enableEgress: true diff --git a/deployments/helm-chart/values-plus.yaml b/deployments/helm-chart/values-plus.yaml deleted file mode 100644 index 479854940d..0000000000 --- a/deployments/helm-chart/values-plus.yaml +++ /dev/null @@ -1,5 +0,0 @@ -controller: - nginxplus: true - image: - repository: nginx-plus-ingress - tag: "2.4.1" diff --git a/deployments/helm-chart/values.schema.json b/deployments/helm-chart/values.schema.json deleted file mode 100644 index 09163df0fe..0000000000 --- a/deployments/helm-chart/values.schema.json +++ /dev/null @@ -1,1595 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2019-09/schema", - "type": "object", - "default": {}, - "title": "Root Schema", - "required": [ - "controller", - "rbac", - "prometheus", - "nginxServiceMesh" - ], - "properties": { - "controller": { - "type": "object", - "default": {}, - "title": "The Ingress Controller Helm Schema", - "required": [ - "kind", - "image" - ], - "properties": { - "name": { - "type": "string", - "default": "", - "title": "The name of the Ingress Controller", - "examples": [ - "nginx-ingress" - ] - }, - "kind": { - "type": "string", - "default": "", - "title": "The kind of the Ingress Controller", - "enum": [ - "deployment", - "daemonset" - ], - "examples": [ - "deployment", - "daemonset" - ] - }, - "nginxplus": { - "type": "boolean", - "default": false, - "title": "Deploys the Ingress Controller for NGINX Plus", - "examples": [ - false, - true - ] - }, - "nginxReloadTimeout": { - "type": "integer", - "default": 0, - "title": "Timeout in milliseconds which the Ingress Controller will wait for a successful NGINX reload after a change or at the initial start", - "examples": [ - 60000 - ] - }, - "appprotect": { - "type": "object", - "default": {}, - "title": "The App Protect WAF Schema", - "required": [ - "enable" - ], - "properties": { - "enable": { - "type": "boolean", - "default": false, - "title": "Enable the App Protect WAF module in the Ingress Controller", - "examples": [ - false, - true - ] - }, - "logLevel": { - "type": "string", - "default": "", - "title": "The logLevel for App Protect WAF", - "enum": [ - "fatal", - "error", - "warn", - "info", - "debug", - "trace" - ], - "examples": [ - "fatal", - "error", - "warn", - "info", - "debug", - "trace" - ] - } - }, - "examples": [ - { - "enable": true, - "logLevel": "fatal" - } - ] - }, - "appprotectdos": { - "type": "object", - "default": {}, - "title": "The App Protect DoS Schema", - "required": [ - "enable" - ], - "properties": { - "enable": { - "type": "boolean", - "default": false, - "title": "Enable the App Protect DoS module in the Ingress Controller", - "examples": [ - false, - true - ] - }, - "debug": { - "type": "boolean", - "default": false, - "title": "debugging for App Protect DoS", - "examples": [ - false, - true - ] - }, - "maxWorkers": { - "type": "integer", - "default": 0, - "title": "Max number of nginx processes to support", - "examples": [ - 0 - ] - }, - "maxDaemons": { - "type": "integer", - "default": 0, - "title": "Max number of ADMD instances", - "examples": [ - 0 - ] - }, - "memory": { - "type": "integer", - "default": 0, - "title": "RAM memory size to consume in MB", - "examples": [ - 0 - ] - } - }, - "examples": [ - { - "enable": true, - "debug": false, - "maxWorkers": 0, - "maxDaemons": 0, - "memory": 0 - } - ] - }, - "hostNetwork": { - "type": "boolean", - "default": false, - "title": "The hostNetwork Schema", - "examples": [ - false, - true - ] - }, - "nginxDebug": { - "type": "boolean", - "default": false, - "title": "Enables debugging for NGINX", - "examples": [ - false, - true - ] - }, - "logLevel": { - "type": "integer", - "default": 1, - "title": "The logLevel of the Ingress Controller", - "enum": [ - 0, - 1, - 2, - 3 - ], - "examples": [ - 1 - ] - }, - "customPorts": { - "type": "array", - "default": [], - "title": "The customPorts to expose on the NGINX ingress controller pod", - "items": { - "type": "object", - "$ref": "https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/v1.25.2/_definitions.json#/definitions/io.k8s.api.core.v1.ContainerPort" - }, - "examples": [ - [ - { - "name": "http", - "containerPort": 80, - "protocol": "TCP" - }, - { - "name": "https", - "containerPort": 443, - "protocol": "TCP" - } - ] - ] - }, - "image": { - "type": "object", - "default": {}, - "title": "The image Schema", - "required": [ - "repository", - "tag" - ], - "properties": { - "repository": { - "type": "string", - "default": "nginx/nginx-ingress", - "title": "The repository of the Ingress Controller", - "examples": [ - "nginx/nginx-ingress" - ] - }, - "tag": { - "type": "string", - "default": "2.3.1", - "title": "The tag of the Ingress Controller image", - "examples": [ - "2.3.1" - ] - }, - "digest": { - "type": "string", - "default": "", - "title": "The digest of the Ingress Controller image", - "examples": [ - "sha256:2710c264e8eaeb663cee63db37b75a1ac1709f63a130fb091c843a6c3a4dc572" - ] - }, - "pullPolicy": { - "type": "string", - "default": "IfNotPresent", - "title": "The pullPolicy for the Ingress Controller image", - "allOf": [ - { - "$ref": "https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/v1.25.2/_definitions.json#/definitions/io.k8s.api.core.v1.Container/properties/imagePullPolicy" - }, - { - "enum": [ - "Always", - "IfNotPresent", - "Never" - ] - } - ], - "examples": [ - "Always", - "IfNotPresent", - "Never" - ] - } - }, - "examples": [ - { - "repository": "nginx/nginx-ingress", - "tag": "2.3.1", - "pullPolicy": "IfNotPresent" - } - ] - }, - "lifecycle": { - "type": "object", - "default": {}, - "title": "The lifecycle Schema", - "$ref": "https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/v1.25.2/_definitions.json#/definitions/io.k8s.api.core.v1.Lifecycle" - }, - "customConfigMap": { - "type": "string", - "default": "", - "title": "The customConfigMap Schema", - "examples": [ - "" - ] - }, - "config": { - "type": "object", - "default": {}, - "title": "The config Schema", - "required": [], - "properties": { - "name": { - "type": "string", - "default": "", - "title": "The name Schema", - "examples": [ - "" - ] - }, - "annotations": { - "type": "object", - "default": {}, - "title": "The annotations Schema", - "$ref": "https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/v1.25.2/_definitions.json#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta/properties/annotations" - }, - "entries": { - "type": "object", - "default": {}, - "title": "The entries Schema", - "required": [], - "properties": {}, - "examples": [ - {} - ] - } - }, - "examples": [ - { - "name": "", - "annotations": {}, - "entries": {} - } - ] - }, - "defaultTLS": { - "type": "object", - "default": {}, - "title": "The defaultTLS Schema", - "required": [], - "properties": { - "cert": { - "type": "string", - "default": "", - "title": "The cert Schema", - "examples": [] - }, - "key": { - "type": "string", - "default": "", - "title": "The key Schema", - "examples": [] - }, - "secret": { - "type": "string", - "default": "", - "title": "The secret Schema", - "examples": [ - "" - ] - } - }, - "examples": [] - }, - "wildcardTLS": { - "type": "object", - "default": {}, - "title": "The wildcardTLS Schema", - "required": [], - "properties": { - "cert": { - "type": "string", - "default": "", - "title": "The cert Schema", - "examples": [ - "" - ] - }, - "key": { - "type": "string", - "default": "", - "title": "The key Schema", - "examples": [ - "" - ] - }, - "secret": { - "type": "string", - "default": "", - "title": "The secret Schema", - "examples": [ - "" - ] - } - }, - "examples": [] - }, - "nodeSelector": { - "type": "object", - "default": {}, - "title": "The nodeSelector Schema", - "$ref": "https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/v1.25.2/_definitions.json#/definitions/io.k8s.api.core.v1.NodeSelector" - }, - "terminationGracePeriodSeconds": { - "type": "integer", - "default": 30, - "title": "The terminationGracePeriodSeconds Schema", - "$ref": "https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/v1.25.2/_definitions.json#/definitions/io.k8s.api.core.v1.PodSpec/properties/terminationGracePeriodSeconds" - }, - "resources": { - "type": "object", - "default": {}, - "title": "The resources Schema", - "$ref": "https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/v1.25.2/_definitions.json#/definitions/io.k8s.api.core.v1.ResourceRequirements" - }, - "tolerations": { - "type": "array", - "default": [], - "title": "The tolerations Schema", - "items": { - "type": "object", - "$ref": "https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/v1.25.2/_definitions.json#/definitions/io.k8s.api.core.v1.Toleration" - } - }, - "affinity": { - "type": "object", - "default": {}, - "title": "The affinity Schema", - "$ref": "https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/v1.25.2/_definitions.json#/definitions/io.k8s.api.core.v1.Affinity" - }, - "topologySpreadConstraints": { - "type": "object", - "default": {}, - "title": "The topologySpreadConstraints Schema", - "$ref": "https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/v1.25.2/_definitions.json#/definitions/io.k8s.api.core.v1.TopologySpreadConstraint" - }, - "volumes": { - "type": "array", - "default": [], - "title": "The volumes Schema", - "items": { - "type": "object", - "$ref": "https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/v1.25.2/_definitions.json#/definitions/io.k8s.api.core.v1.Volume" - } - }, - "volumeMounts": { - "type": "array", - "default": [], - "title": "The volumeMounts Schema", - "items": { - "type": "object", - "$ref": "https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/v1.25.2/_definitions.json#/definitions/io.k8s.api.core.v1.VolumeMount" - } - }, - "initContainers": { - "type": "array", - "default": [], - "title": "The initContainers Schema", - "items": { - "type": "object", - "$ref": "https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/v1.25.2/_definitions.json#/definitions/io.k8s.api.core.v1.Container" - } - }, - "minReadySeconds": { - "type": "integer", - "default": 0, - "title": "The minReadySeconds Schema", - "$ref": "https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/v1.25.2/_definitions.json#/definitions/io.k8s.api.apps.v1.DeploymentSpec/properties/minReadySeconds" - }, - "strategy": { - "type": "object", - "default": {}, - "title": "The strategy Schema", - "allOf": [ - { - "$ref": "https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/v1.25.2/_definitions.json#/definitions/io.k8s.api.apps.v1.DeploymentStrategy" - }, - { - "properties": { - "type": { - "type": "string", - "enum": [ - "Recreate", - "RollingUpdate" - ] - } - } - } - ] - }, - "extraContainers": { - "type": "array", - "default": [], - "title": "The extraContainers Schema", - "items": { - "type": "object", - "$ref": "https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/v1.25.2/_definitions.json#/definitions/io.k8s.api.core.v1.Container" - } - }, - "replicaCount": { - "type": "integer", - "default": 1, - "title": "The replicaCount", - "examples": [ - 1 - ] - }, - "ingressClass": { - "type": "string", - "default": "", - "title": "The ingressClass", - "examples": [ - "nginx" - ] - }, - "setAsDefaultIngress": { - "type": "boolean", - "default": false, - "title": "The setAsDefaultIngress", - "examples": [ - false - ] - }, - "watchNamespace": { - "type": "string", - "default": "", - "title": "The watchNamespace", - "examples": [ - "" - ] - }, - "enableCustomResources": { - "type": "boolean", - "default": false, - "title": "The enableCustomResources", - "examples": [ - true - ] - }, - "enablePreviewPolicies": { - "type": "boolean", - "default": false, - "title": "The enablePreviewPolicies", - "examples": [ - false - ] - }, - "enableOIDC": { - "type": "boolean", - "default": false, - "title": "The enableOIDC", - "examples": [ - false - ] - }, - "includeYear": { - "type": "boolean", - "default": false, - "title": "The includeYear", - "examples": [ - false - ] - }, - "enableTLSPassthrough": { - "type": "boolean", - "default": false, - "title": "The enableTLSPassthrough", - "examples": [ - false - ] - }, - "enableCertManager": { - "type": "boolean", - "default": false, - "title": "The enableCertManager", - "examples": [ - false - ] - }, - "enableExternalDNS": { - "type": "boolean", - "default": false, - "title": "The enableExternalDNS", - "examples": [ - false - ] - }, - "globalConfiguration": { - "type": "object", - "default": {}, - "title": "The globalConfiguration Schema", - "required": [ - "create", - "spec" - ], - "properties": { - "create": { - "type": "boolean", - "default": false, - "title": "The create Schema", - "examples": [ - false - ] - }, - "spec": { - "type": "object", - "default": {}, - "title": "The spec Schema", - "required": [], - "properties": { - "listeners": { - "type": "array", - "default": [], - "title": "The listeners Schema", - "items": { - "type": "object", - "default": {}, - "properties": { - "port": { - "type": "integer", - "default": 0, - "title": "The port", - "examples": [ - 5353 - ] - }, - "protocol": { - "type": "string", - "default": "", - "title": "The protocol", - "examples": [ - "TCP" - ] - }, - "name": { - "type": "string", - "default": "", - "title": "The name", - "examples": [ - "dns-tcp" - ] - } - } - } - } - }, - "examples": [ - {} - ] - } - }, - "examples": [ - { - "create": false, - "spec": {} - } - ] - }, - "enableSnippets": { - "type": "boolean", - "default": false, - "title": "The enableSnippets", - "examples": [ - false - ] - }, - "healthStatus": { - "type": "boolean", - "default": false, - "title": "The healthStatus", - "examples": [ - false - ] - }, - "healthStatusURI": { - "type": "string", - "format": "uri-reference", - "default": "/nginx-health", - "title": "The healthStatusURI Schema", - "examples": [ - "/nginx-health" - ] - }, - "nginxStatus": { - "type": "object", - "default": {}, - "title": "The nginxStatus Schema", - "required": [], - "properties": { - "enable": { - "type": "boolean", - "default": false, - "title": "The enable", - "examples": [ - true - ] - }, - "port": { - "type": "integer", - "default": 8080, - "title": "The port", - "examples": [ - 8080 - ] - }, - "allowCidrs": { - "type": "string", - "default": "127.0.0.1", - "title": "The allowCidrs", - "examples": [ - "127.0.0.1" - ] - } - }, - "examples": [ - { - "enable": true, - "port": 8080, - "allowCidrs": "127.0.0.1" - } - ] - }, - "service": { - "type": "object", - "default": {}, - "title": "The service Schema", - "required": [], - "properties": { - "create": { - "type": "boolean", - "default": false, - "title": "The create", - "examples": [ - true - ] - }, - "type": { - "type": "string", - "default": "", - "title": "The type", - "$ref": "https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/v1.25.2/_definitions.json#/definitions/io.k8s.api.core.v1.ServiceSpec/properties/type" - }, - "externalTrafficPolicy": { - "type": "string", - "default": "", - "title": "The externalTrafficPolicy", - "$ref": "https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/v1.25.2/_definitions.json#/definitions/io.k8s.api.core.v1.ServiceSpec/properties/externalTrafficPolicy" - }, - "annotations": { - "type": "object", - "default": {}, - "title": "The annotations", - "$ref": "https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/v1.25.2/_definitions.json#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta/properties/annotations" - }, - "extraLabels": { - "type": "object", - "default": {}, - "title": "The extraLabels", - "required": [], - "properties": {}, - "examples": [ - {} - ] - }, - "loadBalancerIP": { - "type": "string", - "default": "", - "title": "The loadBalancerIP", - "$ref": "https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/v1.25.2/_definitions.json#/definitions/io.k8s.api.core.v1.ServiceSpec/properties/loadBalancerIP" - }, - "externalIPs": { - "type": "array", - "default": [], - "title": "The externalIPs", - "$ref": "https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/v1.25.2/_definitions.json#/definitions/io.k8s.api.core.v1.ServiceSpec/properties/externalIPs" - }, - "loadBalancerSourceRanges": { - "type": "array", - "default": [], - "title": "The loadBalancerSourceRanges", - "items": {}, - "examples": [ - [] - ] - }, - "name": { - "type": "string", - "default": "", - "title": "The name", - "examples": [ - "" - ] - }, - "allocateLoadBalancerNodePorts": { - "type": "boolean", - "default": false, - "title": "The allocateLoadBalancerNodePorts Schema", - "ref": "https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/v1.25.2/_definitions.json#/definitions/io.k8s.api.core.v1.ServiceSpec/properties/allocateLoadBalancerNodePorts" - }, - "ipFamilyPolicy": { - "type": "string", - "default": "", - "title": "The ipFamilyPolicy Schema", - "$ref": "https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/v1.25.2/_definitions.json#/definitions/io.k8s.api.core.v1.ServiceSpec/properties/ipFamilyPolicy", - "examples": [ - "" - ] - }, - "ipFamilies": { - "type": "array", - "default": [], - "title": "The ipFamilies Schema", - "ref": "https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/v1.25.2/_definitions.json#/definitions/io.k8s.api.core.v1.ServiceSpec/properties/ipFamilies" - }, - "httpPort": { - "type": "object", - "default": {}, - "title": "The httpPort", - "required": [], - "properties": { - "enable": { - "type": "boolean", - "default": false, - "title": "The enable", - "examples": [ - true - ] - }, - "port": { - "type": "integer", - "default": 0, - "title": "The port", - "examples": [ - 80 - ] - }, - "nodePort": { - "type": "integer", - "default": 0, - "title": "The nodePort", - "examples": [ - 443 - ] - }, - "targetPort": { - "type": "integer", - "default": 0, - "title": "The targetPort", - "examples": [ - 80 - ] - } - }, - "examples": [ - { - "enable": true, - "port": 80, - "nodePort": "", - "targetPort": 80 - } - ] - }, - "httpsPort": { - "type": "object", - "default": {}, - "title": "The httpsPort", - "required": [], - "properties": { - "enable": { - "type": "boolean", - "default": false, - "title": "The enable", - "examples": [ - true - ] - }, - "port": { - "type": "integer", - "default": 0, - "title": "The port", - "examples": [ - 443 - ] - }, - "nodePort": { - "type": "integer", - "default": 0, - "title": "The nodePort", - "examples": [ - 443 - ] - }, - "targetPort": { - "type": "integer", - "default": 0, - "title": "The targetPort", - "examples": [ - 443 - ] - } - }, - "examples": [ - { - "enable": true, - "port": 443, - "nodePort": "", - "targetPort": 443 - } - ] - }, - "customPorts": { - "type": "array", - "default": [], - "title": "The customPorts", - "items": { - "type": "object", - "ref": "https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/v1.25.2/_definitions.json#/definitions/io.k8s.api.core.v1.ServicePort" - } - } - }, - "examples": [ - { - "create": true, - "type": "LoadBalancer", - "externalTrafficPolicy": "Local", - "annotations": {}, - "extraLabels": {}, - "loadBalancerIP": "", - "externalIPs": [], - "loadBalancerSourceRanges": [], - "name": "", - "allocateLoadBalancerNodePorts": false, - "ipFamilyPolicy": "", - "ipFamilies": [], - "httpPort": { - "enable": true, - "port": 80, - "targetPort": 80 - }, - "httpsPort": { - "enable": true, - "port": 443, - "targetPort": 443 - }, - "customPorts": [] - } - ] - }, - "serviceAccount": { - "type": "object", - "default": {}, - "title": "The serviceAccount Schema", - "required": [], - "properties": { - "name": { - "type": "string", - "default": "", - "title": "The name Schema", - "examples": [ - "" - ] - }, - "imagePullSecretName": { - "type": "string", - "default": "", - "title": "The imagePullSecretName", - "examples": [ - "" - ] - } - }, - "examples": [ - { - "name": "", - "imagePullSecretName": "" - } - ] - }, - "serviceMonitor": { - "type": "object", - "default": {}, - "title": "The serviceMonitor Schema", - "required": [], - "properties": { - "create": { - "type": "boolean", - "default": false, - "title": "The create", - "examples": [ - false - ] - }, - "name": { - "type": "string", - "default": "", - "title": "The name", - "examples": [ - "" - ] - }, - "labels": { - "type": "object", - "default": {}, - "title": "The labels Schema", - "$ref": "https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/v1.25.2/_definitions.json#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta/properties/labels" - }, - "selectorMatchLabels": { - "type": "object", - "default": {}, - "title": "The selectorMatchLabels Schema", - "$ref": "https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/v1.25.2/_definitions.json#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelector/properties/matchLabels" - }, - "endpoints": { - "type": "array", - "default": [], - "title": "The endpoints", - "required": [], - "items": {} - } - }, - "examples": [ - { - "create": false, - "name": "", - "labels": {}, - "selectorMatchLabels": {}, - "endpoints": [] - } - ] - }, - "reportIngressStatus": { - "type": "object", - "default": {}, - "title": "The reportIngressStatus Schema", - "required": [ - "enable" - ], - "properties": { - "enable": { - "type": "boolean", - "default": false, - "title": "The enable", - "examples": [ - true - ] - }, - "externalService": { - "type": "string", - "default": "", - "title": "The externalService", - "examples": [ - "" - ] - }, - "ingressLink": { - "type": "string", - "default": "", - "title": "The ingressLink", - "examples": [ - "" - ] - }, - "enableLeaderElection": { - "type": "boolean", - "default": false, - "title": "The enableLeaderElection", - "examples": [ - true - ] - }, - "leaderElectionLockName": { - "type": "string", - "default": "", - "title": "The leaderElectionLockName", - "examples": [ - "" - ] - }, - "annotations": { - "type": "object", - "default": {}, - "title": "The annotations Schema", - "$ref": "https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/v1.25.2/_definitions.json#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta/properties/annotations" - } - }, - "examples": [ - { - "enable": true, - "externalService": "", - "ingressLink": "", - "enableLeaderElection": true, - "leaderElectionLockName": "", - "annotations": {} - } - ] - }, - "pod": { - "type": "object", - "default": {}, - "title": "The pod Schema", - "required": [], - "properties": { - "annotations": { - "type": "object", - "default": {}, - "title": "The annotations Schema", - "$ref": "https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/v1.25.2/_definitions.json#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta/properties/annotations" - }, - "extraLabels": { - "type": "object", - "default": {}, - "title": "The extraLabels Schema", - "$ref": "https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/v1.25.2/_definitions.json#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta/properties/labels" - } - }, - "examples": [ - { - "annotations": {}, - "extraLabels": {} - } - ] - }, - "priorityClassName": { - "type": "null", - "default": null, - "title": "The priorityClassName", - "examples": [ - null - ] - }, - "readyStatus": { - "type": "object", - "default": {}, - "title": "The readyStatus", - "required": [], - "properties": { - "enable": { - "type": "boolean", - "default": false, - "title": "The enable", - "examples": [ - true - ] - }, - "port": { - "type": "integer", - "default": 0, - "title": "The port", - "examples": [ - 8081 - ] - }, - "initialDelaySeconds": { - "type": "integer", - "default": 0, - "$ref": "https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/v1.25.2/_definitions.json#/definitions/io.k8s.api.core.v1.Probe/properties/initialDelaySeconds" - } - }, - "examples": [ - { - "enable": true, - "port": 8081, - "initialDelaySeconds": 0 - } - ] - }, - "enableLatencyMetrics": { - "type": "boolean", - "default": false, - "title": "The enableLatencyMetrics", - "examples": [ - false - ] - }, - "disableIPV6": { - "type": "boolean", - "default": false, - "title": "The disableIPV6", - "examples": [ - false - ] - } - }, - "examples": [ - { - "name": "", - "kind": "deployment", - "nginxplus": false, - "nginxReloadTimeout": 60000, - "appprotect": { - "enable": false, - "logLevel": "fatal" - }, - "appprotectdos": { - "enable": false, - "debug": false, - "maxWorkers": 0, - "maxDaemons": 0, - "memory": 0 - }, - "hostNetwork": false, - "nginxDebug": false, - "logLevel": 1, - "customPorts": [], - "image": { - "repository": "nginx/nginx-ingress", - "tag": "2.3.1", - "digest": "", - "pullPolicy": "IfNotPresent" - }, - "lifecycle": {}, - "customConfigMap": "", - "config": { - "name": "", - "annotations": {}, - "entries": {} - }, - "defaultTLS": { - "cert": "", - "key": "", - "secret": "" - }, - "wildcardTLS": { - "cert": "", - "key": "", - "secret": "" - }, - "nodeSelector": {}, - "terminationGracePeriodSeconds": 30, - "resources": { - "requests": { - "cpu": "100m", - "memory": "128Mi" - } - }, - "tolerations": [], - "affinity": {}, - "topologySpreadConstraints": {}, - "volumes": [], - "volumeMounts": [], - "initContainers": [], - "minReadySeconds": 0, - "strategy": {}, - "extraContainers": [], - "replicaCount": 1, - "ingressClass": "nginx", - "setAsDefaultIngress": false, - "watchNamespace": "", - "enableCustomResources": true, - "enablePreviewPolicies": false, - "enableOIDC": false, - "includeYear": false, - "enableTLSPassthrough": false, - "enableCertManager": false, - "enableExternalDNS": false, - "globalConfiguration": { - "create": false, - "spec": {} - }, - "enableSnippets": false, - "healthStatus": false, - "healthStatusURI": "/nginx-health", - "nginxStatus": { - "enable": true, - "port": 8080, - "allowCidrs": "127.0.0.1" - }, - "service": { - "create": true, - "type": "LoadBalancer", - "externalTrafficPolicy": "Local", - "annotations": {}, - "extraLabels": {}, - "loadBalancerIP": "", - "externalIPs": [], - "loadBalancerSourceRanges": [], - "name": "", - "allocateLoadBalancerNodePorts": false, - "ipFamilyPolicy": "", - "ipFamilies": [], - "httpPort": { - "enable": true, - "port": 80, - "targetPort": 80 - }, - "httpsPort": { - "enable": true, - "port": 443, - "targetPort": 443 - }, - "customPorts": [] - }, - "serviceAccount": { - "name": "", - "imagePullSecretName": "" - }, - "serviceMonitor": { - "create": false, - "name": "", - "labels": {}, - "selectorMatchLabels": {}, - "endpoints": {} - }, - "reportIngressStatus": { - "enable": true, - "externalService": "", - "ingressLink": "", - "enableLeaderElection": true, - "leaderElectionLockName": "", - "annotations": {} - }, - "pod": { - "annotations": {}, - "extraLabels": {} - }, - "priorityClassName": null, - "readyStatus": { - "enable": true, - "port": 8081, - "initialDelaySeconds": 0 - }, - "enableLatencyMetrics": false, - "disableIPV6": false - } - ] - }, - "rbac": { - "type": "object", - "default": {}, - "title": "The rbac Schema", - "required": [ - "create" - ], - "properties": { - "create": { - "type": "boolean", - "default": false, - "title": "The create Schema", - "examples": [ - true - ] - } - }, - "examples": [ - { - "create": true - } - ] - }, - "prometheus": { - "type": "object", - "default": {}, - "title": "The prometheus Schema", - "required": [ - "create" - ], - "properties": { - "create": { - "type": "boolean", - "default": false, - "title": "The create", - "examples": [ - true - ] - }, - "port": { - "type": "integer", - "default": 9113, - "title": "The port", - "examples": [ - 9113 - ] - }, - "secret": { - "type": "string", - "default": "", - "title": "The secret", - "examples": [ - "" - ] - }, - "scheme": { - "type": "string", - "default": "http", - "title": "The scheme", - "examples": [ - "http" - ] - } - }, - "examples": [ - { - "create": true, - "port": 9113, - "secret": "", - "scheme": "http" - } - ] - }, - "nginxServiceMesh": { - "type": "object", - "default": {}, - "title": "The nginxServiceMesh Schema", - "required": [ - "enable" - ], - "properties": { - "enable": { - "type": "boolean", - "default": false, - "title": "The enable", - "examples": [ - false - ] - }, - "enableEgress": { - "type": "boolean", - "default": false, - "title": "The enableEgress", - "examples": [ - false - ] - } - }, - "examples": [ - { - "enable": false, - "enableEgress": false - } - ] - } - }, - "examples": [ - { - "controller": { - "name": "", - "kind": "deployment", - "nginxplus": false, - "nginxReloadTimeout": 60000, - "appprotect": { - "enable": false, - "logLevel": "fatal" - }, - "appprotectdos": { - "enable": false, - "debug": false, - "maxWorkers": 0, - "maxDaemons": 0, - "memory": 0 - }, - "hostNetwork": false, - "nginxDebug": false, - "logLevel": 1, - "customPorts": [], - "image": { - "repository": "nginx/nginx-ingress", - "tag": "2.3.1", - "digest": "", - "pullPolicy": "IfNotPresent" - }, - "lifecycle": {}, - "customConfigMap": "", - "config": { - "name": "", - "annotations": {}, - "entries": {} - }, - "defaultTLS": { - "cert": "", - "key": "", - "secret": "" - }, - "wildcardTLS": { - "cert": "", - "key": "", - "secret": "" - }, - "nodeSelector": {}, - "terminationGracePeriodSeconds": 30, - "resources": { - "requests": { - "cpu": "100m", - "memory": "128Mi" - } - }, - "tolerations": [], - "affinity": {}, - "topologySpreadConstraints": {}, - "volumes": [], - "volumeMounts": [], - "initContainers": [], - "minReadySeconds": 0, - "strategy": {}, - "extraContainers": [], - "replicaCount": 1, - "ingressClass": "nginx", - "setAsDefaultIngress": false, - "watchNamespace": "", - "enableCustomResources": true, - "enablePreviewPolicies": false, - "enableOIDC": false, - "includeYear": false, - "enableTLSPassthrough": false, - "enableCertManager": false, - "enableExternalDNS": false, - "globalConfiguration": { - "create": false, - "spec": {} - }, - "enableSnippets": false, - "healthStatus": false, - "healthStatusURI": "/nginx-health", - "nginxStatus": { - "enable": true, - "port": 8080, - "allowCidrs": "127.0.0.1" - }, - "service": { - "create": true, - "type": "LoadBalancer", - "externalTrafficPolicy": "Local", - "annotations": {}, - "extraLabels": {}, - "loadBalancerIP": "", - "externalIPs": [], - "loadBalancerSourceRanges": [], - "name": "", - "allocateLoadBalancerNodePorts": false, - "ipFamilyPolicy": "", - "ipFamilies": [], - "httpPort": { - "enable": true, - "port": 80, - "nodePort": "", - "targetPort": 80 - }, - "httpsPort": { - "enable": true, - "port": 443, - "nodePort": "", - "targetPort": 443 - }, - "customPorts": [] - }, - "serviceAccount": { - "name": "", - "imagePullSecretName": "" - }, - "serviceMonitor": { - "create": false, - "name": "", - "labels": {}, - "selectorMatchLabels": {}, - "endpoints": {} - }, - "reportIngressStatus": { - "enable": true, - "externalService": "", - "ingressLink": "", - "enableLeaderElection": true, - "leaderElectionLockName": "", - "annotations": {} - }, - "pod": { - "annotations": {}, - "extraLabels": {} - }, - "priorityClassName": null, - "readyStatus": { - "enable": true, - "port": 8081, - "initialDelaySeconds": 0 - }, - "enableLatencyMetrics": false, - "disableIPV6": false - }, - "rbac": { - "create": true - }, - "prometheus": { - "create": true, - "port": 9113, - "secret": "", - "scheme": "http" - }, - "nginxServiceMesh": { - "enable": false, - "enableEgress": false - } - } - ] -} diff --git a/deployments/helm-chart/values.yaml b/deployments/helm-chart/values.yaml deleted file mode 100644 index 85788bd8b8..0000000000 --- a/deployments/helm-chart/values.yaml +++ /dev/null @@ -1,425 +0,0 @@ -controller: - ## The name of the Ingress Controller daemonset or deployment. - ## Autogenerated if not set or set to "". - # name: nginx-ingress - - ## The kind of the Ingress Controller installation - deployment or daemonset. - kind: deployment - - ## Annotations for deployments and daemonsets - annotations: {} - - ## Deploys the Ingress Controller for NGINX Plus. - nginxplus: false - - # Timeout in milliseconds which the Ingress Controller will wait for a successful NGINX reload after a change or at the initial start. - nginxReloadTimeout: 60000 - - ## Support for App Protect WAF - appprotect: - ## Enable the App Protect WAF module in the Ingress Controller. - enable: false - ## Sets log level for App Protect WAF. Allowed values: fatal, error, warn, info, debug, trace - # logLevel: fatal - - ## Support for App Protect DoS - appprotectdos: - ## Enable the App Protect DoS module in the Ingress Controller. - enable: false - ## Enable debugging for App Protect DoS. - debug: false - ## Max number of nginx processes to support. - maxWorkers: 0 - ## Max number of ADMD instances. - maxDaemons: 0 - ## RAM memory size to consume in MB. - memory: 0 - - ## Enables the Ingress Controller pods to use the host's network namespace. - hostNetwork: false - - ## DNS policy for the Ingress Controller pods - dnsPolicy: ClusterFirst - - ## Enables debugging for NGINX. Uses the nginx-debug binary. Requires error-log-level: debug in the ConfigMap via `controller.config.entries`. - nginxDebug: false - - ## The log level of the Ingress Controller. - logLevel: 1 - - ## A list of custom ports to expose on the NGINX ingress controller pod. Follows the conventional Kubernetes yaml syntax for container ports. - customPorts: [] - - image: - ## The image repository of the Ingress Controller. - repository: nginx/nginx-ingress - - ## The tag of the Ingress Controller image. - tag: "2.4.1" - - ## The digest of the Ingress Controller image. - ## If digest is specified it has precedence over tag and will be used instead - # digest: "sha256:CHANGEME" - - ## The pull policy for the Ingress Controller image. - pullPolicy: IfNotPresent - - ## The lifecycle of the Ingress Controller pods. - lifecycle: {} - - ## The custom ConfigMap to use instead of the one provided by default - customConfigMap: "" - - config: - ## The name of the ConfigMap used by the Ingress Controller. - ## Autogenerated if not set or set to "". - # name: nginx-config - - ## The annotations of the Ingress Controller configmap. - annotations: {} - - ## The entries of the ConfigMap for customizing NGINX configuration. - entries: {} - - ## It is recommended to use your own TLS certificates and keys - defaultTLS: - ## The base64-encoded TLS certificate for the default HTTPS server. By default, a pre-generated self-signed certificate is used. - ## Note: It is recommended that you specify your own certificate. Alternatively, omitting the default server secret completely will configure NGINX to reject TLS connections to the default server. - cert: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUN2akNDQWFZQ0NRREFPRjl0THNhWFhEQU5CZ2txaGtpRzl3MEJBUXNGQURBaE1SOHdIUVlEVlFRRERCWk8KUjBsT1dFbHVaM0psYzNORGIyNTBjbTlzYkdWeU1CNFhEVEU0TURreE1qRTRNRE16TlZvWERUSXpNRGt4TVRFNApNRE16TlZvd0lURWZNQjBHQTFVRUF3d1dUa2RKVGxoSmJtZHlaWE56UTI5dWRISnZiR3hsY2pDQ0FTSXdEUVlKCktvWklodmNOQVFFQkJRQURnZ0VQQURDQ0FRb0NnZ0VCQUwvN2hIUEtFWGRMdjNyaUM3QlBrMTNpWkt5eTlyQ08KR2xZUXYyK2EzUDF0azIrS3YwVGF5aGRCbDRrcnNUcTZzZm8vWUk1Y2Vhbkw4WGM3U1pyQkVRYm9EN2REbWs1Qgo4eDZLS2xHWU5IWlg0Rm5UZ0VPaStlM2ptTFFxRlBSY1kzVnNPazFFeUZBL0JnWlJVbkNHZUtGeERSN0tQdGhyCmtqSXVuektURXUyaDU4Tlp0S21ScUJHdDEwcTNRYzhZT3ExM2FnbmovUWRjc0ZYYTJnMjB1K1lYZDdoZ3krZksKWk4vVUkxQUQ0YzZyM1lma1ZWUmVHd1lxQVp1WXN2V0RKbW1GNWRwdEMzN011cDBPRUxVTExSakZJOTZXNXIwSAo1TmdPc25NWFJNV1hYVlpiNWRxT3R0SmRtS3FhZ25TZ1JQQVpQN2MwQjFQU2FqYzZjNGZRVXpNQ0F3RUFBVEFOCkJna3Foa2lHOXcwQkFRc0ZBQU9DQVFFQWpLb2tRdGRPcEsrTzhibWVPc3lySmdJSXJycVFVY2ZOUitjb0hZVUoKdGhrYnhITFMzR3VBTWI5dm15VExPY2xxeC9aYzJPblEwMEJCLzlTb0swcitFZ1U2UlVrRWtWcitTTFA3NTdUWgozZWI4dmdPdEduMS9ienM3bzNBaS9kclkrcUI5Q2k1S3lPc3FHTG1US2xFaUtOYkcyR1ZyTWxjS0ZYQU80YTY3Cklnc1hzYktNbTQwV1U3cG9mcGltU1ZmaXFSdkV5YmN3N0NYODF6cFErUyt1eHRYK2VBZ3V0NHh3VlI5d2IyVXYKelhuZk9HbWhWNThDd1dIQnNKa0kxNXhaa2VUWXdSN0diaEFMSkZUUkk3dkhvQXprTWIzbjAxQjQyWjNrN3RXNQpJUDFmTlpIOFUvOWxiUHNoT21FRFZkdjF5ZytVRVJxbStGSis2R0oxeFJGcGZnPT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo= - - ## The base64-encoded TLS key for the default HTTPS server. By default, a pre-generated key is used. - ## Note: It is recommended that you specify your own key. Alternatively, omitting the default server secret completely will configure NGINX to reject TLS connections to the default server. - key: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcEFJQkFBS0NBUUVBdi91RWM4b1JkMHUvZXVJTHNFK1RYZUprckxMMnNJNGFWaEMvYjVyYy9XMlRiNHEvClJOcktGMEdYaVN1eE9ycXgrajlnamx4NXFjdnhkenRKbXNFUkJ1Z1B0ME9hVGtIekhvb3FVWmcwZGxmZ1dkT0EKUTZMNTdlT1l0Q29VOUZ4amRXdzZUVVRJVUQ4R0JsRlNjSVo0b1hFTkhzbysyR3VTTWk2Zk1wTVM3YUhudzFtMApxWkdvRWEzWFNyZEJ6eGc2clhkcUNlUDlCMXl3VmRyYURiUzc1aGQzdUdETDU4cGszOVFqVUFQaHpxdmRoK1JWClZGNGJCaW9CbTVpeTlZTW1hWVhsMm0wTGZzeTZuUTRRdFFzdEdNVWozcGJtdlFmazJBNnljeGRFeFpkZFZsdmwKMm82MjBsMllxcHFDZEtCRThCay90elFIVTlKcU56cHpoOUJUTXdJREFRQUJBb0lCQVFDZklHbXowOHhRVmorNwpLZnZJUXQwQ0YzR2MxNld6eDhVNml4MHg4Mm15d1kxUUNlL3BzWE9LZlRxT1h1SENyUlp5TnUvZ2IvUUQ4bUFOCmxOMjRZTWl0TWRJODg5TEZoTkp3QU5OODJDeTczckM5bzVvUDlkazAvYzRIbjAzSkVYNzZ5QjgzQm9rR1FvYksKMjhMNk0rdHUzUmFqNjd6Vmc2d2szaEhrU0pXSzBwV1YrSjdrUkRWYmhDYUZhNk5nMUZNRWxhTlozVDhhUUtyQgpDUDNDeEFTdjYxWTk5TEI4KzNXWVFIK3NYaTVGM01pYVNBZ1BkQUk3WEh1dXFET1lvMU5PL0JoSGt1aVg2QnRtCnorNTZud2pZMy8yUytSRmNBc3JMTnIwMDJZZi9oY0IraVlDNzVWYmcydVd6WTY3TWdOTGQ5VW9RU3BDRkYrVm4KM0cyUnhybnhBb0dCQU40U3M0ZVlPU2huMVpQQjdhTUZsY0k2RHR2S2ErTGZTTXFyY2pOZjJlSEpZNnhubmxKdgpGenpGL2RiVWVTbWxSekR0WkdlcXZXaHFISy9iTjIyeWJhOU1WMDlRQ0JFTk5jNmtWajJTVHpUWkJVbEx4QzYrCk93Z0wyZHhKendWelU0VC84ajdHalRUN05BZVpFS2FvRHFyRG5BYWkyaW5oZU1JVWZHRXFGKzJyQW9HQkFOMVAKK0tZL0lsS3RWRzRKSklQNzBjUis3RmpyeXJpY05iWCtQVzUvOXFHaWxnY2grZ3l4b25BWlBpd2NpeDN3QVpGdwpaZC96ZFB2aTBkWEppc1BSZjRMazg5b2pCUmpiRmRmc2l5UmJYbyt3TFU4NUhRU2NGMnN5aUFPaTVBRHdVU0FkCm45YWFweUNweEFkREtERHdObit3ZFhtaTZ0OHRpSFRkK3RoVDhkaVpBb0dCQUt6Wis1bG9OOTBtYlF4VVh5YUwKMjFSUm9tMGJjcndsTmVCaWNFSmlzaEhYa2xpSVVxZ3hSZklNM2hhUVRUcklKZENFaHFsV01aV0xPb2I2NTNyZgo3aFlMSXM1ZUtka3o0aFRVdnpldm9TMHVXcm9CV2xOVHlGanIrSWhKZnZUc0hpOGdsU3FkbXgySkJhZUFVWUNXCndNdlQ4NmNLclNyNkQrZG8wS05FZzFsL0FvR0FlMkFVdHVFbFNqLzBmRzgrV3hHc1RFV1JqclRNUzRSUjhRWXQKeXdjdFA4aDZxTGxKUTRCWGxQU05rMXZLTmtOUkxIb2pZT2pCQTViYjhibXNVU1BlV09NNENoaFJ4QnlHbmR2eAphYkJDRkFwY0IvbEg4d1R0alVZYlN5T294ZGt5OEp0ek90ajJhS0FiZHd6NlArWDZDODhjZmxYVFo5MWpYL3RMCjF3TmRKS2tDZ1lCbyt0UzB5TzJ2SWFmK2UwSkN5TGhzVDQ5cTN3Zis2QWVqWGx2WDJ1VnRYejN5QTZnbXo5aCsKcDNlK2JMRUxwb3B0WFhNdUFRR0xhUkcrYlNNcjR5dERYbE5ZSndUeThXczNKY3dlSTdqZVp2b0ZpbmNvVlVIMwphdmxoTUVCRGYxSjltSDB5cDBwWUNaS2ROdHNvZEZtQktzVEtQMjJhTmtsVVhCS3gyZzR6cFE9PQotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo= - - ## The secret with a TLS certificate and key for the default HTTPS server. - ## The value must follow the following format: `/`. - ## Used as an alternative to specifying a certificate and key using `controller.defaultTLS.cert` and `controller.defaultTLS.key` parameters. - ## Note: Alternatively, omitting the default server secret completely will configure NGINX to reject TLS connections to the default server. - ## Format: / - secret: "" - - wildcardTLS: - ## The base64-encoded TLS certificate for every Ingress/VirtualServer host that has TLS enabled but no secret specified. - ## If the parameter is not set, for such Ingress/VirtualServer hosts NGINX will break any attempt to establish a TLS connection. - cert: "" - - ## The base64-encoded TLS key for every Ingress/VirtualServer host that has TLS enabled but no secret specified. - ## If the parameter is not set, for such Ingress/VirtualServer hosts NGINX will break any attempt to establish a TLS connection. - key: "" - - ## The secret with a TLS certificate and key for every Ingress/VirtualServer host that has TLS enabled but no secret specified. - ## The value must follow the following format: `/`. - ## Used as an alternative to specifying a certificate and key using `controller.wildcardTLS.cert` and `controller.wildcardTLS.key` parameters. - ## Format: / - secret: "" - - ## The node selector for pod assignment for the Ingress Controller pods. - # nodeSelector: {} - - ## The termination grace period of the Ingress Controller pod. - terminationGracePeriodSeconds: 30 - - ## The resources of the Ingress Controller pods. - resources: - requests: - cpu: 100m - memory: 128Mi - # limits: - # cpu: 1 - # memory: 1Gi - - - ## The tolerations of the Ingress Controller pods. - tolerations: [] - - ## The affinity of the Ingress Controller pods. - affinity: {} - - ## The topology spread constraints of the Ingress controller pods. - # topologySpreadConstraints: {} - - ## The volumes of the Ingress Controller pods. - volumes: [] - # - name: extra-conf - # configMap: - # name: extra-conf - - ## The volumeMounts of the Ingress Controller pods. - volumeMounts: [] - # - name: extra-conf - # mountPath: /etc/nginx/conf.d/extra.conf - # subPath: extra.conf - - ## InitContainers for the Ingress Controller pods. - initContainers: [] - # - name: init-container - # image: busybox:1.34 - # command: ['sh', '-c', 'echo this is initial setup!'] - - ## The minimum number of seconds for which a newly created Pod should be ready without any of its containers crashing, for it to be considered available. - minReadySeconds: 0 - - ## Strategy used to replace old Pods by new ones. .spec.strategy.type can be "Recreate" or "RollingUpdate". "RollingUpdate" is the default value. - strategy: {} - - ## Extra containers for the Ingress Controller pods. - extraContainers: [] - # - name: container - # image: busybox:1.34 - # command: ['sh', '-c', 'echo this is a sidecar!'] - - ## The number of replicas of the Ingress Controller deployment. - replicaCount: 1 - - ## A class of the Ingress Controller. - - ## IngressClass resource with the name equal to the class must be deployed. Otherwise, - ## the Ingress Controller will fail to start. - ## The Ingress Controller only processes resources that belong to its class - i.e. have the "ingressClassName" field resource equal to the class. - - ## The Ingress Controller processes all the resources that do not have the "ingressClassName" field for all versions of kubernetes. - ingressClass: nginx - - ## New Ingresses without an ingressClassName field specified will be assigned the class specified in `controller.ingressClass`. - setAsDefaultIngress: false - - ## Comma separated list of namespaces to watch for Ingress resources. By default the Ingress Controller watches all namespaces. - watchNamespace: "" - - ## Comma separated list of namespaces to watch for Secret resources. By default the Ingress Controller watches all namespaces. - watchSecretNamespace: "" - - ## Enable the custom resources. - enableCustomResources: true - - ## Enable preview policies. This parameter is deprecated. To enable OIDC Policies please use controller.enableOIDC instead. - enablePreviewPolicies: false - - ## Enable OIDC policies. - enableOIDC: false - - ## Include year in log header. This parameter will be removed in release 2.7 and the year will be included by default. - includeYear: false - - ## Enable TLS Passthrough on port 443. Requires controller.enableCustomResources. - enableTLSPassthrough: false - - ## Enable cert manager for Virtual Server resources. Requires controller.enableCustomResources. - enableCertManager: false - - ## Enable external DNS for Virtual Server resources. Requires controller.enableCustomResources. - enableExternalDNS: false - - globalConfiguration: - ## Creates the GlobalConfiguration custom resource. Requires controller.enableCustomResources. - create: false - - ## The spec of the GlobalConfiguration for defining the global configuration parameters of the Ingress Controller. - spec: {} - # listeners: - # - name: dns-udp - # port: 5353 - # protocol: UDP - # - name: dns-tcp - # port: 5353 - # protocol: TCP - - ## Enable custom NGINX configuration snippets in Ingress, VirtualServer, VirtualServerRoute and TransportServer resources. - enableSnippets: false - - ## Add a location based on the value of health-status-uri to the default server. The location responds with the 200 status code for any request. - ## Useful for external health-checking of the Ingress Controller. - healthStatus: false - - ## Sets the URI of health status location in the default server. Requires controller.healthStatus. - healthStatusURI: "/nginx-health" - - nginxStatus: - ## Enable the NGINX stub_status, or the NGINX Plus API. - enable: true - - ## Set the port where the NGINX stub_status or the NGINX Plus API is exposed. - port: 8080 - - ## Add IPv4 IP/CIDR blocks to the allow list for NGINX stub_status or the NGINX Plus API. Separate multiple IP/CIDR by commas. - allowCidrs: "127.0.0.1" - - service: - ## Creates a service to expose the Ingress Controller pods. - create: true - - ## The type of service to create for the Ingress Controller. - type: LoadBalancer - - ## The externalTrafficPolicy of the service. The value Local preserves the client source IP. - externalTrafficPolicy: Local - - ## The annotations of the Ingress Controller service. - annotations: {} - - ## The extra labels of the service. - extraLabels: {} - - ## The static IP address for the load balancer. Requires controller.service.type set to LoadBalancer. The cloud provider must support this feature. - loadBalancerIP: "" - - ## The list of external IPs for the Ingress Controller service. - externalIPs: [] - - ## The IP ranges (CIDR) that are allowed to access the load balancer. Requires controller.service.type set to LoadBalancer. The cloud provider must support this feature. - loadBalancerSourceRanges: [] - - ## The name of the service - ## Autogenerated if not set or set to "". - # name: nginx-ingress - - ## Whether to automatically allocate NodePorts (only for LoadBalancers). - # allocateLoadBalancerNodePorts: false - - ## Dual stack preference. - ## Valid values: SingleStack, PreferDualStack, RequireDualStack - # ipFamilyPolicy: SingleStack - - ## List of IP families assigned to this service. - ## Valid values: IPv4, IPv6 - # ipFamilies: - # - IPv6 - - httpPort: - ## Enables the HTTP port for the Ingress Controller service. - enable: true - - ## The HTTP port of the Ingress Controller service. - port: 80 - - ## The custom NodePort for the HTTP port. Requires controller.service.type set to NodePort. - # nodePort: 80 - - ## The HTTP port on the POD where the Ingress Controller service is running. - targetPort: 80 - - httpsPort: - ## Enables the HTTPS port for the Ingress Controller service. - enable: true - - ## The HTTPS port of the Ingress Controller service. - port: 443 - - ## The custom NodePort for the HTTPS port. Requires controller.service.type set to NodePort. - # nodePort: 443 - - ## The HTTPS port on the POD where the Ingress Controller service is running. - targetPort: 443 - - ## A list of custom ports to expose through the Ingress Controller service. Follows the conventional Kubernetes yaml syntax for service ports. - customPorts: [] - - serviceAccount: - ## The annotations of the service account of the Ingress Controller pods. - annotations: {} - - ## The name of the service account of the Ingress Controller pods. Used for RBAC. - ## Autogenerated if not set or set to "". - # name: nginx-ingress - - ## The name of the secret containing docker registry credentials. - ## Secret must exist in the same namespace as the helm release. - imagePullSecretName: "" - - serviceMonitor: - ## Creates a serviceMonitor to expose statistics on the kubernetes pods. - create: false - - ## The name of the serviceMonitor - ## Autogenerated if not set or set to "". - # name: nginx-ingress - - - ## Kubernetes object labels to attach to the serviceMonitor object. - labels: {} - - ## A set of labels to allow the selection of endpoints for the ServiceMonitor. - selectorMatchLabels: {} - - ## A list of endpoints allowed as part of this ServiceMonitor. - endpoints: [] - - reportIngressStatus: - ## Updates the address field in the status of Ingress resources with an external address of the Ingress Controller. - ## You must also specify the source of the external address either through an external service via controller.reportIngressStatus.externalService, - ## controller.reportIngressStatus.ingressLink or the external-status-address entry in the ConfigMap via controller.config.entries. - ## Note: controller.config.entries.external-status-address takes precedence over the others. - enable: true - - ## Specifies the name of the service with the type LoadBalancer through which the Ingress Controller is exposed externally. - ## The external address of the service is used when reporting the status of Ingress, VirtualServer and VirtualServerRoute resources. - ## controller.reportIngressStatus.enable must be set to true. - ## The default is autogenerated and matches the created service (see controller.service.create). - # externalService: nginx-ingress - - ## Specifies the name of the IngressLink resource, which exposes the Ingress Controller pods via a BIG-IP system. - ## The IP of the BIG-IP system is used when reporting the status of Ingress, VirtualServer and VirtualServerRoute resources. - ## controller.reportIngressStatus.enable must be set to true. - ingressLink: "" - - ## Enable Leader election to avoid multiple replicas of the controller reporting the status of Ingress resources. controller.reportIngressStatus.enable must be set to true. - enableLeaderElection: true - - ## Specifies the name of the ConfigMap, within the same namespace as the controller, used as the lock for leader election. controller.reportIngressStatus.enableLeaderElection must be set to true. - ## Autogenerated if not set or set to "". - # leaderElectionLockName: "nginx-ingress-leader-election" - - ## The annotations of the leader election configmap. - annotations: {} - - pod: - ## The annotations of the Ingress Controller pod. - annotations: {} - - ## The additional extra labels of the Ingress Controller pod. - extraLabels: {} - - ## The PriorityClass of the ingress controller pods. - priorityClassName: - - readyStatus: - ## Enables readiness endpoint "/nginx-ready". The endpoint returns a success code when NGINX has loaded all the config after startup. - enable: true - - ## Set the port where the readiness endpoint is exposed. - port: 8081 - - ## The number of seconds after the Ingress Controller pod has started before readiness probes are initiated. - initialDelaySeconds: 0 - - ## Enable collection of latency metrics for upstreams. Requires prometheus.create. - enableLatencyMetrics: false - - ## Disable IPV6 listeners explicitly for nodes that do not support the IPV6 stack. - disableIPV6: false - -rbac: - ## Configures RBAC. - create: true - -prometheus: - ## Expose NGINX or NGINX Plus metrics in the Prometheus format. - create: true - - ## Configures the port to scrape the metrics. - port: 9113 - - ## Specifies the namespace/name of a Kubernetes TLS Secret which will be used to protect the Prometheus endpoint. - secret: "" - - ## Configures the HTTP scheme used. - scheme: http - -nginxServiceMesh: - ## Enables integration with NGINX Service Mesh. - ## Requires controller.nginxplus - enable: false - - ## Enables NGINX Service Mesh workload to route egress traffic through the Ingress Controller. - ## Requires nginxServiceMesh.enable - enableEgress: false diff --git a/deployments/rbac/rbac.yaml b/deployments/rbac/rbac.yaml index 0501d9f04e..2c1da125f2 100644 --- a/deployments/rbac/rbac.yaml +++ b/deployments/rbac/rbac.yaml @@ -3,11 +3,25 @@ apiVersion: rbac.authorization.k8s.io/v1 metadata: name: nginx-ingress rules: +- apiGroups: + - discovery.k8s.io + resources: + - endpointslices + verbs: + - get + - list + - watch +- apiGroups: + - "apps" + resources: + - replicasets + - daemonsets + verbs: + - get - apiGroups: - "" resources: - services - - endpoints verbs: - get - list @@ -35,8 +49,10 @@ rules: resources: - pods verbs: + - get - list - watch + - update - apiGroups: - "" resources: @@ -45,6 +61,12 @@ rules: - get - list - watch +- apiGroups: + - "" + resources: + - nodes + verbs: + - list - apiGroups: - "" resources: @@ -105,6 +127,7 @@ rules: - ingressclasses verbs: - get + - list - apiGroups: - cis.f5.com resources: diff --git a/docs/Makefile b/docs/Makefile deleted file mode 100644 index 92d688ffec..0000000000 --- a/docs/Makefile +++ /dev/null @@ -1,40 +0,0 @@ -LOCAL_ROOT = $(git rev-parse --show-toplevel) -SHELL = /bin/bash -THEME_MODULE = gitlab.com/f5/nginx/controller/poc/f5-hugo -# export THEME_PATH="" -THEME_PATH = $(LOCAL_THEME_PATH) -THEME_BRANCH = development - -.PHONY: all all-local clean hugo-mod build-production build-staging hugo-server-drafts hugo-server - -all: hugo-mod build-production - -all-local: clean hugo-mod build-production - -clean: - rm -rf ${LOCAL_ROOT}/public - -hugo-mod: - hugo mod clean - rm -rf _vendor - hugo mod get - hugo mod vendor - -build-production: - hugo --gc -e production - -build-staging: - hugo --gc -e staging - -hugo-server-drafts: - hugo server -e production -b 127.0.0.1/nginx-ingress-controller/ -D --disableFastRender - -hugo-server: - hugo server -e production -b 127.0.0.1/nginx-ingress-controller/ --disableFastRender - -netlify: - netlify build --context=branch-deploy - netlify deploy -d public - -replace-theme: - go mod edit -replace "$(THEME_MODULE)"="$(THEME_PATH)" diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/archetypes/api.md b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/archetypes/api.md deleted file mode 100644 index a4f366bcee..0000000000 --- a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/archetypes/api.md +++ /dev/null @@ -1,17 +0,0 @@ ---- -title: "API Reference v" -description: "Represents the state of NGINX Controller's API for v" -date: 2019-11-15T13:21:02-07:00 -weight: 10 -draft: false -toc: false -tags: ["api"] -categories: [] -doctypes: ["reference"] -menu: "api" -version: [""] -# Create a new entry in the Jira DOCS Catalog and add the ticket ID (DOCS-) below -docs: "DOCS-000" ---- - -{{< openapi spec="/specs/release-/openapi.yaml" >}} diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/archetypes/blog.md b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/archetypes/blog.md deleted file mode 100644 index 0c109e014c..0000000000 --- a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/archetypes/blog.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -title: "" -description: "" -date: {{ .Date }} -weight: 20 -draft: false ---- \ No newline at end of file diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/archetypes/concept.md b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/archetypes/concept.md deleted file mode 100644 index 0cd1d29f6b..0000000000 --- a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/archetypes/concept.md +++ /dev/null @@ -1,44 +0,0 @@ ---- -title: "{{ replace .Name "-" " " | title }}" -date: {{ .Date }} -# Change draft status to false to publish doc -draft: true -# Description -# Add a short description (150 chars) for the doc. Include keywords for SEO. -# The description text appears in search results and at the top of the doc. -description: "" -# Assign weights in increments of 100 -weight: -toc: true -tags: [ "docs" ] -# Create a new entry in the Jira DOCS Catalog and add the ticket ID (DOCS-) below -docs: "DOCS-000" -# Taxonomies -# These are pre-populated with all available terms for your convenience. -# Remove all terms that do not apply. -categories: ["installation", "platform management", "load balancing", "api management", "service mesh", "security", "analytics"] -doctypes: ["concept"] -journeys: ["researching", "getting started", "using", "renewing", "self service"] -personas: ["devops", "netops", "secops", "support"] -versions: [] -authors: [] ---- - -## Overview - -Briefly describe the goal of this document, that is, what the user will learn or accomplish by reading what follows. - -## Concept 1 - format as a noun phrase - -This is where you explain the concept. Provide information that will help the user understand what the element/feature is and how it fits into the overall product. - -Organize content in this section with H3 and H4 headings. - -## Concept 2 - format as a noun phrase - -## Concept 3 - format as a noun phrase - -## What's Next - -- Provide up to 5 links to related topics (optional). -- Format as a bulleted list. \ No newline at end of file diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/archetypes/default.md b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/archetypes/default.md deleted file mode 100644 index cd76c9171f..0000000000 --- a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/archetypes/default.md +++ /dev/null @@ -1,79 +0,0 @@ ---- -title: "{{ replace .Name "-" " " | title }}" -date: {{ .Date }} -# Change draft status to false to publish doc. -draft: true -# Description -# Add a short description (150 chars) for the doc. Include keywords for SEO. -# The description text appears in search results and at the top of the doc. -description: "" -# Assign weights in increments of 100 -weight: -toc: true -tags: [ "docs" ] -# Create a new entry in the Jira DOCS Catalog and add the ticket ID (DOCS-) below -docs: "DOCS-000" -# Taxonomies -# These are pre-populated with all available terms for your convenience. -# Remove all terms that do not apply. -categories: ["installation", "platform management", "load balancing", "api management", "service mesh", "security", "analytics"] -doctypes: ["task"] -journeys: ["researching", "getting started", "using", "renewing", "self service"] -personas: ["devops", "netops", "secops", "support"] -versions: [] -authors: [] - ---- - -## Overview - -Briefly describe the goal of this document, that is, what the user will learn or accomplish by reading what follows. - -Introduce and explain any new concepts the user may need to understand before proceeding. - -## Before You Begin - -To complete the instructions in this guide, you need the following: - -1. Provide any prerequisites here. -2. Format as a numbered or bulleted list as appropriate. -3. Keep the list entries grammatically parallel.1. Provide any prerequisites here. - -## Goal 1 - write as a verb phrase - -Add introductory text. Say what the user will be doing. - -To do xzy, take the following steps: - -1. This is where you provide the steps that the user must take to accomplish the goal. - - ```bash - code examples should be nested within the list - ``` - -2. Format as numbered lists. - - {{< note >}}Add notes like this.{{}} - -3. If there is only one step, you don't need to format it as a numbered list. - -## Goal 2 - write as a verb phrase - -## Goal 3 - write as a verb phrase - -## Discussion - -Use the discussion section to expand on the information presented in the steps above. - -This section contains the "why" information. - -This information lives at the end of the document so that users who just want to follow the steps don't have to scroll through a wall of explanatory text to find them. - -## Verification - -Explain how the user can verify the steps completed successfully. - -## What's Next - -- Provide up to 5 links to related topics (optional). -- Format as a bulleted list. diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/archetypes/openapi.md b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/archetypes/openapi.md deleted file mode 100644 index 3109b5b5a6..0000000000 --- a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/archetypes/openapi.md +++ /dev/null @@ -1,30 +0,0 @@ ---- -title: "{{ replace .Name "-" " " | title }}" -date: {{ .Date }} -# Change draft status to false to publish doc -draft: true -# Description -# Add a short description (150 chars) for the doc. Include keywords for SEO. -# The description text appears in search results and at the top of the doc. -description: "" -# Assign weights in increments of 100 -weight: -doctypes: ["reference"] -toc: true -tags: [ "api" ] -menu: api -layout: api -# Create a new entry in the Jira DOCS Catalog and add the ticket ID (DOCS-) below -docs: "DOCS-000" -# Taxonomies -# These are pre-populated with all available terms for your convenience. -# Remove all terms that do not apply. -categories: ["installation", "platform management", "load balancing", "api management", "service mesh", "security", "analytics"] -doctypes: ["reference"] -journeys: ["researching", "getting started", "using"] -personas: ["devops", "netops", "secops", "support"] -versions: [""] -authors: [] ---- - -{{< openapi spec="/path/to/openapi.yaml" >}} diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/archetypes/reference.md b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/archetypes/reference.md deleted file mode 100644 index 4ff270d4ed..0000000000 --- a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/archetypes/reference.md +++ /dev/null @@ -1,43 +0,0 @@ ---- -title: "{{ replace .Name "-" " " | title }}" -date: {{ .Date }} -# Change draft status to false to publish doc -draft: true -# Description -# Add a short description (150 chars) for the doc. Include keywords for SEO. -# The description text appears in search results and at the top of the doc. -description: "" -# Assign weights in increments of 100 -weight: -toc: true -tags: [ "docs" ] -# Create a new entry in the Jira DOCS Catalog and add the ticket ID (DOCS-) below -docs: "DOCS-000" -# Taxonomies -# These are pre-populated with all available terms for your convenience. -# Remove all terms that do not apply. -categories: ["installation", "platform management", "load balancing", "api management", "service mesh", "security", "analytics"] -doctypes: ["reference"] -journeys: ["researching", "getting started", "using", "renewing", "self service"] -personas: ["devops", "netops", "secops", "support"] -versions: [] -authors: [] ---- - -## Overview - -Briefly describe the goal of this document, that is, what the user will learn or accomplish by reading what follows. - -Introduce and explain any new concepts the user may need to understand before proceeding. -## Reference 1 - format as a noun phrase - -Provide reference material here. This may be tables, paragraphs, imported code, etc. - -## Reference 2 - format as a noun phrase - -## Reference 3 - format as a noun phrase - -## What's Next - -- Provide up to 5 links to related topics (optional). -- Format as a bulleted list. diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/archetypes/troubleshooting.md b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/archetypes/troubleshooting.md deleted file mode 100644 index 5bdf9e72a4..0000000000 --- a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/archetypes/troubleshooting.md +++ /dev/null @@ -1,51 +0,0 @@ ---- -title: "{{ replace .Name "-" " " | title }}" -date: {{ .Date }} -# Change draft status to false to publish doc -draft: true -# Description -# Add a short description (150 chars) for the doc. Include keywords for SEO. -# The description text appears in search results and at the top of the doc. -description: "" -# Assign weights in increments of 100 -weight: -toc: true -tags: [ "docs" ] -# Create a new entry in the Jira DOCS Catalog and add the ticket ID (DOCS-) below -docs: "DOCS-000" -# Taxonomies -# These are pre-populated with all available terms for your convenience. -# Remove all terms that do not apply. -categories: ["installation", "platform management", "load balancing", "api management", "service mesh", "security", "analytics"] -doctypes: ["troubleshooting"] -journeys: ["researching", "getting started", "using", "renewing", "self service"] -personas: ["devops", "netops", "secops", "support"] -versions: [] -authors: [] ---- - -## Overview - -Briefly describe the goal of this document, that is, what the user will accomplish by reading what follows. - -## Issue 1 - write as a verb phrase - -Explain the issue. Include any identifying details, such as error messages. - -When the system does xyz, you may see an error similar to the following: - -```text -error message here -``` - -This issue is caused by -- add cause here. - -To resolve the issue, take the following steps: - -1. The steps -2. To take to -3. Resolve the issue. - -## Issue 2 - write as a verb phrase - -## Issue 3 - write as a verb phrase \ No newline at end of file diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/archetypes/tutorial.md b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/archetypes/tutorial.md deleted file mode 100644 index 849b760ca0..0000000000 --- a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/archetypes/tutorial.md +++ /dev/null @@ -1,59 +0,0 @@ ---- -title: "{{ replace .Name "-" " " | title }}" -date: {{ .Date }} -# Change draft status to false to publish doc -draft: true -# Description -# Add a short description (150 chars) for the doc. Include keywords for SEO. -# The description text appears in search results and at the top of the doc. -description: "" -# Assign weights in increments of 100 -weight: -toc: true -tags: [ "docs" ] -# Create a new entry in the Jira DOCS Catalog and add the ticket ID (DOCS-) below -docs: "DOCS-000" -# Taxonomies -# These are pre-populated with all available terms for your convenience. -# Remove all terms that do not apply. -categories: ["installation", "platform management", "load balancing", "api management", "service mesh", "security", "analytics"] -doctypes: ["tutorial"] -journeys: ["researching", "getting started", "using", "renewing", "self service"] -personas: ["devops", "netops", "secops", "support"] -versions: [] -authors: [] ---- - - -## Overview - -Briefly describe the goal of this document, that is, what the user will learn or accomplish by reading what follows. - -Introduce and explain any new concepts the user may need to understand before proceeding. - -## Before You Begin - -To complete the instructions in this guide, you need the following: - -1. Provide any prerequisites here. -2. Format as a numbered or bulleted list as appropriate. -3. Keep the list entries grammatically parallel. - -## Lesson 1 - -Provide the steps required to complete the first part of the objective. - -This content may (should?) be reused from a task topic. - -## Lesson 2 - -etc. - -## Cleanup - -Provide any steps required to cleanup the test. - -## What's Next - -Provide up to 5 links to related topics (optional). -Format as a bulleted list. \ No newline at end of file diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/assets/css/all.min.css b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/assets/css/all.min.css deleted file mode 100644 index bee12e3e0f..0000000000 --- a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/assets/css/all.min.css +++ /dev/null @@ -1,6 +0,0 @@ -/*! - * Font Awesome Free 6.0.0 by @fontawesome - https://fontawesome.com - * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) - * Copyright 2022 Fonticons, Inc. - */ -.fa{font-family:var(--fa-style-family,"Font Awesome 6 Free");font-weight:var(--fa-style,900)}.fa,.fa-brands,.fa-duotone,.fa-light,.fa-regular,.fa-solid,.fa-thin,.fab,.fad,.fal,.far,.fas,.fat{-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;display:var(--fa-display,inline-block);font-style:normal;font-variant:normal;line-height:1;text-rendering:auto}.fa-1x{font-size:1em}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-6x{font-size:6em}.fa-7x{font-size:7em}.fa-8x{font-size:8em}.fa-9x{font-size:9em}.fa-10x{font-size:10em}.fa-2xs{font-size:.625em;line-height:.1em;vertical-align:.225em}.fa-xs{font-size:.75em;line-height:.08333em;vertical-align:.125em}.fa-sm{font-size:.875em;line-height:.07143em;vertical-align:.05357em}.fa-lg{font-size:1.25em;line-height:.05em;vertical-align:-.075em}.fa-xl{font-size:1.5em;line-height:.04167em;vertical-align:-.125em}.fa-2xl{font-size:2em;line-height:.03125em;vertical-align:-.1875em}.fa-fw{text-align:center;width:1.25em}.fa-ul{list-style-type:none;margin-left:var(--fa-li-margin,2.5em);padding-left:0}.fa-ul>li{position:relative}.fa-li{left:calc(var(--fa-li-width, 2em)*-1);position:absolute;text-align:center;width:var(--fa-li-width,2em);line-height:inherit}.fa-border{border-radius:var(--fa-border-radius,.1em);border:var(--fa-border-width,.08em) var(--fa-border-style,solid) var(--fa-border-color,#eee);padding:var(--fa-border-padding,.2em .25em .15em)}.fa-pull-left{float:left;margin-right:var(--fa-pull-margin,.3em)}.fa-pull-right{float:right;margin-left:var(--fa-pull-margin,.3em)}.fa-beat{-webkit-animation-name:fa-beat;animation-name:fa-beat;-webkit-animation-delay:var(--fa-animation-delay,0);animation-delay:var(--fa-animation-delay,0);-webkit-animation-direction:var(--fa-animation-direction,normal);animation-direction:var(--fa-animation-direction,normal);-webkit-animation-duration:var(--fa-animation-duration,1s);animation-duration:var(--fa-animation-duration,1s);-webkit-animation-iteration-count:var(--fa-animation-iteration-count,infinite);animation-iteration-count:var(--fa-animation-iteration-count,infinite);-webkit-animation-timing-function:var(--fa-animation-timing,ease-in-out);animation-timing-function:var(--fa-animation-timing,ease-in-out)}.fa-bounce{-webkit-animation-name:fa-bounce;animation-name:fa-bounce;-webkit-animation-delay:var(--fa-animation-delay,0);animation-delay:var(--fa-animation-delay,0);-webkit-animation-direction:var(--fa-animation-direction,normal);animation-direction:var(--fa-animation-direction,normal);-webkit-animation-duration:var(--fa-animation-duration,1s);animation-duration:var(--fa-animation-duration,1s);-webkit-animation-iteration-count:var(--fa-animation-iteration-count,infinite);animation-iteration-count:var(--fa-animation-iteration-count,infinite);-webkit-animation-timing-function:var(--fa-animation-timing,cubic-bezier(.28,.84,.42,1));animation-timing-function:var(--fa-animation-timing,cubic-bezier(.28,.84,.42,1))}.fa-fade{-webkit-animation-name:fa-fade;animation-name:fa-fade;-webkit-animation-iteration-count:var(--fa-animation-iteration-count,infinite);animation-iteration-count:var(--fa-animation-iteration-count,infinite);-webkit-animation-timing-function:var(--fa-animation-timing,cubic-bezier(.4,0,.6,1));animation-timing-function:var(--fa-animation-timing,cubic-bezier(.4,0,.6,1))}.fa-beat-fade,.fa-fade{-webkit-animation-delay:var(--fa-animation-delay,0);animation-delay:var(--fa-animation-delay,0);-webkit-animation-direction:var(--fa-animation-direction,normal);animation-direction:var(--fa-animation-direction,normal);-webkit-animation-duration:var(--fa-animation-duration,1s);animation-duration:var(--fa-animation-duration,1s)}.fa-beat-fade{-webkit-animation-name:fa-beat-fade;animation-name:fa-beat-fade;-webkit-animation-iteration-count:var(--fa-animation-iteration-count,infinite);animation-iteration-count:var(--fa-animation-iteration-count,infinite);-webkit-animation-timing-function:var(--fa-animation-timing,cubic-bezier(.4,0,.6,1));animation-timing-function:var(--fa-animation-timing,cubic-bezier(.4,0,.6,1))}.fa-flip{-webkit-animation-name:fa-flip;animation-name:fa-flip;-webkit-animation-delay:var(--fa-animation-delay,0);animation-delay:var(--fa-animation-delay,0);-webkit-animation-direction:var(--fa-animation-direction,normal);animation-direction:var(--fa-animation-direction,normal);-webkit-animation-duration:var(--fa-animation-duration,1s);animation-duration:var(--fa-animation-duration,1s);-webkit-animation-iteration-count:var(--fa-animation-iteration-count,infinite);animation-iteration-count:var(--fa-animation-iteration-count,infinite);-webkit-animation-timing-function:var(--fa-animation-timing,ease-in-out);animation-timing-function:var(--fa-animation-timing,ease-in-out)}.fa-shake{-webkit-animation-name:fa-shake;animation-name:fa-shake;-webkit-animation-duration:var(--fa-animation-duration,1s);animation-duration:var(--fa-animation-duration,1s);-webkit-animation-iteration-count:var(--fa-animation-iteration-count,infinite);animation-iteration-count:var(--fa-animation-iteration-count,infinite);-webkit-animation-timing-function:var(--fa-animation-timing,linear);animation-timing-function:var(--fa-animation-timing,linear)}.fa-shake,.fa-spin{-webkit-animation-delay:var(--fa-animation-delay,0);animation-delay:var(--fa-animation-delay,0);-webkit-animation-direction:var(--fa-animation-direction,normal);animation-direction:var(--fa-animation-direction,normal)}.fa-spin{-webkit-animation-name:fa-spin;animation-name:fa-spin;-webkit-animation-duration:var(--fa-animation-duration,2s);animation-duration:var(--fa-animation-duration,2s);-webkit-animation-iteration-count:var(--fa-animation-iteration-count,infinite);animation-iteration-count:var(--fa-animation-iteration-count,infinite);-webkit-animation-timing-function:var(--fa-animation-timing,linear);animation-timing-function:var(--fa-animation-timing,linear)}.fa-spin-reverse{--fa-animation-direction:reverse}.fa-pulse,.fa-spin-pulse{-webkit-animation-name:fa-spin;animation-name:fa-spin;-webkit-animation-direction:var(--fa-animation-direction,normal);animation-direction:var(--fa-animation-direction,normal);-webkit-animation-duration:var(--fa-animation-duration,1s);animation-duration:var(--fa-animation-duration,1s);-webkit-animation-iteration-count:var(--fa-animation-iteration-count,infinite);animation-iteration-count:var(--fa-animation-iteration-count,infinite);-webkit-animation-timing-function:var(--fa-animation-timing,steps(8));animation-timing-function:var(--fa-animation-timing,steps(8))}@media (prefers-reduced-motion:reduce){.fa-beat,.fa-beat-fade,.fa-bounce,.fa-fade,.fa-flip,.fa-pulse,.fa-shake,.fa-spin,.fa-spin-pulse{-webkit-animation-delay:-1ms;animation-delay:-1ms;-webkit-animation-duration:1ms;animation-duration:1ms;-webkit-animation-iteration-count:1;animation-iteration-count:1;transition-delay:0s;transition-duration:0s}}@-webkit-keyframes fa-beat{0%,90%{-webkit-transform:scale(1);transform:scale(1)}45%{-webkit-transform:scale(var(--fa-beat-scale,1.25));transform:scale(var(--fa-beat-scale,1.25))}}@keyframes fa-beat{0%,90%{-webkit-transform:scale(1);transform:scale(1)}45%{-webkit-transform:scale(var(--fa-beat-scale,1.25));transform:scale(var(--fa-beat-scale,1.25))}}@-webkit-keyframes fa-bounce{0%{-webkit-transform:scale(1) translateY(0);transform:scale(1) translateY(0)}10%{-webkit-transform:scale(var(--fa-bounce-start-scale-x,1.1),var(--fa-bounce-start-scale-y,.9)) translateY(0);transform:scale(var(--fa-bounce-start-scale-x,1.1),var(--fa-bounce-start-scale-y,.9)) translateY(0)}30%{-webkit-transform:scale(var(--fa-bounce-jump-scale-x,.9),var(--fa-bounce-jump-scale-y,1.1)) translateY(var(--fa-bounce-height,-.5em));transform:scale(var(--fa-bounce-jump-scale-x,.9),var(--fa-bounce-jump-scale-y,1.1)) translateY(var(--fa-bounce-height,-.5em))}50%{-webkit-transform:scale(var(--fa-bounce-land-scale-x,1.05),var(--fa-bounce-land-scale-y,.95)) translateY(0);transform:scale(var(--fa-bounce-land-scale-x,1.05),var(--fa-bounce-land-scale-y,.95)) translateY(0)}57%{-webkit-transform:scale(1) translateY(var(--fa-bounce-rebound,-.125em));transform:scale(1) translateY(var(--fa-bounce-rebound,-.125em))}64%{-webkit-transform:scale(1) translateY(0);transform:scale(1) translateY(0)}to{-webkit-transform:scale(1) translateY(0);transform:scale(1) translateY(0)}}@keyframes fa-bounce{0%{-webkit-transform:scale(1) translateY(0);transform:scale(1) translateY(0)}10%{-webkit-transform:scale(var(--fa-bounce-start-scale-x,1.1),var(--fa-bounce-start-scale-y,.9)) translateY(0);transform:scale(var(--fa-bounce-start-scale-x,1.1),var(--fa-bounce-start-scale-y,.9)) translateY(0)}30%{-webkit-transform:scale(var(--fa-bounce-jump-scale-x,.9),var(--fa-bounce-jump-scale-y,1.1)) translateY(var(--fa-bounce-height,-.5em));transform:scale(var(--fa-bounce-jump-scale-x,.9),var(--fa-bounce-jump-scale-y,1.1)) translateY(var(--fa-bounce-height,-.5em))}50%{-webkit-transform:scale(var(--fa-bounce-land-scale-x,1.05),var(--fa-bounce-land-scale-y,.95)) translateY(0);transform:scale(var(--fa-bounce-land-scale-x,1.05),var(--fa-bounce-land-scale-y,.95)) translateY(0)}57%{-webkit-transform:scale(1) translateY(var(--fa-bounce-rebound,-.125em));transform:scale(1) translateY(var(--fa-bounce-rebound,-.125em))}64%{-webkit-transform:scale(1) translateY(0);transform:scale(1) translateY(0)}to{-webkit-transform:scale(1) translateY(0);transform:scale(1) translateY(0)}}@-webkit-keyframes fa-fade{50%{opacity:var(--fa-fade-opacity,.4)}}@keyframes fa-fade{50%{opacity:var(--fa-fade-opacity,.4)}}@-webkit-keyframes fa-beat-fade{0%,to{opacity:var(--fa-beat-fade-opacity,.4);-webkit-transform:scale(1);transform:scale(1)}50%{opacity:1;-webkit-transform:scale(var(--fa-beat-fade-scale,1.125));transform:scale(var(--fa-beat-fade-scale,1.125))}}@keyframes fa-beat-fade{0%,to{opacity:var(--fa-beat-fade-opacity,.4);-webkit-transform:scale(1);transform:scale(1)}50%{opacity:1;-webkit-transform:scale(var(--fa-beat-fade-scale,1.125));transform:scale(var(--fa-beat-fade-scale,1.125))}}@-webkit-keyframes fa-flip{50%{-webkit-transform:rotate3d(var(--fa-flip-x,0),var(--fa-flip-y,1),var(--fa-flip-z,0),var(--fa-flip-angle,-180deg));transform:rotate3d(var(--fa-flip-x,0),var(--fa-flip-y,1),var(--fa-flip-z,0),var(--fa-flip-angle,-180deg))}}@keyframes fa-flip{50%{-webkit-transform:rotate3d(var(--fa-flip-x,0),var(--fa-flip-y,1),var(--fa-flip-z,0),var(--fa-flip-angle,-180deg));transform:rotate3d(var(--fa-flip-x,0),var(--fa-flip-y,1),var(--fa-flip-z,0),var(--fa-flip-angle,-180deg))}}@-webkit-keyframes fa-shake{0%{-webkit-transform:rotate(-15deg);transform:rotate(-15deg)}4%{-webkit-transform:rotate(15deg);transform:rotate(15deg)}8%,24%{-webkit-transform:rotate(-18deg);transform:rotate(-18deg)}12%,28%{-webkit-transform:rotate(18deg);transform:rotate(18deg)}16%{-webkit-transform:rotate(-22deg);transform:rotate(-22deg)}20%{-webkit-transform:rotate(22deg);transform:rotate(22deg)}32%{-webkit-transform:rotate(-12deg);transform:rotate(-12deg)}36%{-webkit-transform:rotate(12deg);transform:rotate(12deg)}40%,to{-webkit-transform:rotate(0deg);transform:rotate(0deg)}}@keyframes fa-shake{0%{-webkit-transform:rotate(-15deg);transform:rotate(-15deg)}4%{-webkit-transform:rotate(15deg);transform:rotate(15deg)}8%,24%{-webkit-transform:rotate(-18deg);transform:rotate(-18deg)}12%,28%{-webkit-transform:rotate(18deg);transform:rotate(18deg)}16%{-webkit-transform:rotate(-22deg);transform:rotate(-22deg)}20%{-webkit-transform:rotate(22deg);transform:rotate(22deg)}32%{-webkit-transform:rotate(-12deg);transform:rotate(-12deg)}36%{-webkit-transform:rotate(12deg);transform:rotate(12deg)}40%,to{-webkit-transform:rotate(0deg);transform:rotate(0deg)}}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}.fa-rotate-90{-webkit-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-webkit-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-webkit-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-webkit-transform:scaleX(-1);transform:scaleX(-1)}.fa-flip-vertical{-webkit-transform:scaleY(-1);transform:scaleY(-1)}.fa-flip-both,.fa-flip-horizontal.fa-flip-vertical{-webkit-transform:scale(-1);transform:scale(-1)}.fa-rotate-by{-webkit-transform:rotate(var(--fa-rotate-angle,none));transform:rotate(var(--fa-rotate-angle,none))}.fa-stack{display:inline-block;height:2em;line-height:2em;position:relative;vertical-align:middle;width:2.5em}.fa-stack-1x,.fa-stack-2x{left:0;position:absolute;text-align:center;width:100%;z-index:var(--fa-stack-z-index,auto)}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:var(--fa-inverse,#fff)}.fa-0:before{content:"\30"}.fa-1:before{content:"\31"}.fa-2:before{content:"\32"}.fa-3:before{content:"\33"}.fa-4:before{content:"\34"}.fa-5:before{content:"\35"}.fa-6:before{content:"\36"}.fa-7:before{content:"\37"}.fa-8:before{content:"\38"}.fa-9:before{content:"\39"}.fa-a:before{content:"\41"}.fa-address-book:before,.fa-contact-book:before{content:"\f2b9"}.fa-address-card:before,.fa-contact-card:before,.fa-vcard:before{content:"\f2bb"}.fa-align-center:before{content:"\f037"}.fa-align-justify:before{content:"\f039"}.fa-align-left:before{content:"\f036"}.fa-align-right:before{content:"\f038"}.fa-anchor:before{content:"\f13d"}.fa-angle-down:before{content:"\f107"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angle-double-down:before,.fa-angles-down:before{content:"\f103"}.fa-angle-double-left:before,.fa-angles-left:before{content:"\f100"}.fa-angle-double-right:before,.fa-angles-right:before{content:"\f101"}.fa-angle-double-up:before,.fa-angles-up:before{content:"\f102"}.fa-ankh:before{content:"\f644"}.fa-apple-alt:before,.fa-apple-whole:before{content:"\f5d1"}.fa-archway:before{content:"\f557"}.fa-arrow-down:before{content:"\f063"}.fa-arrow-down-1-9:before,.fa-sort-numeric-asc:before,.fa-sort-numeric-down:before{content:"\f162"}.fa-arrow-down-9-1:before,.fa-sort-numeric-desc:before,.fa-sort-numeric-down-alt:before{content:"\f886"}.fa-arrow-down-a-z:before,.fa-sort-alpha-asc:before,.fa-sort-alpha-down:before{content:"\f15d"}.fa-arrow-down-long:before,.fa-long-arrow-down:before{content:"\f175"}.fa-arrow-down-short-wide:before,.fa-sort-amount-desc:before,.fa-sort-amount-down-alt:before{content:"\f884"}.fa-arrow-down-wide-short:before,.fa-sort-amount-asc:before,.fa-sort-amount-down:before{content:"\f160"}.fa-arrow-down-z-a:before,.fa-sort-alpha-desc:before,.fa-sort-alpha-down-alt:before{content:"\f881"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-left-long:before,.fa-long-arrow-left:before{content:"\f177"}.fa-arrow-pointer:before,.fa-mouse-pointer:before{content:"\f245"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-right-arrow-left:before,.fa-exchange:before{content:"\f0ec"}.fa-arrow-right-from-bracket:before,.fa-sign-out:before{content:"\f08b"}.fa-arrow-right-long:before,.fa-long-arrow-right:before{content:"\f178"}.fa-arrow-right-to-bracket:before,.fa-sign-in:before{content:"\f090"}.fa-arrow-left-rotate:before,.fa-arrow-rotate-back:before,.fa-arrow-rotate-backward:before,.fa-arrow-rotate-left:before,.fa-undo:before{content:"\f0e2"}.fa-arrow-right-rotate:before,.fa-arrow-rotate-forward:before,.fa-arrow-rotate-right:before,.fa-redo:before{content:"\f01e"}.fa-arrow-trend-down:before{content:"\e097"}.fa-arrow-trend-up:before{content:"\e098"}.fa-arrow-turn-down:before,.fa-level-down:before{content:"\f149"}.fa-arrow-turn-up:before,.fa-level-up:before{content:"\f148"}.fa-arrow-up:before{content:"\f062"}.fa-arrow-up-1-9:before,.fa-sort-numeric-up:before{content:"\f163"}.fa-arrow-up-9-1:before,.fa-sort-numeric-up-alt:before{content:"\f887"}.fa-arrow-up-a-z:before,.fa-sort-alpha-up:before{content:"\f15e"}.fa-arrow-up-from-bracket:before{content:"\e09a"}.fa-arrow-up-long:before,.fa-long-arrow-up:before{content:"\f176"}.fa-arrow-up-right-from-square:before,.fa-external-link:before{content:"\f08e"}.fa-arrow-up-short-wide:before,.fa-sort-amount-up-alt:before{content:"\f885"}.fa-arrow-up-wide-short:before,.fa-sort-amount-up:before{content:"\f161"}.fa-arrow-up-z-a:before,.fa-sort-alpha-up-alt:before{content:"\f882"}.fa-arrows-h:before,.fa-arrows-left-right:before{content:"\f07e"}.fa-arrows-rotate:before,.fa-refresh:before,.fa-sync:before{content:"\f021"}.fa-arrows-up-down:before,.fa-arrows-v:before{content:"\f07d"}.fa-arrows-up-down-left-right:before,.fa-arrows:before{content:"\f047"}.fa-asterisk:before{content:"\2a"}.fa-at:before{content:"\40"}.fa-atom:before{content:"\f5d2"}.fa-audio-description:before{content:"\f29e"}.fa-austral-sign:before{content:"\e0a9"}.fa-award:before{content:"\f559"}.fa-b:before{content:"\42"}.fa-baby:before{content:"\f77c"}.fa-baby-carriage:before,.fa-carriage-baby:before{content:"\f77d"}.fa-backward:before{content:"\f04a"}.fa-backward-fast:before,.fa-fast-backward:before{content:"\f049"}.fa-backward-step:before,.fa-step-backward:before{content:"\f048"}.fa-bacon:before{content:"\f7e5"}.fa-bacteria:before{content:"\e059"}.fa-bacterium:before{content:"\e05a"}.fa-bag-shopping:before,.fa-shopping-bag:before{content:"\f290"}.fa-bahai:before{content:"\f666"}.fa-baht-sign:before{content:"\e0ac"}.fa-ban:before,.fa-cancel:before{content:"\f05e"}.fa-ban-smoking:before,.fa-smoking-ban:before{content:"\f54d"}.fa-band-aid:before,.fa-bandage:before{content:"\f462"}.fa-barcode:before{content:"\f02a"}.fa-bars:before,.fa-navicon:before{content:"\f0c9"}.fa-bars-progress:before,.fa-tasks-alt:before{content:"\f828"}.fa-bars-staggered:before,.fa-reorder:before,.fa-stream:before{content:"\f550"}.fa-baseball-ball:before,.fa-baseball:before{content:"\f433"}.fa-baseball-bat-ball:before{content:"\f432"}.fa-basket-shopping:before,.fa-shopping-basket:before{content:"\f291"}.fa-basketball-ball:before,.fa-basketball:before{content:"\f434"}.fa-bath:before,.fa-bathtub:before{content:"\f2cd"}.fa-battery-0:before,.fa-battery-empty:before{content:"\f244"}.fa-battery-5:before,.fa-battery-full:before,.fa-battery:before{content:"\f240"}.fa-battery-3:before,.fa-battery-half:before{content:"\f242"}.fa-battery-2:before,.fa-battery-quarter:before{content:"\f243"}.fa-battery-4:before,.fa-battery-three-quarters:before{content:"\f241"}.fa-bed:before{content:"\f236"}.fa-bed-pulse:before,.fa-procedures:before{content:"\f487"}.fa-beer-mug-empty:before,.fa-beer:before{content:"\f0fc"}.fa-bell:before{content:"\f0f3"}.fa-bell-concierge:before,.fa-concierge-bell:before{content:"\f562"}.fa-bell-slash:before{content:"\f1f6"}.fa-bezier-curve:before{content:"\f55b"}.fa-bicycle:before{content:"\f206"}.fa-binoculars:before{content:"\f1e5"}.fa-biohazard:before{content:"\f780"}.fa-bitcoin-sign:before{content:"\e0b4"}.fa-blender:before{content:"\f517"}.fa-blender-phone:before{content:"\f6b6"}.fa-blog:before{content:"\f781"}.fa-bold:before{content:"\f032"}.fa-bolt:before,.fa-zap:before{content:"\f0e7"}.fa-bolt-lightning:before{content:"\e0b7"}.fa-bomb:before{content:"\f1e2"}.fa-bone:before{content:"\f5d7"}.fa-bong:before{content:"\f55c"}.fa-book:before{content:"\f02d"}.fa-atlas:before,.fa-book-atlas:before{content:"\f558"}.fa-bible:before,.fa-book-bible:before{content:"\f647"}.fa-book-journal-whills:before,.fa-journal-whills:before{content:"\f66a"}.fa-book-medical:before{content:"\f7e6"}.fa-book-open:before{content:"\f518"}.fa-book-open-reader:before,.fa-book-reader:before{content:"\f5da"}.fa-book-quran:before,.fa-quran:before{content:"\f687"}.fa-book-dead:before,.fa-book-skull:before{content:"\f6b7"}.fa-bookmark:before{content:"\f02e"}.fa-border-all:before{content:"\f84c"}.fa-border-none:before{content:"\f850"}.fa-border-style:before,.fa-border-top-left:before{content:"\f853"}.fa-bowling-ball:before{content:"\f436"}.fa-box:before{content:"\f466"}.fa-archive:before,.fa-box-archive:before{content:"\f187"}.fa-box-open:before{content:"\f49e"}.fa-box-tissue:before{content:"\e05b"}.fa-boxes-alt:before,.fa-boxes-stacked:before,.fa-boxes:before{content:"\f468"}.fa-braille:before{content:"\f2a1"}.fa-brain:before{content:"\f5dc"}.fa-brazilian-real-sign:before{content:"\e46c"}.fa-bread-slice:before{content:"\f7ec"}.fa-briefcase:before{content:"\f0b1"}.fa-briefcase-medical:before{content:"\f469"}.fa-broom:before{content:"\f51a"}.fa-broom-ball:before,.fa-quidditch-broom-ball:before,.fa-quidditch:before{content:"\f458"}.fa-brush:before{content:"\f55d"}.fa-bug:before{content:"\f188"}.fa-bug-slash:before{content:"\e490"}.fa-building:before{content:"\f1ad"}.fa-bank:before,.fa-building-columns:before,.fa-institution:before,.fa-museum:before,.fa-university:before{content:"\f19c"}.fa-bullhorn:before{content:"\f0a1"}.fa-bullseye:before{content:"\f140"}.fa-burger:before,.fa-hamburger:before{content:"\f805"}.fa-bus:before{content:"\f207"}.fa-bus-alt:before,.fa-bus-simple:before{content:"\f55e"}.fa-briefcase-clock:before,.fa-business-time:before{content:"\f64a"}.fa-c:before{content:"\43"}.fa-birthday-cake:before,.fa-cake-candles:before,.fa-cake:before{content:"\f1fd"}.fa-calculator:before{content:"\f1ec"}.fa-calendar:before{content:"\f133"}.fa-calendar-check:before{content:"\f274"}.fa-calendar-day:before{content:"\f783"}.fa-calendar-alt:before,.fa-calendar-days:before{content:"\f073"}.fa-calendar-minus:before{content:"\f272"}.fa-calendar-plus:before{content:"\f271"}.fa-calendar-week:before{content:"\f784"}.fa-calendar-times:before,.fa-calendar-xmark:before{content:"\f273"}.fa-camera-alt:before,.fa-camera:before{content:"\f030"}.fa-camera-retro:before{content:"\f083"}.fa-camera-rotate:before{content:"\e0d8"}.fa-campground:before{content:"\f6bb"}.fa-candy-cane:before{content:"\f786"}.fa-cannabis:before{content:"\f55f"}.fa-capsules:before{content:"\f46b"}.fa-automobile:before,.fa-car:before{content:"\f1b9"}.fa-battery-car:before,.fa-car-battery:before{content:"\f5df"}.fa-car-crash:before{content:"\f5e1"}.fa-car-alt:before,.fa-car-rear:before{content:"\f5de"}.fa-car-side:before{content:"\f5e4"}.fa-caravan:before{content:"\f8ff"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-caret-up:before{content:"\f0d8"}.fa-carrot:before{content:"\f787"}.fa-cart-arrow-down:before{content:"\f218"}.fa-cart-flatbed:before,.fa-dolly-flatbed:before{content:"\f474"}.fa-cart-flatbed-suitcase:before,.fa-luggage-cart:before{content:"\f59d"}.fa-cart-plus:before{content:"\f217"}.fa-cart-shopping:before,.fa-shopping-cart:before{content:"\f07a"}.fa-cash-register:before{content:"\f788"}.fa-cat:before{content:"\f6be"}.fa-cedi-sign:before{content:"\e0df"}.fa-cent-sign:before{content:"\e3f5"}.fa-certificate:before{content:"\f0a3"}.fa-chair:before{content:"\f6c0"}.fa-blackboard:before,.fa-chalkboard:before{content:"\f51b"}.fa-chalkboard-teacher:before,.fa-chalkboard-user:before{content:"\f51c"}.fa-champagne-glasses:before,.fa-glass-cheers:before{content:"\f79f"}.fa-charging-station:before{content:"\f5e7"}.fa-area-chart:before,.fa-chart-area:before{content:"\f1fe"}.fa-bar-chart:before,.fa-chart-bar:before{content:"\f080"}.fa-chart-column:before{content:"\e0e3"}.fa-chart-gantt:before{content:"\e0e4"}.fa-chart-line:before,.fa-line-chart:before{content:"\f201"}.fa-chart-pie:before,.fa-pie-chart:before{content:"\f200"}.fa-check:before{content:"\f00c"}.fa-check-double:before{content:"\f560"}.fa-check-to-slot:before,.fa-vote-yea:before{content:"\f772"}.fa-cheese:before{content:"\f7ef"}.fa-chess:before{content:"\f439"}.fa-chess-bishop:before{content:"\f43a"}.fa-chess-board:before{content:"\f43c"}.fa-chess-king:before{content:"\f43f"}.fa-chess-knight:before{content:"\f441"}.fa-chess-pawn:before{content:"\f443"}.fa-chess-queen:before{content:"\f445"}.fa-chess-rook:before{content:"\f447"}.fa-chevron-down:before{content:"\f078"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-chevron-up:before{content:"\f077"}.fa-child:before{content:"\f1ae"}.fa-church:before{content:"\f51d"}.fa-circle:before{content:"\f111"}.fa-arrow-circle-down:before,.fa-circle-arrow-down:before{content:"\f0ab"}.fa-arrow-circle-left:before,.fa-circle-arrow-left:before{content:"\f0a8"}.fa-arrow-circle-right:before,.fa-circle-arrow-right:before{content:"\f0a9"}.fa-arrow-circle-up:before,.fa-circle-arrow-up:before{content:"\f0aa"}.fa-check-circle:before,.fa-circle-check:before{content:"\f058"}.fa-chevron-circle-down:before,.fa-circle-chevron-down:before{content:"\f13a"}.fa-chevron-circle-left:before,.fa-circle-chevron-left:before{content:"\f137"}.fa-chevron-circle-right:before,.fa-circle-chevron-right:before{content:"\f138"}.fa-chevron-circle-up:before,.fa-circle-chevron-up:before{content:"\f139"}.fa-circle-dollar-to-slot:before,.fa-donate:before{content:"\f4b9"}.fa-circle-dot:before,.fa-dot-circle:before{content:"\f192"}.fa-arrow-alt-circle-down:before,.fa-circle-down:before{content:"\f358"}.fa-circle-exclamation:before,.fa-exclamation-circle:before{content:"\f06a"}.fa-circle-h:before,.fa-hospital-symbol:before{content:"\f47e"}.fa-adjust:before,.fa-circle-half-stroke:before{content:"\f042"}.fa-circle-info:before,.fa-info-circle:before{content:"\f05a"}.fa-arrow-alt-circle-left:before,.fa-circle-left:before{content:"\f359"}.fa-circle-minus:before,.fa-minus-circle:before{content:"\f056"}.fa-circle-notch:before{content:"\f1ce"}.fa-circle-pause:before,.fa-pause-circle:before{content:"\f28b"}.fa-circle-play:before,.fa-play-circle:before{content:"\f144"}.fa-circle-plus:before,.fa-plus-circle:before{content:"\f055"}.fa-circle-question:before,.fa-question-circle:before{content:"\f059"}.fa-circle-radiation:before,.fa-radiation-alt:before{content:"\f7ba"}.fa-arrow-alt-circle-right:before,.fa-circle-right:before{content:"\f35a"}.fa-circle-stop:before,.fa-stop-circle:before{content:"\f28d"}.fa-arrow-alt-circle-up:before,.fa-circle-up:before{content:"\f35b"}.fa-circle-user:before,.fa-user-circle:before{content:"\f2bd"}.fa-circle-xmark:before,.fa-times-circle:before,.fa-xmark-circle:before{content:"\f057"}.fa-city:before{content:"\f64f"}.fa-clapperboard:before{content:"\e131"}.fa-clipboard:before{content:"\f328"}.fa-clipboard-check:before{content:"\f46c"}.fa-clipboard-list:before{content:"\f46d"}.fa-clock-four:before,.fa-clock:before{content:"\f017"}.fa-clock-rotate-left:before,.fa-history:before{content:"\f1da"}.fa-clone:before{content:"\f24d"}.fa-closed-captioning:before{content:"\f20a"}.fa-cloud:before{content:"\f0c2"}.fa-cloud-arrow-down:before,.fa-cloud-download-alt:before,.fa-cloud-download:before{content:"\f0ed"}.fa-cloud-arrow-up:before,.fa-cloud-upload-alt:before,.fa-cloud-upload:before{content:"\f0ee"}.fa-cloud-meatball:before{content:"\f73b"}.fa-cloud-moon:before{content:"\f6c3"}.fa-cloud-moon-rain:before{content:"\f73c"}.fa-cloud-rain:before{content:"\f73d"}.fa-cloud-showers-heavy:before{content:"\f740"}.fa-cloud-sun:before{content:"\f6c4"}.fa-cloud-sun-rain:before{content:"\f743"}.fa-clover:before{content:"\e139"}.fa-code:before{content:"\f121"}.fa-code-branch:before{content:"\f126"}.fa-code-commit:before{content:"\f386"}.fa-code-compare:before{content:"\e13a"}.fa-code-fork:before{content:"\e13b"}.fa-code-merge:before{content:"\f387"}.fa-code-pull-request:before{content:"\e13c"}.fa-coins:before{content:"\f51e"}.fa-colon-sign:before{content:"\e140"}.fa-comment:before{content:"\f075"}.fa-comment-dollar:before{content:"\f651"}.fa-comment-dots:before,.fa-commenting:before{content:"\f4ad"}.fa-comment-medical:before{content:"\f7f5"}.fa-comment-slash:before{content:"\f4b3"}.fa-comment-sms:before,.fa-sms:before{content:"\f7cd"}.fa-comments:before{content:"\f086"}.fa-comments-dollar:before{content:"\f653"}.fa-compact-disc:before{content:"\f51f"}.fa-compass:before{content:"\f14e"}.fa-compass-drafting:before,.fa-drafting-compass:before{content:"\f568"}.fa-compress:before{content:"\f066"}.fa-computer-mouse:before,.fa-mouse:before{content:"\f8cc"}.fa-cookie:before{content:"\f563"}.fa-cookie-bite:before{content:"\f564"}.fa-copy:before{content:"\f0c5"}.fa-copyright:before{content:"\f1f9"}.fa-couch:before{content:"\f4b8"}.fa-credit-card-alt:before,.fa-credit-card:before{content:"\f09d"}.fa-crop:before{content:"\f125"}.fa-crop-alt:before,.fa-crop-simple:before{content:"\f565"}.fa-cross:before{content:"\f654"}.fa-crosshairs:before{content:"\f05b"}.fa-crow:before{content:"\f520"}.fa-crown:before{content:"\f521"}.fa-crutch:before{content:"\f7f7"}.fa-cruzeiro-sign:before{content:"\e152"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-d:before{content:"\44"}.fa-database:before{content:"\f1c0"}.fa-backspace:before,.fa-delete-left:before{content:"\f55a"}.fa-democrat:before{content:"\f747"}.fa-desktop-alt:before,.fa-desktop:before{content:"\f390"}.fa-dharmachakra:before{content:"\f655"}.fa-diagram-next:before{content:"\e476"}.fa-diagram-predecessor:before{content:"\e477"}.fa-diagram-project:before,.fa-project-diagram:before{content:"\f542"}.fa-diagram-successor:before{content:"\e47a"}.fa-diamond:before{content:"\f219"}.fa-diamond-turn-right:before,.fa-directions:before{content:"\f5eb"}.fa-dice:before{content:"\f522"}.fa-dice-d20:before{content:"\f6cf"}.fa-dice-d6:before{content:"\f6d1"}.fa-dice-five:before{content:"\f523"}.fa-dice-four:before{content:"\f524"}.fa-dice-one:before{content:"\f525"}.fa-dice-six:before{content:"\f526"}.fa-dice-three:before{content:"\f527"}.fa-dice-two:before{content:"\f528"}.fa-disease:before{content:"\f7fa"}.fa-divide:before{content:"\f529"}.fa-dna:before{content:"\f471"}.fa-dog:before{content:"\f6d3"}.fa-dollar-sign:before,.fa-dollar:before,.fa-usd:before{content:"\24"}.fa-dolly-box:before,.fa-dolly:before{content:"\f472"}.fa-dong-sign:before{content:"\e169"}.fa-door-closed:before{content:"\f52a"}.fa-door-open:before{content:"\f52b"}.fa-dove:before{content:"\f4ba"}.fa-compress-alt:before,.fa-down-left-and-up-right-to-center:before{content:"\f422"}.fa-down-long:before,.fa-long-arrow-alt-down:before{content:"\f309"}.fa-download:before{content:"\f019"}.fa-dragon:before{content:"\f6d5"}.fa-draw-polygon:before{content:"\f5ee"}.fa-droplet:before,.fa-tint:before{content:"\f043"}.fa-droplet-slash:before,.fa-tint-slash:before{content:"\f5c7"}.fa-drum:before{content:"\f569"}.fa-drum-steelpan:before{content:"\f56a"}.fa-drumstick-bite:before{content:"\f6d7"}.fa-dumbbell:before{content:"\f44b"}.fa-dumpster:before{content:"\f793"}.fa-dumpster-fire:before{content:"\f794"}.fa-dungeon:before{content:"\f6d9"}.fa-e:before{content:"\45"}.fa-deaf:before,.fa-deafness:before,.fa-ear-deaf:before,.fa-hard-of-hearing:before{content:"\f2a4"}.fa-assistive-listening-systems:before,.fa-ear-listen:before{content:"\f2a2"}.fa-earth-africa:before,.fa-globe-africa:before{content:"\f57c"}.fa-earth-america:before,.fa-earth-americas:before,.fa-earth:before,.fa-globe-americas:before{content:"\f57d"}.fa-earth-asia:before,.fa-globe-asia:before{content:"\f57e"}.fa-earth-europe:before,.fa-globe-europe:before{content:"\f7a2"}.fa-earth-oceania:before,.fa-globe-oceania:before{content:"\e47b"}.fa-egg:before{content:"\f7fb"}.fa-eject:before{content:"\f052"}.fa-elevator:before{content:"\e16d"}.fa-ellipsis-h:before,.fa-ellipsis:before{content:"\f141"}.fa-ellipsis-v:before,.fa-ellipsis-vertical:before{content:"\f142"}.fa-envelope:before{content:"\f0e0"}.fa-envelope-open:before{content:"\f2b6"}.fa-envelope-open-text:before{content:"\f658"}.fa-envelopes-bulk:before,.fa-mail-bulk:before{content:"\f674"}.fa-equals:before{content:"\3d"}.fa-eraser:before{content:"\f12d"}.fa-ethernet:before{content:"\f796"}.fa-eur:before,.fa-euro-sign:before,.fa-euro:before{content:"\f153"}.fa-exclamation:before{content:"\21"}.fa-expand:before{content:"\f065"}.fa-eye:before{content:"\f06e"}.fa-eye-dropper-empty:before,.fa-eye-dropper:before,.fa-eyedropper:before{content:"\f1fb"}.fa-eye-low-vision:before,.fa-low-vision:before{content:"\f2a8"}.fa-eye-slash:before{content:"\f070"}.fa-f:before{content:"\46"}.fa-angry:before,.fa-face-angry:before{content:"\f556"}.fa-dizzy:before,.fa-face-dizzy:before{content:"\f567"}.fa-face-flushed:before,.fa-flushed:before{content:"\f579"}.fa-face-frown:before,.fa-frown:before{content:"\f119"}.fa-face-frown-open:before,.fa-frown-open:before{content:"\f57a"}.fa-face-grimace:before,.fa-grimace:before{content:"\f57f"}.fa-face-grin:before,.fa-grin:before{content:"\f580"}.fa-face-grin-beam:before,.fa-grin-beam:before{content:"\f582"}.fa-face-grin-beam-sweat:before,.fa-grin-beam-sweat:before{content:"\f583"}.fa-face-grin-hearts:before,.fa-grin-hearts:before{content:"\f584"}.fa-face-grin-squint:before,.fa-grin-squint:before{content:"\f585"}.fa-face-grin-squint-tears:before,.fa-grin-squint-tears:before{content:"\f586"}.fa-face-grin-stars:before,.fa-grin-stars:before{content:"\f587"}.fa-face-grin-tears:before,.fa-grin-tears:before{content:"\f588"}.fa-face-grin-tongue:before,.fa-grin-tongue:before{content:"\f589"}.fa-face-grin-tongue-squint:before,.fa-grin-tongue-squint:before{content:"\f58a"}.fa-face-grin-tongue-wink:before,.fa-grin-tongue-wink:before{content:"\f58b"}.fa-face-grin-wide:before,.fa-grin-alt:before{content:"\f581"}.fa-face-grin-wink:before,.fa-grin-wink:before{content:"\f58c"}.fa-face-kiss:before,.fa-kiss:before{content:"\f596"}.fa-face-kiss-beam:before,.fa-kiss-beam:before{content:"\f597"}.fa-face-kiss-wink-heart:before,.fa-kiss-wink-heart:before{content:"\f598"}.fa-face-laugh:before,.fa-laugh:before{content:"\f599"}.fa-face-laugh-beam:before,.fa-laugh-beam:before{content:"\f59a"}.fa-face-laugh-squint:before,.fa-laugh-squint:before{content:"\f59b"}.fa-face-laugh-wink:before,.fa-laugh-wink:before{content:"\f59c"}.fa-face-meh:before,.fa-meh:before{content:"\f11a"}.fa-face-meh-blank:before,.fa-meh-blank:before{content:"\f5a4"}.fa-face-rolling-eyes:before,.fa-meh-rolling-eyes:before{content:"\f5a5"}.fa-face-sad-cry:before,.fa-sad-cry:before{content:"\f5b3"}.fa-face-sad-tear:before,.fa-sad-tear:before{content:"\f5b4"}.fa-face-smile:before,.fa-smile:before{content:"\f118"}.fa-face-smile-beam:before,.fa-smile-beam:before{content:"\f5b8"}.fa-face-smile-wink:before,.fa-smile-wink:before{content:"\f4da"}.fa-face-surprise:before,.fa-surprise:before{content:"\f5c2"}.fa-face-tired:before,.fa-tired:before{content:"\f5c8"}.fa-fan:before{content:"\f863"}.fa-faucet:before{content:"\e005"}.fa-fax:before{content:"\f1ac"}.fa-feather:before{content:"\f52d"}.fa-feather-alt:before,.fa-feather-pointed:before{content:"\f56b"}.fa-file:before{content:"\f15b"}.fa-file-arrow-down:before,.fa-file-download:before{content:"\f56d"}.fa-file-arrow-up:before,.fa-file-upload:before{content:"\f574"}.fa-file-audio:before{content:"\f1c7"}.fa-file-code:before{content:"\f1c9"}.fa-file-contract:before{content:"\f56c"}.fa-file-csv:before{content:"\f6dd"}.fa-file-excel:before{content:"\f1c3"}.fa-arrow-right-from-file:before,.fa-file-export:before{content:"\f56e"}.fa-file-image:before{content:"\f1c5"}.fa-arrow-right-to-file:before,.fa-file-import:before{content:"\f56f"}.fa-file-invoice:before{content:"\f570"}.fa-file-invoice-dollar:before{content:"\f571"}.fa-file-alt:before,.fa-file-lines:before,.fa-file-text:before{content:"\f15c"}.fa-file-medical:before{content:"\f477"}.fa-file-pdf:before{content:"\f1c1"}.fa-file-powerpoint:before{content:"\f1c4"}.fa-file-prescription:before{content:"\f572"}.fa-file-signature:before{content:"\f573"}.fa-file-video:before{content:"\f1c8"}.fa-file-medical-alt:before,.fa-file-waveform:before{content:"\f478"}.fa-file-word:before{content:"\f1c2"}.fa-file-archive:before,.fa-file-zipper:before{content:"\f1c6"}.fa-fill:before{content:"\f575"}.fa-fill-drip:before{content:"\f576"}.fa-film:before{content:"\f008"}.fa-filter:before{content:"\f0b0"}.fa-filter-circle-dollar:before,.fa-funnel-dollar:before{content:"\f662"}.fa-filter-circle-xmark:before{content:"\e17b"}.fa-fingerprint:before{content:"\f577"}.fa-fire:before{content:"\f06d"}.fa-fire-extinguisher:before{content:"\f134"}.fa-fire-alt:before,.fa-fire-flame-curved:before{content:"\f7e4"}.fa-burn:before,.fa-fire-flame-simple:before{content:"\f46a"}.fa-fish:before{content:"\f578"}.fa-flag:before{content:"\f024"}.fa-flag-checkered:before{content:"\f11e"}.fa-flag-usa:before{content:"\f74d"}.fa-flask:before{content:"\f0c3"}.fa-floppy-disk:before,.fa-save:before{content:"\f0c7"}.fa-florin-sign:before{content:"\e184"}.fa-folder:before{content:"\f07b"}.fa-folder-minus:before{content:"\f65d"}.fa-folder-open:before{content:"\f07c"}.fa-folder-plus:before{content:"\f65e"}.fa-folder-tree:before{content:"\f802"}.fa-font:before{content:"\f031"}.fa-football-ball:before,.fa-football:before{content:"\f44e"}.fa-forward:before{content:"\f04e"}.fa-fast-forward:before,.fa-forward-fast:before{content:"\f050"}.fa-forward-step:before,.fa-step-forward:before{content:"\f051"}.fa-franc-sign:before{content:"\e18f"}.fa-frog:before{content:"\f52e"}.fa-futbol-ball:before,.fa-futbol:before,.fa-soccer-ball:before{content:"\f1e3"}.fa-g:before{content:"\47"}.fa-gamepad:before{content:"\f11b"}.fa-gas-pump:before{content:"\f52f"}.fa-dashboard:before,.fa-gauge-med:before,.fa-gauge:before,.fa-tachometer-alt-average:before{content:"\f624"}.fa-gauge-high:before,.fa-tachometer-alt-fast:before,.fa-tachometer-alt:before{content:"\f625"}.fa-gauge-simple-med:before,.fa-gauge-simple:before,.fa-tachometer-average:before{content:"\f629"}.fa-gauge-simple-high:before,.fa-tachometer-fast:before,.fa-tachometer:before{content:"\f62a"}.fa-gavel:before,.fa-legal:before{content:"\f0e3"}.fa-cog:before,.fa-gear:before{content:"\f013"}.fa-cogs:before,.fa-gears:before{content:"\f085"}.fa-gem:before{content:"\f3a5"}.fa-genderless:before{content:"\f22d"}.fa-ghost:before{content:"\f6e2"}.fa-gift:before{content:"\f06b"}.fa-gifts:before{content:"\f79c"}.fa-glasses:before{content:"\f530"}.fa-globe:before{content:"\f0ac"}.fa-golf-ball-tee:before,.fa-golf-ball:before{content:"\f450"}.fa-gopuram:before{content:"\f664"}.fa-graduation-cap:before,.fa-mortar-board:before{content:"\f19d"}.fa-greater-than:before{content:"\3e"}.fa-greater-than-equal:before{content:"\f532"}.fa-grip-horizontal:before,.fa-grip:before{content:"\f58d"}.fa-grip-lines:before{content:"\f7a4"}.fa-grip-lines-vertical:before{content:"\f7a5"}.fa-grip-vertical:before{content:"\f58e"}.fa-guarani-sign:before{content:"\e19a"}.fa-guitar:before{content:"\f7a6"}.fa-gun:before{content:"\e19b"}.fa-h:before{content:"\48"}.fa-hammer:before{content:"\f6e3"}.fa-hamsa:before{content:"\f665"}.fa-hand-paper:before,.fa-hand:before{content:"\f256"}.fa-hand-back-fist:before,.fa-hand-rock:before{content:"\f255"}.fa-allergies:before,.fa-hand-dots:before{content:"\f461"}.fa-fist-raised:before,.fa-hand-fist:before{content:"\f6de"}.fa-hand-holding:before{content:"\f4bd"}.fa-hand-holding-dollar:before,.fa-hand-holding-usd:before{content:"\f4c0"}.fa-hand-holding-droplet:before,.fa-hand-holding-water:before{content:"\f4c1"}.fa-hand-holding-heart:before{content:"\f4be"}.fa-hand-holding-medical:before{content:"\e05c"}.fa-hand-lizard:before{content:"\f258"}.fa-hand-middle-finger:before{content:"\f806"}.fa-hand-peace:before{content:"\f25b"}.fa-hand-point-down:before{content:"\f0a7"}.fa-hand-point-left:before{content:"\f0a5"}.fa-hand-point-right:before{content:"\f0a4"}.fa-hand-point-up:before{content:"\f0a6"}.fa-hand-pointer:before{content:"\f25a"}.fa-hand-scissors:before{content:"\f257"}.fa-hand-sparkles:before{content:"\e05d"}.fa-hand-spock:before{content:"\f259"}.fa-hands:before,.fa-sign-language:before,.fa-signing:before{content:"\f2a7"}.fa-american-sign-language-interpreting:before,.fa-asl-interpreting:before,.fa-hands-american-sign-language-interpreting:before,.fa-hands-asl-interpreting:before{content:"\f2a3"}.fa-hands-bubbles:before,.fa-hands-wash:before{content:"\e05e"}.fa-hands-clapping:before{content:"\e1a8"}.fa-hands-holding:before{content:"\f4c2"}.fa-hands-praying:before,.fa-praying-hands:before{content:"\f684"}.fa-handshake:before{content:"\f2b5"}.fa-hands-helping:before,.fa-handshake-angle:before{content:"\f4c4"}.fa-handshake-alt-slash:before,.fa-handshake-simple-slash:before{content:"\e05f"}.fa-handshake-slash:before{content:"\e060"}.fa-hanukiah:before{content:"\f6e6"}.fa-hard-drive:before,.fa-hdd:before{content:"\f0a0"}.fa-hashtag:before{content:"\23"}.fa-hat-cowboy:before{content:"\f8c0"}.fa-hat-cowboy-side:before{content:"\f8c1"}.fa-hat-wizard:before{content:"\f6e8"}.fa-head-side-cough:before{content:"\e061"}.fa-head-side-cough-slash:before{content:"\e062"}.fa-head-side-mask:before{content:"\e063"}.fa-head-side-virus:before{content:"\e064"}.fa-header:before,.fa-heading:before{content:"\f1dc"}.fa-headphones:before{content:"\f025"}.fa-headphones-alt:before,.fa-headphones-simple:before{content:"\f58f"}.fa-headset:before{content:"\f590"}.fa-heart:before{content:"\f004"}.fa-heart-broken:before,.fa-heart-crack:before{content:"\f7a9"}.fa-heart-pulse:before,.fa-heartbeat:before{content:"\f21e"}.fa-helicopter:before{content:"\f533"}.fa-hard-hat:before,.fa-hat-hard:before,.fa-helmet-safety:before{content:"\f807"}.fa-highlighter:before{content:"\f591"}.fa-hippo:before{content:"\f6ed"}.fa-hockey-puck:before{content:"\f453"}.fa-holly-berry:before{content:"\f7aa"}.fa-horse:before{content:"\f6f0"}.fa-horse-head:before{content:"\f7ab"}.fa-hospital-alt:before,.fa-hospital-wide:before,.fa-hospital:before{content:"\f0f8"}.fa-hospital-user:before{content:"\f80d"}.fa-hot-tub-person:before,.fa-hot-tub:before{content:"\f593"}.fa-hotdog:before{content:"\f80f"}.fa-hotel:before{content:"\f594"}.fa-hourglass-2:before,.fa-hourglass-half:before,.fa-hourglass:before{content:"\f254"}.fa-hourglass-empty:before{content:"\f252"}.fa-hourglass-3:before,.fa-hourglass-end:before{content:"\f253"}.fa-hourglass-1:before,.fa-hourglass-start:before{content:"\f251"}.fa-home-alt:before,.fa-home-lg-alt:before,.fa-home:before,.fa-house:before{content:"\f015"}.fa-home-lg:before,.fa-house-chimney:before{content:"\e3af"}.fa-house-chimney-crack:before,.fa-house-damage:before{content:"\f6f1"}.fa-clinic-medical:before,.fa-house-chimney-medical:before{content:"\f7f2"}.fa-house-chimney-user:before{content:"\e065"}.fa-house-chimney-window:before{content:"\e00d"}.fa-house-crack:before{content:"\e3b1"}.fa-house-laptop:before,.fa-laptop-house:before{content:"\e066"}.fa-house-medical:before{content:"\e3b2"}.fa-home-user:before,.fa-house-user:before{content:"\e1b0"}.fa-hryvnia-sign:before,.fa-hryvnia:before{content:"\f6f2"}.fa-i:before{content:"\49"}.fa-i-cursor:before{content:"\f246"}.fa-ice-cream:before{content:"\f810"}.fa-icicles:before{content:"\f7ad"}.fa-heart-music-camera-bolt:before,.fa-icons:before{content:"\f86d"}.fa-id-badge:before{content:"\f2c1"}.fa-drivers-license:before,.fa-id-card:before{content:"\f2c2"}.fa-id-card-alt:before,.fa-id-card-clip:before{content:"\f47f"}.fa-igloo:before{content:"\f7ae"}.fa-image:before{content:"\f03e"}.fa-image-portrait:before,.fa-portrait:before{content:"\f3e0"}.fa-images:before{content:"\f302"}.fa-inbox:before{content:"\f01c"}.fa-indent:before{content:"\f03c"}.fa-indian-rupee-sign:before,.fa-indian-rupee:before,.fa-inr:before{content:"\e1bc"}.fa-industry:before{content:"\f275"}.fa-infinity:before{content:"\f534"}.fa-info:before{content:"\f129"}.fa-italic:before{content:"\f033"}.fa-j:before{content:"\4a"}.fa-jedi:before{content:"\f669"}.fa-fighter-jet:before,.fa-jet-fighter:before{content:"\f0fb"}.fa-joint:before{content:"\f595"}.fa-k:before{content:"\4b"}.fa-kaaba:before{content:"\f66b"}.fa-key:before{content:"\f084"}.fa-keyboard:before{content:"\f11c"}.fa-khanda:before{content:"\f66d"}.fa-kip-sign:before{content:"\e1c4"}.fa-first-aid:before,.fa-kit-medical:before{content:"\f479"}.fa-kiwi-bird:before{content:"\f535"}.fa-l:before{content:"\4c"}.fa-landmark:before{content:"\f66f"}.fa-language:before{content:"\f1ab"}.fa-laptop:before{content:"\f109"}.fa-laptop-code:before{content:"\f5fc"}.fa-laptop-medical:before{content:"\f812"}.fa-lari-sign:before{content:"\e1c8"}.fa-layer-group:before{content:"\f5fd"}.fa-leaf:before{content:"\f06c"}.fa-left-long:before,.fa-long-arrow-alt-left:before{content:"\f30a"}.fa-arrows-alt-h:before,.fa-left-right:before{content:"\f337"}.fa-lemon:before{content:"\f094"}.fa-less-than:before{content:"\3c"}.fa-less-than-equal:before{content:"\f537"}.fa-life-ring:before{content:"\f1cd"}.fa-lightbulb:before{content:"\f0eb"}.fa-chain:before,.fa-link:before{content:"\f0c1"}.fa-chain-broken:before,.fa-chain-slash:before,.fa-link-slash:before,.fa-unlink:before{content:"\f127"}.fa-lira-sign:before{content:"\f195"}.fa-list-squares:before,.fa-list:before{content:"\f03a"}.fa-list-check:before,.fa-tasks:before{content:"\f0ae"}.fa-list-1-2:before,.fa-list-numeric:before,.fa-list-ol:before{content:"\f0cb"}.fa-list-dots:before,.fa-list-ul:before{content:"\f0ca"}.fa-litecoin-sign:before{content:"\e1d3"}.fa-location-arrow:before{content:"\f124"}.fa-location-crosshairs:before,.fa-location:before{content:"\f601"}.fa-location-dot:before,.fa-map-marker-alt:before{content:"\f3c5"}.fa-location-pin:before,.fa-map-marker:before{content:"\f041"}.fa-lock:before{content:"\f023"}.fa-lock-open:before{content:"\f3c1"}.fa-lungs:before{content:"\f604"}.fa-lungs-virus:before{content:"\e067"}.fa-m:before{content:"\4d"}.fa-magnet:before{content:"\f076"}.fa-magnifying-glass:before,.fa-search:before{content:"\f002"}.fa-magnifying-glass-dollar:before,.fa-search-dollar:before{content:"\f688"}.fa-magnifying-glass-location:before,.fa-search-location:before{content:"\f689"}.fa-magnifying-glass-minus:before,.fa-search-minus:before{content:"\f010"}.fa-magnifying-glass-plus:before,.fa-search-plus:before{content:"\f00e"}.fa-manat-sign:before{content:"\e1d5"}.fa-map:before{content:"\f279"}.fa-map-location:before,.fa-map-marked:before{content:"\f59f"}.fa-map-location-dot:before,.fa-map-marked-alt:before{content:"\f5a0"}.fa-map-pin:before{content:"\f276"}.fa-marker:before{content:"\f5a1"}.fa-mars:before{content:"\f222"}.fa-mars-and-venus:before{content:"\f224"}.fa-mars-double:before{content:"\f227"}.fa-mars-stroke:before{content:"\f229"}.fa-mars-stroke-h:before,.fa-mars-stroke-right:before{content:"\f22b"}.fa-mars-stroke-up:before,.fa-mars-stroke-v:before{content:"\f22a"}.fa-glass-martini-alt:before,.fa-martini-glass:before{content:"\f57b"}.fa-cocktail:before,.fa-martini-glass-citrus:before{content:"\f561"}.fa-glass-martini:before,.fa-martini-glass-empty:before{content:"\f000"}.fa-mask:before{content:"\f6fa"}.fa-mask-face:before{content:"\e1d7"}.fa-masks-theater:before,.fa-theater-masks:before{content:"\f630"}.fa-expand-arrows-alt:before,.fa-maximize:before{content:"\f31e"}.fa-medal:before{content:"\f5a2"}.fa-memory:before{content:"\f538"}.fa-menorah:before{content:"\f676"}.fa-mercury:before{content:"\f223"}.fa-comment-alt:before,.fa-message:before{content:"\f27a"}.fa-meteor:before{content:"\f753"}.fa-microchip:before{content:"\f2db"}.fa-microphone:before{content:"\f130"}.fa-microphone-alt:before,.fa-microphone-lines:before{content:"\f3c9"}.fa-microphone-alt-slash:before,.fa-microphone-lines-slash:before{content:"\f539"}.fa-microphone-slash:before{content:"\f131"}.fa-microscope:before{content:"\f610"}.fa-mill-sign:before{content:"\e1ed"}.fa-compress-arrows-alt:before,.fa-minimize:before{content:"\f78c"}.fa-minus:before,.fa-subtract:before{content:"\f068"}.fa-mitten:before{content:"\f7b5"}.fa-mobile-android:before,.fa-mobile-phone:before,.fa-mobile:before{content:"\f3ce"}.fa-mobile-button:before{content:"\f10b"}.fa-mobile-alt:before,.fa-mobile-screen-button:before{content:"\f3cd"}.fa-money-bill:before{content:"\f0d6"}.fa-money-bill-1:before,.fa-money-bill-alt:before{content:"\f3d1"}.fa-money-bill-1-wave:before,.fa-money-bill-wave-alt:before{content:"\f53b"}.fa-money-bill-wave:before{content:"\f53a"}.fa-money-check:before{content:"\f53c"}.fa-money-check-alt:before,.fa-money-check-dollar:before{content:"\f53d"}.fa-monument:before{content:"\f5a6"}.fa-moon:before{content:"\f186"}.fa-mortar-pestle:before{content:"\f5a7"}.fa-mosque:before{content:"\f678"}.fa-motorcycle:before{content:"\f21c"}.fa-mountain:before{content:"\f6fc"}.fa-mug-hot:before{content:"\f7b6"}.fa-coffee:before,.fa-mug-saucer:before{content:"\f0f4"}.fa-music:before{content:"\f001"}.fa-n:before{content:"\4e"}.fa-naira-sign:before{content:"\e1f6"}.fa-network-wired:before{content:"\f6ff"}.fa-neuter:before{content:"\f22c"}.fa-newspaper:before{content:"\f1ea"}.fa-not-equal:before{content:"\f53e"}.fa-note-sticky:before,.fa-sticky-note:before{content:"\f249"}.fa-notes-medical:before{content:"\f481"}.fa-o:before{content:"\4f"}.fa-object-group:before{content:"\f247"}.fa-object-ungroup:before{content:"\f248"}.fa-oil-can:before{content:"\f613"}.fa-om:before{content:"\f679"}.fa-otter:before{content:"\f700"}.fa-dedent:before,.fa-outdent:before{content:"\f03b"}.fa-p:before{content:"\50"}.fa-pager:before{content:"\f815"}.fa-paint-roller:before{content:"\f5aa"}.fa-paint-brush:before,.fa-paintbrush:before{content:"\f1fc"}.fa-palette:before{content:"\f53f"}.fa-pallet:before{content:"\f482"}.fa-panorama:before{content:"\e209"}.fa-paper-plane:before{content:"\f1d8"}.fa-paperclip:before{content:"\f0c6"}.fa-parachute-box:before{content:"\f4cd"}.fa-paragraph:before{content:"\f1dd"}.fa-passport:before{content:"\f5ab"}.fa-file-clipboard:before,.fa-paste:before{content:"\f0ea"}.fa-pause:before{content:"\f04c"}.fa-paw:before{content:"\f1b0"}.fa-peace:before{content:"\f67c"}.fa-pen:before{content:"\f304"}.fa-pen-alt:before,.fa-pen-clip:before{content:"\f305"}.fa-pen-fancy:before{content:"\f5ac"}.fa-pen-nib:before{content:"\f5ad"}.fa-pen-ruler:before,.fa-pencil-ruler:before{content:"\f5ae"}.fa-edit:before,.fa-pen-to-square:before{content:"\f044"}.fa-pencil-alt:before,.fa-pencil:before{content:"\f303"}.fa-people-arrows-left-right:before,.fa-people-arrows:before{content:"\e068"}.fa-people-carry-box:before,.fa-people-carry:before{content:"\f4ce"}.fa-pepper-hot:before{content:"\f816"}.fa-percent:before,.fa-percentage:before{content:"\25"}.fa-male:before,.fa-person:before{content:"\f183"}.fa-biking:before,.fa-person-biking:before{content:"\f84a"}.fa-person-booth:before{content:"\f756"}.fa-diagnoses:before,.fa-person-dots-from-line:before{content:"\f470"}.fa-female:before,.fa-person-dress:before{content:"\f182"}.fa-hiking:before,.fa-person-hiking:before{content:"\f6ec"}.fa-person-praying:before,.fa-pray:before{content:"\f683"}.fa-person-running:before,.fa-running:before{content:"\f70c"}.fa-person-skating:before,.fa-skating:before{content:"\f7c5"}.fa-person-skiing:before,.fa-skiing:before{content:"\f7c9"}.fa-person-skiing-nordic:before,.fa-skiing-nordic:before{content:"\f7ca"}.fa-person-snowboarding:before,.fa-snowboarding:before{content:"\f7ce"}.fa-person-swimming:before,.fa-swimmer:before{content:"\f5c4"}.fa-person-walking:before,.fa-walking:before{content:"\f554"}.fa-blind:before,.fa-person-walking-with-cane:before{content:"\f29d"}.fa-peseta-sign:before{content:"\e221"}.fa-peso-sign:before{content:"\e222"}.fa-phone:before{content:"\f095"}.fa-phone-alt:before,.fa-phone-flip:before{content:"\f879"}.fa-phone-slash:before{content:"\f3dd"}.fa-phone-volume:before,.fa-volume-control-phone:before{content:"\f2a0"}.fa-photo-film:before,.fa-photo-video:before{content:"\f87c"}.fa-piggy-bank:before{content:"\f4d3"}.fa-pills:before{content:"\f484"}.fa-pizza-slice:before{content:"\f818"}.fa-place-of-worship:before{content:"\f67f"}.fa-plane:before{content:"\f072"}.fa-plane-arrival:before{content:"\f5af"}.fa-plane-departure:before{content:"\f5b0"}.fa-plane-slash:before{content:"\e069"}.fa-play:before{content:"\f04b"}.fa-plug:before{content:"\f1e6"}.fa-add:before,.fa-plus:before{content:"\2b"}.fa-plus-minus:before{content:"\e43c"}.fa-podcast:before{content:"\f2ce"}.fa-poo:before{content:"\f2fe"}.fa-poo-bolt:before,.fa-poo-storm:before{content:"\f75a"}.fa-poop:before{content:"\f619"}.fa-power-off:before{content:"\f011"}.fa-prescription:before{content:"\f5b1"}.fa-prescription-bottle:before{content:"\f485"}.fa-prescription-bottle-alt:before,.fa-prescription-bottle-medical:before{content:"\f486"}.fa-print:before{content:"\f02f"}.fa-pump-medical:before{content:"\e06a"}.fa-pump-soap:before{content:"\e06b"}.fa-puzzle-piece:before{content:"\f12e"}.fa-q:before{content:"\51"}.fa-qrcode:before{content:"\f029"}.fa-question:before{content:"\3f"}.fa-quote-left-alt:before,.fa-quote-left:before{content:"\f10d"}.fa-quote-right-alt:before,.fa-quote-right:before{content:"\f10e"}.fa-r:before{content:"\52"}.fa-radiation:before{content:"\f7b9"}.fa-rainbow:before{content:"\f75b"}.fa-receipt:before{content:"\f543"}.fa-record-vinyl:before{content:"\f8d9"}.fa-ad:before,.fa-rectangle-ad:before{content:"\f641"}.fa-list-alt:before,.fa-rectangle-list:before{content:"\f022"}.fa-rectangle-times:before,.fa-rectangle-xmark:before,.fa-times-rectangle:before,.fa-window-close:before{content:"\f410"}.fa-recycle:before{content:"\f1b8"}.fa-registered:before{content:"\f25d"}.fa-repeat:before{content:"\f363"}.fa-mail-reply:before,.fa-reply:before{content:"\f3e5"}.fa-mail-reply-all:before,.fa-reply-all:before{content:"\f122"}.fa-republican:before{content:"\f75e"}.fa-restroom:before{content:"\f7bd"}.fa-retweet:before{content:"\f079"}.fa-ribbon:before{content:"\f4d6"}.fa-right-from-bracket:before,.fa-sign-out-alt:before{content:"\f2f5"}.fa-exchange-alt:before,.fa-right-left:before{content:"\f362"}.fa-long-arrow-alt-right:before,.fa-right-long:before{content:"\f30b"}.fa-right-to-bracket:before,.fa-sign-in-alt:before{content:"\f2f6"}.fa-ring:before{content:"\f70b"}.fa-road:before{content:"\f018"}.fa-robot:before{content:"\f544"}.fa-rocket:before{content:"\f135"}.fa-rotate:before,.fa-sync-alt:before{content:"\f2f1"}.fa-rotate-back:before,.fa-rotate-backward:before,.fa-rotate-left:before,.fa-undo-alt:before{content:"\f2ea"}.fa-redo-alt:before,.fa-rotate-forward:before,.fa-rotate-right:before{content:"\f2f9"}.fa-route:before{content:"\f4d7"}.fa-feed:before,.fa-rss:before{content:"\f09e"}.fa-rouble:before,.fa-rub:before,.fa-ruble-sign:before,.fa-ruble:before{content:"\f158"}.fa-ruler:before{content:"\f545"}.fa-ruler-combined:before{content:"\f546"}.fa-ruler-horizontal:before{content:"\f547"}.fa-ruler-vertical:before{content:"\f548"}.fa-rupee-sign:before,.fa-rupee:before{content:"\f156"}.fa-rupiah-sign:before{content:"\e23d"}.fa-s:before{content:"\53"}.fa-sailboat:before{content:"\e445"}.fa-satellite:before{content:"\f7bf"}.fa-satellite-dish:before{content:"\f7c0"}.fa-balance-scale:before,.fa-scale-balanced:before{content:"\f24e"}.fa-balance-scale-left:before,.fa-scale-unbalanced:before{content:"\f515"}.fa-balance-scale-right:before,.fa-scale-unbalanced-flip:before{content:"\f516"}.fa-school:before{content:"\f549"}.fa-cut:before,.fa-scissors:before{content:"\f0c4"}.fa-screwdriver:before{content:"\f54a"}.fa-screwdriver-wrench:before,.fa-tools:before{content:"\f7d9"}.fa-scroll:before{content:"\f70e"}.fa-scroll-torah:before,.fa-torah:before{content:"\f6a0"}.fa-sd-card:before{content:"\f7c2"}.fa-section:before{content:"\e447"}.fa-seedling:before,.fa-sprout:before{content:"\f4d8"}.fa-server:before{content:"\f233"}.fa-shapes:before,.fa-triangle-circle-square:before{content:"\f61f"}.fa-arrow-turn-right:before,.fa-mail-forward:before,.fa-share:before{content:"\f064"}.fa-share-from-square:before,.fa-share-square:before{content:"\f14d"}.fa-share-alt:before,.fa-share-nodes:before{content:"\f1e0"}.fa-ils:before,.fa-shekel-sign:before,.fa-shekel:before,.fa-sheqel-sign:before,.fa-sheqel:before{content:"\f20b"}.fa-shield:before{content:"\f132"}.fa-shield-alt:before,.fa-shield-blank:before{content:"\f3ed"}.fa-shield-virus:before{content:"\e06c"}.fa-ship:before{content:"\f21a"}.fa-shirt:before,.fa-t-shirt:before,.fa-tshirt:before{content:"\f553"}.fa-shoe-prints:before{content:"\f54b"}.fa-shop:before,.fa-store-alt:before{content:"\f54f"}.fa-shop-slash:before,.fa-store-alt-slash:before{content:"\e070"}.fa-shower:before{content:"\f2cc"}.fa-shrimp:before{content:"\e448"}.fa-random:before,.fa-shuffle:before{content:"\f074"}.fa-shuttle-space:before,.fa-space-shuttle:before{content:"\f197"}.fa-sign-hanging:before,.fa-sign:before{content:"\f4d9"}.fa-signal-5:before,.fa-signal-perfect:before,.fa-signal:before{content:"\f012"}.fa-signature:before{content:"\f5b7"}.fa-map-signs:before,.fa-signs-post:before{content:"\f277"}.fa-sim-card:before{content:"\f7c4"}.fa-sink:before{content:"\e06d"}.fa-sitemap:before{content:"\f0e8"}.fa-skull:before{content:"\f54c"}.fa-skull-crossbones:before{content:"\f714"}.fa-slash:before{content:"\f715"}.fa-sleigh:before{content:"\f7cc"}.fa-sliders-h:before,.fa-sliders:before{content:"\f1de"}.fa-smog:before{content:"\f75f"}.fa-smoking:before{content:"\f48d"}.fa-snowflake:before{content:"\f2dc"}.fa-snowman:before{content:"\f7d0"}.fa-snowplow:before{content:"\f7d2"}.fa-soap:before{content:"\e06e"}.fa-socks:before{content:"\f696"}.fa-solar-panel:before{content:"\f5ba"}.fa-sort:before,.fa-unsorted:before{content:"\f0dc"}.fa-sort-desc:before,.fa-sort-down:before{content:"\f0dd"}.fa-sort-asc:before,.fa-sort-up:before{content:"\f0de"}.fa-spa:before{content:"\f5bb"}.fa-pastafarianism:before,.fa-spaghetti-monster-flying:before{content:"\f67b"}.fa-spell-check:before{content:"\f891"}.fa-spider:before{content:"\f717"}.fa-spinner:before{content:"\f110"}.fa-splotch:before{content:"\f5bc"}.fa-spoon:before,.fa-utensil-spoon:before{content:"\f2e5"}.fa-spray-can:before{content:"\f5bd"}.fa-air-freshener:before,.fa-spray-can-sparkles:before{content:"\f5d0"}.fa-square:before{content:"\f0c8"}.fa-external-link-square:before,.fa-square-arrow-up-right:before{content:"\f14c"}.fa-caret-square-down:before,.fa-square-caret-down:before{content:"\f150"}.fa-caret-square-left:before,.fa-square-caret-left:before{content:"\f191"}.fa-caret-square-right:before,.fa-square-caret-right:before{content:"\f152"}.fa-caret-square-up:before,.fa-square-caret-up:before{content:"\f151"}.fa-check-square:before,.fa-square-check:before{content:"\f14a"}.fa-envelope-square:before,.fa-square-envelope:before{content:"\f199"}.fa-square-full:before{content:"\f45c"}.fa-h-square:before,.fa-square-h:before{content:"\f0fd"}.fa-minus-square:before,.fa-square-minus:before{content:"\f146"}.fa-parking:before,.fa-square-parking:before{content:"\f540"}.fa-pen-square:before,.fa-pencil-square:before,.fa-square-pen:before{content:"\f14b"}.fa-phone-square:before,.fa-square-phone:before{content:"\f098"}.fa-phone-square-alt:before,.fa-square-phone-flip:before{content:"\f87b"}.fa-plus-square:before,.fa-square-plus:before{content:"\f0fe"}.fa-poll-h:before,.fa-square-poll-horizontal:before{content:"\f682"}.fa-poll:before,.fa-square-poll-vertical:before{content:"\f681"}.fa-square-root-alt:before,.fa-square-root-variable:before{content:"\f698"}.fa-rss-square:before,.fa-square-rss:before{content:"\f143"}.fa-share-alt-square:before,.fa-square-share-nodes:before{content:"\f1e1"}.fa-external-link-square-alt:before,.fa-square-up-right:before{content:"\f360"}.fa-square-xmark:before,.fa-times-square:before,.fa-xmark-square:before{content:"\f2d3"}.fa-stairs:before{content:"\e289"}.fa-stamp:before{content:"\f5bf"}.fa-star:before{content:"\f005"}.fa-star-and-crescent:before{content:"\f699"}.fa-star-half:before{content:"\f089"}.fa-star-half-alt:before,.fa-star-half-stroke:before{content:"\f5c0"}.fa-star-of-david:before{content:"\f69a"}.fa-star-of-life:before{content:"\f621"}.fa-gbp:before,.fa-pound-sign:before,.fa-sterling-sign:before{content:"\f154"}.fa-stethoscope:before{content:"\f0f1"}.fa-stop:before{content:"\f04d"}.fa-stopwatch:before{content:"\f2f2"}.fa-stopwatch-20:before{content:"\e06f"}.fa-store:before{content:"\f54e"}.fa-store-slash:before{content:"\e071"}.fa-street-view:before{content:"\f21d"}.fa-strikethrough:before{content:"\f0cc"}.fa-stroopwafel:before{content:"\f551"}.fa-subscript:before{content:"\f12c"}.fa-suitcase:before{content:"\f0f2"}.fa-medkit:before,.fa-suitcase-medical:before{content:"\f0fa"}.fa-suitcase-rolling:before{content:"\f5c1"}.fa-sun:before{content:"\f185"}.fa-superscript:before{content:"\f12b"}.fa-swatchbook:before{content:"\f5c3"}.fa-synagogue:before{content:"\f69b"}.fa-syringe:before{content:"\f48e"}.fa-t:before{content:"\54"}.fa-table:before{content:"\f0ce"}.fa-table-cells:before,.fa-th:before{content:"\f00a"}.fa-table-cells-large:before,.fa-th-large:before{content:"\f009"}.fa-columns:before,.fa-table-columns:before{content:"\f0db"}.fa-table-list:before,.fa-th-list:before{content:"\f00b"}.fa-ping-pong-paddle-ball:before,.fa-table-tennis-paddle-ball:before,.fa-table-tennis:before{content:"\f45d"}.fa-tablet-android:before,.fa-tablet:before{content:"\f3fb"}.fa-tablet-button:before{content:"\f10a"}.fa-tablet-alt:before,.fa-tablet-screen-button:before{content:"\f3fa"}.fa-tablets:before{content:"\f490"}.fa-digital-tachograph:before,.fa-tachograph-digital:before{content:"\f566"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-tape:before{content:"\f4db"}.fa-cab:before,.fa-taxi:before{content:"\f1ba"}.fa-teeth:before{content:"\f62e"}.fa-teeth-open:before{content:"\f62f"}.fa-temperature-0:before,.fa-temperature-empty:before,.fa-thermometer-0:before,.fa-thermometer-empty:before{content:"\f2cb"}.fa-temperature-4:before,.fa-temperature-full:before,.fa-thermometer-4:before,.fa-thermometer-full:before{content:"\f2c7"}.fa-temperature-2:before,.fa-temperature-half:before,.fa-thermometer-2:before,.fa-thermometer-half:before{content:"\f2c9"}.fa-temperature-high:before{content:"\f769"}.fa-temperature-low:before{content:"\f76b"}.fa-temperature-1:before,.fa-temperature-quarter:before,.fa-thermometer-1:before,.fa-thermometer-quarter:before{content:"\f2ca"}.fa-temperature-3:before,.fa-temperature-three-quarters:before,.fa-thermometer-3:before,.fa-thermometer-three-quarters:before{content:"\f2c8"}.fa-tenge-sign:before,.fa-tenge:before{content:"\f7d7"}.fa-terminal:before{content:"\f120"}.fa-text-height:before{content:"\f034"}.fa-remove-format:before,.fa-text-slash:before{content:"\f87d"}.fa-text-width:before{content:"\f035"}.fa-thermometer:before{content:"\f491"}.fa-thumbs-down:before{content:"\f165"}.fa-thumbs-up:before{content:"\f164"}.fa-thumb-tack:before,.fa-thumbtack:before{content:"\f08d"}.fa-ticket:before{content:"\f145"}.fa-ticket-alt:before,.fa-ticket-simple:before{content:"\f3ff"}.fa-timeline:before{content:"\e29c"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-toilet:before{content:"\f7d8"}.fa-toilet-paper:before{content:"\f71e"}.fa-toilet-paper-slash:before{content:"\e072"}.fa-toolbox:before{content:"\f552"}.fa-tooth:before{content:"\f5c9"}.fa-torii-gate:before{content:"\f6a1"}.fa-broadcast-tower:before,.fa-tower-broadcast:before{content:"\f519"}.fa-tractor:before{content:"\f722"}.fa-trademark:before{content:"\f25c"}.fa-traffic-light:before{content:"\f637"}.fa-trailer:before{content:"\e041"}.fa-train:before{content:"\f238"}.fa-subway:before,.fa-train-subway:before{content:"\f239"}.fa-train-tram:before,.fa-tram:before{content:"\f7da"}.fa-transgender-alt:before,.fa-transgender:before{content:"\f225"}.fa-trash:before{content:"\f1f8"}.fa-trash-arrow-up:before,.fa-trash-restore:before{content:"\f829"}.fa-trash-alt:before,.fa-trash-can:before{content:"\f2ed"}.fa-trash-can-arrow-up:before,.fa-trash-restore-alt:before{content:"\f82a"}.fa-tree:before{content:"\f1bb"}.fa-exclamation-triangle:before,.fa-triangle-exclamation:before,.fa-warning:before{content:"\f071"}.fa-trophy:before{content:"\f091"}.fa-truck:before{content:"\f0d1"}.fa-shipping-fast:before,.fa-truck-fast:before{content:"\f48b"}.fa-ambulance:before,.fa-truck-medical:before{content:"\f0f9"}.fa-truck-monster:before{content:"\f63b"}.fa-truck-moving:before{content:"\f4df"}.fa-truck-pickup:before{content:"\f63c"}.fa-truck-loading:before,.fa-truck-ramp-box:before{content:"\f4de"}.fa-teletype:before,.fa-tty:before{content:"\f1e4"}.fa-try:before,.fa-turkish-lira-sign:before,.fa-turkish-lira:before{content:"\e2bb"}.fa-level-down-alt:before,.fa-turn-down:before{content:"\f3be"}.fa-level-up-alt:before,.fa-turn-up:before{content:"\f3bf"}.fa-television:before,.fa-tv-alt:before,.fa-tv:before{content:"\f26c"}.fa-u:before{content:"\55"}.fa-umbrella:before{content:"\f0e9"}.fa-umbrella-beach:before{content:"\f5ca"}.fa-underline:before{content:"\f0cd"}.fa-universal-access:before{content:"\f29a"}.fa-unlock:before{content:"\f09c"}.fa-unlock-alt:before,.fa-unlock-keyhole:before{content:"\f13e"}.fa-arrows-alt-v:before,.fa-up-down:before{content:"\f338"}.fa-arrows-alt:before,.fa-up-down-left-right:before{content:"\f0b2"}.fa-long-arrow-alt-up:before,.fa-up-long:before{content:"\f30c"}.fa-expand-alt:before,.fa-up-right-and-down-left-from-center:before{content:"\f424"}.fa-external-link-alt:before,.fa-up-right-from-square:before{content:"\f35d"}.fa-upload:before{content:"\f093"}.fa-user:before{content:"\f007"}.fa-user-astronaut:before{content:"\f4fb"}.fa-user-check:before{content:"\f4fc"}.fa-user-clock:before{content:"\f4fd"}.fa-user-doctor:before,.fa-user-md:before{content:"\f0f0"}.fa-user-cog:before,.fa-user-gear:before{content:"\f4fe"}.fa-user-graduate:before{content:"\f501"}.fa-user-friends:before,.fa-user-group:before{content:"\f500"}.fa-user-injured:before{content:"\f728"}.fa-user-alt:before,.fa-user-large:before{content:"\f406"}.fa-user-alt-slash:before,.fa-user-large-slash:before{content:"\f4fa"}.fa-user-lock:before{content:"\f502"}.fa-user-minus:before{content:"\f503"}.fa-user-ninja:before{content:"\f504"}.fa-user-nurse:before{content:"\f82f"}.fa-user-edit:before,.fa-user-pen:before{content:"\f4ff"}.fa-user-plus:before{content:"\f234"}.fa-user-secret:before{content:"\f21b"}.fa-user-shield:before{content:"\f505"}.fa-user-slash:before{content:"\f506"}.fa-user-tag:before{content:"\f507"}.fa-user-tie:before{content:"\f508"}.fa-user-times:before,.fa-user-xmark:before{content:"\f235"}.fa-users:before{content:"\f0c0"}.fa-users-cog:before,.fa-users-gear:before{content:"\f509"}.fa-users-slash:before{content:"\e073"}.fa-cutlery:before,.fa-utensils:before{content:"\f2e7"}.fa-v:before{content:"\56"}.fa-shuttle-van:before,.fa-van-shuttle:before{content:"\f5b6"}.fa-vault:before{content:"\e2c5"}.fa-vector-square:before{content:"\f5cb"}.fa-venus:before{content:"\f221"}.fa-venus-double:before{content:"\f226"}.fa-venus-mars:before{content:"\f228"}.fa-vest:before{content:"\e085"}.fa-vest-patches:before{content:"\e086"}.fa-vial:before{content:"\f492"}.fa-vials:before{content:"\f493"}.fa-video-camera:before,.fa-video:before{content:"\f03d"}.fa-video-slash:before{content:"\f4e2"}.fa-vihara:before{content:"\f6a7"}.fa-virus:before{content:"\e074"}.fa-virus-covid:before{content:"\e4a8"}.fa-virus-covid-slash:before{content:"\e4a9"}.fa-virus-slash:before{content:"\e075"}.fa-viruses:before{content:"\e076"}.fa-voicemail:before{content:"\f897"}.fa-volleyball-ball:before,.fa-volleyball:before{content:"\f45f"}.fa-volume-high:before,.fa-volume-up:before{content:"\f028"}.fa-volume-down:before,.fa-volume-low:before{content:"\f027"}.fa-volume-off:before{content:"\f026"}.fa-volume-mute:before,.fa-volume-times:before,.fa-volume-xmark:before{content:"\f6a9"}.fa-vr-cardboard:before{content:"\f729"}.fa-w:before{content:"\57"}.fa-wallet:before{content:"\f555"}.fa-magic:before,.fa-wand-magic:before{content:"\f0d0"}.fa-magic-wand-sparkles:before,.fa-wand-magic-sparkles:before{content:"\e2ca"}.fa-wand-sparkles:before{content:"\f72b"}.fa-warehouse:before{content:"\f494"}.fa-water:before{content:"\f773"}.fa-ladder-water:before,.fa-swimming-pool:before,.fa-water-ladder:before{content:"\f5c5"}.fa-wave-square:before{content:"\f83e"}.fa-weight-hanging:before{content:"\f5cd"}.fa-weight-scale:before,.fa-weight:before{content:"\f496"}.fa-wheelchair:before{content:"\f193"}.fa-glass-whiskey:before,.fa-whiskey-glass:before{content:"\f7a0"}.fa-wifi-3:before,.fa-wifi-strong:before,.fa-wifi:before{content:"\f1eb"}.fa-wind:before{content:"\f72e"}.fa-window-maximize:before{content:"\f2d0"}.fa-window-minimize:before{content:"\f2d1"}.fa-window-restore:before{content:"\f2d2"}.fa-wine-bottle:before{content:"\f72f"}.fa-wine-glass:before{content:"\f4e3"}.fa-wine-glass-alt:before,.fa-wine-glass-empty:before{content:"\f5ce"}.fa-krw:before,.fa-won-sign:before,.fa-won:before{content:"\f159"}.fa-wrench:before{content:"\f0ad"}.fa-x:before{content:"\58"}.fa-x-ray:before{content:"\f497"}.fa-close:before,.fa-multiply:before,.fa-remove:before,.fa-times:before,.fa-xmark:before{content:"\f00d"}.fa-y:before{content:"\59"}.fa-cny:before,.fa-jpy:before,.fa-rmb:before,.fa-yen-sign:before,.fa-yen:before{content:"\f157"}.fa-yin-yang:before{content:"\f6ad"}.fa-z:before{content:"\5a"}.fa-sr-only,.fa-sr-only-focusable:not(:focus),.sr-only,.sr-only-focusable:not(:focus){position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border-width:0}:host,:root{--fa-font-brands:normal 400 1em/1 "Font Awesome 6 Brands"}@font-face{font-family:"Font Awesome 6 Brands";font-style:normal;font-weight:400;font-display:block;src:url(../css/webfonts/fa-brands-400.woff2) format("woff2"),url(../css/webfonts/fa-brands-400.ttf) format("truetype")}.fa-brands,.fab{font-family:"Font Awesome 6 Brands";font-weight:400}.fa-42-group:before,.fa-innosoft:before{content:"\e080"}.fa-500px:before{content:"\f26e"}.fa-accessible-icon:before{content:"\f368"}.fa-accusoft:before{content:"\f369"}.fa-adn:before{content:"\f170"}.fa-adversal:before{content:"\f36a"}.fa-affiliatetheme:before{content:"\f36b"}.fa-airbnb:before{content:"\f834"}.fa-algolia:before{content:"\f36c"}.fa-alipay:before{content:"\f642"}.fa-amazon:before{content:"\f270"}.fa-amazon-pay:before{content:"\f42c"}.fa-amilia:before{content:"\f36d"}.fa-android:before{content:"\f17b"}.fa-angellist:before{content:"\f209"}.fa-angrycreative:before{content:"\f36e"}.fa-angular:before{content:"\f420"}.fa-app-store:before{content:"\f36f"}.fa-app-store-ios:before{content:"\f370"}.fa-apper:before{content:"\f371"}.fa-apple:before{content:"\f179"}.fa-apple-pay:before{content:"\f415"}.fa-artstation:before{content:"\f77a"}.fa-asymmetrik:before{content:"\f372"}.fa-atlassian:before{content:"\f77b"}.fa-audible:before{content:"\f373"}.fa-autoprefixer:before{content:"\f41c"}.fa-avianex:before{content:"\f374"}.fa-aviato:before{content:"\f421"}.fa-aws:before{content:"\f375"}.fa-bandcamp:before{content:"\f2d5"}.fa-battle-net:before{content:"\f835"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-bilibili:before{content:"\e3d9"}.fa-bimobject:before{content:"\f378"}.fa-bitbucket:before{content:"\f171"}.fa-bitcoin:before{content:"\f379"}.fa-bity:before{content:"\f37a"}.fa-black-tie:before{content:"\f27e"}.fa-blackberry:before{content:"\f37b"}.fa-blogger:before{content:"\f37c"}.fa-blogger-b:before{content:"\f37d"}.fa-bluetooth:before{content:"\f293"}.fa-bluetooth-b:before{content:"\f294"}.fa-bootstrap:before{content:"\f836"}.fa-bots:before{content:"\e340"}.fa-btc:before{content:"\f15a"}.fa-buffer:before{content:"\f837"}.fa-buromobelexperte:before{content:"\f37f"}.fa-buy-n-large:before{content:"\f8a6"}.fa-buysellads:before{content:"\f20d"}.fa-canadian-maple-leaf:before{content:"\f785"}.fa-cc-amazon-pay:before{content:"\f42d"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-apple-pay:before{content:"\f416"}.fa-cc-diners-club:before{content:"\f24c"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-jcb:before{content:"\f24b"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-cc-visa:before{content:"\f1f0"}.fa-centercode:before{content:"\f380"}.fa-centos:before{content:"\f789"}.fa-chrome:before{content:"\f268"}.fa-chromecast:before{content:"\f838"}.fa-cloudflare:before{content:"\e07d"}.fa-cloudscale:before{content:"\f383"}.fa-cloudsmith:before{content:"\f384"}.fa-cloudversify:before{content:"\f385"}.fa-cmplid:before{content:"\e360"}.fa-codepen:before{content:"\f1cb"}.fa-codiepie:before{content:"\f284"}.fa-confluence:before{content:"\f78d"}.fa-connectdevelop:before{content:"\f20e"}.fa-contao:before{content:"\f26d"}.fa-cotton-bureau:before{content:"\f89e"}.fa-cpanel:before{content:"\f388"}.fa-creative-commons:before{content:"\f25e"}.fa-creative-commons-by:before{content:"\f4e7"}.fa-creative-commons-nc:before{content:"\f4e8"}.fa-creative-commons-nc-eu:before{content:"\f4e9"}.fa-creative-commons-nc-jp:before{content:"\f4ea"}.fa-creative-commons-nd:before{content:"\f4eb"}.fa-creative-commons-pd:before{content:"\f4ec"}.fa-creative-commons-pd-alt:before{content:"\f4ed"}.fa-creative-commons-remix:before{content:"\f4ee"}.fa-creative-commons-sa:before{content:"\f4ef"}.fa-creative-commons-sampling:before{content:"\f4f0"}.fa-creative-commons-sampling-plus:before{content:"\f4f1"}.fa-creative-commons-share:before{content:"\f4f2"}.fa-creative-commons-zero:before{content:"\f4f3"}.fa-critical-role:before{content:"\f6c9"}.fa-css3:before{content:"\f13c"}.fa-css3-alt:before{content:"\f38b"}.fa-cuttlefish:before{content:"\f38c"}.fa-d-and-d:before{content:"\f38d"}.fa-d-and-d-beyond:before{content:"\f6ca"}.fa-dailymotion:before{content:"\e052"}.fa-dashcube:before{content:"\f210"}.fa-deezer:before{content:"\e077"}.fa-delicious:before{content:"\f1a5"}.fa-deploydog:before{content:"\f38e"}.fa-deskpro:before{content:"\f38f"}.fa-dev:before{content:"\f6cc"}.fa-deviantart:before{content:"\f1bd"}.fa-dhl:before{content:"\f790"}.fa-diaspora:before{content:"\f791"}.fa-digg:before{content:"\f1a6"}.fa-digital-ocean:before{content:"\f391"}.fa-discord:before{content:"\f392"}.fa-discourse:before{content:"\f393"}.fa-dochub:before{content:"\f394"}.fa-docker:before{content:"\f395"}.fa-draft2digital:before{content:"\f396"}.fa-dribbble:before{content:"\f17d"}.fa-dribbble-square:before{content:"\f397"}.fa-dropbox:before{content:"\f16b"}.fa-drupal:before{content:"\f1a9"}.fa-dyalog:before{content:"\f399"}.fa-earlybirds:before{content:"\f39a"}.fa-ebay:before{content:"\f4f4"}.fa-edge:before{content:"\f282"}.fa-edge-legacy:before{content:"\e078"}.fa-elementor:before{content:"\f430"}.fa-ello:before{content:"\f5f1"}.fa-ember:before{content:"\f423"}.fa-empire:before{content:"\f1d1"}.fa-envira:before{content:"\f299"}.fa-erlang:before{content:"\f39d"}.fa-ethereum:before{content:"\f42e"}.fa-etsy:before{content:"\f2d7"}.fa-evernote:before{content:"\f839"}.fa-expeditedssl:before{content:"\f23e"}.fa-facebook:before{content:"\f09a"}.fa-facebook-f:before{content:"\f39e"}.fa-facebook-messenger:before{content:"\f39f"}.fa-facebook-square:before{content:"\f082"}.fa-fantasy-flight-games:before{content:"\f6dc"}.fa-fedex:before{content:"\f797"}.fa-fedora:before{content:"\f798"}.fa-figma:before{content:"\f799"}.fa-firefox:before{content:"\f269"}.fa-firefox-browser:before{content:"\e007"}.fa-first-order:before{content:"\f2b0"}.fa-first-order-alt:before{content:"\f50a"}.fa-firstdraft:before{content:"\f3a1"}.fa-flickr:before{content:"\f16e"}.fa-flipboard:before{content:"\f44d"}.fa-fly:before{content:"\f417"}.fa-font-awesome-flag:before,.fa-font-awesome-logo-full:before,.fa-font-awesome:before{content:"\f2b4"}.fa-fonticons:before{content:"\f280"}.fa-fonticons-fi:before{content:"\f3a2"}.fa-fort-awesome:before{content:"\f286"}.fa-fort-awesome-alt:before{content:"\f3a3"}.fa-forumbee:before{content:"\f211"}.fa-foursquare:before{content:"\f180"}.fa-free-code-camp:before{content:"\f2c5"}.fa-freebsd:before{content:"\f3a4"}.fa-fulcrum:before{content:"\f50b"}.fa-galactic-republic:before{content:"\f50c"}.fa-galactic-senate:before{content:"\f50d"}.fa-get-pocket:before{content:"\f265"}.fa-gg:before{content:"\f260"}.fa-gg-circle:before{content:"\f261"}.fa-git:before{content:"\f1d3"}.fa-git-alt:before{content:"\f841"}.fa-git-square:before{content:"\f1d2"}.fa-github:before{content:"\f09b"}.fa-github-alt:before{content:"\f113"}.fa-github-square:before{content:"\f092"}.fa-gitkraken:before{content:"\f3a6"}.fa-gitlab:before{content:"\f296"}.fa-gitter:before{content:"\f426"}.fa-glide:before{content:"\f2a5"}.fa-glide-g:before{content:"\f2a6"}.fa-gofore:before{content:"\f3a7"}.fa-golang:before{content:"\e40f"}.fa-goodreads:before{content:"\f3a8"}.fa-goodreads-g:before{content:"\f3a9"}.fa-google:before{content:"\f1a0"}.fa-google-drive:before{content:"\f3aa"}.fa-google-pay:before{content:"\e079"}.fa-google-play:before{content:"\f3ab"}.fa-google-plus:before{content:"\f2b3"}.fa-google-plus-g:before{content:"\f0d5"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-wallet:before{content:"\f1ee"}.fa-gratipay:before{content:"\f184"}.fa-grav:before{content:"\f2d6"}.fa-gripfire:before{content:"\f3ac"}.fa-grunt:before{content:"\f3ad"}.fa-guilded:before{content:"\e07e"}.fa-gulp:before{content:"\f3ae"}.fa-hacker-news:before{content:"\f1d4"}.fa-hacker-news-square:before{content:"\f3af"}.fa-hackerrank:before{content:"\f5f7"}.fa-hashnode:before{content:"\e499"}.fa-hips:before{content:"\f452"}.fa-hire-a-helper:before{content:"\f3b0"}.fa-hive:before{content:"\e07f"}.fa-hooli:before{content:"\f427"}.fa-hornbill:before{content:"\f592"}.fa-hotjar:before{content:"\f3b1"}.fa-houzz:before{content:"\f27c"}.fa-html5:before{content:"\f13b"}.fa-hubspot:before{content:"\f3b2"}.fa-ideal:before{content:"\e013"}.fa-imdb:before{content:"\f2d8"}.fa-instagram:before{content:"\f16d"}.fa-instagram-square:before{content:"\e055"}.fa-instalod:before{content:"\e081"}.fa-intercom:before{content:"\f7af"}.fa-internet-explorer:before{content:"\f26b"}.fa-invision:before{content:"\f7b0"}.fa-ioxhost:before{content:"\f208"}.fa-itch-io:before{content:"\f83a"}.fa-itunes:before{content:"\f3b4"}.fa-itunes-note:before{content:"\f3b5"}.fa-java:before{content:"\f4e4"}.fa-jedi-order:before{content:"\f50e"}.fa-jenkins:before{content:"\f3b6"}.fa-jira:before{content:"\f7b1"}.fa-joget:before{content:"\f3b7"}.fa-joomla:before{content:"\f1aa"}.fa-js:before{content:"\f3b8"}.fa-js-square:before{content:"\f3b9"}.fa-jsfiddle:before{content:"\f1cc"}.fa-kaggle:before{content:"\f5fa"}.fa-keybase:before{content:"\f4f5"}.fa-keycdn:before{content:"\f3ba"}.fa-kickstarter:before{content:"\f3bb"}.fa-kickstarter-k:before{content:"\f3bc"}.fa-korvue:before{content:"\f42f"}.fa-laravel:before{content:"\f3bd"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-leanpub:before{content:"\f212"}.fa-less:before{content:"\f41d"}.fa-line:before{content:"\f3c0"}.fa-linkedin:before{content:"\f08c"}.fa-linkedin-in:before{content:"\f0e1"}.fa-linode:before{content:"\f2b8"}.fa-linux:before{content:"\f17c"}.fa-lyft:before{content:"\f3c3"}.fa-magento:before{content:"\f3c4"}.fa-mailchimp:before{content:"\f59e"}.fa-mandalorian:before{content:"\f50f"}.fa-markdown:before{content:"\f60f"}.fa-mastodon:before{content:"\f4f6"}.fa-maxcdn:before{content:"\f136"}.fa-mdb:before{content:"\f8ca"}.fa-medapps:before{content:"\f3c6"}.fa-medium-m:before,.fa-medium:before{content:"\f23a"}.fa-medrt:before{content:"\f3c8"}.fa-meetup:before{content:"\f2e0"}.fa-megaport:before{content:"\f5a3"}.fa-mendeley:before{content:"\f7b3"}.fa-microblog:before{content:"\e01a"}.fa-microsoft:before{content:"\f3ca"}.fa-mix:before{content:"\f3cb"}.fa-mixcloud:before{content:"\f289"}.fa-mixer:before{content:"\e056"}.fa-mizuni:before{content:"\f3cc"}.fa-modx:before{content:"\f285"}.fa-monero:before{content:"\f3d0"}.fa-napster:before{content:"\f3d2"}.fa-neos:before{content:"\f612"}.fa-nimblr:before{content:"\f5a8"}.fa-node:before{content:"\f419"}.fa-node-js:before{content:"\f3d3"}.fa-npm:before{content:"\f3d4"}.fa-ns8:before{content:"\f3d5"}.fa-nutritionix:before{content:"\f3d6"}.fa-octopus-deploy:before{content:"\e082"}.fa-odnoklassniki:before{content:"\f263"}.fa-odnoklassniki-square:before{content:"\f264"}.fa-old-republic:before{content:"\f510"}.fa-opencart:before{content:"\f23d"}.fa-openid:before{content:"\f19b"}.fa-opera:before{content:"\f26a"}.fa-optin-monster:before{content:"\f23c"}.fa-orcid:before{content:"\f8d2"}.fa-osi:before{content:"\f41a"}.fa-padlet:before{content:"\e4a0"}.fa-page4:before{content:"\f3d7"}.fa-pagelines:before{content:"\f18c"}.fa-palfed:before{content:"\f3d8"}.fa-patreon:before{content:"\f3d9"}.fa-paypal:before{content:"\f1ed"}.fa-perbyte:before{content:"\e083"}.fa-periscope:before{content:"\f3da"}.fa-phabricator:before{content:"\f3db"}.fa-phoenix-framework:before{content:"\f3dc"}.fa-phoenix-squadron:before{content:"\f511"}.fa-php:before{content:"\f457"}.fa-pied-piper:before{content:"\f2ae"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-pied-piper-hat:before{content:"\f4e5"}.fa-pied-piper-pp:before{content:"\f1a7"}.fa-pied-piper-square:before{content:"\e01e"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-p:before{content:"\f231"}.fa-pinterest-square:before{content:"\f0d3"}.fa-pix:before{content:"\e43a"}.fa-playstation:before{content:"\f3df"}.fa-product-hunt:before{content:"\f288"}.fa-pushed:before{content:"\f3e1"}.fa-python:before{content:"\f3e2"}.fa-qq:before{content:"\f1d6"}.fa-quinscape:before{content:"\f459"}.fa-quora:before{content:"\f2c4"}.fa-r-project:before{content:"\f4f7"}.fa-raspberry-pi:before{content:"\f7bb"}.fa-ravelry:before{content:"\f2d9"}.fa-react:before{content:"\f41b"}.fa-reacteurope:before{content:"\f75d"}.fa-readme:before{content:"\f4d5"}.fa-rebel:before{content:"\f1d0"}.fa-red-river:before{content:"\f3e3"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-alien:before{content:"\f281"}.fa-reddit-square:before{content:"\f1a2"}.fa-redhat:before{content:"\f7bc"}.fa-renren:before{content:"\f18b"}.fa-replyd:before{content:"\f3e6"}.fa-researchgate:before{content:"\f4f8"}.fa-resolving:before{content:"\f3e7"}.fa-rev:before{content:"\f5b2"}.fa-rocketchat:before{content:"\f3e8"}.fa-rockrms:before{content:"\f3e9"}.fa-rust:before{content:"\e07a"}.fa-safari:before{content:"\f267"}.fa-salesforce:before{content:"\f83b"}.fa-sass:before{content:"\f41e"}.fa-schlix:before{content:"\f3ea"}.fa-scribd:before{content:"\f28a"}.fa-searchengin:before{content:"\f3eb"}.fa-sellcast:before{content:"\f2da"}.fa-sellsy:before{content:"\f213"}.fa-servicestack:before{content:"\f3ec"}.fa-shirtsinbulk:before{content:"\f214"}.fa-shopify:before{content:"\e057"}.fa-shopware:before{content:"\f5b5"}.fa-simplybuilt:before{content:"\f215"}.fa-sistrix:before{content:"\f3ee"}.fa-sith:before{content:"\f512"}.fa-sitrox:before{content:"\e44a"}.fa-sketch:before{content:"\f7c6"}.fa-skyatlas:before{content:"\f216"}.fa-skype:before{content:"\f17e"}.fa-slack-hash:before,.fa-slack:before{content:"\f198"}.fa-slideshare:before{content:"\f1e7"}.fa-snapchat-ghost:before,.fa-snapchat:before{content:"\f2ab"}.fa-snapchat-square:before{content:"\f2ad"}.fa-soundcloud:before{content:"\f1be"}.fa-sourcetree:before{content:"\f7d3"}.fa-speakap:before{content:"\f3f3"}.fa-speaker-deck:before{content:"\f83c"}.fa-spotify:before{content:"\f1bc"}.fa-square-font-awesome:before{content:"\f425"}.fa-font-awesome-alt:before,.fa-square-font-awesome-stroke:before{content:"\f35c"}.fa-squarespace:before{content:"\f5be"}.fa-stack-exchange:before{content:"\f18d"}.fa-stack-overflow:before{content:"\f16c"}.fa-stackpath:before{content:"\f842"}.fa-staylinked:before{content:"\f3f5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-steam-symbol:before{content:"\f3f6"}.fa-sticker-mule:before{content:"\f3f7"}.fa-strava:before{content:"\f428"}.fa-stripe:before{content:"\f429"}.fa-stripe-s:before{content:"\f42a"}.fa-studiovinari:before{content:"\f3f8"}.fa-stumbleupon:before{content:"\f1a4"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-superpowers:before{content:"\f2dd"}.fa-supple:before{content:"\f3f9"}.fa-suse:before{content:"\f7d6"}.fa-swift:before{content:"\f8e1"}.fa-symfony:before{content:"\f83d"}.fa-teamspeak:before{content:"\f4f9"}.fa-telegram-plane:before,.fa-telegram:before{content:"\f2c6"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-the-red-yeti:before{content:"\f69d"}.fa-themeco:before{content:"\f5c6"}.fa-themeisle:before{content:"\f2b2"}.fa-think-peaks:before{content:"\f731"}.fa-tiktok:before{content:"\e07b"}.fa-trade-federation:before{content:"\f513"}.fa-trello:before{content:"\f181"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-twitch:before{content:"\f1e8"}.fa-twitter:before{content:"\f099"}.fa-twitter-square:before{content:"\f081"}.fa-typo3:before{content:"\f42b"}.fa-uber:before{content:"\f402"}.fa-ubuntu:before{content:"\f7df"}.fa-uikit:before{content:"\f403"}.fa-umbraco:before{content:"\f8e8"}.fa-uncharted:before{content:"\e084"}.fa-uniregistry:before{content:"\f404"}.fa-unity:before{content:"\e049"}.fa-unsplash:before{content:"\e07c"}.fa-untappd:before{content:"\f405"}.fa-ups:before{content:"\f7e0"}.fa-usb:before{content:"\f287"}.fa-usps:before{content:"\f7e1"}.fa-ussunnah:before{content:"\f407"}.fa-vaadin:before{content:"\f408"}.fa-viacoin:before{content:"\f237"}.fa-viadeo:before{content:"\f2a9"}.fa-viadeo-square:before{content:"\f2aa"}.fa-viber:before{content:"\f409"}.fa-vimeo:before{content:"\f40a"}.fa-vimeo-square:before{content:"\f194"}.fa-vimeo-v:before{content:"\f27d"}.fa-vine:before{content:"\f1ca"}.fa-vk:before{content:"\f189"}.fa-vnv:before{content:"\f40b"}.fa-vuejs:before{content:"\f41f"}.fa-watchman-monitoring:before{content:"\e087"}.fa-waze:before{content:"\f83f"}.fa-weebly:before{content:"\f5cc"}.fa-weibo:before{content:"\f18a"}.fa-weixin:before{content:"\f1d7"}.fa-whatsapp:before{content:"\f232"}.fa-whatsapp-square:before{content:"\f40c"}.fa-whmcs:before{content:"\f40d"}.fa-wikipedia-w:before{content:"\f266"}.fa-windows:before{content:"\f17a"}.fa-wirsindhandwerk:before,.fa-wsh:before{content:"\e2d0"}.fa-wix:before{content:"\f5cf"}.fa-wizards-of-the-coast:before{content:"\f730"}.fa-wodu:before{content:"\e088"}.fa-wolf-pack-battalion:before{content:"\f514"}.fa-wordpress:before{content:"\f19a"}.fa-wordpress-simple:before{content:"\f411"}.fa-wpbeginner:before{content:"\f297"}.fa-wpexplorer:before{content:"\f2de"}.fa-wpforms:before{content:"\f298"}.fa-wpressr:before{content:"\f3e4"}.fa-xbox:before{content:"\f412"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-y-combinator:before{content:"\f23b"}.fa-yahoo:before{content:"\f19e"}.fa-yammer:before{content:"\f840"}.fa-yandex:before{content:"\f413"}.fa-yandex-international:before{content:"\f414"}.fa-yarn:before{content:"\f7e3"}.fa-yelp:before{content:"\f1e9"}.fa-yoast:before{content:"\f2b1"}.fa-youtube:before{content:"\f167"}.fa-youtube-square:before{content:"\f431"}.fa-zhihu:before{content:"\f63f"}:host,:root{--fa-font-regular:normal 400 1em/1 "Font Awesome 6 Free"}@font-face{font-family:"Font Awesome 6 Free";font-style:normal;font-weight:400;font-display:block;src:url(../css/webfonts/fa-regular-400.woff2) format("woff2"),url(../css/webfonts/fa-regular-400.ttf) format("truetype")}.fa-regular,.far{font-family:"Font Awesome 6 Free";font-weight:400}:host,:root{--fa-font-solid:normal 900 1em/1 "Font Awesome 6 Free"}@font-face{font-family:"Font Awesome 6 Free";font-style:normal;font-weight:900;font-display:block;src:url(../css/webfonts/fa-solid-900.woff2) format("woff2"),url(../css/webfonts/fa-solid-900.ttf) format("truetype")}.fa-solid,.fas{font-family:"Font Awesome 6 Free";font-weight:900}@font-face{font-family:"Font Awesome 5 Brands";font-display:block;font-weight:400;src:url(../css/webfonts/fa-brands-400.woff2) format("woff2"),url(../css/webfonts/fa-brands-400.ttf) format("truetype")}@font-face{font-family:"Font Awesome 5 Free";font-display:block;font-weight:900;src:url(../css/webfonts/fa-solid-900.woff2) format("woff2"),url(../css/webfonts/fa-solid-900.ttf) format("truetype")}@font-face{font-family:"Font Awesome 5 Free";font-display:block;font-weight:400;src:url(../css/webfonts/fa-regular-400.woff2) format("woff2"),url(../css/webfonts/fa-regular-400.ttf) format("truetype")}@font-face{font-family:"FontAwesome";font-display:block;src:url(../css/webfonts/fa-solid-900.woff2) format("woff2"),url(../css/webfonts/fa-solid-900.ttf) format("truetype")}@font-face{font-family:"FontAwesome";font-display:block;src:url(../css/webfonts/fa-brands-400.woff2) format("woff2"),url(../css/webfonts/fa-brands-400.ttf) format("truetype")}@font-face{font-family:"FontAwesome";font-display:block;src:url(../css/webfonts/fa-regular-400.woff2) format("woff2"),url(../css/webfonts/fa-regular-400.ttf) format("truetype");unicode-range:u+f003,u+f006,u+f014,u+f016-f017,u+f01a-f01b,u+f01d,u+f022,u+f03e,u+f044,u+f046,u+f05c-f05d,u+f06e,u+f070,u+f087-f088,u+f08a,u+f094,u+f096-f097,u+f09d,u+f0a0,u+f0a2,u+f0a4-f0a7,u+f0c5,u+f0c7,u+f0e5-f0e6,u+f0eb,u+f0f6-f0f8,u+f10c,u+f114-f115,u+f118-f11a,u+f11c-f11d,u+f133,u+f147,u+f14e,u+f150-f152,u+f185-f186,u+f18e,u+f190-f192,u+f196,u+f1c1-f1c9,u+f1d9,u+f1db,u+f1e3,u+f1ea,u+f1f7,u+f1f9,u+f20a,u+f247-f248,u+f24a,u+f24d,u+f255-f25b,u+f25d,u+f271-f274,u+f278,u+f27b,u+f28c,u+f28e,u+f29c,u+f2b5,u+f2b7,u+f2ba,u+f2bc,u+f2be,u+f2c0-f2c1,u+f2c3,u+f2d0,u+f2d2,u+f2d4,u+f2dc}@font-face{font-family:"FontAwesome";font-display:block;src:url(../css/webfonts/fa-v4compatibility.woff2) format("woff2"),url(../css/webfonts/fa-v4compatibility.ttf) format("truetype");unicode-range:u+f041,u+f047,u+f065-f066,u+f07d-f07e,u+f080,u+f08b,u+f08e,u+f090,u+f09a,u+f0ac,u+f0ae,u+f0b2,u+f0d0,u+f0d6,u+f0e4,u+f0ec,u+f10a-f10b,u+f123,u+f13e,u+f148-f149,u+f14c,u+f156,u+f15e,u+f160-f161,u+f163,u+f175-f178,u+f195,u+f1f8,u+f219,u+f250,u+f252,u+f27a} \ No newline at end of file diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/assets/css/bootstrap-docs.css b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/assets/css/bootstrap-docs.css deleted file mode 100644 index a9820c2b72..0000000000 --- a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/assets/css/bootstrap-docs.css +++ /dev/null @@ -1,1579 +0,0 @@ -/*! - * Bootstrap Docs (https://getbootstrap.com/) - * Copyright 2011-2021 The Bootstrap Authors - * Copyright 2011-2021 Twitter, Inc. - * Licensed under the Creative Commons Attribution 3.0 Unported License. - * For details, see https://creativecommons.org/licenses/by/3.0/. - */ - .bd-navbar { - min-height: 4rem; - background-color: #7952b3; - box-shadow: 0 0.5rem 1rem rgba(0,0,0,0.05),inset 0 -1px 0 rgba(0,0,0,0.1) -} - -@media (max-width: 991.98px) { - .bd-navbar { - padding-right:.5rem; - padding-left: .5rem - } - - .bd-navbar .navbar-nav-scroll { - max-width: 100%; - height: 2.5rem; - margin-top: .25rem; - overflow: hidden - } - - .bd-navbar .navbar-nav-scroll .navbar-nav { - padding-bottom: 2rem; - overflow-x: auto; - white-space: nowrap; - -webkit-overflow-scrolling: touch - } -} - -@media (min-width: 768px) { - @supports ((position: -webkit-sticky) or (position: sticky)) { - .bd-navbar { - position:-webkit-sticky; - position: sticky; - top: 0; - z-index: 1071 - } - } -} - -.bd-navbar .navbar-nav .nav-link { - padding-right: .5rem; - padding-left: .5rem; - color: rgba(255,255,255,0.85) -} - -.bd-navbar .navbar-nav .nav-link.active,.bd-navbar .navbar-nav .nav-link:hover { - color: #fff; - background-color: transparent -} - -.bd-navbar .navbar-nav .nav-link.active { - font-weight: 600 -} - -.bd-navbar .navbar-nav-svg { - display: inline-block; - width: 1rem; - height: 1rem; - vertical-align: text-top -} - -.bd-navbar .dropdown-menu { - font-size: .875rem -} - -.bd-navbar .dropdown-item.active { - font-weight: 600; - color: #212529; - background: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%23292b2c' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e") no-repeat 0.4rem 0.6rem/0.75rem 0.75rem -} - -.bd-masthead { - position: relative; - padding: 3rem 15px; - background: linear-gradient(to right bottom, #f7f5fb 50%, #fff 50%) -} - -.bd-masthead h1 { - font-size: 4rem; - line-height: 1 -} - -@media (max-width: 1200px) { - .bd-masthead h1 { - font-size:calc(1.525rem + 3.3vw) - } -} - -.bd-masthead .lead { - font-size: 1.5rem; - font-weight: 400; - color: #495057 -} - -@media (max-width: 1200px) { - .bd-masthead .lead { - font-size:calc(1.275rem + .3vw) - } -} - -.bd-masthead .btn { - padding: .8rem 2rem; - font-weight: 600; - font-size: 1.25rem -} - -.bd-masthead .carbonad { - margin-top: 0 !important; - margin-bottom: -3rem !important -} - -@media (min-width: 576px) { - .bd-masthead { - padding-top:5rem; - padding-bottom: 5rem - } - - .bd-masthead .carbonad { - margin-bottom: 0 !important - } -} - -@media (min-width: 768px) { - .bd-masthead .carbonad { - margin-top:3rem !important - } -} - -.masthead-followup h2 { - font-size: 2.5rem -} - -@media (max-width: 1200px) { - .masthead-followup h2 { - font-size:calc(1.375rem + 1.5vw) - } -} - -.masthead-followup .highlight { - border-radius: .5rem -} - -.masthead-followup .highlight pre::-webkit-scrollbar { - display: none -} - -.masthead-followup .highlight pre code { - display: inline-block; - white-space: pre -} - -.masthead-followup-icon { - padding: .75rem; - background-image: linear-gradient(to bottom right, rgba(255,255,255,0.2), rgba(255,255,255,0.01)); - border-radius: .75rem; - box-shadow: 0 0.125rem 0.25rem rgba(0,0,0,0.1) -} - -.masthead-followup-svg { - -webkit-filter: drop-shadow(0 1px 0 rgba(0,0,0,0.125)); - filter: drop-shadow(0 1px 0 rgba(0,0,0,0.125)) -} - -#carbonads { - position: static; - display: block; - max-width: 400px; - padding: 15px 15px 15px 160px; - margin: 2rem 0; - overflow: hidden; - font-size: .8125rem; - line-height: 1.4; - text-align: left; - background-color: rgba(0,0,0,0.05) -} - -#carbonads a { - color: #333; - text-decoration: none -} - -@media (min-width: 576px) { - #carbonads { - max-width:330px; - border-radius: 4px - } -} - -.carbon-img { - float: left; - margin-left: -145px -} - -.carbon-poweredby { - display: block; - margin-top: .75rem; - color: #777 !important -} - -b -.bd-title { - margin-top: 1rem; - margin-bottom: .5rem; - font-size: 3rem -} - -@media (max-width: 1200px) { - .bd-title { - font-size:calc(1.425rem + 2.1vw) - } -} - -.bd-lead { - font-size: 1.5rem; - font-weight: 300 -} - -@media (max-width: 1200px) { - .bd-lead { - font-size:calc(1.275rem + .3vw) - } -} - -@media (min-width: 992px) { - .bd-lead { - max-width:80% - } -} - -.bd-text-purple { - color: #563d7c -} - -.bd-text-purple-bright { - color: #7952b3 -} - -.bd-bg-purple-bright { - background-color: #7952b3 -} - -.skippy { - background-color: #563d7c -} - -.skippy a { - color: #fff -} - -.skippy:focus-within a { - position: static !important; - width: auto !important; - height: auto !important; - padding: .5rem !important; - margin: .25rem !important; - overflow: visible !important; - clip: auto !important; - white-space: normal !important -} - -.bd-sidebar { - -ms-flex-order: 0; - order: 0; - border-bottom: 1px solid rgba(0,0,0,0.1) -} - -@media (min-width: 768px) { - .bd-sidebar { - border-right:1px solid rgba(0,0,0,0.1) - } - - @supports ((position: -webkit-sticky) or (position: sticky)) { - .bd-sidebar { - position:-webkit-sticky; - position: sticky; - top: 4rem; - z-index: 1000; - height: calc(100vh - 4rem) - } - } -} - -@media (min-width: 1200px) { - .bd-sidebar { - -ms-flex:0 1 320px; - flex: 0 1 320px - } -} - -.bd-links { - width: 100%; - padding-top: 1rem; - padding-bottom: 1rem; - border-top: 1px solid rgba(0,0,0,0.05) -} - -@media (min-width: 768px) { - @supports ((position: -webkit-sticky) or (position: sticky)) { - .bd-links { - max-height:calc(100vh - 9rem); - overflow-y: auto - } - } -} - -.bd-search { - position: relative; - padding: 1rem 15px; - margin-right: -15px; - margin-left: -15px -} - -.bd-search .form-control:focus { - border-color: #7952b3; - box-shadow: 0 0 0 3px rgba(121,82,179,0.25) -} - -.bd-search-docs-toggle { - color: #212529 -} - -.bd-sidenav { - display: none -} - -.bd-toc-link { - display: block; - padding: .25rem 1.5rem; - font-weight: 600; - color: rgba(0,0,0,0.65) -} - -.bd-toc-link:hover { - color: rgba(0,0,0,0.85); - text-decoration: none -} - -.bd-toc-item.active { - margin-bottom: 1rem -} - -.bd-toc-item.active:not(:first-child) { - margin-top: 1rem -} - -.bd-toc-item.active>.bd-toc-link { - color: rgba(0,0,0,0.85) -} - -.bd-toc-item.active>.bd-toc-link:hover { - background-color: transparent -} - -.bd-toc-item.active>.bd-sidenav { - display: block -} - -.bd-sidebar .nav>li>a { - display: block; - padding: .25rem 1.5rem; - font-size: 90%; - color: rgba(0,0,0,0.65) -} - -.bd-sidebar .nav>li>a:hover { - color: rgba(0,0,0,0.85); - text-decoration: none; - background-color: transparent -} - -.bd-sidebar .nav>.active>a,.bd-sidebar .nav>.active:hover>a { - font-weight: 600; - color: rgba(0,0,0,0.85); - background-color: transparent -} - -.bd-toc { - -ms-flex-order: 2; - order: 2; - padding-top: 1.5rem; - padding-bottom: 1.5rem; - font-size: .875rem -} - -@supports ((position: -webkit-sticky) or (position: sticky)) { - .bd-toc { - position:-webkit-sticky; - position: sticky; - top: 4rem; - height: calc(100vh - 4rem); - overflow-y: auto - } -} - -.bd-toc nav { - padding-left: 0; - border-left: 1px solid #eee -} - -.bd-toc nav ul { - padding-left: 0 -} - -.bd-toc nav ul ul { - padding-left: 1rem -} - -.bd-toc nav a code { - font: inherit -} - -.bd-toc nav li { - display: block -} - -.bd-toc nav li ul li ul { - padding-left: 1rem -} - -.bd-toc nav li a { - display: block; - padding: .125rem 1.5rem; - color: #77757a -} - -.bd-toc nav li a:hover { - color: #007bff; - text-decoration: none -} - -.bd-footer { - font-size: .875rem; - text-align: center; - background-color: #f7f7f7 -} - -.bd-footer a { - font-weight: 600; - color: #495057 -} - -.bd-footer a:hover,.bd-footer a:focus { - color: #007bff -} - -.bd-footer p { - margin-bottom: 0 -} - -@media (min-width: 576px) { - .bd-footer { - text-align:left - } -} - -.bd-footer-links { - padding-left: 0; - margin-bottom: 1rem -} - -.bd-footer-links li { - display: inline-block -} - -.bd-footer-links li+li { - margin-left: 1rem -} - -.bd-example-row .row>.col,.bd-example-row .row>[class^="col-"] { - padding-top: .75rem; - padding-bottom: .75rem; - background-color: rgba(86,61,124,0.15); - border: 1px solid rgba(86,61,124,0.2) -} - -.bd-example-row .row+.row { - margin-top: 1rem -} - -.bd-example-row .flex-items-top,.bd-example-row .flex-items-middle,.bd-example-row .flex-items-bottom { - min-height: 6rem; - background-color: rgba(255,0,0,0.1) -} - -.bd-example-row-flex-cols .row { - min-height: 10rem; - background-color: rgba(255,0,0,0.1) -} - -.bd-highlight { - background-color: rgba(86,61,124,0.15); - border: 1px solid rgba(86,61,124,0.15) -} - -.bd-example-responsive-containers [class^="container"] { - padding-top: .75rem; - padding-bottom: .75rem; - background-color: rgba(86,61,124,0.15); - border: 1px solid rgba(86,61,124,0.2) -} - -.example-container { - width: 800px; - width: 100%; - padding-right: 15px; - padding-left: 15px; - margin-right: auto; - margin-left: auto -} - -.example-row { - display: -ms-flexbox; - display: flex; - -ms-flex-wrap: wrap; - flex-wrap: wrap; - margin-right: -15px; - margin-left: -15px -} - -.example-content-main { - position: relative; - width: 100%; - padding-right: 15px; - padding-left: 15px -} - -@media (min-width: 576px) { - .example-content-main { - -ms-flex:0 0 50%; - flex: 0 0 50%; - max-width: 50% - } -} - -@media (min-width: 992px) { - .example-content-main { - -ms-flex:0 0 66.666667%; - flex: 0 0 66.666667%; - max-width: 66.666667% - } -} - -.example-content-secondary { - position: relative; - width: 100%; - padding-right: 15px; - padding-left: 15px -} - -@media (min-width: 576px) { - .example-content-secondary { - -ms-flex:0 0 50%; - flex: 0 0 50%; - max-width: 50% - } -} - -@media (min-width: 992px) { - .example-content-secondary { - -ms-flex:0 0 33.333333%; - flex: 0 0 33.333333%; - max-width: 33.333333% - } -} - -.bd-example { - position: relative; - padding: 1rem; - margin: 1rem -15px 0; - border: solid #f8f9fa; - border-width: .2rem 0 0 -} - -.bd-example::after { - display: block; - clear: both; - content: "" -} - -@media (min-width: 576px) { - .bd-example { - padding:1.5rem; - margin-right: 0; - margin-left: 0; - border-width: .2rem - } -} - -.bd-example+.highlight,.bd-example+.clipboard+.highlight { - margin-top: 0 -} - -.bd-example+p { - margin-top: 2rem -} - -.bd-example .custom-file-input:lang(es)~.custom-file-label::after { - content: "Elegir" -} - -.bd-example>.form-control+.form-control { - margin-top: .5rem -} - -.bd-example>.nav+.nav,.bd-example>.alert+.alert,.bd-example>.navbar+.navbar,.bd-example>.progress+.progress,.bd-example>.progress+.btn { - margin-top: 1rem -} - -.bd-example>.dropdown-menu:first-child { - position: static; - display: block -} - -.bd-example>.form-group:last-child { - margin-bottom: 0 -} - -.bd-example>.close { - float: none -} - -.bd-example-type .table td { - padding: 1rem 0; - border-color: #eee -} - -.bd-example-type .table tr:first-child td { - border-top: 0 -} - -.bd-example-type h1,.bd-example-type h2,.bd-example-type h3,.bd-example-type h4,.bd-example-type h5,.bd-example-type h6 { - margin-top: 0; - margin-bottom: 0 -} - -.bd-example-bg-classes p { - padding: 1rem -} - -.bd-example>svg+svg,.bd-example>img+img { - margin-left: .5rem -} - -.bd-example>.btn,.bd-example>.btn-group { - margin-top: .25rem; - margin-bottom: .25rem -} - -.bd-example>.btn-toolbar+.btn-toolbar { - margin-top: .5rem -} - -.bd-example-control-sizing select,.bd-example-control-sizing input[type="text"]+input[type="text"] { - margin-top: .5rem -} - -.bd-example-form .input-group { - margin-bottom: .5rem -} - -.bd-example>textarea.form-control { - resize: vertical -} - -.bd-example>.list-group { - max-width: 400px -} - -.bd-example>[class*="list-group-horizontal"] { - max-width: 100% -} - -.bd-example .fixed-top,.bd-example .sticky-top { - position: static; - margin: -1rem -1rem 1rem -} - -.bd-example .fixed-bottom { - position: static; - margin: 1rem -1rem -1rem -} - -@media (min-width: 576px) { - .bd-example .fixed-top,.bd-example .sticky-top { - margin:-1.5rem -1.5rem 1rem - } - - .bd-example .fixed-bottom { - margin: 1rem -1.5rem -1.5rem - } -} - -.bd-example .pagination { - margin-top: .5rem; - margin-bottom: .5rem -} - -.modal { - z-index: 1072 -} - -.modal .tooltip,.modal .popover { - z-index: 1073 -} - -.modal-backdrop { - z-index: 1071 -} - -.bd-example-modal { - background-color: #fafafa -} - -.bd-example-modal .modal { - position: relative; - top: auto; - right: auto; - bottom: auto; - left: auto; - z-index: 1; - display: block -} - -.bd-example-modal .modal-dialog { - left: auto; - margin-right: auto; - margin-left: auto -} - -.bd-example-tabs .nav-tabs { - margin-bottom: 1rem -} - -.bd-example-popover-static { - padding-bottom: 1.5rem; - background-color: #f9f9f9 -} - -.bd-example-popover-static .popover { - position: relative; - display: block; - float: left; - width: 260px; - margin: 1.25rem -} - -.tooltip-demo a { - white-space: nowrap -} - -.bd-example-tooltip-static .tooltip { - position: relative; - display: inline-block; - margin: 10px 20px; - opacity: 1 -} - -.scrollspy-example { - position: relative; - height: 200px; - margin-top: .5rem; - overflow: auto -} - -.scrollspy-example-2 { - position: relative; - height: 350px; - overflow: auto -} - -.bd-example-border-utils [class^="border"] { - display: inline-block; - width: 5rem; - height: 5rem; - margin: .25rem; - background-color: #f5f5f5 -} - -.bd-example-border-utils-0 [class^="border"] { - border: 1px solid #dee2e6 -} - -.btn-bd-primary { - font-weight: 600; - color: #fff; - background-color: #7952b3; - border-color: #7952b3 -} - -.btn-bd-primary:hover,.btn-bd-primary:active { - color: #fff; - background-color: #614092; - border-color: #614092 -} - -.btn-bd-primary:focus { - box-shadow: 0 0 0 3px rgba(121,82,179,0.25) -} - -.btn-bd-download { - font-weight: 600; - color: #ffe484; - border-color: #ffe484 -} - -.btn-bd-download:hover,.btn-bd-download:active { - color: #2a2730; - background-color: #ffe484; - border-color: #ffe484 -} - -.btn-bd-download:focus { - box-shadow: 0 0 0 3px rgba(255,228,132,0.25) -} - -.btn-bd-light { - color: #6c757d; - border-color: #dee2e6 -} - -.show>.btn-bd-light,.btn-bd-light:hover,.btn-bd-light:active { - color: #7952b3; - background-color: #fff; - border-color: #7952b3 -} - -.btn-bd-light:focus { - box-shadow: 0 0 0 3px rgba(121,82,179,0.25) -} - -.bd-callout { - padding: 1.25rem; - margin-top: 1.25rem; - margin-bottom: 1.25rem; - border: 1px solid #eee; - border-left-width: .25rem; - border-radius: .25rem -} - -.bd-callout h4 { - margin-top: 0; - margin-bottom: .25rem -} - -.bd-callout p:last-child { - margin-bottom: 0 -} - -.bd-callout code { - border-radius: .25rem -} - -.bd-callout+.bd-callout { - margin-top: -.25rem -} - -.bd-callout-info { - border-left-color: #5bc0de -} - -.bd-callout-info h4 { - color: #5bc0de -} - -.bd-callout-warning { - border-left-color: #f0ad4e -} - -.bd-callout-warning h4 { - color: #f0ad4e -} - -.bd-callout-danger { - border-left-color: #d9534f -} - -.bd-callout-danger h4 { - color: #d9534f -} - -.bd-browser-bugs td p { - margin-bottom: 0 -} - -.bd-browser-bugs th:first-child { - width: 18% -} - -.bd-brand-logos { - display: table; - width: 100%; - margin-bottom: 1rem; - overflow: hidden; - color: #563d7c; - background-color: #f9f9f9; - border-radius: .25rem -} - -.bd-brand-logos .inverse { - color: #fff; - background-color: #563d7c -} - -.bd-brand-item { - padding: 4rem 0; - text-align: center -} - -.bd-brand-item+.bd-brand-item { - border-top: 1px solid #fff -} - -.bd-brand-item h1,.bd-brand-item h3 { - margin-top: 0; - margin-bottom: 0 -} - -@media (min-width: 768px) { - .bd-brand-item { - display:table-cell; - width: 1% - } - - .bd-brand-item+.bd-brand-item { - border-top: 0; - border-left: 1px solid #fff - } - - .bd-brand-item h1 { - font-size: 4rem - } -} - -@media (min-width: 768px) and (max-width: 1200px) { - .bd-brand-item h1 { - font-size:calc(1.525rem + 3.3vw) - } -} - -.color-swatches { - margin: 0 -5px; - overflow: hidden -} - -.color-swatches .bd-purple { - background-color: #563d7c -} - -.color-swatches .bd-purple-light { - background-color: #cbbde2 -} - -.color-swatches .bd-purple-lighter { - background-color: #e5e1ea -} - -.color-swatches .bd-gray { - background-color: #f9f9f9 -} - -.color-swatch { - float: left; - width: 4rem; - height: 4rem; - margin-right: .25rem; - margin-left: .25rem; - border-radius: .25rem -} - -@media (min-width: 768px) { - .color-swatch { - width:6rem; - height: 6rem - } -} - -.swatch-blue { - color: #fff; - background-color: #007bff -} - -.swatch-indigo { - color: #fff; - background-color: #6610f2 -} - -.swatch-purple { - color: #fff; - background-color: #6f42c1 -} - -.swatch-pink { - color: #fff; - background-color: #e83e8c -} - -.swatch-red { - color: #fff; - background-color: #dc3545 -} - -.swatch-orange { - color: #212529; - background-color: #fd7e14 -} - -.swatch-yellow { - color: #212529; - background-color: #ffc107 -} - -.swatch-green { - color: #fff; - background-color: #28a745 -} - -.swatch-teal { - color: #fff; - background-color: #20c997 -} - -.swatch-cyan { - color: #fff; - background-color: #17a2b8 -} - -.swatch-white { - color: #212529; - background-color: #fff -} - -.swatch-gray { - color: #fff; - background-color: #6c757d -} - -.swatch-gray-dark { - color: #fff; - background-color: #343a40 -} - -.swatch-100 { - color: #212529; - background-color: #f8f9fa -} - -.swatch-200 { - color: #212529; - background-color: #e9ecef -} - -.swatch-300 { - color: #212529; - background-color: #dee2e6 -} - -.swatch-400 { - color: #212529; - background-color: #ced4da -} - -.swatch-500 { - color: #212529; - background-color: #adb5bd -} - -.swatch-600 { - color: #fff; - background-color: #6c757d -} - -.swatch-700 { - color: #fff; - background-color: #495057 -} - -.swatch-800 { - color: #fff; - background-color: #343a40 -} - -.swatch-900 { - color: #fff; - background-color: #212529 -} - -.bd-clipboard { - position: relative; - display: none; - float: right -} - -.bd-clipboard+.highlight { - margin-top: 0 -} - -@media (min-width: 768px) { - .bd-clipboard { - display:block - } -} - -.btn-clipboard { - position: absolute; - top: .65rem; - right: .65rem; - z-index: 10; - display: block; - padding: .25rem .5rem; - font-size: 65%; - color: #007bff; - background-color: #fff; - border: 1px solid; - border-radius: .25rem -} - -.btn-clipboard:hover,.btn-clipboard:focus { - color: #fff; - background-color: #007bff -} - -.bd-placeholder-img { - font-size: 1.125rem; - text-anchor: middle; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none -} - -.bd-placeholder-img-lg { - font-size: 3.5rem -} - -@media (max-width: 1200px) { - .bd-placeholder-img-lg { - font-size:calc(1.475rem + 2.7vw) - } -} - -.chroma .c { - color: #727272 -} - -.chroma .ch { - font-style: italic; - color: #60a0b0 -} - -.chroma .cm { - color: #727272 -} - -.chroma .cp { - color: #008085 -} - -.chroma .cpf { - color: #007020 -} - -.chroma .c1 { - color: #727272 -} - -.chroma .cs { - color: #727272 -} - -.chroma .gd { - background-color: #fcc; - border: 1px solid #c00 -} - -.chroma .ge { - font-style: italic -} - -.chroma .gr { - color: #f00 -} - -.chroma .gh { - color: #030 -} - -.chroma .gi { - background-color: #cfc; - border: 1px solid #0c0 -} - -.chroma .go { - color: #aaa -} - -.chroma .gp { - color: #009 -} - -.chroma .gs { - font-weight: 700 -} - -.chroma .gu { - color: #030 -} - -.chroma .gt { - color: #9c6 -} - -.chroma .gl { - text-decoration: underline -} - -.chroma .k { - color: #069 -} - -.chroma .kc { - color: #069 -} - -.chroma .kd { - color: #069 -} - -.chroma .kn { - color: #069 -} - -.chroma .kp { - color: #069 -} - -.chroma .kr { - color: #069 -} - -.chroma .kt { - color: #078 -} - -.chroma .m { - color: #c24f19 -} - -.chroma .mb { - color: #40a070 -} - -.chroma .mf { - color: #c24f19 -} - -.chroma .mh { - color: #c24f19 -} - -.chroma .mi { - color: #c24f19 -} - -.chroma .il { - color: #c24f19 -} - -.chroma .mo { - color: #c24f19 -} - -.chroma .s { - color: #d73038 -} - -.chroma .sa { - color: #4070a0 -} - -.chroma .sb { - color: #c30 -} - -.chroma .sc { - color: #c30 -} - -.chroma .dl { - color: #4070a0 -} - -.chroma .sd { - font-style: italic; - color: #c30 -} - -.chroma .s2 { - color: #c30 -} - -.chroma .se { - color: #c30 -} - -.chroma .sh { - color: #c30 -} - -.chroma .si { - color: #a00 -} - -.chroma .sx { - color: #c30 -} - -.chroma .sr { - color: #337e7e -} - -.chroma .s1 { - color: #c30 -} - -.chroma .ss { - color: #fc3 -} - -.chroma .na { - color: #006ee0 -} - -.chroma .nb { - color: #366 -} - -.chroma .nc { - color: #168174 -} - -.chroma .no { - color: #360 -} - -.chroma .nd { - color: #6b62de -} - -.chroma .ni { - color: #727272 -} - -.chroma .ne { - color: #c00 -} - -.chroma .nf { - color: #b715f4 -} - -.chroma .nl { - color: #6b62de -} - -.chroma .nn { - color: #007ca5 -} - -.chroma .nt { - color: #2f6f9f -} - -.chroma .nv { - color: #033 -} - -.chroma .o { - color: #555 -} - -.chroma .ow { - color: #000 -} - -.chroma .w { - color: #bbb -} - -/* -.chroma .language-bash::before,.chroma .language-sh::before { - color: #009; - content: "$ "; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none -} -*/ - -.chroma .language-powershell::before { - color: #009; - content: "PM> "; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none -} - -.anchorjs-link { - font-weight: 400; - color: rgba(0,123,255,0.5); - transition: color 0.15s ease-in-out,opacity 0.15s ease-in-out -} - -@media (prefers-reduced-motion: reduce) { - .anchorjs-link { - transition: none - } -} - -.anchorjs-link:focus,.anchorjs-link:hover { - color: #007bff; - text-decoration: none -} - -.algolia-autocomplete { - display: block !important; - -ms-flex: 1; - flex: 1 -} - -.algolia-autocomplete .ds-dropdown-menu { - width: 100%; - min-width: 0 !important; - max-width: none !important; - padding: .75rem 0 !important; - background-color: #fff; - background-clip: padding-box; - border: 1px solid rgba(0,0,0,0.1); - box-shadow: 0 0.5rem 1rem rgba(0,0,0,0.175) -} - -@media (min-width: 768px) { - .algolia-autocomplete .ds-dropdown-menu { - width:175% - } -} - -.algolia-autocomplete .ds-dropdown-menu::before { - display: none !important -} - -.algolia-autocomplete .ds-dropdown-menu [class^="ds-dataset-"] { - padding: 0 !important; - overflow: visible !important; - background-color: transparent !important; - border: 0 !important -} - -.algolia-autocomplete .ds-dropdown-menu .ds-suggestions { - margin-top: 0 !important -} - -.algolia-autocomplete .algolia-docsearch-suggestion { - padding: 0 !important; - overflow: visible !important -} - -.algolia-autocomplete .algolia-docsearch-suggestion--category-header { - padding: .125rem 1rem !important; - margin-top: 0 !important; - font-size: .875rem !important; - font-weight: 600 !important; - color: #7952b3 !important; - border-bottom: 0 !important -} - -.algolia-autocomplete .algolia-docsearch-suggestion--wrapper { - float: none !important; - padding-top: 0 !important -} - -.algolia-autocomplete .algolia-docsearch-suggestion--subcategory-column { - float: none !important; - width: auto !important; - padding: 0 !important; - text-align: left !important -} - -.algolia-autocomplete .algolia-docsearch-suggestion--subcategory-inline { - display: block !important; - font-size: .875rem; - color: #495057 -} - -.algolia-autocomplete .algolia-docsearch-suggestion--subcategory-inline::after { - padding: 0 .25rem; - content: "/" -} - -.algolia-autocomplete .algolia-docsearch-suggestion--content { - display: -ms-flexbox; - display: flex; - -ms-flex-wrap: wrap; - flex-wrap: wrap; - float: none !important; - width: 100% !important; - padding: .25rem 1rem !important -} - -.algolia-autocomplete .algolia-docsearch-suggestion--content::before { - display: none !important -} - -.algolia-autocomplete .ds-suggestion:not(:first-child) .algolia-docsearch-suggestion--category-header { - padding-top: .75rem !important; - margin-top: .75rem !important; - border-top: 1px solid rgba(0,0,0,0.1) -} - -.algolia-autocomplete .ds-suggestion .algolia-docsearch-suggestion--subcategory-column { - display: none !important -} - -.algolia-autocomplete .algolia-docsearch-suggestion--title { - display: block; - margin-bottom: 0 !important; - font-size: .875rem !important; - font-weight: 400 !important -} - -.algolia-autocomplete .algolia-docsearch-suggestion--text { - -ms-flex: 0 0 100%; - flex: 0 0 100%; - max-width: 100%; - padding: .2rem 0; - font-size: .8125rem !important; - font-weight: 400; - line-height: 1.25 !important; - color: #6c757d -} - -.algolia-autocomplete .algolia-docsearch-footer { - float: none !important; - width: auto !important; - height: auto !important; - padding: .75rem 1rem 0; - font-size: .75rem !important; - line-height: 1 !important; - color: #767676 !important; - border-top: 1px solid rgba(0,0,0,0.1) -} - -.algolia-autocomplete .algolia-docsearch-footer--logo { - display: inline !important; - overflow: visible !important; - color: inherit !important; - text-indent: 0 !important; - background: none !important -} - -.algolia-autocomplete .algolia-docsearch-suggestion--highlight { - color: #5f2dab; - background-color: rgba(154,132,187,0.12) -} - -.algolia-autocomplete .algolia-docsearch-suggestion--text .algolia-docsearch-suggestion--highlight { - box-shadow: inset 0 -2px 0 0 rgba(95,45,171,0.5) !important -} - -.algolia-autocomplete .ds-suggestion.ds-cursor .algolia-docsearch-suggestion--content { - background-color: rgba(208,189,236,0.15) !important -} diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/assets/css/bootstrap.min.css b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/assets/css/bootstrap.min.css deleted file mode 100644 index ef399d21ce..0000000000 --- a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/assets/css/bootstrap.min.css +++ /dev/null @@ -1,7 +0,0 @@ -/*! - * Bootstrap v4.6.0 (https://getbootstrap.com/) - * Copyright 2011-2021 The Bootstrap Authors - * Copyright 2011-2021 Twitter, Inc. - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) - */:root{--blue:#007bff;--indigo:#6610f2;--purple:#6f42c1;--pink:#e83e8c;--red:#dc3545;--orange:#fd7e14;--yellow:#ffc107;--green:#28a745;--teal:#20c997;--cyan:#17a2b8;--white:#fff;--gray:#6c757d;--gray-dark:#343a40;--primary:#007bff;--secondary:#6c757d;--success:#28a745;--info:#17a2b8;--warning:#ffc107;--danger:#dc3545;--light:#f8f9fa;--dark:#343a40;--breakpoint-xs:0;--breakpoint-sm:576px;--breakpoint-md:768px;--breakpoint-lg:992px;--breakpoint-xl:1200px;--font-family-sans-serif:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans","Liberation Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-family-monospace:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace}*,::after,::before{box-sizing:border-box}html{font-family:sans-serif;line-height:1.15;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}article,aside,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}body{margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans","Liberation Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-size:1rem;font-weight:400;line-height:1.5;color:#212529;text-align:left;background-color:#fff}[tabindex="-1"]:focus:not(:focus-visible){outline:0!important}hr{box-sizing:content-box;height:0;overflow:visible}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem}p{margin-top:0;margin-bottom:1rem}abbr[data-original-title],abbr[title]{text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;border-bottom:0;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#007bff;text-decoration:none;background-color:transparent}a:hover{color:#0056b3;text-decoration:underline}a:not([href]):not([class]){color:inherit;text-decoration:none}a:not([href]):not([class]):hover{color:inherit;text-decoration:none}code,kbd,pre,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em}pre{margin-top:0;margin-bottom:1rem;overflow:auto;-ms-overflow-style:scrollbar}figure{margin:0 0 1rem}img{vertical-align:middle;border-style:none}svg{overflow:hidden;vertical-align:middle}table{border-collapse:collapse}caption{padding-top:.75rem;padding-bottom:.75rem;color:#6c757d;text-align:left;caption-side:bottom}th{text-align:inherit;text-align:-webkit-match-parent}label{display:inline-block;margin-bottom:.5rem}button{border-radius:0}button:focus:not(:focus-visible){outline:0}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,input{overflow:visible}button,select{text-transform:none}[role=button]{cursor:pointer}select{word-wrap:normal}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{padding:0;border-style:none}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}textarea{overflow:auto;resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;max-width:100%;padding:0;margin-bottom:.5rem;font-size:1.5rem;line-height:inherit;color:inherit;white-space:normal}progress{vertical-align:baseline}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:none}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}summary{display:list-item;cursor:pointer}template{display:none}[hidden]{display:none!important}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{margin-bottom:.5rem;font-weight:500;line-height:1.2}.h1,h1{font-size:2.5rem}.h2,h2{font-size:2rem}.h3,h3{font-size:1.75rem}.h4,h4{font-size:1.5rem}.h5,h5{font-size:1.25rem}.h6,h6{font-size:1rem}.lead{font-size:1.25rem;font-weight:300}.display-1{font-size:6rem;font-weight:300;line-height:1.2}.display-2{font-size:5.5rem;font-weight:300;line-height:1.2}.display-3{font-size:4.5rem;font-weight:300;line-height:1.2}.display-4{font-size:3.5rem;font-weight:300;line-height:1.2}hr{margin-top:1rem;margin-bottom:1rem;border:0;border-top:1px solid rgba(0,0,0,.1)}.small,small{font-size:80%;font-weight:400}.mark,mark{padding:.2em;background-color:#fcf8e3}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none}.list-inline-item{display:inline-block}.list-inline-item:not(:last-child){margin-right:.5rem}.initialism{font-size:90%;text-transform:uppercase}.blockquote{margin-bottom:1rem;font-size:1.25rem}.blockquote-footer{display:block;font-size:80%;color:#6c757d}.blockquote-footer::before{content:"\2014\00A0"}.img-fluid{max-width:100%;height:auto}.img-thumbnail{padding:.25rem;background-color:#fff;border:1px solid #dee2e6;border-radius:.25rem;max-width:100%;height:auto}.figure{display:inline-block}.figure-img{margin-bottom:.5rem;line-height:1}.figure-caption{font-size:90%;color:#6c757d}code{font-size:87.5%;color:#e83e8c;word-wrap:break-word}a>code{color:inherit}kbd{padding:.2rem .4rem;font-size:87.5%;color:#fff;background-color:#212529;border-radius:.2rem}kbd kbd{padding:0;font-size:100%;font-weight:700}pre{display:block;font-size:87.5%;color:#212529}pre code{font-size:inherit;color:inherit;word-break:normal}.pre-scrollable{max-height:340px;overflow-y:scroll}.container,.container-fluid,.container-lg,.container-md,.container-sm,.container-xl{width:100%;padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media (min-width:576px){.container,.container-sm{max-width:540px}}@media (min-width:768px){.container,.container-md,.container-sm{max-width:720px}}@media (min-width:992px){.container,.container-lg,.container-md,.container-sm{max-width:960px}}@media (min-width:1200px){.container,.container-lg,.container-md,.container-sm,.container-xl{max-width:1140px}}.row{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;margin-right:-15px;margin-left:-15px}.no-gutters{margin-right:0;margin-left:0}.no-gutters>.col,.no-gutters>[class*=col-]{padding-right:0;padding-left:0}.col,.col-1,.col-10,.col-11,.col-12,.col-2,.col-3,.col-4,.col-5,.col-6,.col-7,.col-8,.col-9,.col-auto,.col-lg,.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-auto,.col-md,.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-auto,.col-sm,.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-auto,.col-xl,.col-xl-1,.col-xl-10,.col-xl-11,.col-xl-12,.col-xl-2,.col-xl-3,.col-xl-4,.col-xl-5,.col-xl-6,.col-xl-7,.col-xl-8,.col-xl-9,.col-xl-auto{position:relative;width:100%;padding-right:15px;padding-left:15px}.col{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;max-width:100%}.row-cols-1>*{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.row-cols-2>*{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.row-cols-3>*{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.row-cols-4>*{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.row-cols-5>*{-ms-flex:0 0 20%;flex:0 0 20%;max-width:20%}.row-cols-6>*{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-first{-ms-flex-order:-1;order:-1}.order-last{-ms-flex-order:13;order:13}.order-0{-ms-flex-order:0;order:0}.order-1{-ms-flex-order:1;order:1}.order-2{-ms-flex-order:2;order:2}.order-3{-ms-flex-order:3;order:3}.order-4{-ms-flex-order:4;order:4}.order-5{-ms-flex-order:5;order:5}.order-6{-ms-flex-order:6;order:6}.order-7{-ms-flex-order:7;order:7}.order-8{-ms-flex-order:8;order:8}.order-9{-ms-flex-order:9;order:9}.order-10{-ms-flex-order:10;order:10}.order-11{-ms-flex-order:11;order:11}.order-12{-ms-flex-order:12;order:12}.offset-1{margin-left:8.333333%}.offset-2{margin-left:16.666667%}.offset-3{margin-left:25%}.offset-4{margin-left:33.333333%}.offset-5{margin-left:41.666667%}.offset-6{margin-left:50%}.offset-7{margin-left:58.333333%}.offset-8{margin-left:66.666667%}.offset-9{margin-left:75%}.offset-10{margin-left:83.333333%}.offset-11{margin-left:91.666667%}@media (min-width:576px){.col-sm{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;max-width:100%}.row-cols-sm-1>*{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.row-cols-sm-2>*{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.row-cols-sm-3>*{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.row-cols-sm-4>*{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.row-cols-sm-5>*{-ms-flex:0 0 20%;flex:0 0 20%;max-width:20%}.row-cols-sm-6>*{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-sm-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-sm-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-sm-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-sm-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-sm-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-sm-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-sm-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-sm-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-sm-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-sm-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-sm-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-sm-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-sm-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-sm-first{-ms-flex-order:-1;order:-1}.order-sm-last{-ms-flex-order:13;order:13}.order-sm-0{-ms-flex-order:0;order:0}.order-sm-1{-ms-flex-order:1;order:1}.order-sm-2{-ms-flex-order:2;order:2}.order-sm-3{-ms-flex-order:3;order:3}.order-sm-4{-ms-flex-order:4;order:4}.order-sm-5{-ms-flex-order:5;order:5}.order-sm-6{-ms-flex-order:6;order:6}.order-sm-7{-ms-flex-order:7;order:7}.order-sm-8{-ms-flex-order:8;order:8}.order-sm-9{-ms-flex-order:9;order:9}.order-sm-10{-ms-flex-order:10;order:10}.order-sm-11{-ms-flex-order:11;order:11}.order-sm-12{-ms-flex-order:12;order:12}.offset-sm-0{margin-left:0}.offset-sm-1{margin-left:8.333333%}.offset-sm-2{margin-left:16.666667%}.offset-sm-3{margin-left:25%}.offset-sm-4{margin-left:33.333333%}.offset-sm-5{margin-left:41.666667%}.offset-sm-6{margin-left:50%}.offset-sm-7{margin-left:58.333333%}.offset-sm-8{margin-left:66.666667%}.offset-sm-9{margin-left:75%}.offset-sm-10{margin-left:83.333333%}.offset-sm-11{margin-left:91.666667%}}@media (min-width:768px){.col-md{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;max-width:100%}.row-cols-md-1>*{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.row-cols-md-2>*{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.row-cols-md-3>*{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.row-cols-md-4>*{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.row-cols-md-5>*{-ms-flex:0 0 20%;flex:0 0 20%;max-width:20%}.row-cols-md-6>*{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-md-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-md-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-md-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-md-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-md-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-md-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-md-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-md-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-md-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-md-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-md-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-md-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-md-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-md-first{-ms-flex-order:-1;order:-1}.order-md-last{-ms-flex-order:13;order:13}.order-md-0{-ms-flex-order:0;order:0}.order-md-1{-ms-flex-order:1;order:1}.order-md-2{-ms-flex-order:2;order:2}.order-md-3{-ms-flex-order:3;order:3}.order-md-4{-ms-flex-order:4;order:4}.order-md-5{-ms-flex-order:5;order:5}.order-md-6{-ms-flex-order:6;order:6}.order-md-7{-ms-flex-order:7;order:7}.order-md-8{-ms-flex-order:8;order:8}.order-md-9{-ms-flex-order:9;order:9}.order-md-10{-ms-flex-order:10;order:10}.order-md-11{-ms-flex-order:11;order:11}.order-md-12{-ms-flex-order:12;order:12}.offset-md-0{margin-left:0}.offset-md-1{margin-left:8.333333%}.offset-md-2{margin-left:16.666667%}.offset-md-3{margin-left:25%}.offset-md-4{margin-left:33.333333%}.offset-md-5{margin-left:41.666667%}.offset-md-6{margin-left:50%}.offset-md-7{margin-left:58.333333%}.offset-md-8{margin-left:66.666667%}.offset-md-9{margin-left:75%}.offset-md-10{margin-left:83.333333%}.offset-md-11{margin-left:91.666667%}}@media (min-width:992px){.col-lg{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;max-width:100%}.row-cols-lg-1>*{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.row-cols-lg-2>*{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.row-cols-lg-3>*{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.row-cols-lg-4>*{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.row-cols-lg-5>*{-ms-flex:0 0 20%;flex:0 0 20%;max-width:20%}.row-cols-lg-6>*{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-lg-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-lg-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-lg-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-lg-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-lg-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-lg-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-lg-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-lg-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-lg-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-lg-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-lg-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-lg-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-lg-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-lg-first{-ms-flex-order:-1;order:-1}.order-lg-last{-ms-flex-order:13;order:13}.order-lg-0{-ms-flex-order:0;order:0}.order-lg-1{-ms-flex-order:1;order:1}.order-lg-2{-ms-flex-order:2;order:2}.order-lg-3{-ms-flex-order:3;order:3}.order-lg-4{-ms-flex-order:4;order:4}.order-lg-5{-ms-flex-order:5;order:5}.order-lg-6{-ms-flex-order:6;order:6}.order-lg-7{-ms-flex-order:7;order:7}.order-lg-8{-ms-flex-order:8;order:8}.order-lg-9{-ms-flex-order:9;order:9}.order-lg-10{-ms-flex-order:10;order:10}.order-lg-11{-ms-flex-order:11;order:11}.order-lg-12{-ms-flex-order:12;order:12}.offset-lg-0{margin-left:0}.offset-lg-1{margin-left:8.333333%}.offset-lg-2{margin-left:16.666667%}.offset-lg-3{margin-left:25%}.offset-lg-4{margin-left:33.333333%}.offset-lg-5{margin-left:41.666667%}.offset-lg-6{margin-left:50%}.offset-lg-7{margin-left:58.333333%}.offset-lg-8{margin-left:66.666667%}.offset-lg-9{margin-left:75%}.offset-lg-10{margin-left:83.333333%}.offset-lg-11{margin-left:91.666667%}}@media (min-width:1200px){.col-xl{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;max-width:100%}.row-cols-xl-1>*{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.row-cols-xl-2>*{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.row-cols-xl-3>*{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.row-cols-xl-4>*{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.row-cols-xl-5>*{-ms-flex:0 0 20%;flex:0 0 20%;max-width:20%}.row-cols-xl-6>*{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-xl-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-xl-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-xl-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-xl-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-xl-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-xl-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-xl-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-xl-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-xl-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-xl-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-xl-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-xl-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-xl-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-xl-first{-ms-flex-order:-1;order:-1}.order-xl-last{-ms-flex-order:13;order:13}.order-xl-0{-ms-flex-order:0;order:0}.order-xl-1{-ms-flex-order:1;order:1}.order-xl-2{-ms-flex-order:2;order:2}.order-xl-3{-ms-flex-order:3;order:3}.order-xl-4{-ms-flex-order:4;order:4}.order-xl-5{-ms-flex-order:5;order:5}.order-xl-6{-ms-flex-order:6;order:6}.order-xl-7{-ms-flex-order:7;order:7}.order-xl-8{-ms-flex-order:8;order:8}.order-xl-9{-ms-flex-order:9;order:9}.order-xl-10{-ms-flex-order:10;order:10}.order-xl-11{-ms-flex-order:11;order:11}.order-xl-12{-ms-flex-order:12;order:12}.offset-xl-0{margin-left:0}.offset-xl-1{margin-left:8.333333%}.offset-xl-2{margin-left:16.666667%}.offset-xl-3{margin-left:25%}.offset-xl-4{margin-left:33.333333%}.offset-xl-5{margin-left:41.666667%}.offset-xl-6{margin-left:50%}.offset-xl-7{margin-left:58.333333%}.offset-xl-8{margin-left:66.666667%}.offset-xl-9{margin-left:75%}.offset-xl-10{margin-left:83.333333%}.offset-xl-11{margin-left:91.666667%}}.table{width:100%;margin-bottom:1rem;color:#212529}.table td,.table th{padding:.75rem;vertical-align:top;border-top:1px solid #dee2e6}.table thead th{vertical-align:bottom;border-bottom:2px solid #dee2e6}.table tbody+tbody{border-top:2px solid #dee2e6}.table-sm td,.table-sm th{padding:.3rem}.table-bordered{border:1px solid #dee2e6}.table-bordered td,.table-bordered th{border:1px solid #dee2e6}.table-bordered thead td,.table-bordered thead th{border-bottom-width:2px}.table-borderless tbody+tbody,.table-borderless td,.table-borderless th,.table-borderless thead th{border:0}.table-striped tbody tr:nth-of-type(odd){background-color:rgba(0,0,0,.05)}.table-hover tbody tr:hover{color:#212529;background-color:rgba(0,0,0,.075)}.table-primary,.table-primary>td,.table-primary>th{background-color:#b8daff}.table-primary tbody+tbody,.table-primary td,.table-primary th,.table-primary thead th{border-color:#7abaff}.table-hover .table-primary:hover{background-color:#9fcdff}.table-hover .table-primary:hover>td,.table-hover .table-primary:hover>th{background-color:#9fcdff}.table-secondary,.table-secondary>td,.table-secondary>th{background-color:#d6d8db}.table-secondary tbody+tbody,.table-secondary td,.table-secondary th,.table-secondary thead th{border-color:#b3b7bb}.table-hover .table-secondary:hover{background-color:#c8cbcf}.table-hover .table-secondary:hover>td,.table-hover .table-secondary:hover>th{background-color:#c8cbcf}.table-success,.table-success>td,.table-success>th{background-color:#c3e6cb}.table-success tbody+tbody,.table-success td,.table-success th,.table-success thead th{border-color:#8fd19e}.table-hover .table-success:hover{background-color:#b1dfbb}.table-hover .table-success:hover>td,.table-hover .table-success:hover>th{background-color:#b1dfbb}.table-info,.table-info>td,.table-info>th{background-color:#bee5eb}.table-info tbody+tbody,.table-info td,.table-info th,.table-info thead th{border-color:#86cfda}.table-hover .table-info:hover{background-color:#abdde5}.table-hover .table-info:hover>td,.table-hover .table-info:hover>th{background-color:#abdde5}.table-warning,.table-warning>td,.table-warning>th{background-color:#ffeeba}.table-warning tbody+tbody,.table-warning td,.table-warning th,.table-warning thead th{border-color:#ffdf7e}.table-hover .table-warning:hover{background-color:#ffe8a1}.table-hover .table-warning:hover>td,.table-hover .table-warning:hover>th{background-color:#ffe8a1}.table-danger,.table-danger>td,.table-danger>th{background-color:#f5c6cb}.table-danger tbody+tbody,.table-danger td,.table-danger th,.table-danger thead th{border-color:#ed969e}.table-hover .table-danger:hover{background-color:#f1b0b7}.table-hover .table-danger:hover>td,.table-hover .table-danger:hover>th{background-color:#f1b0b7}.table-light,.table-light>td,.table-light>th{background-color:#fdfdfe}.table-light tbody+tbody,.table-light td,.table-light th,.table-light thead th{border-color:#fbfcfc}.table-hover .table-light:hover{background-color:#ececf6}.table-hover .table-light:hover>td,.table-hover .table-light:hover>th{background-color:#ececf6}.table-dark,.table-dark>td,.table-dark>th{background-color:#c6c8ca}.table-dark tbody+tbody,.table-dark td,.table-dark th,.table-dark thead th{border-color:#95999c}.table-hover .table-dark:hover{background-color:#b9bbbe}.table-hover .table-dark:hover>td,.table-hover .table-dark:hover>th{background-color:#b9bbbe}.table-active,.table-active>td,.table-active>th{background-color:rgba(0,0,0,.075)}.table-hover .table-active:hover{background-color:rgba(0,0,0,.075)}.table-hover .table-active:hover>td,.table-hover .table-active:hover>th{background-color:rgba(0,0,0,.075)}.table .thead-dark th{color:#fff;background-color:#343a40;border-color:#454d55}.table .thead-light th{color:#495057;background-color:#e9ecef;border-color:#dee2e6}.table-dark{color:#fff;background-color:#343a40}.table-dark td,.table-dark th,.table-dark thead th{border-color:#454d55}.table-dark.table-bordered{border:0}.table-dark.table-striped tbody tr:nth-of-type(odd){background-color:rgba(255,255,255,.05)}.table-dark.table-hover tbody tr:hover{color:#fff;background-color:rgba(255,255,255,.075)}@media (max-width:575.98px){.table-responsive-sm{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-sm>.table-bordered{border:0}}@media (max-width:767.98px){.table-responsive-md{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-md>.table-bordered{border:0}}@media (max-width:991.98px){.table-responsive-lg{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-lg>.table-bordered{border:0}}@media (max-width:1199.98px){.table-responsive-xl{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-xl>.table-bordered{border:0}}.table-responsive{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive>.table-bordered{border:0}.form-control{display:block;width:100%;height:calc(1.5em + .75rem + 2px);padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#495057;background-color:#fff;background-clip:padding-box;border:1px solid #ced4da;border-radius:.25rem;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-control{transition:none}}.form-control::-ms-expand{background-color:transparent;border:0}.form-control:-moz-focusring{color:transparent;text-shadow:0 0 0 #495057}.form-control:focus{color:#495057;background-color:#fff;border-color:#80bdff;outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.form-control::-webkit-input-placeholder{color:#6c757d;opacity:1}.form-control::-moz-placeholder{color:#6c757d;opacity:1}.form-control:-ms-input-placeholder{color:#6c757d;opacity:1}.form-control::-ms-input-placeholder{color:#6c757d;opacity:1}.form-control::placeholder{color:#6c757d;opacity:1}.form-control:disabled,.form-control[readonly]{background-color:#e9ecef;opacity:1}input[type=date].form-control,input[type=datetime-local].form-control,input[type=month].form-control,input[type=time].form-control{-webkit-appearance:none;-moz-appearance:none;appearance:none}select.form-control:focus::-ms-value{color:#495057;background-color:#fff}.form-control-file,.form-control-range{display:block;width:100%}.col-form-label{padding-top:calc(.375rem + 1px);padding-bottom:calc(.375rem + 1px);margin-bottom:0;font-size:inherit;line-height:1.5}.col-form-label-lg{padding-top:calc(.5rem + 1px);padding-bottom:calc(.5rem + 1px);font-size:1.25rem;line-height:1.5}.col-form-label-sm{padding-top:calc(.25rem + 1px);padding-bottom:calc(.25rem + 1px);font-size:.875rem;line-height:1.5}.form-control-plaintext{display:block;width:100%;padding:.375rem 0;margin-bottom:0;font-size:1rem;line-height:1.5;color:#212529;background-color:transparent;border:solid transparent;border-width:1px 0}.form-control-plaintext.form-control-lg,.form-control-plaintext.form-control-sm{padding-right:0;padding-left:0}.form-control-sm{height:calc(1.5em + .5rem + 2px);padding:.25rem .5rem;font-size:.875rem;line-height:1.5;border-radius:.2rem}.form-control-lg{height:calc(1.5em + 1rem + 2px);padding:.5rem 1rem;font-size:1.25rem;line-height:1.5;border-radius:.3rem}select.form-control[multiple],select.form-control[size]{height:auto}textarea.form-control{height:auto}.form-group{margin-bottom:1rem}.form-text{display:block;margin-top:.25rem}.form-row{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;margin-right:-5px;margin-left:-5px}.form-row>.col,.form-row>[class*=col-]{padding-right:5px;padding-left:5px}.form-check{position:relative;display:block;padding-left:1.25rem}.form-check-input{position:absolute;margin-top:.3rem;margin-left:-1.25rem}.form-check-input:disabled~.form-check-label,.form-check-input[disabled]~.form-check-label{color:#6c757d}.form-check-label{margin-bottom:0}.form-check-inline{display:-ms-inline-flexbox;display:inline-flex;-ms-flex-align:center;align-items:center;padding-left:0;margin-right:.75rem}.form-check-inline .form-check-input{position:static;margin-top:0;margin-right:.3125rem;margin-left:0}.valid-feedback{display:none;width:100%;margin-top:.25rem;font-size:80%;color:#28a745}.valid-tooltip{position:absolute;top:100%;left:0;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;line-height:1.5;color:#fff;background-color:rgba(40,167,69,.9);border-radius:.25rem}.form-row>.col>.valid-tooltip,.form-row>[class*=col-]>.valid-tooltip{left:5px}.is-valid~.valid-feedback,.is-valid~.valid-tooltip,.was-validated :valid~.valid-feedback,.was-validated :valid~.valid-tooltip{display:block}.form-control.is-valid,.was-validated .form-control:valid{border-color:#28a745;padding-right:calc(1.5em + .75rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath fill='%2328a745' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right calc(.375em + .1875rem) center;background-size:calc(.75em + .375rem) calc(.75em + .375rem)}.form-control.is-valid:focus,.was-validated .form-control:valid:focus{border-color:#28a745;box-shadow:0 0 0 .2rem rgba(40,167,69,.25)}.was-validated textarea.form-control:valid,textarea.form-control.is-valid{padding-right:calc(1.5em + .75rem);background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem)}.custom-select.is-valid,.was-validated .custom-select:valid{border-color:#28a745;padding-right:calc(.75em + 2.3125rem);background:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") right .75rem center/8px 10px no-repeat,#fff url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath fill='%2328a745' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e") center right 1.75rem/calc(.75em + .375rem) calc(.75em + .375rem) no-repeat}.custom-select.is-valid:focus,.was-validated .custom-select:valid:focus{border-color:#28a745;box-shadow:0 0 0 .2rem rgba(40,167,69,.25)}.form-check-input.is-valid~.form-check-label,.was-validated .form-check-input:valid~.form-check-label{color:#28a745}.form-check-input.is-valid~.valid-feedback,.form-check-input.is-valid~.valid-tooltip,.was-validated .form-check-input:valid~.valid-feedback,.was-validated .form-check-input:valid~.valid-tooltip{display:block}.custom-control-input.is-valid~.custom-control-label,.was-validated .custom-control-input:valid~.custom-control-label{color:#28a745}.custom-control-input.is-valid~.custom-control-label::before,.was-validated .custom-control-input:valid~.custom-control-label::before{border-color:#28a745}.custom-control-input.is-valid:checked~.custom-control-label::before,.was-validated .custom-control-input:valid:checked~.custom-control-label::before{border-color:#34ce57;background-color:#34ce57}.custom-control-input.is-valid:focus~.custom-control-label::before,.was-validated .custom-control-input:valid:focus~.custom-control-label::before{box-shadow:0 0 0 .2rem rgba(40,167,69,.25)}.custom-control-input.is-valid:focus:not(:checked)~.custom-control-label::before,.was-validated .custom-control-input:valid:focus:not(:checked)~.custom-control-label::before{border-color:#28a745}.custom-file-input.is-valid~.custom-file-label,.was-validated .custom-file-input:valid~.custom-file-label{border-color:#28a745}.custom-file-input.is-valid:focus~.custom-file-label,.was-validated .custom-file-input:valid:focus~.custom-file-label{border-color:#28a745;box-shadow:0 0 0 .2rem rgba(40,167,69,.25)}.invalid-feedback{display:none;width:100%;margin-top:.25rem;font-size:80%;color:#dc3545}.invalid-tooltip{position:absolute;top:100%;left:0;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;line-height:1.5;color:#fff;background-color:rgba(220,53,69,.9);border-radius:.25rem}.form-row>.col>.invalid-tooltip,.form-row>[class*=col-]>.invalid-tooltip{left:5px}.is-invalid~.invalid-feedback,.is-invalid~.invalid-tooltip,.was-validated :invalid~.invalid-feedback,.was-validated :invalid~.invalid-tooltip{display:block}.form-control.is-invalid,.was-validated .form-control:invalid{border-color:#dc3545;padding-right:calc(1.5em + .75rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='none' stroke='%23dc3545' viewBox='0 0 12 12'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right calc(.375em + .1875rem) center;background-size:calc(.75em + .375rem) calc(.75em + .375rem)}.form-control.is-invalid:focus,.was-validated .form-control:invalid:focus{border-color:#dc3545;box-shadow:0 0 0 .2rem rgba(220,53,69,.25)}.was-validated textarea.form-control:invalid,textarea.form-control.is-invalid{padding-right:calc(1.5em + .75rem);background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem)}.custom-select.is-invalid,.was-validated .custom-select:invalid{border-color:#dc3545;padding-right:calc(.75em + 2.3125rem);background:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") right .75rem center/8px 10px no-repeat,#fff url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='none' stroke='%23dc3545' viewBox='0 0 12 12'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e") center right 1.75rem/calc(.75em + .375rem) calc(.75em + .375rem) no-repeat}.custom-select.is-invalid:focus,.was-validated .custom-select:invalid:focus{border-color:#dc3545;box-shadow:0 0 0 .2rem rgba(220,53,69,.25)}.form-check-input.is-invalid~.form-check-label,.was-validated .form-check-input:invalid~.form-check-label{color:#dc3545}.form-check-input.is-invalid~.invalid-feedback,.form-check-input.is-invalid~.invalid-tooltip,.was-validated .form-check-input:invalid~.invalid-feedback,.was-validated .form-check-input:invalid~.invalid-tooltip{display:block}.custom-control-input.is-invalid~.custom-control-label,.was-validated .custom-control-input:invalid~.custom-control-label{color:#dc3545}.custom-control-input.is-invalid~.custom-control-label::before,.was-validated .custom-control-input:invalid~.custom-control-label::before{border-color:#dc3545}.custom-control-input.is-invalid:checked~.custom-control-label::before,.was-validated .custom-control-input:invalid:checked~.custom-control-label::before{border-color:#e4606d;background-color:#e4606d}.custom-control-input.is-invalid:focus~.custom-control-label::before,.was-validated .custom-control-input:invalid:focus~.custom-control-label::before{box-shadow:0 0 0 .2rem rgba(220,53,69,.25)}.custom-control-input.is-invalid:focus:not(:checked)~.custom-control-label::before,.was-validated .custom-control-input:invalid:focus:not(:checked)~.custom-control-label::before{border-color:#dc3545}.custom-file-input.is-invalid~.custom-file-label,.was-validated .custom-file-input:invalid~.custom-file-label{border-color:#dc3545}.custom-file-input.is-invalid:focus~.custom-file-label,.was-validated .custom-file-input:invalid:focus~.custom-file-label{border-color:#dc3545;box-shadow:0 0 0 .2rem rgba(220,53,69,.25)}.form-inline{display:-ms-flexbox;display:flex;-ms-flex-flow:row wrap;flex-flow:row wrap;-ms-flex-align:center;align-items:center}.form-inline .form-check{width:100%}@media (min-width:576px){.form-inline label{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center;margin-bottom:0}.form-inline .form-group{display:-ms-flexbox;display:flex;-ms-flex:0 0 auto;flex:0 0 auto;-ms-flex-flow:row wrap;flex-flow:row wrap;-ms-flex-align:center;align-items:center;margin-bottom:0}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .form-control-plaintext{display:inline-block}.form-inline .custom-select,.form-inline .input-group{width:auto}.form-inline .form-check{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center;width:auto;padding-left:0}.form-inline .form-check-input{position:relative;-ms-flex-negative:0;flex-shrink:0;margin-top:0;margin-right:.25rem;margin-left:0}.form-inline .custom-control{-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center}.form-inline .custom-control-label{margin-bottom:0}}.btn{display:inline-block;font-weight:400;color:#212529;text-align:center;vertical-align:middle;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background-color:transparent;border:1px solid transparent;padding:.375rem .75rem;font-size:1rem;line-height:1.5;border-radius:.25rem;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.btn{transition:none}}.btn:hover{color:#212529;text-decoration:none}.btn.focus,.btn:focus{outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.btn.disabled,.btn:disabled{opacity:.65}.btn:not(:disabled):not(.disabled){cursor:pointer}a.btn.disabled,fieldset:disabled a.btn{pointer-events:none}.btn-primary{color:#fff;background-color:#007bff;border-color:#007bff}.btn-primary:hover{color:#fff;background-color:#0069d9;border-color:#0062cc}.btn-primary.focus,.btn-primary:focus{color:#fff;background-color:#0069d9;border-color:#0062cc;box-shadow:0 0 0 .2rem rgba(38,143,255,.5)}.btn-primary.disabled,.btn-primary:disabled{color:#fff;background-color:#007bff;border-color:#007bff}.btn-primary:not(:disabled):not(.disabled).active,.btn-primary:not(:disabled):not(.disabled):active,.show>.btn-primary.dropdown-toggle{color:#fff;background-color:#0062cc;border-color:#005cbf}.btn-primary:not(:disabled):not(.disabled).active:focus,.btn-primary:not(:disabled):not(.disabled):active:focus,.show>.btn-primary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(38,143,255,.5)}.btn-secondary{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-secondary:hover{color:#fff;background-color:#5a6268;border-color:#545b62}.btn-secondary.focus,.btn-secondary:focus{color:#fff;background-color:#5a6268;border-color:#545b62;box-shadow:0 0 0 .2rem rgba(130,138,145,.5)}.btn-secondary.disabled,.btn-secondary:disabled{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-secondary:not(:disabled):not(.disabled).active,.btn-secondary:not(:disabled):not(.disabled):active,.show>.btn-secondary.dropdown-toggle{color:#fff;background-color:#545b62;border-color:#4e555b}.btn-secondary:not(:disabled):not(.disabled).active:focus,.btn-secondary:not(:disabled):not(.disabled):active:focus,.show>.btn-secondary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(130,138,145,.5)}.btn-success{color:#fff;background-color:#28a745;border-color:#28a745}.btn-success:hover{color:#fff;background-color:#218838;border-color:#1e7e34}.btn-success.focus,.btn-success:focus{color:#fff;background-color:#218838;border-color:#1e7e34;box-shadow:0 0 0 .2rem rgba(72,180,97,.5)}.btn-success.disabled,.btn-success:disabled{color:#fff;background-color:#28a745;border-color:#28a745}.btn-success:not(:disabled):not(.disabled).active,.btn-success:not(:disabled):not(.disabled):active,.show>.btn-success.dropdown-toggle{color:#fff;background-color:#1e7e34;border-color:#1c7430}.btn-success:not(:disabled):not(.disabled).active:focus,.btn-success:not(:disabled):not(.disabled):active:focus,.show>.btn-success.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(72,180,97,.5)}.btn-info{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-info:hover{color:#fff;background-color:#138496;border-color:#117a8b}.btn-info.focus,.btn-info:focus{color:#fff;background-color:#138496;border-color:#117a8b;box-shadow:0 0 0 .2rem rgba(58,176,195,.5)}.btn-info.disabled,.btn-info:disabled{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-info:not(:disabled):not(.disabled).active,.btn-info:not(:disabled):not(.disabled):active,.show>.btn-info.dropdown-toggle{color:#fff;background-color:#117a8b;border-color:#10707f}.btn-info:not(:disabled):not(.disabled).active:focus,.btn-info:not(:disabled):not(.disabled):active:focus,.show>.btn-info.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(58,176,195,.5)}.btn-warning{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-warning:hover{color:#212529;background-color:#e0a800;border-color:#d39e00}.btn-warning.focus,.btn-warning:focus{color:#212529;background-color:#e0a800;border-color:#d39e00;box-shadow:0 0 0 .2rem rgba(222,170,12,.5)}.btn-warning.disabled,.btn-warning:disabled{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-warning:not(:disabled):not(.disabled).active,.btn-warning:not(:disabled):not(.disabled):active,.show>.btn-warning.dropdown-toggle{color:#212529;background-color:#d39e00;border-color:#c69500}.btn-warning:not(:disabled):not(.disabled).active:focus,.btn-warning:not(:disabled):not(.disabled):active:focus,.show>.btn-warning.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(222,170,12,.5)}.btn-danger{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-danger:hover{color:#fff;background-color:#c82333;border-color:#bd2130}.btn-danger.focus,.btn-danger:focus{color:#fff;background-color:#c82333;border-color:#bd2130;box-shadow:0 0 0 .2rem rgba(225,83,97,.5)}.btn-danger.disabled,.btn-danger:disabled{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-danger:not(:disabled):not(.disabled).active,.btn-danger:not(:disabled):not(.disabled):active,.show>.btn-danger.dropdown-toggle{color:#fff;background-color:#bd2130;border-color:#b21f2d}.btn-danger:not(:disabled):not(.disabled).active:focus,.btn-danger:not(:disabled):not(.disabled):active:focus,.show>.btn-danger.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(225,83,97,.5)}.btn-light{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-light:hover{color:#212529;background-color:#e2e6ea;border-color:#dae0e5}.btn-light.focus,.btn-light:focus{color:#212529;background-color:#e2e6ea;border-color:#dae0e5;box-shadow:0 0 0 .2rem rgba(216,217,219,.5)}.btn-light.disabled,.btn-light:disabled{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-light:not(:disabled):not(.disabled).active,.btn-light:not(:disabled):not(.disabled):active,.show>.btn-light.dropdown-toggle{color:#212529;background-color:#dae0e5;border-color:#d3d9df}.btn-light:not(:disabled):not(.disabled).active:focus,.btn-light:not(:disabled):not(.disabled):active:focus,.show>.btn-light.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(216,217,219,.5)}.btn-dark{color:#fff;background-color:#343a40;border-color:#343a40}.btn-dark:hover{color:#fff;background-color:#23272b;border-color:#1d2124}.btn-dark.focus,.btn-dark:focus{color:#fff;background-color:#23272b;border-color:#1d2124;box-shadow:0 0 0 .2rem rgba(82,88,93,.5)}.btn-dark.disabled,.btn-dark:disabled{color:#fff;background-color:#343a40;border-color:#343a40}.btn-dark:not(:disabled):not(.disabled).active,.btn-dark:not(:disabled):not(.disabled):active,.show>.btn-dark.dropdown-toggle{color:#fff;background-color:#1d2124;border-color:#171a1d}.btn-dark:not(:disabled):not(.disabled).active:focus,.btn-dark:not(:disabled):not(.disabled):active:focus,.show>.btn-dark.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(82,88,93,.5)}.btn-outline-primary{color:#007bff;border-color:#007bff}.btn-outline-primary:hover{color:#fff;background-color:#007bff;border-color:#007bff}.btn-outline-primary.focus,.btn-outline-primary:focus{box-shadow:0 0 0 .2rem rgba(0,123,255,.5)}.btn-outline-primary.disabled,.btn-outline-primary:disabled{color:#007bff;background-color:transparent}.btn-outline-primary:not(:disabled):not(.disabled).active,.btn-outline-primary:not(:disabled):not(.disabled):active,.show>.btn-outline-primary.dropdown-toggle{color:#fff;background-color:#007bff;border-color:#007bff}.btn-outline-primary:not(:disabled):not(.disabled).active:focus,.btn-outline-primary:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-primary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(0,123,255,.5)}.btn-outline-secondary{color:#6c757d;border-color:#6c757d}.btn-outline-secondary:hover{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-outline-secondary.focus,.btn-outline-secondary:focus{box-shadow:0 0 0 .2rem rgba(108,117,125,.5)}.btn-outline-secondary.disabled,.btn-outline-secondary:disabled{color:#6c757d;background-color:transparent}.btn-outline-secondary:not(:disabled):not(.disabled).active,.btn-outline-secondary:not(:disabled):not(.disabled):active,.show>.btn-outline-secondary.dropdown-toggle{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-outline-secondary:not(:disabled):not(.disabled).active:focus,.btn-outline-secondary:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-secondary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(108,117,125,.5)}.btn-outline-success{color:#28a745;border-color:#28a745}.btn-outline-success:hover{color:#fff;background-color:#28a745;border-color:#28a745}.btn-outline-success.focus,.btn-outline-success:focus{box-shadow:0 0 0 .2rem rgba(40,167,69,.5)}.btn-outline-success.disabled,.btn-outline-success:disabled{color:#28a745;background-color:transparent}.btn-outline-success:not(:disabled):not(.disabled).active,.btn-outline-success:not(:disabled):not(.disabled):active,.show>.btn-outline-success.dropdown-toggle{color:#fff;background-color:#28a745;border-color:#28a745}.btn-outline-success:not(:disabled):not(.disabled).active:focus,.btn-outline-success:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-success.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(40,167,69,.5)}.btn-outline-info{color:#17a2b8;border-color:#17a2b8}.btn-outline-info:hover{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-outline-info.focus,.btn-outline-info:focus{box-shadow:0 0 0 .2rem rgba(23,162,184,.5)}.btn-outline-info.disabled,.btn-outline-info:disabled{color:#17a2b8;background-color:transparent}.btn-outline-info:not(:disabled):not(.disabled).active,.btn-outline-info:not(:disabled):not(.disabled):active,.show>.btn-outline-info.dropdown-toggle{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-outline-info:not(:disabled):not(.disabled).active:focus,.btn-outline-info:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-info.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(23,162,184,.5)}.btn-outline-warning{color:#ffc107;border-color:#ffc107}.btn-outline-warning:hover{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-outline-warning.focus,.btn-outline-warning:focus{box-shadow:0 0 0 .2rem rgba(255,193,7,.5)}.btn-outline-warning.disabled,.btn-outline-warning:disabled{color:#ffc107;background-color:transparent}.btn-outline-warning:not(:disabled):not(.disabled).active,.btn-outline-warning:not(:disabled):not(.disabled):active,.show>.btn-outline-warning.dropdown-toggle{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-outline-warning:not(:disabled):not(.disabled).active:focus,.btn-outline-warning:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-warning.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(255,193,7,.5)}.btn-outline-danger{color:#dc3545;border-color:#dc3545}.btn-outline-danger:hover{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-outline-danger.focus,.btn-outline-danger:focus{box-shadow:0 0 0 .2rem rgba(220,53,69,.5)}.btn-outline-danger.disabled,.btn-outline-danger:disabled{color:#dc3545;background-color:transparent}.btn-outline-danger:not(:disabled):not(.disabled).active,.btn-outline-danger:not(:disabled):not(.disabled):active,.show>.btn-outline-danger.dropdown-toggle{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-outline-danger:not(:disabled):not(.disabled).active:focus,.btn-outline-danger:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-danger.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(220,53,69,.5)}.btn-outline-light{color:#f8f9fa;border-color:#f8f9fa}.btn-outline-light:hover{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-outline-light.focus,.btn-outline-light:focus{box-shadow:0 0 0 .2rem rgba(248,249,250,.5)}.btn-outline-light.disabled,.btn-outline-light:disabled{color:#f8f9fa;background-color:transparent}.btn-outline-light:not(:disabled):not(.disabled).active,.btn-outline-light:not(:disabled):not(.disabled):active,.show>.btn-outline-light.dropdown-toggle{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-outline-light:not(:disabled):not(.disabled).active:focus,.btn-outline-light:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-light.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(248,249,250,.5)}.btn-outline-dark{color:#343a40;border-color:#343a40}.btn-outline-dark:hover{color:#fff;background-color:#343a40;border-color:#343a40}.btn-outline-dark.focus,.btn-outline-dark:focus{box-shadow:0 0 0 .2rem rgba(52,58,64,.5)}.btn-outline-dark.disabled,.btn-outline-dark:disabled{color:#343a40;background-color:transparent}.btn-outline-dark:not(:disabled):not(.disabled).active,.btn-outline-dark:not(:disabled):not(.disabled):active,.show>.btn-outline-dark.dropdown-toggle{color:#fff;background-color:#343a40;border-color:#343a40}.btn-outline-dark:not(:disabled):not(.disabled).active:focus,.btn-outline-dark:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-dark.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(52,58,64,.5)}.btn-link{font-weight:400;color:#007bff;text-decoration:none}.btn-link:hover{color:#0056b3;text-decoration:underline}.btn-link.focus,.btn-link:focus{text-decoration:underline}.btn-link.disabled,.btn-link:disabled{color:#6c757d;pointer-events:none}.btn-group-lg>.btn,.btn-lg{padding:.5rem 1rem;font-size:1.25rem;line-height:1.5;border-radius:.3rem}.btn-group-sm>.btn,.btn-sm{padding:.25rem .5rem;font-size:.875rem;line-height:1.5;border-radius:.2rem}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:.5rem}input[type=button].btn-block,input[type=reset].btn-block,input[type=submit].btn-block{width:100%}.fade{transition:opacity .15s linear}@media (prefers-reduced-motion:reduce){.fade{transition:none}}.fade:not(.show){opacity:0}.collapse:not(.show){display:none}.collapsing{position:relative;height:0;overflow:hidden;transition:height .35s ease}@media (prefers-reduced-motion:reduce){.collapsing{transition:none}}.dropdown,.dropleft,.dropright,.dropup{position:relative}.dropdown-toggle{white-space:nowrap}.dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid;border-right:.3em solid transparent;border-bottom:0;border-left:.3em solid transparent}.dropdown-toggle:empty::after{margin-left:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:10rem;padding:.5rem 0;margin:.125rem 0 0;font-size:1rem;color:#212529;text-align:left;list-style:none;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.15);border-radius:.25rem}.dropdown-menu-left{right:auto;left:0}.dropdown-menu-right{right:0;left:auto}@media (min-width:576px){.dropdown-menu-sm-left{right:auto;left:0}.dropdown-menu-sm-right{right:0;left:auto}}@media (min-width:768px){.dropdown-menu-md-left{right:auto;left:0}.dropdown-menu-md-right{right:0;left:auto}}@media (min-width:992px){.dropdown-menu-lg-left{right:auto;left:0}.dropdown-menu-lg-right{right:0;left:auto}}@media (min-width:1200px){.dropdown-menu-xl-left{right:auto;left:0}.dropdown-menu-xl-right{right:0;left:auto}}.dropup .dropdown-menu{top:auto;bottom:100%;margin-top:0;margin-bottom:.125rem}.dropup .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:0;border-right:.3em solid transparent;border-bottom:.3em solid;border-left:.3em solid transparent}.dropup .dropdown-toggle:empty::after{margin-left:0}.dropright .dropdown-menu{top:0;right:auto;left:100%;margin-top:0;margin-left:.125rem}.dropright .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:0;border-bottom:.3em solid transparent;border-left:.3em solid}.dropright .dropdown-toggle:empty::after{margin-left:0}.dropright .dropdown-toggle::after{vertical-align:0}.dropleft .dropdown-menu{top:0;right:100%;left:auto;margin-top:0;margin-right:.125rem}.dropleft .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:""}.dropleft .dropdown-toggle::after{display:none}.dropleft .dropdown-toggle::before{display:inline-block;margin-right:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:.3em solid;border-bottom:.3em solid transparent}.dropleft .dropdown-toggle:empty::after{margin-left:0}.dropleft .dropdown-toggle::before{vertical-align:0}.dropdown-menu[x-placement^=bottom],.dropdown-menu[x-placement^=left],.dropdown-menu[x-placement^=right],.dropdown-menu[x-placement^=top]{right:auto;bottom:auto}.dropdown-divider{height:0;margin:.5rem 0;overflow:hidden;border-top:1px solid #e9ecef}.dropdown-item{display:block;width:100%;padding:.25rem 1.5rem;clear:both;font-weight:400;color:#212529;text-align:inherit;white-space:nowrap;background-color:transparent;border:0}.dropdown-item:focus,.dropdown-item:hover{color:#16181b;text-decoration:none;background-color:#e9ecef}.dropdown-item.active,.dropdown-item:active{color:#fff;text-decoration:none;background-color:#007bff}.dropdown-item.disabled,.dropdown-item:disabled{color:#adb5bd;pointer-events:none;background-color:transparent}.dropdown-menu.show{display:block}.dropdown-header{display:block;padding:.5rem 1.5rem;margin-bottom:0;font-size:.875rem;color:#6c757d;white-space:nowrap}.dropdown-item-text{display:block;padding:.25rem 1.5rem;color:#212529}.btn-group,.btn-group-vertical{position:relative;display:-ms-inline-flexbox;display:inline-flex;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{position:relative;-ms-flex:1 1 auto;flex:1 1 auto}.btn-group-vertical>.btn:hover,.btn-group>.btn:hover{z-index:1}.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus{z-index:1}.btn-toolbar{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-pack:start;justify-content:flex-start}.btn-toolbar .input-group{width:auto}.btn-group>.btn-group:not(:first-child),.btn-group>.btn:not(:first-child){margin-left:-1px}.btn-group>.btn-group:not(:last-child)>.btn,.btn-group>.btn:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:not(:first-child)>.btn,.btn-group>.btn:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.dropdown-toggle-split{padding-right:.5625rem;padding-left:.5625rem}.dropdown-toggle-split::after,.dropright .dropdown-toggle-split::after,.dropup .dropdown-toggle-split::after{margin-left:0}.dropleft .dropdown-toggle-split::before{margin-right:0}.btn-group-sm>.btn+.dropdown-toggle-split,.btn-sm+.dropdown-toggle-split{padding-right:.375rem;padding-left:.375rem}.btn-group-lg>.btn+.dropdown-toggle-split,.btn-lg+.dropdown-toggle-split{padding-right:.75rem;padding-left:.75rem}.btn-group-vertical{-ms-flex-direction:column;flex-direction:column;-ms-flex-align:start;align-items:flex-start;-ms-flex-pack:center;justify-content:center}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group{width:100%}.btn-group-vertical>.btn-group:not(:first-child),.btn-group-vertical>.btn:not(:first-child){margin-top:-1px}.btn-group-vertical>.btn-group:not(:last-child)>.btn,.btn-group-vertical>.btn:not(:last-child):not(.dropdown-toggle){border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:not(:first-child)>.btn,.btn-group-vertical>.btn:not(:first-child){border-top-left-radius:0;border-top-right-radius:0}.btn-group-toggle>.btn,.btn-group-toggle>.btn-group>.btn{margin-bottom:0}.btn-group-toggle>.btn input[type=checkbox],.btn-group-toggle>.btn input[type=radio],.btn-group-toggle>.btn-group>.btn input[type=checkbox],.btn-group-toggle>.btn-group>.btn input[type=radio]{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.input-group{position:relative;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-align:stretch;align-items:stretch;width:100%}.input-group>.custom-file,.input-group>.custom-select,.input-group>.form-control,.input-group>.form-control-plaintext{position:relative;-ms-flex:1 1 auto;flex:1 1 auto;width:1%;min-width:0;margin-bottom:0}.input-group>.custom-file+.custom-file,.input-group>.custom-file+.custom-select,.input-group>.custom-file+.form-control,.input-group>.custom-select+.custom-file,.input-group>.custom-select+.custom-select,.input-group>.custom-select+.form-control,.input-group>.form-control+.custom-file,.input-group>.form-control+.custom-select,.input-group>.form-control+.form-control,.input-group>.form-control-plaintext+.custom-file,.input-group>.form-control-plaintext+.custom-select,.input-group>.form-control-plaintext+.form-control{margin-left:-1px}.input-group>.custom-file .custom-file-input:focus~.custom-file-label,.input-group>.custom-select:focus,.input-group>.form-control:focus{z-index:3}.input-group>.custom-file .custom-file-input:focus{z-index:4}.input-group>.custom-select:not(:first-child),.input-group>.form-control:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.input-group>.custom-file{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center}.input-group>.custom-file:not(:first-child) .custom-file-label,.input-group>.custom-file:not(:last-child) .custom-file-label{border-top-left-radius:0;border-bottom-left-radius:0}.input-group:not(.has-validation)>.custom-file:not(:last-child) .custom-file-label::after,.input-group:not(.has-validation)>.custom-select:not(:last-child),.input-group:not(.has-validation)>.form-control:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.input-group.has-validation>.custom-file:nth-last-child(n+3) .custom-file-label::after,.input-group.has-validation>.custom-select:nth-last-child(n+3),.input-group.has-validation>.form-control:nth-last-child(n+3){border-top-right-radius:0;border-bottom-right-radius:0}.input-group-append,.input-group-prepend{display:-ms-flexbox;display:flex}.input-group-append .btn,.input-group-prepend .btn{position:relative;z-index:2}.input-group-append .btn:focus,.input-group-prepend .btn:focus{z-index:3}.input-group-append .btn+.btn,.input-group-append .btn+.input-group-text,.input-group-append .input-group-text+.btn,.input-group-append .input-group-text+.input-group-text,.input-group-prepend .btn+.btn,.input-group-prepend .btn+.input-group-text,.input-group-prepend .input-group-text+.btn,.input-group-prepend .input-group-text+.input-group-text{margin-left:-1px}.input-group-prepend{margin-right:-1px}.input-group-append{margin-left:-1px}.input-group-text{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;padding:.375rem .75rem;margin-bottom:0;font-size:1rem;font-weight:400;line-height:1.5;color:#495057;text-align:center;white-space:nowrap;background-color:#e9ecef;border:1px solid #ced4da;border-radius:.25rem}.input-group-text input[type=checkbox],.input-group-text input[type=radio]{margin-top:0}.input-group-lg>.custom-select,.input-group-lg>.form-control:not(textarea){height:calc(1.5em + 1rem + 2px)}.input-group-lg>.custom-select,.input-group-lg>.form-control,.input-group-lg>.input-group-append>.btn,.input-group-lg>.input-group-append>.input-group-text,.input-group-lg>.input-group-prepend>.btn,.input-group-lg>.input-group-prepend>.input-group-text{padding:.5rem 1rem;font-size:1.25rem;line-height:1.5;border-radius:.3rem}.input-group-sm>.custom-select,.input-group-sm>.form-control:not(textarea){height:calc(1.5em + .5rem + 2px)}.input-group-sm>.custom-select,.input-group-sm>.form-control,.input-group-sm>.input-group-append>.btn,.input-group-sm>.input-group-append>.input-group-text,.input-group-sm>.input-group-prepend>.btn,.input-group-sm>.input-group-prepend>.input-group-text{padding:.25rem .5rem;font-size:.875rem;line-height:1.5;border-radius:.2rem}.input-group-lg>.custom-select,.input-group-sm>.custom-select{padding-right:1.75rem}.input-group.has-validation>.input-group-append:nth-last-child(n+3)>.btn,.input-group.has-validation>.input-group-append:nth-last-child(n+3)>.input-group-text,.input-group:not(.has-validation)>.input-group-append:not(:last-child)>.btn,.input-group:not(.has-validation)>.input-group-append:not(:last-child)>.input-group-text,.input-group>.input-group-append:last-child>.btn:not(:last-child):not(.dropdown-toggle),.input-group>.input-group-append:last-child>.input-group-text:not(:last-child),.input-group>.input-group-prepend>.btn,.input-group>.input-group-prepend>.input-group-text{border-top-right-radius:0;border-bottom-right-radius:0}.input-group>.input-group-append>.btn,.input-group>.input-group-append>.input-group-text,.input-group>.input-group-prepend:first-child>.btn:not(:first-child),.input-group>.input-group-prepend:first-child>.input-group-text:not(:first-child),.input-group>.input-group-prepend:not(:first-child)>.btn,.input-group>.input-group-prepend:not(:first-child)>.input-group-text{border-top-left-radius:0;border-bottom-left-radius:0}.custom-control{position:relative;z-index:1;display:block;min-height:1.5rem;padding-left:1.5rem;-webkit-print-color-adjust:exact;color-adjust:exact}.custom-control-inline{display:-ms-inline-flexbox;display:inline-flex;margin-right:1rem}.custom-control-input{position:absolute;left:0;z-index:-1;width:1rem;height:1.25rem;opacity:0}.custom-control-input:checked~.custom-control-label::before{color:#fff;border-color:#007bff;background-color:#007bff}.custom-control-input:focus~.custom-control-label::before{box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.custom-control-input:focus:not(:checked)~.custom-control-label::before{border-color:#80bdff}.custom-control-input:not(:disabled):active~.custom-control-label::before{color:#fff;background-color:#b3d7ff;border-color:#b3d7ff}.custom-control-input:disabled~.custom-control-label,.custom-control-input[disabled]~.custom-control-label{color:#6c757d}.custom-control-input:disabled~.custom-control-label::before,.custom-control-input[disabled]~.custom-control-label::before{background-color:#e9ecef}.custom-control-label{position:relative;margin-bottom:0;vertical-align:top}.custom-control-label::before{position:absolute;top:.25rem;left:-1.5rem;display:block;width:1rem;height:1rem;pointer-events:none;content:"";background-color:#fff;border:#adb5bd solid 1px}.custom-control-label::after{position:absolute;top:.25rem;left:-1.5rem;display:block;width:1rem;height:1rem;content:"";background:50%/50% 50% no-repeat}.custom-checkbox .custom-control-label::before{border-radius:.25rem}.custom-checkbox .custom-control-input:checked~.custom-control-label::after{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath fill='%23fff' d='M6.564.75l-3.59 3.612-1.538-1.55L0 4.26l2.974 2.99L8 2.193z'/%3e%3c/svg%3e")}.custom-checkbox .custom-control-input:indeterminate~.custom-control-label::before{border-color:#007bff;background-color:#007bff}.custom-checkbox .custom-control-input:indeterminate~.custom-control-label::after{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='4' viewBox='0 0 4 4'%3e%3cpath stroke='%23fff' d='M0 2h4'/%3e%3c/svg%3e")}.custom-checkbox .custom-control-input:disabled:checked~.custom-control-label::before{background-color:rgba(0,123,255,.5)}.custom-checkbox .custom-control-input:disabled:indeterminate~.custom-control-label::before{background-color:rgba(0,123,255,.5)}.custom-radio .custom-control-label::before{border-radius:50%}.custom-radio .custom-control-input:checked~.custom-control-label::after{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23fff'/%3e%3c/svg%3e")}.custom-radio .custom-control-input:disabled:checked~.custom-control-label::before{background-color:rgba(0,123,255,.5)}.custom-switch{padding-left:2.25rem}.custom-switch .custom-control-label::before{left:-2.25rem;width:1.75rem;pointer-events:all;border-radius:.5rem}.custom-switch .custom-control-label::after{top:calc(.25rem + 2px);left:calc(-2.25rem + 2px);width:calc(1rem - 4px);height:calc(1rem - 4px);background-color:#adb5bd;border-radius:.5rem;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out,-webkit-transform .15s ease-in-out;transition:transform .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:transform .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out,-webkit-transform .15s ease-in-out}@media (prefers-reduced-motion:reduce){.custom-switch .custom-control-label::after{transition:none}}.custom-switch .custom-control-input:checked~.custom-control-label::after{background-color:#fff;-webkit-transform:translateX(.75rem);transform:translateX(.75rem)}.custom-switch .custom-control-input:disabled:checked~.custom-control-label::before{background-color:rgba(0,123,255,.5)}.custom-select{display:inline-block;width:100%;height:calc(1.5em + .75rem + 2px);padding:.375rem 1.75rem .375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#495057;vertical-align:middle;background:#fff url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") right .75rem center/8px 10px no-repeat;border:1px solid #ced4da;border-radius:.25rem;-webkit-appearance:none;-moz-appearance:none;appearance:none}.custom-select:focus{border-color:#80bdff;outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.custom-select:focus::-ms-value{color:#495057;background-color:#fff}.custom-select[multiple],.custom-select[size]:not([size="1"]){height:auto;padding-right:.75rem;background-image:none}.custom-select:disabled{color:#6c757d;background-color:#e9ecef}.custom-select::-ms-expand{display:none}.custom-select:-moz-focusring{color:transparent;text-shadow:0 0 0 #495057}.custom-select-sm{height:calc(1.5em + .5rem + 2px);padding-top:.25rem;padding-bottom:.25rem;padding-left:.5rem;font-size:.875rem}.custom-select-lg{height:calc(1.5em + 1rem + 2px);padding-top:.5rem;padding-bottom:.5rem;padding-left:1rem;font-size:1.25rem}.custom-file{position:relative;display:inline-block;width:100%;height:calc(1.5em + .75rem + 2px);margin-bottom:0}.custom-file-input{position:relative;z-index:2;width:100%;height:calc(1.5em + .75rem + 2px);margin:0;overflow:hidden;opacity:0}.custom-file-input:focus~.custom-file-label{border-color:#80bdff;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.custom-file-input:disabled~.custom-file-label,.custom-file-input[disabled]~.custom-file-label{background-color:#e9ecef}.custom-file-input:lang(en)~.custom-file-label::after{content:"Browse"}.custom-file-input~.custom-file-label[data-browse]::after{content:attr(data-browse)}.custom-file-label{position:absolute;top:0;right:0;left:0;z-index:1;height:calc(1.5em + .75rem + 2px);padding:.375rem .75rem;overflow:hidden;font-weight:400;line-height:1.5;color:#495057;background-color:#fff;border:1px solid #ced4da;border-radius:.25rem}.custom-file-label::after{position:absolute;top:0;right:0;bottom:0;z-index:3;display:block;height:calc(1.5em + .75rem);padding:.375rem .75rem;line-height:1.5;color:#495057;content:"Browse";background-color:#e9ecef;border-left:inherit;border-radius:0 .25rem .25rem 0}.custom-range{width:100%;height:1.4rem;padding:0;background-color:transparent;-webkit-appearance:none;-moz-appearance:none;appearance:none}.custom-range:focus{outline:0}.custom-range:focus::-webkit-slider-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .2rem rgba(0,123,255,.25)}.custom-range:focus::-moz-range-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .2rem rgba(0,123,255,.25)}.custom-range:focus::-ms-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .2rem rgba(0,123,255,.25)}.custom-range::-moz-focus-outer{border:0}.custom-range::-webkit-slider-thumb{width:1rem;height:1rem;margin-top:-.25rem;background-color:#007bff;border:0;border-radius:1rem;-webkit-transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;-webkit-appearance:none;appearance:none}@media (prefers-reduced-motion:reduce){.custom-range::-webkit-slider-thumb{-webkit-transition:none;transition:none}}.custom-range::-webkit-slider-thumb:active{background-color:#b3d7ff}.custom-range::-webkit-slider-runnable-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:#dee2e6;border-color:transparent;border-radius:1rem}.custom-range::-moz-range-thumb{width:1rem;height:1rem;background-color:#007bff;border:0;border-radius:1rem;-moz-transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;-moz-appearance:none;appearance:none}@media (prefers-reduced-motion:reduce){.custom-range::-moz-range-thumb{-moz-transition:none;transition:none}}.custom-range::-moz-range-thumb:active{background-color:#b3d7ff}.custom-range::-moz-range-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:#dee2e6;border-color:transparent;border-radius:1rem}.custom-range::-ms-thumb{width:1rem;height:1rem;margin-top:0;margin-right:.2rem;margin-left:.2rem;background-color:#007bff;border:0;border-radius:1rem;-ms-transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;appearance:none}@media (prefers-reduced-motion:reduce){.custom-range::-ms-thumb{-ms-transition:none;transition:none}}.custom-range::-ms-thumb:active{background-color:#b3d7ff}.custom-range::-ms-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:transparent;border-color:transparent;border-width:.5rem}.custom-range::-ms-fill-lower{background-color:#dee2e6;border-radius:1rem}.custom-range::-ms-fill-upper{margin-right:15px;background-color:#dee2e6;border-radius:1rem}.custom-range:disabled::-webkit-slider-thumb{background-color:#adb5bd}.custom-range:disabled::-webkit-slider-runnable-track{cursor:default}.custom-range:disabled::-moz-range-thumb{background-color:#adb5bd}.custom-range:disabled::-moz-range-track{cursor:default}.custom-range:disabled::-ms-thumb{background-color:#adb5bd}.custom-control-label::before,.custom-file-label,.custom-select{transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.custom-control-label::before,.custom-file-label,.custom-select{transition:none}}.nav{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;padding-left:0;margin-bottom:0;list-style:none}.nav-link{display:block;padding:.5rem 1rem}.nav-link:focus,.nav-link:hover{text-decoration:none}.nav-link.disabled{color:#6c757d;pointer-events:none;cursor:default}.nav-tabs{border-bottom:1px solid #dee2e6}.nav-tabs .nav-link{margin-bottom:-1px;border:1px solid transparent;border-top-left-radius:.25rem;border-top-right-radius:.25rem}.nav-tabs .nav-link:focus,.nav-tabs .nav-link:hover{border-color:#e9ecef #e9ecef #dee2e6}.nav-tabs .nav-link.disabled{color:#6c757d;background-color:transparent;border-color:transparent}.nav-tabs .nav-item.show .nav-link,.nav-tabs .nav-link.active{color:#495057;background-color:#fff;border-color:#dee2e6 #dee2e6 #fff}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.nav-pills .nav-link{border-radius:.25rem}.nav-pills .nav-link.active,.nav-pills .show>.nav-link{color:#fff;background-color:#007bff}.nav-fill .nav-item,.nav-fill>.nav-link{-ms-flex:1 1 auto;flex:1 1 auto;text-align:center}.nav-justified .nav-item,.nav-justified>.nav-link{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;text-align:center}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.navbar{position:relative;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-align:center;align-items:center;-ms-flex-pack:justify;justify-content:space-between;padding:.5rem 1rem}.navbar .container,.navbar .container-fluid,.navbar .container-lg,.navbar .container-md,.navbar .container-sm,.navbar .container-xl{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-align:center;align-items:center;-ms-flex-pack:justify;justify-content:space-between}.navbar-brand{display:inline-block;padding-top:.3125rem;padding-bottom:.3125rem;margin-right:1rem;font-size:1.25rem;line-height:inherit;white-space:nowrap}.navbar-brand:focus,.navbar-brand:hover{text-decoration:none}.navbar-nav{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;padding-left:0;margin-bottom:0;list-style:none}.navbar-nav .nav-link{padding-right:0;padding-left:0}.navbar-nav .dropdown-menu{position:static;float:none}.navbar-text{display:inline-block;padding-top:.5rem;padding-bottom:.5rem}.navbar-collapse{-ms-flex-preferred-size:100%;flex-basis:100%;-ms-flex-positive:1;flex-grow:1;-ms-flex-align:center;align-items:center}.navbar-toggler{padding:.25rem .75rem;font-size:1.25rem;line-height:1;background-color:transparent;border:1px solid transparent;border-radius:.25rem}.navbar-toggler:focus,.navbar-toggler:hover{text-decoration:none}.navbar-toggler-icon{display:inline-block;width:1.5em;height:1.5em;vertical-align:middle;content:"";background:50%/100% 100% no-repeat}.navbar-nav-scroll{max-height:75vh;overflow-y:auto}@media (max-width:575.98px){.navbar-expand-sm>.container,.navbar-expand-sm>.container-fluid,.navbar-expand-sm>.container-lg,.navbar-expand-sm>.container-md,.navbar-expand-sm>.container-sm,.navbar-expand-sm>.container-xl{padding-right:0;padding-left:0}}@media (min-width:576px){.navbar-expand-sm{-ms-flex-flow:row nowrap;flex-flow:row nowrap;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-sm .navbar-nav{-ms-flex-direction:row;flex-direction:row}.navbar-expand-sm .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-sm .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-sm>.container,.navbar-expand-sm>.container-fluid,.navbar-expand-sm>.container-lg,.navbar-expand-sm>.container-md,.navbar-expand-sm>.container-sm,.navbar-expand-sm>.container-xl{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-sm .navbar-nav-scroll{overflow:visible}.navbar-expand-sm .navbar-collapse{display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-sm .navbar-toggler{display:none}}@media (max-width:767.98px){.navbar-expand-md>.container,.navbar-expand-md>.container-fluid,.navbar-expand-md>.container-lg,.navbar-expand-md>.container-md,.navbar-expand-md>.container-sm,.navbar-expand-md>.container-xl{padding-right:0;padding-left:0}}@media (min-width:768px){.navbar-expand-md{-ms-flex-flow:row nowrap;flex-flow:row nowrap;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-md .navbar-nav{-ms-flex-direction:row;flex-direction:row}.navbar-expand-md .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-md .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-md>.container,.navbar-expand-md>.container-fluid,.navbar-expand-md>.container-lg,.navbar-expand-md>.container-md,.navbar-expand-md>.container-sm,.navbar-expand-md>.container-xl{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-md .navbar-nav-scroll{overflow:visible}.navbar-expand-md .navbar-collapse{display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-md .navbar-toggler{display:none}}@media (max-width:991.98px){.navbar-expand-lg>.container,.navbar-expand-lg>.container-fluid,.navbar-expand-lg>.container-lg,.navbar-expand-lg>.container-md,.navbar-expand-lg>.container-sm,.navbar-expand-lg>.container-xl{padding-right:0;padding-left:0}}@media (min-width:992px){.navbar-expand-lg{-ms-flex-flow:row nowrap;flex-flow:row nowrap;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-lg .navbar-nav{-ms-flex-direction:row;flex-direction:row}.navbar-expand-lg .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-lg .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-lg>.container,.navbar-expand-lg>.container-fluid,.navbar-expand-lg>.container-lg,.navbar-expand-lg>.container-md,.navbar-expand-lg>.container-sm,.navbar-expand-lg>.container-xl{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-lg .navbar-nav-scroll{overflow:visible}.navbar-expand-lg .navbar-collapse{display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-lg .navbar-toggler{display:none}}@media (max-width:1199.98px){.navbar-expand-xl>.container,.navbar-expand-xl>.container-fluid,.navbar-expand-xl>.container-lg,.navbar-expand-xl>.container-md,.navbar-expand-xl>.container-sm,.navbar-expand-xl>.container-xl{padding-right:0;padding-left:0}}@media (min-width:1200px){.navbar-expand-xl{-ms-flex-flow:row nowrap;flex-flow:row nowrap;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-xl .navbar-nav{-ms-flex-direction:row;flex-direction:row}.navbar-expand-xl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xl .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-xl>.container,.navbar-expand-xl>.container-fluid,.navbar-expand-xl>.container-lg,.navbar-expand-xl>.container-md,.navbar-expand-xl>.container-sm,.navbar-expand-xl>.container-xl{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-xl .navbar-nav-scroll{overflow:visible}.navbar-expand-xl .navbar-collapse{display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-xl .navbar-toggler{display:none}}.navbar-expand{-ms-flex-flow:row nowrap;flex-flow:row nowrap;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand>.container,.navbar-expand>.container-fluid,.navbar-expand>.container-lg,.navbar-expand>.container-md,.navbar-expand>.container-sm,.navbar-expand>.container-xl{padding-right:0;padding-left:0}.navbar-expand .navbar-nav{-ms-flex-direction:row;flex-direction:row}.navbar-expand .navbar-nav .dropdown-menu{position:absolute}.navbar-expand .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand>.container,.navbar-expand>.container-fluid,.navbar-expand>.container-lg,.navbar-expand>.container-md,.navbar-expand>.container-sm,.navbar-expand>.container-xl{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand .navbar-nav-scroll{overflow:visible}.navbar-expand .navbar-collapse{display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand .navbar-toggler{display:none}.navbar-light .navbar-brand{color:rgba(0,0,0,.9)}.navbar-light .navbar-brand:focus,.navbar-light .navbar-brand:hover{color:rgba(0,0,0,.9)}.navbar-light .navbar-nav .nav-link{color:rgba(0,0,0,.5)}.navbar-light .navbar-nav .nav-link:focus,.navbar-light .navbar-nav .nav-link:hover{color:rgba(0,0,0,.7)}.navbar-light .navbar-nav .nav-link.disabled{color:rgba(0,0,0,.3)}.navbar-light .navbar-nav .active>.nav-link,.navbar-light .navbar-nav .nav-link.active,.navbar-light .navbar-nav .nav-link.show,.navbar-light .navbar-nav .show>.nav-link{color:rgba(0,0,0,.9)}.navbar-light .navbar-toggler{color:rgba(0,0,0,.5);border-color:rgba(0,0,0,.1)}.navbar-light .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='30' height='30' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%280, 0, 0, 0.5%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.navbar-light .navbar-text{color:rgba(0,0,0,.5)}.navbar-light .navbar-text a{color:rgba(0,0,0,.9)}.navbar-light .navbar-text a:focus,.navbar-light .navbar-text a:hover{color:rgba(0,0,0,.9)}.navbar-dark .navbar-brand{color:#fff}.navbar-dark .navbar-brand:focus,.navbar-dark .navbar-brand:hover{color:#fff}.navbar-dark .navbar-nav .nav-link{color:rgba(255,255,255,.5)}.navbar-dark .navbar-nav .nav-link:focus,.navbar-dark .navbar-nav .nav-link:hover{color:rgba(255,255,255,.75)}.navbar-dark .navbar-nav .nav-link.disabled{color:rgba(255,255,255,.25)}.navbar-dark .navbar-nav .active>.nav-link,.navbar-dark .navbar-nav .nav-link.active,.navbar-dark .navbar-nav .nav-link.show,.navbar-dark .navbar-nav .show>.nav-link{color:#fff}.navbar-dark .navbar-toggler{color:rgba(255,255,255,.5);border-color:rgba(255,255,255,.1)}.navbar-dark .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='30' height='30' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.5%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.navbar-dark .navbar-text{color:rgba(255,255,255,.5)}.navbar-dark .navbar-text a{color:#fff}.navbar-dark .navbar-text a:focus,.navbar-dark .navbar-text a:hover{color:#fff}.card{position:relative;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;min-width:0;word-wrap:break-word;background-color:#fff;background-clip:border-box;border:1px solid rgba(0,0,0,.125);border-radius:.25rem}.card>hr{margin-right:0;margin-left:0}.card>.list-group{border-top:inherit;border-bottom:inherit}.card>.list-group:first-child{border-top-width:0;border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.card>.list-group:last-child{border-bottom-width:0;border-bottom-right-radius:calc(.25rem - 1px);border-bottom-left-radius:calc(.25rem - 1px)}.card>.card-header+.list-group,.card>.list-group+.card-footer{border-top:0}.card-body{-ms-flex:1 1 auto;flex:1 1 auto;min-height:1px;padding:1.25rem}.card-title{margin-bottom:.75rem}.card-subtitle{margin-top:-.375rem;margin-bottom:0}.card-text:last-child{margin-bottom:0}.card-link:hover{text-decoration:none}.card-link+.card-link{margin-left:1.25rem}.card-header{padding:.75rem 1.25rem;margin-bottom:0;background-color:rgba(0,0,0,.03);border-bottom:1px solid rgba(0,0,0,.125)}.card-header:first-child{border-radius:calc(.25rem - 1px) calc(.25rem - 1px) 0 0}.card-footer{padding:.75rem 1.25rem;background-color:rgba(0,0,0,.03);border-top:1px solid rgba(0,0,0,.125)}.card-footer:last-child{border-radius:0 0 calc(.25rem - 1px) calc(.25rem - 1px)}.card-header-tabs{margin-right:-.625rem;margin-bottom:-.75rem;margin-left:-.625rem;border-bottom:0}.card-header-pills{margin-right:-.625rem;margin-left:-.625rem}.card-img-overlay{position:absolute;top:0;right:0;bottom:0;left:0;padding:1.25rem;border-radius:calc(.25rem - 1px)}.card-img,.card-img-bottom,.card-img-top{-ms-flex-negative:0;flex-shrink:0;width:100%}.card-img,.card-img-top{border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.card-img,.card-img-bottom{border-bottom-right-radius:calc(.25rem - 1px);border-bottom-left-radius:calc(.25rem - 1px)}.card-deck .card{margin-bottom:15px}@media (min-width:576px){.card-deck{display:-ms-flexbox;display:flex;-ms-flex-flow:row wrap;flex-flow:row wrap;margin-right:-15px;margin-left:-15px}.card-deck .card{-ms-flex:1 0 0%;flex:1 0 0%;margin-right:15px;margin-bottom:0;margin-left:15px}}.card-group>.card{margin-bottom:15px}@media (min-width:576px){.card-group{display:-ms-flexbox;display:flex;-ms-flex-flow:row wrap;flex-flow:row wrap}.card-group>.card{-ms-flex:1 0 0%;flex:1 0 0%;margin-bottom:0}.card-group>.card+.card{margin-left:0;border-left:0}.card-group>.card:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.card-group>.card:not(:last-child) .card-header,.card-group>.card:not(:last-child) .card-img-top{border-top-right-radius:0}.card-group>.card:not(:last-child) .card-footer,.card-group>.card:not(:last-child) .card-img-bottom{border-bottom-right-radius:0}.card-group>.card:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.card-group>.card:not(:first-child) .card-header,.card-group>.card:not(:first-child) .card-img-top{border-top-left-radius:0}.card-group>.card:not(:first-child) .card-footer,.card-group>.card:not(:first-child) .card-img-bottom{border-bottom-left-radius:0}}.card-columns .card{margin-bottom:.75rem}@media (min-width:576px){.card-columns{-webkit-column-count:3;-moz-column-count:3;column-count:3;-webkit-column-gap:1.25rem;-moz-column-gap:1.25rem;column-gap:1.25rem;orphans:1;widows:1}.card-columns .card{display:inline-block;width:100%}}.accordion{overflow-anchor:none}.accordion>.card{overflow:hidden}.accordion>.card:not(:last-of-type){border-bottom:0;border-bottom-right-radius:0;border-bottom-left-radius:0}.accordion>.card:not(:first-of-type){border-top-left-radius:0;border-top-right-radius:0}.accordion>.card>.card-header{border-radius:0;margin-bottom:-1px}.breadcrumb{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;padding:.75rem 1rem;margin-bottom:1rem;list-style:none;background-color:#e9ecef;border-radius:.25rem}.breadcrumb-item+.breadcrumb-item{padding-left:.5rem}.breadcrumb-item+.breadcrumb-item::before{float:left;padding-right:.5rem;color:#6c757d;content:"/"}.breadcrumb-item+.breadcrumb-item:hover::before{text-decoration:underline}.breadcrumb-item+.breadcrumb-item:hover::before{text-decoration:none}.breadcrumb-item.active{color:#6c757d}.pagination{display:-ms-flexbox;display:flex;padding-left:0;list-style:none;border-radius:.25rem}.page-link{position:relative;display:block;padding:.5rem .75rem;margin-left:-1px;line-height:1.25;color:#007bff;background-color:#fff;border:1px solid #dee2e6}.page-link:hover{z-index:2;color:#0056b3;text-decoration:none;background-color:#e9ecef;border-color:#dee2e6}.page-link:focus{z-index:3;outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.page-item:first-child .page-link{margin-left:0;border-top-left-radius:.25rem;border-bottom-left-radius:.25rem}.page-item:last-child .page-link{border-top-right-radius:.25rem;border-bottom-right-radius:.25rem}.page-item.active .page-link{z-index:3;color:#fff;background-color:#007bff;border-color:#007bff}.page-item.disabled .page-link{color:#6c757d;pointer-events:none;cursor:auto;background-color:#fff;border-color:#dee2e6}.pagination-lg .page-link{padding:.75rem 1.5rem;font-size:1.25rem;line-height:1.5}.pagination-lg .page-item:first-child .page-link{border-top-left-radius:.3rem;border-bottom-left-radius:.3rem}.pagination-lg .page-item:last-child .page-link{border-top-right-radius:.3rem;border-bottom-right-radius:.3rem}.pagination-sm .page-link{padding:.25rem .5rem;font-size:.875rem;line-height:1.5}.pagination-sm .page-item:first-child .page-link{border-top-left-radius:.2rem;border-bottom-left-radius:.2rem}.pagination-sm .page-item:last-child .page-link{border-top-right-radius:.2rem;border-bottom-right-radius:.2rem}.badge{display:inline-block;padding:.25em .4em;font-size:75%;font-weight:700;line-height:1;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25rem;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.badge{transition:none}}a.badge:focus,a.badge:hover{text-decoration:none}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.badge-pill{padding-right:.6em;padding-left:.6em;border-radius:10rem}.badge-primary{color:#fff;background-color:#007bff}a.badge-primary:focus,a.badge-primary:hover{color:#fff;background-color:#0062cc}a.badge-primary.focus,a.badge-primary:focus{outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.5)}.badge-secondary{color:#fff;background-color:#6c757d}a.badge-secondary:focus,a.badge-secondary:hover{color:#fff;background-color:#545b62}a.badge-secondary.focus,a.badge-secondary:focus{outline:0;box-shadow:0 0 0 .2rem rgba(108,117,125,.5)}.badge-success{color:#fff;background-color:#28a745}a.badge-success:focus,a.badge-success:hover{color:#fff;background-color:#1e7e34}a.badge-success.focus,a.badge-success:focus{outline:0;box-shadow:0 0 0 .2rem rgba(40,167,69,.5)}.badge-info{color:#fff;background-color:#17a2b8}a.badge-info:focus,a.badge-info:hover{color:#fff;background-color:#117a8b}a.badge-info.focus,a.badge-info:focus{outline:0;box-shadow:0 0 0 .2rem rgba(23,162,184,.5)}.badge-warning{color:#212529;background-color:#ffc107}a.badge-warning:focus,a.badge-warning:hover{color:#212529;background-color:#d39e00}a.badge-warning.focus,a.badge-warning:focus{outline:0;box-shadow:0 0 0 .2rem rgba(255,193,7,.5)}.badge-danger{color:#fff;background-color:#dc3545}a.badge-danger:focus,a.badge-danger:hover{color:#fff;background-color:#bd2130}a.badge-danger.focus,a.badge-danger:focus{outline:0;box-shadow:0 0 0 .2rem rgba(220,53,69,.5)}.badge-light{color:#212529;background-color:#f8f9fa}a.badge-light:focus,a.badge-light:hover{color:#212529;background-color:#dae0e5}a.badge-light.focus,a.badge-light:focus{outline:0;box-shadow:0 0 0 .2rem rgba(248,249,250,.5)}.badge-dark{color:#fff;background-color:#343a40}a.badge-dark:focus,a.badge-dark:hover{color:#fff;background-color:#1d2124}a.badge-dark.focus,a.badge-dark:focus{outline:0;box-shadow:0 0 0 .2rem rgba(52,58,64,.5)}.jumbotron{padding:2rem 1rem;margin-bottom:2rem;background-color:#e9ecef;border-radius:.3rem}@media (min-width:576px){.jumbotron{padding:4rem 2rem}}.jumbotron-fluid{padding-right:0;padding-left:0;border-radius:0}.alert{position:relative;padding:.75rem 1.25rem;margin-bottom:1rem;border:1px solid transparent;border-radius:.25rem}.alert-heading{color:inherit}.alert-link{font-weight:700}.alert-dismissible{padding-right:4rem}.alert-dismissible .close{position:absolute;top:0;right:0;z-index:2;padding:.75rem 1.25rem;color:inherit}.alert-primary{color:#004085;background-color:#cce5ff;border-color:#b8daff}.alert-primary hr{border-top-color:#9fcdff}.alert-primary .alert-link{color:#002752}.alert-secondary{color:#383d41;background-color:#e2e3e5;border-color:#d6d8db}.alert-secondary hr{border-top-color:#c8cbcf}.alert-secondary .alert-link{color:#202326}.alert-success{color:#155724;background-color:#d4edda;border-color:#c3e6cb}.alert-success hr{border-top-color:#b1dfbb}.alert-success .alert-link{color:#0b2e13}.alert-info{color:#0c5460;background-color:#d1ecf1;border-color:#bee5eb}.alert-info hr{border-top-color:#abdde5}.alert-info .alert-link{color:#062c33}.alert-warning{color:#856404;background-color:#fff3cd;border-color:#ffeeba}.alert-warning hr{border-top-color:#ffe8a1}.alert-warning .alert-link{color:#533f03}.alert-danger{color:#721c24;background-color:#f8d7da;border-color:#f5c6cb}.alert-danger hr{border-top-color:#f1b0b7}.alert-danger .alert-link{color:#491217}.alert-light{color:#818182;background-color:#fefefe;border-color:#fdfdfe}.alert-light hr{border-top-color:#ececf6}.alert-light .alert-link{color:#686868}.alert-dark{color:#1b1e21;background-color:#d6d8d9;border-color:#c6c8ca}.alert-dark hr{border-top-color:#b9bbbe}.alert-dark .alert-link{color:#040505}@-webkit-keyframes progress-bar-stripes{from{background-position:1rem 0}to{background-position:0 0}}@keyframes progress-bar-stripes{from{background-position:1rem 0}to{background-position:0 0}}.progress{display:-ms-flexbox;display:flex;height:1rem;overflow:hidden;line-height:0;font-size:.75rem;background-color:#e9ecef;border-radius:.25rem}.progress-bar{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;-ms-flex-pack:center;justify-content:center;overflow:hidden;color:#fff;text-align:center;white-space:nowrap;background-color:#007bff;transition:width .6s ease}@media (prefers-reduced-motion:reduce){.progress-bar{transition:none}}.progress-bar-striped{background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-size:1rem 1rem}.progress-bar-animated{-webkit-animation:1s linear infinite progress-bar-stripes;animation:1s linear infinite progress-bar-stripes}@media (prefers-reduced-motion:reduce){.progress-bar-animated{-webkit-animation:none;animation:none}}.media{display:-ms-flexbox;display:flex;-ms-flex-align:start;align-items:flex-start}.media-body{-ms-flex:1;flex:1}.list-group{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;padding-left:0;margin-bottom:0;border-radius:.25rem}.list-group-item-action{width:100%;color:#495057;text-align:inherit}.list-group-item-action:focus,.list-group-item-action:hover{z-index:1;color:#495057;text-decoration:none;background-color:#f8f9fa}.list-group-item-action:active{color:#212529;background-color:#e9ecef}.list-group-item{position:relative;display:block;padding:.75rem 1.25rem;background-color:#fff;border:1px solid rgba(0,0,0,.125)}.list-group-item:first-child{border-top-left-radius:inherit;border-top-right-radius:inherit}.list-group-item:last-child{border-bottom-right-radius:inherit;border-bottom-left-radius:inherit}.list-group-item.disabled,.list-group-item:disabled{color:#6c757d;pointer-events:none;background-color:#fff}.list-group-item.active{z-index:2;color:#fff;background-color:#007bff;border-color:#007bff}.list-group-item+.list-group-item{border-top-width:0}.list-group-item+.list-group-item.active{margin-top:-1px;border-top-width:1px}.list-group-horizontal{-ms-flex-direction:row;flex-direction:row}.list-group-horizontal>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal>.list-group-item.active{margin-top:0}.list-group-horizontal>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}@media (min-width:576px){.list-group-horizontal-sm{-ms-flex-direction:row;flex-direction:row}.list-group-horizontal-sm>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-sm>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-sm>.list-group-item.active{margin-top:0}.list-group-horizontal-sm>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-sm>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:768px){.list-group-horizontal-md{-ms-flex-direction:row;flex-direction:row}.list-group-horizontal-md>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-md>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-md>.list-group-item.active{margin-top:0}.list-group-horizontal-md>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-md>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:992px){.list-group-horizontal-lg{-ms-flex-direction:row;flex-direction:row}.list-group-horizontal-lg>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-lg>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-lg>.list-group-item.active{margin-top:0}.list-group-horizontal-lg>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-lg>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:1200px){.list-group-horizontal-xl{-ms-flex-direction:row;flex-direction:row}.list-group-horizontal-xl>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-xl>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-xl>.list-group-item.active{margin-top:0}.list-group-horizontal-xl>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-xl>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}.list-group-flush{border-radius:0}.list-group-flush>.list-group-item{border-width:0 0 1px}.list-group-flush>.list-group-item:last-child{border-bottom-width:0}.list-group-item-primary{color:#004085;background-color:#b8daff}.list-group-item-primary.list-group-item-action:focus,.list-group-item-primary.list-group-item-action:hover{color:#004085;background-color:#9fcdff}.list-group-item-primary.list-group-item-action.active{color:#fff;background-color:#004085;border-color:#004085}.list-group-item-secondary{color:#383d41;background-color:#d6d8db}.list-group-item-secondary.list-group-item-action:focus,.list-group-item-secondary.list-group-item-action:hover{color:#383d41;background-color:#c8cbcf}.list-group-item-secondary.list-group-item-action.active{color:#fff;background-color:#383d41;border-color:#383d41}.list-group-item-success{color:#155724;background-color:#c3e6cb}.list-group-item-success.list-group-item-action:focus,.list-group-item-success.list-group-item-action:hover{color:#155724;background-color:#b1dfbb}.list-group-item-success.list-group-item-action.active{color:#fff;background-color:#155724;border-color:#155724}.list-group-item-info{color:#0c5460;background-color:#bee5eb}.list-group-item-info.list-group-item-action:focus,.list-group-item-info.list-group-item-action:hover{color:#0c5460;background-color:#abdde5}.list-group-item-info.list-group-item-action.active{color:#fff;background-color:#0c5460;border-color:#0c5460}.list-group-item-warning{color:#856404;background-color:#ffeeba}.list-group-item-warning.list-group-item-action:focus,.list-group-item-warning.list-group-item-action:hover{color:#856404;background-color:#ffe8a1}.list-group-item-warning.list-group-item-action.active{color:#fff;background-color:#856404;border-color:#856404}.list-group-item-danger{color:#721c24;background-color:#f5c6cb}.list-group-item-danger.list-group-item-action:focus,.list-group-item-danger.list-group-item-action:hover{color:#721c24;background-color:#f1b0b7}.list-group-item-danger.list-group-item-action.active{color:#fff;background-color:#721c24;border-color:#721c24}.list-group-item-light{color:#818182;background-color:#fdfdfe}.list-group-item-light.list-group-item-action:focus,.list-group-item-light.list-group-item-action:hover{color:#818182;background-color:#ececf6}.list-group-item-light.list-group-item-action.active{color:#fff;background-color:#818182;border-color:#818182}.list-group-item-dark{color:#1b1e21;background-color:#c6c8ca}.list-group-item-dark.list-group-item-action:focus,.list-group-item-dark.list-group-item-action:hover{color:#1b1e21;background-color:#b9bbbe}.list-group-item-dark.list-group-item-action.active{color:#fff;background-color:#1b1e21;border-color:#1b1e21}.close{float:right;font-size:1.5rem;font-weight:700;line-height:1;color:#000;text-shadow:0 1px 0 #fff;opacity:.5}.close:hover{color:#000;text-decoration:none}.close:not(:disabled):not(.disabled):focus,.close:not(:disabled):not(.disabled):hover{opacity:.75}button.close{padding:0;background-color:transparent;border:0}a.close.disabled{pointer-events:none}.toast{-ms-flex-preferred-size:350px;flex-basis:350px;max-width:350px;font-size:.875rem;background-color:rgba(255,255,255,.85);background-clip:padding-box;border:1px solid rgba(0,0,0,.1);box-shadow:0 .25rem .75rem rgba(0,0,0,.1);opacity:0;border-radius:.25rem}.toast:not(:last-child){margin-bottom:.75rem}.toast.showing{opacity:1}.toast.show{display:block;opacity:1}.toast.hide{display:none}.toast-header{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;padding:.25rem .75rem;color:#6c757d;background-color:rgba(255,255,255,.85);background-clip:padding-box;border-bottom:1px solid rgba(0,0,0,.05);border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.toast-body{padding:.75rem}.modal-open{overflow:hidden}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal{position:fixed;top:0;left:0;z-index:1050;display:none;width:100%;height:100%;overflow:hidden;outline:0}.modal-dialog{position:relative;width:auto;margin:.5rem;pointer-events:none}.modal.fade .modal-dialog{transition:-webkit-transform .3s ease-out;transition:transform .3s ease-out;transition:transform .3s ease-out,-webkit-transform .3s ease-out;-webkit-transform:translate(0,-50px);transform:translate(0,-50px)}@media (prefers-reduced-motion:reduce){.modal.fade .modal-dialog{transition:none}}.modal.show .modal-dialog{-webkit-transform:none;transform:none}.modal.modal-static .modal-dialog{-webkit-transform:scale(1.02);transform:scale(1.02)}.modal-dialog-scrollable{display:-ms-flexbox;display:flex;max-height:calc(100% - 1rem)}.modal-dialog-scrollable .modal-content{max-height:calc(100vh - 1rem);overflow:hidden}.modal-dialog-scrollable .modal-footer,.modal-dialog-scrollable .modal-header{-ms-flex-negative:0;flex-shrink:0}.modal-dialog-scrollable .modal-body{overflow-y:auto}.modal-dialog-centered{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;min-height:calc(100% - 1rem)}.modal-dialog-centered::before{display:block;height:calc(100vh - 1rem);height:-webkit-min-content;height:-moz-min-content;height:min-content;content:""}.modal-dialog-centered.modal-dialog-scrollable{-ms-flex-direction:column;flex-direction:column;-ms-flex-pack:center;justify-content:center;height:100%}.modal-dialog-centered.modal-dialog-scrollable .modal-content{max-height:none}.modal-dialog-centered.modal-dialog-scrollable::before{content:none}.modal-content{position:relative;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;width:100%;pointer-events:auto;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);border-radius:.3rem;outline:0}.modal-backdrop{position:fixed;top:0;left:0;z-index:1040;width:100vw;height:100vh;background-color:#000}.modal-backdrop.fade{opacity:0}.modal-backdrop.show{opacity:.5}.modal-header{display:-ms-flexbox;display:flex;-ms-flex-align:start;align-items:flex-start;-ms-flex-pack:justify;justify-content:space-between;padding:1rem 1rem;border-bottom:1px solid #dee2e6;border-top-left-radius:calc(.3rem - 1px);border-top-right-radius:calc(.3rem - 1px)}.modal-header .close{padding:1rem 1rem;margin:-1rem -1rem -1rem auto}.modal-title{margin-bottom:0;line-height:1.5}.modal-body{position:relative;-ms-flex:1 1 auto;flex:1 1 auto;padding:1rem}.modal-footer{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-align:center;align-items:center;-ms-flex-pack:end;justify-content:flex-end;padding:.75rem;border-top:1px solid #dee2e6;border-bottom-right-radius:calc(.3rem - 1px);border-bottom-left-radius:calc(.3rem - 1px)}.modal-footer>*{margin:.25rem}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media (min-width:576px){.modal-dialog{max-width:500px;margin:1.75rem auto}.modal-dialog-scrollable{max-height:calc(100% - 3.5rem)}.modal-dialog-scrollable .modal-content{max-height:calc(100vh - 3.5rem)}.modal-dialog-centered{min-height:calc(100% - 3.5rem)}.modal-dialog-centered::before{height:calc(100vh - 3.5rem);height:-webkit-min-content;height:-moz-min-content;height:min-content}.modal-sm{max-width:300px}}@media (min-width:992px){.modal-lg,.modal-xl{max-width:800px}}@media (min-width:1200px){.modal-xl{max-width:1140px}}.tooltip{position:absolute;z-index:1070;display:block;margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans","Liberation Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:.875rem;word-wrap:break-word;opacity:0}.tooltip.show{opacity:.9}.tooltip .arrow{position:absolute;display:block;width:.8rem;height:.4rem}.tooltip .arrow::before{position:absolute;content:"";border-color:transparent;border-style:solid}.bs-tooltip-auto[x-placement^=top],.bs-tooltip-top{padding:.4rem 0}.bs-tooltip-auto[x-placement^=top] .arrow,.bs-tooltip-top .arrow{bottom:0}.bs-tooltip-auto[x-placement^=top] .arrow::before,.bs-tooltip-top .arrow::before{top:0;border-width:.4rem .4rem 0;border-top-color:#000}.bs-tooltip-auto[x-placement^=right],.bs-tooltip-right{padding:0 .4rem}.bs-tooltip-auto[x-placement^=right] .arrow,.bs-tooltip-right .arrow{left:0;width:.4rem;height:.8rem}.bs-tooltip-auto[x-placement^=right] .arrow::before,.bs-tooltip-right .arrow::before{right:0;border-width:.4rem .4rem .4rem 0;border-right-color:#000}.bs-tooltip-auto[x-placement^=bottom],.bs-tooltip-bottom{padding:.4rem 0}.bs-tooltip-auto[x-placement^=bottom] .arrow,.bs-tooltip-bottom .arrow{top:0}.bs-tooltip-auto[x-placement^=bottom] .arrow::before,.bs-tooltip-bottom .arrow::before{bottom:0;border-width:0 .4rem .4rem;border-bottom-color:#000}.bs-tooltip-auto[x-placement^=left],.bs-tooltip-left{padding:0 .4rem}.bs-tooltip-auto[x-placement^=left] .arrow,.bs-tooltip-left .arrow{right:0;width:.4rem;height:.8rem}.bs-tooltip-auto[x-placement^=left] .arrow::before,.bs-tooltip-left .arrow::before{left:0;border-width:.4rem 0 .4rem .4rem;border-left-color:#000}.tooltip-inner{max-width:200px;padding:.25rem .5rem;color:#fff;text-align:center;background-color:#000;border-radius:.25rem}.popover{position:absolute;top:0;left:0;z-index:1060;display:block;max-width:276px;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans","Liberation Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:.875rem;word-wrap:break-word;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);border-radius:.3rem}.popover .arrow{position:absolute;display:block;width:1rem;height:.5rem;margin:0 .3rem}.popover .arrow::after,.popover .arrow::before{position:absolute;display:block;content:"";border-color:transparent;border-style:solid}.bs-popover-auto[x-placement^=top],.bs-popover-top{margin-bottom:.5rem}.bs-popover-auto[x-placement^=top]>.arrow,.bs-popover-top>.arrow{bottom:calc(-.5rem - 1px)}.bs-popover-auto[x-placement^=top]>.arrow::before,.bs-popover-top>.arrow::before{bottom:0;border-width:.5rem .5rem 0;border-top-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=top]>.arrow::after,.bs-popover-top>.arrow::after{bottom:1px;border-width:.5rem .5rem 0;border-top-color:#fff}.bs-popover-auto[x-placement^=right],.bs-popover-right{margin-left:.5rem}.bs-popover-auto[x-placement^=right]>.arrow,.bs-popover-right>.arrow{left:calc(-.5rem - 1px);width:.5rem;height:1rem;margin:.3rem 0}.bs-popover-auto[x-placement^=right]>.arrow::before,.bs-popover-right>.arrow::before{left:0;border-width:.5rem .5rem .5rem 0;border-right-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=right]>.arrow::after,.bs-popover-right>.arrow::after{left:1px;border-width:.5rem .5rem .5rem 0;border-right-color:#fff}.bs-popover-auto[x-placement^=bottom],.bs-popover-bottom{margin-top:.5rem}.bs-popover-auto[x-placement^=bottom]>.arrow,.bs-popover-bottom>.arrow{top:calc(-.5rem - 1px)}.bs-popover-auto[x-placement^=bottom]>.arrow::before,.bs-popover-bottom>.arrow::before{top:0;border-width:0 .5rem .5rem .5rem;border-bottom-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=bottom]>.arrow::after,.bs-popover-bottom>.arrow::after{top:1px;border-width:0 .5rem .5rem .5rem;border-bottom-color:#fff}.bs-popover-auto[x-placement^=bottom] .popover-header::before,.bs-popover-bottom .popover-header::before{position:absolute;top:0;left:50%;display:block;width:1rem;margin-left:-.5rem;content:"";border-bottom:1px solid #f7f7f7}.bs-popover-auto[x-placement^=left],.bs-popover-left{margin-right:.5rem}.bs-popover-auto[x-placement^=left]>.arrow,.bs-popover-left>.arrow{right:calc(-.5rem - 1px);width:.5rem;height:1rem;margin:.3rem 0}.bs-popover-auto[x-placement^=left]>.arrow::before,.bs-popover-left>.arrow::before{right:0;border-width:.5rem 0 .5rem .5rem;border-left-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=left]>.arrow::after,.bs-popover-left>.arrow::after{right:1px;border-width:.5rem 0 .5rem .5rem;border-left-color:#fff}.popover-header{padding:.5rem .75rem;margin-bottom:0;font-size:1rem;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-top-left-radius:calc(.3rem - 1px);border-top-right-radius:calc(.3rem - 1px)}.popover-header:empty{display:none}.popover-body{padding:.5rem .75rem;color:#212529}.carousel{position:relative}.carousel.pointer-event{-ms-touch-action:pan-y;touch-action:pan-y}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner::after{display:block;clear:both;content:""}.carousel-item{position:relative;display:none;float:left;width:100%;margin-right:-100%;-webkit-backface-visibility:hidden;backface-visibility:hidden;transition:-webkit-transform .6s ease-in-out;transition:transform .6s ease-in-out;transition:transform .6s ease-in-out,-webkit-transform .6s ease-in-out}@media (prefers-reduced-motion:reduce){.carousel-item{transition:none}}.carousel-item-next,.carousel-item-prev,.carousel-item.active{display:block}.active.carousel-item-right,.carousel-item-next:not(.carousel-item-left){-webkit-transform:translateX(100%);transform:translateX(100%)}.active.carousel-item-left,.carousel-item-prev:not(.carousel-item-right){-webkit-transform:translateX(-100%);transform:translateX(-100%)}.carousel-fade .carousel-item{opacity:0;transition-property:opacity;-webkit-transform:none;transform:none}.carousel-fade .carousel-item-next.carousel-item-left,.carousel-fade .carousel-item-prev.carousel-item-right,.carousel-fade .carousel-item.active{z-index:1;opacity:1}.carousel-fade .active.carousel-item-left,.carousel-fade .active.carousel-item-right{z-index:0;opacity:0;transition:opacity 0s .6s}@media (prefers-reduced-motion:reduce){.carousel-fade .active.carousel-item-left,.carousel-fade .active.carousel-item-right{transition:none}}.carousel-control-next,.carousel-control-prev{position:absolute;top:0;bottom:0;z-index:1;display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center;width:15%;color:#fff;text-align:center;opacity:.5;transition:opacity .15s ease}@media (prefers-reduced-motion:reduce){.carousel-control-next,.carousel-control-prev{transition:none}}.carousel-control-next:focus,.carousel-control-next:hover,.carousel-control-prev:focus,.carousel-control-prev:hover{color:#fff;text-decoration:none;outline:0;opacity:.9}.carousel-control-prev{left:0}.carousel-control-next{right:0}.carousel-control-next-icon,.carousel-control-prev-icon{display:inline-block;width:20px;height:20px;background:50%/100% 100% no-repeat}.carousel-control-prev-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath d='M5.25 0l-4 4 4 4 1.5-1.5L4.25 4l2.5-2.5L5.25 0z'/%3e%3c/svg%3e")}.carousel-control-next-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath d='M2.75 0l-1.5 1.5L3.75 4l-2.5 2.5L2.75 8l4-4-4-4z'/%3e%3c/svg%3e")}.carousel-indicators{position:absolute;right:0;bottom:0;left:0;z-index:15;display:-ms-flexbox;display:flex;-ms-flex-pack:center;justify-content:center;padding-left:0;margin-right:15%;margin-left:15%;list-style:none}.carousel-indicators li{box-sizing:content-box;-ms-flex:0 1 auto;flex:0 1 auto;width:30px;height:3px;margin-right:3px;margin-left:3px;text-indent:-999px;cursor:pointer;background-color:#fff;background-clip:padding-box;border-top:10px solid transparent;border-bottom:10px solid transparent;opacity:.5;transition:opacity .6s ease}@media (prefers-reduced-motion:reduce){.carousel-indicators li{transition:none}}.carousel-indicators .active{opacity:1}.carousel-caption{position:absolute;right:15%;bottom:20px;left:15%;z-index:10;padding-top:20px;padding-bottom:20px;color:#fff;text-align:center}@-webkit-keyframes spinner-border{to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes spinner-border{to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}.spinner-border{display:inline-block;width:2rem;height:2rem;vertical-align:text-bottom;border:.25em solid currentColor;border-right-color:transparent;border-radius:50%;-webkit-animation:.75s linear infinite spinner-border;animation:.75s linear infinite spinner-border}.spinner-border-sm{width:1rem;height:1rem;border-width:.2em}@-webkit-keyframes spinner-grow{0%{-webkit-transform:scale(0);transform:scale(0)}50%{opacity:1;-webkit-transform:none;transform:none}}@keyframes spinner-grow{0%{-webkit-transform:scale(0);transform:scale(0)}50%{opacity:1;-webkit-transform:none;transform:none}}.spinner-grow{display:inline-block;width:2rem;height:2rem;vertical-align:text-bottom;background-color:currentColor;border-radius:50%;opacity:0;-webkit-animation:.75s linear infinite spinner-grow;animation:.75s linear infinite spinner-grow}.spinner-grow-sm{width:1rem;height:1rem}@media (prefers-reduced-motion:reduce){.spinner-border,.spinner-grow{-webkit-animation-duration:1.5s;animation-duration:1.5s}}.align-baseline{vertical-align:baseline!important}.align-top{vertical-align:top!important}.align-middle{vertical-align:middle!important}.align-bottom{vertical-align:bottom!important}.align-text-bottom{vertical-align:text-bottom!important}.align-text-top{vertical-align:text-top!important}.bg-primary{background-color:#007bff!important}a.bg-primary:focus,a.bg-primary:hover,button.bg-primary:focus,button.bg-primary:hover{background-color:#0062cc!important}.bg-secondary{background-color:#6c757d!important}a.bg-secondary:focus,a.bg-secondary:hover,button.bg-secondary:focus,button.bg-secondary:hover{background-color:#545b62!important}.bg-success{background-color:#28a745!important}a.bg-success:focus,a.bg-success:hover,button.bg-success:focus,button.bg-success:hover{background-color:#1e7e34!important}.bg-info{background-color:#17a2b8!important}a.bg-info:focus,a.bg-info:hover,button.bg-info:focus,button.bg-info:hover{background-color:#117a8b!important}.bg-warning{background-color:#ffc107!important}a.bg-warning:focus,a.bg-warning:hover,button.bg-warning:focus,button.bg-warning:hover{background-color:#d39e00!important}.bg-danger{background-color:#dc3545!important}a.bg-danger:focus,a.bg-danger:hover,button.bg-danger:focus,button.bg-danger:hover{background-color:#bd2130!important}.bg-light{background-color:#f8f9fa!important}a.bg-light:focus,a.bg-light:hover,button.bg-light:focus,button.bg-light:hover{background-color:#dae0e5!important}.bg-dark{background-color:#343a40!important}a.bg-dark:focus,a.bg-dark:hover,button.bg-dark:focus,button.bg-dark:hover{background-color:#1d2124!important}.bg-white{background-color:#fff!important}.bg-transparent{background-color:transparent!important}.border{border:1px solid #dee2e6!important}.border-top{border-top:1px solid #dee2e6!important}.border-right{border-right:1px solid #dee2e6!important}.border-bottom{border-bottom:1px solid #dee2e6!important}.border-left{border-left:1px solid #dee2e6!important}.border-0{border:0!important}.border-top-0{border-top:0!important}.border-right-0{border-right:0!important}.border-bottom-0{border-bottom:0!important}.border-left-0{border-left:0!important}.border-primary{border-color:#007bff!important}.border-secondary{border-color:#6c757d!important}.border-success{border-color:#28a745!important}.border-info{border-color:#17a2b8!important}.border-warning{border-color:#ffc107!important}.border-danger{border-color:#dc3545!important}.border-light{border-color:#f8f9fa!important}.border-dark{border-color:#343a40!important}.border-white{border-color:#fff!important}.rounded-sm{border-radius:.2rem!important}.rounded{border-radius:.25rem!important}.rounded-top{border-top-left-radius:.25rem!important;border-top-right-radius:.25rem!important}.rounded-right{border-top-right-radius:.25rem!important;border-bottom-right-radius:.25rem!important}.rounded-bottom{border-bottom-right-radius:.25rem!important;border-bottom-left-radius:.25rem!important}.rounded-left{border-top-left-radius:.25rem!important;border-bottom-left-radius:.25rem!important}.rounded-lg{border-radius:.3rem!important}.rounded-circle{border-radius:50%!important}.rounded-pill{border-radius:50rem!important}.rounded-0{border-radius:0!important}.clearfix::after{display:block;clear:both;content:""}.d-none{display:none!important}.d-inline{display:inline!important}.d-inline-block{display:inline-block!important}.d-block{display:block!important}.d-table{display:table!important}.d-table-row{display:table-row!important}.d-table-cell{display:table-cell!important}.d-flex{display:-ms-flexbox!important;display:flex!important}.d-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}@media (min-width:576px){.d-sm-none{display:none!important}.d-sm-inline{display:inline!important}.d-sm-inline-block{display:inline-block!important}.d-sm-block{display:block!important}.d-sm-table{display:table!important}.d-sm-table-row{display:table-row!important}.d-sm-table-cell{display:table-cell!important}.d-sm-flex{display:-ms-flexbox!important;display:flex!important}.d-sm-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}}@media (min-width:768px){.d-md-none{display:none!important}.d-md-inline{display:inline!important}.d-md-inline-block{display:inline-block!important}.d-md-block{display:block!important}.d-md-table{display:table!important}.d-md-table-row{display:table-row!important}.d-md-table-cell{display:table-cell!important}.d-md-flex{display:-ms-flexbox!important;display:flex!important}.d-md-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}}@media (min-width:992px){.d-lg-none{display:none!important}.d-lg-inline{display:inline!important}.d-lg-inline-block{display:inline-block!important}.d-lg-block{display:block!important}.d-lg-table{display:table!important}.d-lg-table-row{display:table-row!important}.d-lg-table-cell{display:table-cell!important}.d-lg-flex{display:-ms-flexbox!important;display:flex!important}.d-lg-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}}@media (min-width:1200px){.d-xl-none{display:none!important}.d-xl-inline{display:inline!important}.d-xl-inline-block{display:inline-block!important}.d-xl-block{display:block!important}.d-xl-table{display:table!important}.d-xl-table-row{display:table-row!important}.d-xl-table-cell{display:table-cell!important}.d-xl-flex{display:-ms-flexbox!important;display:flex!important}.d-xl-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}}@media print{.d-print-none{display:none!important}.d-print-inline{display:inline!important}.d-print-inline-block{display:inline-block!important}.d-print-block{display:block!important}.d-print-table{display:table!important}.d-print-table-row{display:table-row!important}.d-print-table-cell{display:table-cell!important}.d-print-flex{display:-ms-flexbox!important;display:flex!important}.d-print-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}}.embed-responsive{position:relative;display:block;width:100%;padding:0;overflow:hidden}.embed-responsive::before{display:block;content:""}.embed-responsive .embed-responsive-item,.embed-responsive embed,.embed-responsive iframe,.embed-responsive object,.embed-responsive video{position:absolute;top:0;bottom:0;left:0;width:100%;height:100%;border:0}.embed-responsive-21by9::before{padding-top:42.857143%}.embed-responsive-16by9::before{padding-top:56.25%}.embed-responsive-4by3::before{padding-top:75%}.embed-responsive-1by1::before{padding-top:100%}.flex-row{-ms-flex-direction:row!important;flex-direction:row!important}.flex-column{-ms-flex-direction:column!important;flex-direction:column!important}.flex-row-reverse{-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-column-reverse{-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.flex-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.flex-grow-0{-ms-flex-positive:0!important;flex-grow:0!important}.flex-grow-1{-ms-flex-positive:1!important;flex-grow:1!important}.flex-shrink-0{-ms-flex-negative:0!important;flex-shrink:0!important}.flex-shrink-1{-ms-flex-negative:1!important;flex-shrink:1!important}.justify-content-start{-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-end{-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-center{-ms-flex-pack:center!important;justify-content:center!important}.justify-content-between{-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-start{-ms-flex-align:start!important;align-items:flex-start!important}.align-items-end{-ms-flex-align:end!important;align-items:flex-end!important}.align-items-center{-ms-flex-align:center!important;align-items:center!important}.align-items-baseline{-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-stretch{-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}@media (min-width:576px){.flex-sm-row{-ms-flex-direction:row!important;flex-direction:row!important}.flex-sm-column{-ms-flex-direction:column!important;flex-direction:column!important}.flex-sm-row-reverse{-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-sm-column-reverse{-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-sm-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-sm-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-sm-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.flex-sm-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.flex-sm-grow-0{-ms-flex-positive:0!important;flex-grow:0!important}.flex-sm-grow-1{-ms-flex-positive:1!important;flex-grow:1!important}.flex-sm-shrink-0{-ms-flex-negative:0!important;flex-shrink:0!important}.flex-sm-shrink-1{-ms-flex-negative:1!important;flex-shrink:1!important}.justify-content-sm-start{-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-sm-end{-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-sm-center{-ms-flex-pack:center!important;justify-content:center!important}.justify-content-sm-between{-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-sm-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-sm-start{-ms-flex-align:start!important;align-items:flex-start!important}.align-items-sm-end{-ms-flex-align:end!important;align-items:flex-end!important}.align-items-sm-center{-ms-flex-align:center!important;align-items:center!important}.align-items-sm-baseline{-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-sm-stretch{-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-sm-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-sm-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-sm-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-sm-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-sm-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-sm-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-sm-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-sm-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-sm-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-sm-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-sm-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-sm-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}}@media (min-width:768px){.flex-md-row{-ms-flex-direction:row!important;flex-direction:row!important}.flex-md-column{-ms-flex-direction:column!important;flex-direction:column!important}.flex-md-row-reverse{-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-md-column-reverse{-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-md-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-md-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-md-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.flex-md-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.flex-md-grow-0{-ms-flex-positive:0!important;flex-grow:0!important}.flex-md-grow-1{-ms-flex-positive:1!important;flex-grow:1!important}.flex-md-shrink-0{-ms-flex-negative:0!important;flex-shrink:0!important}.flex-md-shrink-1{-ms-flex-negative:1!important;flex-shrink:1!important}.justify-content-md-start{-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-md-end{-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-md-center{-ms-flex-pack:center!important;justify-content:center!important}.justify-content-md-between{-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-md-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-md-start{-ms-flex-align:start!important;align-items:flex-start!important}.align-items-md-end{-ms-flex-align:end!important;align-items:flex-end!important}.align-items-md-center{-ms-flex-align:center!important;align-items:center!important}.align-items-md-baseline{-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-md-stretch{-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-md-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-md-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-md-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-md-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-md-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-md-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-md-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-md-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-md-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-md-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-md-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-md-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}}@media (min-width:992px){.flex-lg-row{-ms-flex-direction:row!important;flex-direction:row!important}.flex-lg-column{-ms-flex-direction:column!important;flex-direction:column!important}.flex-lg-row-reverse{-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-lg-column-reverse{-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-lg-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-lg-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-lg-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.flex-lg-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.flex-lg-grow-0{-ms-flex-positive:0!important;flex-grow:0!important}.flex-lg-grow-1{-ms-flex-positive:1!important;flex-grow:1!important}.flex-lg-shrink-0{-ms-flex-negative:0!important;flex-shrink:0!important}.flex-lg-shrink-1{-ms-flex-negative:1!important;flex-shrink:1!important}.justify-content-lg-start{-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-lg-end{-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-lg-center{-ms-flex-pack:center!important;justify-content:center!important}.justify-content-lg-between{-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-lg-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-lg-start{-ms-flex-align:start!important;align-items:flex-start!important}.align-items-lg-end{-ms-flex-align:end!important;align-items:flex-end!important}.align-items-lg-center{-ms-flex-align:center!important;align-items:center!important}.align-items-lg-baseline{-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-lg-stretch{-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-lg-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-lg-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-lg-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-lg-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-lg-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-lg-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-lg-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-lg-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-lg-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-lg-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-lg-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-lg-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}}@media (min-width:1200px){.flex-xl-row{-ms-flex-direction:row!important;flex-direction:row!important}.flex-xl-column{-ms-flex-direction:column!important;flex-direction:column!important}.flex-xl-row-reverse{-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-xl-column-reverse{-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-xl-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-xl-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-xl-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.flex-xl-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.flex-xl-grow-0{-ms-flex-positive:0!important;flex-grow:0!important}.flex-xl-grow-1{-ms-flex-positive:1!important;flex-grow:1!important}.flex-xl-shrink-0{-ms-flex-negative:0!important;flex-shrink:0!important}.flex-xl-shrink-1{-ms-flex-negative:1!important;flex-shrink:1!important}.justify-content-xl-start{-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-xl-end{-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-xl-center{-ms-flex-pack:center!important;justify-content:center!important}.justify-content-xl-between{-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-xl-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-xl-start{-ms-flex-align:start!important;align-items:flex-start!important}.align-items-xl-end{-ms-flex-align:end!important;align-items:flex-end!important}.align-items-xl-center{-ms-flex-align:center!important;align-items:center!important}.align-items-xl-baseline{-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-xl-stretch{-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-xl-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-xl-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-xl-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-xl-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-xl-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-xl-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-xl-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-xl-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-xl-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-xl-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-xl-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-xl-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}}.float-left{float:left!important}.float-right{float:right!important}.float-none{float:none!important}@media (min-width:576px){.float-sm-left{float:left!important}.float-sm-right{float:right!important}.float-sm-none{float:none!important}}@media (min-width:768px){.float-md-left{float:left!important}.float-md-right{float:right!important}.float-md-none{float:none!important}}@media (min-width:992px){.float-lg-left{float:left!important}.float-lg-right{float:right!important}.float-lg-none{float:none!important}}@media (min-width:1200px){.float-xl-left{float:left!important}.float-xl-right{float:right!important}.float-xl-none{float:none!important}}.user-select-all{-webkit-user-select:all!important;-moz-user-select:all!important;user-select:all!important}.user-select-auto{-webkit-user-select:auto!important;-moz-user-select:auto!important;-ms-user-select:auto!important;user-select:auto!important}.user-select-none{-webkit-user-select:none!important;-moz-user-select:none!important;-ms-user-select:none!important;user-select:none!important}.overflow-auto{overflow:auto!important}.overflow-hidden{overflow:hidden!important}.position-static{position:static!important}.position-relative{position:relative!important}.position-absolute{position:absolute!important}.position-fixed{position:fixed!important}.position-sticky{position:-webkit-sticky!important;position:sticky!important}.fixed-top{position:fixed;top:0;right:0;left:0;z-index:1030}.fixed-bottom{position:fixed;right:0;bottom:0;left:0;z-index:1030}@supports ((position:-webkit-sticky) or (position:sticky)){.sticky-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;overflow:visible;clip:auto;white-space:normal}.shadow-sm{box-shadow:0 .125rem .25rem rgba(0,0,0,.075)!important}.shadow{box-shadow:0 .5rem 1rem rgba(0,0,0,.15)!important}.shadow-lg{box-shadow:0 1rem 3rem rgba(0,0,0,.175)!important}.shadow-none{box-shadow:none!important}.w-25{width:25%!important}.w-50{width:50%!important}.w-75{width:75%!important}.w-100{width:100%!important}.w-auto{width:auto!important}.h-25{height:25%!important}.h-50{height:50%!important}.h-75{height:75%!important}.h-100{height:100%!important}.h-auto{height:auto!important}.mw-100{max-width:100%!important}.mh-100{max-height:100%!important}.min-vw-100{min-width:100vw!important}.min-vh-100{min-height:100vh!important}.vw-100{width:100vw!important}.vh-100{height:100vh!important}.m-0{margin:0!important}.mt-0,.my-0{margin-top:0!important}.mr-0,.mx-0{margin-right:0!important}.mb-0,.my-0{margin-bottom:0!important}.ml-0,.mx-0{margin-left:0!important}.m-1{margin:.25rem!important}.mt-1,.my-1{margin-top:.25rem!important}.mr-1,.mx-1{margin-right:.25rem!important}.mb-1,.my-1{margin-bottom:.25rem!important}.ml-1,.mx-1{margin-left:.25rem!important}.m-2{margin:.5rem!important}.mt-2,.my-2{margin-top:.5rem!important}.mr-2,.mx-2{margin-right:.5rem!important}.mb-2,.my-2{margin-bottom:.5rem!important}.ml-2,.mx-2{margin-left:.5rem!important}.m-3{margin:1rem!important}.mt-3,.my-3{margin-top:1rem!important}.mr-3,.mx-3{margin-right:1rem!important}.mb-3,.my-3{margin-bottom:1rem!important}.ml-3,.mx-3{margin-left:1rem!important}.m-4{margin:1.5rem!important}.mt-4,.my-4{margin-top:1.5rem!important}.mr-4,.mx-4{margin-right:1.5rem!important}.mb-4,.my-4{margin-bottom:1.5rem!important}.ml-4,.mx-4{margin-left:1.5rem!important}.m-5{margin:3rem!important}.mt-5,.my-5{margin-top:3rem!important}.mr-5,.mx-5{margin-right:3rem!important}.mb-5,.my-5{margin-bottom:3rem!important}.ml-5,.mx-5{margin-left:3rem!important}.p-0{padding:0!important}.pt-0,.py-0{padding-top:0!important}.pr-0,.px-0{padding-right:0!important}.pb-0,.py-0{padding-bottom:0!important}.pl-0,.px-0{padding-left:0!important}.p-1{padding:.25rem!important}.pt-1,.py-1{padding-top:.25rem!important}.pr-1,.px-1{padding-right:.25rem!important}.pb-1,.py-1{padding-bottom:.25rem!important}.pl-1,.px-1{padding-left:.25rem!important}.p-2{padding:.5rem!important}.pt-2,.py-2{padding-top:.5rem!important}.pr-2,.px-2{padding-right:.5rem!important}.pb-2,.py-2{padding-bottom:.5rem!important}.pl-2,.px-2{padding-left:.5rem!important}.p-3{padding:1rem!important}.pt-3,.py-3{padding-top:1rem!important}.pr-3,.px-3{padding-right:1rem!important}.pb-3,.py-3{padding-bottom:1rem!important}.pl-3,.px-3{padding-left:1rem!important}.p-4{padding:1.5rem!important}.pt-4,.py-4{padding-top:1.5rem!important}.pr-4,.px-4{padding-right:1.5rem!important}.pb-4,.py-4{padding-bottom:1.5rem!important}.pl-4,.px-4{padding-left:1.5rem!important}.p-5{padding:3rem!important}.pt-5,.py-5{padding-top:3rem!important}.pr-5,.px-5{padding-right:3rem!important}.pb-5,.py-5{padding-bottom:3rem!important}.pl-5,.px-5{padding-left:3rem!important}.m-n1{margin:-.25rem!important}.mt-n1,.my-n1{margin-top:-.25rem!important}.mr-n1,.mx-n1{margin-right:-.25rem!important}.mb-n1,.my-n1{margin-bottom:-.25rem!important}.ml-n1,.mx-n1{margin-left:-.25rem!important}.m-n2{margin:-.5rem!important}.mt-n2,.my-n2{margin-top:-.5rem!important}.mr-n2,.mx-n2{margin-right:-.5rem!important}.mb-n2,.my-n2{margin-bottom:-.5rem!important}.ml-n2,.mx-n2{margin-left:-.5rem!important}.m-n3{margin:-1rem!important}.mt-n3,.my-n3{margin-top:-1rem!important}.mr-n3,.mx-n3{margin-right:-1rem!important}.mb-n3,.my-n3{margin-bottom:-1rem!important}.ml-n3,.mx-n3{margin-left:-1rem!important}.m-n4{margin:-1.5rem!important}.mt-n4,.my-n4{margin-top:-1.5rem!important}.mr-n4,.mx-n4{margin-right:-1.5rem!important}.mb-n4,.my-n4{margin-bottom:-1.5rem!important}.ml-n4,.mx-n4{margin-left:-1.5rem!important}.m-n5{margin:-3rem!important}.mt-n5,.my-n5{margin-top:-3rem!important}.mr-n5,.mx-n5{margin-right:-3rem!important}.mb-n5,.my-n5{margin-bottom:-3rem!important}.ml-n5,.mx-n5{margin-left:-3rem!important}.m-auto{margin:auto!important}.mt-auto,.my-auto{margin-top:auto!important}.mr-auto,.mx-auto{margin-right:auto!important}.mb-auto,.my-auto{margin-bottom:auto!important}.ml-auto,.mx-auto{margin-left:auto!important}@media (min-width:576px){.m-sm-0{margin:0!important}.mt-sm-0,.my-sm-0{margin-top:0!important}.mr-sm-0,.mx-sm-0{margin-right:0!important}.mb-sm-0,.my-sm-0{margin-bottom:0!important}.ml-sm-0,.mx-sm-0{margin-left:0!important}.m-sm-1{margin:.25rem!important}.mt-sm-1,.my-sm-1{margin-top:.25rem!important}.mr-sm-1,.mx-sm-1{margin-right:.25rem!important}.mb-sm-1,.my-sm-1{margin-bottom:.25rem!important}.ml-sm-1,.mx-sm-1{margin-left:.25rem!important}.m-sm-2{margin:.5rem!important}.mt-sm-2,.my-sm-2{margin-top:.5rem!important}.mr-sm-2,.mx-sm-2{margin-right:.5rem!important}.mb-sm-2,.my-sm-2{margin-bottom:.5rem!important}.ml-sm-2,.mx-sm-2{margin-left:.5rem!important}.m-sm-3{margin:1rem!important}.mt-sm-3,.my-sm-3{margin-top:1rem!important}.mr-sm-3,.mx-sm-3{margin-right:1rem!important}.mb-sm-3,.my-sm-3{margin-bottom:1rem!important}.ml-sm-3,.mx-sm-3{margin-left:1rem!important}.m-sm-4{margin:1.5rem!important}.mt-sm-4,.my-sm-4{margin-top:1.5rem!important}.mr-sm-4,.mx-sm-4{margin-right:1.5rem!important}.mb-sm-4,.my-sm-4{margin-bottom:1.5rem!important}.ml-sm-4,.mx-sm-4{margin-left:1.5rem!important}.m-sm-5{margin:3rem!important}.mt-sm-5,.my-sm-5{margin-top:3rem!important}.mr-sm-5,.mx-sm-5{margin-right:3rem!important}.mb-sm-5,.my-sm-5{margin-bottom:3rem!important}.ml-sm-5,.mx-sm-5{margin-left:3rem!important}.p-sm-0{padding:0!important}.pt-sm-0,.py-sm-0{padding-top:0!important}.pr-sm-0,.px-sm-0{padding-right:0!important}.pb-sm-0,.py-sm-0{padding-bottom:0!important}.pl-sm-0,.px-sm-0{padding-left:0!important}.p-sm-1{padding:.25rem!important}.pt-sm-1,.py-sm-1{padding-top:.25rem!important}.pr-sm-1,.px-sm-1{padding-right:.25rem!important}.pb-sm-1,.py-sm-1{padding-bottom:.25rem!important}.pl-sm-1,.px-sm-1{padding-left:.25rem!important}.p-sm-2{padding:.5rem!important}.pt-sm-2,.py-sm-2{padding-top:.5rem!important}.pr-sm-2,.px-sm-2{padding-right:.5rem!important}.pb-sm-2,.py-sm-2{padding-bottom:.5rem!important}.pl-sm-2,.px-sm-2{padding-left:.5rem!important}.p-sm-3{padding:1rem!important}.pt-sm-3,.py-sm-3{padding-top:1rem!important}.pr-sm-3,.px-sm-3{padding-right:1rem!important}.pb-sm-3,.py-sm-3{padding-bottom:1rem!important}.pl-sm-3,.px-sm-3{padding-left:1rem!important}.p-sm-4{padding:1.5rem!important}.pt-sm-4,.py-sm-4{padding-top:1.5rem!important}.pr-sm-4,.px-sm-4{padding-right:1.5rem!important}.pb-sm-4,.py-sm-4{padding-bottom:1.5rem!important}.pl-sm-4,.px-sm-4{padding-left:1.5rem!important}.p-sm-5{padding:3rem!important}.pt-sm-5,.py-sm-5{padding-top:3rem!important}.pr-sm-5,.px-sm-5{padding-right:3rem!important}.pb-sm-5,.py-sm-5{padding-bottom:3rem!important}.pl-sm-5,.px-sm-5{padding-left:3rem!important}.m-sm-n1{margin:-.25rem!important}.mt-sm-n1,.my-sm-n1{margin-top:-.25rem!important}.mr-sm-n1,.mx-sm-n1{margin-right:-.25rem!important}.mb-sm-n1,.my-sm-n1{margin-bottom:-.25rem!important}.ml-sm-n1,.mx-sm-n1{margin-left:-.25rem!important}.m-sm-n2{margin:-.5rem!important}.mt-sm-n2,.my-sm-n2{margin-top:-.5rem!important}.mr-sm-n2,.mx-sm-n2{margin-right:-.5rem!important}.mb-sm-n2,.my-sm-n2{margin-bottom:-.5rem!important}.ml-sm-n2,.mx-sm-n2{margin-left:-.5rem!important}.m-sm-n3{margin:-1rem!important}.mt-sm-n3,.my-sm-n3{margin-top:-1rem!important}.mr-sm-n3,.mx-sm-n3{margin-right:-1rem!important}.mb-sm-n3,.my-sm-n3{margin-bottom:-1rem!important}.ml-sm-n3,.mx-sm-n3{margin-left:-1rem!important}.m-sm-n4{margin:-1.5rem!important}.mt-sm-n4,.my-sm-n4{margin-top:-1.5rem!important}.mr-sm-n4,.mx-sm-n4{margin-right:-1.5rem!important}.mb-sm-n4,.my-sm-n4{margin-bottom:-1.5rem!important}.ml-sm-n4,.mx-sm-n4{margin-left:-1.5rem!important}.m-sm-n5{margin:-3rem!important}.mt-sm-n5,.my-sm-n5{margin-top:-3rem!important}.mr-sm-n5,.mx-sm-n5{margin-right:-3rem!important}.mb-sm-n5,.my-sm-n5{margin-bottom:-3rem!important}.ml-sm-n5,.mx-sm-n5{margin-left:-3rem!important}.m-sm-auto{margin:auto!important}.mt-sm-auto,.my-sm-auto{margin-top:auto!important}.mr-sm-auto,.mx-sm-auto{margin-right:auto!important}.mb-sm-auto,.my-sm-auto{margin-bottom:auto!important}.ml-sm-auto,.mx-sm-auto{margin-left:auto!important}}@media (min-width:768px){.m-md-0{margin:0!important}.mt-md-0,.my-md-0{margin-top:0!important}.mr-md-0,.mx-md-0{margin-right:0!important}.mb-md-0,.my-md-0{margin-bottom:0!important}.ml-md-0,.mx-md-0{margin-left:0!important}.m-md-1{margin:.25rem!important}.mt-md-1,.my-md-1{margin-top:.25rem!important}.mr-md-1,.mx-md-1{margin-right:.25rem!important}.mb-md-1,.my-md-1{margin-bottom:.25rem!important}.ml-md-1,.mx-md-1{margin-left:.25rem!important}.m-md-2{margin:.5rem!important}.mt-md-2,.my-md-2{margin-top:.5rem!important}.mr-md-2,.mx-md-2{margin-right:.5rem!important}.mb-md-2,.my-md-2{margin-bottom:.5rem!important}.ml-md-2,.mx-md-2{margin-left:.5rem!important}.m-md-3{margin:1rem!important}.mt-md-3,.my-md-3{margin-top:1rem!important}.mr-md-3,.mx-md-3{margin-right:1rem!important}.mb-md-3,.my-md-3{margin-bottom:1rem!important}.ml-md-3,.mx-md-3{margin-left:1rem!important}.m-md-4{margin:1.5rem!important}.mt-md-4,.my-md-4{margin-top:1.5rem!important}.mr-md-4,.mx-md-4{margin-right:1.5rem!important}.mb-md-4,.my-md-4{margin-bottom:1.5rem!important}.ml-md-4,.mx-md-4{margin-left:1.5rem!important}.m-md-5{margin:3rem!important}.mt-md-5,.my-md-5{margin-top:3rem!important}.mr-md-5,.mx-md-5{margin-right:3rem!important}.mb-md-5,.my-md-5{margin-bottom:3rem!important}.ml-md-5,.mx-md-5{margin-left:3rem!important}.p-md-0{padding:0!important}.pt-md-0,.py-md-0{padding-top:0!important}.pr-md-0,.px-md-0{padding-right:0!important}.pb-md-0,.py-md-0{padding-bottom:0!important}.pl-md-0,.px-md-0{padding-left:0!important}.p-md-1{padding:.25rem!important}.pt-md-1,.py-md-1{padding-top:.25rem!important}.pr-md-1,.px-md-1{padding-right:.25rem!important}.pb-md-1,.py-md-1{padding-bottom:.25rem!important}.pl-md-1,.px-md-1{padding-left:.25rem!important}.p-md-2{padding:.5rem!important}.pt-md-2,.py-md-2{padding-top:.5rem!important}.pr-md-2,.px-md-2{padding-right:.5rem!important}.pb-md-2,.py-md-2{padding-bottom:.5rem!important}.pl-md-2,.px-md-2{padding-left:.5rem!important}.p-md-3{padding:1rem!important}.pt-md-3,.py-md-3{padding-top:1rem!important}.pr-md-3,.px-md-3{padding-right:1rem!important}.pb-md-3,.py-md-3{padding-bottom:1rem!important}.pl-md-3,.px-md-3{padding-left:1rem!important}.p-md-4{padding:1.5rem!important}.pt-md-4,.py-md-4{padding-top:1.5rem!important}.pr-md-4,.px-md-4{padding-right:1.5rem!important}.pb-md-4,.py-md-4{padding-bottom:1.5rem!important}.pl-md-4,.px-md-4{padding-left:1.5rem!important}.p-md-5{padding:3rem!important}.pt-md-5,.py-md-5{padding-top:3rem!important}.pr-md-5,.px-md-5{padding-right:3rem!important}.pb-md-5,.py-md-5{padding-bottom:3rem!important}.pl-md-5,.px-md-5{padding-left:3rem!important}.m-md-n1{margin:-.25rem!important}.mt-md-n1,.my-md-n1{margin-top:-.25rem!important}.mr-md-n1,.mx-md-n1{margin-right:-.25rem!important}.mb-md-n1,.my-md-n1{margin-bottom:-.25rem!important}.ml-md-n1,.mx-md-n1{margin-left:-.25rem!important}.m-md-n2{margin:-.5rem!important}.mt-md-n2,.my-md-n2{margin-top:-.5rem!important}.mr-md-n2,.mx-md-n2{margin-right:-.5rem!important}.mb-md-n2,.my-md-n2{margin-bottom:-.5rem!important}.ml-md-n2,.mx-md-n2{margin-left:-.5rem!important}.m-md-n3{margin:-1rem!important}.mt-md-n3,.my-md-n3{margin-top:-1rem!important}.mr-md-n3,.mx-md-n3{margin-right:-1rem!important}.mb-md-n3,.my-md-n3{margin-bottom:-1rem!important}.ml-md-n3,.mx-md-n3{margin-left:-1rem!important}.m-md-n4{margin:-1.5rem!important}.mt-md-n4,.my-md-n4{margin-top:-1.5rem!important}.mr-md-n4,.mx-md-n4{margin-right:-1.5rem!important}.mb-md-n4,.my-md-n4{margin-bottom:-1.5rem!important}.ml-md-n4,.mx-md-n4{margin-left:-1.5rem!important}.m-md-n5{margin:-3rem!important}.mt-md-n5,.my-md-n5{margin-top:-3rem!important}.mr-md-n5,.mx-md-n5{margin-right:-3rem!important}.mb-md-n5,.my-md-n5{margin-bottom:-3rem!important}.ml-md-n5,.mx-md-n5{margin-left:-3rem!important}.m-md-auto{margin:auto!important}.mt-md-auto,.my-md-auto{margin-top:auto!important}.mr-md-auto,.mx-md-auto{margin-right:auto!important}.mb-md-auto,.my-md-auto{margin-bottom:auto!important}.ml-md-auto,.mx-md-auto{margin-left:auto!important}}@media (min-width:992px){.m-lg-0{margin:0!important}.mt-lg-0,.my-lg-0{margin-top:0!important}.mr-lg-0,.mx-lg-0{margin-right:0!important}.mb-lg-0,.my-lg-0{margin-bottom:0!important}.ml-lg-0,.mx-lg-0{margin-left:0!important}.m-lg-1{margin:.25rem!important}.mt-lg-1,.my-lg-1{margin-top:.25rem!important}.mr-lg-1,.mx-lg-1{margin-right:.25rem!important}.mb-lg-1,.my-lg-1{margin-bottom:.25rem!important}.ml-lg-1,.mx-lg-1{margin-left:.25rem!important}.m-lg-2{margin:.5rem!important}.mt-lg-2,.my-lg-2{margin-top:.5rem!important}.mr-lg-2,.mx-lg-2{margin-right:.5rem!important}.mb-lg-2,.my-lg-2{margin-bottom:.5rem!important}.ml-lg-2,.mx-lg-2{margin-left:.5rem!important}.m-lg-3{margin:1rem!important}.mt-lg-3,.my-lg-3{margin-top:1rem!important}.mr-lg-3,.mx-lg-3{margin-right:1rem!important}.mb-lg-3,.my-lg-3{margin-bottom:1rem!important}.ml-lg-3,.mx-lg-3{margin-left:1rem!important}.m-lg-4{margin:1.5rem!important}.mt-lg-4,.my-lg-4{margin-top:1.5rem!important}.mr-lg-4,.mx-lg-4{margin-right:1.5rem!important}.mb-lg-4,.my-lg-4{margin-bottom:1.5rem!important}.ml-lg-4,.mx-lg-4{margin-left:1.5rem!important}.m-lg-5{margin:3rem!important}.mt-lg-5,.my-lg-5{margin-top:3rem!important}.mr-lg-5,.mx-lg-5{margin-right:3rem!important}.mb-lg-5,.my-lg-5{margin-bottom:3rem!important}.ml-lg-5,.mx-lg-5{margin-left:3rem!important}.p-lg-0{padding:0!important}.pt-lg-0,.py-lg-0{padding-top:0!important}.pr-lg-0,.px-lg-0{padding-right:0!important}.pb-lg-0,.py-lg-0{padding-bottom:0!important}.pl-lg-0,.px-lg-0{padding-left:0!important}.p-lg-1{padding:.25rem!important}.pt-lg-1,.py-lg-1{padding-top:.25rem!important}.pr-lg-1,.px-lg-1{padding-right:.25rem!important}.pb-lg-1,.py-lg-1{padding-bottom:.25rem!important}.pl-lg-1,.px-lg-1{padding-left:.25rem!important}.p-lg-2{padding:.5rem!important}.pt-lg-2,.py-lg-2{padding-top:.5rem!important}.pr-lg-2,.px-lg-2{padding-right:.5rem!important}.pb-lg-2,.py-lg-2{padding-bottom:.5rem!important}.pl-lg-2,.px-lg-2{padding-left:.5rem!important}.p-lg-3{padding:1rem!important}.pt-lg-3,.py-lg-3{padding-top:1rem!important}.pr-lg-3,.px-lg-3{padding-right:1rem!important}.pb-lg-3,.py-lg-3{padding-bottom:1rem!important}.pl-lg-3,.px-lg-3{padding-left:1rem!important}.p-lg-4{padding:1.5rem!important}.pt-lg-4,.py-lg-4{padding-top:1.5rem!important}.pr-lg-4,.px-lg-4{padding-right:1.5rem!important}.pb-lg-4,.py-lg-4{padding-bottom:1.5rem!important}.pl-lg-4,.px-lg-4{padding-left:1.5rem!important}.p-lg-5{padding:3rem!important}.pt-lg-5,.py-lg-5{padding-top:3rem!important}.pr-lg-5,.px-lg-5{padding-right:3rem!important}.pb-lg-5,.py-lg-5{padding-bottom:3rem!important}.pl-lg-5,.px-lg-5{padding-left:3rem!important}.m-lg-n1{margin:-.25rem!important}.mt-lg-n1,.my-lg-n1{margin-top:-.25rem!important}.mr-lg-n1,.mx-lg-n1{margin-right:-.25rem!important}.mb-lg-n1,.my-lg-n1{margin-bottom:-.25rem!important}.ml-lg-n1,.mx-lg-n1{margin-left:-.25rem!important}.m-lg-n2{margin:-.5rem!important}.mt-lg-n2,.my-lg-n2{margin-top:-.5rem!important}.mr-lg-n2,.mx-lg-n2{margin-right:-.5rem!important}.mb-lg-n2,.my-lg-n2{margin-bottom:-.5rem!important}.ml-lg-n2,.mx-lg-n2{margin-left:-.5rem!important}.m-lg-n3{margin:-1rem!important}.mt-lg-n3,.my-lg-n3{margin-top:-1rem!important}.mr-lg-n3,.mx-lg-n3{margin-right:-1rem!important}.mb-lg-n3,.my-lg-n3{margin-bottom:-1rem!important}.ml-lg-n3,.mx-lg-n3{margin-left:-1rem!important}.m-lg-n4{margin:-1.5rem!important}.mt-lg-n4,.my-lg-n4{margin-top:-1.5rem!important}.mr-lg-n4,.mx-lg-n4{margin-right:-1.5rem!important}.mb-lg-n4,.my-lg-n4{margin-bottom:-1.5rem!important}.ml-lg-n4,.mx-lg-n4{margin-left:-1.5rem!important}.m-lg-n5{margin:-3rem!important}.mt-lg-n5,.my-lg-n5{margin-top:-3rem!important}.mr-lg-n5,.mx-lg-n5{margin-right:-3rem!important}.mb-lg-n5,.my-lg-n5{margin-bottom:-3rem!important}.ml-lg-n5,.mx-lg-n5{margin-left:-3rem!important}.m-lg-auto{margin:auto!important}.mt-lg-auto,.my-lg-auto{margin-top:auto!important}.mr-lg-auto,.mx-lg-auto{margin-right:auto!important}.mb-lg-auto,.my-lg-auto{margin-bottom:auto!important}.ml-lg-auto,.mx-lg-auto{margin-left:auto!important}}@media (min-width:1200px){.m-xl-0{margin:0!important}.mt-xl-0,.my-xl-0{margin-top:0!important}.mr-xl-0,.mx-xl-0{margin-right:0!important}.mb-xl-0,.my-xl-0{margin-bottom:0!important}.ml-xl-0,.mx-xl-0{margin-left:0!important}.m-xl-1{margin:.25rem!important}.mt-xl-1,.my-xl-1{margin-top:.25rem!important}.mr-xl-1,.mx-xl-1{margin-right:.25rem!important}.mb-xl-1,.my-xl-1{margin-bottom:.25rem!important}.ml-xl-1,.mx-xl-1{margin-left:.25rem!important}.m-xl-2{margin:.5rem!important}.mt-xl-2,.my-xl-2{margin-top:.5rem!important}.mr-xl-2,.mx-xl-2{margin-right:.5rem!important}.mb-xl-2,.my-xl-2{margin-bottom:.5rem!important}.ml-xl-2,.mx-xl-2{margin-left:.5rem!important}.m-xl-3{margin:1rem!important}.mt-xl-3,.my-xl-3{margin-top:1rem!important}.mr-xl-3,.mx-xl-3{margin-right:1rem!important}.mb-xl-3,.my-xl-3{margin-bottom:1rem!important}.ml-xl-3,.mx-xl-3{margin-left:1rem!important}.m-xl-4{margin:1.5rem!important}.mt-xl-4,.my-xl-4{margin-top:1.5rem!important}.mr-xl-4,.mx-xl-4{margin-right:1.5rem!important}.mb-xl-4,.my-xl-4{margin-bottom:1.5rem!important}.ml-xl-4,.mx-xl-4{margin-left:1.5rem!important}.m-xl-5{margin:3rem!important}.mt-xl-5,.my-xl-5{margin-top:3rem!important}.mr-xl-5,.mx-xl-5{margin-right:3rem!important}.mb-xl-5,.my-xl-5{margin-bottom:3rem!important}.ml-xl-5,.mx-xl-5{margin-left:3rem!important}.p-xl-0{padding:0!important}.pt-xl-0,.py-xl-0{padding-top:0!important}.pr-xl-0,.px-xl-0{padding-right:0!important}.pb-xl-0,.py-xl-0{padding-bottom:0!important}.pl-xl-0,.px-xl-0{padding-left:0!important}.p-xl-1{padding:.25rem!important}.pt-xl-1,.py-xl-1{padding-top:.25rem!important}.pr-xl-1,.px-xl-1{padding-right:.25rem!important}.pb-xl-1,.py-xl-1{padding-bottom:.25rem!important}.pl-xl-1,.px-xl-1{padding-left:.25rem!important}.p-xl-2{padding:.5rem!important}.pt-xl-2,.py-xl-2{padding-top:.5rem!important}.pr-xl-2,.px-xl-2{padding-right:.5rem!important}.pb-xl-2,.py-xl-2{padding-bottom:.5rem!important}.pl-xl-2,.px-xl-2{padding-left:.5rem!important}.p-xl-3{padding:1rem!important}.pt-xl-3,.py-xl-3{padding-top:1rem!important}.pr-xl-3,.px-xl-3{padding-right:1rem!important}.pb-xl-3,.py-xl-3{padding-bottom:1rem!important}.pl-xl-3,.px-xl-3{padding-left:1rem!important}.p-xl-4{padding:1.5rem!important}.pt-xl-4,.py-xl-4{padding-top:1.5rem!important}.pr-xl-4,.px-xl-4{padding-right:1.5rem!important}.pb-xl-4,.py-xl-4{padding-bottom:1.5rem!important}.pl-xl-4,.px-xl-4{padding-left:1.5rem!important}.p-xl-5{padding:3rem!important}.pt-xl-5,.py-xl-5{padding-top:3rem!important}.pr-xl-5,.px-xl-5{padding-right:3rem!important}.pb-xl-5,.py-xl-5{padding-bottom:3rem!important}.pl-xl-5,.px-xl-5{padding-left:3rem!important}.m-xl-n1{margin:-.25rem!important}.mt-xl-n1,.my-xl-n1{margin-top:-.25rem!important}.mr-xl-n1,.mx-xl-n1{margin-right:-.25rem!important}.mb-xl-n1,.my-xl-n1{margin-bottom:-.25rem!important}.ml-xl-n1,.mx-xl-n1{margin-left:-.25rem!important}.m-xl-n2{margin:-.5rem!important}.mt-xl-n2,.my-xl-n2{margin-top:-.5rem!important}.mr-xl-n2,.mx-xl-n2{margin-right:-.5rem!important}.mb-xl-n2,.my-xl-n2{margin-bottom:-.5rem!important}.ml-xl-n2,.mx-xl-n2{margin-left:-.5rem!important}.m-xl-n3{margin:-1rem!important}.mt-xl-n3,.my-xl-n3{margin-top:-1rem!important}.mr-xl-n3,.mx-xl-n3{margin-right:-1rem!important}.mb-xl-n3,.my-xl-n3{margin-bottom:-1rem!important}.ml-xl-n3,.mx-xl-n3{margin-left:-1rem!important}.m-xl-n4{margin:-1.5rem!important}.mt-xl-n4,.my-xl-n4{margin-top:-1.5rem!important}.mr-xl-n4,.mx-xl-n4{margin-right:-1.5rem!important}.mb-xl-n4,.my-xl-n4{margin-bottom:-1.5rem!important}.ml-xl-n4,.mx-xl-n4{margin-left:-1.5rem!important}.m-xl-n5{margin:-3rem!important}.mt-xl-n5,.my-xl-n5{margin-top:-3rem!important}.mr-xl-n5,.mx-xl-n5{margin-right:-3rem!important}.mb-xl-n5,.my-xl-n5{margin-bottom:-3rem!important}.ml-xl-n5,.mx-xl-n5{margin-left:-3rem!important}.m-xl-auto{margin:auto!important}.mt-xl-auto,.my-xl-auto{margin-top:auto!important}.mr-xl-auto,.mx-xl-auto{margin-right:auto!important}.mb-xl-auto,.my-xl-auto{margin-bottom:auto!important}.ml-xl-auto,.mx-xl-auto{margin-left:auto!important}}.stretched-link::after{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;pointer-events:auto;content:"";background-color:rgba(0,0,0,0)}.text-monospace{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace!important}.text-justify{text-align:justify!important}.text-wrap{white-space:normal!important}.text-nowrap{white-space:nowrap!important}.text-truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.text-left{text-align:left!important}.text-right{text-align:right!important}.text-center{text-align:center!important}@media (min-width:576px){.text-sm-left{text-align:left!important}.text-sm-right{text-align:right!important}.text-sm-center{text-align:center!important}}@media (min-width:768px){.text-md-left{text-align:left!important}.text-md-right{text-align:right!important}.text-md-center{text-align:center!important}}@media (min-width:992px){.text-lg-left{text-align:left!important}.text-lg-right{text-align:right!important}.text-lg-center{text-align:center!important}}@media (min-width:1200px){.text-xl-left{text-align:left!important}.text-xl-right{text-align:right!important}.text-xl-center{text-align:center!important}}.text-lowercase{text-transform:lowercase!important}.text-uppercase{text-transform:uppercase!important}.text-capitalize{text-transform:capitalize!important}.font-weight-light{font-weight:300!important}.font-weight-lighter{font-weight:lighter!important}.font-weight-normal{font-weight:400!important}.font-weight-bold{font-weight:700!important}.font-weight-bolder{font-weight:bolder!important}.font-italic{font-style:italic!important}.text-white{color:#fff!important}.text-primary{color:#007bff!important}a.text-primary:focus,a.text-primary:hover{color:#0056b3!important}.text-secondary{color:#6c757d!important}a.text-secondary:focus,a.text-secondary:hover{color:#494f54!important}.text-success{color:#28a745!important}a.text-success:focus,a.text-success:hover{color:#19692c!important}.text-info{color:#17a2b8!important}a.text-info:focus,a.text-info:hover{color:#0f6674!important}.text-warning{color:#ffc107!important}a.text-warning:focus,a.text-warning:hover{color:#ba8b00!important}.text-danger{color:#dc3545!important}a.text-danger:focus,a.text-danger:hover{color:#a71d2a!important}.text-light{color:#f8f9fa!important}a.text-light:focus,a.text-light:hover{color:#cbd3da!important}.text-dark{color:#343a40!important}a.text-dark:focus,a.text-dark:hover{color:#121416!important}.text-body{color:#212529!important}.text-muted{color:#6c757d!important}.text-black-50{color:rgba(0,0,0,.5)!important}.text-white-50{color:rgba(255,255,255,.5)!important}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.text-decoration-none{text-decoration:none!important}.text-break{word-break:break-word!important;word-wrap:break-word!important}.text-reset{color:inherit!important}.visible{visibility:visible!important}.invisible{visibility:hidden!important}@media print{*,::after,::before{text-shadow:none!important;box-shadow:none!important}a:not(.btn){text-decoration:underline}abbr[title]::after{content:" (" attr(title) ")"}pre{white-space:pre-wrap!important}blockquote,pre{border:1px solid #adb5bd;page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}h2,h3,p{orphans:3;widows:3}h2,h3{page-break-after:avoid}@page{size:a3}body{min-width:992px!important}.container{min-width:992px!important}.navbar{display:none}.badge{border:1px solid #000}.table{border-collapse:collapse!important}.table td,.table th{background-color:#fff!important}.table-bordered td,.table-bordered th{border:1px solid #dee2e6!important}.table-dark{color:inherit}.table-dark tbody+tbody,.table-dark td,.table-dark th,.table-dark thead th{border-color:#dee2e6}.table .thead-dark th{color:inherit;border-color:#dee2e6}} -/*# sourceMappingURL=bootstrap.min.css.map */ \ No newline at end of file diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/assets/css/coveo.css b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/assets/css/coveo.css deleted file mode 100644 index 6139d363d7..0000000000 --- a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/assets/css/coveo.css +++ /dev/null @@ -1,264 +0,0 @@ -@font-face { - font-family: Proxima-Regular; - font-style: normal; - font-weight: 500; - src: url(https://cdn.f5.com/websites/support/assets/fonts/ProximaRegular.woff2)format("woff2"),url(https://cdn.f5.com/websites/support/assets/fonts/ProximaRegular.woff)format("woff") -} - -.CoveoSearchInterface { - font-family: Proxima-Regular -} - -#searchbox { - max-width: 600px; - margin: 0 auto; - background: #fff; - border-radius: 4px; - height: 44px; - width:100%; -} - -#searchbox .CoveoSearchbox .magic-box,#searchbox .CoveoSearchbox .CoveoSearchButton,#searchbox .CoveoSearchbox .magic-box .magic-box-input,#searchbox .CoveoSearchbox .magic-box .magic-box-input>input,#searchbox .CoveoOmnibox.magic-box .magic-box-input .magic-box-underlay,#searchbox .magic-box .magic-box-clear { - height: 44px; - border: 0 -} - -#searchbox .coveo-search-button-svg,.CoveoSearchbox .magic-box .magic-box-input>input { - color: #000 -} - -#searchbox .CoveoSearchButton:hover .coveo-magnifier-circle-svg { - fill: #009639 -} - -#searchbox .CoveoSearchButton:hover .coveo-search-button-svg { - color: #009639 -} - -#searchbox .CoveoSearchbox .magic-box .magic-box-clear-svg { - color: #111922 -} - -#searchbox .magic-box-icon { - height: 44px; - line-height: 41px -} - -#search .coveo-facet-column { - padding-top: 0 -} - -.coveo-facet-header { - background: 0 0 -} - -.CoveoFacet { - border: 0; - margin-bottom: 0 -} - -.coveo-facet-footer { - display: none -} - -.coveo-facet-header-title { - color: #000; - font-size: 18px; - font-weight: 700 -} - -.coveo-facet-value-label { - color: #111922; - font-size: 14px -} - -.coveo-facet-value.coveo-with-hover.coveo-focused,.coveo-facet-value.coveo-with-hover:hover { - background-color: initial -} - -.coveo-facet-header-eraser svg { - width: 14px; - height: 14px; - margin-right: 7px; - color: #111922 -} - -#search .CoveoResultLink,#search .CoveoQuickview,#search .coveo-facet-breadcrumb-value *,#search .coveo-breadcrumb-clear-all { - color: #009639; - font-size: 14px -} - -#search .coveo-results-header { - height: 50px; - border-radius: 2px; - padding: 15px 0 15px 15px; - box-shadow: none; - box-sizing: border-box; - margin-top: 5px -} -#search .coveo-dropdown-header-wrapper{margin-top:10px;} -.coveo-facet-header-settings { - display: none -} - -.coveo-sort-section:before { - color: #111922; - content: "Sort by:"; - font-size: 14px; - padding-right: 15px; -} - -.coveo-sort-icon-descending-svg { - color: #000 -} - -.CoveoExcerpt { - font-size: 14px; - color: #000 -} - -#uri_link { - line-height: 10px -} - -#uri_link span { - color: #666; - font-size: 12px; - line-height: 10px -} - -.modified_date { - color: #666; - font-size: 12px; - font-weight: 700 -} - -.modified_date .coveo-field-caption { - color: #666; - font-size: 12px; - font-weight: 400 -} - -.coveo-summary-section span { - color: #666 -} - -.pagination-bar { - height: 50px; - background: #f1f1f1; - padding: 6px; - border-radius: 2px -} - -.pagination-bar .CoveoPager { - float: right -} - -.pagination-bar .CoveoResultsPerPage { - float: left -} - -.CoveoResultList { - margin: 0 -} - -.coveo-list-layout.CoveoResult { - padding-left: 0 -} - -.CoveoSort { - border-bottom: 0; - margin: 0 5px; - color: #666; - padding: 0 5px 5px; - font-weight: 400; - font-size: 14px; - text-transform: capitalize -} - -.CoveoSort.coveo-selected,.CoveoSort.coveo-selected:hover { - border-bottom: 2px solid #111922; - font-weight: 700; - color: #111922 -} - -.CoveoBreadcrumb,.coveo-list-layout.CoveoResult:last-child { - border-bottom: 0 -} - -.CoveoBreadcrumb { - padding: 5px 0 0 -} - -.coveo-pager-list-item.coveo-active,.coveo-pager-list-item:hover { - color: #111922; - background-color: initial -} - -.coveo-pager-list-item.coveo-active a,.coveo-pager-list-item:hover a { - color: #009639; - background-color: initial -} - -.coveo-results-per-page-text,.coveo-results-per-page-list-item,.coveo-pager-list-item,.coveo-pager-next-icon-svg,.coveo-pager-previous-icon-svg,.coveo-pager-next-icon-svg,.coveo-pager-previous-icon-svg { - color: #111922 -} - -.coveo-pager-next-icon-svg,.coveo-pager-previous-icon-svg,.coveo-pager-next-icon-svg,.coveo-pager-previous-icon-svg { - height: 16px; - margin-bottom: 4px -} - -.coveo-results-per-page-list-item.coveo-active,.coveo-results-per-page-list-item:hover { - color: #009639; - background-color: initial -} - -.coveo-results-per-page-list-item.coveo-active a,.coveo-results-per-page-list-item:hover a { - color: #009639 -} - -#reset_btn { - font-size: 12px; - color: #111922; - background: 0 0; - border: 0; - font-weight: 600; - margin-left: 10px; - cursor: pointer; - line-height: 16px; - display: none -} - -#reset_btn img { - float: left; - margin: 0 5px 0 0; - width: 15px -} - -.coveo-modal-backdrop{ -z-index: 1072 -} -.coveo-modal-container{ -z-index: 1073 -} -.coveo-modal-header{margin-bottom: 0} -#search .coveo-results-header.coveo-no-results {height:auto;} -#search .coveo-no-results .pagination-bar{background:none;} -#search .coveo-results-header.coveo-no-results .coveo-sort-section:before{display:none;} -@media (max-width: 800px) { -.pagination-bar .CoveoResultsPerPage{ -margin: 0 -} -#search .CoveoPager { - margin-top: 0; - margin-left: 0; - display: inline-block -} -#search .coveo-results-per-page-text{display:none} -} - -@media (max-width: 485px) { - -#search .coveo-results-header{height:auto} -} diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/assets/css/docs-nginx-com/nginx-site-header.css b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/assets/css/docs-nginx-com/nginx-site-header.css deleted file mode 100644 index 2a239bef36..0000000000 --- a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/assets/css/docs-nginx-com/nginx-site-header.css +++ /dev/null @@ -1,1018 +0,0 @@ -/* -All CSS related to site header includeing logo, CSR FTR buttons, search box, menu items. -*/ -#nx_masthead { - background-color: #21201F; - color: #f1f1f1; - position: absolute; - z-index: 110; - height:63px; -} -#nx_masthead.nx-site-header{ - top: 0; - z-index: 150; - position: fixed; - width: 100%; - -webkit-transition: all 200ms ease-in-out; - -moz-transition: all 200ms ease-in-out; - -ms-transition: all 200ms ease-in-out; - -o-transition: all 200ms ease-in-out; - transition: all 200ms ease-in-out - -} - -#nx_masthead+section, -#nx_masthead+div, -.single #content, -.athp-lead-section { - padding-top: 62px; -} -.single-nx_videos .site-content { - padding-top: 25px !important -} -.nx-site-header { - padding: 0 -} - - -.admin-bar #nx_masthead.nx-site-header{ - top: 32px -} - -#nx_masthead.nx-site-header .nx-primary-menu-location { - -webkit-transition: all 200ms ease-in-out; - -moz-transition: all 200ms ease-in-out; - -ms-transition: all 200ms ease-in-out; - -o-transition: all 200ms ease-in-out; - transition: all 200ms ease-in-out -} - -.nx-menu-logo{ - float: left; - margin-right:5px; - width: 140px; - margin-top: -14px; -} -.nx-header-login{ - float: left; - margin-top: 7px; - margin-left: 12px; -} -.nx-site-branding-wrapper{ - display: none; -} -.nx-primary-menu-location{ - margin-left: 20px; -} -.nx-primary-menu-location .menu-item>a { - padding: 5px 9px 0; -} - -.nx-login-text{ - display: none; -} - -.nx-site-header-inner { - padding: 18px 0 0; -} -.nx-site-title img { - width: 200px; - margin-top:-5px; -} -.header-actions-menu-location{ - top: 15px; -} -.header-actions-menu-location .menu-item { - display: inline-block -} -.header-actions-menu-location .menu-item .menu-item { - display: none -} -.header-actions-menu-location a { - border: 1px solid #F4A846; - border-radius: 3px; - display: block; - font-weight: 400; - padding: 8px 9px; - text-align: center -} -@media screen and (min-width: 980px) { - .header-actions-menu-location .no-border a { - border: 0; - padding-left: 0; - padding-right: 0 - } -} -.nx-header-menus a:link, -.nx-header-menus a:visited { - color: #d9d9d6 -} -.nx-header-menus a:hover, -.nx-header-menus a:active, -.nx-header-menus a:focus { - color: #fff -} -.search-form-wrapper { - font-size: 17px; - font-size: 1.7rem; - float: left; - padding-top: 3px; - position: relative; - margin-bottom: 14px; - height: 31px -} -.mobile-search-button-wrapper .search-form-wrapper { - height: 29px; - padding-top: 0; - position: absolute; - right: 0; - width: 29px -} -.search-form-wrapper:before { - -webkit-font-smoothing: antialiased; - content: "S"; - font-family: "nginx-font" !important; - font-style: normal !important; - font-variant: normal !important; - font-weight: normal !important; - left: 5px; - line-height: 1; - position: absolute; - speak: none; - text-transform: none !important; - top: 7px; - transition: all 0.4s ease; - z-index: 20 -} -.mobile-search-button-wrapper .search-form-wrapper:before { - left: auto; - right: 7px -} -.search-form-wrapper.focused:before { - color: #099650 -} -.mobile-search-button-wrapper .search-form-wrapper.focused:before { - right: 170px -} -.search-form-wrapper .search-button { - display: none -} -.search-form-wrapper .search-field { - background-color: rgba(255, 255, 255, 0); - border: none; - box-shadow: none; - color: #ccc; - cursor: pointer; - height: 24px; - margin: 0; - max-width: none; - outline: 0; - padding: 0 3px 0 24px; - position: relative; - transition: width 0.4s ease, background 0.4s ease; - width: 24px; - z-index: 30; - font-size: 14px -} -.mobile-search-button-wrapper .search-form-wrapper .search-field { - border-radius: 3px; - border: 1px solid #333333; - display: inline-block; - height: 29px; - line-height: 29px; - width: 29px -} -.mobile-search-button-wrapper .search-form-wrapper .search-field { - position: absolute; - right: 0 -} -.search-form-wrapper .search-field:focus { - background-color: #fff; - color: #111; - cursor: text; - width: 190px; - z-index: 10 -} -.icon-menu:before { - content: "k" -} - -.single-nx_faqs .nginx-single-meta{ - display: none; -} - -/*free trial and contact sales*/ -.free-trial-inner{ - -moz-box-shadow: 0 0 5px 3px rgba(0,0,0,0.06); - -webkit-box-shadow: 0 0 5px 3px rgba(0,0,0,0.06); - box-shadow: 0 0 5px 3px rgba(0,0,0,0.06); -} -.free-trial-inner form input, -.contact-us-inner form input, -.partner-credit-inner form input, -.controller-beta-inner form input, -.unit-beta-inner form input, -.get-certified-pop-content form input, -.partners-popup-content form input, -.page-template-developer-license-php .freetrialpg-form-wrapper form input, -.free-trial-inner textarea, .contact-us-inner textarea, -.partner-credit-inner textarea, .controller-beta-inner textarea, -.get-certified-pop-content textarea, -.partners-popup-content textarea, -.page-template-developer-license-php .freetrialpg-form-wrapper form textarea, -.unit-beta-inner textarea { - background-color: transparent; - color: #989898; - font-weight: normal; - margin-bottom: 7px; - width: 100%; - box-shadow: none; - border: 1px solid #979797; - border-radius: 3px; - padding-left: 10px; - padding-right: 10px; - font: 200 15px "Roboto-Regular","Helvetica Neue",Arial,sans-serif; -} -.free-trial-inner form input, -.contact-us-inner form input, -.partner-credit-inner form input, -.controller-beta-inner form input, -.get-certified-pop-content form input, -.page-template-developer-license-php .freetrialpg-form-wrapper form input, -.partners-popup-content form input, -.unit-beta-inner form input { - height: 32px; - line-height: 30px; -} -.free-trial-inner textarea, .contact-us-inner textarea, -.partner-credit-inner textarea, .controller-beta-inner textarea, -.get-certified-pop-content textarea, -.partners-popup-content textarea, -.page-template-developer-license-php .freetrialpg-form-wrapper form textarea, -.unit-beta-inner textarea { - height: 65px; -} -.select2-container .select2-selection--single .select2-selection__rendered{ - border-radius: 3px; - font: 200 15px "Roboto-Regular","Helvetica Neue",Arial,sans-serif; -} -.free-trial-dropdown.select2-selection.select2-selection--single{ - font: 200 15px "Roboto-Regular","Helvetica Neue",Arial,sans-serif; -} -.free-trial-inner button, .contact-us-inner button, .partner-credit-inner button{ - margin-top: 12px; -} -.free-trial-inner, -.contact-us-inner, -.partner-credit-inner, -.controller-beta-inner, -.unit-beta-inner, -.get-certified-pop-content, -.partners-popup-content{ - padding: 25px !important; -} -.free-trial-inner button.nginx-button, -.contact-us-inner button.nginx-button, -.partner-credit-inner button.nginx-button, -.controller-beta-inner button.nginx-button, -.get-certified-pop-content button.nginx-button, -.unit-beta-inner button.nginx-button { - box-shadow: none; - height: auto; - text-shadow: none; - transition: none; - border: 2px solid #F0A828; - font-size: 17px; - text-transform: uppercase; - border-radius: 4px; - padding: 11px 35px; - font-weight: 500; - position: relative; -} -.free-trial-inner h2, -.contact-us-inner h2, -.partner-credit-inner h2, -.controller-beta-inner h2, -.unit-beta-inner h2{ - font-size: 26px; - margin-bottom: 5px; -} - -/*end*/ - -@media screen and (min-width: 800px){ - .single .site-content-inner { - padding-top: 16px; - } - .nx-footer-menu .sub-menu a { - margin-bottom: 3px; - line-height: 20px; - } -} - - -@media screen and (max-width: 600px) { - #nx_masthead .site-branding .nx-site-title.logo-left { - margin-left: 50px - } -}/*end @media max-width: 600px */ - -@media screen and (max-width: 979px) { - #nx_masthead { - height: 69px - } - #nx_masthead .nx-site-title { - font-size: 18px; - font-size: 1.8rem; - margin: 2px auto 0; - min-height: 30px; - width: 104px - } - #nx_masthead+section, - #nx_masthead+div, - .single #content, - .athp-lead-section { - padding-top: 68px - } - .nx-site-header-inner { - padding-bottom: 0 - } - .nx-header-menus { - background-color: #020101; - border-bottom: 1px solid #009647; - box-shadow: 0 10px 15px 0 rgba(0, 0, 0, 0.25); - display: none; - padding-top: 12px; - position: absolute; - top: 69px; - width: 100%; - z-index: 300 - } - .nx-site-header.mobile-expanded .nx-header-menus { - display: block - } - .mobile-menu-button-wrapper, - .mobile-search-button-wrapper { - line-height: 1; - position: absolute; - top: 18px - } - .mobile-menu-button { - border-radius: 3px; - display: inline-block; - height: 29px; - line-height: 29px; - text-align: center; - width: 29px - } - .mobile-menu-button:link, - .mobile-menu-button:visited { - background-color: transparent; - border: 1px solid #333333; - color: #FFF - } - .mobile-menu-button:hover, - .mobile-menu-button:active, - .mobile-menu-button:focus { - background-color: #F2A83A; - border: 1px solid transparent; - color: #FFF - } - .mobile-menu-button .icon { - display: inline; - line-height: 1.5; - vertical-align: middle - } - .mobile-expanded .mobile-menu-button:link, - .mobile-expanded .mobile-menu-button:visited, - .mobile-expanded .mobile-menu-button:focus { - background-color: #5a3806 - } - .mobile-expanded .mobile-menu-button:hover, - .mobile-expanded .mobile-menu-button:active { - background-color: #F2A83A - } - .mobile-menu-button-wrapper { - left: 2.5788% - } - .mobile-search-button-wrapper { - right: 2.5788% - } - .nx-menu, - .sub-menu { - display: block; - list-style: none; - margin-bottom: 0; - margin-left: 0 - } - .nx-menu { - text-transform: uppercase - } - .sub-menu { - background-color: #21201F; - display: none; - padding-top: 4px; - text-transform: none - } - .item-mobile-expanded .sub-menu { - display: block - } - .nx-primary-menu-location .search-form-wrapper { - display: none - } - .nx-primary-menu-location .menu-item { - border-top: 1px solid #333232 - } - .nx-primary-menu-location .menu-item a { - display: block; - position: relative - } - .nx-primary-menu-location .menu-item a .inner { - display: block; - padding: 14px 46px 14px 2.5788%; - position: relative; - padding-left:19px; - } - .nx-primary-menu-location .menu-item .menu-item { - border-top: none - } - .nx-primary-menu-location .menu-item .menu-item a { - display: block - } - .nx-primary-menu-location .menu-item .menu-item .inner { - background-color: transparent; - padding: 14px 0 14px 5.15759% - } - .nx-primary-menu-location .menu-item .menu-item:last-of-type { - border-bottom: none - } - .nx-primary-menu-location .menu-item:last-of-type { - border-bottom: 1px solid #333232 - } - .nx-primary-menu-location .menu-item.menu-item-has-children>a .icon { - border-left: 1px solid #333232; - font-size: 16px; - font-size: 1.6rem; - height: 80%; - line-height: 39px; - padding: 0 18px; - position: absolute; - right: 0; - top: 10% - } - - .header-actions-menu-location { - padding: 0 2.5788% - } - .header-actions-menu-location .nx-menu { - *zoom: 1; - margin-bottom: 0 - } - .header-actions-menu-location .nx-menu:before, - .header-actions-menu-location .nx-menu:after { - content: " "; - display: table - } - .header-actions-menu-location .nx-menu:after { - clear: both - } - .header-actions-menu-location .nx-menu-item { - float: left; - margin-bottom: 12px; - padding-right: 12px; - width: 50% - } - .header-actions-menu-location .menu-item:nth-child(2n) { - padding-right: 0 - } - - .header-extras-wrapper a { - display: block; - text-align: center - } - .header-extras-wrapper a:link, - .header-extras-wrapper a:visited { - color: #999999 - } - .header-extras-wrapper a:hover, - .header-extras-wrapper a:active, - .header-extras-wrapper a:focus { - color: #d9d9d6 - } - .header-extras-wrapper .header-extras { - *zoom: 1 - } - .header-extras-wrapper .header-extras:before, - .header-extras-wrapper .header-extras:after { - content: " "; - display: table - } - .header-extras-wrapper .header-extras:after { - clear: both - } - .header-extras-wrapper .header-extra { - border-right: 1px solid #333232; - float: left; - padding: 14px 7px 14px 2.5788%; - width: 50% - } - .header-extras-wrapper .header-extra:last-child { - border-right: none - } - .header-extras-wrapper .header-tel { - display: inline-block - } - .header-extras-wrapper .login-link .icon, - .header-extras-wrapper .login-link .nx-login-text { - display: inline - } - .header-extras-wrapper .login-link .icon { - vertical-align: middle - } - #menu-item-3677 { - display: none - } - - -}/* end @media screen and (max-width: 979px) */ - -@media screen and (max-width: 979px) and (min-width: 500px) { - .sub-menu { - -moz-column-count: 2; - -webkit-column-count: 2; - column-count: 2 - } -} - -@media screen and (max-width: 979px) and (max-width: 350px) { - .header-actions-menu-location { - font-size: 14px; - font-size: 1.4rem - } -} - -@media screen and (min-width: 980px) { - .mobile-menu-button-wrapper, - .mobile-search-button-wrapper { - display: none - } - .nx-primary-menu-location { - display: inline-block; - font-size: 14px; - font-size: 1.4rem; - font-weight: 400; - float: left; - margin-left: 20px; - margin-right: 20px; - position: relative; - text-transform: uppercase - } - .nx-primary-menu-location .menu-primary-container { - position: relative; - float: left - } - .nx-primary-menu-location a { - transition: color 0.1s ease - } - .nx-primary-menu-location a:link, - .nx-primary-menu-location a:visited { - color: #d9d9d6 - } - .nx-primary-menu-location a:hover, - .nx-primary-menu-location a:active, - .nx-primary-menu-location a:focus { - color: #fff - } - .nx-primary-menu-location .nx-menu { - display: block; - list-style: none; - margin: 0 0 0 0 - } - .nx-primary-menu-location .menu-item { - display: inline-block; - margin-right: 6px; - padding: 0 0; - position: relative - } - - .nx-site-header { - padding: 0 2.5788% - } - .nx-primary-menu-location .menu-item.current-menu-item a:link, - .nx-primary-menu-location .menu-item.current-menu-item a:visited, - .nx-primary-menu-location .menu-item.current-menu-item a:hover, - .nx-primary-menu-location .menu-item.current-menu-item a:active, - .nx-primary-menu-location .menu-item.current-menu-item a:focus { - color: #FFF - } - .nx-primary-menu-location .menu-item.current-menu-item>a .inner { - border-bottom: 2px solid #14943E - } - .nx-primary-menu-location .menu-item>a { - display: block; - padding: 5px 11px 0; - position: relative - } - .nx-primary-menu-location .menu-item>a .inner { - display: inline-block; - padding-bottom: 17px; - position: relative - } - .nx-primary-menu-location .menu-item.menu-item-has-children>a .icon { - display: inline-block; - font-size: 16px; - font-size: 1.6rem - } - .nx-primary-menu-location .menu-item.menu-item-has-children>a .icon::before { - vertical-align: middle - } - .nx-primary-menu-location .menu-item.menu-item-has-children:hover>a { - background-color: #14943E - } - .nx-primary-menu-location .menu-item.menu-item-has-children:hover>a::after { - content: ''; - background-image: url('img/primary-menu-parent-item-edge.png'); - height: 31px; - position: absolute; - right: -21px; - top: 0; - width: 21px - } - .nx-primary-menu-location .menu-item.menu-item-has-children:hover .sub-menu { - display: block - } - .nx-primary-menu-location .menu-item .menu-item { - line-height: 1.2; - margin: 0 0 1px 0 - } - .nx-primary-menu-location .menu-item .menu-item:last-child { - margin-bottom: 0 - } - .nx-primary-menu-location .menu-item .menu-item:last-child::after { - display: none - } - .nx-primary-menu-location .menu-item .menu-item::after { - background-color: #403F3F; - content: ''; - display: block; - height: 1px; - left: 0; - margin: 0 12px 0 14px; - position: absolute; - right: 0 - } - .nx-primary-menu-location .menu-item .menu-item a { - display: block; - padding: 12px 12px 12px 14px; - padding-bottom: 0; - } - .nx-primary-menu-location .menu-item .menu-item a:hover, - .nx-primary-menu-location .menu-item .menu-item a:active, - .nx-primary-menu-location .menu-item .menu-item a:focus { - background-color: #323130 - } - .nx-primary-menu-location .menu-item .menu-item a .inner { - display: block; - padding-bottom: 16px; - } - .nx-primary-menu-location .menu-item .menu-item .menu-item { - display: none - } - .nx-primary-menu-location .sub-menu { - background-color: #232221; - border-left: none; - border-right: none; - display: none; - left: 0; - margin-left: 0; - position: absolute; - text-transform: none; - top: 31px; - width: 210px; - z-index: 300 - } - .nx-primary-menu-location .sub-menu li { - display: block - } - .header-actions-menu-location { - font-size: 14px; - font-size: 1.4rem; - line-height: 1.1; - position: absolute; - right: 0; - text-transform: uppercase; - } - .header-actions-menu-location .nx-menu { - display: block; - list-style: none; - margin: 0 0 0 0 - } - .header-actions-menu-location .menu-item { - margin-left: 9px - } - - .header-extras-wrapper { - color: #d9d9d6; - float: right; - font-size: 14px; - font-size: 1.4rem; - font-weight: 400; - margin-bottom: 14px; - margin-left: 1em; - padding-top: 7px - } - - .header-extras-wrapper .header-tel { - display: none - } - .header-extras-wrapper .nx-header-login { - display: inline-block; - vertical-align: top - } - .header-extras-wrapper .nx-login-text { - display: none - } - .header-extras-wrapper .header-tel { - margin-right: 1em - } - .header-extras-wrapper .icon-user, - .header-extras-wrapper .icon-user { - display: inline; - font-size: 20px; - font-size: 2rem; - line-height: 1 - } - .header-extras-wrapper .nx-header-login a:link, - .header-extras-wrapper .nx-header-login a:visited { - color: #d9d9d6 - } - .header-extras-wrapper .nx-header-login a:hover, - .header-extras-wrapper .nx-header-login a:active, - .header-extras-wrapper .nx-header-login a:focus { - color: #FFF - } - -}/* end @media screen and (min-width: 980px) */ - -@media screen and (min-width: 980px) and (max-width: 1200px) { - .nx-primary-menu-location { - font-size: 12px - } -} - -@media screen and (min-width: 980px) and (min-width: 1120px) { - .nx-primary-menu-location .menu-item { - margin-right: 6px; - } -} - -@media screen and (min-width: 980px) and (max-width: 1200px) { - .header-extras-wrapper { - font-size: 12px; - } -} - -@media screen and (max-width: 800px) { - .single-post .hentry{ - margin-top:20px; - } -} -@media only screen and (max-width: 979px) { - .nx-site-branding-wrapper{ - display: block; - } - .nx-menu-logo, - .icon-user{ - display: none; - } - .nx-login-text{ - display: block; - padding: 8px 0 15px 15px; - text-transform: uppercase; - } - .nx-site-branding-wrapper{ - display: block; - } - - .nx-login-text{ - display: block; - padding: 8px 0 15px 15px; - text-transform: uppercase; - } - .nx-site-header{ - position: fixed; - } - .nx-header-menus{ - overflow-y: scroll; - } -}/* end @media only screen and (max-width: 767px) */ - -@media only screen and (min-width: 979px) { - .nx-primary-menu-location .menu-item.menu-item-has-children:hover>a::after, - .nx-primary-menu-location .menu-item.menu-item-has-children>a .icon{ - display: none !important; - } -} -@media screen and (max-width: 767px){ - .nx-footer-menu .sub-menu a { - margin-bottom: 2px; - } -} -@media screen and (max-width: 550px) { - .single-post .nginx-single-meta{ - height: auto; - } - .single-post .nginx-post-date{ - padding-left: 10px; - } -} -@media screen and (max-width: 480px) { - .single-post .nginx-post-author{ - padding-left: 10px; - } -} - - -/*============== Footer CSS ==============*/ -.site-footer:before, -.site-footer:after{content:"";display:table} -.infinite-scroll.neverending .site-footer{ - display:none; -} -.infinity-end.neverending .site-footer{ - display:block -} -#colophon { - background-color: #32302F; - box-shadow: 0 -10px 15px -3px rgba(0,0,0,0.2); - color: #f1f1f1; - position: relative; - z-index: 100; -} -.site-footer { - padding: 0 2.5788%; -} -.site-footer-inner { - max-width: 1120px; - margin-left: auto; - margin-right: auto; - min-width: 280px; - position: relative; -} -.site-footer-inner { - padding: 0px 0; -} -.site-footer .site-info { - clear: both; - color: #999; - width: 100%; - font-size: 12px; - font-size: 1.2rem; - font-weight: 300; - padding-top: 35px; - opacity: .5; -} - - -.footer-primary-nav, .footer-secondary-nav, .footer-connect-nav { - content: ""; - display: table; - float: left; - max-width: 400px; - width: 100%; -} -.nx-footer-menu-wrap { - width: 45.92145%; - float: left; - margin-right: 8.1571%; -} -.nx-footer-menu { - display: block; - font-size: 13px; - font-size: 1.3rem; - font-weight: 700; - list-style: none; - margin: 0; -} -.footer-secondary-nav{ - position:relative -} -.footer-secondary-nav:after{ - background-color:#999; - bottom:0; - content:''; - display:none; - opacity:.5; - position:absolute; - top:0; - width:1px -} -.footer-connect-nav{ - clear:both -} -.footer-connect-nav .footer-head{ - color:#999; - font-size:15px; - font-size:1.5rem; - font-weight:700; - margin-bottom:10px -} -.nx-footer-menu-wrap:last-child{ - float:right; - margin-right:0 -} -.nx-footer-menu li:first-child>a{ - padding-top:0 -} -.nx-footer-menu a{ - display:block; - padding:10px 0 -} -.nx-footer-menu a:link,.nx-footer-menu a:visited{ - color:#999; - text-decoration:none -} -.nx-footer-menu a:hover,.nx-footer-menu a:active,.nx-footer-menu a:focus{ - color:#f1f1f1; - text-decoration:underline -} -.nx-footer-menu .sub-menu{ - background-color:transparent; - -moz-column-count:auto; - -webkit-column-count:auto; - column-count:auto; - display:block; - list-style:none; - margin:-2px 0 10px; - padding-top:0 -} -.nx-footer-menu .sub-menu a{ - font-weight:300; - padding:2px 0 -} - - - - -@media screen and (min-width: 500px){ - .footer-primary-nav, .footer-secondary-nav, .footer-connect-nav { - width: 48.7106%; - float: left; - margin-right: 2.5788%; - } - -} -@media screen and (min-width: 800px){ - .site-footer-inner { - padding: 20px 0; - } - .footer-primary-nav, .footer-secondary-nav, .footer-connect-nav { - width: 31.61414%; - float: left; - margin-right: 2.5788%; - max-width: none; - } - .footer-connect-nav{ - clear:none; - float:right; - margin-right:0 - } - .footer-secondary-nav:after{ - display:block; - right:0 - } - .footer-social{ - margin-top:0 - } -} -@media screen and (min-width: 1120px){ - .site-footer .site-info { - font-size: 15px; - padding-top: 30px; - } - .nx-footer-menu-wrap { - width: 45.92145%; - float: left; - margin-right: 8.1571%; - } - .nx-footer-menu { - font-size: 15px; - font-size: 1.5rem; - } - .footer-connect-nav{ - width:23.0659%; - float:left; - margin-right:2.5788%; - float:right; - margin-right:0 - } - .footer-secondary-nav:after{ - right:-70px - } -} -@media screen and (min-width: 500px) and (max-width: 799px){ - .footer-secondary-nav{ - float:right; - margin-right:0 - } -} diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/assets/css/docs-nginx-com/style.css b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/assets/css/docs-nginx-com/style.css deleted file mode 100644 index fcddb4958e..0000000000 --- a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/assets/css/docs-nginx-com/style.css +++ /dev/null @@ -1,2249 +0,0 @@ -/*copy from nginx-theme.css */ - -big, -html, -small { - font-family: inherit -} -body { - vertical-align: baseline; - background: #32302f -} -a, -abbr, -acronym, -applet, -del, -div, -dl, -fieldset, -font, -form, -iframe, -ins, -label, -legend, -li, -object, -q, -s, -samp, -span, -strike, -tbody, -tfoot, -thead, -tr { - border: 0; - font-family: inherit; - font-size: 100%; - font-style: inherit; - font-weight: inherit; - margin: 0; - outline: 0; - padding: 0; - vertical-align: baseline -} -h1, -h2, -h3, -h4, -h5, -h6 { - padding: 0; - vertical-align: baseline -} -address, -blockquote, -dd, -ol, -p, -table, -ul { - border: 0; - font-family: inherit; - font-style: inherit; - font-weight: inherit; - outline: 0; - padding: 0 -} -cite, -dfn, -em, -h1, -h2, -h3, -h4, -h5, -h6, -pre { - border: 0; - outline: 0 -} -h1, -h2, -h3, -h4, -h5, -h6, -pre { - font-style: inherit -} -cite, -dfn, -em { - font-family: inherit; - font-weight: inherit; - margin: 0; - padding: 0 -} -big, -body, -caption, -code, -dt, -html, -kbd, -small, -strong, -sub, -sup, -td, -th, -tt, -var { - border: 0; - font-style: inherit; - margin: 0; - outline: 0; - padding: 0 -} -big, -code, -html, -kbd, -pre, -small, -tt, -var { - font-weight: inherit; - vertical-align: baseline -} -caption, -dt, -strong, -sub, -sup, -td, -th { - font-family: inherit -} -address, -blockquote, -caption, -cite, -dd, -dfn, -dt, -em, -ol, -p, -strong, -table, -td, -th, -ul { - font-size: 100%; - vertical-align: baseline -} -sub, -sup { - font-weight: inherit -} -html { - scroll-behavior: smooth; - font-size: 62.5%; - overflow-y: scroll; - -webkit-text-size-adjust: 100%; - -ms-text-size-adjust: 100%; - box-sizing: border-box -} -*, -:after, -:before { - box-sizing: inherit -} -article, -aside, -details, -figcaption, -figure, -footer, -header, -main, -nav, -section { - display: block -} - -caption, -td { - font-weight: 300 -} -caption, -td, -th { - text-align: left -} -blockquote:after, -blockquote:before, -q:after, -q:before { - content: "" -} -blockquote, -q { - quotes: "" "" -} -a:focus { - outline: thin dotted -} -a:active, -a:hover { - outline: 0 -} -a img, -hr { - border: 0 -} -body { - color: #404040; - font-size: 15.5px; - font-weight: 300 -} -button, -input, -select { - color: #404040; - font-weight: 300 -} -body, -button, -h1, -h2, -h3, -h4, -h5, -h6, -input, -select, -textarea { - font-family: "Roboto-Regular", "Helvetica Neue", Arial, sans-serif -} -body, -input, -select, -textarea { - font-size: 1.4rem; - line-height: 1.4; -} -h1, -h2, -h3, -h4, -textarea { - font-weight: 300 -} -h1, -h2, -h3, -h4, -h5, -h6 { - clear: both; - line-height: 1.25; - margin: 0 0 .5em -} -h1 { - font-size: 38px; -} -h2 { - font-size: 31.5px; -} -h3 { - font-size: 20px; -} -h4, -h5, -h6 { - font-size: 17px; -} -b, -dt, -h5, -h6, -strong, -th { - font-weight: 700 -} -cite, -dfn, -em, -i { - font-style: italic -} - -img{ - max-width: 100% -} - -code, -kbd, -tt, -var { - font-family: Consolas, "Andale Mono", "DejaVu Sans Mono", monospace; - font-size: 100% -} -pre code { - font-size: .85em -} -abbr, -acronym { - border-bottom: 1px dotted #666; - cursor: help -} -ins, -mark { - background: #fff9c0; - text-decoration: none -} -sub, -sup { - font-size: 75%; - height: 0; - line-height: 0; - position: relative; - vertical-align: baseline -} -sup { - bottom: 1ex -} -sub { - top: .5ex -} -small { - font-size: 75% -} -big { - font-size: 125% -} -hr { - background-color: #ccc; - height: 1px; - margin-bottom: 1.5em -} -ol, -ul { - margin: 0 0 1.5em 1em -} -ul { - list-style: disc -} -ol { - list-style: decimal -} -li>ol, -li>ul { - margin-bottom: 0; - margin-left: 1.5em -} -dd { - margin: 0 1.5em 1.5em -} -img { - height: auto -} -button, -figure { - margin: 0 -} -button, -input, -select { - vertical-align: baseline -} -input, -select, -textarea { - font-size: 100%; - margin: 0 -} -button, -input[type=button], -input[type=reset], -input[type=submit] { - border: 1px solid; - border-color: #ccc #ccc #bbb; - border-radius: 3px; - background: #e6e6e6; - box-shadow: inset 0 1px 0 rgba(255, 255, 255, .5), inset 0 15px 17px rgba(255, 255, 255, .5), inset 0 -5px 12px rgba(0, 0, 0, .05); - color: rgba(0, 0, 0, .8); - cursor: pointer; - -webkit-appearance: button; - font-size: 12px; - font-size: 1.2rem; - line-height: 1; - padding: .6em 1em .4em; - text-shadow: 0 1px 0 rgba(255, 255, 255, .8) -} -button:hover, -input[type=button]:hover, -input[type=reset]:hover, -input[type=submit]:hover { - border: none; - box-shadow: none; - background-color: #0c5c8d; -} -button:active, -button:focus, -input[type=button]:active, -input[type=button]:focus, -input[type=reset]:active, -input[type=reset]:focus, -input[type=submit]:active, -input[type=submit]:focus { - border: none; - box-shadow: none; -} -input[type=checkbox], -input[type=radio] { - padding: 0 -} -input[type=search] { - -webkit-appearance: textfield -} -input[type=search]::-webkit-search-decoration { - -webkit-appearance: none -} -button::-moz-focus-inner, -input::-moz-focus-inner { - border: 0; - padding: 0 -} -input[type=email], -input[type=password], -input[type=search], -input[type=text], -input[type=url], -textarea { - color: #666; - border: 1px solid #ccc; - border-radius: 2px; - font-size: inherit; - font-weight: normal; -} -input[type=email]:focus, -input[type=password]:focus, -input[type=search]:focus, -input[type=text]:focus, -input[type=url]:focus, -textarea:focus { - color: #111 -} -input[type=email], -input[type=password], -input[type=search], -input[type=text], -input[type=url] { - padding: 4px -} -textarea { - overflow: auto; - padding-left: 4px; - vertical-align: top; - width: 100% -} -a { - color: #009639 -} - -@font-face { - font-family: 'Roboto-Regular'; - src: url('../fonts/roboto/Roboto-Regular.eot'); - src: url('../fonts/roboto/Roboto-Regular.woff2') format('woff2'), - url('../fonts/roboto/Roboto-Regular.woff') format('woff'), - url('../fonts/roboto/Roboto-Regular.ttf') format('truetype'), - url('../fonts/roboto/Roboto-Regular.svg#Roboto-Regular') format('svg'), - url('../fonts/roboto/Roboto-Regular.eot?#iefix') format('embedded-opentype'); - font-weight: normal; - font-style: normal; -} -@font-face { - font-family: 'Roboto-Regular'; - src: url('../fonts/roboto/Roboto-ThinItalic.eot'); - src: url('../fonts/roboto/Roboto-ThinItalic.woff2') format('woff2'), - url('../fonts/roboto/Roboto-ThinItalic.woff') format('woff'), - url('../fonts/roboto/Roboto-ThinItalic.ttf') format('truetype'), - url('../fonts/roboto/Roboto-ThinItalic.svg#Roboto-ThinItalic') format('svg'), - url('../fonts/roboto/Roboto-ThinItalic.eot?#iefix') format('embedded-opentype'); - font-weight: 200; - font-style: italic; -} -@font-face { - font-family: 'Roboto-Regular'; - src: url('../fonts/roboto/Roboto-Thin.eot'); - src: url('../fonts/roboto/Roboto-Thin.woff2') format('woff2'), - url('../fonts/roboto/Roboto-Thin.woff') format('woff'), - url('../fonts/roboto/Roboto-Thin.ttf') format('truetype'), - url('../fonts/roboto/Roboto-Thin.svg#Roboto-Thin') format('svg'), - url('../fonts/roboto/Roboto-Thin.eot?#iefix') format('embedded-opentype'); - font-weight: 200; - font-style: normal; -} -@font-face { - font-family: 'Roboto-Regular'; - src: url('../fonts/roboto/Roboto-MediumItalic.eot'); - src: url('../fonts/roboto/Roboto-MediumItalic.woff2') format('woff2'), - url('../fonts/roboto/Roboto-MediumItalic.woff') format('woff'), - url('../fonts/roboto/Roboto-MediumItalic.ttf') format('truetype'), - url('../fonts/roboto/Roboto-MediumItalic.svg#Roboto-MediumItalic') format('svg'), - url('../fonts/roboto/Roboto-MediumItalic.eot?#iefix') format('embedded-opentype'); - font-weight: 400; - font-style: italic; -} -@font-face { - font-family: 'Roboto-Regular'; - src: url('../fonts/roboto/Roboto-Medium.eot'); - src: url('../fonts/roboto/Roboto-Medium.woff2') format('woff2'), - url('../fonts/roboto/Roboto-Medium.woff') format('woff'), - url('../fonts/roboto/Roboto-Medium.ttf') format('truetype'), - url('../fonts/roboto/Roboto-Medium.svg#Roboto-Medium') format('svg'), - url('../fonts/roboto/Roboto-Medium.eot?#iefix') format('embedded-opentype'); - font-weight: 400; - font-style: normal; -} -@font-face { - font-family: 'Roboto-Regular'; - src: url('../fonts/roboto/Roboto-LightItalic.eot'); - src: url('../fonts/roboto/Roboto-LightItalic.woff2') format('woff2'), - url('../fonts/roboto/Roboto-LightItalic.woff') format('woff'), - url('../fonts/roboto/Roboto-LightItalic.ttf') format('truetype'), - url('../fonts/roboto/Roboto-LightItalic.svg#Roboto-LightItalic') format('svg'), - url('../fonts/roboto/Roboto-LightItalic.eot?#iefix') format('embedded-opentype'); - font-weight: 300; - font-style: italic; -} -@font-face { - font-family: 'Roboto-Regular'; - src: url('../fonts/roboto/Roboto-Light.eot'); - src: url('../fonts/roboto/Roboto-Light.woff2') format('woff2'), - url('../fonts/roboto/Roboto-Light.woff') format('woff'), - url('../fonts/roboto/Roboto-Light.ttf') format('truetype'), - url('../fonts/roboto/Roboto-Light.svg#Roboto-Light') format('svg'), - url('../fonts/roboto/Roboto-Light.eot?#iefix') format('embedded-opentype'); - font-weight: 300; - font-style: normal; -} -@font-face { - font-family: 'Roboto-Regular'; - src: url('../fonts/roboto/Roboto-Italic.eot'); - src: url('../fonts/roboto/Roboto-Italic.woff2') format('woff2'), - url('../fonts/roboto/Roboto-Italic.woff') format('woff'), - url('../fonts/roboto/Roboto-Italic.ttf') format('truetype'), - url('../fonts/roboto/Roboto-Italic.svg#Roboto-Italic') format('svg'), - url('../fonts/roboto/Roboto-Italic.eot?#iefix') format('embedded-opentype'); - font-weight: normal; - font-style: italic; -} -@font-face { - font-family: 'Roboto-Regular'; - src: url('../fonts/roboto/Roboto-BoldItalic.eot'); - src: url('../fonts/roboto/Roboto-BoldItalic.woff2') format('woff2'), - url('../fonts/roboto/Roboto-BoldItalic.woff') format('woff'), - url('../fonts/roboto/Roboto-BoldItalic.ttf') format('truetype'), - url('../fonts/roboto/Roboto-BoldItalic.svg#Roboto-BoldItalic') format('svg'), - url('../fonts/roboto/Roboto-BoldItalic.eot?#iefix') format('embedded-opentype'); - font-weight: 700; - font-style: italic; -} -@font-face { - font-family: 'Roboto-Regular'; - src: url('../fonts/roboto/Roboto-Bold.eot'); - src: url('../fonts/roboto/Roboto-Bold.woff2') format('woff2'), - url('../fonts/roboto/Roboto-Bold.woff') format('woff'), - url('../fonts/roboto/Roboto-Bold.ttf') format('truetype'), - url('../fonts/roboto/Roboto-Bold.svg#Roboto-Bold') format('svg'), - url('../fonts/roboto/Roboto-Bold.eot?#iefix') format('embedded-opentype'); - font-weight: 700; - font-style: normal; -} -@font-face { - font-family: 'Roboto-Regular'; - src: url('../fonts/roboto/Roboto-BlackItalic.eot'); - src: url('../fonts/roboto/Roboto-BlackItalic.woff2') format('woff2'), - url('../fonts/roboto/Roboto-BlackItalic.woff') format('woff'), - url('../fonts/roboto/Roboto-BlackItalic.ttf') format('truetype'), - url('../fonts/roboto/Roboto-BlackItalic.svg#Roboto-BlackItalic') format('svg'), - url('../fonts/roboto/Roboto-BlackItalic.eot?#iefix') format('embedded-opentype'); - font-weight: 900; - font-style: italic; -} -@font-face { - font-family: 'Roboto-Regular'; - src: url('../fonts/roboto/Roboto-Black.eot'); - src: url('../fonts/roboto/Roboto-Black.woff2') format('woff2'), - url('../fonts/roboto/Roboto-Black.woff') format('woff'), - url('../fonts/roboto/Roboto-Black.ttf') format('truetype'), - url('../fonts/roboto/Roboto-Black.svg#Roboto-Black') format('svg'), - url('../fonts/roboto/Roboto-Black.eot?#iefix') format('embedded-opentype'); - font-weight: 900; - font-style: normal; -} - - -@font-face { - font-family: "nginx-font"; - src: url(../fonts/nginx-font/fonts/nginx-font.eot?1480854649); - src: url(../fonts/nginx-font/fonts/nginx-font.eot?&1480854649#iefix) format("embedded-opentype"), url(../fonts/nginx-font/fonts/nginx-font.woff?1480854649) format("woff"), url(../fonts/nginx-font/fonts/nginx-font.ttf?1480854649) format("truetype"), url(../fonts/nginx-font/fonts/nginx-font.svg?1480854649#nginx-font) format("svg"); - font-weight: 400; - font-style: normal -} -[data-icon]:before { - content: attr(data-icon) -} -[class*=" icon-"]:before, -[class^=icon-]:before, -[data-icon]:before { - font-family: "nginx-font"!important; - font-style: normal!important; - font-weight: 400!important; - font-variant: normal!important; - text-transform: none!important; - speak-as: none; - line-height: 1; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale -} -.icon-arrow-disc:before { - content: "1" -} -.icon-nginx-cloud:before { - content: "b" -} -.icon-nginx-testdeploy:before { - content: "c" -} -.icon-nginx-webserver:before { - content: "d" -} -.icon-nginx-connected:before { - content: "e" -} -.icon-arrow-down:before { - content: "3" -} -.icon-arrow-right:before { - content: "4" -} -.icon-nginx-database:before { - content: "h" -} -.icon-nginx-dynamic:before { - content: "i" -} -.icon-arrow-submit:before { - content: "2" -} -.icon-nginx-focus:before { - content: "l" -} -.icon-nginx-internet:before { - content: "m" -} -.icon-linkedin:before { - content: "L" -} -.icon-play:before { - content: "9" -} -.icon-nginx-loadbalance:before { - content: "p" -} -.icon-nginx-modern:before { - content: "q" -} -.icon-rss:before { - content: "R" -} -.icon-search:before { - content: "S" -} -.icon-nginx-monitoring:before { - content: "t" -} -.icon-nginx-performance:before { - content: "u" -} -.icon-twitter:before { - content: "T" -} -.icon-user:before { - content: "U" -} -.icon-nginx-reliability:before { - content: "x" -} -.icon-nginx-softwareconfigure:before { - content: "y" -} -.icon-nginx-application:before { - content: "z" -} -.icon-nginx-caching:before { - content: "a" -} -.icon-nginx-streamingmedia:before { - content: "f" -} -.icon-facebook:before { - content: "F" -} -.icon-arrow-left:before { - content: "g" -} -.icon-arrow-up:before { - content: "j" -} -.icon-menu:before { - content: "k" -} -@-moz-keyframes nx-button-wiggle-for-scroll { - 0%, to { - -moz-transform: translate(0, 0); - transform: translate(0, 0) - } - 33% { - -moz-transform: translate(0, 2px); - transform: translate(0, 2px) - } - 66% { - -moz-transform: translate(0, -2px); - transform: translate(0, -2px) - } -} -@-webkit-keyframes nx-button-wiggle-for-scroll { - 0%, to { - -webkit-transform: translate(0, 0); - transform: translate(0, 0) - } - 33% { - -webkit-transform: translate(0, 2px); - transform: translate(0, 2px) - } - 66% { - -webkit-transform: translate(0, -2px); - transform: translate(0, -2px) - } -} -@keyframes nx-button-wiggle-for-scroll { - 0%, to { - -moz-transform: translate(0, 0); - -ms-transform: translate(0, 0); - -webkit-transform: translate(0, 0); - transform: translate(0, 0) - } - 33% { - -moz-transform: translate(0, 2px); - -ms-transform: translate(0, 2px); - -webkit-transform: translate(0, 2px); - transform: translate(0, 2px) - } - 66% { - -moz-transform: translate(0, -2px); - -ms-transform: translate(0, -2px); - -webkit-transform: translate(0, -2px); - transform: translate(0, -2px) - } -} -@-moz-keyframes nx-button-wiggle-for-hover { - 0%, to { - -moz-transform: translate(0, 0); - transform: translate(0, 0) - } - 33% { - -moz-transform: translate(0, 1px); - transform: translate(0, 1px) - } - 66% { - -moz-transform: translate(0, -1px); - transform: translate(0, -1px) - } -} -@-webkit-keyframes nx-button-wiggle-for-hover { - 0%, to { - -webkit-transform: translate(0, 0); - transform: translate(0, 0) - } - 33% { - -webkit-transform: translate(0, 1px); - transform: translate(0, 1px) - } - 66% { - -webkit-transform: translate(0, -1px); - transform: translate(0, -1px) - } -} -@keyframes nx-button-wiggle-for-hover { - 0%, to { - -moz-transform: translate(0, 0); - -ms-transform: translate(0, 0); - -webkit-transform: translate(0, 0); - transform: translate(0, 0) - } - 33% { - -moz-transform: translate(0, 1px); - -ms-transform: translate(0, 1px); - -webkit-transform: translate(0, 1px); - transform: translate(0, 1px) - } - 66% { - -moz-transform: translate(0, -1px); - -ms-transform: translate(0, -1px); - -webkit-transform: translate(0, -1px); - transform: translate(0, -1px) - } -} -@-moz-keyframes nx-fade-in { - 0% { - opacity: 0 - } - to { - opacity: 1 - } -} -@-webkit-keyframes nx-fade-in { - 0% { - opacity: 0 - } - to { - opacity: 1 - } -} -@keyframes nx-fade-in { - 0% { - opacity: 0 - } - to { - opacity: 1 - } -} -.screen-reader-text { - clip: rect(1px, 1px, 1px, 1px); - position: absolute!important; - height: 1px; - width: 1px; - overflow: hidden -} -.screen-reader-text:active, -.screen-reader-text:focus, -.screen-reader-text:hover { - background-color: #f1f1f1; - border-radius: 3px; - box-shadow: 0 0 2px 2px rgba(0, 0, 0, .6); - clip: auto!important; - color: #21759b; - display: block; - font-size: 14px; - font-size: 1.4rem; - font-weight: 700; - height: auto; - left: 5px; - line-height: normal; - padding: 15px 23px 14px; - text-decoration: none; - top: 5px; - width: auto; - z-index: 100000 -} -.alignleft { - display: inline; - float: left; - margin-right: 1.5em -} -.alignright { - display: inline; - float: right; - margin-left: 1.5em -} -.aligncenter { - display: block; - margin: 0 auto -} -.clear:after, -.clear:before, -.comment-content:after, -.comment-content:before, -.entry-content:after, -.entry-content:before, -.site-content:after, -.site-content:before, -.site-footer:after, -.site-footer:before, -.nx-site-header:after, -.nx-site-header:before { - content: ""; - display: table -} -.clear:after, -.comment-content:after, -.entry-content:after, -.site-content:after, -.site-footer:after, -.nx-site-header:after { - clear: both -} - - -a { - transition: all .1s ease -} -a:link, -a:visited { - color: #009639; - text-decoration: none -} -a:active, -a:focus, -a:hover { - color: #0bb35f; - text-decoration: none -} -.site-content-inner, -.site-footer-inner, -.nx-site-header-inner { - max-width: 1120px; - margin-left: auto; - margin-right: auto; - min-width: 280px; - position: relative -} -.site-content-inner:after, -.site-footer-inner:after, -.nx-site-header-inner:after { - content: " "; - display: block; - clear: both -} -.site-content, -.site-footer { - padding: 0 2.5788% -} -.site-content { - color: #000; - position: relative; - z-index: 95 -} - -#page{ - background-color: #fff -} - -/* end copy */ - -/* Styles for Sphinx generated elements */ - -table { - border-collapse: separate; - border-spacing: 0; - margin: 0 0 1.5em; - width: 100% -} - -table.docutils { - border: 1px solid #888; - -moz-box-shadow: 2px 2px 4px #eee; - -webkit-box-shadow: 2px 2px 4px #eee; - box-shadow: 2px 2px 4px #eee; -} - -table.docutils td, table.docutils th { - border: 1px solid #888; - padding: 0.25em 0.7em; -} - -table.field-list, table.footnote { - border: none; - -moz-box-shadow: none; - -webkit-box-shadow: none; - box-shadow: none; -} - -table.footnote { - margin: 15px 0; - width: 100%; - border: 1px solid #ccc; - background: #eee; - font-size: 0.9em; -} - -table.footnote + table.footnote { - margin-top: -15px; - border-top: none; -} - -table.field-list th { - padding: 0 0.8em 0 0; -} - -table.field-list td { - padding: 0; -} - -table.footnote td.label { - width: 0px; - padding: 0.3em 0 0.3em 0.5em; -} - -table.footnote td { - padding: 0.3em 0.5em; -} - -/* Hide fugly table cell borders in ..bibliography:: directive output */ -table.docutils.citation, table.docutils.citation td, table.docutils.citation th { - border: none; - /* Below needed in some edge cases; if not applied, bottom shadows appear */ - -moz-box-shadow: none; - -webkit-box-shadow: none; - box-shadow: none; -} - -dl dd { - margin-left: 30px; -} - -blockquote { - - display:block; - padding: 10px 5px 3px 19px; - margin: 5px 0 20px; - position: relative; - border-left: 1px solid #00953a; - float: left; - width: 100%; -} - -blockquote strong{ - font-weight: 400; - color: #000; -} - -ul, ol { - margin: 10px 0 10px 30px; - padding: 0; -} - -pre { - max-width: 100%; - background: #f7f7f7; - /*font-family: "Courier 10 Pitch", Courier, monospace;*/ - font-size: 1.5rem; - line-height: 1.4; - margin: 0; - overflow: auto; - padding: 1em; - tab-size: 4; - -moz-tab-size: 4; - -o-tab-size: 4; - /* white-space: pre-wrap */ - /* - tab-size: 0; - -moz-tab-size: 0; - -o-tab-size: 0; - */ - color: #202221; - font-size: 15px; - font-family: monospace; -} - - - -dt:target{ - background: #f7f7f7; -} - -/* end Styles for Sphinx generated elements */ - -/*================================================ - Header Style -================================================*/ -label, .label, .group-label, input, textarea, select { - color: #989898; -} - -.label, .group-label{ - font-weight: normal; -} - -.label span, .group-label span{ - font-weight: lighter; -} - -.label { - clear: both; - padding-top: 5px; -} - -.input-wrapper .label { - padding-top: 5px; -} - -div.label { - padding-top: 10px; -} - -.input-wrapper select.pull-right { - margin-top: 15px; -} - -.input-wrapper .select-wrapper::after { - top: 17px; -} - -.pull-left{ - float: left; -} - -.pull-right{ - float: right; -} - -.label.pull-left { - width: 70%; -} - -select.pull-right { - margin-top: 15px; - max-width: 65px; - width: 28%; -} - -input.pull-left { - margin: 0 !important; - width: 20px !important; -} - -.regpg-main-section-inner .regpg-main-description { - margin-right: 4%; - width: 56%; -} - -.regpg-main-section-inner .regpg-main-form { - width: 40%; -} - -.column.pull-left { - margin-right: 1%; - margin-left: 0; - width: 49%; -} - -.column.pull-right { - margin-left: 1%; - margin-right: 0; - width: 49%; -} - - - -.select-wrapper{ - position: relative; -} - -.select-wrapper::after { - color: #989898; - content: "3"; - font-family: "nginx-font" !important; - font-size: 20px; - position: absolute; - right: 10px; - top: 9px; - pointer-events:none; /*To make this arrow clickable, othewise this is not expanded when click on this arrow*/ -} -.input-wrapper { - clear: both; -} - -/*print css */ -#site_logo_print_only{ - text-align: center; - display: none; - } -#site_logo_print_only img{ - width: 200px; - margin: 0px; - padding: 0px; -} -@media only print { - #nx_masthead, #secondary, #nx_breadcrumb_wrap, .social_share_buttons_wrap{ - display: none; - } - #site_logo_print_only{ - display: block; - } - /*hide chat box*/ - #olark-wrapper{ - display: none!important; - } - #habla_window_div *{ display: none !important; } - /*For Product page */ - .prodspage-lead-section .prodspage-lead-solutions-list-wrapper { - float: none; - width: 100%; - } - .prodspage-lead-solutions-list-wrapper li.prodspage-lead-solution-item{ - position: static; - display: block; - width: 100%; - clear: left; - } - .prodspage-lead-solutions-list-wrapper li.prodspage-lead-solution-item .prodspage-lead-solution-image{ - display: none; - } - .prodspage-third-section .prodspage-third-section-inner{padding-bottom:0} - .prodspage-third-section .prodspage-third-section-content{height:auto} - .prodspage-third-section .prodspage-third-expand-button-wrapper{display:none} - .prodspage-third-section .prodspage-third-collapse-button-wrapper{ display: none;} - .nxmtrx-section .nxmtrx-cell{ - float: none!important; - display: inline-block!important; - } - .nxmtrx-section .nxmtrx-cell.nxmtrx-col-1, .nxmtrx-section .nxmtrx-cell.nxmtrx-col-2 , .nxmtrx-section .nxmtrx-cell.nxmtrx-col-3,.nxmtrx-section .nxmtrx-cell.nxmtrx-col-4{ - float: none!important; - display: inline-block!important; - border-right:0px solid #ff0000; - } - .nxmtrx-section .nxmtrx-cell.nxmtrx-col-1{width: 4%!important;} - .nxmtrx-section .nxmtrx-cell.nxmtrx-col-2{ width: 48%!important;} - .nxmtrx-section .nxmtrx-cell.nxmtrx-col-3{ width: 23%!important;} - .nxmtrx-section .nxmtrx-cell.nxmtrx-col-4{ width: 23%!important;} - .nxmtrx .nxmtrx-yes{ display: none; } - .nxmtrx img.nxmtrx-yes-img{ display: block!important; } -}/*---end @media only print ---*/ - - -@media only screen and (max-width: 480px) { - .footer-menu .sub-menu { - margin-bottom:20px; - } -} - -@media only screen and (max-width: 767px) { - - .regpg-main-section-inner .regpg-main-description { - display: block; - float: none; - margin: 0 auto; - width: 100%; - } - - .regpg-main-section-inner .regpg-main-form { - display: block; - float: none; - margin: 0 auto; - width: 100%; - } - - .pull-left, .pull-right, .column.pull-left, .column.pull-right { - float: none; - margin: 5px 0 !important; - width: 100% !important; - } - - .input-wrapper .select-wrapper { - max-width: 70px; - } - - - .select-wrapper::after, .input-wrapper .select-wrapper::after { - top: 8px; - } - -} - -.nginx-doc-container{ - width: 100%; - overflow: hidden; - background: #f7f7f7; -} -.nginx-doc-sidebar{ - width: 25%; - float: left; - padding: 35px 0; -} -.nginx-doc-sidebar-inner{ - padding: 0 50px 0 25px; -} -.nginx-doc-sidebar h3{ - font-size: 20.7px; - margin-bottom: 20px; - color: #00953a; - font-weight: 500; - padding-left: 20px; -} -.search-doc-wrapper{ - width: calc(100% - 20px); - border: 1px solid #77777a; - border-radius: 4px; - background: #fff; - position: relative; - margin-bottom: 30px; - margin-left: 20px; -} -.search-doc-wrapper .search-button{ - background: none; - font-size: 20px; - border: 0; - color: #898787; - box-shadow: none; - padding: 4px; - position: absolute; - left: 5px; - top: 3px; -} -.search-doc-wrapper .search-field{ - background: none; - border: 0; - width: 100%; - padding-left: 39px; - line-height: 26px; - font-size: 15px; - padding-right: 10px; -} -.search-doc-wrapper input::-webkit-input-placeholder, -.search-doc-wrapper input::-moz-placeholder, -.search-doc-wrapper input:-ms-input-placeholder, -.search-doc-wrapper input:-moz-placeholder , -.search-doc-wrapper input:placeholder { - color: #898787; - font-weight: 500; -} -.search-doc-wrapper .search-field:focus, -.search-doc-wrapper .search-button:focus{ - outline: none; -} - - -/*----------- Update code -----------*/ -.nginx-doc-sidenav{ - padding-left: 20px; -} -.nginx-doc-sidenav ul{ - list-style-type: none; - padding-left: 0; - margin-left: 3px; -} -.nginx-doc-sidenav ul li{ - margin-bottom: 13px; - font-size: 21px; - font-weight: 500; -} -.nginx-doc-sidenav ul li.has-sub-nav{ - position: relative; -} -.nginx-doc-sidenav ul li span.open-sidenav:before { - content: "3"; - font-family: "nginx-font" !important; - font-style: normal !important; - font-weight: normal !important; - font-variant: normal !important; - text-transform: none !important; - speak: none; - line-height: 1; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; - position: absolute; - left: -31px; - top: 0px; - -webkit-font-smoothing: antialiased; - -webkit-text-stroke: 1px #f7f7f7; - font-size: 30px; - cursor: pointer; - color: #898787 -} -.nginx-doc-sidenav ul li.active>span.open-sidenav:before, -.nginx-doc-sidenav ul li.current>span.open-sidenav:before{ - /*content: "j"; - font-size: 38px; - top: -3px;*/ - transform: rotate(180deg); -} -.nginx-doc-sidenav li ul{ - margin-left: 21px; - margin-top: 7px; - display: none; -} -.nginx-doc-sidenav ul li.has-sub-nav .sub-side-nav li.has-sub-nav>a:before{ - top: 0; -} -.nginx-doc-sidenav .sub-side-nav li{ - font-size: 19px; - line-height: 24px; - margin-bottom: 6px; -} -.nginx-doc-sidenav ul li ul li ul li{ - font-weight: 300; -} -.nginx-doc-sidenav .sub-side-nav li .sub-side-nav li { - font-size: 18px; -} -.nginx-doc-sidenav .sub-side-nav li .sub-side-nav li .sub-side-nav li { - font-size: 16px; - line-height: 21px; - margin-bottom: 12px; -} -.nginx-doc-sidenav .current a{ - background: none; -} -.nginx-doc-sidenav a, -.nginx-doc-sidenav .current a{ - color: #898787 !important; -} -.nginx-doc-sidenav a:hover, -.nginx-doc-sidenav .current>a, -.nginx-doc-sidenav .active>a, -.nginx-doc-sidenav .current>span.open-sidenav:before, -.nginx-doc-sidenav .active>span.open-sidenav:before, -.nginx-doc-sidenav .current{ - color: #00953a !important; -} -/*---------------- End ------------------*/ - -.doc-search-result{ - border-bottom: 1px solid #979799; - padding-bottom: 16px; - margin-bottom: 16px; - width: 100%; - color: #898787; - line-height: 24px; -} -.doc-search-result a{ - color: #898787; - font-size: 18px; - -} -.doc-search-result a strong{ - font-weight: 400; -} -.doc-search-result a span{ - color: #00953a; - font-weight: 400; -} - - -.nginx-doc-content{ - width: 75%; - float: right; - background: #fff; -} -.nginx-doc-banner{ - width: 100%; - padding: 90px 67px; - background-image: url(../images/green-background.png); - background-position: top left; - background-repeat: no-repeat; - background-size: cover; -} -.nginx-doc-banner h1, -.nginx-doc-banner p{ - color: #fff; -} -.nginx-doc-banner h1{ - font-size: 38.7px; - font-weight: 600; - margin-bottom: 20px; -} -.nginx-doc-banner p{ - padding-right: 23%; - font-size: 18px; - line-height: 23px; - margin-bottom: 0; -} -.nginx-doc-content-inner{ - padding: 20px 62px 60px; - overflow: hidden; -} -.nginx-doc-content-menu{ - max-width: 585px; - overflow: hidden; -} -.nginx-doc-menu-block1{ - padding-top: 20px; -} -.nginx-doc-menu-block2{ - padding-top: 20px; -} -.nginx-doc-content-menu h3{ - color: #363534; - font-weight: 700; - margin-bottom:15px; - text-align: center; - font-size: 26px; -} -.nginx-doc-content-menu .nginx-doc-menu-block2 a{ - float: left; - display: inline-block; - width: 166px; - height: 199px; - margin: 0 40px 40px 0; - background-color: #f7f7f7; - border:1px solid #00953a; - padding: 110px 9px 10px; - color: #363534; - font-size: 23px; - line-height: 26px; - text-align: center; - font-weight: 300; - background-repeat: no-repeat; - background-position: center 20px; - background-size: 70px 81px; -} -.nginx-doc-content-menu a.nginx-oss { - float: inherit; - width: inherit; - background-color: white; - border: none; - height: inherit; - padding: inherit; - font-size: inherit; - color: #0bb35f; - margin: inherit; -} - -.nginx-doc-menu-block1 a:nth-child(3n), -.nginx-doc-menu-block2 a:nth-child(3n){ - margin-right: 0; -} -.nginx-doc-content-menu a:hover{ - color:#3f3e3e; -} - -.product-list-17-col { - width: 95%; - padding-right: 40px; - margin-bottom: 30px; - overflow:hidden; -} -.product-17-icon { - width: 54px; - float: left; - margin-right: 26px; -} -.product-17-icon img { - width: 100%; - margin: 0 !important; -} -.product-17-details { - width: calc(100% - 80px); - float: left; - text-align: left; -} -.nginx-doc-content-inner .product-17-details h3 { - font-weight: 400; - font-size: 20px; - margin-bottom: 13px; - text-align: left; - margin-top: 10px; -} -.nginx-doc-content-inner .product-17-details p { - margin-bottom: 12px; -} -.learn-more-link { - font-size: 13.7px; - display: block; - font-weight: 500; - color: #009639; - text-decoration: none; -} - -/*-------------- Updated CSS ----------------*/ -.nginx-doc-menu-block1 a.link-box-nx-oss{background-image:url(../images/icons/icon-NGINX-Plus-70x81.png);} -.nginx-doc-menu-block1 a.link-box-nx-oss:hover{background-image:url(../images/icons/icon-NGINX-Plus-hover-70x81.png);} -.nginx-doc-menu-block1 a.link-box-nx-plus{background-image:url(../images/icons/icon-NGINX-Plus-70x81.svg);} -.nginx-doc-menu-block1 a.link-box-nx-plus:hover{background-image:url(../images/icons/icon-NGINX-Plus-hover-70x81.svg);} -.nginx-doc-menu-block1 a.link-box-nx-amplify{background-image:url(../images/icons/icon-NGINX-Amplify-70x81.svg);} -.nginx-doc-menu-block1 a.link-box-nx-amplify:hover{background-image:url(../images/icons/icon-NGINX-Amplify-hover-70x81.svg);} -.nginx-doc-menu-block1 a.link-box-nx-controller{background-image:url(../images/icons/icon-NGINX-Controller-70x81.svg);} -.nginx-doc-menu-block1 a.link-box-nx-controller:hover{background-image:url(../images/icons/icon-NGINX-Controller-hover-70x81.svg);} -.nginx-doc-menu-block1 a.link-box-nx-unit{background-image:url(../images/icons/icon-NGINX-Unit-70x81.svg);} -.nginx-doc-menu-block1 a.link-box-nx-unit:hover{background-image:url(../images/icons/icon-NGINX-Unit-hover-70x81.svg);} -.nginx-doc-menu-block1 a.link-box-nx-waf{background-image:url(../images/icons/icon-NGINX-WAF-70x81.svg);} -.nginx-doc-menu-block1 a.link-box-nx-waf:hover{background-image:url(../images/icons/icon-NGINX-WAF-hover-70x81.svg);} -.nginx-doc-menu-block2 a.link-box-nx-support{background-image:url(../images/icons/icon-Support-70x81.svg);} -.nginx-doc-menu-block2 a.link-box-nx-support:hover{background-image:url(../images/icons/icon-Support-hover-70x81.svg);} -.nginx-doc-menu-block2 a.link-box-nx-pro-services{background-image:url(../images/icons/icon-ProServ-70x81.svg);} -.nginx-doc-menu-block2 a.link-box-nx-pro-services:hover{background-image:url(../images/icons/icon-ProServ-hover-70x81.svg);} -.nginx-doc-menu-block2 a.link-box-nx-training{background-image:url(../images/icons/icon-Training-70x81.svg);} -.nginx-doc-menu-block2 a.link-box-nx-training:hover{background-image:url(../images/icons/icon-Training-hover-70x81.svg);} -/*-------------- End ----------------*/ - - -.nginx-doc-content-inner p{ - font-size: 15.5px; - line-height: 21px; - margin-bottom: 24px; -} -.nginx-doc-content-links{ - padding-top: 25px; -} - -.nginx-doc-content-inner ul{ - padding-left: 4px; - margin-bottom: 20px; -} -.nginx-doc-content-inner ul li{ - line-height: 20px; - margin-bottom: 5px; - /*font-size: 16px;*/ - font-size: 15.5px; -} -.nginx-doc-content-inner li { - font-size: 15.5px; -} -.nginx-doc-content-links ul { - list-style-type: none; - margin-left: 20px; - margin-bottom: 55px; - padding-left: 0; -} -.nginx-doc-content-links ul li { - font-size: 23px; - line-height: 28px; - margin-bottom: 15px; -} -.nginx-doc-search-results .doc-search-result:last-child{ - border-bottom:none; -} -.nginx-doc-meta-block{ - margin-bottom: 15px; -} -.nginx-doc-meta-block a{ - color: #00953a; - background-image: url(../images/github-logo.png); - background-repeat: no-repeat; - background-size: auto 100%; - display: inline-block; - padding-left: 23px; - font-weight: 600; - font-size: 14px; -} - - -.nginx-doc-content-inner p a{ - color: #00a23f; -} - -.nginx-doc-content-inner code{ - background: #f7f7f7; - padding-left: 3px; - padding-right: 3px; - color: #363534; -} - -.nginx-doc-content-inner a code{ - color: #0bb35f; -} - -.nginx-doc-content-inner code.error-code{ - color: #00953a; -} - -.nginx-doc-content-inner img{ - display: block; - margin-left: auto; - margin-right: auto; - margin-top: 30px; - margin-bottom: 15px; -} -.nginx-doc-content-inner ol li ul{ - margin-top: 14px; - margin-bottom: 30px; -} - -.nginx-doc-content-inner h1{ - font-size: 38.7px; - margin-bottom: 18px; - font-weight: 500; - padding-top: 30px; -} -.nginx-doc-content-inner h2{ - font-size: 31.5px; - font-weight: 300; - margin-bottom: 17px; - /*margin-top: 25px;*/ - margin-top:45px; -} -.nginx-doc-content-inner h3{ - /*font-size: 23px; */ - font-size: 25px; - font-weight: 300; - margin-bottom: 12px; - margin-top: 15px; - color: #363534; -} -.nginx-doc-content-inner h4{ - font-size: 21px; -} -.nginx-doc-content-inner h5{ - font-size: 18px; -} -.nginx-doc-content-inner h6{ - font-size: 17px; -} - -.nginx-doc-content-inner h1, -.nginx-doc-content-inner h2, -.nginx-doc-content-inner h3, -.nginx-doc-content-inner h4, -.nginx-doc-content-inner h5, -.nginx-doc-content-inner h6{ - position: relative; -} - -.nginx-doc-footer{ - border-top: 1px solid #e1e1e1; - padding-top: 45px; - margin-top: 44px; - width: 100%; - clear: both; -} -.prev-block, -.next-block{ - width:200px; - float: left; - position: relative; - font-size: 14px; -} -.next-block{ - float: right; - text-align: right; -} -.nginx-doc-footer a{ - display: block; -} -.nginx-doc-footer a.next-prev-btn{ - text-transform: uppercase; - color: #898787; - font-weight: 500; -} -.prev-icon, -.next-icon{ - position: relative; - height: 7px; - top: -11px; - font-size: 20px; -} -.prev-icon{ - left: -30px; -} -.next-icon{ - right: -30px; -} - - -.footer-help-block{ - width: 100%; - padding: 80px 25px; - background-image: url(../images/r12-background.png); - background-position: bottom right; - background-repeat: no-repeat; - background-size: cover; - text-align: center; -} -.footer-help-block h2{ - color: #fff; - font-size: 36px; - font-weight: 600; - margin-bottom: 50px; -} -.help-menu-wrap{ - width: 100%; - overflow: hidden; -} -.help-menu-wrap a{ - margin: 0 26px 10px; -} -.help-menu-wrap .button-secondary{ - color: #d9d8d5; -} -.help-menu-wrap .button-primary{ - background: #f7a800; - color: #000; -} - -.site-footer .site-footer-inner .footer-connect-nav .footer-email-signup .nginx-button { - padding: 9px; - color: #343434!important; - background: #666; - border-color: #666; -} - - -@media screen and (min-width: 801px) { - .nginx-doc-sidebar{ - position: fixed; - top: 63px; - left: 0; - overflow: hidden; - } - .remove-sticky-sidebar{ - position: relative; - top: 0; - } - .nginx-doc-sidebar-inner{ - overflow-y: auto; - width: calc(100% + 20px); - } -} - -/*===================================== - Footer Style -=====================================*/ - -.site-footer{ - border-top: 1px solid #545353; -} - -/*=================================== - Button Styles -====================================*/ - -.nginx-button{ - display: inline-block; - border:2px solid #F0A828; - font-size: 17px; - text-transform: uppercase; - border-radius: 4px; - padding: 11px 35px; - font-weight: 500; - position: relative; -} -a.button-primary, -button.button-primary, -.button-primary{ - background: #F0A828; - color: #200000; -} -a.button-primary:hover, -button.button-primary:hover, -.button-primary:hover{ - background: #eea119; -} -a.button-secondary, -button.button-secondary, -.button-secondary{ - background: none; - color: #000; -} -a.button-secondary:hover, -button.button-secondary:hover, -.button-secondary:hover{ - /*background: #fbfbfb;*/ -} - -a.btn-have-icon, -button.btn-have-icon, -.btn-have-icon{ - padding: 11px 54px 11px 22px; -} -.btn-have-icon .icon { - bottom: 0; - font-size: 24px; - height: 24px; - line-height: 1.1; - margin: auto; - padding-top: 1px; - position: absolute; - right: 18px; - top: 0; -} - -.pre-footer-cta-button .nginx-button{ - font-size: 18px; - font-weight: 700; -} -.share-report .nginx-button{ - font-size: 15px; -} -form .nginx-button{ - width: 100%; -} -button.nginx-button.btn-have-icon span.icon{ - margin: auto !important; -} - -@media screen and (max-width: 767px){ - .pre-footer-cta-button .nginx-button{ - width: 100%; - } -} - -/*=====Button Instructions ==== -Button 1 -Button 2 -Button 3 -=====================*/ - -/*================Additional styles added by oneTarek =========*/ - -/*hide the page title h1 from begening of content*/ -.nginx-doc-content-inner #page_content h1:first-of-type{display: none!important;} - -/*hide the navigation /relbar */ -div.related { - display: none; -} - -/* Hide nginx.com search box */ -#nx_masthead .search-form-wrapper, -#mobile-search-button-wrapper{ - display: none!important; -} - -/*=================== Gabrial Responsive code ================*/ -.doc-mob-sidenav { - display: none; -} -@media screen and (min-width: 801px){ - .doc-mob-sidenav h3,.mob-search-doc{display: none;} - .nginx-doc-sidebar-inner{display: block !important;} - .nginx-doc-content-inner{ - min-height: 2500px; - } -} -@media screen and (max-width: 1024px){ - .nginx-doc-content-menu a { - margin: 0 25px 25px 0; - } -} -@media screen and (max-width: 992px){ - .nginx-doc-sidebar-inner { - padding: 0 45px 0 45px; - } - .nginx-doc-banner { - padding: 60px 50px; - } - .nginx-doc-content-inner { - padding: 70px 45px 60px; - } - .nginx-doc-menu-block1 a:nth-child(3n), .nginx-doc-menu-block2 a:nth-child(3n) { - margin-right: 25px; - } - -} -@media screen and (max-width: 800px){ - .nginx-doc-content{ - width: 100%; - } - .nginx-doc-banner { - padding: 45px 50px; - } - .nginx-doc-sidebar{ - background: #fff; - width: 100%; - padding: 15px 25px; - border-bottom: 1px solid rgba(0,0,0,0.14); - } - .doc-mob-sidenav{ - width: 100%; - overflow: hidden; - cursor: pointer; - display: block; - } - .doc-mob-sidenav h3{ - font-size: 22px; - font-weight: 300; - margin-bottom: 0; - display: block; - padding-left: 0; - } - .doc-mob-sidenav span.icon{ - float: right; - position: relative; - top: 4px; - } - .search-doc-wrapper{ - margin-bottom: 30px; - width: 100%; - margin-left: 0; - } - .nginx-doc-sidebar-inner h3{ - display: none; - } - .nginx-doc-sidebar-inner { - padding: 15px 0; - display: none; - } - .nginx-doc-sidenav{ - padding-left: 20px; - height: auto; - } - .nginx-doc-banner p{ - padding-right: 0; - } - .nginx-doc-content-inner { - padding: 50px 35px 60px; - } - .nginx-doc-sidebar-inner .search-doc-wrapper{ - display: none; - } -} - -@media screen and (max-width: 480px){ - .nginx-doc-menu-block1 a:nth-child(3n), - .nginx-doc-menu-block2 a:nth-child(3n), - .nginx-doc-content-menu a{ - margin: 0 15px 15px 0; - } - .nginx-doc-menu-block1 a:nth-child(2n), - .nginx-doc-menu-block2 a:nth-child(2n){ - margin-right: 0; - } - .help-menu-wrap a{ - width: 245px; - padding-left:10px; - padding-right: 10px; - } - .prev-block, .next-block{ - width: 100%; - } - .prev-block{ - margin-bottom: 25px; - } - .nginx-doc-content-inner h1{ - font-size: 35px; - } - .product-list-17-col { - width: 100%; - padding-right: 10px; - } -} -@media screen and (max-width: 440px){ - .nginx-doc-content-menu{ - text-align: center; - } - .nginx-doc-content-menu a{ - margin:0 auto 15px !important; - float:none; - display: block; - } - .nginx-doc-content-inner h2 { - font-size: 30px; - } -} - - - -/* ============SYNTAX HIGHLIGHTER =======EXTEND OR OVERRIDE pygments.css==============*/ -.highlight {margin:28px 0px;} - -/* Remove lines below after updating old technique of adding terminal code */ -div.highlight-default{ - background: #f7f7f7; - margin-top: 30px; - margin-bottom: 25px; -} - -div.highlight-default.terminal{ - background: #232222; - margin: 28px 0; -} -div.highlight-default.terminal pre{ - background: #232222; - color: #76c973; -} -/* end remove */ -/* =====END====SYNTAX HIGHLIGHTER =========pygments.css==============*/ - -/* =====SEARCH RESULT=================*/ -.page-search #page_title{ - display: none; -} -.page-search #page_content form{ - display: none; -} -.page-search #page_content #fallback+p{ - display: none; -} -#search-results{} -#search-results ul.search{ - padding-left: 0px; - margin-bottom: 20px; - margin-left: 0; - list-style-type: none; -} -#search-results ul.search li{ - padding-bottom: 15px; - margin-bottom: 15px; - border-bottom: 1px solid #eeeeee; - list-style-type: none; -} - -#search-results ul.search li:last-child{ - border-bottom: none; - margin-bottom:0; -} -#search-results ul.search li a{ - font-size: 18px; -} -#search-results ul.search li .context{ - margin-top: 5px; - font-size: 14px; - color: #888; -} -#search-results .highlighted{ - font-weight: bold; -} - -#search-results .search-item-info span{ - border-radius: 3px; - -moz-border-radius: 3px; - -webkit-border-radius: 3px; - font-weight: 100; - color: #dfdfdf; - font-size: 11px; - padding: 0px 5px; - display: inline-block; - vertical-align: bottom; - margin-left: 10px; -} -#search-results .search-item-info .product-name{ - background: #039639; -} -#search-results .search-item-info .directory-name{ - background: #7b7b7b; -} -/* =====END SEARCH RESULT=================*/ - -/*======GDPR2==================*/ - .hidden{ - display: none; - } - #nx_gdpr_modal2 { - - position: fixed; - left: 0; - bottom: 25%; - width: 420px; - background: #fff; - border: 1px solid #ccc; - border-radius: 5px; - -webkit-box-shadow: 1px 0 1px #ccc; - box-shadow: 1px 0 1px #ccc; - z-index: 999; - padding: 2rem 3rem 1rem; - color: #404040; - font-style: normal; - font-weight: 300; - -} -#nx_gdpr_modal2 #nx_gdpr_modal_more_info2 { - padding-bottom: 2rem; -} -#nx_gdpr_modal2 h2 { - margin: 1rem 0 0; - font-size: 1.5rem; - color: #009639; - font-weight: 500; - text-align: inherit; - clear: both; - font-family: Roboto-Regular,"Helvetica Neue",Arial,sans-serif; - line-height: 1.25; -} -#nx_gdpr_modal2 #nx_gdpr_modal_main2 p { - margin-bottom: 0; - color: #404040; - font-style: normal; - font-weight: 300; - -} -#nx_gdpr_modal2 p { - font-size: 1.6rem; -} -#nx_gdpr_modal2 .submit-button-wrap { - text-align: center; - padding: 0 2rem 1rem; - overflow: hidden; - float: right; -} -#nx_gdpr_modal2 #nx_gdpr_accept_btn { - float: right; - font-size: 1.5rem; - padding: .7rem 1.8rem; - background-color: #fff; -} - -#nx_gdpr_modal2 #nx_gdpr_modal_more_info2 { - padding-bottom: 2rem; -} -#nx_gdpr_modal2 #nx_gdpr_modal_more_info2 .less-info-wrap { - float: right; -} -#nx_gdpr_modal2 #nx_gdpr_modal_more_info2 .less-info-wrap a { - font-size: 3rem; - outline: 0; - color: #009639; -text-decoration: none; -} - -#nx_gdpr_modal2 #nx_gdpr_modal_more_info2 .gdpr_info_section .gdpr_modal_chkbox2 { - text-align: left; - float: left; - margin-right: 20px; - display: inline-block; -} -#nx_gdpr_modal2 #nx_gdpr_modal_more_info2 .gdpr_info_section p { - margin-bottom: .2rem; - line-height: 2.3rem; - width: 87%; - display: inline-block; - -} - - -@media screen and (max-width: 575.98px) { - - #nx_masthead { - height: auto!important; - padding-bottom: 14px; - } - #nx_masthead .nx-header-menus{ - top:64px!important; - } - - #nx_gdpr_modal2 { - - width: auto; - position: relative; - border-radius: 0; - background-color: #f0eeed; - - } - - #nx_gdpr_modal2 #nx_gdpr_modal_main2 { - text-align: center; - } - #nx_gdpr_modal2 .submit-button-wrap { - padding-top: 2rem; - float: none; - } - #nx_gdpr_modal2 #nx_gdpr_accept_btn { - float: none; - margin: 0 auto; - } - - - -} - -/*======END GDPR2==================*/ - diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/assets/css/f5-hugo.css b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/assets/css/f5-hugo.css deleted file mode 100644 index 65942df426..0000000000 --- a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/assets/css/f5-hugo.css +++ /dev/null @@ -1,1155 +0,0 @@ -:root { - --nginx-green: #009639; -} - -.col, .col-1, .col-10, .col-11, .col-12, .col-2, .col-3, .col-4, .col-5, .col-6, .col-7, .col-8, .col-9, .col-auto, .col-lg, .col-lg-1, .col-lg-10, .col-lg-11, .col-lg-12, .col-lg-2, .col-lg-3, .col-lg-4, .col-lg-5, .col-lg-6, .col-lg-7, .col-lg-8, .col-lg-9, .col-lg-auto, .col-md, .col-md-1, .col-md-10, .col-md-11, .col-md-12, .col-md-2, .col-md-3, .col-md-4, .col-md-5, .col-md-6, .col-md-7, .col-md-8, .col-md-9, .col-md-auto, .col-sm, .col-sm-1, .col-sm-10, .col-sm-11, .col-sm-12, .col-sm-2, .col-sm-3, .col-sm-4, .col-sm-5, .col-sm-6, .col-sm-7, .col-sm-8, .col-sm-9, .col-sm-auto, .col-xl, .col-xl-1, .col-xl-10, .col-xl-11, .col-xl-12, .col-xl-2, .col-xl-3, .col-xl-4, .col-xl-5, .col-xl-6, .col-xl-7, .col-xl-8, .col-xl-9, .col-xl-auto { - padding-right: 8px; - padding-left: 8px; -} - -.button { - text-transform: uppercase; - font-size: 14px; - font-weight: 600; -} - -a, abbr, acronym, applet, del, div, dl, fieldset, font, form, iframe, ins, label, legend, li, object, q, s, samp, span, strike, tbody, tfoot, thead, tr { - border: 0; - font-family: inherit; - font-size: 100%; - font-style: inherit; - font-weight: inherit; - margin: 0; - outline: 0; - padding: 0; - vertical-align: baseline; -} - -a { - transition: all .1s ease; -} - -ul, ol { - margin: 0px 0 10px 30px; - padding: 0; -} - -li { - padding-bottom: 8px!important; -} - -ol > li { - list-style: decimal; - font-family: roboto-regular, Arial, Helvetica, sans-serif; - font-size: inherit; -} - -ol > li > ol { - list-style: lower-alpha; - font-family: roboto-regular, Arial, Helvetica, sans-serif; - font-size: inherit; -} - -ul > li > ul, -ul > li > ol, -ol > li > ul, -ol > li > ol { - padding-bottom: 8px; -} - -.card-deck { - padding-bottom: 2em; - width: -webkit-fill-available; - width: -moz-available -} - -.card-holder { - padding: 12px; -} - -.card { - padding: 1.5em; - margin-top: 2em; - /*min-height: 330px;*/ - min-width: 40%; - max-width: 100%; - background: #FFFFFF; - border: 1px solid #EEEEEE; - box-sizing: border-box; - box-shadow: 0px 1px 2px rgba(0, 0, 0, 0.15); - border-radius: 2px; -} - -.products-card { - color: #33444d; - background-color: #fff; - box-shadow: 0px 1px 4px 0px #4856653D; - box-shadow: 0px 4px 8px 0px #48566529; - padding: 24px 36px; - min-height: 180px; - border: 1px solid #f8f9f9; - box-sizing: border-box; - border-radius: 4px; - height: 100%; - } - - .saas-card { - display: flex; -} - -.saas-icon { - width: 46px; - margin-right: 5px; -} - -.saas-description { - flex: 75%; - -} - -.saas-title { - overflow-wrap: normal; - padding-top: 4px; - padding-bottom: 4px; -} - -h3.saas-title a { - color: #000; -} - -h3.saas-title a:hover { - color: var(--nginx-green); -} - -h3.saas-title { - font-style: normal; - font-weight: bold; - font-size: 24px; - line-height: 28px; -} - - - a.products-card { - display: block; - transition: transform 150ms ease-in-out; - color: #000; - } - - a.products-card:hover { - opacity: 1; - /* transform: scale(1.03); */ - box-shadow: 0px 5px 10px 0px #4856650F; - box-shadow: 0px 12px 24px 0px #48566533; - } - - .card-img { - background-repeat: no-repeat; - } - -.card-text-placeholder { - min-height: 35px; -} - -.list-card { - min-height: 40px; - padding: 1em; - border: 1px solid #eee; - margin: .5em; - box-shadow: 0px 1px 2px rgba(0, 0, 0, 0.15); -} - -.list-page { - margin: 0 auto; - max-width: 1200px; -} - -.list-page .page-header { - margin-top: 20px; -} - -.list-page .page-header.center { - text-align: center; -} - -.section-index .entry { - padding: .75rem; -} - -.section-index h5 { - margin-bottom: 0; - font-size: 1.5rem; -} - -.card-small { - padding: 2em; - min-height: fit-content; -} - -.card-empty { - box-shadow: none !important; - border: none !important; -} - -.card-img, .card-img-top { - width: auto; - height: 40px; - color: var(--nginx-green); - margin-right: 5px; -} - -.card-img.list { - width: auto; - height: 40px; -} - -.img-large { - width: 100px !important; -} - -.card-title { - overflow-wrap: normal; - -} -.products-card > .card-title { - padding-left: 52px; - text-indent: -52px; -} - - - -h3.card-title a { - color: #000; -} - -h3.card-title a:hover { - color: var(--nginx-green); -} - -h3.card-title { - font-style: normal; - font-weight: bold; - font-size: 24px; - line-height: 28px; -} - -.card-text { - font-style: normal; - font-weight: normal; - font-size: 14px; - line-height: 18px; - /* or 129% */ - font-feature-settings: 'case' on; -} - -.card-description { - padding-left: 52px; -} - -.card-list { - list-style-type: square; - margin-left: 15px; -} - -.card-list > li { - padding-bottom: 6px; - font-weight: 400; -} - -#f5-related, #nginx-products { - width: 100%; - padding: 50px; - position: relative; -} - -#f5-related { - color: #f1f1f1; - background: #F7F8FA; -} - -#search-bar { - width: 100%; - background-color: #222222!important; -} - -.navbar-dark, .bg-dark { - background-color: #222222!important; -} - -.navbar-img { - height: 56px; - padding: 4px 48px; -} - -ul.navbar-nav>li { - list-style: none; -} - -.nav-item { - color: #f1f1f1; - text-transform: uppercase; - font-size: 16px; - padding-right: 1.4rem; - font-weight: 400; -} - -a.dropdown-item { - color: #FFFFFF; -} - -a.dropdown-item:hover { - color: #000; -} - -.dropdown-item { - font-size: 1.4rem; - margin-bottom: 0; - padding: 1rem; - padding-right: 1.2rem; -} - -#nx_masthead.nx-site-header { - top: 0; - z-index: 150; - position: fixed; - width: 100%; - -webkit-transition: all .2s ease-in-out; - transition: all .2s ease-in-out; -} - -main { - padding-top: 24px!important; - padding-left: 24px; -} -.main { - background: #FFFFFF; -} - -address, blockquote, dd, ol, p, table, ul { - border: 0; - font-family: inherit; - font-style: normal; - font-weight: 300; - font-size: 16px; - line-height: 20px; - outline: 0; - padding: 0; -} - -.section-heading { - padding-top: 20px; -} - -.form-inline .form-control { - width: 580px; -} - -#footer { - width: 100%; - position: relative; - background: #32302F; - padding: 8px 48px; -} - -#footer .row { - padding-top: 24px; - padding-bottom: 32px; - border-bottom: 1px solid #595959; -} - -footer ul { - list-style-type: none; - margin: 0 0 10px 0px; - align-items: center; -} - -footer a:link, -footer a:active, -footer a:hover, -#footer ul li a, -#footer .site-info.footer-text a, -#footer .site-info.footer-text a:active, -#footer .site-info.footer-text a:hover { - color: #CCCCCC; -} - -.footer-text, -.footer-text li, -.footer-social li { - font-style: normal; - font-size: 14px; - line-height: 20px; - font-weight: 300; - /* identical to box height */ - color: #CCCCCC; -} - -.site-info { - padding: 24px 0px; -} - -.site-info.footer-text p { - font-weight: 300; - font-size: 12px; - line-height: 16px; -} - -.footer-head { - font-weight: 700; - color: #CCCCCC; - font-size: 14px; - line-height: 20px; -} - -.nginx-logo-footer { - height: 116px; - padding-top: 24px; - padding-bottom: 24px; -} - -.footer-social i { - color: #9A9A9A; - padding-right: 8px; -} - -.footer-social li, -#footer ul li, -.footer-head { - margin: 8px 0 0 0; -} - -.breadcrumb { - background: #343434; - margin-bottom: 0 !important; - padding: 4px 27px; - font-weight: 400; - font-size: 16px; - line-height: 26px; - color: #FFFFFF; - border: none!important; - border-radius: 0px; -} - -.breadcrumb a { - color: #FFFFFF; -} - -.breadcrumb a:hover { - color: #11BD8D; -} - -ol.breadcrumb { - margin-left: 0px; - margin-bottom: 0px; - font-size: 14px; -} - -.breadcrumb>li { - display: inline-block; -} - -.breadcrumb>li>i.fas.fa-chevron-right { - padding-right: 8px; - padding-left: 8px; -} - -.hero { - padding-top: 2rem; -} - -ul.pagination > li { - color: var(--nginx-green); - display: inline-block; -} - -ul.pagination .prev { - margin-right: 1em; -} - -ul.pagination .prev:before { - content: "\00ab"; - padding-right: .5em; -} - -ul.pagination .next { - margin-left: 1em; -} - -ul.pagination .next:after { - content: "\00bb"; - padding-left: 1em; -} - -/* Callouts */ - -blockquote { - border-radius: 4px; - padding: 8px; - background: #F8F9F9; - margin: 0 8px 12px 0; - float: none!important; -} - -.caution { - border-left: 4px solid #f29a36; -} - -.note { - border-left: 4px solid #62c026; -} - -.tip, .beta { - border-left: 4px solid #1d9cd3; - -} - -.warning, .important { - border-left: 4px solid #c20025; -} - -div.main ul > li, -div.main ol > li { - clear: both; -} -/* Feature States */ - -.beta, .stable, .alpha, .deprecated { - border-left: 4px solid transparent; - border-radius: 4px; -} - -.beta { - color: #0c5c8d; - border-color: #7cc0eb; -} - -.stable { - color: #438c15; - border-color: #a4e171; -} - -.alpha { - color: #343434; - border-color: #bd7421; -} - -.deprecated { - color: #343434; - border-color: #f1f1f1; -} - -/* -/// ReDoc API customizations -*/ - -.redoc-json>code { - background: #11181A; - color: #fbfbfb; - font-size: 100%; - font-family: Consolas, Monaco, courier new, monospace; -} - -/* -/ Override ReDoc font settings -*/ - -redoc, .redoc-wrap { - font-family: Arial, Helvetica, serif !important; -} - -.menu-content { - top: 66px !important; - height: calc(100vh - 66px) !important; -} - -redoc .search-icon { - display: none; -} - -/* -/ Custom style for vendor extensions -*/ - -.lpeYvY { - font-weight: 600; -} - -/* -/ Fix jumpiness on page scroll -*/ - -redoc { - overflow-anchor: auto; - overscroll-behavior-y: none; -} - -.nginx-docs-api-container { - width: 100%; -} - -.nginx-docs-api-container button { - border-color: inherit; - box-shadow: none!important; -} - -.nginx-docs-api-container>#operation>button:hover, .nginx-docs-api-container .button:hover { - filter: brightness(85%)!important; -} - -.api-content * { - font-family: "Roboto-Regular", "Helvetica Neue", Arial, sans-serif; -} - -/* -/ Make tooltip appear on hover -*/ - -.label { - display: inline-block; - cursor: default; -} - -.my-collapse-custom { - margin-bottom: 24px; -} - -.collapse-box { - margin-bottom: 8px; - margin-top: 8px; -} - -.my-collapse-custom-header { - float: right; - display: block; -} - -/* Tooltip container */ - -.tooltip { - position: relative; - display: inline-block; -} - -/* Tooltip text */ - -.tooltip .tooltiptext { - visibility: hidden; - width: fit-content; - background-color: #555; - color: #fff; - text-align: center; - padding: 10px 20px; - border-radius: 4px; - /* Position the tooltip text */ - position: absolute; - z-index: 1; - bottom: 125%; - left: 0%; - margin-left: -60px; - /* Fade in tooltip */ - opacity: 0; - transition: opacity 0.3s; -} - -/* Tooltip arrow */ - -.tooltip .tooltiptext::after { - content: ""; - position: absolute; - top: 100%; - left: 50%; - margin-left: -5px; - border-width: 5px; - border-style: solid; - border-color: #555 transparent transparent transparent; -} - -/* Show the tooltip text when you mouse over the tooltip container */ - -.tooltip:hover .tooltiptext { - visibility: visible; - opacity: 1; -} - -div pre>code { - white-space: pre; -} - -.content { - background-color: #FFFFFF; - padding-top: 32px; - padding-left: 24px; - padding-right: 16px; -} - -body { - margin: 0; - color: #222222; - background: #F8F9F9; - text-align: left; - font-size: 14px; - line-height: 20px; - font-weight: normal; - cursor: auto; -} - -h3.bd-links, #TableOfContents ul li a { - color: #343434; -} - -.bd-links { - display: block; - border: none; -} - -.bd-links ul > li:last-child { - padding-bottom: 0px !important; - margin-bottom: 0px !important; -} - -.bd-links ul > li > ul:last-child{ - padding-bottom: 0px !important; -} - -.page { - background-color: #FFFFFF; -} - -.bg-md-gray { - background-color: #e6e6e6; -} - -.bg-dk-gray { - background-color: #343434; -} - -.highlight { - margin: 12px 0; -} - -pre code { - font-size: 14px; - background: transparent; -} - -pre { - max-width: 100%; - margin: 0; - overflow: auto; - padding: 8px; - tab-size: 4; - -moz-tab-size: 4; - -o-tab-size: 4; - border-radius: 4px; -} - -code { - font-family: Courier; - color: #343434; - font-style: normal; - font-weight: 400; - line-height: 24px; - letter-spacing: 0em; - text-align: left; - font-size: inherit; - white-space: nowrap; - padding: 1px 0px; - background: #f1f1f1; - border-radius: 4px; -} - -a>code:hover { - color: #11BD8D; -} - -.min-page-height { - min-height: 100vh; -} - -/* bootstrap overrides/tweaks */ - -.alert { - display: flex; -} - -.alert > p { - margin-bottom: 0; -} - -.beta-icon { - margin: 5px 10px 0 0; -} - -.navbar-button, .footer-button, input[type=submit] { - padding: 16px; - height: 32px; - background: #1d9cd3; - border-radius: 4px; - color: #FFFFFF; - margin: 0px 6px; - font-size: 14px; - line-height: 20px; - - /* Auto Layout */ - display: flex; - flex-direction: row; - align-items: center; - text-align: center; - font-feature-settings: 'tnum' on, 'lnum' on; - /* Inside Auto Layout */ - flex: none; - order: 1; - flex-grow: 0; - margin: 0px 8px; -} - -button, input[type=button], input[type=reset], input[type=submit] { - box-shadow: none; - text-shadow: none; - border: none; - font-feature-settings: 'tnum' on, 'lnum' on; - /* identical to box height, or 133% */ - display: flex; - align-items: center; - text-align: center; - font-weight: normal; -} - -.navbar .button > a, -.navbar .button, -.footer-button, -.footer-button a, -.footer-button a:hover, -.footer-button a:active, -.footer-button a:link, -.button a:hover { - color: #FFFFFF; -} - -.footer-button { - min-width: 122px; -} - -.next-prev-icon { - display: flex; - list-style-type: none; - padding: 2%; -} - -#sidebar ul { - list-style-type: none; - display: list-item; - padding-left: 0; - margin-left: 0rem; - margin-bottom: 0 !important; -} - -.sidebar-l1-padding { - padding-left: 7px !important; -} - -.sidebar-l2-padding { - padding-left: 8px !important; -} - -.sidebar-reg-padding { - padding-left: 7px !important; -} - -.sidebar-il-border { - border-left: 1px solid #C5CCD3; - padding-left: 10px !important; -} - - -#sidebar ul:not(:first-child) { - display: list-item; - padding-left: 0px; - font-size: 95%; - -} - -#sidebar > .l2 ul li:last-child, -#sidebar > .l2 ul li ul li:last-child, -#sidebar > .l3 ul li:last-child, -#sidebar > .l3 ul li ul li:last-child { - padding-bottom: 8px; -} - -#sidebar .l3{ - padding-left: 21px; -} - -#sidebar a { - color: inherit; - text-align: left; - padding: 0; - font-weight: 300; -} - -#sidebar a:hover, -#TableOfContents a:hover { - color: inherit; - background-color: transparent; - color: #11BD8D; -} - -main a:hover { - color: #11BD8D; -} - -.sidebar-title { - font-size: 22px; - font-weight: 600 !important; -} - -#sidebar > h3 > a:hover { - color: #000; -} - -#sidebar > ul li { - padding-bottom: 8px !important; - padding-top: 8px !important; -} - -#sidebar > ul ul li { - padding-left: 0px; -} - -.nginx-toc { - display: block; - background: #F8F9F9; - padding-top: 24px; - padding-bottom: 32px; - font-size: 14px; - line-height: 20px; -} - -.nginx-toc-link.l1, -.nginx-toc-link.l1 > a { - font-weight: 700; - font-size: 18px; - line-height: 26px; - letter-spacing: -0.11px; - color: #111922; -} - -.nginx-toc-link.l2, -.nginx-toc-link.l2 > a { - color: #111922; - font-size: 18px; - line-height: 26px; - letter-spacing: -0.11px; -} - -.nginx-toc-link.l3, -.nginx-toc-link.l3 > a { - color: #111922; - font-size: 18px; - line-height: 26px; - letter-spacing: -0.11px; -} - -.nginx-toc > .bd-links { - position: sticky; - top: 0rem; - align-self: start; - min-height: 100vh; -} - -.l2, .l2 > a { - padding-left: 14px; - font-weight: normal; - line-height: 20px; -} - -.l3, .l3 > a { - - padding-left: 15px; - font-weight: normal; - line-height: 20px; -} - - - -.sidenav { - background: #f8f9f9; - padding-left: 24px; - padding-top: 20px; - position: sticky; - top: 0rem; - align-self: start; - height: 100vh; -} - -#sidebar { - display: block; - font-size: 1.2em; - overflow: auto; -} - -#sidebar.content > h3 { - font-weight: 600; -} - -nav#TableOfContents > ul > li { - margin-bottom: 10px; - font-size: 16px; - font-weight: 400; -} - -#TableOfContents ul { - list-style-type: none; - margin-left: 16px; - margin-right: 10px; -} - -nav#TableOfContents li>ol, -nav#TableOfContents li>ul { - margin-bottom: 0; - margin-left: 0.5em; -} - -nav#TableOfContents > ul > li > ul > li { - margin-bottom: 10px; - font-weight: 300; - margin-top: 6px; -} - -nav#TableOfContents > ul:not(:first-child) { - margin-left: 12px; - padding-top: 10px; - -} - - -#sidebar.content > ul > li.nginx-toc-link.has-subnav:before { - content: "3"; - font-family: "nginx-font" !important; - font-style: normal !important; - font-weight: normal !important; - font-variant: normal !important; - text-transform: none !important; - line-height: 1; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; - position: absolute; - left: -31px; - top: 0px; - -webkit-font-smoothing: antialiased; - -webkit-text-stroke: 1px #f7f7f7; - font-size: 30px; - cursor: pointer; - color: #898787; -} - -a[aria-expanded=true] .fa-chevron-right { - display: none; - } - a[aria-expanded=false] .fa-chevron-down { - display: none; - } - -a.headerlink { - font-size: 2rem; - vertical-align: super; - color: #e6e6e6; - opacity: 75%; -} - -a.headerlink:hover { - opacity: 100%; -} - -/* credit: - Randy_Lough - https://discourse.gohugo.io/t/bootstrap-4-pagination-some-added-functionality/11393 -*/ -/* This is css styling for the page numbers */ -.pagination > .page-item-number.active > a { - background-color: #898787; - border-color: #898787; - color: #fff; -} -.page-item-first a, -.page-item-previous a, -.page-item-ellipse, -.page-item-next a, -.page-item-last a { - color: #898787; -} - -/*.page-item-number {}*/ - - -/* end credit to Randy_Lough */ - -.form-group > label { - padding-bottom: 8px; - font-size: 2rem; - color: #000!important; -} - -.form-group > textarea { - font-size: 16px; -} - -/* style all input elements with a required attribute */ - - /** - * style input elements that have a required - * attribute and a focus state - */ - input:required:focus, - textarea:required:focus { - border: .25px solid red; - outline: none; - } - - /** - * style input elements that have a required - * attribute and a hover state - */ - input:required:hover, - textarea:required:hover { - opacity: 1; - } - -.btn-outline-success:hover { - background-color: var(--nginx-green); - border-color: var(--nginx-green); -} - -.btn-outline-success { - color: var(--nginx-green); - border-color: var(--nginx-green); -} - -.dk-border-bottom { - border-bottom: 1px solid #595959; - margin: 0 0 32px 0; -} - -.text-semibold { - font-weight: 500; -} - -.align-right { - text-align: end; -} - -.align-center { - text-align: center; -} - -ol.breadcrumb > li > a, -ol.breadcrumb > li:last-child { - padding: 0 4px; -} - -ol.breadcrumb > li:first-child { - padding-left: 0px; -} - -.fa, .far, .fas { - padding-right: 4px; -} \ No newline at end of file diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/assets/css/highlight.css b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/assets/css/highlight.css deleted file mode 100644 index 5e900dd345..0000000000 --- a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/assets/css/highlight.css +++ /dev/null @@ -1,82 +0,0 @@ -/* Background */ .chroma { color: #f8f8f2; background-color: #272822 } -/* Other */ .chroma .x { } -/* Error */ .chroma .err { color: #960050; background-color: #1e0010 } -/* LineTableTD */ .chroma .lntd { vertical-align: top; padding: 0; margin: 0; border: 0; } -/* LineTable */ .chroma .lntable { border-spacing: 0; padding: 0; margin: 0; border: 0; width: auto; overflow: auto; display: block; } -/* LineHighlight */ .chroma .hl { display: block; width: 100%;background-color: #7a7a55 } -/* LineNumbersTable */ .chroma .lnt { margin-right: 0.4em; padding: 0 0.4em 0 0.4em;color: #7f7f7f } -/* LineNumbers */ .chroma .ln { margin-right: 0.4em; padding: 0 0.4em 0 0.4em;color: #7f7f7f } -/* Keyword */ .chroma .k { color: #66d9ef } -/* KeywordConstant */ .chroma .kc { color: #66d9ef } -/* KeywordDeclaration */ .chroma .kd { color: #66d9ef } -/* KeywordNamespace */ .chroma .kn { color: #f92672 } -/* KeywordPseudo */ .chroma .kp { color: #66d9ef } -/* KeywordReserved */ .chroma .kr { color: #66d9ef } -/* KeywordType */ .chroma .kt { color: #66d9ef } -/* Name */ .chroma .n { } -/* NameAttribute */ .chroma .na { color: #a6e22e } -/* NameBuiltin */ .chroma .nb { color: #a6e22e } -/* NameBuiltinPseudo */ .chroma .bp { } -/* NameClass */ .chroma .nc { color: #a6e22e } -/* NameConstant */ .chroma .no { color: #66d9ef } -/* NameDecorator */ .chroma .nd { color: #a6e22e } -/* NameEntity */ .chroma .ni { } -/* NameException */ .chroma .ne { color: #a6e22e } -/* NameFunction */ .chroma .nf { color: #a6e22e } -/* NameFunctionMagic */ .chroma .fm { } -/* NameLabel */ .chroma .nl { } -/* NameNamespace */ .chroma .nn { } -/* NameOther */ .chroma .nx { color: #a6e22e } -/* NameProperty */ .chroma .py { } -/* NameTag */ .chroma .nt { color: #f92672 } -/* NameVariable */ .chroma .nv { color: #719e16 } -/* NameVariableClass */ .chroma .vc { } -/* NameVariableGlobal */ .chroma .vg { } -/* NameVariableInstance */ .chroma .vi { } -/* NameVariableMagic */ .chroma .vm { } -/* Literal */ .chroma .l { color: #ae81ff } -/* LiteralDate */ .chroma .ld { color: #e6db74 } -/* LiteralString */ .chroma .s { color: #e6db74 } -/* LiteralStringAffix */ .chroma .sa { color: #e6db74 } -/* LiteralStringBacktick */ .chroma .sb { color: #e6db74 } -/* LiteralStringChar */ .chroma .sc { color: #e6db74 } -/* LiteralStringDelimiter */ .chroma .dl { color: #e6db74 } -/* LiteralStringDoc */ .chroma .sd { color: #e6db74 } -/* LiteralStringDouble */ .chroma .s2 { color: #e6db74 } -/* LiteralStringEscape */ .chroma .se { color: #ae81ff } -/* LiteralStringHeredoc */ .chroma .sh { color: #e6db74 } -/* LiteralStringInterpol */ .chroma .si { color: #e6db74 } -/* LiteralStringOther */ .chroma .sx { color: #e6db74 } -/* LiteralStringRegex */ .chroma .sr { color: #e6db74 } -/* LiteralStringSingle */ .chroma .s1 { color: #e6db74 } -/* LiteralStringSymbol */ .chroma .ss { color: #e6db74 } -/* LiteralNumber */ .chroma .m { color: #ae81ff } -/* LiteralNumberBin */ .chroma .mb { color: #ae81ff } -/* LiteralNumberFloat */ .chroma .mf { color: #ae81ff } -/* LiteralNumberHex */ .chroma .mh { color: #ae81ff } -/* LiteralNumberInteger */ .chroma .mi { color: #ae81ff } -/* LiteralNumberIntegerLong */ .chroma .il { color: #ae81ff } -/* LiteralNumberOct */ .chroma .mo { color: #ae81ff } -/* Operator */ .chroma .o { color: #f92672 } -/* OperatorWord */ .chroma .ow { color: #f92672 } -/* Punctuation */ .chroma .p { } -/* Comment */ .chroma .c { color: #54f36e } -/* CommentHashbang */ .chroma .ch { color: #54f36e } -/* CommentMultiline */ .chroma .cm { color: #54f36e } -/* CommentSingle */ .chroma .c1 { color: #54f36e } -/* CommentSpecial */ .chroma .cs { color: #54f36e } -/* CommentPreproc */ .chroma .cp { color: #54f36e } -/* CommentPreprocFile */ .chroma .cpf { color: #54f36e } -/* Generic */ .chroma .g { } -/* GenericDeleted */ .chroma .gd { color: #f92672 } -/* GenericEmph */ .chroma .ge { font-style: italic } -/* GenericError */ .chroma .gr { } -/* GenericHeading */ .chroma .gh { } -/* GenericInserted */ .chroma .gi { color: #a6e22e } -/* GenericOutput */ .chroma .go { } -/* GenericPrompt */ .chroma .gp { } -/* GenericStrong */ .chroma .gs { font-weight: bold } -/* GenericSubheading */ .chroma .gu { color: #54f36e } -/* GenericTraceback */ .chroma .gt { } -/* GenericUnderline */ .chroma .gl { } -/* TextWhitespace */ .chroma .w { } diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/assets/css/kube.css b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/assets/css/kube.css deleted file mode 100644 index f3034ebb4d..0000000000 --- a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/assets/css/kube.css +++ /dev/null @@ -1,1308 +0,0 @@ -/* Kube theme elements -/ adapted from Kube framework - http://imperavi.com/kube/ - - Copyright (c) 2009-2017, Imperavi LLC. - License: MIT -/ -*/ -#hero { - padding-top: 48px; - padding-bottom: 56px; - text-align: center; -} - -#page-components { - text-align: center; -} - -#page-components.lists { - text-align: left; -} - -#page-components.lists .item { - padding: 24px; } - -#page-components.lists .item:hover { - background-color: rgb(247, 247, 247); -} - -#page-components .start { - font-size: 24px; - line-height: 32px; -} - -#page-components #search-box { - padding: 24px; - background-color: rgb(247, 247, 247); - margin-bottom: 24px; -} - -#page-components .item { - background-color: rgb(247, 247, 247); - padding: 68px 24px 60px 24px; - margin-bottom: 20px; -} - -#page-components figure { - margin-bottom: 0; -} - -#page-components h4 { - font-size: 19px; - margin-top: 0; - margin-bottom: 8px; -} - -#page-components ul { - margin-left: 0; - margin-top: 24px; - list-style: none; -} - -#page-components li { - line-height: 32px; - margin-bottom: 4px; -} - -#page-components li a { - display: inline-block; - line-height: 24px; -} - -#page-components li a:hover { - -moz-transition: all linear 0.2s; - transition: all linear 0.2s; - text-decoration: underline; -} - -#page-components p { - max-width: 220px; - margin: auto; - font-size: 13px; - line-height: 20px; -} - -#page-components #docs-search-results p { - max-width: none; - margin-bottom: 16px; -} - -#page-components h3 a, -#page-components h3 a:visited { - color: #000; -} - -#page-components h3 a:hover { - color: #0bb35f; -} - -.row { - display: flex; - flex-direction: row; - flex-wrap: wrap; } - @media (max-width: 768px) { - .row { - flex-direction: column; - flex-wrap: nowrap; } } - .row.gutters, - .row.gutters > .row { - margin-left: -2%; } - @media (max-width: 768px) { - .row.gutters, - .row.gutters > .row { - margin-left: 0; } } - .row.gutters > .col, - .row.gutters > .row > .col { - margin-left: 2%; } - @media (max-width: 768px) { - .row.gutters > .col, - .row.gutters > .row > .col { - margin-left: 0; } } - .row.around { - justify-content: space-around; } - .row.between { - justify-content: space-between; } - .row.auto .col { - flex-grow: 1; } - -.col-1 { - width: 8.33333%; } - -.offset-1 { - margin-left: 8.33333%; } - -.col-2 { - width: 16.66667%; } - -.offset-2 { - margin-left: 16.66667%; } - -.col-3 { - width: 25%; } - -.offset-3 { - margin-left: 25%; } - -.col-4 { - width: 33.33333%; } - -.offset-4 { - margin-left: 33.33333%; } - -.col-5 { - width: 41.66667%; } - -.offset-5 { - margin-left: 41.66667%; } - -.col-6 { - width: 50%; } - -.offset-6 { - margin-left: 50%; } - -.col-7 { - width: 58.33333%; } - -.offset-7 { - margin-left: 58.33333%; } - -.col-8 { - width: 66.66667%; } - -.offset-8 { - margin-left: 66.66667%; } - -.col-9 { - width: 75%; } - -.offset-9 { - margin-left: 75%; } - -.col-10 { - width: 83.33333%; } - -.offset-10 { - margin-left: 83.33333%; } - -.col-11 { - width: 91.66667%; } - -.offset-11 { - margin-left: 91.66667%; } - -.col-12 { - width: 100%; } - -.offset-12 { - margin-left: 100%; } - -.gutters > .col-1 { - width: calc(8.33333% - 2%); } - -.gutters > .offset-1 { - margin-left: calc(8.33333% + 2%) !important; } - -.gutters > .col-2 { - width: calc(16.66667% - 2%); } - -.gutters > .offset-2 { - margin-left: calc(16.66667% + 2%) !important; } - -.gutters > .col-3 { - width: calc(25% - 2%); } - -.gutters > .offset-3 { - margin-left: calc(25% + 2%) !important; } - -.gutters > .col-4 { - width: calc(33.33333% - 2%); } - -.gutters > .offset-4 { - margin-left: calc(33.33333% + 2%) !important; } - -.gutters > .col-5 { - width: calc(41.66667% - 2%); } - -.gutters > .offset-5 { - margin-left: calc(41.66667% + 2%) !important; } - -.gutters > .col-6 { - width: calc(50% - 2%); } - -.gutters > .offset-6 { - margin-left: calc(50% + 2%) !important; } - -.gutters > .col-7 { - width: calc(58.33333% - 2%); } - -.gutters > .offset-7 { - margin-left: calc(58.33333% + 2%) !important; } - -.gutters > .col-8 { - width: calc(66.66667% - 2%); } - -.gutters > .offset-8 { - margin-left: calc(66.66667% + 2%) !important; } - -.gutters > .col-9 { - width: calc(75% - 2%); } - -.gutters > .offset-9 { - margin-left: calc(75% + 2%) !important; } - -.gutters > .col-10 { - width: calc(83.33333% - 2%); } - -.gutters > .offset-10 { - margin-left: calc(83.33333% + 2%) !important; } - -.gutters > .col-11 { - width: calc(91.66667% - 2%); } - -.gutters > .offset-11 { - margin-left: calc(91.66667% + 2%) !important; } - -.gutters > .col-12 { - width: calc(100% - 2%); } - -.gutters > .offset-12 { - margin-left: calc(100% + 2%) !important; } - -@media (max-width: 768px) { - [class^='offset-'], - [class*=' offset-'] { - margin-left: 0; } } - -.first { - order: -1; } - -.last { - order: 1; } - -@media (max-width: 768px) { - .row .col { - margin-left: 0; - width: 100%; } - .row.gutters .col { - margin-bottom: 16px; } - .first-sm { - order: -1; } - .last-sm { - order: 1; } } - - -.pager span { - line-height: 24px; } - -.pager span, -.pager a { - padding-left: 16px; - padding-right: 16px; - border-radius: 64px; - border-color: rgba(0, 0, 0, 0.1); } - -.pager li { - flex-basis: 50%; } - -.pager li.next { - text-align: right; } - -.pager.align-center li { - flex-basis: auto; - margin-left: 4px; - margin-right: 4px; } - -.pager.flat span, -.pager.flat a { - border: none; - display: block; - padding: 0; } - -.pager.flat a { - font-weight: bold; } - .pager.flat a:hover { - background: none; - text-decoration: underline; } - -@media (max-width: 768px) { - .pager.flat ul { - flex-direction: column; } - .pager.flat li { - flex-basis: 100%; - margin-bottom: 8px; - text-align: left; } } - -@font-face { - font-family: 'Kube'; - src: url("data:application/x-font-ttf;charset=utf-8;base64,AAEAAAALAIAAAwAwT1MvMg8SBfgAAAC8AAAAYGNtYXAXVtKOAAABHAAAAFRnYXNwAAAAEAAAAXAAAAAIZ2x5ZsMn2SAAAAF4AAADeGhlYWQMP9EUAAAE8AAAADZoaGVhB8IDzQAABSgAAAAkaG10eCYABd4AAAVMAAAAMGxvY2EFWASuAAAFfAAAABptYXhwABcAmwAABZgAAAAgbmFtZfMJxocAAAW4AAABYnBvc3QAAwAAAAAHHAAAACAAAwPHAZAABQAAApkCzAAAAI8CmQLMAAAB6wAzAQkAAAAAAAAAAAAAAAAAAAABEAAAAAAAAAAAAAAAAAAAAABAAADpBwPA/8AAQAPAAEAAAAABAAAAAAAAAAAAAAAgAAAAAAADAAAAAwAAABwAAQADAAAAHAADAAEAAAAcAAQAOAAAAAoACAACAAIAAQAg6Qf//f//AAAAAAAg6QD//f//AAH/4xcEAAMAAQAAAAAAAAAAAAAAAQAB//8ADwABAAAAAAAAAAAAAgAANzkBAAAAAAEAAAAAAAAAAAACAAA3OQEAAAAAAQAAAAAAAAAAAAIAADc5AQAAAAAKAAAAAAQAA8AADwAUACQANABEAFYAaAB4AIgAmAAAEyIGFREUFjMhMjY1ETQmIwUhESEREzgBMSIGFRQWMzI2NTQmIzM4ATEiBhUUFjMyNjU0JiMzOAExIgYVFBYzMjY1NCYjATIWHQEUBiMiJj0BNDYzOAExITIWHQEUBiMiJj0BNDYzOAExATgBMSIGFRQWMzI2NTQmIzM4ATEiBhUUFjMyNjU0JiMzOAExIgYVFBYzMjY1NCYjwFBwcFACgFBwcFD9IQM+/MKrHioqHh4qKh70HioqHh4qKh70HisrHh0rKh7+MBQdHRQUHBwUAbgUHBwUFB0dFP4wHioqHh4qKh70HioqHh4qKh70HisrHh0rKh4DYHBQ/iBQcHBQAeBQcF/9XwKh/n8qHh4qKh4eKioeHioqHh4qKh4eKioeHioCQBwVjhUcHBWOFRwcFY4VHBwVjhUc/rAqHh4qKh4eKioeHioqHh4qKh4eKioeHioAAAABAQAAwAMAAcAACwAAAQcXBycHJzcnNxc3AwDMAjMDAzMCzDTMzAGVqAIrAgIrAqgrqKgAAQGAAEACgAJAAAsAACUnByc3JzcXNxcHFwJVqAIrAgIrAqgrqKhAzAIzAwMzAsw0zMwAAAEBgABAAoACQAALAAABFzcXBxcHJwcnNycBq6gCKwICKwKoK6ioAkDMAjMDAzMCzDTMzAABAQAAwAMAAcAACwAAJTcnNxc3FwcXBycHAQDMAjMDAzMCzDTMzOuoAisCAisCqCuoqAAAAgAP/+UD1AOqAAQACAAAEwEHATcFAScBSwOJPPx3PAOJ/Hc8A4kDqvx3PAOJPDz8dzwDiQAAAAADAIAAgAOAAwAAAwAHAAsAADc1IRUBIRUhESEVIYADAP0AAwD9AAMA/QCAgIABgIABgIAAAgBPAA8DsgNxABgALQAAJQcBDgEjIi4CNTQ+AjMyHgIVFAYHAQEiDgIVFB4CMzI+AjU0LgIjA7JY/t4lWTBBc1YxMVZzQUFzVTIcGQEi/dgxVkAlJUBWMTFWQCUlQFYxZ1gBIRkcMlVzQUFzVjExVnNBMFkm/uACuyVAVjExVkAlJUBWMTFWQCUAAAABAAAAAQAABhlWm18PPPUACwQAAAAAANSQRjkAAAAA1JBGOQAA/+UEAAPAAAAACAACAAAAAAAAAAEAAAPA/8AAAAQAAAAAAAQAAAEAAAAAAAAAAAAAAAAAAAAMBAAAAAAAAAAAAAAAAgAAAAQAAAAEAAEABAABgAQAAYAEAAEABAAADwQAAIAEAABPAAAAAAAKABQAHgDYAPIBDAEmAUABXAF2AbwAAAABAAAADACZAAoAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAADgCuAAEAAAAAAAEABAAAAAEAAAAAAAIABwBFAAEAAAAAAAMABAAtAAEAAAAAAAQABABaAAEAAAAAAAUACwAMAAEAAAAAAAYABAA5AAEAAAAAAAoAGgBmAAMAAQQJAAEACAAEAAMAAQQJAAIADgBMAAMAAQQJAAMACAAxAAMAAQQJAAQACABeAAMAAQQJAAUAFgAXAAMAAQQJAAYACAA9AAMAAQQJAAoANACAS3ViZQBLAHUAYgBlVmVyc2lvbiAxLjAAVgBlAHIAcwBpAG8AbgAgADEALgAwS3ViZQBLAHUAYgBlS3ViZQBLAHUAYgBlUmVndWxhcgBSAGUAZwB1AGwAYQByS3ViZQBLAHUAYgBlRm9udCBnZW5lcmF0ZWQgYnkgSWNvTW9vbi4ARgBvAG4AdAAgAGcAZQBuAGUAcgBhAHQAZQBkACAAYgB5ACAASQBjAG8ATQBvAG8AbgAuAAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==") format("truetype"); - font-weight: normal; - font-style: normal; } - -[class^="kube-"], [class*=" kube-"], .close, .caret { - /* use !important to prevent issues with browser extensions that change fonts */ - font-family: 'Kube' !important; - speak: none; - font-style: normal; - font-weight: normal; - font-variant: normal; - text-transform: none; - line-height: 1; - /* Better Font Rendering =========== */ - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; } - -.kube-calendar:before { - content: "\e900"; } - -.caret.down:before, -.kube-caret-down:before { - content: "\e901"; } - -.caret.left:before, -.kube-caret-left:before { - content: "\e902"; } - -.caret.right:before, -.kube-caret-right:before { - content: "\e903"; } - -.caret.up:before, -.kube-caret-up:before { - content: "\e904"; } - -.close:before, -.kube-close:before { - content: "\e905"; } - -.kube-menu:before { - content: "\e906"; } - -.kube-search:before { - content: "\e907"; } - -.gutters .column.push-left, -.push-left { - margin-right: auto; } - -.gutters .column.push-right, -.push-right { - margin-left: auto; } - -.gutters .column.push-center, -.push-center { - margin-left: auto; - margin-right: auto; } - -.gutters .column.push-middle, -.push-middle { - margin-top: auto; - margin-bottom: auto; } - -.push-bottom { - margin-top: auto; } - -@media (max-width: 768px) { - .gutters .column.push-left-sm, - .push-left-sm { - margin-left: 0; } - .gutters .column.push-center-sm, - .push-center-sm { - margin-left: auto; - margin-right: auto; } - .push-top-sm { - margin-top: 0; } } - -.align-middle { - align-items: center; } - -.align-right { - justify-content: flex-end; } - -.align-center { - justify-content: center; } - -@media (max-width: 768px) { - .align-left-sm { - justify-content: flex-start; } } - -.float-right { - float: right; } - -.float-left { - float: left; } - -@media (max-width: 768px) { - .float-right { - float: none; } - .float-left { - float: none; } } - -.fixed { - position: fixed; - top: 0; - left: 0; - z-index: 100; - width: 100%; } - -.w5 { - width: 5%; } - -.w10 { - width: 10%; } - -.w15 { - width: 15%; } - -.w20 { - width: 20%; } - -.w25 { - width: 25%; } - -.w30 { - width: 30%; } - -.w35 { - width: 35%; } - -.w40 { - width: 40%; } - -.w45 { - width: 45%; } - -.w50 { - width: 50%; } - -.w55 { - width: 55%; } - -.w60 { - width: 60%; } - -.w65 { - width: 65%; } - -.w70 { - width: 70%; } - -.w75 { - width: 75%; } - -.w80 { - width: 80%; } - -.w85 { - width: 85%; } - -.w90 { - width: 90%; } - -.w95 { - width: 95%; } - -.w100 { - width: 100%; } - -.w-auto { - width: auto; } - -.w-small { - width: 480px; } - -.w-medium { - width: 600px; } - -.w-big { - width: 740px; } - -.w-large { - width: 840px; } - -@media (max-width: 768px) { - .w-auto-sm { - width: auto; } - .w100-sm, - .w-small, - .w-medium, - .w-big, - .w-large { - width: 100%; } } - -.max-w5 { - max-width: 5%; } - -.max-w10 { - max-width: 10%; } - -.max-w15 { - max-width: 15%; } - -.max-w20 { - max-width: 20%; } - -.max-w25 { - max-width: 25%; } - -.max-w30 { - max-width: 30%; } - -.max-w35 { - max-width: 35%; } - -.max-w40 { - max-width: 40%; } - -.max-w45 { - max-width: 45%; } - -.max-w50 { - max-width: 50%; } - -.max-w55 { - max-width: 55%; } - -.max-w60 { - max-width: 60%; } - -.max-w65 { - max-width: 65%; } - -.max-w70 { - max-width: 70%; } - -.max-w75 { - max-width: 75%; } - -.max-w80 { - max-width: 80%; } - -.max-w85 { - max-width: 85%; } - -.max-w90 { - max-width: 90%; } - -.max-w95 { - max-width: 95%; } - -.max-w100 { - max-width: 100%; } - -.max-w-small { - max-width: 480px; } - -.max-w-medium { - max-width: 600px; } - -.max-w-big { - max-width: 740px; } - -.max-w-large { - max-width: 840px; } - -@media (max-width: 768px) { - .max-w-auto-sm, - .max-w-small, - .max-w-medium, - .max-w-big, - .max-w-large { - max-width: auto; } } - -.min-w5 { - min-width: 5%; } - -.min-w10 { - min-width: 10%; } - -.min-w15 { - min-width: 15%; } - -.min-w20 { - min-width: 20%; } - -.min-w25 { - min-width: 25%; } - -.min-w30 { - min-width: 30%; } - -.min-w35 { - min-width: 35%; } - -.min-w40 { - min-width: 40%; } - -.min-w45 { - min-width: 45%; } - -.min-w50 { - min-width: 50%; } - -.min-w55 { - min-width: 55%; } - -.min-w60 { - min-width: 60%; } - -.min-w65 { - min-width: 65%; } - -.min-w70 { - min-width: 70%; } - -.min-w75 { - min-width: 75%; } - -.min-w80 { - min-width: 80%; } - -.min-w85 { - min-width: 85%; } - -.min-w90 { - min-width: 90%; } - -.min-w95 { - min-width: 95%; } - -.min-w100 { - min-width: 100%; } - -.h25 { - height: 25%; } - -.h50 { - height: 50%; } - -.h100 { - height: 100%; } - -.group:after { - content: ''; - display: table; - clear: both; } - -.flex { - display: flex; } - -@media (max-width: 768px) { - .flex-column-sm { - flex-direction: column; } - .flex-w100-sm { - flex: 0 0 100%; } } - @media (max-width: 768px) and (max-width: 768px) { - .flex-w100-sm { - flex: 0 0 100% !important; } } - -.invisible { - visibility: hidden; } - -.visible { - visibility: visible; } - -.display-block { - display: block; } - -.hide { - display: none !important; } - -@media (max-width: 768px) { - .hide-sm { - display: none !important; } } - -@media (min-width: 769px) { - .show-sm { - display: none !important; } } - -@media print { - .hide-print { - display: none !important; } - .show-print { - display: block !important; } } - -.no-scroll { - overflow: hidden; - position: fixed; - top: 0; - left: 0; - width: 100%; - height: 100% !important; } - -.scrollbar-measure { - position: absolute; - top: -9999px; - width: 50px; - height: 50px; - overflow: scroll; } - -.video-container { - height: 0; - padding-bottom: 56.25%; - position: relative; - margin-bottom: 16px; } - .video-container iframe, - .video-container object, - .video-container embed { - position: absolute; - top: 0; - left: 0; - width: 100% !important; - height: 100% !important; } - -.close { - display: inline-block; - min-height: 16px; - min-width: 16px; - line-height: 16px; - vertical-align: middle; - text-align: center; - font-size: 12px; - opacity: .6; } - .close:hover { - opacity: 1; } - .close.small { - font-size: 8px; } - .close.big { - font-size: 18px; } - .close.white { - color: #fff; } - -.caret { - display: inline-block; } - -.button .caret { - margin-right: -8px; } - -.overlay { - position: fixed; - z-index: 200; - top: 0; - left: 0; - right: 0; - bottom: 0; - background-color: rgba(255, 255, 255, 0.95); } - .overlay > .close { - position: fixed; - top: 1rem; - right: 1rem; } - -@media print { - * { - background: transparent !important; - color: black !important; - box-shadow: none !important; - text-shadow: none !important; } - a, - a:visited { - text-decoration: underline; } - pre, blockquote { - border: 1px solid #999; - page-break-inside: avoid; } - p, h2, h3 { - orphans: 3; - widows: 3; } - thead { - display: table-header-group; } - tr, img { - page-break-inside: avoid; } - img { - max-width: 100% !important; } - h2, h3, h4 { - page-break-after: avoid; } - @page { - margin: 0.5cm; } } - -@keyframes slideUp { - to { - height: 0; - padding-top: 0; - padding-bottom: 0; } } - -@keyframes slideDown { - from { - height: 0; - padding-top: 0; - padding-bottom: 0; } } - -@keyframes fadeIn { - from { - opacity: 0; } - to { - opacity: 1; } } - -@keyframes fadeOut { - from { - opacity: 1; } - to { - opacity: 0; } } - -@keyframes flipIn { - from { - opacity: 0; - transform: scaleY(0); } - to { - opacity: 1; - transform: scaleY(1); } } - -@keyframes flipOut { - from { - opacity: 1; - transform: scaleY(1); } - to { - opacity: 0; - transform: scaleY(0); } } - -@keyframes zoomIn { - from { - opacity: 0; - transform: scale3d(0.3, 0.3, 0.3); } - 50% { - opacity: 1; } } - -@keyframes zoomOut { - from { - opacity: 1; } - 50% { - opacity: 0; - transform: scale3d(0.3, 0.3, 0.3); } - to { - opacity: 0; } } - -@keyframes slideInRight { - from { - transform: translate3d(100%, 0, 0); - visibility: visible; } - to { - transform: translate3d(0, 0, 0); } } - -@keyframes slideInLeft { - from { - transform: translate3d(-100%, 0, 0); - visibility: visible; } - to { - transform: translate3d(0, 0, 0); } } - -@keyframes slideInDown { - from { - transform: translate3d(0, -100%, 0); - visibility: visible; } - to { - transform: translate3d(0, 0, 0); } } - -@keyframes slideOutLeft { - from { - transform: translate3d(0, 0, 0); } - to { - visibility: hidden; - transform: translate3d(-100%, 0, 0); } } - -@keyframes slideOutRight { - from { - transform: translate3d(0, 0, 0); } - to { - visibility: hidden; - transform: translate3d(100%, 0, 0); } } - -@keyframes slideOutUp { - from { - transform: translate3d(0, 0, 0); } - to { - visibility: hidden; - transform: translate3d(0, -100%, 0); } } - -@keyframes rotate { - from { - transform: rotate(0deg); } - to { - transform: rotate(360deg); } } - -@keyframes pulse { - from { - transform: scale3d(1, 1, 1); } - 50% { - transform: scale3d(1.03, 1.03, 1.03); } - to { - transform: scale3d(1, 1, 1); } } - -@keyframes shake { - 15% { - transform: translateX(0.5rem); } - 30% { - transform: translateX(-0.4rem); } - 45% { - transform: translateX(0.3rem); } - 60% { - transform: translateX(-0.2rem); } - 75% { - transform: translateX(0.1rem); } - 90% { - transform: translateX(0); } - 90% { - transform: translateX(0); } } - -.fadeIn { - animation: fadeIn 250ms; } - -.fadeOut { - animation: fadeOut 250ms; } - -.zoomIn { - animation: zoomIn 200ms; } - -.zoomOut { - animation: zoomOut 500ms; } - -.slideInRight { - animation: slideInRight 500ms; } - -.slideInLeft { - animation: slideInLeft 500ms; } - -.slideInDown { - animation: slideInDown 500ms; } - -.slideOutLeft { - animation: slideOutLeft 500ms; } - -.slideOutRight { - animation: slideOutRight 500ms; } - -.slideOutUp { - animation: slideOutUp 500ms; } - -.slideUp { - overflow: hidden; - animation: slideUp 200ms ease-in-out; } - -.slideDown { - overflow: hidden; - animation: slideDown 80ms ease-in-out; } - -.flipIn { - animation: flipIn 250ms cubic-bezier(0.5, -0.5, 0.5, 1.5); } - -.flipOut { - animation: flipOut 500ms cubic-bezier(0.5, -0.5, 0.5, 1.5); } - -.rotate { - animation: rotate 500ms; } - -.pulse { - animation: pulse 250ms 2; } - -.shake { - animation: shake 500ms; } - -.dropdown { - position: absolute; - z-index: 100; - top: 0; - right: 0; - width: 280px; - color: #000; - font-size: 15px; - background: #fff; - box-shadow: 0 10px 25px rgba(0, 0, 0, 0.15); - border-radius: 3px; - max-height: 300px; - margin: 0; - padding: 0; - overflow: hidden; } - .dropdown.dropdown-mobile { - position: fixed; - top: 0; - left: 0; - right: 0; - bottom: 0; - width: 100%; - max-height: none; - border: none; } - .dropdown .close { - margin: 20px auto; } - .dropdown.open { - overflow: auto; } - .dropdown ul { - list-style: none; - margin: 0; } - .dropdown ul li { - border-bottom: 1px solid rgba(0, 0, 0, 0.07); } - .dropdown ul li:last-child { - border-bottom: none; } - .dropdown ul a { - display: block; - padding: 12px; - text-decoration: none; - color: #000; } - .dropdown ul a:hover { - background: rgba(0, 0, 0, 0.05); } - - -.message { - font-family: Consolas, Monaco, "Courier New", monospace; - font-size: 14px; - line-height: 20px; - background: #e0e1e1; - color: #313439; - padding: 1rem; - padding-right: 2.5em; - padding-bottom: .75rem; - margin-bottom: 24px; - position: relative; } - .message a { - color: inherit; } - .message h2, - .message h3, - .message h4, - .message h5, - .message h6 { - margin-bottom: 0; } - .message .close { - position: absolute; - right: 1rem; - top: 1.1rem; } - -.message.error { - background: #f03c69; - color: #fff; } - -.message.success { - background: #35beb1; - color: #fff; } - -.message.warning { - background: #f7ba45; } - -.message.focus { - background: #1c86f2; - color: #fff; } - -.message.black { - background: #0d0d0e; - color: #fff; } - -.message.inverted { - background: #fff; } - -.modal-box { - position: fixed; - top: 0; - left: 0; - bottom: 0; - right: 0; - overflow-x: hidden; - overflow-y: auto; - z-index: 200; } - -.modal { - position: relative; - margin: auto; - margin-top: 16px; - padding: 0; - background: #fff; - box-shadow: 0 10px 25px rgba(0, 0, 0, 0.15); - border-radius: 8px; - color: #000; } - @media (max-width: 768px) { - .modal input, - .modal textarea { - font-size: 16px; } } - .modal .close { - position: absolute; - top: 18px; - right: 16px; - opacity: .3; } - .modal .close:hover { - opacity: 1; } - -.modal-header { - padding: 24px 32px; - font-size: 18px; - font-weight: bold; - border-bottom: 1px solid rgba(0, 0, 0, 0.05); } - .modal-header:empty { - display: none; } - -.modal-body { - padding: 36px 56px; } - -@media (max-width: 768px) { - .modal-header, - .modal-body { - padding: 24px; } } - -.offcanvas { - background: #fff; - position: fixed; - padding: 24px; - height: 100%; - top: 0; - left: 0; - z-index: 300; - overflow-y: scroll; } - -.offcanvas .close { - position: absolute; - top: 8px; - right: 8px; } - -.offcanvas-left { - border-right: 1px solid rgba(0, 0, 0, 0.1); } - -.offcanvas-right { - left: auto; - right: 0; - border-left: 1px solid rgba(0, 0, 0, 0.1); } - -.offcanvas-push-body { - position: relative; } - -.tabs { - margin-bottom: 24px; - font-size: 14px; } - .tabs li em, - .tabs li.active a { - color: #313439; - border: 1px solid rgba(0, 0, 0, 0.1); - cursor: default; - text-decoration: none; - background: none; } - .tabs em, - .tabs a { - position: relative; - top: 1px; - font-style: normal; - display: block; - padding: .5rem 1rem; - border: 1px solid transparent; - color: rgba(0, 0, 0, 0.5); - text-decoration: none; } - .tabs a:hover { - -moz-transition: all linear 0.2s; - transition: all linear 0.2s; - color: #313439; - text-decoration: underline; - background-color: #e0e1e1; } - -@media (min-width: 768px) { - .tabs ul { - display: flex; - margin-top: -1px; - border-bottom: 1px solid rgba(0, 0, 0, 0.1); } - .tabs li em, - .tabs li.active a { - border-bottom: 1px solid #fff; } } - -label { - display: block; - color: #313439; - margin-bottom: 4px; - /*font-size: 15px*/;} - label.checkbox, - label .desc, - label .success, - label .error { - text-transform: none; - font-weight: normal; } - label.checkbox { - font-size: 16px; - line-height: 24px; - cursor: pointer; - color: inherit; } - label.checkbox input { - margin-top: 0; } - -.label { - display: inline-block; - font-size: 45%; - line-height: 18px; - padding: 5px 10px; - font-weight: 500; - color: #313439; - border: 1px solid transparent; - vertical-align: middle; - text-decoration: none; - border-radius: 4px; } - .label a, - .label a:hover { - color: inherit; - text-decoration: none; } - -.label.big { - font-size: 14px; - line-height: 24px; - padding: 0 12px; } - -.label.upper { - text-transform: uppercase; - font-size: 11px; } - -.label.outline { - background: none; - border-color: #bdbdbd; } - -.label.badge { - text-align: center; - border-radius: 64px; - padding: 0 6px; } - .label.badge.big { - padding: 0 8px; } - -.label.tag { - padding: 0; - background: none; - border: none; - text-transform: uppercase; - font-size: 11px; } - .label.tag.big { - font-size: 13px; } - -.label.success { - background: #62c026; - color: #fff; } - .label.success.tag, .label.success.outline { - background: none; - border-color: #62c026; - color: #62c026; } - -.label.error { - background: #c20025; - color: #fff; } - .label.error.tag, .label.error.outline { - background: none; - border-color: #c20025; - color: #c20025; } - -.label.warning { - border-color: #f29a36; - color: #0d0d0e; } - .label.warning.tag, .label.warning.outline { - background: none; - border-color: #f29a36; - color: #f29a36; } - -.label.focus, .label.beta { - background: #1d9cd3; - color: #fff; } - .label.focus.tag, .label.focus.outline { - background: none; - border-color: #1d9cd3; - color: #1d9cd3; } - -.label.black { - background: #0d0d0e; - color: #fff; } - .label.black.tag, .label.black.outline { - background: none; - border-color: #0d0d0e; - color: #0d0d0e; } - -.label.inverted { - background: #fff; - color: #0d0d0e; } - .label.inverted.tag, .label.inverted.outline { - background: none; - border-color: #fff; - color: #fff; } \ No newline at end of file diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/assets/js/all.min.js b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/assets/js/all.min.js deleted file mode 100644 index 4cf4ba9570..0000000000 --- a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/assets/js/all.min.js +++ /dev/null @@ -1,6 +0,0 @@ -/*! - * Font Awesome Free 6.0.0 by @fontawesome - https://fontawesome.com - * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) - * Copyright 2022 Fonticons, Inc. - */ -!function(){"use strict";var C={},c={};try{"undefined"!=typeof window&&(C=window),"undefined"!=typeof document&&(c=document)}catch(C){}var l=(C.navigator||{}).userAgent,z=void 0===l?"":l,a=C,e=c;a.document,e.documentElement&&e.head&&"function"==typeof e.addEventListener&&e.createElement,~z.indexOf("MSIE")||z.indexOf("Trident/");function M(c,C){var l,z=Object.keys(c);return Object.getOwnPropertySymbols&&(l=Object.getOwnPropertySymbols(c),C&&(l=l.filter(function(C){return Object.getOwnPropertyDescriptor(c,C).enumerable})),z.push.apply(z,l)),z}function t(z){for(var C=1;CC.length)&&(c=C.length);for(var l=0,z=new Array(c);lC.length)&&(c=C.length);for(var l=0,z=new Array(c);lC.length)&&(c=C.length);for(var l=0,z=new Array(c);lC.length)&&(c=C.length);for(var l=0,z=new Array(c);l>>0;l--;)c[l]=C[l];return c}function J(C){return C.classList?$(C.classList):(C.getAttribute("class")||"").split(" ").filter(function(C){return C})}function Z(C){return"".concat(C).replace(/&/g,"&").replace(/"/g,""").replace(/'/g,"'").replace(//g,">")}function C1(l){return Object.keys(l||{}).reduce(function(C,c){return C+"".concat(c,": ").concat(l[c].trim(),";")},"")}function c1(C){return C.size!==Q.size||C.x!==Q.x||C.y!==Q.y||C.rotate!==Q.rotate||C.flipX||C.flipY}function l1(){var C,c,l=p,z=U.familyPrefix,a=U.replacementClass,e=':host,:root{--fa-font-solid:normal 900 1em/1 "Font Awesome 6 Solid";--fa-font-regular:normal 400 1em/1 "Font Awesome 6 Regular";--fa-font-light:normal 300 1em/1 "Font Awesome 6 Light";--fa-font-thin:normal 100 1em/1 "Font Awesome 6 Thin";--fa-font-duotone:normal 900 1em/1 "Font Awesome 6 Duotone";--fa-font-brands:normal 400 1em/1 "Font Awesome 6 Brands"}svg:not(:host).svg-inline--fa,svg:not(:root).svg-inline--fa{overflow:visible;box-sizing:content-box}.svg-inline--fa{display:var(--fa-display,inline-block);height:1em;overflow:visible;vertical-align:-.125em}.svg-inline--fa.fa-2xs{vertical-align:.1em}.svg-inline--fa.fa-xs{vertical-align:0}.svg-inline--fa.fa-sm{vertical-align:-.0714285705em}.svg-inline--fa.fa-lg{vertical-align:-.2em}.svg-inline--fa.fa-xl{vertical-align:-.25em}.svg-inline--fa.fa-2xl{vertical-align:-.3125em}.svg-inline--fa.fa-pull-left{margin-right:var(--fa-pull-margin,.3em);width:auto}.svg-inline--fa.fa-pull-right{margin-left:var(--fa-pull-margin,.3em);width:auto}.svg-inline--fa.fa-li{width:var(--fa-li-width,2em);top:.25em}.svg-inline--fa.fa-fw{width:var(--fa-fw-width,1.25em)}.fa-layers svg.svg-inline--fa{bottom:0;left:0;margin:auto;position:absolute;right:0;top:0}.fa-layers-counter,.fa-layers-text{display:inline-block;position:absolute;text-align:center}.fa-layers{display:inline-block;height:1em;position:relative;text-align:center;vertical-align:-.125em;width:1em}.fa-layers svg.svg-inline--fa{-webkit-transform-origin:center center;transform-origin:center center}.fa-layers-text{left:50%;top:50%;-webkit-transform:translate(-50%,-50%);transform:translate(-50%,-50%);-webkit-transform-origin:center center;transform-origin:center center}.fa-layers-counter{background-color:var(--fa-counter-background-color,#ff253a);border-radius:var(--fa-counter-border-radius,1em);box-sizing:border-box;color:var(--fa-inverse,#fff);line-height:var(--fa-counter-line-height,1);max-width:var(--fa-counter-max-width,5em);min-width:var(--fa-counter-min-width,1.5em);overflow:hidden;padding:var(--fa-counter-padding,.25em .5em);right:var(--fa-right,0);text-overflow:ellipsis;top:var(--fa-top,0);-webkit-transform:scale(var(--fa-counter-scale,.25));transform:scale(var(--fa-counter-scale,.25));-webkit-transform-origin:top right;transform-origin:top right}.fa-layers-bottom-right{bottom:var(--fa-bottom,0);right:var(--fa-right,0);top:auto;-webkit-transform:scale(var(--fa-layers-scale,.25));transform:scale(var(--fa-layers-scale,.25));-webkit-transform-origin:bottom right;transform-origin:bottom right}.fa-layers-bottom-left{bottom:var(--fa-bottom,0);left:var(--fa-left,0);right:auto;top:auto;-webkit-transform:scale(var(--fa-layers-scale,.25));transform:scale(var(--fa-layers-scale,.25));-webkit-transform-origin:bottom left;transform-origin:bottom left}.fa-layers-top-right{top:var(--fa-top,0);right:var(--fa-right,0);-webkit-transform:scale(var(--fa-layers-scale,.25));transform:scale(var(--fa-layers-scale,.25));-webkit-transform-origin:top right;transform-origin:top right}.fa-layers-top-left{left:var(--fa-left,0);right:auto;top:var(--fa-top,0);-webkit-transform:scale(var(--fa-layers-scale,.25));transform:scale(var(--fa-layers-scale,.25));-webkit-transform-origin:top left;transform-origin:top left}.fa-1x{font-size:1em}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-6x{font-size:6em}.fa-7x{font-size:7em}.fa-8x{font-size:8em}.fa-9x{font-size:9em}.fa-10x{font-size:10em}.fa-2xs{font-size:.625em;line-height:.1em;vertical-align:.225em}.fa-xs{font-size:.75em;line-height:.0833333337em;vertical-align:.125em}.fa-sm{font-size:.875em;line-height:.0714285718em;vertical-align:.0535714295em}.fa-lg{font-size:1.25em;line-height:.05em;vertical-align:-.075em}.fa-xl{font-size:1.5em;line-height:.0416666682em;vertical-align:-.125em}.fa-2xl{font-size:2em;line-height:.03125em;vertical-align:-.1875em}.fa-fw{text-align:center;width:1.25em}.fa-ul{list-style-type:none;margin-left:var(--fa-li-margin,2.5em);padding-left:0}.fa-ul>li{position:relative}.fa-li{left:calc(var(--fa-li-width,2em) * -1);position:absolute;text-align:center;width:var(--fa-li-width,2em);line-height:inherit}.fa-border{border-color:var(--fa-border-color,#eee);border-radius:var(--fa-border-radius,.1em);border-style:var(--fa-border-style,solid);border-width:var(--fa-border-width,.08em);padding:var(--fa-border-padding,.2em .25em .15em)}.fa-pull-left{float:left;margin-right:var(--fa-pull-margin,.3em)}.fa-pull-right{float:right;margin-left:var(--fa-pull-margin,.3em)}.fa-beat{-webkit-animation-name:fa-beat;animation-name:fa-beat;-webkit-animation-delay:var(--fa-animation-delay,0);animation-delay:var(--fa-animation-delay,0);-webkit-animation-direction:var(--fa-animation-direction,normal);animation-direction:var(--fa-animation-direction,normal);-webkit-animation-duration:var(--fa-animation-duration,1s);animation-duration:var(--fa-animation-duration,1s);-webkit-animation-iteration-count:var(--fa-animation-iteration-count,infinite);animation-iteration-count:var(--fa-animation-iteration-count,infinite);-webkit-animation-timing-function:var(--fa-animation-timing,ease-in-out);animation-timing-function:var(--fa-animation-timing,ease-in-out)}.fa-bounce{-webkit-animation-name:fa-bounce;animation-name:fa-bounce;-webkit-animation-delay:var(--fa-animation-delay,0);animation-delay:var(--fa-animation-delay,0);-webkit-animation-direction:var(--fa-animation-direction,normal);animation-direction:var(--fa-animation-direction,normal);-webkit-animation-duration:var(--fa-animation-duration,1s);animation-duration:var(--fa-animation-duration,1s);-webkit-animation-iteration-count:var(--fa-animation-iteration-count,infinite);animation-iteration-count:var(--fa-animation-iteration-count,infinite);-webkit-animation-timing-function:var(--fa-animation-timing,cubic-bezier(.28,.84,.42,1));animation-timing-function:var(--fa-animation-timing,cubic-bezier(.28,.84,.42,1))}.fa-fade{-webkit-animation-name:fa-fade;animation-name:fa-fade;-webkit-animation-delay:var(--fa-animation-delay,0);animation-delay:var(--fa-animation-delay,0);-webkit-animation-direction:var(--fa-animation-direction,normal);animation-direction:var(--fa-animation-direction,normal);-webkit-animation-duration:var(--fa-animation-duration,1s);animation-duration:var(--fa-animation-duration,1s);-webkit-animation-iteration-count:var(--fa-animation-iteration-count,infinite);animation-iteration-count:var(--fa-animation-iteration-count,infinite);-webkit-animation-timing-function:var(--fa-animation-timing,cubic-bezier(.4,0,.6,1));animation-timing-function:var(--fa-animation-timing,cubic-bezier(.4,0,.6,1))}.fa-beat-fade{-webkit-animation-name:fa-beat-fade;animation-name:fa-beat-fade;-webkit-animation-delay:var(--fa-animation-delay,0);animation-delay:var(--fa-animation-delay,0);-webkit-animation-direction:var(--fa-animation-direction,normal);animation-direction:var(--fa-animation-direction,normal);-webkit-animation-duration:var(--fa-animation-duration,1s);animation-duration:var(--fa-animation-duration,1s);-webkit-animation-iteration-count:var(--fa-animation-iteration-count,infinite);animation-iteration-count:var(--fa-animation-iteration-count,infinite);-webkit-animation-timing-function:var(--fa-animation-timing,cubic-bezier(.4,0,.6,1));animation-timing-function:var(--fa-animation-timing,cubic-bezier(.4,0,.6,1))}.fa-flip{-webkit-animation-name:fa-flip;animation-name:fa-flip;-webkit-animation-delay:var(--fa-animation-delay,0);animation-delay:var(--fa-animation-delay,0);-webkit-animation-direction:var(--fa-animation-direction,normal);animation-direction:var(--fa-animation-direction,normal);-webkit-animation-duration:var(--fa-animation-duration,1s);animation-duration:var(--fa-animation-duration,1s);-webkit-animation-iteration-count:var(--fa-animation-iteration-count,infinite);animation-iteration-count:var(--fa-animation-iteration-count,infinite);-webkit-animation-timing-function:var(--fa-animation-timing,ease-in-out);animation-timing-function:var(--fa-animation-timing,ease-in-out)}.fa-shake{-webkit-animation-name:fa-shake;animation-name:fa-shake;-webkit-animation-delay:var(--fa-animation-delay,0);animation-delay:var(--fa-animation-delay,0);-webkit-animation-direction:var(--fa-animation-direction,normal);animation-direction:var(--fa-animation-direction,normal);-webkit-animation-duration:var(--fa-animation-duration,1s);animation-duration:var(--fa-animation-duration,1s);-webkit-animation-iteration-count:var(--fa-animation-iteration-count,infinite);animation-iteration-count:var(--fa-animation-iteration-count,infinite);-webkit-animation-timing-function:var(--fa-animation-timing,linear);animation-timing-function:var(--fa-animation-timing,linear)}.fa-spin{-webkit-animation-name:fa-spin;animation-name:fa-spin;-webkit-animation-delay:var(--fa-animation-delay,0);animation-delay:var(--fa-animation-delay,0);-webkit-animation-direction:var(--fa-animation-direction,normal);animation-direction:var(--fa-animation-direction,normal);-webkit-animation-duration:var(--fa-animation-duration,2s);animation-duration:var(--fa-animation-duration,2s);-webkit-animation-iteration-count:var(--fa-animation-iteration-count,infinite);animation-iteration-count:var(--fa-animation-iteration-count,infinite);-webkit-animation-timing-function:var(--fa-animation-timing,linear);animation-timing-function:var(--fa-animation-timing,linear)}.fa-spin-reverse{--fa-animation-direction:reverse}.fa-pulse,.fa-spin-pulse{-webkit-animation-name:fa-spin;animation-name:fa-spin;-webkit-animation-direction:var(--fa-animation-direction,normal);animation-direction:var(--fa-animation-direction,normal);-webkit-animation-duration:var(--fa-animation-duration,1s);animation-duration:var(--fa-animation-duration,1s);-webkit-animation-iteration-count:var(--fa-animation-iteration-count,infinite);animation-iteration-count:var(--fa-animation-iteration-count,infinite);-webkit-animation-timing-function:var(--fa-animation-timing,steps(8));animation-timing-function:var(--fa-animation-timing,steps(8))}@media (prefers-reduced-motion:reduce){.fa-beat,.fa-beat-fade,.fa-bounce,.fa-fade,.fa-flip,.fa-pulse,.fa-shake,.fa-spin,.fa-spin-pulse{-webkit-animation-delay:-1ms;animation-delay:-1ms;-webkit-animation-duration:1ms;animation-duration:1ms;-webkit-animation-iteration-count:1;animation-iteration-count:1;transition-delay:0s;transition-duration:0s}}@-webkit-keyframes fa-beat{0%,90%{-webkit-transform:scale(1);transform:scale(1)}45%{-webkit-transform:scale(var(--fa-beat-scale,1.25));transform:scale(var(--fa-beat-scale,1.25))}}@keyframes fa-beat{0%,90%{-webkit-transform:scale(1);transform:scale(1)}45%{-webkit-transform:scale(var(--fa-beat-scale,1.25));transform:scale(var(--fa-beat-scale,1.25))}}@-webkit-keyframes fa-bounce{0%{-webkit-transform:scale(1,1) translateY(0);transform:scale(1,1) translateY(0)}10%{-webkit-transform:scale(var(--fa-bounce-start-scale-x,1.1),var(--fa-bounce-start-scale-y,.9)) translateY(0);transform:scale(var(--fa-bounce-start-scale-x,1.1),var(--fa-bounce-start-scale-y,.9)) translateY(0)}30%{-webkit-transform:scale(var(--fa-bounce-jump-scale-x,.9),var(--fa-bounce-jump-scale-y,1.1)) translateY(var(--fa-bounce-height,-.5em));transform:scale(var(--fa-bounce-jump-scale-x,.9),var(--fa-bounce-jump-scale-y,1.1)) translateY(var(--fa-bounce-height,-.5em))}50%{-webkit-transform:scale(var(--fa-bounce-land-scale-x,1.05),var(--fa-bounce-land-scale-y,.95)) translateY(0);transform:scale(var(--fa-bounce-land-scale-x,1.05),var(--fa-bounce-land-scale-y,.95)) translateY(0)}57%{-webkit-transform:scale(1,1) translateY(var(--fa-bounce-rebound,-.125em));transform:scale(1,1) translateY(var(--fa-bounce-rebound,-.125em))}64%{-webkit-transform:scale(1,1) translateY(0);transform:scale(1,1) translateY(0)}100%{-webkit-transform:scale(1,1) translateY(0);transform:scale(1,1) translateY(0)}}@keyframes fa-bounce{0%{-webkit-transform:scale(1,1) translateY(0);transform:scale(1,1) translateY(0)}10%{-webkit-transform:scale(var(--fa-bounce-start-scale-x,1.1),var(--fa-bounce-start-scale-y,.9)) translateY(0);transform:scale(var(--fa-bounce-start-scale-x,1.1),var(--fa-bounce-start-scale-y,.9)) translateY(0)}30%{-webkit-transform:scale(var(--fa-bounce-jump-scale-x,.9),var(--fa-bounce-jump-scale-y,1.1)) translateY(var(--fa-bounce-height,-.5em));transform:scale(var(--fa-bounce-jump-scale-x,.9),var(--fa-bounce-jump-scale-y,1.1)) translateY(var(--fa-bounce-height,-.5em))}50%{-webkit-transform:scale(var(--fa-bounce-land-scale-x,1.05),var(--fa-bounce-land-scale-y,.95)) translateY(0);transform:scale(var(--fa-bounce-land-scale-x,1.05),var(--fa-bounce-land-scale-y,.95)) translateY(0)}57%{-webkit-transform:scale(1,1) translateY(var(--fa-bounce-rebound,-.125em));transform:scale(1,1) translateY(var(--fa-bounce-rebound,-.125em))}64%{-webkit-transform:scale(1,1) translateY(0);transform:scale(1,1) translateY(0)}100%{-webkit-transform:scale(1,1) translateY(0);transform:scale(1,1) translateY(0)}}@-webkit-keyframes fa-fade{50%{opacity:var(--fa-fade-opacity,.4)}}@keyframes fa-fade{50%{opacity:var(--fa-fade-opacity,.4)}}@-webkit-keyframes fa-beat-fade{0%,100%{opacity:var(--fa-beat-fade-opacity,.4);-webkit-transform:scale(1);transform:scale(1)}50%{opacity:1;-webkit-transform:scale(var(--fa-beat-fade-scale,1.125));transform:scale(var(--fa-beat-fade-scale,1.125))}}@keyframes fa-beat-fade{0%,100%{opacity:var(--fa-beat-fade-opacity,.4);-webkit-transform:scale(1);transform:scale(1)}50%{opacity:1;-webkit-transform:scale(var(--fa-beat-fade-scale,1.125));transform:scale(var(--fa-beat-fade-scale,1.125))}}@-webkit-keyframes fa-flip{50%{-webkit-transform:rotate3d(var(--fa-flip-x,0),var(--fa-flip-y,1),var(--fa-flip-z,0),var(--fa-flip-angle,-180deg));transform:rotate3d(var(--fa-flip-x,0),var(--fa-flip-y,1),var(--fa-flip-z,0),var(--fa-flip-angle,-180deg))}}@keyframes fa-flip{50%{-webkit-transform:rotate3d(var(--fa-flip-x,0),var(--fa-flip-y,1),var(--fa-flip-z,0),var(--fa-flip-angle,-180deg));transform:rotate3d(var(--fa-flip-x,0),var(--fa-flip-y,1),var(--fa-flip-z,0),var(--fa-flip-angle,-180deg))}}@-webkit-keyframes fa-shake{0%{-webkit-transform:rotate(-15deg);transform:rotate(-15deg)}4%{-webkit-transform:rotate(15deg);transform:rotate(15deg)}24%,8%{-webkit-transform:rotate(-18deg);transform:rotate(-18deg)}12%,28%{-webkit-transform:rotate(18deg);transform:rotate(18deg)}16%{-webkit-transform:rotate(-22deg);transform:rotate(-22deg)}20%{-webkit-transform:rotate(22deg);transform:rotate(22deg)}32%{-webkit-transform:rotate(-12deg);transform:rotate(-12deg)}36%{-webkit-transform:rotate(12deg);transform:rotate(12deg)}100%,40%{-webkit-transform:rotate(0);transform:rotate(0)}}@keyframes fa-shake{0%{-webkit-transform:rotate(-15deg);transform:rotate(-15deg)}4%{-webkit-transform:rotate(15deg);transform:rotate(15deg)}24%,8%{-webkit-transform:rotate(-18deg);transform:rotate(-18deg)}12%,28%{-webkit-transform:rotate(18deg);transform:rotate(18deg)}16%{-webkit-transform:rotate(-22deg);transform:rotate(-22deg)}20%{-webkit-transform:rotate(22deg);transform:rotate(22deg)}32%{-webkit-transform:rotate(-12deg);transform:rotate(-12deg)}36%{-webkit-transform:rotate(12deg);transform:rotate(12deg)}100%,40%{-webkit-transform:rotate(0);transform:rotate(0)}}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0);transform:rotate(0)}100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0);transform:rotate(0)}100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}.fa-rotate-90{-webkit-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-webkit-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-webkit-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-webkit-transform:scale(-1,1);transform:scale(-1,1)}.fa-flip-vertical{-webkit-transform:scale(1,-1);transform:scale(1,-1)}.fa-flip-both,.fa-flip-horizontal.fa-flip-vertical{-webkit-transform:scale(-1,-1);transform:scale(-1,-1)}.fa-rotate-by{-webkit-transform:rotate(var(--fa-rotate-angle,none));transform:rotate(var(--fa-rotate-angle,none))}.fa-stack{display:inline-block;vertical-align:middle;height:2em;position:relative;width:2.5em}.fa-stack-1x,.fa-stack-2x{bottom:0;left:0;margin:auto;position:absolute;right:0;top:0;z-index:var(--fa-stack-z-index,auto)}.svg-inline--fa.fa-stack-1x{height:1em;width:1.25em}.svg-inline--fa.fa-stack-2x{height:2em;width:2.5em}.fa-inverse{color:var(--fa-inverse,#fff)}.fa-sr-only,.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border-width:0}.fa-sr-only-focusable:not(:focus),.sr-only-focusable:not(:focus){position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border-width:0}.svg-inline--fa .fa-primary{fill:var(--fa-primary-color,currentColor);opacity:var(--fa-primary-opacity,1)}.svg-inline--fa .fa-secondary{fill:var(--fa-secondary-color,currentColor);opacity:var(--fa-secondary-opacity,.4)}.svg-inline--fa.fa-swap-opacity .fa-primary{opacity:var(--fa-secondary-opacity,.4)}.svg-inline--fa.fa-swap-opacity .fa-secondary{opacity:var(--fa-primary-opacity,1)}.svg-inline--fa mask .fa-primary,.svg-inline--fa mask .fa-secondary{fill:#000}.fa-duotone.fa-inverse,.fad.fa-inverse{color:var(--fa-inverse,#fff)}';return"fa"===z&&a===l||(C=new RegExp("\\.".concat("fa","\\-"),"g"),c=new RegExp("\\--".concat("fa","\\-"),"g"),l=new RegExp("\\.".concat(l),"g"),e=e.replace(C,".".concat(z,"-")).replace(c,"--".concat(z,"-")).replace(l,".".concat(a))),e}var z1=!1;function a1(){U.autoAddCss&&!z1&&(function(C){if(C&&o){var c=v.createElement("style");c.setAttribute("type","text/css"),c.innerHTML=C;for(var l=v.head.childNodes,z=null,a=l.length-1;-1").concat(z.map(r1).join(""),"")}function L1(C,c,l){if(C&&C[c]&&C[c][l])return{prefix:c,iconName:l,icon:C[c][l]}}o&&((s1=(v.documentElement.doScroll?/^loaded|^c/:/^loaded|^i|^c/).test(v.readyState))||v.addEventListener("DOMContentLoaded",e1));function n1(C,c,l,z){for(var a,e,M=Object.keys(C),t=M.length,s=void 0!==z?H1(c,z):c,h=void 0===l?(a=1,C[M[0]]):(a=0,l);a=4)throw new Error("Bootstrap's JavaScript requires at least jQuery v1.9.1 but less than v4.0.0")}};l.jQueryDetection(),i.default.fn.emulateTransitionEnd=s,i.default.event.special[l.TRANSITION_END]={bindType:"transitionend",delegateType:"transitionend",handle:function(t){if(i.default(t.target).is(this))return t.handleObj.handler.apply(this,arguments)}};var u="alert",f=i.default.fn[u],d=function(){function t(t){this._element=t}var e=t.prototype;return e.close=function(t){var e=this._element;t&&(e=this._getRootElement(t)),this._triggerCloseEvent(e).isDefaultPrevented()||this._removeElement(e)},e.dispose=function(){i.default.removeData(this._element,"bs.alert"),this._element=null},e._getRootElement=function(t){var e=l.getSelectorFromElement(t),n=!1;return e&&(n=document.querySelector(e)),n||(n=i.default(t).closest(".alert")[0]),n},e._triggerCloseEvent=function(t){var e=i.default.Event("close.bs.alert");return i.default(t).trigger(e),e},e._removeElement=function(t){var e=this;if(i.default(t).removeClass("show"),i.default(t).hasClass("fade")){var n=l.getTransitionDurationFromElement(t);i.default(t).one(l.TRANSITION_END,(function(n){return e._destroyElement(t,n)})).emulateTransitionEnd(n)}else this._destroyElement(t)},e._destroyElement=function(t){i.default(t).detach().trigger("closed.bs.alert").remove()},t._jQueryInterface=function(e){return this.each((function(){var n=i.default(this),o=n.data("bs.alert");o||(o=new t(this),n.data("bs.alert",o)),"close"===e&&o[e](this)}))},t._handleDismiss=function(t){return function(e){e&&e.preventDefault(),t.close(this)}},r(t,null,[{key:"VERSION",get:function(){return"4.6.0"}}]),t}();i.default(document).on("click.bs.alert.data-api",'[data-dismiss="alert"]',d._handleDismiss(new d)),i.default.fn[u]=d._jQueryInterface,i.default.fn[u].Constructor=d,i.default.fn[u].noConflict=function(){return i.default.fn[u]=f,d._jQueryInterface};var c=i.default.fn.button,h=function(){function t(t){this._element=t,this.shouldAvoidTriggerChange=!1}var e=t.prototype;return e.toggle=function(){var t=!0,e=!0,n=i.default(this._element).closest('[data-toggle="buttons"]')[0];if(n){var o=this._element.querySelector('input:not([type="hidden"])');if(o){if("radio"===o.type)if(o.checked&&this._element.classList.contains("active"))t=!1;else{var r=n.querySelector(".active");r&&i.default(r).removeClass("active")}t&&("checkbox"!==o.type&&"radio"!==o.type||(o.checked=!this._element.classList.contains("active")),this.shouldAvoidTriggerChange||i.default(o).trigger("change")),o.focus(),e=!1}}this._element.hasAttribute("disabled")||this._element.classList.contains("disabled")||(e&&this._element.setAttribute("aria-pressed",!this._element.classList.contains("active")),t&&i.default(this._element).toggleClass("active"))},e.dispose=function(){i.default.removeData(this._element,"bs.button"),this._element=null},t._jQueryInterface=function(e,n){return this.each((function(){var o=i.default(this),r=o.data("bs.button");r||(r=new t(this),o.data("bs.button",r)),r.shouldAvoidTriggerChange=n,"toggle"===e&&r[e]()}))},r(t,null,[{key:"VERSION",get:function(){return"4.6.0"}}]),t}();i.default(document).on("click.bs.button.data-api",'[data-toggle^="button"]',(function(t){var e=t.target,n=e;if(i.default(e).hasClass("btn")||(e=i.default(e).closest(".btn")[0]),!e||e.hasAttribute("disabled")||e.classList.contains("disabled"))t.preventDefault();else{var o=e.querySelector('input:not([type="hidden"])');if(o&&(o.hasAttribute("disabled")||o.classList.contains("disabled")))return void t.preventDefault();"INPUT"!==n.tagName&&"LABEL"===e.tagName||h._jQueryInterface.call(i.default(e),"toggle","INPUT"===n.tagName)}})).on("focus.bs.button.data-api blur.bs.button.data-api",'[data-toggle^="button"]',(function(t){var e=i.default(t.target).closest(".btn")[0];i.default(e).toggleClass("focus",/^focus(in)?$/.test(t.type))})),i.default(window).on("load.bs.button.data-api",(function(){for(var t=[].slice.call(document.querySelectorAll('[data-toggle="buttons"] .btn')),e=0,n=t.length;e0,this._pointerEvent=Boolean(window.PointerEvent||window.MSPointerEvent),this._addEventListeners()}var e=t.prototype;return e.next=function(){this._isSliding||this._slide("next")},e.nextWhenVisible=function(){var t=i.default(this._element);!document.hidden&&t.is(":visible")&&"hidden"!==t.css("visibility")&&this.next()},e.prev=function(){this._isSliding||this._slide("prev")},e.pause=function(t){t||(this._isPaused=!0),this._element.querySelector(".carousel-item-next, .carousel-item-prev")&&(l.triggerTransitionEnd(this._element),this.cycle(!0)),clearInterval(this._interval),this._interval=null},e.cycle=function(t){t||(this._isPaused=!1),this._interval&&(clearInterval(this._interval),this._interval=null),this._config.interval&&!this._isPaused&&(this._updateInterval(),this._interval=setInterval((document.visibilityState?this.nextWhenVisible:this.next).bind(this),this._config.interval))},e.to=function(t){var e=this;this._activeElement=this._element.querySelector(".active.carousel-item");var n=this._getItemIndex(this._activeElement);if(!(t>this._items.length-1||t<0))if(this._isSliding)i.default(this._element).one("slid.bs.carousel",(function(){return e.to(t)}));else{if(n===t)return this.pause(),void this.cycle();var o=t>n?"next":"prev";this._slide(o,this._items[t])}},e.dispose=function(){i.default(this._element).off(m),i.default.removeData(this._element,"bs.carousel"),this._items=null,this._config=null,this._element=null,this._interval=null,this._isPaused=null,this._isSliding=null,this._activeElement=null,this._indicatorsElement=null},e._getConfig=function(t){return t=a({},v,t),l.typeCheckConfig(p,t,_),t},e._handleSwipe=function(){var t=Math.abs(this.touchDeltaX);if(!(t<=40)){var e=t/this.touchDeltaX;this.touchDeltaX=0,e>0&&this.prev(),e<0&&this.next()}},e._addEventListeners=function(){var t=this;this._config.keyboard&&i.default(this._element).on("keydown.bs.carousel",(function(e){return t._keydown(e)})),"hover"===this._config.pause&&i.default(this._element).on("mouseenter.bs.carousel",(function(e){return t.pause(e)})).on("mouseleave.bs.carousel",(function(e){return t.cycle(e)})),this._config.touch&&this._addTouchEventListeners()},e._addTouchEventListeners=function(){var t=this;if(this._touchSupported){var e=function(e){t._pointerEvent&&b[e.originalEvent.pointerType.toUpperCase()]?t.touchStartX=e.originalEvent.clientX:t._pointerEvent||(t.touchStartX=e.originalEvent.touches[0].clientX)},n=function(e){t._pointerEvent&&b[e.originalEvent.pointerType.toUpperCase()]&&(t.touchDeltaX=e.originalEvent.clientX-t.touchStartX),t._handleSwipe(),"hover"===t._config.pause&&(t.pause(),t.touchTimeout&&clearTimeout(t.touchTimeout),t.touchTimeout=setTimeout((function(e){return t.cycle(e)}),500+t._config.interval))};i.default(this._element.querySelectorAll(".carousel-item img")).on("dragstart.bs.carousel",(function(t){return t.preventDefault()})),this._pointerEvent?(i.default(this._element).on("pointerdown.bs.carousel",(function(t){return e(t)})),i.default(this._element).on("pointerup.bs.carousel",(function(t){return n(t)})),this._element.classList.add("pointer-event")):(i.default(this._element).on("touchstart.bs.carousel",(function(t){return e(t)})),i.default(this._element).on("touchmove.bs.carousel",(function(e){return function(e){e.originalEvent.touches&&e.originalEvent.touches.length>1?t.touchDeltaX=0:t.touchDeltaX=e.originalEvent.touches[0].clientX-t.touchStartX}(e)})),i.default(this._element).on("touchend.bs.carousel",(function(t){return n(t)})))}},e._keydown=function(t){if(!/input|textarea/i.test(t.target.tagName))switch(t.which){case 37:t.preventDefault(),this.prev();break;case 39:t.preventDefault(),this.next()}},e._getItemIndex=function(t){return this._items=t&&t.parentNode?[].slice.call(t.parentNode.querySelectorAll(".carousel-item")):[],this._items.indexOf(t)},e._getItemByDirection=function(t,e){var n="next"===t,i="prev"===t,o=this._getItemIndex(e),r=this._items.length-1;if((i&&0===o||n&&o===r)&&!this._config.wrap)return e;var a=(o+("prev"===t?-1:1))%this._items.length;return-1===a?this._items[this._items.length-1]:this._items[a]},e._triggerSlideEvent=function(t,e){var n=this._getItemIndex(t),o=this._getItemIndex(this._element.querySelector(".active.carousel-item")),r=i.default.Event("slide.bs.carousel",{relatedTarget:t,direction:e,from:o,to:n});return i.default(this._element).trigger(r),r},e._setActiveIndicatorElement=function(t){if(this._indicatorsElement){var e=[].slice.call(this._indicatorsElement.querySelectorAll(".active"));i.default(e).removeClass("active");var n=this._indicatorsElement.children[this._getItemIndex(t)];n&&i.default(n).addClass("active")}},e._updateInterval=function(){var t=this._activeElement||this._element.querySelector(".active.carousel-item");if(t){var e=parseInt(t.getAttribute("data-interval"),10);e?(this._config.defaultInterval=this._config.defaultInterval||this._config.interval,this._config.interval=e):this._config.interval=this._config.defaultInterval||this._config.interval}},e._slide=function(t,e){var n,o,r,a=this,s=this._element.querySelector(".active.carousel-item"),u=this._getItemIndex(s),f=e||s&&this._getItemByDirection(t,s),d=this._getItemIndex(f),c=Boolean(this._interval);if("next"===t?(n="carousel-item-left",o="carousel-item-next",r="left"):(n="carousel-item-right",o="carousel-item-prev",r="right"),f&&i.default(f).hasClass("active"))this._isSliding=!1;else if(!this._triggerSlideEvent(f,r).isDefaultPrevented()&&s&&f){this._isSliding=!0,c&&this.pause(),this._setActiveIndicatorElement(f),this._activeElement=f;var h=i.default.Event("slid.bs.carousel",{relatedTarget:f,direction:r,from:u,to:d});if(i.default(this._element).hasClass("slide")){i.default(f).addClass(o),l.reflow(f),i.default(s).addClass(n),i.default(f).addClass(n);var p=l.getTransitionDurationFromElement(s);i.default(s).one(l.TRANSITION_END,(function(){i.default(f).removeClass(n+" "+o).addClass("active"),i.default(s).removeClass("active "+o+" "+n),a._isSliding=!1,setTimeout((function(){return i.default(a._element).trigger(h)}),0)})).emulateTransitionEnd(p)}else i.default(s).removeClass("active"),i.default(f).addClass("active"),this._isSliding=!1,i.default(this._element).trigger(h);c&&this.cycle()}},t._jQueryInterface=function(e){return this.each((function(){var n=i.default(this).data("bs.carousel"),o=a({},v,i.default(this).data());"object"==typeof e&&(o=a({},o,e));var r="string"==typeof e?e:o.slide;if(n||(n=new t(this,o),i.default(this).data("bs.carousel",n)),"number"==typeof e)n.to(e);else if("string"==typeof r){if("undefined"==typeof n[r])throw new TypeError('No method named "'+r+'"');n[r]()}else o.interval&&o.ride&&(n.pause(),n.cycle())}))},t._dataApiClickHandler=function(e){var n=l.getSelectorFromElement(this);if(n){var o=i.default(n)[0];if(o&&i.default(o).hasClass("carousel")){var r=a({},i.default(o).data(),i.default(this).data()),s=this.getAttribute("data-slide-to");s&&(r.interval=!1),t._jQueryInterface.call(i.default(o),r),s&&i.default(o).data("bs.carousel").to(s),e.preventDefault()}}},r(t,null,[{key:"VERSION",get:function(){return"4.6.0"}},{key:"Default",get:function(){return v}}]),t}();i.default(document).on("click.bs.carousel.data-api","[data-slide], [data-slide-to]",y._dataApiClickHandler),i.default(window).on("load.bs.carousel.data-api",(function(){for(var t=[].slice.call(document.querySelectorAll('[data-ride="carousel"]')),e=0,n=t.length;e0&&(this._selector=a,this._triggerArray.push(r))}this._parent=this._config.parent?this._getParent():null,this._config.parent||this._addAriaAndCollapsedClass(this._element,this._triggerArray),this._config.toggle&&this.toggle()}var e=t.prototype;return e.toggle=function(){i.default(this._element).hasClass("show")?this.hide():this.show()},e.show=function(){var e,n,o=this;if(!this._isTransitioning&&!i.default(this._element).hasClass("show")&&(this._parent&&0===(e=[].slice.call(this._parent.querySelectorAll(".show, .collapsing")).filter((function(t){return"string"==typeof o._config.parent?t.getAttribute("data-parent")===o._config.parent:t.classList.contains("collapse")}))).length&&(e=null),!(e&&(n=i.default(e).not(this._selector).data("bs.collapse"))&&n._isTransitioning))){var r=i.default.Event("show.bs.collapse");if(i.default(this._element).trigger(r),!r.isDefaultPrevented()){e&&(t._jQueryInterface.call(i.default(e).not(this._selector),"hide"),n||i.default(e).data("bs.collapse",null));var a=this._getDimension();i.default(this._element).removeClass("collapse").addClass("collapsing"),this._element.style[a]=0,this._triggerArray.length&&i.default(this._triggerArray).removeClass("collapsed").attr("aria-expanded",!0),this.setTransitioning(!0);var s="scroll"+(a[0].toUpperCase()+a.slice(1)),u=l.getTransitionDurationFromElement(this._element);i.default(this._element).one(l.TRANSITION_END,(function(){i.default(o._element).removeClass("collapsing").addClass("collapse show"),o._element.style[a]="",o.setTransitioning(!1),i.default(o._element).trigger("shown.bs.collapse")})).emulateTransitionEnd(u),this._element.style[a]=this._element[s]+"px"}}},e.hide=function(){var t=this;if(!this._isTransitioning&&i.default(this._element).hasClass("show")){var e=i.default.Event("hide.bs.collapse");if(i.default(this._element).trigger(e),!e.isDefaultPrevented()){var n=this._getDimension();this._element.style[n]=this._element.getBoundingClientRect()[n]+"px",l.reflow(this._element),i.default(this._element).addClass("collapsing").removeClass("collapse show");var o=this._triggerArray.length;if(o>0)for(var r=0;r=0)return 1;return 0}();var k=D&&window.Promise?function(t){var e=!1;return function(){e||(e=!0,window.Promise.resolve().then((function(){e=!1,t()})))}}:function(t){var e=!1;return function(){e||(e=!0,setTimeout((function(){e=!1,t()}),N))}};function A(t){return t&&"[object Function]"==={}.toString.call(t)}function I(t,e){if(1!==t.nodeType)return[];var n=t.ownerDocument.defaultView.getComputedStyle(t,null);return e?n[e]:n}function O(t){return"HTML"===t.nodeName?t:t.parentNode||t.host}function x(t){if(!t)return document.body;switch(t.nodeName){case"HTML":case"BODY":return t.ownerDocument.body;case"#document":return t.body}var e=I(t),n=e.overflow,i=e.overflowX,o=e.overflowY;return/(auto|scroll|overlay)/.test(n+o+i)?t:x(O(t))}function j(t){return t&&t.referenceNode?t.referenceNode:t}var L=D&&!(!window.MSInputMethodContext||!document.documentMode),P=D&&/MSIE 10/.test(navigator.userAgent);function F(t){return 11===t?L:10===t?P:L||P}function R(t){if(!t)return document.documentElement;for(var e=F(10)?document.body:null,n=t.offsetParent||null;n===e&&t.nextElementSibling;)n=(t=t.nextElementSibling).offsetParent;var i=n&&n.nodeName;return i&&"BODY"!==i&&"HTML"!==i?-1!==["TH","TD","TABLE"].indexOf(n.nodeName)&&"static"===I(n,"position")?R(n):n:t?t.ownerDocument.documentElement:document.documentElement}function H(t){return null!==t.parentNode?H(t.parentNode):t}function M(t,e){if(!(t&&t.nodeType&&e&&e.nodeType))return document.documentElement;var n=t.compareDocumentPosition(e)&Node.DOCUMENT_POSITION_FOLLOWING,i=n?t:e,o=n?e:t,r=document.createRange();r.setStart(i,0),r.setEnd(o,0);var a,s,l=r.commonAncestorContainer;if(t!==l&&e!==l||i.contains(o))return"BODY"===(s=(a=l).nodeName)||"HTML"!==s&&R(a.firstElementChild)!==a?R(l):l;var u=H(t);return u.host?M(u.host,e):M(t,H(e).host)}function q(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"top",n="top"===e?"scrollTop":"scrollLeft",i=t.nodeName;if("BODY"===i||"HTML"===i){var o=t.ownerDocument.documentElement,r=t.ownerDocument.scrollingElement||o;return r[n]}return t[n]}function B(t,e){var n=arguments.length>2&&void 0!==arguments[2]&&arguments[2],i=q(e,"top"),o=q(e,"left"),r=n?-1:1;return t.top+=i*r,t.bottom+=i*r,t.left+=o*r,t.right+=o*r,t}function Q(t,e){var n="x"===e?"Left":"Top",i="Left"===n?"Right":"Bottom";return parseFloat(t["border"+n+"Width"])+parseFloat(t["border"+i+"Width"])}function W(t,e,n,i){return Math.max(e["offset"+t],e["scroll"+t],n["client"+t],n["offset"+t],n["scroll"+t],F(10)?parseInt(n["offset"+t])+parseInt(i["margin"+("Height"===t?"Top":"Left")])+parseInt(i["margin"+("Height"===t?"Bottom":"Right")]):0)}function U(t){var e=t.body,n=t.documentElement,i=F(10)&&getComputedStyle(n);return{height:W("Height",e,n,i),width:W("Width",e,n,i)}}var V=function(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")},Y=function(){function t(t,e){for(var n=0;n2&&void 0!==arguments[2]&&arguments[2],i=F(10),o="HTML"===e.nodeName,r=G(t),a=G(e),s=x(t),l=I(e),u=parseFloat(l.borderTopWidth),f=parseFloat(l.borderLeftWidth);n&&o&&(a.top=Math.max(a.top,0),a.left=Math.max(a.left,0));var d=K({top:r.top-a.top-u,left:r.left-a.left-f,width:r.width,height:r.height});if(d.marginTop=0,d.marginLeft=0,!i&&o){var c=parseFloat(l.marginTop),h=parseFloat(l.marginLeft);d.top-=u-c,d.bottom-=u-c,d.left-=f-h,d.right-=f-h,d.marginTop=c,d.marginLeft=h}return(i&&!n?e.contains(s):e===s&&"BODY"!==s.nodeName)&&(d=B(d,e)),d}function J(t){var e=arguments.length>1&&void 0!==arguments[1]&&arguments[1],n=t.ownerDocument.documentElement,i=$(t,n),o=Math.max(n.clientWidth,window.innerWidth||0),r=Math.max(n.clientHeight,window.innerHeight||0),a=e?0:q(n),s=e?0:q(n,"left"),l={top:a-i.top+i.marginTop,left:s-i.left+i.marginLeft,width:o,height:r};return K(l)}function Z(t){var e=t.nodeName;if("BODY"===e||"HTML"===e)return!1;if("fixed"===I(t,"position"))return!0;var n=O(t);return!!n&&Z(n)}function tt(t){if(!t||!t.parentElement||F())return document.documentElement;for(var e=t.parentElement;e&&"none"===I(e,"transform");)e=e.parentElement;return e||document.documentElement}function et(t,e,n,i){var o=arguments.length>4&&void 0!==arguments[4]&&arguments[4],r={top:0,left:0},a=o?tt(t):M(t,j(e));if("viewport"===i)r=J(a,o);else{var s=void 0;"scrollParent"===i?"BODY"===(s=x(O(e))).nodeName&&(s=t.ownerDocument.documentElement):s="window"===i?t.ownerDocument.documentElement:i;var l=$(s,a,o);if("HTML"!==s.nodeName||Z(a))r=l;else{var u=U(t.ownerDocument),f=u.height,d=u.width;r.top+=l.top-l.marginTop,r.bottom=f+l.top,r.left+=l.left-l.marginLeft,r.right=d+l.left}}var c="number"==typeof(n=n||0);return r.left+=c?n:n.left||0,r.top+=c?n:n.top||0,r.right-=c?n:n.right||0,r.bottom-=c?n:n.bottom||0,r}function nt(t){return t.width*t.height}function it(t,e,n,i,o){var r=arguments.length>5&&void 0!==arguments[5]?arguments[5]:0;if(-1===t.indexOf("auto"))return t;var a=et(n,i,r,o),s={top:{width:a.width,height:e.top-a.top},right:{width:a.right-e.right,height:a.height},bottom:{width:a.width,height:a.bottom-e.bottom},left:{width:e.left-a.left,height:a.height}},l=Object.keys(s).map((function(t){return X({key:t},s[t],{area:nt(s[t])})})).sort((function(t,e){return e.area-t.area})),u=l.filter((function(t){var e=t.width,i=t.height;return e>=n.clientWidth&&i>=n.clientHeight})),f=u.length>0?u[0].key:l[0].key,d=t.split("-")[1];return f+(d?"-"+d:"")}function ot(t,e,n){var i=arguments.length>3&&void 0!==arguments[3]?arguments[3]:null,o=i?tt(e):M(e,j(n));return $(n,o,i)}function rt(t){var e=t.ownerDocument.defaultView.getComputedStyle(t),n=parseFloat(e.marginTop||0)+parseFloat(e.marginBottom||0),i=parseFloat(e.marginLeft||0)+parseFloat(e.marginRight||0);return{width:t.offsetWidth+i,height:t.offsetHeight+n}}function at(t){var e={left:"right",right:"left",bottom:"top",top:"bottom"};return t.replace(/left|right|bottom|top/g,(function(t){return e[t]}))}function st(t,e,n){n=n.split("-")[0];var i=rt(t),o={width:i.width,height:i.height},r=-1!==["right","left"].indexOf(n),a=r?"top":"left",s=r?"left":"top",l=r?"height":"width",u=r?"width":"height";return o[a]=e[a]+e[l]/2-i[l]/2,o[s]=n===s?e[s]-i[u]:e[at(s)],o}function lt(t,e){return Array.prototype.find?t.find(e):t.filter(e)[0]}function ut(t,e,n){return(void 0===n?t:t.slice(0,function(t,e,n){if(Array.prototype.findIndex)return t.findIndex((function(t){return t[e]===n}));var i=lt(t,(function(t){return t[e]===n}));return t.indexOf(i)}(t,"name",n))).forEach((function(t){t.function&&console.warn("`modifier.function` is deprecated, use `modifier.fn`!");var n=t.function||t.fn;t.enabled&&A(n)&&(e.offsets.popper=K(e.offsets.popper),e.offsets.reference=K(e.offsets.reference),e=n(e,t))})),e}function ft(){if(!this.state.isDestroyed){var t={instance:this,styles:{},arrowStyles:{},attributes:{},flipped:!1,offsets:{}};t.offsets.reference=ot(this.state,this.popper,this.reference,this.options.positionFixed),t.placement=it(this.options.placement,t.offsets.reference,this.popper,this.reference,this.options.modifiers.flip.boundariesElement,this.options.modifiers.flip.padding),t.originalPlacement=t.placement,t.positionFixed=this.options.positionFixed,t.offsets.popper=st(this.popper,t.offsets.reference,t.placement),t.offsets.popper.position=this.options.positionFixed?"fixed":"absolute",t=ut(this.modifiers,t),this.state.isCreated?this.options.onUpdate(t):(this.state.isCreated=!0,this.options.onCreate(t))}}function dt(t,e){return t.some((function(t){var n=t.name;return t.enabled&&n===e}))}function ct(t){for(var e=[!1,"ms","Webkit","Moz","O"],n=t.charAt(0).toUpperCase()+t.slice(1),i=0;i1&&void 0!==arguments[1]&&arguments[1],n=Tt.indexOf(t),i=Tt.slice(n+1).concat(Tt.slice(0,n));return e?i.reverse():i}var St="flip",Dt="clockwise",Nt="counterclockwise";function kt(t,e,n,i){var o=[0,0],r=-1!==["right","left"].indexOf(i),a=t.split(/(\+|\-)/).map((function(t){return t.trim()})),s=a.indexOf(lt(a,(function(t){return-1!==t.search(/,|\s/)})));a[s]&&-1===a[s].indexOf(",")&&console.warn("Offsets separated by white space(s) are deprecated, use a comma (,) instead.");var l=/\s*,\s*|\s+/,u=-1!==s?[a.slice(0,s).concat([a[s].split(l)[0]]),[a[s].split(l)[1]].concat(a.slice(s+1))]:[a];return(u=u.map((function(t,i){var o=(1===i?!r:r)?"height":"width",a=!1;return t.reduce((function(t,e){return""===t[t.length-1]&&-1!==["+","-"].indexOf(e)?(t[t.length-1]=e,a=!0,t):a?(t[t.length-1]+=e,a=!1,t):t.concat(e)}),[]).map((function(t){return function(t,e,n,i){var o=t.match(/((?:\-|\+)?\d*\.?\d*)(.*)/),r=+o[1],a=o[2];if(!r)return t;if(0===a.indexOf("%")){var s=void 0;switch(a){case"%p":s=n;break;case"%":case"%r":default:s=i}return K(s)[e]/100*r}if("vh"===a||"vw"===a)return("vh"===a?Math.max(document.documentElement.clientHeight,window.innerHeight||0):Math.max(document.documentElement.clientWidth,window.innerWidth||0))/100*r;return r}(t,o,e,n)}))}))).forEach((function(t,e){t.forEach((function(n,i){_t(n)&&(o[e]+=n*("-"===t[i-1]?-1:1))}))})),o}var At={placement:"bottom",positionFixed:!1,eventsEnabled:!0,removeOnDestroy:!1,onCreate:function(){},onUpdate:function(){},modifiers:{shift:{order:100,enabled:!0,fn:function(t){var e=t.placement,n=e.split("-")[0],i=e.split("-")[1];if(i){var o=t.offsets,r=o.reference,a=o.popper,s=-1!==["bottom","top"].indexOf(n),l=s?"left":"top",u=s?"width":"height",f={start:z({},l,r[l]),end:z({},l,r[l]+r[u]-a[u])};t.offsets.popper=X({},a,f[i])}return t}},offset:{order:200,enabled:!0,fn:function(t,e){var n=e.offset,i=t.placement,o=t.offsets,r=o.popper,a=o.reference,s=i.split("-")[0],l=void 0;return l=_t(+n)?[+n,0]:kt(n,r,a,s),"left"===s?(r.top+=l[0],r.left-=l[1]):"right"===s?(r.top+=l[0],r.left+=l[1]):"top"===s?(r.left+=l[0],r.top-=l[1]):"bottom"===s&&(r.left+=l[0],r.top+=l[1]),t.popper=r,t},offset:0},preventOverflow:{order:300,enabled:!0,fn:function(t,e){var n=e.boundariesElement||R(t.instance.popper);t.instance.reference===n&&(n=R(n));var i=ct("transform"),o=t.instance.popper.style,r=o.top,a=o.left,s=o[i];o.top="",o.left="",o[i]="";var l=et(t.instance.popper,t.instance.reference,e.padding,n,t.positionFixed);o.top=r,o.left=a,o[i]=s,e.boundaries=l;var u=e.priority,f=t.offsets.popper,d={primary:function(t){var n=f[t];return f[t]l[t]&&!e.escapeWithReference&&(i=Math.min(f[n],l[t]-("right"===t?f.width:f.height))),z({},n,i)}};return u.forEach((function(t){var e=-1!==["left","top"].indexOf(t)?"primary":"secondary";f=X({},f,d[e](t))})),t.offsets.popper=f,t},priority:["left","right","top","bottom"],padding:5,boundariesElement:"scrollParent"},keepTogether:{order:400,enabled:!0,fn:function(t){var e=t.offsets,n=e.popper,i=e.reference,o=t.placement.split("-")[0],r=Math.floor,a=-1!==["top","bottom"].indexOf(o),s=a?"right":"bottom",l=a?"left":"top",u=a?"width":"height";return n[s]r(i[s])&&(t.offsets.popper[l]=r(i[s])),t}},arrow:{order:500,enabled:!0,fn:function(t,e){var n;if(!wt(t.instance.modifiers,"arrow","keepTogether"))return t;var i=e.element;if("string"==typeof i){if(!(i=t.instance.popper.querySelector(i)))return t}else if(!t.instance.popper.contains(i))return console.warn("WARNING: `arrow.element` must be child of its popper element!"),t;var o=t.placement.split("-")[0],r=t.offsets,a=r.popper,s=r.reference,l=-1!==["left","right"].indexOf(o),u=l?"height":"width",f=l?"Top":"Left",d=f.toLowerCase(),c=l?"left":"top",h=l?"bottom":"right",p=rt(i)[u];s[h]-pa[h]&&(t.offsets.popper[d]+=s[d]+p-a[h]),t.offsets.popper=K(t.offsets.popper);var m=s[d]+s[u]/2-p/2,g=I(t.instance.popper),v=parseFloat(g["margin"+f]),_=parseFloat(g["border"+f+"Width"]),b=m-t.offsets.popper[d]-v-_;return b=Math.max(Math.min(a[u]-p,b),0),t.arrowElement=i,t.offsets.arrow=(z(n={},d,Math.round(b)),z(n,c,""),n),t},element:"[x-arrow]"},flip:{order:600,enabled:!0,fn:function(t,e){if(dt(t.instance.modifiers,"inner"))return t;if(t.flipped&&t.placement===t.originalPlacement)return t;var n=et(t.instance.popper,t.instance.reference,e.padding,e.boundariesElement,t.positionFixed),i=t.placement.split("-")[0],o=at(i),r=t.placement.split("-")[1]||"",a=[];switch(e.behavior){case St:a=[i,o];break;case Dt:a=Ct(i);break;case Nt:a=Ct(i,!0);break;default:a=e.behavior}return a.forEach((function(s,l){if(i!==s||a.length===l+1)return t;i=t.placement.split("-")[0],o=at(i);var u=t.offsets.popper,f=t.offsets.reference,d=Math.floor,c="left"===i&&d(u.right)>d(f.left)||"right"===i&&d(u.left)d(f.top)||"bottom"===i&&d(u.top)d(n.right),m=d(u.top)d(n.bottom),v="left"===i&&h||"right"===i&&p||"top"===i&&m||"bottom"===i&&g,_=-1!==["top","bottom"].indexOf(i),b=!!e.flipVariations&&(_&&"start"===r&&h||_&&"end"===r&&p||!_&&"start"===r&&m||!_&&"end"===r&&g),y=!!e.flipVariationsByContent&&(_&&"start"===r&&p||_&&"end"===r&&h||!_&&"start"===r&&g||!_&&"end"===r&&m),w=b||y;(c||v||w)&&(t.flipped=!0,(c||v)&&(i=a[l+1]),w&&(r=function(t){return"end"===t?"start":"start"===t?"end":t}(r)),t.placement=i+(r?"-"+r:""),t.offsets.popper=X({},t.offsets.popper,st(t.instance.popper,t.offsets.reference,t.placement)),t=ut(t.instance.modifiers,t,"flip"))})),t},behavior:"flip",padding:5,boundariesElement:"viewport",flipVariations:!1,flipVariationsByContent:!1},inner:{order:700,enabled:!1,fn:function(t){var e=t.placement,n=e.split("-")[0],i=t.offsets,o=i.popper,r=i.reference,a=-1!==["left","right"].indexOf(n),s=-1===["top","left"].indexOf(n);return o[a?"left":"top"]=r[n]-(s?o[a?"width":"height"]:0),t.placement=at(e),t.offsets.popper=K(o),t}},hide:{order:800,enabled:!0,fn:function(t){if(!wt(t.instance.modifiers,"hide","preventOverflow"))return t;var e=t.offsets.reference,n=lt(t.instance.modifiers,(function(t){return"preventOverflow"===t.name})).boundaries;if(e.bottomn.right||e.top>n.bottom||e.right2&&void 0!==arguments[2]?arguments[2]:{};V(this,t),this.scheduleUpdate=function(){return requestAnimationFrame(i.update)},this.update=k(this.update.bind(this)),this.options=X({},t.Defaults,o),this.state={isDestroyed:!1,isCreated:!1,scrollParents:[]},this.reference=e&&e.jquery?e[0]:e,this.popper=n&&n.jquery?n[0]:n,this.options.modifiers={},Object.keys(X({},t.Defaults.modifiers,o.modifiers)).forEach((function(e){i.options.modifiers[e]=X({},t.Defaults.modifiers[e]||{},o.modifiers?o.modifiers[e]:{})})),this.modifiers=Object.keys(this.options.modifiers).map((function(t){return X({name:t},i.options.modifiers[t])})).sort((function(t,e){return t.order-e.order})),this.modifiers.forEach((function(t){t.enabled&&A(t.onLoad)&&t.onLoad(i.reference,i.popper,i.options,t,i.state)})),this.update();var r=this.options.eventsEnabled;r&&this.enableEventListeners(),this.state.eventsEnabled=r}return Y(t,[{key:"update",value:function(){return ft.call(this)}},{key:"destroy",value:function(){return ht.call(this)}},{key:"enableEventListeners",value:function(){return gt.call(this)}},{key:"disableEventListeners",value:function(){return vt.call(this)}}]),t}();It.Utils=("undefined"!=typeof window?window:global).PopperUtils,It.placements=Et,It.Defaults=At;var Ot="dropdown",xt=i.default.fn[Ot],jt=new RegExp("38|40|27"),Lt={offset:0,flip:!0,boundary:"scrollParent",reference:"toggle",display:"dynamic",popperConfig:null},Pt={offset:"(number|string|function)",flip:"boolean",boundary:"(string|element)",reference:"(string|element)",display:"string",popperConfig:"(null|object)"},Ft=function(){function t(t,e){this._element=t,this._popper=null,this._config=this._getConfig(e),this._menu=this._getMenuElement(),this._inNavbar=this._detectNavbar(),this._addEventListeners()}var e=t.prototype;return e.toggle=function(){if(!this._element.disabled&&!i.default(this._element).hasClass("disabled")){var e=i.default(this._menu).hasClass("show");t._clearMenus(),e||this.show(!0)}},e.show=function(e){if(void 0===e&&(e=!1),!(this._element.disabled||i.default(this._element).hasClass("disabled")||i.default(this._menu).hasClass("show"))){var n={relatedTarget:this._element},o=i.default.Event("show.bs.dropdown",n),r=t._getParentFromElement(this._element);if(i.default(r).trigger(o),!o.isDefaultPrevented()){if(!this._inNavbar&&e){if("undefined"==typeof It)throw new TypeError("Bootstrap's dropdowns require Popper (https://popper.js.org)");var a=this._element;"parent"===this._config.reference?a=r:l.isElement(this._config.reference)&&(a=this._config.reference,"undefined"!=typeof this._config.reference.jquery&&(a=this._config.reference[0])),"scrollParent"!==this._config.boundary&&i.default(r).addClass("position-static"),this._popper=new It(a,this._menu,this._getPopperConfig())}"ontouchstart"in document.documentElement&&0===i.default(r).closest(".navbar-nav").length&&i.default(document.body).children().on("mouseover",null,i.default.noop),this._element.focus(),this._element.setAttribute("aria-expanded",!0),i.default(this._menu).toggleClass("show"),i.default(r).toggleClass("show").trigger(i.default.Event("shown.bs.dropdown",n))}}},e.hide=function(){if(!this._element.disabled&&!i.default(this._element).hasClass("disabled")&&i.default(this._menu).hasClass("show")){var e={relatedTarget:this._element},n=i.default.Event("hide.bs.dropdown",e),o=t._getParentFromElement(this._element);i.default(o).trigger(n),n.isDefaultPrevented()||(this._popper&&this._popper.destroy(),i.default(this._menu).toggleClass("show"),i.default(o).toggleClass("show").trigger(i.default.Event("hidden.bs.dropdown",e)))}},e.dispose=function(){i.default.removeData(this._element,"bs.dropdown"),i.default(this._element).off(".bs.dropdown"),this._element=null,this._menu=null,null!==this._popper&&(this._popper.destroy(),this._popper=null)},e.update=function(){this._inNavbar=this._detectNavbar(),null!==this._popper&&this._popper.scheduleUpdate()},e._addEventListeners=function(){var t=this;i.default(this._element).on("click.bs.dropdown",(function(e){e.preventDefault(),e.stopPropagation(),t.toggle()}))},e._getConfig=function(t){return t=a({},this.constructor.Default,i.default(this._element).data(),t),l.typeCheckConfig(Ot,t,this.constructor.DefaultType),t},e._getMenuElement=function(){if(!this._menu){var e=t._getParentFromElement(this._element);e&&(this._menu=e.querySelector(".dropdown-menu"))}return this._menu},e._getPlacement=function(){var t=i.default(this._element.parentNode),e="bottom-start";return t.hasClass("dropup")?e=i.default(this._menu).hasClass("dropdown-menu-right")?"top-end":"top-start":t.hasClass("dropright")?e="right-start":t.hasClass("dropleft")?e="left-start":i.default(this._menu).hasClass("dropdown-menu-right")&&(e="bottom-end"),e},e._detectNavbar=function(){return i.default(this._element).closest(".navbar").length>0},e._getOffset=function(){var t=this,e={};return"function"==typeof this._config.offset?e.fn=function(e){return e.offsets=a({},e.offsets,t._config.offset(e.offsets,t._element)||{}),e}:e.offset=this._config.offset,e},e._getPopperConfig=function(){var t={placement:this._getPlacement(),modifiers:{offset:this._getOffset(),flip:{enabled:this._config.flip},preventOverflow:{boundariesElement:this._config.boundary}}};return"static"===this._config.display&&(t.modifiers.applyStyle={enabled:!1}),a({},t,this._config.popperConfig)},t._jQueryInterface=function(e){return this.each((function(){var n=i.default(this).data("bs.dropdown");if(n||(n=new t(this,"object"==typeof e?e:null),i.default(this).data("bs.dropdown",n)),"string"==typeof e){if("undefined"==typeof n[e])throw new TypeError('No method named "'+e+'"');n[e]()}}))},t._clearMenus=function(e){if(!e||3!==e.which&&("keyup"!==e.type||9===e.which))for(var n=[].slice.call(document.querySelectorAll('[data-toggle="dropdown"]')),o=0,r=n.length;o0&&a--,40===e.which&&adocument.documentElement.clientHeight;n||(this._element.style.overflowY="hidden"),this._element.classList.add("modal-static");var o=l.getTransitionDurationFromElement(this._dialog);i.default(this._element).off(l.TRANSITION_END),i.default(this._element).one(l.TRANSITION_END,(function(){t._element.classList.remove("modal-static"),n||i.default(t._element).one(l.TRANSITION_END,(function(){t._element.style.overflowY=""})).emulateTransitionEnd(t._element,o)})).emulateTransitionEnd(o),this._element.focus()}},e._showElement=function(t){var e=this,n=i.default(this._element).hasClass("fade"),o=this._dialog?this._dialog.querySelector(".modal-body"):null;this._element.parentNode&&this._element.parentNode.nodeType===Node.ELEMENT_NODE||document.body.appendChild(this._element),this._element.style.display="block",this._element.removeAttribute("aria-hidden"),this._element.setAttribute("aria-modal",!0),this._element.setAttribute("role","dialog"),i.default(this._dialog).hasClass("modal-dialog-scrollable")&&o?o.scrollTop=0:this._element.scrollTop=0,n&&l.reflow(this._element),i.default(this._element).addClass("show"),this._config.focus&&this._enforceFocus();var r=i.default.Event("shown.bs.modal",{relatedTarget:t}),a=function(){e._config.focus&&e._element.focus(),e._isTransitioning=!1,i.default(e._element).trigger(r)};if(n){var s=l.getTransitionDurationFromElement(this._dialog);i.default(this._dialog).one(l.TRANSITION_END,a).emulateTransitionEnd(s)}else a()},e._enforceFocus=function(){var t=this;i.default(document).off("focusin.bs.modal").on("focusin.bs.modal",(function(e){document!==e.target&&t._element!==e.target&&0===i.default(t._element).has(e.target).length&&t._element.focus()}))},e._setEscapeEvent=function(){var t=this;this._isShown?i.default(this._element).on("keydown.dismiss.bs.modal",(function(e){t._config.keyboard&&27===e.which?(e.preventDefault(),t.hide()):t._config.keyboard||27!==e.which||t._triggerBackdropTransition()})):this._isShown||i.default(this._element).off("keydown.dismiss.bs.modal")},e._setResizeEvent=function(){var t=this;this._isShown?i.default(window).on("resize.bs.modal",(function(e){return t.handleUpdate(e)})):i.default(window).off("resize.bs.modal")},e._hideModal=function(){var t=this;this._element.style.display="none",this._element.setAttribute("aria-hidden",!0),this._element.removeAttribute("aria-modal"),this._element.removeAttribute("role"),this._isTransitioning=!1,this._showBackdrop((function(){i.default(document.body).removeClass("modal-open"),t._resetAdjustments(),t._resetScrollbar(),i.default(t._element).trigger("hidden.bs.modal")}))},e._removeBackdrop=function(){this._backdrop&&(i.default(this._backdrop).remove(),this._backdrop=null)},e._showBackdrop=function(t){var e=this,n=i.default(this._element).hasClass("fade")?"fade":"";if(this._isShown&&this._config.backdrop){if(this._backdrop=document.createElement("div"),this._backdrop.className="modal-backdrop",n&&this._backdrop.classList.add(n),i.default(this._backdrop).appendTo(document.body),i.default(this._element).on("click.dismiss.bs.modal",(function(t){e._ignoreBackdropClick?e._ignoreBackdropClick=!1:t.target===t.currentTarget&&("static"===e._config.backdrop?e._triggerBackdropTransition():e.hide())})),n&&l.reflow(this._backdrop),i.default(this._backdrop).addClass("show"),!t)return;if(!n)return void t();var o=l.getTransitionDurationFromElement(this._backdrop);i.default(this._backdrop).one(l.TRANSITION_END,t).emulateTransitionEnd(o)}else if(!this._isShown&&this._backdrop){i.default(this._backdrop).removeClass("show");var r=function(){e._removeBackdrop(),t&&t()};if(i.default(this._element).hasClass("fade")){var a=l.getTransitionDurationFromElement(this._backdrop);i.default(this._backdrop).one(l.TRANSITION_END,r).emulateTransitionEnd(a)}else r()}else t&&t()},e._adjustDialog=function(){var t=this._element.scrollHeight>document.documentElement.clientHeight;!this._isBodyOverflowing&&t&&(this._element.style.paddingLeft=this._scrollbarWidth+"px"),this._isBodyOverflowing&&!t&&(this._element.style.paddingRight=this._scrollbarWidth+"px")},e._resetAdjustments=function(){this._element.style.paddingLeft="",this._element.style.paddingRight=""},e._checkScrollbar=function(){var t=document.body.getBoundingClientRect();this._isBodyOverflowing=Math.round(t.left+t.right)
',trigger:"hover focus",title:"",delay:0,html:!1,selector:!1,placement:"top",offset:0,container:!1,fallbackPlacement:"flip",boundary:"scrollParent",customClass:"",sanitize:!0,sanitizeFn:null,whiteList:Qt,popperConfig:null},Zt={HIDE:"hide.bs.tooltip",HIDDEN:"hidden.bs.tooltip",SHOW:"show.bs.tooltip",SHOWN:"shown.bs.tooltip",INSERTED:"inserted.bs.tooltip",CLICK:"click.bs.tooltip",FOCUSIN:"focusin.bs.tooltip",FOCUSOUT:"focusout.bs.tooltip",MOUSEENTER:"mouseenter.bs.tooltip",MOUSELEAVE:"mouseleave.bs.tooltip"},te=function(){function t(t,e){if("undefined"==typeof It)throw new TypeError("Bootstrap's tooltips require Popper (https://popper.js.org)");this._isEnabled=!0,this._timeout=0,this._hoverState="",this._activeTrigger={},this._popper=null,this.element=t,this.config=this._getConfig(e),this.tip=null,this._setListeners()}var e=t.prototype;return e.enable=function(){this._isEnabled=!0},e.disable=function(){this._isEnabled=!1},e.toggleEnabled=function(){this._isEnabled=!this._isEnabled},e.toggle=function(t){if(this._isEnabled)if(t){var e=this.constructor.DATA_KEY,n=i.default(t.currentTarget).data(e);n||(n=new this.constructor(t.currentTarget,this._getDelegateConfig()),i.default(t.currentTarget).data(e,n)),n._activeTrigger.click=!n._activeTrigger.click,n._isWithActiveTrigger()?n._enter(null,n):n._leave(null,n)}else{if(i.default(this.getTipElement()).hasClass("show"))return void this._leave(null,this);this._enter(null,this)}},e.dispose=function(){clearTimeout(this._timeout),i.default.removeData(this.element,this.constructor.DATA_KEY),i.default(this.element).off(this.constructor.EVENT_KEY),i.default(this.element).closest(".modal").off("hide.bs.modal",this._hideModalHandler),this.tip&&i.default(this.tip).remove(),this._isEnabled=null,this._timeout=null,this._hoverState=null,this._activeTrigger=null,this._popper&&this._popper.destroy(),this._popper=null,this.element=null,this.config=null,this.tip=null},e.show=function(){var t=this;if("none"===i.default(this.element).css("display"))throw new Error("Please use show on visible elements");var e=i.default.Event(this.constructor.Event.SHOW);if(this.isWithContent()&&this._isEnabled){i.default(this.element).trigger(e);var n=l.findShadowRoot(this.element),o=i.default.contains(null!==n?n:this.element.ownerDocument.documentElement,this.element);if(e.isDefaultPrevented()||!o)return;var r=this.getTipElement(),a=l.getUID(this.constructor.NAME);r.setAttribute("id",a),this.element.setAttribute("aria-describedby",a),this.setContent(),this.config.animation&&i.default(r).addClass("fade");var s="function"==typeof this.config.placement?this.config.placement.call(this,r,this.element):this.config.placement,u=this._getAttachment(s);this.addAttachmentClass(u);var f=this._getContainer();i.default(r).data(this.constructor.DATA_KEY,this),i.default.contains(this.element.ownerDocument.documentElement,this.tip)||i.default(r).appendTo(f),i.default(this.element).trigger(this.constructor.Event.INSERTED),this._popper=new It(this.element,r,this._getPopperConfig(u)),i.default(r).addClass("show"),i.default(r).addClass(this.config.customClass),"ontouchstart"in document.documentElement&&i.default(document.body).children().on("mouseover",null,i.default.noop);var d=function(){t.config.animation&&t._fixTransition();var e=t._hoverState;t._hoverState=null,i.default(t.element).trigger(t.constructor.Event.SHOWN),"out"===e&&t._leave(null,t)};if(i.default(this.tip).hasClass("fade")){var c=l.getTransitionDurationFromElement(this.tip);i.default(this.tip).one(l.TRANSITION_END,d).emulateTransitionEnd(c)}else d()}},e.hide=function(t){var e=this,n=this.getTipElement(),o=i.default.Event(this.constructor.Event.HIDE),r=function(){"show"!==e._hoverState&&n.parentNode&&n.parentNode.removeChild(n),e._cleanTipClass(),e.element.removeAttribute("aria-describedby"),i.default(e.element).trigger(e.constructor.Event.HIDDEN),null!==e._popper&&e._popper.destroy(),t&&t()};if(i.default(this.element).trigger(o),!o.isDefaultPrevented()){if(i.default(n).removeClass("show"),"ontouchstart"in document.documentElement&&i.default(document.body).children().off("mouseover",null,i.default.noop),this._activeTrigger.click=!1,this._activeTrigger.focus=!1,this._activeTrigger.hover=!1,i.default(this.tip).hasClass("fade")){var a=l.getTransitionDurationFromElement(n);i.default(n).one(l.TRANSITION_END,r).emulateTransitionEnd(a)}else r();this._hoverState=""}},e.update=function(){null!==this._popper&&this._popper.scheduleUpdate()},e.isWithContent=function(){return Boolean(this.getTitle())},e.addAttachmentClass=function(t){i.default(this.getTipElement()).addClass("bs-tooltip-"+t)},e.getTipElement=function(){return this.tip=this.tip||i.default(this.config.template)[0],this.tip},e.setContent=function(){var t=this.getTipElement();this.setElementContent(i.default(t.querySelectorAll(".tooltip-inner")),this.getTitle()),i.default(t).removeClass("fade show")},e.setElementContent=function(t,e){"object"!=typeof e||!e.nodeType&&!e.jquery?this.config.html?(this.config.sanitize&&(e=Vt(e,this.config.whiteList,this.config.sanitizeFn)),t.html(e)):t.text(e):this.config.html?i.default(e).parent().is(t)||t.empty().append(e):t.text(i.default(e).text())},e.getTitle=function(){var t=this.element.getAttribute("data-original-title");return t||(t="function"==typeof this.config.title?this.config.title.call(this.element):this.config.title),t},e._getPopperConfig=function(t){var e=this;return a({},{placement:t,modifiers:{offset:this._getOffset(),flip:{behavior:this.config.fallbackPlacement},arrow:{element:".arrow"},preventOverflow:{boundariesElement:this.config.boundary}},onCreate:function(t){t.originalPlacement!==t.placement&&e._handlePopperPlacementChange(t)},onUpdate:function(t){return e._handlePopperPlacementChange(t)}},this.config.popperConfig)},e._getOffset=function(){var t=this,e={};return"function"==typeof this.config.offset?e.fn=function(e){return e.offsets=a({},e.offsets,t.config.offset(e.offsets,t.element)||{}),e}:e.offset=this.config.offset,e},e._getContainer=function(){return!1===this.config.container?document.body:l.isElement(this.config.container)?i.default(this.config.container):i.default(document).find(this.config.container)},e._getAttachment=function(t){return $t[t.toUpperCase()]},e._setListeners=function(){var t=this;this.config.trigger.split(" ").forEach((function(e){if("click"===e)i.default(t.element).on(t.constructor.Event.CLICK,t.config.selector,(function(e){return t.toggle(e)}));else if("manual"!==e){var n="hover"===e?t.constructor.Event.MOUSEENTER:t.constructor.Event.FOCUSIN,o="hover"===e?t.constructor.Event.MOUSELEAVE:t.constructor.Event.FOCUSOUT;i.default(t.element).on(n,t.config.selector,(function(e){return t._enter(e)})).on(o,t.config.selector,(function(e){return t._leave(e)}))}})),this._hideModalHandler=function(){t.element&&t.hide()},i.default(this.element).closest(".modal").on("hide.bs.modal",this._hideModalHandler),this.config.selector?this.config=a({},this.config,{trigger:"manual",selector:""}):this._fixTitle()},e._fixTitle=function(){var t=typeof this.element.getAttribute("data-original-title");(this.element.getAttribute("title")||"string"!==t)&&(this.element.setAttribute("data-original-title",this.element.getAttribute("title")||""),this.element.setAttribute("title",""))},e._enter=function(t,e){var n=this.constructor.DATA_KEY;(e=e||i.default(t.currentTarget).data(n))||(e=new this.constructor(t.currentTarget,this._getDelegateConfig()),i.default(t.currentTarget).data(n,e)),t&&(e._activeTrigger["focusin"===t.type?"focus":"hover"]=!0),i.default(e.getTipElement()).hasClass("show")||"show"===e._hoverState?e._hoverState="show":(clearTimeout(e._timeout),e._hoverState="show",e.config.delay&&e.config.delay.show?e._timeout=setTimeout((function(){"show"===e._hoverState&&e.show()}),e.config.delay.show):e.show())},e._leave=function(t,e){var n=this.constructor.DATA_KEY;(e=e||i.default(t.currentTarget).data(n))||(e=new this.constructor(t.currentTarget,this._getDelegateConfig()),i.default(t.currentTarget).data(n,e)),t&&(e._activeTrigger["focusout"===t.type?"focus":"hover"]=!1),e._isWithActiveTrigger()||(clearTimeout(e._timeout),e._hoverState="out",e.config.delay&&e.config.delay.hide?e._timeout=setTimeout((function(){"out"===e._hoverState&&e.hide()}),e.config.delay.hide):e.hide())},e._isWithActiveTrigger=function(){for(var t in this._activeTrigger)if(this._activeTrigger[t])return!0;return!1},e._getConfig=function(t){var e=i.default(this.element).data();return Object.keys(e).forEach((function(t){-1!==Kt.indexOf(t)&&delete e[t]})),"number"==typeof(t=a({},this.constructor.Default,e,"object"==typeof t&&t?t:{})).delay&&(t.delay={show:t.delay,hide:t.delay}),"number"==typeof t.title&&(t.title=t.title.toString()),"number"==typeof t.content&&(t.content=t.content.toString()),l.typeCheckConfig(Yt,t,this.constructor.DefaultType),t.sanitize&&(t.template=Vt(t.template,t.whiteList,t.sanitizeFn)),t},e._getDelegateConfig=function(){var t={};if(this.config)for(var e in this.config)this.constructor.Default[e]!==this.config[e]&&(t[e]=this.config[e]);return t},e._cleanTipClass=function(){var t=i.default(this.getTipElement()),e=t.attr("class").match(Xt);null!==e&&e.length&&t.removeClass(e.join(""))},e._handlePopperPlacementChange=function(t){this.tip=t.instance.popper,this._cleanTipClass(),this.addAttachmentClass(this._getAttachment(t.placement))},e._fixTransition=function(){var t=this.getTipElement(),e=this.config.animation;null===t.getAttribute("x-placement")&&(i.default(t).removeClass("fade"),this.config.animation=!1,this.hide(),this.show(),this.config.animation=e)},t._jQueryInterface=function(e){return this.each((function(){var n=i.default(this),o=n.data("bs.tooltip"),r="object"==typeof e&&e;if((o||!/dispose|hide/.test(e))&&(o||(o=new t(this,r),n.data("bs.tooltip",o)),"string"==typeof e)){if("undefined"==typeof o[e])throw new TypeError('No method named "'+e+'"');o[e]()}}))},r(t,null,[{key:"VERSION",get:function(){return"4.6.0"}},{key:"Default",get:function(){return Jt}},{key:"NAME",get:function(){return Yt}},{key:"DATA_KEY",get:function(){return"bs.tooltip"}},{key:"Event",get:function(){return Zt}},{key:"EVENT_KEY",get:function(){return".bs.tooltip"}},{key:"DefaultType",get:function(){return Gt}}]),t}();i.default.fn[Yt]=te._jQueryInterface,i.default.fn[Yt].Constructor=te,i.default.fn[Yt].noConflict=function(){return i.default.fn[Yt]=zt,te._jQueryInterface};var ee="popover",ne=i.default.fn[ee],ie=new RegExp("(^|\\s)bs-popover\\S+","g"),oe=a({},te.Default,{placement:"right",trigger:"click",content:"",template:''}),re=a({},te.DefaultType,{content:"(string|element|function)"}),ae={HIDE:"hide.bs.popover",HIDDEN:"hidden.bs.popover",SHOW:"show.bs.popover",SHOWN:"shown.bs.popover",INSERTED:"inserted.bs.popover",CLICK:"click.bs.popover",FOCUSIN:"focusin.bs.popover",FOCUSOUT:"focusout.bs.popover",MOUSEENTER:"mouseenter.bs.popover",MOUSELEAVE:"mouseleave.bs.popover"},se=function(t){var e,n;function o(){return t.apply(this,arguments)||this}n=t,(e=o).prototype=Object.create(n.prototype),e.prototype.constructor=e,e.__proto__=n;var a=o.prototype;return a.isWithContent=function(){return this.getTitle()||this._getContent()},a.addAttachmentClass=function(t){i.default(this.getTipElement()).addClass("bs-popover-"+t)},a.getTipElement=function(){return this.tip=this.tip||i.default(this.config.template)[0],this.tip},a.setContent=function(){var t=i.default(this.getTipElement());this.setElementContent(t.find(".popover-header"),this.getTitle());var e=this._getContent();"function"==typeof e&&(e=e.call(this.element)),this.setElementContent(t.find(".popover-body"),e),t.removeClass("fade show")},a._getContent=function(){return this.element.getAttribute("data-content")||this.config.content},a._cleanTipClass=function(){var t=i.default(this.getTipElement()),e=t.attr("class").match(ie);null!==e&&e.length>0&&t.removeClass(e.join(""))},o._jQueryInterface=function(t){return this.each((function(){var e=i.default(this).data("bs.popover"),n="object"==typeof t?t:null;if((e||!/dispose|hide/.test(t))&&(e||(e=new o(this,n),i.default(this).data("bs.popover",e)),"string"==typeof t)){if("undefined"==typeof e[t])throw new TypeError('No method named "'+t+'"');e[t]()}}))},r(o,null,[{key:"VERSION",get:function(){return"4.6.0"}},{key:"Default",get:function(){return oe}},{key:"NAME",get:function(){return ee}},{key:"DATA_KEY",get:function(){return"bs.popover"}},{key:"Event",get:function(){return ae}},{key:"EVENT_KEY",get:function(){return".bs.popover"}},{key:"DefaultType",get:function(){return re}}]),o}(te);i.default.fn[ee]=se._jQueryInterface,i.default.fn[ee].Constructor=se,i.default.fn[ee].noConflict=function(){return i.default.fn[ee]=ne,se._jQueryInterface};var le="scrollspy",ue=i.default.fn[le],fe={offset:10,method:"auto",target:""},de={offset:"number",method:"string",target:"(string|element)"},ce=function(){function t(t,e){var n=this;this._element=t,this._scrollElement="BODY"===t.tagName?window:t,this._config=this._getConfig(e),this._selector=this._config.target+" .nav-link,"+this._config.target+" .list-group-item,"+this._config.target+" .dropdown-item",this._offsets=[],this._targets=[],this._activeTarget=null,this._scrollHeight=0,i.default(this._scrollElement).on("scroll.bs.scrollspy",(function(t){return n._process(t)})),this.refresh(),this._process()}var e=t.prototype;return e.refresh=function(){var t=this,e=this._scrollElement===this._scrollElement.window?"offset":"position",n="auto"===this._config.method?e:this._config.method,o="position"===n?this._getScrollTop():0;this._offsets=[],this._targets=[],this._scrollHeight=this._getScrollHeight(),[].slice.call(document.querySelectorAll(this._selector)).map((function(t){var e,r=l.getSelectorFromElement(t);if(r&&(e=document.querySelector(r)),e){var a=e.getBoundingClientRect();if(a.width||a.height)return[i.default(e)[n]().top+o,r]}return null})).filter((function(t){return t})).sort((function(t,e){return t[0]-e[0]})).forEach((function(e){t._offsets.push(e[0]),t._targets.push(e[1])}))},e.dispose=function(){i.default.removeData(this._element,"bs.scrollspy"),i.default(this._scrollElement).off(".bs.scrollspy"),this._element=null,this._scrollElement=null,this._config=null,this._selector=null,this._offsets=null,this._targets=null,this._activeTarget=null,this._scrollHeight=null},e._getConfig=function(t){if("string"!=typeof(t=a({},fe,"object"==typeof t&&t?t:{})).target&&l.isElement(t.target)){var e=i.default(t.target).attr("id");e||(e=l.getUID(le),i.default(t.target).attr("id",e)),t.target="#"+e}return l.typeCheckConfig(le,t,de),t},e._getScrollTop=function(){return this._scrollElement===window?this._scrollElement.pageYOffset:this._scrollElement.scrollTop},e._getScrollHeight=function(){return this._scrollElement.scrollHeight||Math.max(document.body.scrollHeight,document.documentElement.scrollHeight)},e._getOffsetHeight=function(){return this._scrollElement===window?window.innerHeight:this._scrollElement.getBoundingClientRect().height},e._process=function(){var t=this._getScrollTop()+this._config.offset,e=this._getScrollHeight(),n=this._config.offset+e-this._getOffsetHeight();if(this._scrollHeight!==e&&this.refresh(),t>=n){var i=this._targets[this._targets.length-1];this._activeTarget!==i&&this._activate(i)}else{if(this._activeTarget&&t0)return this._activeTarget=null,void this._clear();for(var o=this._offsets.length;o--;){this._activeTarget!==this._targets[o]&&t>=this._offsets[o]&&("undefined"==typeof this._offsets[o+1]||t li > .active":".active";n=(n=i.default.makeArray(i.default(o).find(a)))[n.length-1]}var s=i.default.Event("hide.bs.tab",{relatedTarget:this._element}),u=i.default.Event("show.bs.tab",{relatedTarget:n});if(n&&i.default(n).trigger(s),i.default(this._element).trigger(u),!u.isDefaultPrevented()&&!s.isDefaultPrevented()){r&&(e=document.querySelector(r)),this._activate(this._element,o);var f=function(){var e=i.default.Event("hidden.bs.tab",{relatedTarget:t._element}),o=i.default.Event("shown.bs.tab",{relatedTarget:n});i.default(n).trigger(e),i.default(t._element).trigger(o)};e?this._activate(e,e.parentNode,f):f()}}},e.dispose=function(){i.default.removeData(this._element,"bs.tab"),this._element=null},e._activate=function(t,e,n){var o=this,r=(!e||"UL"!==e.nodeName&&"OL"!==e.nodeName?i.default(e).children(".active"):i.default(e).find("> li > .active"))[0],a=n&&r&&i.default(r).hasClass("fade"),s=function(){return o._transitionComplete(t,r,n)};if(r&&a){var u=l.getTransitionDurationFromElement(r);i.default(r).removeClass("show").one(l.TRANSITION_END,s).emulateTransitionEnd(u)}else s()},e._transitionComplete=function(t,e,n){if(e){i.default(e).removeClass("active");var o=i.default(e.parentNode).find("> .dropdown-menu .active")[0];o&&i.default(o).removeClass("active"),"tab"===e.getAttribute("role")&&e.setAttribute("aria-selected",!1)}if(i.default(t).addClass("active"),"tab"===t.getAttribute("role")&&t.setAttribute("aria-selected",!0),l.reflow(t),t.classList.contains("fade")&&t.classList.add("show"),t.parentNode&&i.default(t.parentNode).hasClass("dropdown-menu")){var r=i.default(t).closest(".dropdown")[0];if(r){var a=[].slice.call(r.querySelectorAll(".dropdown-toggle"));i.default(a).addClass("active")}t.setAttribute("aria-expanded",!0)}n&&n()},t._jQueryInterface=function(e){return this.each((function(){var n=i.default(this),o=n.data("bs.tab");if(o||(o=new t(this),n.data("bs.tab",o)),"string"==typeof e){if("undefined"==typeof o[e])throw new TypeError('No method named "'+e+'"');o[e]()}}))},r(t,null,[{key:"VERSION",get:function(){return"4.6.0"}}]),t}();i.default(document).on("click.bs.tab.data-api",'[data-toggle="tab"], [data-toggle="pill"], [data-toggle="list"]',(function(t){t.preventDefault(),pe._jQueryInterface.call(i.default(this),"show")})),i.default.fn.tab=pe._jQueryInterface,i.default.fn.tab.Constructor=pe,i.default.fn.tab.noConflict=function(){return i.default.fn.tab=he,pe._jQueryInterface};var me=i.default.fn.toast,ge={animation:"boolean",autohide:"boolean",delay:"number"},ve={animation:!0,autohide:!0,delay:500},_e=function(){function t(t,e){this._element=t,this._config=this._getConfig(e),this._timeout=null,this._setListeners()}var e=t.prototype;return e.show=function(){var t=this,e=i.default.Event("show.bs.toast");if(i.default(this._element).trigger(e),!e.isDefaultPrevented()){this._clearTimeout(),this._config.animation&&this._element.classList.add("fade");var n=function(){t._element.classList.remove("showing"),t._element.classList.add("show"),i.default(t._element).trigger("shown.bs.toast"),t._config.autohide&&(t._timeout=setTimeout((function(){t.hide()}),t._config.delay))};if(this._element.classList.remove("hide"),l.reflow(this._element),this._element.classList.add("showing"),this._config.animation){var o=l.getTransitionDurationFromElement(this._element);i.default(this._element).one(l.TRANSITION_END,n).emulateTransitionEnd(o)}else n()}},e.hide=function(){if(this._element.classList.contains("show")){var t=i.default.Event("hide.bs.toast");i.default(this._element).trigger(t),t.isDefaultPrevented()||this._close()}},e.dispose=function(){this._clearTimeout(),this._element.classList.contains("show")&&this._element.classList.remove("show"),i.default(this._element).off("click.dismiss.bs.toast"),i.default.removeData(this._element,"bs.toast"),this._element=null,this._config=null},e._getConfig=function(t){return t=a({},ve,i.default(this._element).data(),"object"==typeof t&&t?t:{}),l.typeCheckConfig("toast",t,this.constructor.DefaultType),t},e._setListeners=function(){var t=this;i.default(this._element).on("click.dismiss.bs.toast",'[data-dismiss="toast"]',(function(){return t.hide()}))},e._close=function(){var t=this,e=function(){t._element.classList.add("hide"),i.default(t._element).trigger("hidden.bs.toast")};if(this._element.classList.remove("show"),this._config.animation){var n=l.getTransitionDurationFromElement(this._element);i.default(this._element).one(l.TRANSITION_END,e).emulateTransitionEnd(n)}else e()},e._clearTimeout=function(){clearTimeout(this._timeout),this._timeout=null},t._jQueryInterface=function(e){return this.each((function(){var n=i.default(this),o=n.data("bs.toast");if(o||(o=new t(this,"object"==typeof e&&e),n.data("bs.toast",o)),"string"==typeof e){if("undefined"==typeof o[e])throw new TypeError('No method named "'+e+'"');o[e](this)}}))},r(t,null,[{key:"VERSION",get:function(){return"4.6.0"}},{key:"DefaultType",get:function(){return ge}},{key:"Default",get:function(){return ve}}]),t}();i.default.fn.toast=_e._jQueryInterface,i.default.fn.toast.Constructor=_e,i.default.fn.toast.noConflict=function(){return i.default.fn.toast=me,_e._jQueryInterface},t.Alert=d,t.Button=h,t.Carousel=y,t.Collapse=S,t.Dropdown=Ft,t.Modal=qt,t.Popover=se,t.Scrollspy=ce,t.Tab=pe,t.Toast=_e,t.Tooltip=te,t.Util=l,Object.defineProperty(t,"__esModule",{value:!0})})); -//# sourceMappingURL=bootstrap.bundle.min.js.map \ No newline at end of file diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/assets/js/coveo.js b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/assets/js/coveo.js deleted file mode 100644 index 62cf13bff0..0000000000 --- a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/assets/js/coveo.js +++ /dev/null @@ -1,39 +0,0 @@ -document.addEventListener('DOMContentLoaded', function () { - Coveo.SearchEndpoint.configureCloudV2Endpoint("", 'xx79df1e1f-11e4-4da5-8ea8-2ed7e24cca6a'); // Prod API key - // Coveo.SearchEndpoint.configureCloudV2Endpoint("", 'xxe1e9046f-585c-4518-a14a-6b986a5efffd'); // test API key - const root = document.getElementById("search"); - const searchBoxRoot = document.getElementById("searchbox"); - Coveo.initSearchbox(searchBoxRoot, "/search.html"); - var resetbtn = document.querySelector('#reset_btn'); - if (resetbtn) { - resetbtn.onclick = function () { - document.querySelector('.coveo-facet-header-eraser').click(); - }; - } - Coveo.$$(root).on("querySuccess", function (e, args) { - resetbtn.style.display = "block"; - }); - Coveo.$$(root).on('afterComponentsInitialization', function (e, data) { - setTimeout(function () { - document.querySelector('.CoveoOmnibox input').value = Coveo.state(root, 'q'); - }, 1000); - }); - Coveo.$('#search').on("newResultsDisplayed", function (e, args) { - for (var i = 0; i < e.target.lastChild.children.length; i++) { - //Remove the title for tooltip box - Coveo.$('.CoveoResultLink').removeAttr('title'); - } - }); - Coveo.init(root, { - f5_product_module: { - dependsOn: "@f5_product", - dependsOnCondition: (parentFacet) => { - const id = parentFacet.options.id; - const value = "NGINX Management Suite"; - const selected = parentFacet.queryStateModel.get(`f:${id}`) - return selected.includes(value); - } - } - }); -}) - diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/assets/js/kube.js b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/assets/js/kube.js deleted file mode 100644 index 534a5ad669..0000000000 --- a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/assets/js/kube.js +++ /dev/null @@ -1,2201 +0,0 @@ -/* - Kube. CSS & JS Framework - Version 6.5.2 - Updated: February 2, 2017 - - http://imperavi.com/kube/ - - Copyright (c) 2009-2017, Imperavi LLC. - License: MIT -*/ -if (typeof jQuery === 'undefined') {throw new Error('Kube\'s requires jQuery')}; -;(function($) { var version = $.fn.jquery.split('.'); if (version[0] == 1 && version[1] < 8) {throw new Error('Kube\'s requires at least jQuery v1.8'); }})(jQuery); - -;(function() -{ - // Inherits - Function.prototype.inherits = function(parent) - { - var F = function () {}; - F.prototype = parent.prototype; - var f = new F(); - - for (var prop in this.prototype) f[prop] = this.prototype[prop]; - this.prototype = f; - this.prototype.super = parent.prototype; - }; - - // Core Class - var Kube = function(element, options) - { - options = (typeof options === 'object') ? options : {}; - - this.$element = $(element); - this.opts = $.extend(true, this.defaults, $.fn[this.namespace].options, this.$element.data(), options); - this.$target = (typeof this.opts.target === 'string') ? $(this.opts.target) : null; - }; - - // Core Functionality - Kube.prototype = { - getInstance: function() - { - return this.$element.data('fn.' + this.namespace); - }, - hasTarget: function() - { - return !(this.$target === null); - }, - callback: function(type) - { - var args = [].slice.call(arguments).splice(1); - - // on element callback - if (this.$element) - { - args = this._fireCallback($._data(this.$element[0], 'events'), type, this.namespace, args); - } - - // on target callback - if (this.$target) - { - args = this._fireCallback($._data(this.$target[0], 'events'), type, this.namespace, args); - } - - // opts callback - if (this.opts && this.opts.callbacks && $.isFunction(this.opts.callbacks[type])) - { - return this.opts.callbacks[type].apply(this, args); - } - - return args; - }, - _fireCallback: function(events, type, eventNamespace, args) - { - if (events && typeof events[type] !== 'undefined') - { - var len = events[type].length; - for (var i = 0; i < len; i++) - { - var namespace = events[type][i].namespace; - if (namespace === eventNamespace) - { - var value = events[type][i].handler.apply(this, args); - } - } - } - - return (typeof value === 'undefined') ? args : value; - } - }; - - // Scope - window.Kube = Kube; - -})(); -/** - * @library Kube Plugin - * @author Imperavi LLC - * @license MIT - */ -(function(Kube) -{ - Kube.Plugin = { - create: function(classname, pluginname) - { - pluginname = (typeof pluginname === 'undefined') ? classname.toLowerCase() : pluginname; - - $.fn[pluginname] = function(method, options) - { - var args = Array.prototype.slice.call(arguments, 1); - var name = 'fn.' + pluginname; - var val = []; - - this.each(function() - { - var $this = $(this), data = $this.data(name); - options = (typeof method === 'object') ? method : options; - - if (!data) - { - // Initialization - $this.data(name, {}); - $this.data(name, (data = new Kube[classname](this, options))); - } - - // Call methods - if (typeof method === 'string') - { - if ($.isFunction(data[method])) - { - var methodVal = data[method].apply(data, args); - if (methodVal !== undefined) - { - val.push(methodVal); - } - } - else - { - $.error('No such method "' + method + '" for ' + classname); - } - } - - }); - - return (val.length === 0 || val.length === 1) ? ((val.length === 0) ? this : val[0]) : val; - }; - - $.fn[pluginname].options = {}; - - return this; - }, - autoload: function(pluginname) - { - var arr = pluginname.split(','); - var len = arr.length; - - for (var i = 0; i < len; i++) - { - var name = arr[i].toLowerCase().split(',').map(function(s) { return s.trim() }).join(','); - this.autoloadQueue.push(name); - } - - return this; - }, - autoloadQueue: [], - startAutoload: function() - { - if (!window.MutationObserver || this.autoloadQueue.length === 0) - { - return; - } - - var self = this; - var observer = new MutationObserver(function(mutations) - { - mutations.forEach(function(mutation) - { - var newNodes = mutation.addedNodes; - if (newNodes.length === 0 || (newNodes.length === 1 && newNodes.nodeType === 3)) - { - return; - } - - self.startAutoloadOnce(); - }); - }); - - // pass in the target node, as well as the observer options - observer.observe(document, { - subtree: true, - childList: true - }); - }, - startAutoloadOnce: function() - { - var self = this; - var $nodes = $('[data-component]').not('[data-loaded]'); - $nodes.each(function() - { - var $el = $(this); - var pluginname = $el.data('component'); - - if (self.autoloadQueue.indexOf(pluginname) !== -1) - { - $el.attr('data-loaded', true); - $el[pluginname](); - } - }); - - }, - watch: function() - { - Kube.Plugin.startAutoloadOnce(); - Kube.Plugin.startAutoload(); - } - }; - - $(window).on('load', function() - { - Kube.Plugin.watch(); - }); - -}(Kube)); -/** - * @library Kube Animation - * @author Imperavi LLC - * @license MIT - */ -(function(Kube) -{ - Kube.Animation = function(element, effect, callback) - { - this.namespace = 'animation'; - this.defaults = {}; - - // Parent Constructor - Kube.apply(this, arguments); - - // Initialization - this.effect = effect; - this.completeCallback = (typeof callback === 'undefined') ? false : callback; - this.prefixes = ['', '-moz-', '-o-animation-', '-webkit-']; - this.queue = []; - - this.start(); - }; - - Kube.Animation.prototype = { - start: function() - { - if (this.isSlideEffect()) this.setElementHeight(); - - this.addToQueue(); - this.clean(); - this.animate(); - }, - addToQueue: function() - { - this.queue.push(this.effect); - }, - setElementHeight: function() - { - this.$element.height(this.$element.height()); - }, - removeElementHeight: function() - { - this.$element.css('height', ''); - }, - isSlideEffect: function() - { - return (this.effect === 'slideDown' || this.effect === 'slideUp'); - }, - isHideableEffect: function() - { - var effects = ['fadeOut', 'slideUp', 'flipOut', 'zoomOut', 'slideOutUp', 'slideOutRight', 'slideOutLeft']; - - return ($.inArray(this.effect, effects) !== -1); - }, - isToggleEffect: function() - { - return (this.effect === 'show' || this.effect === 'hide'); - }, - storeHideClasses: function() - { - if (this.$element.hasClass('hide-sm')) this.$element.data('hide-sm-class', true); - else if (this.$element.hasClass('hide-md')) this.$element.data('hide-md-class', true); - }, - revertHideClasses: function() - { - if (this.$element.data('hide-sm-class')) this.$element.addClass('hide-sm').removeData('hide-sm-class'); - else if (this.$element.data('hide-md-class')) this.$element.addClass('hide-md').removeData('hide-md-class'); - else this.$element.addClass('hide'); - }, - removeHideClass: function() - { - if (this.$element.data('hide-sm-class')) this.$element.removeClass('hide-sm'); - else if (this.$element.data('hide-md-class')) this.$element.removeClass('hide-md'); - else this.$element.removeClass('hide'); - }, - animate: function() - { - this.storeHideClasses(); - if (this.isToggleEffect()) - { - return this.makeSimpleEffects(); - } - - this.$element.addClass('kubeanimated'); - this.$element.addClass(this.queue[0]); - this.removeHideClass(); - - var _callback = (this.queue.length > 1) ? null : this.completeCallback; - this.complete('AnimationEnd', $.proxy(this.makeComplete, this), _callback); - }, - makeSimpleEffects: function() - { - if (this.effect === 'show') this.removeHideClass(); - else if (this.effect === 'hide') this.revertHideClasses(); - - if (typeof this.completeCallback === 'function') this.completeCallback(this); - }, - makeComplete: function() - { - if (this.$element.hasClass(this.queue[0])) - { - this.clean(); - this.queue.shift(); - - if (this.queue.length) this.animate(); - } - }, - complete: function(type, make, callback) - { - var event = type.toLowerCase() + ' webkit' + type + ' o' + type + ' MS' + type; - - this.$element.one(event, $.proxy(function() - { - if (typeof make === 'function') make(); - if (this.isHideableEffect()) this.revertHideClasses(); - if (this.isSlideEffect()) this.removeElementHeight(); - if (typeof callback === 'function') callback(this); - - this.$element.off(event); - - }, this)); - }, - clean: function() - { - this.$element.removeClass('kubeanimated').removeClass(this.queue[0]); - } - }; - - // Inheritance - Kube.Animation.inherits(Kube); - -}(Kube)); - -// Plugin -(function($) -{ - $.fn.animation = function(effect, callback) - { - var name = 'fn.animation'; - - return this.each(function() - { - var $this = $(this), data = $this.data(name); - - $this.data(name, {}); - $this.data(name, (data = new Kube.Animation(this, effect, callback))); - }); - }; - - $.fn.animation.options = {}; - -})(jQuery); -/** - * @library Kube Detect - * @author Imperavi LLC - * @license MIT - */ -(function(Kube) -{ - Kube.Detect = function() {}; - - Kube.Detect.prototype = { - isMobile: function() - { - return /(iPhone|iPod|BlackBerry|Android)/.test(navigator.userAgent); - }, - isDesktop: function() - { - return !/(iPhone|iPod|iPad|BlackBerry|Android)/.test(navigator.userAgent); - }, - isMobileScreen: function() - { - return ($(window).width() <= 768); - }, - isTabletScreen: function() - { - return ($(window).width() > 768 && $(window).width() <= 1024); - }, - isDesktopScreen: function() - { - return ($(window).width() > 1024); - } - }; - - -}(Kube)); -/** - * @library Kube FormData - * @author Imperavi LLC - * @license MIT - */ -(function(Kube) -{ - Kube.FormData = function(app) - { - this.opts = app.opts; - }; - - Kube.FormData.prototype = { - set: function(data) - { - this.data = data; - }, - get: function(formdata) - { - this.formdata = formdata; - - if (this.opts.appendForms) this.appendForms(); - if (this.opts.appendFields) this.appendFields(); - - return this.data; - }, - appendFields: function() - { - var $fields = $(this.opts.appendFields); - if ($fields.length === 0) - { - return; - } - - var self = this; - var str = ''; - - if (this.formdata) - { - $fields.each(function() - { - self.data.append($(this).attr('name'), $(this).val()); - }); - } - else - { - $fields.each(function() - { - str += '&' + $(this).attr('name') + '=' + $(this).val(); - }); - - this.data = (this.data === '') ? str.replace(/^&/, '') : this.data + str; - } - }, - appendForms: function() - { - var $forms = $(this.opts.appendForms); - if ($forms.length === 0) - { - return; - } - - if (this.formdata) - { - var self = this; - var formsData = $(this.opts.appendForms).serializeArray(); - $.each(formsData, function(i,s) - { - self.data.append(s.name, s.value); - }); - } - else - { - var str = $forms.serialize(); - - this.data = (this.data === '') ? str : this.data + '&' + str; - } - } - }; - - -}(Kube)); -/** - * @library Kube Response - * @author Imperavi LLC - * @license MIT - */ -(function(Kube) -{ - Kube.Response = function(app) {}; - - Kube.Response.prototype = { - parse: function(str) - { - if (str === '') return false; - - var obj = {}; - - try { - obj = JSON.parse(str); - } catch (e) { - return false; - } - - if (obj[0] !== undefined) - { - for (var item in obj) - { - this.parseItem(obj[item]); - } - } - else - { - this.parseItem(obj); - } - - return obj; - }, - parseItem: function(item) - { - if (item.type === 'value') - { - $.each(item.data, $.proxy(function(key, val) - { - val = (val === null || val === false) ? 0 : val; - val = (val === true) ? 1 : val; - - $(key).val(val); - - }, this)); - } - else if (item.type === 'html') - { - $.each(item.data, $.proxy(function(key, val) - { - val = (val === null || val === false) ? '' : val; - - $(key).html(this.stripslashes(val)); - - }, this)); - } - else if (item.type === 'addClass') - { - $.each(item.data, function(key, val) - { - $(key).addClass(val); - }); - } - else if (item.type === 'removeClass') - { - $.each(item.data, function(key, val) - { - $(key).removeClass(val); - }); - } - else if (item.type === 'command') - { - $.each(item.data, function(key, val) - { - $(val)[key](); - }); - } - else if (item.type === 'animation') - { - $.each(item.data, function(key, data) - { - data.opts = (typeof data.opts === 'undefined') ? {} : data.opts; - - $(key).animation(data.name, data.opts); - }); - } - else if (item.type === 'location') - { - top.location.href = item.data; - } - else if (item.type === 'notify') - { - $.notify(item.data); - } - - return item; - }, - stripslashes: function(str) - { - return (str+'').replace(/\0/g, '0').replace(/\\([\\'"])/g, '$1'); - } - }; - - -}(Kube)); -/** - * @library Kube Utils - * @author Imperavi LLC - * @license MIT - */ -(function(Kube) -{ - Kube.Utils = function() {}; - - Kube.Utils.prototype = { - disableBodyScroll: function() - { - var $body = $('html'); - var windowWidth = window.innerWidth; - - if (!windowWidth) - { - var documentElementRect = document.documentElement.getBoundingClientRect(); - windowWidth = documentElementRect.right - Math.abs(documentElementRect.left); - } - - var isOverflowing = document.body.clientWidth < windowWidth; - var scrollbarWidth = this.measureScrollbar(); - - $body.css('overflow', 'hidden'); - if (isOverflowing) $body.css('padding-right', scrollbarWidth); - }, - measureScrollbar: function() - { - var $body = $('body'); - var scrollDiv = document.createElement('div'); - scrollDiv.className = 'scrollbar-measure'; - - $body.append(scrollDiv); - var scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth; - $body[0].removeChild(scrollDiv); - return scrollbarWidth; - }, - enableBodyScroll: function() - { - $('html').css({ 'overflow': '', 'padding-right': '' }); - } - }; - - -}(Kube)); -/** - * @library Kube Message - * @author Imperavi LLC - * @license MIT - */ -(function(Kube) -{ - Kube.Message = function(element, options) - { - this.namespace = 'message'; - this.defaults = { - closeSelector: '.close', - closeEvent: 'click', - animationOpen: 'fadeIn', - animationClose: 'fadeOut', - callbacks: ['open', 'opened', 'close', 'closed'] - }; - - // Parent Constructor - Kube.apply(this, arguments); - - // Initialization - this.start(); - }; - - // Functionality - Kube.Message.prototype = { - start: function() - { - this.$close = this.$element.find(this.opts.closeSelector); - this.$close.on(this.opts.closeEvent + '.' + this.namespace, $.proxy(this.close, this)); - this.$element.addClass('open'); - }, - stop: function() - { - this.$close.off('.' + this.namespace); - this.$element.removeClass('open'); - }, - open: function(e) - { - if (e) e.preventDefault(); - - if (!this.isOpened()) - { - this.callback('open'); - this.$element.animation(this.opts.animationOpen, $.proxy(this.onOpened, this)); - } - }, - isOpened: function() - { - return this.$element.hasClass('open'); - }, - onOpened: function() - { - this.callback('opened'); - this.$element.addClass('open'); - }, - close: function(e) - { - if (e) e.preventDefault(); - - if (this.isOpened()) - { - this.callback('close'); - this.$element.animation(this.opts.animationClose, $.proxy(this.onClosed, this)); - } - }, - onClosed: function() - { - this.callback('closed'); - this.$element.removeClass('open'); - } - }; - - // Inheritance - Kube.Message.inherits(Kube); - - // Plugin - Kube.Plugin.create('Message'); - Kube.Plugin.autoload('Message'); - -}(Kube)); -/** - * @library Kube Sticky - * @author Imperavi LLC - * @license MIT - */ -(function(Kube) -{ - Kube.Sticky = function(element, options) - { - this.namespace = 'sticky'; - this.defaults = { - classname: 'fixed', - offset: 0, // pixels - callbacks: ['fixed', 'unfixed'] - }; - - // Parent Constructor - Kube.apply(this, arguments); - - // Initialization - this.start(); - }; - - // Functionality - Kube.Sticky.prototype = { - start: function() - { - this.offsetTop = this.getOffsetTop(); - - this.load(); - $(window).scroll($.proxy(this.load, this)); - }, - getOffsetTop: function() - { - return this.$element.offset().top; - }, - load: function() - { - return (this.isFix()) ? this.fixed() : this.unfixed(); - }, - isFix: function() - { - return ($(window).scrollTop() > (this.offsetTop + this.opts.offset)); - }, - fixed: function() - { - this.$element.addClass(this.opts.classname).css('top', this.opts.offset + 'px'); - this.callback('fixed'); - }, - unfixed: function() - { - this.$element.removeClass(this.opts.classname).css('top', ''); - this.callback('unfixed'); - } - }; - - // Inheritance - Kube.Sticky.inherits(Kube); - - // Plugin - Kube.Plugin.create('Sticky'); - Kube.Plugin.autoload('Sticky'); - -}(Kube)); -/** - * @library Kube Toggleme - * @author Imperavi LLC - * @license MIT - */ -(function(Kube) -{ - Kube.Toggleme = function(element, options) - { - this.namespace = 'toggleme'; - this.defaults = { - toggleEvent: 'click', - target: null, - text: '', - animationOpen: 'slideDown', - animationClose: 'slideUp', - callbacks: ['open', 'opened', 'close', 'closed'] - }; - - // Parent Constructor - Kube.apply(this, arguments); - - // Initialization - this.start(); - }; - - // Functionality - Kube.Toggleme.prototype = { - start: function() - { - if (!this.hasTarget()) return; - - this.$element.on(this.opts.toggleEvent + '.' + this.namespace, $.proxy(this.toggle, this)); - }, - stop: function() - { - this.$element.off('.' + this.namespace); - this.revertText(); - }, - toggle: function(e) - { - if (this.isOpened()) this.close(e); - else this.open(e); - }, - open: function(e) - { - if (e) e.preventDefault(); - - if (!this.isOpened()) - { - this.storeText(); - this.callback('open'); - this.$target.animation('slideDown', $.proxy(this.onOpened, this)); - - // changes the text of $element with a less delay to smooth - setTimeout($.proxy(this.replaceText, this), 100); - } - }, - close: function(e) - { - if (e) e.preventDefault(); - - if (this.isOpened()) - { - this.callback('close'); - this.$target.animation('slideUp', $.proxy(this.onClosed, this)); - } - }, - isOpened: function() - { - return (this.$target.hasClass('open')); - }, - onOpened: function() - { - this.$target.addClass('open'); - this.callback('opened'); - }, - onClosed: function() - { - this.$target.removeClass('open'); - this.revertText(); - this.callback('closed'); - }, - storeText: function() - { - this.$element.data('replacement-text', this.$element.html()); - }, - revertText: function() - { - var text = this.$element.data('replacement-text'); - if (text) this.$element.html(text); - - this.$element.removeData('replacement-text'); - }, - replaceText: function() - { - if (this.opts.text !== '') - { - this.$element.html(this.opts.text); - } - } - }; - - // Inheritance - Kube.Toggleme.inherits(Kube); - - // Plugin - Kube.Plugin.create('Toggleme'); - Kube.Plugin.autoload('Toggleme'); - -}(Kube)); -/** - * @library Kube Offcanvas - * @author Imperavi LLC - * @license MIT - */ -(function(Kube) -{ - Kube.Offcanvas = function(element, options) - { - this.namespace = 'offcanvas'; - this.defaults = { - target: null, // selector - push: true, // boolean - width: '250px', // string - direction: 'left', // string: left or right - toggleEvent: 'click', - clickOutside: true, // boolean - animationOpen: 'slideInLeft', - animationClose: 'slideOutLeft', - callbacks: ['open', 'opened', 'close', 'closed'] - }; - - // Parent Constructor - Kube.apply(this, arguments); - - // Services - this.utils = new Kube.Utils(); - this.detect = new Kube.Detect(); - - // Initialization - this.start(); - }; - - // Functionality - Kube.Offcanvas.prototype = { - start: function() - { - if (!this.hasTarget()) return; - - this.buildTargetWidth(); - this.buildAnimationDirection(); - - this.$close = this.getCloseLink(); - this.$element.on(this.opts.toggleEvent + '.' + this.namespace, $.proxy(this.toggle, this)); - this.$target.addClass('offcanvas'); - }, - stop: function() - { - this.closeAll(); - - this.$element.off('.' + this.namespace); - this.$close.off('.' + this.namespace); - $(document).off('.' + this.namespace); - }, - toggle: function(e) - { - if (this.isOpened()) this.close(e); - else this.open(e); - }, - buildTargetWidth: function() - { - this.opts.width = ($(window).width() < parseInt(this.opts.width)) ? '100%' : this.opts.width; - }, - buildAnimationDirection: function() - { - if (this.opts.direction === 'right') - { - this.opts.animationOpen = 'slideInRight'; - this.opts.animationClose = 'slideOutRight'; - } - }, - getCloseLink: function() - { - return this.$target.find('.close'); - }, - open: function(e) - { - if (e) e.preventDefault(); - - if (!this.isOpened()) - { - this.closeAll(); - this.callback('open'); - - this.$target.addClass('offcanvas-' + this.opts.direction); - this.$target.css('width', this.opts.width); - - this.pushBody(); - - this.$target.animation(this.opts.animationOpen, $.proxy(this.onOpened, this)); - } - }, - closeAll: function() - { - var $elms = $(document).find('.offcanvas'); - if ($elms.length !== 0) - { - $elms.each(function() - { - var $el = $(this); - - if ($el.hasClass('open')) - { - $el.css('width', '').animation('hide'); - $el.removeClass('open offcanvas-left offcanvas-right'); - } - - }); - - $(document).off('.' + this.namespace); - $('body').css('left', ''); - } - }, - close: function(e) - { - if (e) - { - var $el = $(e.target); - var isTag = ($el[0].tagName === 'A' || $el[0].tagName === 'BUTTON'); - if (isTag && $el.closest('.offcanvas').length !== 0 && !$el.hasClass('close')) - { - return; - } - - e.preventDefault(); - } - - if (this.isOpened()) - { - this.utils.enableBodyScroll(); - this.callback('close'); - this.pullBody(); - this.$target.animation(this.opts.animationClose, $.proxy(this.onClosed, this)); - } - }, - isOpened: function() - { - return (this.$target.hasClass('open')); - }, - onOpened: function() - { - if (this.opts.clickOutside) $(document).on('click.' + this.namespace, $.proxy(this.close, this)); - if (this.detect.isMobileScreen()) $('html').addClass('no-scroll'); - - $(document).on('keyup.' + this.namespace, $.proxy(this.handleKeyboard, this)); - this.$close.on('click.' + this.namespace, $.proxy(this.close, this)); - - this.utils.disableBodyScroll(); - this.$target.addClass('open'); - this.callback('opened'); - }, - onClosed: function() - { - if (this.detect.isMobileScreen()) $('html').removeClass('no-scroll'); - - this.$target.css('width', '').removeClass('offcanvas-' + this.opts.direction); - - this.$close.off('.' + this.namespace); - $(document).off('.' + this.namespace); - - this.$target.removeClass('open'); - this.callback('closed'); - }, - handleKeyboard: function(e) - { - if (e.which === 27) this.close(); - }, - pullBody: function() - { - if (this.opts.push) - { - $('body').animate({ left: 0 }, 350, function() { $(this).removeClass('offcanvas-push-body'); }); - } - }, - pushBody: function() - { - if (this.opts.push) - { - var properties = (this.opts.direction === 'left') ? { 'left': this.opts.width } : { 'left': '-' + this.opts.width }; - $('body').addClass('offcanvas-push-body').animate(properties, 200); - } - } - }; - - // Inheritance - Kube.Offcanvas.inherits(Kube); - - // Plugin - Kube.Plugin.create('Offcanvas'); - Kube.Plugin.autoload('Offcanvas'); - -}(Kube)); -/** - * @library Kube Collapse - * @author Imperavi LLC - * @license MIT - */ -(function(Kube) -{ - Kube.Collapse = function(element, options) - { - this.namespace = 'collapse'; - this.defaults = { - target: null, - toggle: true, - active: false, // string (hash = tab id selector) - toggleClass: 'collapse-toggle', - boxClass: 'collapse-box', - callbacks: ['open', 'opened', 'close', 'closed'], - - // private - hashes: [], - currentHash: false, - currentItem: false - }; - - // Parent Constructor - Kube.apply(this, arguments); - - // Initialization - this.start(); - }; - - // Functionality - Kube.Collapse.prototype = { - start: function() - { - // items - this.$items = this.getItems(); - this.$items.each($.proxy(this.loadItems, this)); - - // boxes - this.$boxes = this.getBoxes(); - - // active - this.setActiveItem(); - }, - getItems: function() - { - return this.$element.find('.' + this.opts.toggleClass); - }, - getBoxes: function() - { - return this.$element.find('.' + this.opts.boxClass); - }, - loadItems: function(i, el) - { - var item = this.getItem(el); - - // set item identificator - item.$el.attr('rel', item.hash); - - // active - if (!$(item.hash).hasClass('hide')) - { - this.opts.currentItem = item; - this.opts.active = item.hash; - - item.$el.addClass('active'); - } - - // event - item.$el.on('click.collapse', $.proxy(this.toggle, this)); - - }, - setActiveItem: function() - { - if (this.opts.active !== false) - { - this.opts.currentItem = this.getItemBy(this.opts.active); - this.opts.active = this.opts.currentItem.hash; - } - - if (this.opts.currentItem !== false) - { - this.addActive(this.opts.currentItem); - this.opts.currentItem.$box.removeClass('hide'); - } - }, - addActive: function(item) - { - item.$box.removeClass('hide').addClass('open'); - item.$el.addClass('active'); - - if (item.$caret !== false) item.$caret.removeClass('down').addClass('up'); - if (item.$parent !== false) item.$parent.addClass('active'); - - this.opts.currentItem = item; - }, - removeActive: function(item) - { - item.$box.removeClass('open'); - item.$el.removeClass('active'); - - if (item.$caret !== false) item.$caret.addClass('down').removeClass('up'); - if (item.$parent !== false) item.$parent.removeClass('active'); - - this.opts.currentItem = false; - }, - toggle: function(e) - { - if (e) e.preventDefault(); - - var target = $(e.target).closest('.' + this.opts.toggleClass).get(0) || e.target; - var item = this.getItem(target); - - if (this.isOpened(item.hash)) this.close(item.hash); - else this.open(e) - }, - openAll: function() - { - this.$items.addClass('active'); - this.$boxes.addClass('open').removeClass('hide'); - }, - open: function(e, push) - { - if (typeof e === 'undefined') return; - if (typeof e === 'object') e.preventDefault(); - - var target = $(e.target).closest('.' + this.opts.toggleClass).get(0) || e.target; - var item = (typeof e === 'object') ? this.getItem(target) : this.getItemBy(e); - - if (item.$box.hasClass('open')) - { - return; - } - - if (this.opts.toggle) this.closeAll(); - - this.callback('open', item); - this.addActive(item); - - item.$box.animation('slideDown', $.proxy(this.onOpened, this)); - }, - onOpened: function() - { - this.callback('opened', this.opts.currentItem); - }, - closeAll: function() - { - this.$items.removeClass('active').closest('li').removeClass('active'); - this.$boxes.removeClass('open').addClass('hide'); - }, - close: function(num) - { - var item = this.getItemBy(num); - - this.callback('close', item); - - this.opts.currentItem = item; - - item.$box.animation('slideUp', $.proxy(this.onClosed, this)); - }, - onClosed: function() - { - var item = this.opts.currentItem; - - this.removeActive(item); - this.callback('closed', item); - }, - isOpened: function(hash) - { - return $(hash).hasClass('open'); - }, - getItem: function(element) - { - var item = {}; - - item.$el = $(element); - item.hash = item.$el.attr('href'); - item.$box = $(item.hash); - - var $parent = item.$el.parent(); - item.$parent = ($parent[0].tagName === 'LI') ? $parent : false; - - var $caret = item.$el.find('.caret'); - item.$caret = ($caret.length !== 0) ? $caret : false; - - return item; - }, - getItemBy: function(num) - { - var element = (typeof num === 'number') ? this.$items.eq(num-1) : this.$element.find('[rel="' + num + '"]'); - - return this.getItem(element); - } - }; - - // Inheritance - Kube.Collapse.inherits(Kube); - - // Plugin - Kube.Plugin.create('Collapse'); - Kube.Plugin.autoload('Collapse'); - -}(Kube)); -/** - * @library Kube Dropdown - * @author Imperavi LLC - * @license MIT - */ -(function(Kube) -{ - Kube.Dropdown = function(element, options) - { - this.namespace = 'dropdown'; - this.defaults = { - target: null, - toggleEvent: 'click', - height: false, // integer - width: false, // integer - animationOpen: 'slideDown', - animationClose: 'slideUp', - caretUp: false, - callbacks: ['open', 'opened', 'close', 'closed'] - }; - - // Parent Constructor - Kube.apply(this, arguments); - - // Services - this.utils = new Kube.Utils(); - this.detect = new Kube.Detect(); - - // Initialization - this.start(); - }; - - // Functionality - Kube.Dropdown.prototype = { - start: function() - { - this.buildClose(); - this.buildCaret(); - - if (this.detect.isMobile()) this.buildMobileAnimation(); - - this.$target.addClass('hide'); - this.$element.on(this.opts.toggleEvent + '.' + this.namespace, $.proxy(this.toggle, this)); - - }, - stop: function() - { - this.$element.off('.' + this.namespace); - this.$target.removeClass('open').addClass('hide'); - this.disableEvents(); - }, - buildMobileAnimation: function() - { - this.opts.animationOpen = 'fadeIn'; - this.opts.animationClose = 'fadeOut'; - }, - buildClose: function() - { - this.$close = this.$target.find('.close'); - }, - buildCaret: function() - { - this.$caret = this.getCaret(); - this.buildCaretPosition(); - }, - buildCaretPosition: function() - { - var height = this.$element.offset().top + this.$element.innerHeight() + this.$target.innerHeight(); - - if ($(document).height() > height) - { - return; - } - - this.opts.caretUp = true; - this.$caret.addClass('up'); - }, - getCaret: function() - { - return this.$element.find('.caret'); - }, - toggleCaretOpen: function() - { - if (this.opts.caretUp) this.$caret.removeClass('up').addClass('down'); - else this.$caret.removeClass('down').addClass('up'); - }, - toggleCaretClose: function() - { - if (this.opts.caretUp) this.$caret.removeClass('down').addClass('up'); - else this.$caret.removeClass('up').addClass('down'); - }, - toggle: function(e) - { - if (this.isOpened()) this.close(e); - else this.open(e); - }, - open: function(e) - { - if (e) e.preventDefault(); - - this.callback('open'); - $('.dropdown').removeClass('open').addClass('hide'); - - if (this.opts.height) this.$target.css('min-height', this.opts.height + 'px'); - if (this.opts.width) this.$target.width(this.opts.width); - - this.setPosition(); - this.toggleCaretOpen(); - - this.$target.animation(this.opts.animationOpen, $.proxy(this.onOpened, this)); - }, - close: function(e) - { - if (!this.isOpened()) - { - return; - } - - if (e) - { - if (this.shouldNotBeClosed(e.target)) - { - return; - } - - e.preventDefault(); - } - - this.utils.enableBodyScroll(); - this.callback('close'); - this.toggleCaretClose(); - - this.$target.animation(this.opts.animationClose, $.proxy(this.onClosed, this)); - }, - onClosed: function() - { - this.$target.removeClass('open'); - this.disableEvents(); - this.callback('closed'); - }, - onOpened: function() - { - this.$target.addClass('open'); - this.enableEvents(); - this.callback('opened'); - }, - isOpened: function() - { - return (this.$target.hasClass('open')); - }, - enableEvents: function() - { - if (this.detect.isDesktop()) - { - this.$target.on('mouseover.' + this.namespace, $.proxy(this.utils.disableBodyScroll, this.utils)) - .on('mouseout.' + this.namespace, $.proxy(this.utils.enableBodyScroll, this.utils)); - } - - $(document).on('scroll.' + this.namespace, $.proxy(this.setPosition, this)); - $(window).on('resize.' + this.namespace, $.proxy(this.setPosition, this)); - $(document).on('click.' + this.namespace + ' touchstart.' + this.namespace, $.proxy(this.close, this)); - $(document).on('keydown.' + this.namespace, $.proxy(this.handleKeyboard, this)); - this.$target.find('[data-action="dropdown-close"]').on('click.' + this.namespace, $.proxy(this.close, this)); - }, - disableEvents: function() - { - this.$target.off('.' + this.namespace); - $(document).off('.' + this.namespace); - $(window).off('.' + this.namespace); - }, - handleKeyboard: function(e) - { - if (e.which === 27) this.close(e); - }, - shouldNotBeClosed: function(el) - { - if ($(el).attr('data-action') === 'dropdown-close' || el === this.$close[0]) - { - return false; - } - else if ($(el).closest('.dropdown').length === 0) - { - return false; - } - - return true; - }, - isNavigationFixed: function() - { - return (this.$element.closest('.fixed').length !== 0); - }, - getPlacement: function(height) - { - return ($(document).height() < height) ? 'top' : 'bottom'; - }, - getOffset: function(position) - { - return (this.isNavigationFixed()) ? this.$element.position() : this.$element.offset(); - }, - getPosition: function() - { - return (this.isNavigationFixed()) ? 'fixed' : 'absolute'; - }, - setPosition: function() - { - if (this.detect.isMobile()) - { - this.$target.addClass('dropdown-mobile'); - return; - } - - var position = this.getPosition(); - var coords = this.getOffset(position); - var height = this.$target.innerHeight(); - var width = this.$target.innerWidth(); - var placement = this.getPlacement(coords.top + height + this.$element.innerHeight()); - var leftFix = ($(window).width() < (coords.left + width)) ? (width - this.$element.innerWidth()) : 0; - var top, left = coords.left - leftFix; - - if (placement === 'bottom') - { - if (!this.isOpened()) this.$caret.removeClass('up').addClass('down'); - - this.opts.caretUp = false; - top = coords.top + this.$element.outerHeight() + 1; - } - else - { - this.opts.animationOpen = 'show'; - this.opts.animationClose = 'hide'; - - if (!this.isOpened()) this.$caret.addClass('up').removeClass('down'); - - this.opts.caretUp = true; - top = coords.top - height - 1; - } - - this.$target.css({ position: position, top: top + 'px', left: left + 'px' }); - } - }; - - // Inheritance - Kube.Dropdown.inherits(Kube); - - // Plugin - Kube.Plugin.create('Dropdown'); - Kube.Plugin.autoload('Dropdown'); - -}(Kube)); -/** - * @library Kube Tabs - * @author Imperavi LLC - * @license MIT - */ -(function(Kube) -{ - Kube.Tabs = function(element, options) - { - this.namespace = 'tabs'; - this.defaults = { - equals: false, - active: false, // string (hash = tab id selector) - live: false, // class selector - hash: true, //boolean - callbacks: ['init', 'next', 'prev', 'open', 'opened', 'close', 'closed'] - }; - - // Parent Constructor - Kube.apply(this, arguments); - - // Initialization - this.start(); - }; - - // Functionality - Kube.Tabs.prototype = { - start: function() - { - if (this.opts.live !== false) this.buildLiveTabs(); - - this.tabsCollection = []; - this.hashesCollection = []; - this.currentHash = []; - this.currentItem = false; - - // items - this.$items = this.getItems(); - this.$items.each($.proxy(this.loadItems, this)); - - // tabs - this.$tabs = this.getTabs(); - - // location hash - this.currentHash = this.getLocationHash(); - - // close all - this.closeAll(); - - // active & height - this.setActiveItem(); - this.setItemHeight(); - - // callback - this.callback('init'); - - }, - getTabs: function() - { - return $(this.tabsCollection).map(function() - { - return this.toArray(); - }); - }, - getItems: function() - { - return this.$element.find('a'); - }, - loadItems: function(i, el) - { - var item = this.getItem(el); - - // set item identificator - item.$el.attr('rel', item.hash); - - // collect item - this.collectItem(item); - - // active - if (item.$parent.hasClass('active')) - { - this.currentItem = item; - this.opts.active = item.hash; - } - - // event - item.$el.on('click.tabs', $.proxy(this.open, this)); - - }, - collectItem: function(item) - { - this.tabsCollection.push(item.$tab); - this.hashesCollection.push(item.hash); - }, - buildLiveTabs: function() - { - var $layers = $(this.opts.live); - - if ($layers.length === 0) - { - return; - } - - this.$liveTabsList = $('
    '); - $layers.each($.proxy(this.buildLiveItem, this)); - - this.$element.html('').append(this.$liveTabsList); - - }, - buildLiveItem: function(i, tab) - { - var $tab = $(tab); - var $li = $('
  • '); - var $a = $(''); - var index = i + 1; - - $tab.attr('id', this.getLiveItemId($tab, index)); - - var hash = '#' + $tab.attr('id'); - var title = this.getLiveItemTitle($tab); - - $a.attr('href', hash).attr('rel', hash).text(title); - $li.append($a); - - this.$liveTabsList.append($li); - }, - getLiveItemId: function($tab, index) - { - return (typeof $tab.attr('id') === 'undefined') ? this.opts.live.replace('.', '') + index : $tab.attr('id'); - }, - getLiveItemTitle: function($tab) - { - return (typeof $tab.attr('data-title') === 'undefined') ? $tab.attr('id') : $tab.attr('data-title'); - }, - setActiveItem: function() - { - if (this.currentHash) - { - this.currentItem = this.getItemBy(this.currentHash); - this.opts.active = this.currentHash; - } - else if (this.opts.active === false) - { - this.currentItem = this.getItem(this.$items.first()); - this.opts.active = this.currentItem.hash; - } - - this.addActive(this.currentItem); - }, - addActive: function(item) - { - item.$parent.addClass('active'); - item.$tab.removeClass('hide').addClass('open'); - - this.currentItem = item; - }, - removeActive: function(item) - { - item.$parent.removeClass('active'); - item.$tab.addClass('hide').removeClass('open'); - - this.currentItem = false; - }, - next: function(e) - { - if (e) e.preventDefault(); - - var item = this.getItem(this.fetchElement('next')); - - this.open(item.hash); - this.callback('next', item); - - }, - prev: function(e) - { - if (e) e.preventDefault(); - - var item = this.getItem(this.fetchElement('prev')); - - this.open(item.hash); - this.callback('prev', item); - }, - fetchElement: function(type) - { - var element; - if (this.currentItem !== false) - { - // prev or next - element = this.currentItem.$parent[type]().find('a'); - - if (element.length === 0) - { - return; - } - } - else - { - // first - element = this.$items[0]; - } - - return element; - }, - open: function(e, push) - { - if (typeof e === 'undefined') return; - if (typeof e === 'object') e.preventDefault(); - - var item = (typeof e === 'object') ? this.getItem(e.target) : this.getItemBy(e); - this.closeAll(); - - this.callback('open', item); - this.addActive(item); - - // push state (doesn't need to push at the start) - this.pushStateOpen(push, item); - this.callback('opened', item); - }, - pushStateOpen: function(push, item) - { - if (push !== false && this.opts.hash !== false) - { - history.pushState(false, false, item.hash); - } - }, - close: function(num) - { - var item = this.getItemBy(num); - - if (!item.$parent.hasClass('active')) - { - return; - } - - this.callback('close', item); - this.removeActive(item); - this.pushStateClose(); - this.callback('closed', item); - - }, - pushStateClose: function() - { - if (this.opts.hash !== false) - { - history.pushState(false, false, ' '); - } - }, - closeAll: function() - { - this.$tabs.removeClass('open').addClass('hide'); - this.$items.parent().removeClass('active'); - }, - getItem: function(element) - { - var item = {}; - - item.$el = $(element); - item.hash = item.$el.attr('href'); - item.$parent = item.$el.parent(); - item.$tab = $(item.hash); - - return item; - }, - getItemBy: function(num) - { - var element = (typeof num === 'number') ? this.$items.eq(num-1) : this.$element.find('[rel="' + num + '"]'); - - return this.getItem(element); - }, - getLocationHash: function() - { - if (this.opts.hash === false) - { - return false; - } - - return (this.isHash()) ? top.location.hash : false; - }, - isHash: function() - { - return !(top.location.hash === '' || $.inArray(top.location.hash, this.hashesCollection) === -1); - }, - setItemHeight: function() - { - if (this.opts.equals) - { - var minHeight = this.getItemMaxHeight() + 'px'; - this.$tabs.css('min-height', minHeight); - } - }, - getItemMaxHeight: function() - { - var max = 0; - this.$tabs.each(function() - { - var h = $(this).height(); - max = h > max ? h : max; - }); - - return max; - } - }; - - // Inheritance - Kube.Tabs.inherits(Kube); - - // Plugin - Kube.Plugin.create('Tabs'); - Kube.Plugin.autoload('Tabs'); - -}(Kube)); -/** - * @library Kube Modal - * @author Imperavi LLC - * @license MIT - */ -(function($) -{ - $.modalcurrent = null; - $.modalwindow = function(options) - { - var opts = $.extend({}, options, { show: true }); - var $element = $(''); - - $element.modal(opts); - }; - -})(jQuery); - -(function(Kube) -{ - Kube.Modal = function(element, options) - { - this.namespace = 'modal'; - this.defaults = { - target: null, - show: false, - url: false, - header: false, - width: '600px', // string - height: false, // or string - maxHeight: false, - position: 'center', // top or center - overlay: true, - appendForms: false, - appendFields: false, - animationOpen: 'show', - animationClose: 'hide', - callbacks: ['open', 'opened', 'close', 'closed'] - }; - - // Parent Constructor - Kube.apply(this, arguments); - - // Services - this.utils = new Kube.Utils(); - this.detect = new Kube.Detect(); - - // Initialization - this.start(); - }; - - // Functionality - Kube.Modal.prototype = { - start: function() - { - if (!this.hasTarget()) - { - return; - } - - if (this.opts.show) this.load(); - else this.$element.on('click.' + this.namespace, $.proxy(this.load, this)); - }, - buildModal: function() - { - this.$modal = this.$target.find('.modal'); - this.$header = this.$target.find('.modal-header'); - this.$close = this.$target.find('.close'); - this.$body = this.$target.find('.modal-body'); - }, - buildOverlay: function() - { - if (this.opts.overlay === false) - { - return; - } - - if ($('#modal-overlay').length !== 0) - { - this.$overlay = $('#modal-overlay'); - } - else - { - this.$overlay = $('
  • "+e+"
  • \n"},a.prototype.checkbox=function(e){return" "},a.prototype.paragraph=function(e){return"

    "+e+"

    \n"},a.prototype.table=function(e,t){return t&&(t=""+t+""),"\n\n"+e+"\n"+t+"
    \n"},a.prototype.tablerow=function(e){return"\n"+e+"\n"},a.prototype.tablecell=function(e,t){var n=t.header?"th":"td";return(t.align?"<"+n+' align="'+t.align+'">':"<"+n+">")+e+"\n"},a.prototype.strong=function(e){return""+e+""},a.prototype.em=function(e){return""+e+""},a.prototype.codespan=function(e){return""+e+""},a.prototype.br=function(){return this.options.xhtml?"
    ":"
    "},a.prototype.del=function(e){return""+e+""},a.prototype.link=function(e,t,n){if(null===(e=d(this.options.sanitize,this.options.baseUrl,e)))return n;var r='
    "},a.prototype.image=function(e,t,n){if(null===(e=d(this.options.sanitize,this.options.baseUrl,e)))return n;var r=''+n+'":">"},a.prototype.text=function(e){return e},s.prototype.strong=s.prototype.em=s.prototype.codespan=s.prototype.del=s.prototype.text=function(e){return e},s.prototype.link=s.prototype.image=function(e,t,n){return""+n},s.prototype.br=function(){return""},l.parse=function(e,t){return new l(t).parse(e)},l.prototype.parse=function(e){this.inline=new i(e.links,this.options),this.inlineText=new i(e.links,y({},this.options,{renderer:new s})),this.tokens=e.reverse();for(var t="";this.next();)t+=this.tok();return t},l.prototype.next=function(){return this.token=this.tokens.pop(),this.token},l.prototype.peek=function(){return this.tokens[this.tokens.length-1]||0},l.prototype.parseText=function(){for(var e=this.token.text;"text"===this.peek().type;)e+="\n"+this.next().text;return this.inline.output(e)},l.prototype.tok=function(){switch(this.token.type){case"space":return"";case"hr":return this.renderer.hr();case"heading":return this.renderer.heading(this.inline.output(this.token.text),this.token.depth,p(this.inlineText.output(this.token.text)),this.slugger);case"code":return this.renderer.code(this.token.text,this.token.lang,this.token.escaped);case"table":var e,t,n,r,o="",i="";for(n="",e=0;e?@[\]^`{|}~]/g,"").replace(/\s/g,"-");if(this.seen.hasOwnProperty(t)){var n=t;do{this.seen[n]++,t=n+"-"+this.seen[n]}while(this.seen.hasOwnProperty(t))}return this.seen[t]=0,t},u.escapeTest=/[&<>"']/,u.escapeReplace=/[&<>"']/g,u.replacements={"&":"&","<":"<",">":">",'"':""","'":"'"},u.escapeTestNoEncode=/[<>"']|&(?!#?\w+;)/,u.escapeReplaceNoEncode=/[<>"']|&(?!#?\w+;)/g;var h={},m=/^$|^[a-z][a-z0-9+.-]*:|^[?#]/i;function g(){}function y(e){for(var t,n,r=1;r=0&&"\\"===n[o];)r=!r;return r?"|":" |"})).split(/ \|/),r=0;if(n.length>t)n.splice(t);else for(;n.lengthAn error occurred:

    "+u(e.message+"",!0)+"
    ";throw e}}g.exec=g,k.options=k.setOptions=function(e){return y(k.defaults,e),k},k.getDefaults=function(){return{baseUrl:null,breaks:!1,gfm:!0,headerIds:!0,headerPrefix:"",highlight:null,langPrefix:"language-",mangle:!0,pedantic:!1,renderer:new a,sanitize:!1,sanitizer:null,silent:!1,smartLists:!1,smartypants:!1,xhtml:!1}},k.defaults=k.getDefaults(),k.Parser=l,k.parser=l.parse,k.Renderer=a,k.TextRenderer=s,k.Lexer=r,k.lexer=r.lex,k.InlineLexer=i,k.inlineLexer=i.output,k.Slugger=c,k.parse=k,e.exports=k}(this||"undefined"!=typeof window&&window)}).call(this,n(5))},function(e,t,n){var r=n(9);e.exports=function(e,t){if(!r(e))return e;var n,o;if(t&&"function"==typeof(n=e.toString)&&!r(o=n.call(e)))return o;if("function"==typeof(n=e.valueOf)&&!r(o=n.call(e)))return o;if(!t&&"function"==typeof(n=e.toString)&&!r(o=n.call(e)))return o;throw TypeError("Can't convert object to primitive value")}},function(e,t){var n=0,r=Math.random();e.exports=function(e){return"Symbol("+String(void 0===e?"":e)+")_"+(++n+r).toString(36)}},function(e,t,n){var r=n(68),o=n(51),i=r("keys");e.exports=function(e){return i[e]||(i[e]=o(e))}},function(e,t,n){var r,o=n(18),i=n(171),a=n(78),s=n(40),l=n(108),c=n(70),u=n(52),p=u("IE_PROTO"),f=function(){},d=function(e){return" - - - diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/partials/devportal/manifest.html b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/partials/devportal/manifest.html deleted file mode 100644 index 3aa94b0d8e..0000000000 --- a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/partials/devportal/manifest.html +++ /dev/null @@ -1,8 +0,0 @@ - -{{ $string := (printf "window.__NGINX_DevPortal_Manifest__={paths:{docs:'%s',config:'%s'}};" ( .Params.devportal.spec | relURL ) ( .Params.devportal.config | relURL ) ) }} - -{{ $targetPath := "js/devportal/manifest.js" }} -{{ $manifest := $string | resources.FromString $targetPath | fingerprint "sha512" }} - - - diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/partials/devportal/splash.html b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/partials/devportal/splash.html deleted file mode 100644 index 960f66d1c0..0000000000 --- a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/partials/devportal/splash.html +++ /dev/null @@ -1,5 +0,0 @@ -{{/* load devportal splash script */}} - -{{ $splashjs := resources.Get "js/devportal/splash.js" | fingerprint "sha512" }} - - diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/partials/devportal/style.html b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/partials/devportal/style.html deleted file mode 100644 index 7f756a506d..0000000000 --- a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/partials/devportal/style.html +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/partials/draft-badge.html b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/partials/draft-badge.html deleted file mode 100644 index e3711d9770..0000000000 --- a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/partials/draft-badge.html +++ /dev/null @@ -1,3 +0,0 @@ - diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/partials/favicon.html b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/partials/favicon.html deleted file mode 100644 index 7d0ba68c6d..0000000000 --- a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/partials/favicon.html +++ /dev/null @@ -1 +0,0 @@ - diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/partials/feedback-form.html b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/partials/feedback-form.html deleted file mode 100644 index 44c1fc74e2..0000000000 --- a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/partials/feedback-form.html +++ /dev/null @@ -1,34 +0,0 @@ -
    -
    -
    - - -
    - -
    - - - We'll never share your email with anyone else. -
    - -
    - - - If you are reporting a doc bug, please provide the URL of the page where you found the issue. -
    - -
    - - -
    - -
    - All fields marked with an asterisk (*) are required. -
    - -
    - -
    - -
    -
    diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/partials/footer.html b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/partials/footer.html deleted file mode 100644 index 1a45ed65a7..0000000000 --- a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/partials/footer.html +++ /dev/null @@ -1,92 +0,0 @@ -
    diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/partials/header.html b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/partials/header.html deleted file mode 100644 index a9b9c4a166..0000000000 --- a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/partials/header.html +++ /dev/null @@ -1,37 +0,0 @@ -{{ define "header" }} - - -{{ end }} diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/partials/list-main.html b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/partials/list-main.html deleted file mode 100644 index 4764807617..0000000000 --- a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/partials/list-main.html +++ /dev/null @@ -1,79 +0,0 @@ -
    -
    - -
    - -
    - -
    -
    - {{ if .Sections }} - {{ range .Sections }} -
    -
    -

    - - {{ .Title }} -

    - {{/*}}

    - {{ if .Description }}{{ .Description | markdownify }}{{ end }} -

    {{*/}} - {{ if and (eq .Site.Params.useSectionPageLists "true") (.Pages) }} -
      - {{ range first 5 .Pages.ByWeight }} -
    • - {{ .Title }} -
    • - {{ end }} - {{ if gt .Pages "5" }} -
    • - More... -
    • - {{ end }} -
    - {{ end }} -
    -
    - {{ end }} - {{ end }} - - {{ range (.Paginate ( where .Pages.ByWeight ".Kind" "!=" "section" )).Pages }} -
    -
    -

    - - {{ .Title }} -

    - {{/*}} -

    - {{ if .Description }} {{ .Description | markdownify }}{{ end }} -

    {{*/}} -
    -
    - {{ end }} -
    -
    -
    - - {{ if not .IsHome }} -
    - {{ partial "pagination.html" . }} -
    - {{ end }} - - -
    diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/partials/load_jquery.html b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/partials/load_jquery.html deleted file mode 100644 index bc00f10623..0000000000 --- a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/partials/load_jquery.html +++ /dev/null @@ -1 +0,0 @@ - diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/partials/meta.html b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/partials/meta.html deleted file mode 100644 index 9fd1bf7d99..0000000000 --- a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/partials/meta.html +++ /dev/null @@ -1,73 +0,0 @@ - - - - -{{ block "title" . }}{{ if .IsHome }}{{ .Site.Title }}{{else}}{{ .Title }}{{end }}{{ if and ( not .IsHome ) ( .Site.Title ) }} | {{ .Site.Title }}{{end}}{{ end }} - -{{ with .Description }} - -{{ end }} - -{{ if .Params.categories }} - -{{ end }} - - - - - -{{ $default_noindex_kinds := slice "section" "taxonomy" "taxonomyTerm" }} -{{ $noindex_kinds := .Site.Params.noindex_kinds | default $default_noindex_kinds }} -{{ $is_noindex_true := and (isset .Params "noindex") .Params.noindex }} -{{ if or (in $noindex_kinds .Kind) ($is_noindex_true) }} - -{{ end }} - - - - - - - - - - - -{{ if .Page.Lastmod }} - -{{ end }} - - -{{/* set custom CSP to load styles and scripts with special handling for GTM scripts (requires unsafe-inline) and Dev Portal page(s) (requires 'unsafe-eval') */}} - -{{/* end */}} - - - - - - - - - - \ No newline at end of file diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/partials/page-summary.html b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/partials/page-summary.html deleted file mode 100644 index b9ea8cc15a..0000000000 --- a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/partials/page-summary.html +++ /dev/null @@ -1,9 +0,0 @@ -
  • -

    - {{ .Title }} -

    -
    - {{ if .Description }} -

    {{ .Description | markdownify }}

    - {{ end }} -
  • diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/partials/pagination.html b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/partials/pagination.html deleted file mode 100644 index e5d922845d..0000000000 --- a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/partials/pagination.html +++ /dev/null @@ -1,65 +0,0 @@ -{{ $pag := $.Paginator }} -{{ if gt $pag.TotalPages 1 }} - -{{ end }} \ No newline at end of file diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/partials/post/byauthor.html b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/partials/post/byauthor.html deleted file mode 100644 index 579a64d3e2..0000000000 --- a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/partials/post/byauthor.html +++ /dev/null @@ -1,20 +0,0 @@ -

    - Published - {{ if ne .Site.Params.hide_author true }} - {{ with .Params.author }} - by - {{ else }} - by - {{ end }} - {{ end }} - - {{ with .Params.categories }} - in {{ delimit (apply (apply (sort .) "partial" "post/category-link" ".") "chomp" ".") ", " " and " }} - {{ end }} - {{ with .Params.tags }} - and tagged {{ delimit (apply (apply (sort .) "partial" "post/tag-link" ".") "chomp" ".") ", " " and " }} - {{ end }} - using {{ .WordCount }} words. -

    diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/partials/post/category-link.html b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/partials/post/category-link.html deleted file mode 100644 index 6bee96b1af..0000000000 --- a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/partials/post/category-link.html +++ /dev/null @@ -1 +0,0 @@ -{{ . }} diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/partials/post/meta.html b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/partials/post/meta.html deleted file mode 100644 index 62b4b451de..0000000000 --- a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/partials/post/meta.html +++ /dev/null @@ -1,14 +0,0 @@ - - - -{{ .ReadingTime }} minute read - - - -{{ if .PublishDate.IsZero }} - Published: -{{ else if lt .PublishDate .Lastmod }} - Modified: -{{ else }} - Published: -{{ end }} diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/partials/post/related-content.html b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/partials/post/related-content.html deleted file mode 100644 index 49310b6083..0000000000 --- a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/partials/post/related-content.html +++ /dev/null @@ -1,16 +0,0 @@ -{{ range first 1 (where (where .Site.Pages ".Params.tags" "intersect" .Params.tags) "Permalink" "!=" .Permalink) }} - {{ $.Scratch.Set "has_related" true }} -{{ end }} - -{{ if $.Scratch.Get "has_related" }} - -{{ end }} diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/partials/post/tag-link.html b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/partials/post/tag-link.html deleted file mode 100644 index 8c03421001..0000000000 --- a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/partials/post/tag-link.html +++ /dev/null @@ -1 +0,0 @@ -{{ . }} diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/partials/previous-next-links-in-section-with-title.html b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/partials/previous-next-links-in-section-with-title.html deleted file mode 100644 index 16168a7da7..0000000000 --- a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/partials/previous-next-links-in-section-with-title.html +++ /dev/null @@ -1,17 +0,0 @@ -{{ if or .PrevInSection .NextInSection }} -{{/* this div holds these a tags as a unit for flex-box display */}} - - -{{ end }} \ No newline at end of file diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/partials/previous-next-links-in-section.html b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/partials/previous-next-links-in-section.html deleted file mode 100644 index 01ce27b2f7..0000000000 --- a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/partials/previous-next-links-in-section.html +++ /dev/null @@ -1,16 +0,0 @@ -{{ if or .PrevInSection .NextInSection }} -{{/* this div holds these a tags as a unit for flex-box display */}} - -{{ end }} \ No newline at end of file diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/partials/products-menu.html b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/partials/products-menu.html deleted file mode 100644 index d57c1dfca9..0000000000 --- a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/partials/products-menu.html +++ /dev/null @@ -1,14 +0,0 @@ -{{ $currentPage := . }} - -{{ if in .Site.Params.buildtype "package" }} - diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/shortcodes/fa.html b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/shortcodes/fa.html deleted file mode 100644 index 69734dc3a7..0000000000 --- a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/shortcodes/fa.html +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/shortcodes/heading.html b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/shortcodes/heading.html deleted file mode 100644 index 01e6cf2f26..0000000000 --- a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/shortcodes/heading.html +++ /dev/null @@ -1,4 +0,0 @@ - -{{- $heading := .Get 0 -}} -{{- $heading := printf "%s_heading" $heading -}} -{{- T $heading | safeHTML -}} \ No newline at end of file diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/shortcodes/icon-bug.html b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/shortcodes/icon-bug.html deleted file mode 100644 index bd9d8b17e2..0000000000 --- a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/shortcodes/icon-bug.html +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/shortcodes/icon-feature.html b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/shortcodes/icon-feature.html deleted file mode 100644 index 31bddbc58e..0000000000 --- a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/shortcodes/icon-feature.html +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/shortcodes/icon-resolved.html b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/shortcodes/icon-resolved.html deleted file mode 100644 index 93145a9efb..0000000000 --- a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/shortcodes/icon-resolved.html +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/shortcodes/img.html b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/shortcodes/img.html deleted file mode 100644 index ec935e96a9..0000000000 --- a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/shortcodes/img.html +++ /dev/null @@ -1,28 +0,0 @@ - - {{- if .Get "link" -}} - - {{- end }} - {{ with .Get - {{- if .Get "link" }}{{ end -}} - {{- if or (or (.Get "title") (.Get "caption")) (.Get "attr") -}} -
    - {{ with (.Get "title") -}} -

    {{ . }}

    - {{- end -}} - {{- if or (.Get "caption") (.Get "attr") -}}

    - {{- .Get "caption" | markdownify -}} - {{- with .Get "attrlink" }} - - {{- end -}} - {{- .Get "attr" | markdownify -}} - {{- if .Get "attrlink" }}{{ end }}

    - {{- end }} -
    - {{- end }} - \ No newline at end of file diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/shortcodes/important.html b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/shortcodes/important.html deleted file mode 100644 index ccc342c15b..0000000000 --- a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/shortcodes/important.html +++ /dev/null @@ -1,3 +0,0 @@ -
    -
    Important:
    {{ .Inner | markdownify }}
    -
    diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/shortcodes/include.html b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/shortcodes/include.html deleted file mode 100644 index 042e17ed8c..0000000000 --- a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/shortcodes/include.html +++ /dev/null @@ -1,27 +0,0 @@ -{{/* This will try to find a resource in the "includes" bundle */}} -{{- $name := .Get 0 -}} -{{- if $name -}} - {{- $bundle := site.GetPage "page" "includes" -}} - {{- with $bundle -}} - {{- $pattern := printf "%s*" $name -}} - {{- range $bundle.Resources -}} - {{- end -}} - {{- $resource := $bundle.Resources.GetMatch $pattern -}} - {{- with $resource -}} - {{- .Content | safeHTML -}} - {{- else -}} - {{/* It is not a resource in the includes bundle. Try to find the page relative to the current. */}} - {{- $path := path.Join $.Page.File.Dir $name -}} - {{- $page := site.GetPage "page" $path -}} - {{- with $page }} - {{ .Content }} - {{- else -}} - {{ errorf "[%s] no Resource or Page matching %q." $.Page.Lang ($pattern | safeHTML ) }} - {{- end -}} - {{- end -}} - {{- else -}} - {{ errorf "[%s] the 'includes' bundle was not found." $.Page.Lang }} - {{- end -}} -{{- else -}} - {{- errorf "[%s] missing resource name in include for page %q" $.Page.Lang $.Page.Path -}} -{{- end -}} diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/shortcodes/link.html b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/shortcodes/link.html deleted file mode 100644 index bea0851b87..0000000000 --- a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/shortcodes/link.html +++ /dev/null @@ -1,13 +0,0 @@ -{{ $ref := .Get 0 | relURL }} - -{{ if .Get 1 }} - {{ $text := .Get 1 | markdownify }} - {{$text}} -{{ else if .Get 0 }} - {{ $text := .Get 0 | markdownify }} - {{$text}} -{{ else }} - - {{ errorf "missing value for param 'name': %s" .Position }} - -{{ end }} \ No newline at end of file diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/shortcodes/metrics.html b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/shortcodes/metrics.html deleted file mode 100644 index 3388b5f513..0000000000 --- a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/shortcodes/metrics.html +++ /dev/null @@ -1,76 +0,0 @@ - -{{ $jsPath := .Get "path" }} -{{ $metricsRef := getJSON $jsPath }} - - - -
    -
    -
    - {{ range where $metricsRef "visible" true}} - {{ $p := . }} - - -

    {{$p.name}} -

    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    description{{$p.description}}
    type{{$p.type}}
    categories{{$p.categories}}
    source{{$p.source}}
    rollup_aggregate{{$p.rollup_aggregate}}
    unit{{$p.unit}}
    aggregations{{$p.aggregations}}
    dimensions -
      - {{range sort $p.dimensions}} - {{$dim := .}} -
    • {{$dim}}
    • - {{end}} -
    -
    - -
    - - {{ end }} -
    - -
    -
      - {{ range where $metricsRef "visible" true}} - {{ $p := . }} -
    • - {{$p.name}} -
    • - - {{ end }} -
    -
    - -
    \ No newline at end of file diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/shortcodes/note.html b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/shortcodes/note.html deleted file mode 100644 index 95b0ebf27a..0000000000 --- a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/shortcodes/note.html +++ /dev/null @@ -1,3 +0,0 @@ -
    -
    Note:
    {{ .Inner | markdownify }}
    -
    diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/shortcodes/openapi.html b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/shortcodes/openapi.html deleted file mode 100644 index 7c72e637e5..0000000000 --- a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/shortcodes/openapi.html +++ /dev/null @@ -1,4 +0,0 @@ - - - diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/shortcodes/raw-html.html b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/shortcodes/raw-html.html deleted file mode 100644 index 520ec17864..0000000000 --- a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/shortcodes/raw-html.html +++ /dev/null @@ -1,2 +0,0 @@ - -{{.Inner}} \ No newline at end of file diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/shortcodes/readfile.html b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/shortcodes/readfile.html deleted file mode 100644 index 36400ac55d..0000000000 --- a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/shortcodes/readfile.html +++ /dev/null @@ -1,8 +0,0 @@ -{{$file := .Get "file"}} -{{- if eq (.Get "markdown") "true" -}} -{{- $file | readFile | markdownify -}} -{{- else if (.Get "highlight") -}} -{{- highlight ($file | readFile) (.Get "highlight") "" -}} -{{- else -}} -{{ $file | readFile | safeHTML }} -{{- end -}} \ No newline at end of file diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/shortcodes/see-also.html b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/shortcodes/see-also.html deleted file mode 100644 index fdd716031c..0000000000 --- a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/shortcodes/see-also.html +++ /dev/null @@ -1,3 +0,0 @@ -
    -
    See Also:
    {{ .Inner | markdownify }}
    -
    diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/shortcodes/shortversions.html b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/shortcodes/shortversions.html deleted file mode 100644 index b6cfbd7459..0000000000 --- a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/shortcodes/shortversions.html +++ /dev/null @@ -1,69 +0,0 @@ -{{/* Use this shortcode to display the versions that the document applies to - Parameters: "first version" "last version" "product" - First version can be a version number, e.g. "3.18", or "first" to use as the 1st version to show - Last version can be a version number, e.g. "3.21", to show as the 2nd version to show, or it can be "latest" to show 'and later' - Product can be "ctrlvers" "apimvers" "adcvers" "nimvers" "acmvers" */}} - {{ $start := string (.Get 0) }} - {{ $end := string (.Get 1) }} - {{ $product := string (.Get 2) }} - {{ $temp := slice }} - {{ $result := ""}} - {{ $ver := slice }} - {{ $prodname := ""}} - {{ $istart := 0}} - {{ $iend := 100}} - {{ $islatest := 0}} - {{if eq $product "apimvers" }} - {{ $ver = $.Site.Params.apimvers }} - {{ $prodname = "NGINX Controller API Management module" }} - {{else if eq $product "adcvers" }} - {{ $ver = $.Site.Params.adcvers }} - {{ $prodname = "NGINX Controller App Delivery module" }} - {{else if eq $product "nimvers" }} - {{ $ver = $.Site.Params.nimvers }} - {{ $prodname = "NGINX Management Suite Instance Manager" }} - {{else if eq $product "acmvers" }} - {{ $ver = $.Site.Params.acmvers }} - {{ $prodname = "NGINX Management Suite API Connectivity Manager" }} - {{else}} - {{ $ver = $.Site.Params.ctrlvers }} - {{ $prodname = "NGINX Controller" }} - {{end}} - {{if eq $start "first"}} - {{$start = first 1 $ver}} - {{end}} - {{if eq $end "latest"}} - {{$islatest = true}} - {{$end = last 1 $ver}} - {{end}} - {{ $.Scratch.Set "counter" 0 }} - {{ range $ver }} - {{ $index := $.Scratch.Get "counter"}} - {{ $current := index $ver $index}} - {{if eq $current $start }} - {{$istart = $index}} - {{end}} - {{if eq $current $end}} - {{$iend = $index}} - {{end}} - {{ $.Scratch.Set "counter" (add ($.Scratch.Get "counter") 1) }} - {{ end }} - {{ $temp = after $istart $ver }} - {{ $iend = add (sub $iend $istart) 1 }} - {{ $temp = first $iend $temp }} - {{ $result = delimit $temp ", " " and " }} - {{ if $islatest}} -
    -
    -

    This documentation applies to {{$prodname}} {{$start}} and later.

    -
    - {{ if eq $product "nimvers" }} -
    - {{end}} - {{else}} -
    -
    -

    This documentation applies to {{$prodname}} {{$start}} - {{$end}}.

    -
    -
    - {{end}} \ No newline at end of file diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/shortcodes/survey.html b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/shortcodes/survey.html deleted file mode 100644 index 944749bf39..0000000000 --- a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/shortcodes/survey.html +++ /dev/null @@ -1,3 +0,0 @@ -
    -
    {{ .Inner | markdownify }}
    -
    diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/shortcodes/tab.html b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/shortcodes/tab.html deleted file mode 100644 index 52ee3d64ef..0000000000 --- a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/shortcodes/tab.html +++ /dev/null @@ -1,19 +0,0 @@ -{{ if .Parent }} - {{ $name := trim (.Get "name") " " }} - {{ $include := trim (.Get "include") " "}} - {{ $codelang := .Get "codelang" }} - {{ if not (.Parent.Scratch.Get "tabs") }} - {{ .Parent.Scratch.Set "tabs" slice }} - {{ end }} - {{ with .Inner }} - {{ if $codelang }} - {{ $.Parent.Scratch.Add "tabs" (dict "name" $name "content" (highlight . $codelang "") ) }} - {{ else }} - {{ $.Parent.Scratch.Add "tabs" (dict "name" $name "content" . ) }} - {{ end }} - {{ else }} - {{ $.Parent.Scratch.Add "tabs" (dict "name" $name "include" $include "codelang" $codelang) }} - {{ end }} -{{ else }} - {{- errorf "[%s] %q: tab shortcode missing its parent" site.Language.Lang .Page.Path -}} -{{ end}} \ No newline at end of file diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/shortcodes/table.html b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/shortcodes/table.html deleted file mode 100644 index a285af5320..0000000000 --- a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/shortcodes/table.html +++ /dev/null @@ -1,17 +0,0 @@ -{{/* Use this shortcode to wrap big tables that you want to have scrollbars when being shown in smaller screens */}} - -
    -{{.Inner}} -
    \ No newline at end of file diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/shortcodes/tabs.html b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/shortcodes/tabs.html deleted file mode 100644 index aeb9582b28..0000000000 --- a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/shortcodes/tabs.html +++ /dev/null @@ -1,50 +0,0 @@ -{{- .Page.Scratch.Add "tabset-counter" 1 -}} -{{- $tab_set_id := .Get "name" | default (printf "tabset-%s-%d" (.Page.RelPermalink) (.Page.Scratch.Get "tabset-counter") ) | anchorize -}} -{{- $tabs := .Scratch.Get "tabs" -}} -{{- if .Inner -}}{{- /* We don't use the inner content, but Hugo will complain if we don't reference it. */ -}}{{- end -}} - -
    -{{- range $i, $e := $tabs -}} -{{- $id := printf "%s-%d" $tab_set_id $i -}} -{{- if (eq $i 0) -}} -
    -{{ else }} -
    -{{ end }} -

    - {{- with .content -}} - {{- . -}} - {{- else -}} - {{- if eq $.Page.BundleType "leaf" -}} - {{- /* find the file somewhere inside the bundle. Note the use of double asterisk */ -}} - {{- with $.Page.Resources.GetMatch (printf "**%s*" .include) -}} - {{- if ne .ResourceType "page" -}} - {{- /* Assume it is a file that needs code highlighting. */ -}} - {{- $codelang := $e.codelang | default ( path.Ext .Name | strings.TrimPrefix ".") -}} - {{- highlight .Content $codelang "" -}} - {{- else -}} - {{- .Content -}} - {{- end -}} - {{- end -}} - {{- else -}} - {{- $path := path.Join $.Page.File.Dir .include -}} - {{- $page := site.GetPage "page" $path -}} - {{- with $page -}} - {{- .Content -}} - {{- else -}} - {{- errorf "[%s] tabs include not found for path %q" site.Language.Lang $path -}} - {{- end -}} - {{- end -}} - {{- end -}} -

    -{{- end -}} -
    diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/shortcodes/tip.html b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/shortcodes/tip.html deleted file mode 100644 index 93ec44b0fb..0000000000 --- a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/shortcodes/tip.html +++ /dev/null @@ -1,3 +0,0 @@ -
    -
    Tip:
    {{ .Inner | markdownify }}
    -
    diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/shortcodes/versions.html b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/shortcodes/versions.html deleted file mode 100644 index 9d4488ff61..0000000000 --- a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/shortcodes/versions.html +++ /dev/null @@ -1,56 +0,0 @@ -{{/* Use this shortcode to display a list of controller versions that the document applies to - Parameters: "first version" "last version" "product" - First version can be a version number, e.g. "3.18", or "first" to use the lowest value in the array - Last version can be a version number, e.g. "3.21", or "latest" to use the highest value in the array - Product can be "ctrlvers" "apimvers" "adcvers" "nimvers" "acmvers" */}} - {{ $start := string (.Get 0) }} - {{ $end := string (.Get 1) }} - {{ $product := string (.Get 2) }} - {{ $temp := slice }} - {{ $result := ""}} - {{ $ver := slice }} - {{ $prodname := ""}} - {{ $istart := 0}} - {{ $iend := 100}} - {{if eq $product "apimvers" }} - {{ $ver = $.Site.Params.apimvers }} - {{ $prodname = "NGINX Controller API Management module" }} - {{else if eq $product "adcvers" }} - {{ $ver = $.Site.Params.adcvers }} - {{ $prodname = "NGINX Controller App Delivery module" }} - {{else if eq $product "nimvers" }} - {{ $ver = $.Site.Params.nimvers }} - {{ $prodname = "NGINX Management Suite Instance Manager" }} - {{else if eq $product "acmvers" }} - {{ $ver = $.Site.Params.acmvers }} - {{ $prodname = "NGINX Management Suite API Connectivity Manager" }} - {{else}} - {{ $ver = $.Site.Params.ctrlvers }} - {{ $prodname = "NGINX Controller" }} - {{end}} - {{if eq $start "first"}} - {{$start = first 1 $ver}} - {{end}} - {{if eq $end "latest"}} - {{$end = last 1 $ver}} - {{end}} - {{ $.Scratch.Set "counter" 0 }} - {{ range $ver }} - {{ $index := $.Scratch.Get "counter"}} - {{ $current := index $ver $index}} - {{if eq $current $start }} - {{$istart = $index}} - {{end}} - {{if eq $current $end}} - {{$iend = $index}} - {{end}} - {{ $.Scratch.Set "counter" (add ($.Scratch.Get "counter") 1) }} - {{ end }} - {{ $temp = after $istart $ver }} - {{ $iend = add (sub $iend $istart) 1 }} - {{ $temp = first $iend $temp }} - {{ $result = delimit $temp ", " " and " }} -
    -
    -

    This documentation applies to the following versions of {{$prodname}}: {{$result}}.

    -
    \ No newline at end of file diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/shortcodes/warning.html b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/shortcodes/warning.html deleted file mode 100644 index 40565f8dbe..0000000000 --- a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/shortcodes/warning.html +++ /dev/null @@ -1,3 +0,0 @@ -
    -
    Warning:
    {{ .Inner | markdownify }}
    -
    diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/success/single.html b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/success/single.html deleted file mode 100644 index 6ce6cb4a5f..0000000000 --- a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/success/single.html +++ /dev/null @@ -1,23 +0,0 @@ -{{ define "main" }} - -
    -
    -
    -
    -

    {{ .Title }}

    -
    - -
    -

    Your comments have been received. - We appreciate you taking the time to give us feedback!

    -
    - - -
    -
    -
    - -{{ end }} diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/topics/concept.html b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/topics/concept.html deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/topics/task.html b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/topics/task.html deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/topics/tutorial.html b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/layouts/topics/tutorial.html deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/package.json b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/package.json deleted file mode 100644 index 315d9eb55c..0000000000 --- a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/package.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "name": "controller-docs", - "version": "1.0.0", - "description": "Documentation for NGINX Controller", - "main": "index.js", - "dependencies": { - "redoc": "^2.0.0-rc.36" - }, - "devDependencies": { - "@fortawesome/fontawesome-free": "^5.14.0", - "autoprefixer": "^9.8.6", - "mobx": "^5.15.4", - "postcss-cli": "^7.1.1", - "react": "^16.13.1", - "react-dom": "^16.13.1", - "styled-components": "^4.4.1", - "swagger-cli": "^3.0.1", - "swagger-parser": "9.0.1" - }, - "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" - }, - "repository": { - "type": "git", - "url": "git@gitswarm.f5net.com:indigo/product/controller-docs.git" - }, - "author": "F5, Inc.", - "license": "SEE LICENSE IN LICENSE" -} diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/nginx-font/fonts/nginx-font.eot b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/nginx-font/fonts/nginx-font.eot deleted file mode 100644 index 469ad93abb..0000000000 Binary files a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/nginx-font/fonts/nginx-font.eot and /dev/null differ diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/nginx-font/fonts/nginx-font.svg b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/nginx-font/fonts/nginx-font.svg deleted file mode 100644 index 321d7063fd..0000000000 --- a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/nginx-font/fonts/nginx-font.svg +++ /dev/null @@ -1,41 +0,0 @@ - - - -Generated by Fontastic.me - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/nginx-font/fonts/nginx-font.ttf b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/nginx-font/fonts/nginx-font.ttf deleted file mode 100644 index d8c704a49f..0000000000 Binary files a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/nginx-font/fonts/nginx-font.ttf and /dev/null differ diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/nginx-font/fonts/nginx-font.woff b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/nginx-font/fonts/nginx-font.woff deleted file mode 100644 index ea0c6e1f2d..0000000000 Binary files a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/nginx-font/fonts/nginx-font.woff and /dev/null differ diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/nginx-font/icons-reference.html b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/nginx-font/icons-reference.html deleted file mode 100644 index 180e907ca7..0000000000 --- a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/nginx-font/icons-reference.html +++ /dev/null @@ -1,317 +0,0 @@ - - - - - - - Font Reference - NGINX Font - - - - - -
    -

    NGINX Font

    -

    This font was created withFontastic

    -

    CSS mapping

    -
      -
    • -
      - -
    • -
    • -
      - -
    • -
    • -
      - -
    • -
    • -
      - -
    • -
    • -
      - -
    • -
    • -
      - -
    • -
    • -
      - -
    • -
    • -
      - -
    • -
    • -
      - -
    • -
    • -
      - -
    • -
    • -
      - -
    • -
    • -
      - -
    • -
    • -
      - -
    • -
    • -
      - -
    • -
    • -
      - -
    • -
    • -
      - -
    • -
    • -
      - -
    • -
    • - - -
    • -
    • -
      - -
    • -
    • -
      - -
    • -
    • -
      - -
    • -
    • -
      - -
    • -
    • -
      - -
    • -
    • -
      - -
    • -
    • -
      - -
    • -
    • -
      - -
    • -
    • -
      - -
    • -
    • -
      - -
    • -
    • -
      - -
    • -
    • -
      - -
    • -
    • -
      - -
    • -
    -

    Character mapping

    -
      -
    • -
      - -
    • -
    • -
      - -
    • -
    • -
      - -
    • -
    • -
      - -
    • -
    • -
      - -
    • -
    • -
      - -
    • -
    • -
      - -
    • -
    • -
      - -
    • -
    • -
      - -
    • -
    • -
      - -
    • -
    • -
      - -
    • -
    • -
      - -
    • -
    • -
      - -
    • -
    • -
      - -
    • -
    • -
      - -
    • -
    • -
      - -
    • -
    • -
      - -
    • -
    • -
      - -
    • -
    • -
      - -
    • -
    • -
      - -
    • -
    • -
      - -
    • -
    • -
      - -
    • -
    • -
      - -
    • -
    • -
      - -
    • -
    • -
      - -
    • -
    • -
      - -
    • -
    • -
      - -
    • -
    • -
      - -
    • -
    • -
      - -
    • -
    • -
      - -
    • -
    • -
      - -
    • -
    -
    - - diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-Black.eot b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-Black.eot deleted file mode 100644 index 1bb20ab72d..0000000000 Binary files a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-Black.eot and /dev/null differ diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-Black.svg b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-Black.svg deleted file mode 100644 index 29fb136248..0000000000 --- a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-Black.svg +++ /dev/null @@ -1,11487 +0,0 @@ - - - - -Created by FontForge 20170910 at Wed Mar 29 14:02:20 2017 - By Jimmy Wärting -Copyright 2011 Google Inc. All Rights Reserved. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-Black.ttf b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-Black.ttf deleted file mode 100644 index 3aece6b118..0000000000 Binary files a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-Black.ttf and /dev/null differ diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-Black.woff b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-Black.woff deleted file mode 100644 index 791ccfea6b..0000000000 Binary files a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-Black.woff and /dev/null differ diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-Black.woff2 b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-Black.woff2 deleted file mode 100644 index fbd8bb5529..0000000000 Binary files a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-Black.woff2 and /dev/null differ diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-BlackItalic.eot b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-BlackItalic.eot deleted file mode 100644 index ca3d8873e0..0000000000 Binary files a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-BlackItalic.eot and /dev/null differ diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-BlackItalic.svg b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-BlackItalic.svg deleted file mode 100644 index 0a7bf47fd5..0000000000 --- a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-BlackItalic.svg +++ /dev/null @@ -1,11613 +0,0 @@ - - - - -Created by FontForge 20170925 at Wed Mar 29 14:02:25 2017 - By Jimmy Wärting -Copyright 2011 Google Inc. All Rights Reserved. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-BlackItalic.ttf b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-BlackItalic.ttf deleted file mode 100644 index 9fa2197885..0000000000 Binary files a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-BlackItalic.ttf and /dev/null differ diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-BlackItalic.woff b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-BlackItalic.woff deleted file mode 100644 index 02de6bec15..0000000000 Binary files a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-BlackItalic.woff and /dev/null differ diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-BlackItalic.woff2 b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-BlackItalic.woff2 deleted file mode 100644 index df3c3f4472..0000000000 Binary files a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-BlackItalic.woff2 and /dev/null differ diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-Bold.eot b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-Bold.eot deleted file mode 100644 index a4defea4c7..0000000000 Binary files a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-Bold.eot and /dev/null differ diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-Bold.svg b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-Bold.svg deleted file mode 100644 index 70e8b2cf4e..0000000000 --- a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-Bold.svg +++ /dev/null @@ -1,11535 +0,0 @@ - - - - -Created by FontForge 20170925 at Wed Mar 29 14:02:30 2017 - By Jimmy Wärting -Copyright 2011 Google Inc. All Rights Reserved. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-Bold.ttf b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-Bold.ttf deleted file mode 100644 index 5eda42cef2..0000000000 Binary files a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-Bold.ttf and /dev/null differ diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-Bold.woff b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-Bold.woff deleted file mode 100644 index e467192a58..0000000000 Binary files a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-Bold.woff and /dev/null differ diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-Bold.woff2 b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-Bold.woff2 deleted file mode 100644 index 76817cc761..0000000000 Binary files a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-Bold.woff2 and /dev/null differ diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-BoldItalic.eot b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-BoldItalic.eot deleted file mode 100644 index 01197d4e79..0000000000 Binary files a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-BoldItalic.eot and /dev/null differ diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-BoldItalic.svg b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-BoldItalic.svg deleted file mode 100644 index 3112d55fa7..0000000000 --- a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-BoldItalic.svg +++ /dev/null @@ -1,11622 +0,0 @@ - - - - -Created by FontForge 20170910 at Wed Mar 29 14:02:34 2017 - By Jimmy Wärting -Copyright 2011 Google Inc. All Rights Reserved. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-BoldItalic.ttf b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-BoldItalic.ttf deleted file mode 100644 index 153329b3e7..0000000000 Binary files a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-BoldItalic.ttf and /dev/null differ diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-BoldItalic.woff b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-BoldItalic.woff deleted file mode 100644 index aa9888f90f..0000000000 Binary files a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-BoldItalic.woff and /dev/null differ diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-BoldItalic.woff2 b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-BoldItalic.woff2 deleted file mode 100644 index 5290917b5d..0000000000 Binary files a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-BoldItalic.woff2 and /dev/null differ diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-Italic.eot b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-Italic.eot deleted file mode 100644 index 8c70b47302..0000000000 Binary files a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-Italic.eot and /dev/null differ diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-Italic.svg b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-Italic.svg deleted file mode 100644 index ba9147ed55..0000000000 --- a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-Italic.svg +++ /dev/null @@ -1,11192 +0,0 @@ - - - - -Created by FontForge 20170925 at Wed Mar 29 14:02:39 2017 - By Jimmy Wärting -Copyright 2011 Google Inc. All Rights Reserved. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-Italic.ttf b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-Italic.ttf deleted file mode 100644 index bccbce462a..0000000000 Binary files a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-Italic.ttf and /dev/null differ diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-Italic.woff b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-Italic.woff deleted file mode 100644 index 57f37bb5bb..0000000000 Binary files a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-Italic.woff and /dev/null differ diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-Italic.woff2 b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-Italic.woff2 deleted file mode 100644 index e7f173b7ed..0000000000 Binary files a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-Italic.woff2 and /dev/null differ diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-Light.eot b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-Light.eot deleted file mode 100644 index 068974abd9..0000000000 Binary files a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-Light.eot and /dev/null differ diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-Light.svg b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-Light.svg deleted file mode 100644 index 70751761f1..0000000000 --- a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-Light.svg +++ /dev/null @@ -1,11119 +0,0 @@ - - - - -Created by FontForge 20170910 at Wed Mar 29 14:02:43 2017 - By Jimmy Wärting -Copyright 2011 Google Inc. All Rights Reserved. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-Light.ttf b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-Light.ttf deleted file mode 100644 index b404b43647..0000000000 Binary files a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-Light.ttf and /dev/null differ diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-Light.woff b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-Light.woff deleted file mode 100644 index 9ce3718f4a..0000000000 Binary files a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-Light.woff and /dev/null differ diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-Light.woff2 b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-Light.woff2 deleted file mode 100644 index 94a15d41c2..0000000000 Binary files a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-Light.woff2 and /dev/null differ diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-LightItalic.eot b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-LightItalic.eot deleted file mode 100644 index 1cd6a1ce93..0000000000 Binary files a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-LightItalic.eot and /dev/null differ diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-LightItalic.svg b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-LightItalic.svg deleted file mode 100644 index 7d108e17fd..0000000000 --- a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-LightItalic.svg +++ /dev/null @@ -1,11211 +0,0 @@ - - - - -Created by FontForge 20170925 at Wed Mar 29 14:02:47 2017 - By Jimmy Wärting -Copyright 2011 Google Inc. All Rights Reserved. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-LightItalic.ttf b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-LightItalic.ttf deleted file mode 100644 index 4179967905..0000000000 Binary files a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-LightItalic.ttf and /dev/null differ diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-LightItalic.woff b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-LightItalic.woff deleted file mode 100644 index 61a49cf833..0000000000 Binary files a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-LightItalic.woff and /dev/null differ diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-LightItalic.woff2 b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-LightItalic.woff2 deleted file mode 100644 index ba70c2a396..0000000000 Binary files a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-LightItalic.woff2 and /dev/null differ diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-Medium.eot b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-Medium.eot deleted file mode 100644 index a07651d076..0000000000 Binary files a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-Medium.eot and /dev/null differ diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-Medium.svg b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-Medium.svg deleted file mode 100644 index c75af7d752..0000000000 --- a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-Medium.svg +++ /dev/null @@ -1,11529 +0,0 @@ - - - - -Created by FontForge 20170925 at Wed Mar 29 14:02:52 2017 - By Jimmy Wärting -Copyright 2011 Google Inc. All Rights Reserved. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-Medium.ttf b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-Medium.ttf deleted file mode 100644 index df8a2764ca..0000000000 Binary files a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-Medium.ttf and /dev/null differ diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-Medium.woff b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-Medium.woff deleted file mode 100644 index c91b1d19fb..0000000000 Binary files a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-Medium.woff and /dev/null differ diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-Medium.woff2 b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-Medium.woff2 deleted file mode 100644 index 6a88805fed..0000000000 Binary files a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-Medium.woff2 and /dev/null differ diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-MediumItalic.eot b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-MediumItalic.eot deleted file mode 100644 index d2a36ba003..0000000000 Binary files a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-MediumItalic.eot and /dev/null differ diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-MediumItalic.svg b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-MediumItalic.svg deleted file mode 100644 index c4d383c7e6..0000000000 --- a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-MediumItalic.svg +++ /dev/null @@ -1,11632 +0,0 @@ - - - - -Created by FontForge 20170910 at Wed Mar 29 14:02:56 2017 - By Jimmy Wärting -Copyright 2011 Google Inc. All Rights Reserved. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-MediumItalic.ttf b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-MediumItalic.ttf deleted file mode 100644 index b56d95c0d9..0000000000 Binary files a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-MediumItalic.ttf and /dev/null differ diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-MediumItalic.woff b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-MediumItalic.woff deleted file mode 100644 index ad07068ae2..0000000000 Binary files a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-MediumItalic.woff and /dev/null differ diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-MediumItalic.woff2 b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-MediumItalic.woff2 deleted file mode 100644 index bdf886d757..0000000000 Binary files a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-MediumItalic.woff2 and /dev/null differ diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-Regular.eot b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-Regular.eot deleted file mode 100644 index b96a731fae..0000000000 Binary files a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-Regular.eot and /dev/null differ diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-Regular.svg b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-Regular.svg deleted file mode 100644 index 656bd354dd..0000000000 --- a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-Regular.svg +++ /dev/null @@ -1,11080 +0,0 @@ - - - - -Created by FontForge 20170910 at Wed Mar 29 14:03:00 2017 - By Jimmy Wärting -Copyright 2011 Google Inc. All Rights Reserved. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-Regular.ttf b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-Regular.ttf deleted file mode 100644 index e2722e2052..0000000000 Binary files a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-Regular.ttf and /dev/null differ diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-Regular.woff b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-Regular.woff deleted file mode 100644 index 81a88b4846..0000000000 Binary files a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-Regular.woff and /dev/null differ diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-Regular.woff2 b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-Regular.woff2 deleted file mode 100644 index 6fa4939186..0000000000 Binary files a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-Regular.woff2 and /dev/null differ diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-Thin.eot b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-Thin.eot deleted file mode 100644 index 075d976e5f..0000000000 Binary files a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-Thin.eot and /dev/null differ diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-Thin.svg b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-Thin.svg deleted file mode 100644 index 5b77d0029f..0000000000 --- a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-Thin.svg +++ /dev/null @@ -1,11122 +0,0 @@ - - - - -Created by FontForge 20170925 at Wed Mar 29 14:03:05 2017 - By Jimmy Wärting -Copyright 2011 Google Inc. All Rights Reserved. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-Thin.ttf b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-Thin.ttf deleted file mode 100644 index adb308f680..0000000000 Binary files a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-Thin.ttf and /dev/null differ diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-Thin.woff b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-Thin.woff deleted file mode 100644 index fe8596623a..0000000000 Binary files a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-Thin.woff and /dev/null differ diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-Thin.woff2 b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-Thin.woff2 deleted file mode 100644 index 2fb9b5f68c..0000000000 Binary files a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-Thin.woff2 and /dev/null differ diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-ThinItalic.eot b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-ThinItalic.eot deleted file mode 100644 index 2e7e8dcdcd..0000000000 Binary files a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-ThinItalic.eot and /dev/null differ diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-ThinItalic.svg b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-ThinItalic.svg deleted file mode 100644 index aae4817abf..0000000000 --- a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-ThinItalic.svg +++ /dev/null @@ -1,11231 +0,0 @@ - - - - -Created by FontForge 20170910 at Wed Mar 29 14:03:09 2017 - By Jimmy Wärting -Copyright 2011 Google Inc. All Rights Reserved. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-ThinItalic.ttf b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-ThinItalic.ttf deleted file mode 100644 index c86b24ca9d..0000000000 Binary files a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-ThinItalic.ttf and /dev/null differ diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-ThinItalic.woff b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-ThinItalic.woff deleted file mode 100644 index 5ee2aadbe1..0000000000 Binary files a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-ThinItalic.woff and /dev/null differ diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-ThinItalic.woff2 b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-ThinItalic.woff2 deleted file mode 100644 index ba9cf9d823..0000000000 Binary files a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/fonts/roboto/Roboto-ThinItalic.woff2 and /dev/null differ diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/webfonts/fa-brands-400.ttf b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/webfonts/fa-brands-400.ttf deleted file mode 100644 index 227f022db5..0000000000 Binary files a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/webfonts/fa-brands-400.ttf and /dev/null differ diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/webfonts/fa-brands-400.woff2 b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/webfonts/fa-brands-400.woff2 deleted file mode 100644 index 73c5c12940..0000000000 Binary files a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/webfonts/fa-brands-400.woff2 and /dev/null differ diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/webfonts/fa-regular-400.ttf b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/webfonts/fa-regular-400.ttf deleted file mode 100644 index c8ed46d4ce..0000000000 Binary files a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/webfonts/fa-regular-400.ttf and /dev/null differ diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/webfonts/fa-regular-400.woff2 b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/webfonts/fa-regular-400.woff2 deleted file mode 100644 index c9291c7b66..0000000000 Binary files a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/webfonts/fa-regular-400.woff2 and /dev/null differ diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/webfonts/fa-solid-900.ttf b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/webfonts/fa-solid-900.ttf deleted file mode 100644 index 99b35ad505..0000000000 Binary files a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/webfonts/fa-solid-900.ttf and /dev/null differ diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/webfonts/fa-solid-900.woff2 b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/webfonts/fa-solid-900.woff2 deleted file mode 100644 index c7bd59c241..0000000000 Binary files a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/webfonts/fa-solid-900.woff2 and /dev/null differ diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/webfonts/fa-v4compatibility.ttf b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/webfonts/fa-v4compatibility.ttf deleted file mode 100644 index be0afc27ae..0000000000 Binary files a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/webfonts/fa-v4compatibility.ttf and /dev/null differ diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/webfonts/fa-v4compatibility.woff2 b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/webfonts/fa-v4compatibility.woff2 deleted file mode 100644 index 37a9b8c79e..0000000000 Binary files a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/css/webfonts/fa-v4compatibility.woff2 and /dev/null differ diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/NGINX-Logo-White-Endorsement-RGB.svg b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/NGINX-Logo-White-Endorsement-RGB.svg deleted file mode 100644 index 474e54b081..0000000000 --- a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/NGINX-Logo-White-Endorsement-RGB.svg +++ /dev/null @@ -1,51 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/NGINX-Logo-White-on-White-Endorsement-RGB-2000x792.png b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/NGINX-Logo-White-on-White-Endorsement-RGB-2000x792.png deleted file mode 100644 index be50c4e0f0..0000000000 Binary files a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/NGINX-Logo-White-on-White-Endorsement-RGB-2000x792.png and /dev/null differ diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/article-icon.svg b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/article-icon.svg deleted file mode 100644 index 8b34acaf33..0000000000 --- a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/article-icon.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/cookie-img.png b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/cookie-img.png deleted file mode 100644 index dd77128e61..0000000000 Binary files a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/cookie-img.png and /dev/null differ diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/favicon-48x48.ico b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/favicon-48x48.ico deleted file mode 100644 index c509adfeac..0000000000 Binary files a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/favicon-48x48.ico and /dev/null differ diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/favicon-64x64.ico b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/favicon-64x64.ico deleted file mode 100644 index 1d741baafc..0000000000 Binary files a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/favicon-64x64.ico and /dev/null differ diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/github-logo.png b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/github-logo.png deleted file mode 100644 index 8b25551a97..0000000000 Binary files a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/github-logo.png and /dev/null differ diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icon-link.svg b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icon-link.svg deleted file mode 100644 index 08e9e18f35..0000000000 --- a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icon-link.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/NGINX-Amplify-product-icon-RGB.png b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/NGINX-Amplify-product-icon-RGB.png deleted file mode 100644 index 081fafd4af..0000000000 Binary files a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/NGINX-Amplify-product-icon-RGB.png and /dev/null differ diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/NGINX-Amplify-product-icon-RGB.svg b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/NGINX-Amplify-product-icon-RGB.svg deleted file mode 100644 index d6faff9d8f..0000000000 --- a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/NGINX-Amplify-product-icon-RGB.svg +++ /dev/null @@ -1 +0,0 @@ -NGINX-hex-source-RGB-02 \ No newline at end of file diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/NGINX-App-Protect-DoS-product-icon.png b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/NGINX-App-Protect-DoS-product-icon.png deleted file mode 100644 index c83a041bd0..0000000000 Binary files a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/NGINX-App-Protect-DoS-product-icon.png and /dev/null differ diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/NGINX-App-Protect-DoS-product-icon.svg b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/NGINX-App-Protect-DoS-product-icon.svg deleted file mode 100644 index 06ae5c0430..0000000000 --- a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/NGINX-App-Protect-DoS-product-icon.svg +++ /dev/null @@ -1,53 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/NGINX-App-Protect-WAF-product-icon.png b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/NGINX-App-Protect-WAF-product-icon.png deleted file mode 100644 index 9ec1d4f487..0000000000 Binary files a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/NGINX-App-Protect-WAF-product-icon.png and /dev/null differ diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/NGINX-App-Protect-WAF-product-icon.svg b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/NGINX-App-Protect-WAF-product-icon.svg deleted file mode 100644 index 45e89568e1..0000000000 --- a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/NGINX-App-Protect-WAF-product-icon.svg +++ /dev/null @@ -1,46 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/NGINX-App-Protect-product-icon-RGB.svg b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/NGINX-App-Protect-product-icon-RGB.svg deleted file mode 100644 index a5c68e6e05..0000000000 --- a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/NGINX-App-Protect-product-icon-RGB.svg +++ /dev/null @@ -1 +0,0 @@ -NGINX-hex-source-RGB-02 \ No newline at end of file diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/NGINX-Controller-product-icon-RGB.png b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/NGINX-Controller-product-icon-RGB.png deleted file mode 100644 index 8c8da9fd43..0000000000 Binary files a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/NGINX-Controller-product-icon-RGB.png and /dev/null differ diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/NGINX-Controller-product-icon-RGB.svg b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/NGINX-Controller-product-icon-RGB.svg deleted file mode 100644 index 35b65aa080..0000000000 --- a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/NGINX-Controller-product-icon-RGB.svg +++ /dev/null @@ -1 +0,0 @@ -NGINX-hex-source-RGB-02 \ No newline at end of file diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/NGINX-Docs-horiz-black-type.svg b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/NGINX-Docs-horiz-black-type.svg deleted file mode 100644 index b39627942d..0000000000 --- a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/NGINX-Docs-horiz-black-type.svg +++ /dev/null @@ -1,80 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/NGINX-Docs-horiz-white-type.svg b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/NGINX-Docs-horiz-white-type.svg deleted file mode 100644 index 5174b2da30..0000000000 --- a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/NGINX-Docs-horiz-white-type.svg +++ /dev/null @@ -1,83 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/NGINX-Docs-new-docs-dark-1200x630.png b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/NGINX-Docs-new-docs-dark-1200x630.png deleted file mode 100644 index bb3ab82020..0000000000 Binary files a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/NGINX-Docs-new-docs-dark-1200x630.png and /dev/null differ diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/NGINX-Docs-new-docs-square-381x327.png b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/NGINX-Docs-new-docs-square-381x327.png deleted file mode 100644 index 28b7b3e374..0000000000 Binary files a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/NGINX-Docs-new-docs-square-381x327.png and /dev/null differ diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/NGINX-Docs-vertical-black-type-517x327@2x.png b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/NGINX-Docs-vertical-black-type-517x327@2x.png deleted file mode 100644 index 6a42e6880c..0000000000 Binary files a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/NGINX-Docs-vertical-black-type-517x327@2x.png and /dev/null differ diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/NGINX-F5-DNS-Cloud-Services-product-icon.png b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/NGINX-F5-DNS-Cloud-Services-product-icon.png deleted file mode 100644 index c94a51653b..0000000000 Binary files a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/NGINX-F5-DNS-Cloud-Services-product-icon.png and /dev/null differ diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/NGINX-F5-DNS-Cloud-Services-product-icon.svg b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/NGINX-F5-DNS-Cloud-Services-product-icon.svg deleted file mode 100644 index 7c794caf92..0000000000 --- a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/NGINX-F5-DNS-Cloud-Services-product-icon.svg +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/NGINX-Ingress-Controller-product-icon.png b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/NGINX-Ingress-Controller-product-icon.png deleted file mode 100644 index 95cf19c8b6..0000000000 Binary files a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/NGINX-Ingress-Controller-product-icon.png and /dev/null differ diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/NGINX-Ingress-Controller-product-icon.svg b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/NGINX-Ingress-Controller-product-icon.svg deleted file mode 100644 index 52d351c0ee..0000000000 --- a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/NGINX-Ingress-Controller-product-icon.svg +++ /dev/null @@ -1,57 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/NGINX-Instance-Manager-product-icon.png b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/NGINX-Instance-Manager-product-icon.png deleted file mode 100644 index 0500e841b5..0000000000 Binary files a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/NGINX-Instance-Manager-product-icon.png and /dev/null differ diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/NGINX-Instance-Manager-product-icon.svg b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/NGINX-Instance-Manager-product-icon.svg deleted file mode 100644 index 3abc6b9f1a..0000000000 --- a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/NGINX-Instance-Manager-product-icon.svg +++ /dev/null @@ -1,83 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/NGINX-Management-Suite-product-icon-RGB.png b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/NGINX-Management-Suite-product-icon-RGB.png deleted file mode 100644 index e440e14769..0000000000 Binary files a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/NGINX-Management-Suite-product-icon-RGB.png and /dev/null differ diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/NGINX-Management-Suite-product-icon.svg b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/NGINX-Management-Suite-product-icon.svg deleted file mode 100644 index 703c044a8f..0000000000 --- a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/NGINX-Management-Suite-product-icon.svg +++ /dev/null @@ -1,68 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/NGINX-ModSecurity-WAF-product-icon-RGB.svg b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/NGINX-ModSecurity-WAF-product-icon-RGB.svg deleted file mode 100644 index a293289c6e..0000000000 --- a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/NGINX-ModSecurity-WAF-product-icon-RGB.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/NGINX-Part-of-F5-horiz-all-white-525x208@2x.png b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/NGINX-Part-of-F5-horiz-all-white-525x208@2x.png deleted file mode 100644 index fed4aedded..0000000000 Binary files a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/NGINX-Part-of-F5-horiz-all-white-525x208@2x.png and /dev/null differ diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/NGINX-Part-of-F5-vertical-all-black-284x373@2x.png b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/NGINX-Part-of-F5-vertical-all-black-284x373@2x.png deleted file mode 100644 index 0d08b79938..0000000000 Binary files a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/NGINX-Part-of-F5-vertical-all-black-284x373@2x.png and /dev/null differ diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/NGINX-Plus-product-icon-RGB.png b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/NGINX-Plus-product-icon-RGB.png deleted file mode 100644 index f8e7ea62ac..0000000000 Binary files a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/NGINX-Plus-product-icon-RGB.png and /dev/null differ diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/NGINX-Plus-product-icon-RGB.svg b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/NGINX-Plus-product-icon-RGB.svg deleted file mode 100644 index d3dc6515f4..0000000000 --- a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/NGINX-Plus-product-icon-RGB.svg +++ /dev/null @@ -1 +0,0 @@ -NGINX-hex-source-RGB-02 \ No newline at end of file diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/NGINX-Service-Mesh-product-icon.png b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/NGINX-Service-Mesh-product-icon.png deleted file mode 100644 index 129815d913..0000000000 Binary files a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/NGINX-Service-Mesh-product-icon.png and /dev/null differ diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/NGINX-Service-Mesh-product-icon.svg b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/NGINX-Service-Mesh-product-icon.svg deleted file mode 100644 index 36d5419a43..0000000000 --- a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/NGINX-Service-Mesh-product-icon.svg +++ /dev/null @@ -1,46 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/NGINX-Unit-product-icon-RGB.png b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/NGINX-Unit-product-icon-RGB.png deleted file mode 100644 index 0d17e65050..0000000000 Binary files a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/NGINX-Unit-product-icon-RGB.png and /dev/null differ diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/NGINX-Unit-product-icon-RGB.svg b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/NGINX-Unit-product-icon-RGB.svg deleted file mode 100644 index 5d807303ad..0000000000 --- a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/NGINX-Unit-product-icon-RGB.svg +++ /dev/null @@ -1 +0,0 @@ -NGINX-hex-source-RGB-02 \ No newline at end of file diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/NGINX-WAF-product-icon-RGB.png b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/NGINX-WAF-product-icon-RGB.png deleted file mode 100644 index d9dec5e0c3..0000000000 Binary files a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/NGINX-WAF-product-icon-RGB.png and /dev/null differ diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/NGINX-WAF-product-icon-RGB.svg b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/NGINX-WAF-product-icon-RGB.svg deleted file mode 100644 index a5c68e6e05..0000000000 --- a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/NGINX-WAF-product-icon-RGB.svg +++ /dev/null @@ -1 +0,0 @@ -NGINX-hex-source-RGB-02 \ No newline at end of file diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/NGINX-for-Azure-icon.svg b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/NGINX-for-Azure-icon.svg deleted file mode 100644 index 1d52497da0..0000000000 --- a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/NGINX-for-Azure-icon.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/NGINX-for-Azure-product-icon.png b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/NGINX-for-Azure-product-icon.png deleted file mode 100644 index 9d49b826c3..0000000000 Binary files a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/NGINX-for-Azure-product-icon.png and /dev/null differ diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/NGINX-product-icon.png b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/NGINX-product-icon.png deleted file mode 100644 index b342ab08ca..0000000000 Binary files a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/NGINX-product-icon.png and /dev/null differ diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/NGINX-product-icon.svg b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/NGINX-product-icon.svg deleted file mode 100644 index 543e1b4046..0000000000 --- a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/NGINX-product-icon.svg +++ /dev/null @@ -1 +0,0 @@ -NGINX-hex-source-RGB-02 \ No newline at end of file diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/icon-NGINX-Amplify-70x81.png b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/icon-NGINX-Amplify-70x81.png deleted file mode 100644 index 3f65c37416..0000000000 Binary files a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/icon-NGINX-Amplify-70x81.png and /dev/null differ diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/icon-NGINX-Amplify-70x81.svg b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/icon-NGINX-Amplify-70x81.svg deleted file mode 100644 index 5d0b8dc8fa..0000000000 --- a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/icon-NGINX-Amplify-70x81.svg +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - - - - - - - diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/icon-NGINX-Amplify-hover-70x81.png b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/icon-NGINX-Amplify-hover-70x81.png deleted file mode 100644 index e429c8584c..0000000000 Binary files a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/icon-NGINX-Amplify-hover-70x81.png and /dev/null differ diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/icon-NGINX-Amplify-hover-70x81.svg b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/icon-NGINX-Amplify-hover-70x81.svg deleted file mode 100644 index 79e2e72ecf..0000000000 --- a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/icon-NGINX-Amplify-hover-70x81.svg +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - - diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/icon-NGINX-Controller-70x81.png b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/icon-NGINX-Controller-70x81.png deleted file mode 100644 index a22452d535..0000000000 Binary files a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/icon-NGINX-Controller-70x81.png and /dev/null differ diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/icon-NGINX-Controller-70x81.svg b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/icon-NGINX-Controller-70x81.svg deleted file mode 100644 index 74bf9094c6..0000000000 --- a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/icon-NGINX-Controller-70x81.svg +++ /dev/null @@ -1,61 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/icon-NGINX-Controller-hover-70x81.png b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/icon-NGINX-Controller-hover-70x81.png deleted file mode 100644 index 7f4d6fe053..0000000000 Binary files a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/icon-NGINX-Controller-hover-70x81.png and /dev/null differ diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/icon-NGINX-Controller-hover-70x81.svg b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/icon-NGINX-Controller-hover-70x81.svg deleted file mode 100644 index b974dd8c51..0000000000 --- a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/icon-NGINX-Controller-hover-70x81.svg +++ /dev/null @@ -1,72 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/icon-NGINX-Plus-70x81.png b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/icon-NGINX-Plus-70x81.png deleted file mode 100644 index ee66371700..0000000000 Binary files a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/icon-NGINX-Plus-70x81.png and /dev/null differ diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/icon-NGINX-Plus-70x81.svg b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/icon-NGINX-Plus-70x81.svg deleted file mode 100644 index 62365b9c0c..0000000000 --- a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/icon-NGINX-Plus-70x81.svg +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - - - - - - - - diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/icon-NGINX-Plus-hover-70x81.png b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/icon-NGINX-Plus-hover-70x81.png deleted file mode 100644 index 61f6a0eafd..0000000000 Binary files a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/icon-NGINX-Plus-hover-70x81.png and /dev/null differ diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/icon-NGINX-Plus-hover-70x81.svg b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/icon-NGINX-Plus-hover-70x81.svg deleted file mode 100644 index 2d6a0e3ad6..0000000000 --- a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/icon-NGINX-Plus-hover-70x81.svg +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - - - - - - - - diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/icon-NGINX-Unit-70x81.png b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/icon-NGINX-Unit-70x81.png deleted file mode 100644 index 83eaa170f0..0000000000 Binary files a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/icon-NGINX-Unit-70x81.png and /dev/null differ diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/icon-NGINX-Unit-70x81.svg b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/icon-NGINX-Unit-70x81.svg deleted file mode 100644 index 37edb6f16a..0000000000 --- a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/icon-NGINX-Unit-70x81.svg +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/icon-NGINX-Unit-hover-70x81.png b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/icon-NGINX-Unit-hover-70x81.png deleted file mode 100644 index bfdb6e9768..0000000000 Binary files a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/icon-NGINX-Unit-hover-70x81.png and /dev/null differ diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/icon-NGINX-Unit-hover-70x81.svg b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/icon-NGINX-Unit-hover-70x81.svg deleted file mode 100644 index b6b53b0eb6..0000000000 --- a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/icon-NGINX-Unit-hover-70x81.svg +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - - - diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/icon-NGINX-WAF-70x81.png b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/icon-NGINX-WAF-70x81.png deleted file mode 100644 index c93f9e2cef..0000000000 Binary files a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/icon-NGINX-WAF-70x81.png and /dev/null differ diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/icon-NGINX-WAF-70x81.svg b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/icon-NGINX-WAF-70x81.svg deleted file mode 100644 index 3756e72c31..0000000000 --- a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/icon-NGINX-WAF-70x81.svg +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - - - diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/icon-NGINX-WAF-hover-70x81.png b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/icon-NGINX-WAF-hover-70x81.png deleted file mode 100644 index 4b9b0938d6..0000000000 Binary files a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/icon-NGINX-WAF-hover-70x81.png and /dev/null differ diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/icon-NGINX-WAF-hover-70x81.svg b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/icon-NGINX-WAF-hover-70x81.svg deleted file mode 100644 index 02a889f5ad..0000000000 --- a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/icons/icon-NGINX-WAF-hover-70x81.svg +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/img.png b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/img.png deleted file mode 100644 index 9cdc63703f..0000000000 Binary files a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/img.png and /dev/null differ diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/logo.png b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/logo.png deleted file mode 100644 index 86b825c556..0000000000 Binary files a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/logo.png and /dev/null differ diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/logo.svg b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/logo.svg deleted file mode 100644 index 4bb0ed7aac..0000000000 --- a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/logo.svg +++ /dev/null @@ -1,15 +0,0 @@ - - - -Layer 1 - - - - - - - - - \ No newline at end of file diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/nginx-documentation-500x300.png b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/nginx-documentation-500x300.png deleted file mode 100644 index 9d6a6b6012..0000000000 Binary files a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/nginx-documentation-500x300.png and /dev/null differ diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/refresh.svg b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/refresh.svg deleted file mode 100644 index 34e80046c7..0000000000 --- a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/refresh.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/svg/caret-right.svg b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/svg/caret-right.svg deleted file mode 100644 index 0c518eacb2..0000000000 --- a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/svg/caret-right.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/svg/refresh.svg b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/svg/refresh.svg deleted file mode 100644 index 34e80046c7..0000000000 --- a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/images/svg/refresh.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/js/.gitkeep b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/js/.gitkeep deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/pygments.css b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/pygments.css deleted file mode 100644 index 0eea184729..0000000000 --- a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/pygments.css +++ /dev/null @@ -1,75 +0,0 @@ -.highlight { background: #444; color: #d0d0d0 } -.highlight pre { background: #444; color: #d0d0d0 } -.highlight .hll { background-color: #222 } -.highlight .c { color: #999; font-style: italic } -.highlight .err { color: #a61717; background-color: #e3d2d2 } -.highlight .esc { color: #d0d0d0 } -.highlight .g { color: #d0d0d0 } -.highlight .k { color: #6ab825; font-weight: bold } -.highlight .l { color: #d0d0d0 } -.highlight .n { color: #d0d0d0 } -.highlight .o { color: #d0d0d0 } -.highlight .x { color: #d0d0d0 } -.highlight .p { color: #d0d0d0 } -.highlight .ch { color: #999; font-style: italic } -.highlight .cm { color: #999; font-style: italic } -.highlight .cp { color: #cd2828; font-weight: bold } -.highlight .cpf { color: #999; font-style: italic } -.highlight .c1 { color: #999; font-style: italic } -.highlight .cs { color: #e50808; font-weight: bold; background-color: #520000 } -.highlight .gd { color: #d22323 } -.highlight .ge { color: #d0d0d0; font-style: italic } -.highlight .gr { color: #d22323 } -.highlight .gh { color: #fff; font-weight: bold } -.highlight .gi { color: #589819 } -.highlight .go { color: #ccc } -.highlight .gp { color: #aaa } -.highlight .gs { color: #d0d0d0; font-weight: bold } -.highlight .gu { color: #fff; text-decoration: underline } -.highlight .gt { color: #d22323 } -.highlight .kc { color: #6ab825; font-weight: bold } -.highlight .kd { color: #6ab825; font-weight: bold } -.highlight .kn { color: #6ab825; font-weight: bold } -.highlight .kp { color: #6ab825 } -.highlight .kr { color: #6ab825; font-weight: bold } -.highlight .kt { color: #6ab825; font-weight: bold } -.highlight .ld { color: #d0d0d0 } -.highlight .m { color: #56abed } -.highlight .s { color: #ed9d13 } -.highlight .na { color: #bbb } -.highlight .nb { color: #24909d } -.highlight .nc { color: #447fcf; text-decoration: underline } -.highlight .no { color: #40ffff } -.highlight .nd { color: #ffa500 } -.highlight .ni { color: #d0d0d0 } -.highlight .ne { color: #bbb } -.highlight .nf { color: #447fcf } -.highlight .nl { color: #d0d0d0 } -.highlight .nn { color: #447fcf; text-decoration: underline } -.highlight .nx { color: #d0d0d0 } -.highlight .py { color: #d0d0d0 } -.highlight .nt { color: #6ab825; font-weight: bold } -.highlight .nv { color: #40ffff } -.highlight .ow { color: #6ab825; font-weight: bold } -.highlight .w { color: #666 } -.highlight .mb { color: #56abed } -.highlight .mf { color: #56abed } -.highlight .mh { color: #56abed } -.highlight .mi { color: #56abed } -.highlight .mo { color: #56abed } -.highlight .sb { color: #ed9d13 } -.highlight .sc { color: #ed9d13 } -.highlight .sd { color: #ed9d13 } -.highlight .s2 { color: #ed9d13 } -.highlight .se { color: #ed9d13 } -.highlight .sh { color: #ed9d13 } -.highlight .si { color: #ed9d13 } -.highlight .sx { color: #ffa500 } -.highlight .sr { color: #ed9d13 } -.highlight .s1 { color: #ed9d13 } -.highlight .ss { color: #ed9d13 } -.highlight .bp { color: #24909d } -.highlight .vc { color: #40ffff } -.highlight .vg { color: #40ffff } -.highlight .vi { color: #40ffff } -.highlight .il { color: #3677a9 } \ No newline at end of file diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/specs/.gitkeep b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/static/specs/.gitkeep deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/theme.toml b/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/theme.toml deleted file mode 100644 index 5e228c71e3..0000000000 --- a/docs/_vendor/gitlab.com/f5/nginx/controller/poc/f5-hugo/theme.toml +++ /dev/null @@ -1,20 +0,0 @@ -# theme.toml template for a Hugo theme -# See https://github.com/gohugoio/hugoThemes#themetoml for an example - -name = "f5-hugo" -license = "Proprietary" -licenselink = "https://gitswarm.f5net.com/indigo/poc/f5-hugo/blob/master/LICENSE" -description = "Hugo theme for F5 documentation" -homepage = "https://www.f5.com/" - -min_version = "0.74.3" - -[author] - name = "F5, Inc." - homepage = "https://www.f5.com" - -[original] - name = "kube" - homepage = "https://kube.elemnts.net/" - repo = "https://github.com/jeblister/kube" - diff --git a/docs/_vendor/modules.txt b/docs/_vendor/modules.txt deleted file mode 100644 index 3cf010ec97..0000000000 --- a/docs/_vendor/modules.txt +++ /dev/null @@ -1 +0,0 @@ -# gitlab.com/f5/nginx/controller/poc/f5-hugo v0.24.1 diff --git a/docs/config/_default/config.toml b/docs/config/_default/config.toml deleted file mode 100644 index d098113c6c..0000000000 --- a/docs/config/_default/config.toml +++ /dev/null @@ -1,64 +0,0 @@ -title = "NGINX Ingress Controller" -baseURL = "/" -enableGitInfo = false -staticDir = ["static"] -languageCode = "en-us" -description = "Enterprise-grade Ingress load balancing on Kubernetes platforms." -refLinksErrorLevel = "ERROR" -enableRobotsTXT = "true" -canonifyURLs = true - -[caches] - [caches.modules] - dir = "/tmp/hugo_cache/modules" - maxAge = -1 - -[[module.imports]] - path="gitlab.com/f5/nginx/controller/poc/f5-hugo" - -[markup] - [markup.highlight] - codeFences = true - guessSyntax = true - hl_Lines = "" - lineNoStart = 1 - lineNos = false - lineNumbersInTable = true - noClasses = false - style = "monokai" - tabWidth = 4 - [markup.goldmark] - [markup.goldmark.extensions] - definitionList = true - footnote = true - linkify = true - strikethrough = true - table = true - taskList = true - typographer = true - [markup.goldmark.parser] - attribute = true - autoHeadingID = true - autoHeadingIDType = "gitlab" - [markup.goldmark.renderer] - hardWraps = false - unsafe = true - xhtml = false - -[params] - buildtype = "webdocs" - useSectionPageLists = "true" - RSSLink = "/index.xml" - author = "NGINX Inc." # add your company name - github = "nginxinc" # add your github profile name - twitter = "@nginx" # add your twitter profile - #email = "" - noindex_kinds = [ - "taxonomy", - "taxonomyTerm" - ] - logo = "NGINX-Ingress-Controller-product-icon.svg" - -sectionPagesMenu = "docs" - -ignoreFiles = [ "\\.sh$", "\\.DS_Store$", "\\.git.*$", "\\.txt$", "\\/config\\/.*"] diff --git a/docs/config/staging/config.toml b/docs/config/staging/config.toml deleted file mode 100644 index 8bf1d0c5af..0000000000 --- a/docs/config/staging/config.toml +++ /dev/null @@ -1,4 +0,0 @@ -baseURL = "https://docs-staging.nginx.com/nginx-ingress-controller" -title = "STAGING -- NGINX Docs" -publishDir = "public/nginx-ingress-controller" -canonifyURLs = false diff --git a/docs/content/app-protect-dos/_index.md b/docs/content/app-protect-dos/_index.md deleted file mode 100644 index 9f9c0ba7eb..0000000000 --- a/docs/content/app-protect-dos/_index.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -title: Using with NGINX App Protect DoS -description: Learn how to use NGINX Ingress Controller for Kubernetes with NGINX App Protect DoS. -weight: 1600 -menu: - docs: - parent: NGINX Ingress Controller ---- diff --git a/docs/content/app-protect-dos/configuration.md b/docs/content/app-protect-dos/configuration.md deleted file mode 100644 index b620063a24..0000000000 --- a/docs/content/app-protect-dos/configuration.md +++ /dev/null @@ -1,154 +0,0 @@ ---- -title: Configuration - -description: "This document describes how to configure the NGINX App Protect Dos module." -weight: 1900 -doctypes: [""] -toc: true -docs: "DOCS-580" ---- - -This document describes how to configure the NGINX App Protect DoS module -> Check out the complete [NGINX Ingress Controller with App Protect DoS example for VirtualServer](https://github.com/nginxinc/kubernetes-ingress/tree/v2.4.1/examples/custom-resources/app-protect-dos) and the [NGINX Ingress Controller with App Protect DoS example for Ingress](https://github.com/nginxinc/kubernetes-ingress/tree/v2.4.1/examples/ingress-resources/app-protect-dos). - -## App Protect DoS Configuration - -A `DosProtectedResource` is a [Custom Resource](https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/) that holds the configuration of a collection of protected resources. -An [Ingress](/nginx-ingress-controller/configuration/ingress-resources/basic-configuration), [VirtualServer and VirtualServerRoute](/nginx-ingress-controller/configuration/virtualserver-and-virtualserverroute-resources/) can be protected by specifying a reference to the DosProtectedResource. - -1. Create an `DosProtectedResource` Custom resource manifest. As an example: - ```yaml -apiVersion: appprotectdos.f5.com/v1beta1 -kind: DosProtectedResource -metadata: - name: dos-protected -spec: - enable: true - name: "webapp.example.com" - apDosMonitor: - uri: "webapp.example.com" - protocol: "http1" - timeout: 5 - ``` -2. Enable App Protect DoS on an Ingress by adding an annotation on the Ingress. Set the value of the annotation to the qualified identifier(`namespace/name`) of a DosProtectedResource: - ```yaml - apiVersion: networking.k8s.io/v1 - kind: Ingress - metadata: - name: webapp-ingress - annotations: - appprotectdos.f5.com/app-protect-dos-resource: "default/dos-protected" - ``` -3. Enable App Protect DoS on a VirtualServer by setting the `dos` field value to the qualified identifier(`namespace/name`) of a DosProtectedResource: - ```yaml -apiVersion: k8s.nginx.org/v1 -kind: VirtualServer -metadata: - name: webapp -spec: - host: webapp.example.com - upstreams: - - name: webapp - service: webapp-svc - port: 80 - routes: - - path: / - dos: dos-protected - action: - pass: webapp - ``` - -## DoS Policy Configuration - -You can configure the policy for DoS by creating an `APDosPolicy` [Custom Resource](https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/) and specifying the qualified identifier(`namespace/name`) of the `ApDosPolicy` in the `DosProtectedResource`. - -For example, say you want to use DoS Policy as shown below: - - ```json - { - mitigation_mode: "standard", - signatures: "on", - bad_actors: "on", - automation_tools_detection: "on", - tls_fingerprint: "on", -} - ``` - -You would create an `APDosPolicy` resource with the policy defined in the `spec`, as shown below: - - ```yaml - apiVersion: appprotectdos.f5.com/v1beta1 - kind: APDosPolicy - metadata: - name: dospolicy - spec: - mitigation_mode: "standard" - signatures: "on" - bad_actors: "on" - automation_tools_detection: "on" - tls_fingerprint: "on" - ``` - -Then add a reference in the `DosProtectedResource` to the `ApDosPolicy`: - ```yaml - apiVersion: appprotectdos.f5.com/v1beta1 - kind: DosProtectedResource - metadata: - name: dos-protected - spec: - enable: true - name: "my-dos" - apDosMonitor: - uri: "webapp.example.com" - apDosPolicy: "default/dospolicy" - ``` - -## App Protect DoS Logs - -You can set the [App Protect DoS Log configuration](/nginx-app-protect-dos/monitoring/types-of-logs/) by creating an `APDosLogConf` [Custom Resource](https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/) and specifying the qualified identifier(`namespace/name`) of the `ApDosLogConf` in the `DosProtectedResource`. - -For example, say you want to log state changing requests for your Ingress resources using App Protect DoS. The App Protect DoS log configuration looks like this: - -```json -{ - "filter": { - "traffic-mitigation-stats": "all", - "bad-actors": "top 10", - "attack-signatures": "top 10" - } -} -``` - -You would add that config in the `spec` of your `APDosLogConf` resource as follows: - -```yaml -apiVersion: appprotectdos.f5.com/v1beta1 -kind: APDosLogConf -metadata: - name: doslogconf -spec: - filter: - traffic-mitigation-stats: all - bad-actors: top 10 - attack-signatures: top 10 -``` - -Then add a reference in the `DosProtectedResource` to the `APDosLogConf`: - ```yaml - apiVersion: appprotectdos.f5.com/v1beta1 - kind: DosProtectedResource - metadata: - name: dos-protected - spec: - enable: true - name: "my-dos" - apDosMonitor: - uri: "webapp.example.com" - dosSecurityLog: - enable: true - apDosLogConf: "doslogconf" - dosLogDest: "syslog-svc.default.svc.cluster.local:514" - ``` -## Global Configuration - -The NGINX Ingress Controller has a set of global configuration parameters that align with those available in the NGINX App Protect DoS module. See [ConfigMap keys](/nginx-ingress-controller/configuration/global-configuration/configmap-resource/#modules) for the complete list. The App Protect parameters use the `app-protect-dos*` prefix. diff --git a/docs/content/app-protect-dos/dos-protected.md b/docs/content/app-protect-dos/dos-protected.md deleted file mode 100644 index 422a6bdc0f..0000000000 --- a/docs/content/app-protect-dos/dos-protected.md +++ /dev/null @@ -1,111 +0,0 @@ ---- -title: DoS Protected Resource - -description: "Dos Protected Resource Specification" -weight: 1800 -doctypes: [""] -toc: true -docs: "DOCS-581" ---- - -> Note: This feature is only available in NGINX Plus with AppProtectDos. - -> Note: The feature is implemented using the NGINX Plus [NGINX App Protect Dos Module](/nginx-app-protect-dos/deployment-guide/learn-about-deployment/). - - -## DoS Protected Resource Specification - -Below is an example of a dos protected resource. -```yaml -apiVersion: appprotectdos.f5.com/v1beta1 -kind: DosProtectedResource -metadata: - name: dos-protected -spec: - enable: true - name: "my-dos" - apDosMonitor: - uri: "webapp.example.com" - -``` - -{{% table %}} -|Field | Description | Type | Required | -| ---| ---| ---| --- | -|``enable`` | Enables NGINX App Protect DoS. | ``bool`` | No | -|``name`` | Name of the protected object, max of 63 characters. | ``string`` | No | -|``apDosMonitor.uri`` | The destination to the desired protected object. [App Protect DoS monitor](#dosprotectedresourceapdosmonitor) Default value: None, URL will be extracted from the first request which arrives and taken from "Host" header or from destination ip+port. | ``string`` | No | -|``apDosMonitor.protocol`` | Determines if the server listens on http1 / http2 / grpc. [App Protect DoS monitor](#dosprotectedresourceapdosmonitor) Default value: http1. | ``enum`` | No | -|``apDosMonitor.timeout`` | Determines how long (in seconds) should NGINX App Protect DoS wait for a response. [App Protect DoS monitor](#dosprotectedresourceapdosmonitor) Default value: 10 seconds for http1/http2 and 5 seconds for grpc. | ``int64`` | No | -|``apDosPolicy`` | The [App Protect DoS policy](#dosprotectedresourceapdospolicy) of the dos. Accepts an optional namespace. | ``string`` | No | -|``dosSecurityLog.enable`` | Enables security log. | ``bool`` | No | -|``dosSecurityLog.apDosLogConf`` | The [App Protect DoS log conf](/nginx-ingress-controller/app-protect-dos/configuration/#app-protect-dos-logs) resource. Accepts an optional namespace. | ``string`` | No | -|``dosSecurityLog.dosLogDest`` | The log destination for the security log. Accepted variables are ``syslog:server=:``, ``stderr``, ````. Default is ``"syslog:server=127.0.0.1:514"``. | ``string`` | No | -{{% /table %}} - -### DosProtectedResource.apDosPolicy - -The `apDosPolicy` is a reference (qualified identifier in the format `namespace/name`) to the policy configuration defined as an `ApDosPolicy`. - -### DosProtectedResource.apDosMonitor - -This is how NGINX App Protect DoS monitors the stress level of the protected object. The monitor requests are sent from localhost (127.0.0.1). - -### Invalid DoS Protected Resources - -NGINX will treat a dos protected resource as invalid if one of the following conditions is met: -* The dos protected resource doesn't pass the [comprehensive validation](#comprehensive-validation). -* The dos protected resource isn't present in the cluster. - -### Validation - -Two types of validation are available for the dos protected resource: -* *Structural validation*, done by `kubectl` and the Kubernetes API server. -* *Comprehensive validation*, done by the Ingress Controller. - -#### Structural Validation - -The custom resource definition for the dos protected resource includes a structural OpenAPI schema, which describes the type of every field of the resource. - -If you try to create (or update) a resource that violates the structural schema -- for example, the resource uses a string value instead of a bool in the `enable` field -- `kubectl` and the Kubernetes API server will reject the resource. -* Example of `kubectl` validation: - ``` - $ kubectl apply -f apdos-protected.yaml - error: error validating "examples/app-protect-dos/apdos-protected.yaml": error validating data: ValidationError(DosProtectedResource.spec.enable): invalid type for com.f5.appprotectdos.v1beta1.DosProtectedResource.spec.enable: got "string", expected "boolean"; if you choose to ignore these errors, turn validation off with --validate=false - ``` -* Example of Kubernetes API server validation: - ``` - $ kubectl apply -f access-control-policy-allow.yaml --validate=false - The DosProtectedResource "dos-protected" is invalid: spec.enable: Invalid value: "string": spec.enable in body must be of type boolean: "string" - ``` - -If a resource passes structural validation, then the Ingress Controller's comprehensive validation runs. - - -#### Comprehensive Validation - -The Ingress Controller validates the fields of a dos protected resource. If a resource is invalid, the Ingress Controller will reject it. The resource will continue to exist in the cluster, but the Ingress Controller will ignore it. - -You can use `kubectl` to check if the Ingress Controller successfully applied a dos protected resource configuration. For our example `dos-protected` dos protected resource, we can run: -``` -$ kubectl describe dosprotectedresource dos-protected -. . . -Events: - Type Reason Age From Message - ---- ------ ---- ---- ------- - Normal AddedOrUpdated 12s (x2 over 18h) nginx-ingress-controller Configuration for default/dos-protected was added or updated -``` -Note how the events section includes a Normal event with the AddedOrUpdated reason that informs us that the configuration was successfully applied. - -If you create an invalid resource, the Ingress Controller will reject it and emit a Rejected event. For example, if you create a dos protected resource `dos-protected` with an invalid URI `bad` in the `dosSecurityLog/dosLogDest` field, you will get: -``` -$ kubectl describe policy webapp-policy -. . . -Events: - Type Reason Age From Message - ---- ------ ---- ---- ------- - Warning Rejected 2s nginx-ingress-controller error validating DosProtectedResource: dos-protected invalid field: dosSecurityLog/dosLogDest err: invalid log destination: bad, must follow format: : or stderr -``` -Note how the events section includes a Warning event with the Rejected reason. - -**Note**: If you make an existing resource invalid, the Ingress Controller will reject it. diff --git a/docs/content/app-protect-dos/installation-with-helm-dos-arbitrator.md b/docs/content/app-protect-dos/installation-with-helm-dos-arbitrator.md deleted file mode 100644 index 17b3f1e514..0000000000 --- a/docs/content/app-protect-dos/installation-with-helm-dos-arbitrator.md +++ /dev/null @@ -1,98 +0,0 @@ ---- -title: Installation with Helm App Protect DoS Arbitrator -description: -weight: 1900 -doctypes: [""] -toc: true -docs: "DOCS-582" ---- - -## Prerequisites - - - A [Kubernetes Version Supported by the Ingress Controller](https://docs.nginx.com/nginx-ingress-controller/technical-specifications/#supported-kubernetes-versions) - - Helm 3.0+. - - Git. - -## Getting the Chart Sources - -This step is required if you're installing the chart using its sources. Additionally, the step is also required for managing the custom resource definitions (CRDs), which the Ingress Controller requires by default, or for upgrading/deleting the CRDs. - -1. Clone the Ingress Controller repo: - ```console - $ git clone https://github.com/nginxinc/kubernetes-ingress.git --branch v2.4.1 - ``` -2. Change your working directory to /deployments/helm-chart-dos-arbitrator: - ```console - $ cd kubernetes-ingress/deployments/helm-chart-dos-arbitrator - ``` - -## Adding the Helm Repository - -This step is required if you're installing the chart via the helm repository. - -```console -$ helm repo add nginx-stable https://helm.nginx.com/stable -$ helm repo update -``` - -## Installing the Chart - -### Installing via Helm Repository - -To install the chart with the release name my-release-dos (my-release-dos is the name that you choose): - -```console -$ helm install my-release-dos nginx-stable/nginx-appprotect-dos-arbitrator -``` - - -### Installing Using Chart Sources - -To install the chart with the release name my-release-dos (my-release-dos is the name that you choose): - -```console -$ helm install my-release-dos . -``` - -The command deploys the App Protect DoS Arbitrator in your Kubernetes cluster in the default configuration. The configuration section lists the parameters that can be configured during installation. - -## Upgrading the Chart - -### Upgrading the Release - -To upgrade the release `my-release-dos`: - -#### Upgrade Using Chart Sources: - -```console -$ helm upgrade my-release-dos . -``` - -#### Upgrade via Helm Repository: - -```console -$ helm upgrade my-release-dos nginx-stable/nginx-appprotect-dos-arbitrator -``` - -## Uninstalling the Chart - -### Uninstalling the Release - -To uninstall/delete the release `my-release-dos`: - -```console -$ helm uninstall my-release-dos -``` - -The command removes all the Kubernetes components associated with the release and deletes the release. - -## Configuration - -The following tables lists the configurable parameters of the NGINX App Protect DoS Arbitrator chart and their default values. - -Parameter | Description | Default ---- | --- | --- -`arbitrator.resources` | The resources of the Arbitrator pods. | limits:
    cpu: 500m
    memory: 128Mi -`arbitrator.image.repository` | The image repository of the Arbitrator image. | docker-registry.nginx.com/nap-dos/app_protect_dos_arb -`arbitrator.image.tag` | The tag of the Arbitrator image. | latest -`arbitrator.image.pullPolicy` | The pull policy for the Arbitrator image. | IfNotPresent diff --git a/docs/content/app-protect-dos/installation.md b/docs/content/app-protect-dos/installation.md deleted file mode 100644 index 622b548611..0000000000 --- a/docs/content/app-protect-dos/installation.md +++ /dev/null @@ -1,69 +0,0 @@ ---- -title: Installation with NGINX App Protect Dos -description: "This document provides an overview of the steps required to use NGINX App Protect Dos with your NGINX Ingress Controller deployment." -weight: 1800 -doctypes: [""] -toc: true -docs: "DOCS-583" ---- - -> **Note**: The NGINX Kubernetes Ingress Controller integration with NGINX App Protect DoS requires the use of NGINX Plus. - -This document provides an overview of the steps required to use NGINX App Protect DoS with your NGINX Ingress Controller deployment. You can visit the linked documents to find additional information and instructions. - -## Prerequisites - -1. Make sure you have access to the Ingress Controller image: - * For NGINX Plus Ingress Controller, see [here](/nginx-ingress-controller/installation/pulling-ingress-controller-image) for details on how to pull the image from the F5 Docker registry. - * To pull from the F5 Container registry in your Kubernetes cluster, configure a docker registry secret using your JWT token from the MyF5 portal by following the instructions from [here](/nginx-ingress-controller/installation/using-the-jwt-token-docker-secret). - * It is also possible to build your own image and push it to your private Docker registry by following the instructions from [here](/nginx-ingress-controller/installation/building-ingress-controller-image). -2. Clone the Ingress Controller repo: - ``` - $ git clone https://github.com/nginxinc/kubernetes-ingress.git --branch v2.4.1 - $ cd kubernetes-ingress - ``` - -## Create the namespace and service account - -```bash - kubectl apply -f common/ns-and-sa.yaml -``` - -## Install the App Protect DoS Arbitrator - -- Deploy the app protect dos arbitrator - ```bash - kubectl apply -f deployment/appprotect-dos-arb.yaml - kubectl apply -f service/appprotect-dos-arb-svc.yaml - ``` - -## Build the Docker Image - -Take the steps below to create the Docker image that you'll use to deploy NGINX Ingress Controller with App Protect DoS in Kubernetes. - -- [Build the NGINX Ingress Controller image](/nginx-ingress-controller/installation/building-ingress-controller-image). - - When running the `make` command to build the image, be sure to use the `debian-image-dos-plus` target. For example: - - ```bash - make debian-image-dos-plus PREFIX=/nginx-plus-ingress - ``` - Alternatively, if you want to run on an [OpenShift](https://www.openshift.com/) cluster, use the `ubi-image-dos-plus` target. - - If you want to include the App Protect WAF module in the image, you can use the `debian-image-nap-dos-plus` target or the `ubi-image-nap-dos-plus` target for OpenShift. - -- [Push the image to your local Docker registry](/nginx-ingress-controller/installation/building-ingress-controller-image/#building-the-image-and-pushing-it-to-the-private-registry). - -## Install the Ingress Controller - -Take the steps below to set up and deploy the NGINX Ingress Controller and App Protect DoS module in your Kubernetes cluster. - -1. [Configure role-based access control (RBAC)](/nginx-ingress-controller/installation/installation-with-manifests/#1-configure-rbac). - - > **Important**: You must have an admin role to configure RBAC in your Kubernetes cluster. - -2. [Create the common Kubernetes resources](/nginx-ingress-controller/installation/installation-with-manifests/#2-create-common-resources). -3. Enable the App Protect Dos module by adding the `enable-app-protect-dos` [cli argument](/nginx-ingress-controller/configuration/global-configuration/command-line-arguments/#cmdoption-enable-app-protect-dos) to your Deployment or DaemonSet file. -4. [Deploy the Ingress Controller](/nginx-ingress-controller/installation/installation-with-manifests/#3-deploy-the-ingress-controller). - -For more information, see the [Configuration guide](/nginx-ingress-controller/app-protect-dos/configuration),the [NGINX Ingress Controller with App Protect DoS example for VirtualServer](https://github.com/nginxinc/kubernetes-ingress/tree/v2.4.1/examples/custom-resources/app-protect-dos) and the [NGINX Ingress Controller with App Protect DoS example for Ingress](https://github.com/nginxinc/kubernetes-ingress/tree/v2.4.1/examples/ingress-resources/app-protect-dos). diff --git a/docs/content/app-protect-waf/_index.md b/docs/content/app-protect-waf/_index.md deleted file mode 100644 index 2f33c4e3e7..0000000000 --- a/docs/content/app-protect-waf/_index.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -title: Using with NGINX App Protect -description: Learn how to use NGINX Ingress Controller for Kubernetes with NGINX App Protect. -weight: 1600 -aliases: ["/nginx-ingress-controller/app-protect/"] -menu: - docs: - parent: NGINX Ingress Controller ---- diff --git a/docs/content/app-protect-waf/configuration.md b/docs/content/app-protect-waf/configuration.md deleted file mode 100644 index 096d45eaec..0000000000 --- a/docs/content/app-protect-waf/configuration.md +++ /dev/null @@ -1,489 +0,0 @@ ---- -title: Configuration - -description: "This document describes how to configure the NGINX App Protect WAF module." -weight: 1900 -doctypes: [""] -toc: true -docs: "DOCS-578" -aliases: ["/app-protect/configuration/"] ---- - -> Check out the complete NGINX Ingress Controller with App Protect WAF example resources on GitHub [for VirtualServer resources](https://github.com/nginxinc/kubernetes-ingress/tree/v2.4.1/examples/custom-resources/app-protect-waf) and [for Ingress resources](https://github.com/nginxinc/kubernetes-ingress/tree/v2.4.1/examples/ingress-resources/app-protect-waf). - -## Global Configuration - -The NGINX Ingress Controller has a set of global configuration parameters that align with those available in the NGINX App Protect WAF module. See [ConfigMap keys](/nginx-ingress-controller/configuration/global-configuration/configmap-resource/#modules) for the complete list. The App Protect parameters use the `app-protect*` prefix. - -## Enabling App Protect - -You can enable and configure NGINX App Protect WAF on the Custom Resources (VirtualServer, VirtualServerRoute) or on the Ingress-resource basis. - -To configure NGINX App Protect WAF on a VirtualServer resource, you would create a Policy Custom Resource referencing the APPolicy Custom Resource, and add this to the VirtualServer definition. See the documentation on the [App Protect WAF Policy](/nginx-ingress-controller/configuration/policy-resource/#waf). - -To configure NGINX App Protect WAF on an Ingress resource, you would apply the [App Protect annotations](/nginx-ingress-controller/configuration/ingress-resources/advanced-configuration-with-annotations/#app-protect) to each desired resource. - - -## App Protect WAF Policies - -You can define App Protect WAF policies for your VirtualServer, VirtualServerRoute, or Ingress resources by creating an `APPolicy` [Custom Resource](https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/). - - > **Note**: The fields `policy.signature-requirements[].minRevisionDatetime` and `policy.signature-requirements[].maxRevisionDatetime` are not currently supported. - - > **Note**: [The Advanced gRPC Protection for Unary Traffic](/nginx-app-protect/configuration/#advanced-grpc-protection-for-unary-traffic) only supports providing an `idl-file` inline. The fields `policy.idl-files[].link`, `policy.idl-files[].$ref`, and - `policy.idl-files[].file` are not supported. The IDL file should be provided in field `policy.idl-files[].contents`. The value of this field can be base64 encoded. In this case the field `policy.idl-files[].isBase64` should be set to `true`. - - > **Note**: [External References](/nginx-app-protect/configuration-guide/configuration/#external-references) in the Ingress Controller are deprecated and will not be supported in future releases. - -To add any [App Protect WAF policy](/nginx-app-protect/declarative-policy/policy/) to an Ingress resource: - -1. Create an `APPolicy` Custom resource manifest. -2. Add the desired policy to the `spec` field in the `APPolicy` resource. - - > **Note**: The relationship between the Policy JSON and the resource spec is 1:1. If you're defining your resources in YAML, as we do in our examples, you'll need to represent the policy as YAML. The fields must match those in the source JSON exactly in name and level. - - For example, say you want to use the [DataGuard policy](/nginx-app-protect/declarative-policy/policy/#policy/data-guard) shown below: - - ```json - { - "policy": { - "name": "dataguard_blocking", - "template": { "name": "POLICY_TEMPLATE_NGINX_BASE" }, - "applicationLanguage": "utf-8", - "enforcementMode": "blocking", - "blocking-settings": { - "violations": [ - { - "name": "VIOL_DATA_GUARD", - "alarm": true, - "block": true - } - ] - }, - "data-guard": { - "enabled": true, - "maskData": true, - "creditCardNumbers": true, - "usSocialSecurityNumbers": true, - "enforcementMode": "ignore-urls-in-list", - "enforcementUrls": [] - } - } - } - ``` - - You would create an `APPolicy` resource with the policy defined in the `spec`, as shown below: - - ```yaml - apiVersion: appprotect.f5.com/v1beta1 - kind: APPolicy - metadata: - name: dataguard-blocking - spec: - policy: - name: dataguard_blocking - template: - name: POLICY_TEMPLATE_NGINX_BASE - applicationLanguage: utf-8 - enforcementMode: blocking - blocking-settings: - violations: - - name: VIOL_DATA_GUARD - alarm: true - block: true - data-guard: - enabled: true - maskData: true - creditCardNumbers: true - usSocialSecurityNumbers: true - enforcementMode: ignore-urls-in-list - enforcementUrls: [] - ``` - - > Notice how the fields match exactly in name and level. The Ingress Controller will transform the YAML into a valid JSON App Protect WAF policy config. -
    - -## App Protect WAF Logs - -You can set the [App Protect WAF log configurations](/nginx-app-protect/troubleshooting/#app-protect-logging-overview) by creating an `APLogConf` [Custom Resource](https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/). - -To add the [App Protect WAF log configurations](/nginx-app-protect/configuration/#security-logs) to a VirtualServer or an Ingress resource: - -1. Create an `APLogConf` Custom Resource manifest. -2. Add the desired log configuration to the `spec` field in the `APLogConf` resource. -3. Add the `APLogConf` reference to the [VirtualServer Policy resource](/nginx-ingress-controller/configuration/policy-resource/#waf) or the [Ingress resource](/nginx-ingress-controller/configuration/ingress-resources/advanced-configuration-with-annotations/#app-protect) as per the documentation. - - > **Note**: The fields from the JSON must be presented in the YAML *exactly* the same, in name and level. The Ingress Controller will transform the YAML into a valid JSON App Protect WAF log config. - -For example, say you want to [log state changing requests](/nginx-app-protect/configuration/#security-log-configuration-file) for your VirtualServer or Ingress resources using App Protect WAF. The App Protect WAF log configuration looks like this: - -```json -{ - "filter": { - "request_type": "all" - }, - "content": { - "format": "default", - "max_request_size": "any", - "max_message_size": "5k" - } -} -``` - -You would define that config in the `spec` of your `APLogConf` resource as follows: - -```yaml -apiVersion: appprotect.f5.com/v1beta1 -kind: APLogConf -metadata: - name: logconf -spec: - filter: - request_type: all - content: - format: default - max_request_size: any - max_message_size: 5k -``` -## App Protect WAF User Defined Signatures - -You can define App Protect WAF [User Defined Signatures](https://docs.nginx.com/nginx-app-protect/configuration/#user-defined-signature-definitions) for your VirtualServer or Ingress resources by creating an `APUserSig` [Custom Resource](https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/). - - > **Note**: The field `revisionDatetime` is not currently supported. - -> **Note**: `APUserSig` resources increase the reload time of NGINX Plus compared with `APPolicy` and `APLogConf` resources. Refer to [NGINX Fails to Start or Reload](/nginx-ingress-controller/app-protect/troubleshooting/#nginx-fails-to-start-or-reload) for more information. - -To add the [User Defined Signatures](https://docs.nginx.com/nginx-app-protect/configuration/#user-defined-signature-definitions) to a VirtualServer or Ingress resource: - -1. Create an `APUserSig` Custom resource manifest. -2. Add the desired User defined signature to the `spec` field in the `APUserSig` resource. - - > **Note**: The fields from the JSON must be presented in the YAML *exactly* the same, in name and level. The Ingress Controller will transform the YAML into a valid JSON App Protect WAF User Defined signature. There is no need to reference the user defined signature resource in the Policy or Ingress resources. - -For example, say you want to create the following user defined signature: - -```json -{ "softwareVersion": "15.1.0", - "tag": "Fruits", - "signatures": [ - { - "name": "Apple_medium_acc", - "rule": "content:\"apple\"; nocase;", - "signatureType": "request", - "attackType": { - "name": "Brute Force Attack" - }, - "systems": [ - {"name": "Microsoft Windows"}, - {"name": "Unix/Linux"} - ], - "risk": "medium", - "accuracy": "medium", - "description": "Medium accuracy user defined signature with tag (Fruits)" - } - ] -} -``` - -You would add that config in the `spec` of your `APUserSig` resource as follows: - -```yaml -apiVersion: appprotect.f5.com/v1beta1 -kind: APUserSig -metadata: - name: apple -spec: - signatures: - - accuracy: medium - attackType: - name: Brute Force Attack - description: Medium accuracy user defined signature with tag (Fruits) - name: Apple_medium_acc - risk: medium - rule: content:"apple"; nocase; - signatureType: request - systems: - - name: Microsoft Windows - - name: Unix/Linux - softwareVersion: 15.1.0 - tag: Fruits -``` - -## OpenAPI Specification in NGINX Ingress Controller - -The OpenAPI Specification defines the spec file format needed to describe RESTful APIs. The spec file can be written either in JSON or YAML. Using a spec file simplifies the work of implementing API protection. Refer to the [OpenAPI Specification](#https://github.com/OAI/OpenAPI-Specification) (formerly called Swagger) for details. - -NGINX Ingress Controller supports OpenAPI Specification versions 2.0 and 3.0. - -The simplest way to create an API protection policy is using an OpenAPI Specification file to import the details of the APIs. If you use an OpenAPI Specification file, NGINX App Protect WAF will automatically create a policy for the following properties (depending on what's included in the spec file): -* Methods -* URLs -* Parameters -* JSON profiles - -An OpenAPI-ready policy template is provided with the NGINX App Protect WAF packages and is located in: `/etc/app_protect/conf/NginxApiSecurityPolicy.json` - -It contains violations related to OpenAPI set to blocking (enforced). - -### Types of OpenAPI References - -There are different ways of referencing OpenAPI Specification files. The configuration is similar to [External References](/nginx-app-protect/configuration-guide/configuration/#external-references). - -**Note**: Any update of an OpenAPI Specification file referenced in the policy will not trigger a policy compilation. This action needs to be done actively by reloading the NGINX configuration. - -#### URL Reference - -URL reference is the method of referencing an external source by providing its full URL. - -Make sure to configure certificates prior to using the HTTPS protocol - see the [External References](/nginx-app-protect/configuration-guide/configuration/#types-of-references) for details. - -## Configuration in NGINX Ingress Controller - -These are the typical steps to deploy an OpenAPI protection Policy in NGINX Ingress Controller: - -1. Copy the API security policy `/etc/app_protect/conf/NginxApiSecurityPolicy.json` to a different file so that it can be edited. -2. Add the reference to the desired OpenAPI file. -3. Make other custom changes if needed (e.g. enable Data Guard protection). -4. Use a tool to convert the result to YAML. There are many, for example: [`yq` utility](https://github.com/mikefarah/yq). -5. Add the YAML properties to create an `APPolicy` Custom Resource putting the policy itself (as in step 4) within the `spec` property of the Custom Resource. Refer to [App Protect Policies](#app-protect-policies) section above. -6. Create a `Policy` object which references the `APPolicy` Custom Resource as in [this example](https://github.com/nginxinc/kubernetes-ingress/blob/v2.4.1/examples/custom-resources/waf/waf.yaml). -7. Finally, attach the `Policy` object to a `VirtualServer` resource as in [this example](https://github.com/nginxinc/kubernetes-ingress/blob/v2.4.1/examples/custom-resources/waf/virtual-server.yaml). - -**Note**: You need to make sure that the server where the resource files are located is always available when you are compiling your policy. - -##### Example Configuration - -In this example, we are adding an OpenAPI Specification file reference to `/etc/app_protect/conf/NginxApiSecurityPolicy.yaml` using the [link](https://raw.githubusercontent.com/aws-samples/api-gateway-secure-pet-store/master/src/main/resources/swagger.yaml). This will configure allowed data types for `query_int` and `query_str` parameters values. - -**Policy configuration:** - -~~~yaml ---- -apiVersion: appprotect.f5.com/v1beta1 - kind: APPolicy - metadata: - name: petstore_api_security_policy - spec: - policy: - name: petstore_api_security_policy - description: NGINX App Protect WAF API Security Policy for the Petstore API - template: - name: POLICY_TEMPLATE_NGINX_BASE - open-api-files: - - link: https://raw.githubusercontent.com/aws-samples/api-gateway-secure-pet-store/master/src/main/resources/swagger.yaml - blocking-settings: - violations: - - block: true - description: Disallowed file upload content detected in body - name: VIOL_FILE_UPLOAD_IN_BODY - - block: true - description: Mandatory request body is missing - name: VIOL_MANDATORY_REQUEST_BODY - - block: true - description: Illegal parameter location - name: VIOL_PARAMETER_LOCATION - - block: true - description: Mandatory parameter is missing - name: VIOL_MANDATORY_PARAMETER - - block: true - description: JSON data does not comply with JSON schema - name: VIOL_JSON_SCHEMA - - block: true - description: Illegal parameter array value - name: VIOL_PARAMETER_ARRAY_VALUE - - block: true - description: Illegal Base64 value - name: VIOL_PARAMETER_VALUE_BASE64 - - block: true - description: Disallowed file upload content detected - name: VIOL_FILE_UPLOAD - - block: true - description: Illegal request content type - name: VIOL_URL_CONTENT_TYPE - - block: true - description: Illegal static parameter value - name: VIOL_PARAMETER_STATIC_VALUE - - block: true - description: Illegal parameter value length - name: VIOL_PARAMETER_VALUE_LENGTH - - block: true - description: Illegal parameter data type - name: VIOL_PARAMETER_DATA_TYPE - - block: true - description: Illegal parameter numeric value - name: VIOL_PARAMETER_NUMERIC_VALUE - - block: true - description: Parameter value does not comply with regular expression - name: VIOL_PARAMETER_VALUE_REGEXP - - block: true - description: Illegal URL - name: VIOL_URL - - block: true - description: Illegal parameter - name: VIOL_PARAMETER - - block: true - description: Illegal empty parameter value - name: VIOL_PARAMETER_EMPTY_VALUE - - block: true - description: Illegal repeated parameter name - name: VIOL_PARAMETER_REPEATED - -~~~ - -Content of the referenced file `myapi.yaml`: - -~~~yaml -openapi: 3.0.1 -info: - title: 'Primitive data types' - description: 'Primitive data types.' - version: '2.5.0' -servers: - - url: http://localhost -paths: - /query: - get: - tags: - - query_int_str - description: query_int_str - operationId: query_int_str - parameters: - - name: query_int - in: query - required: false - allowEmptyValue: false - schema: - type: integer - - name: query_str - in: query - required: false - allowEmptyValue: true - schema: - type: string - responses: - 200: - description: OK - 404: - description: NotFound -~~~ - -In this case, the following request will trigger an `Illegal parameter data type` violation, as we expect to have an integer value in the `query_int` parameter: - -``` -http://localhost/query?query_int=abc -``` - -The request will be blocked. - -The `link` option is also available in the `openApiFileReference` property and is synonymous with the `open-api-files` property as seen in the App Protect WAF policy example above. - -**Note**: `openApiFileReference` is not an array. - - -## Configuration in NGINX Plus Ingress Controller using Virtual Server Resource -In this example we deploy the NGINX Plus Ingress Controller with NGINX App Protect WAF, a simple web application and then configure load balancing and WAF protection for that application using the VirtualServer resource. - -**Note:** You can find the example, and the files referenced, on [GitHub](https://github.com/nginxinc/kubernetes-ingress/tree/v2.4.1/examples/custom-resources/waf). - -## Prerequisites - -1. Follow the installation [instructions](https://docs.nginx.com/nginx-ingress-controller/installation) to deploy the Ingress Controller with NGINX App Protect WAF. -2. Save the public IP address of the Ingress Controller into a shell variable: - ``` - $ IC_IP=XXX.YYY.ZZZ.III - ``` - -3. Save the HTTP port of the Ingress Controller into a shell variable: - ``` - $ IC_HTTP_PORT= - ``` - -### Step 1. Deploy a Web Application - -Create the application deployment and service: - ``` - $ kubectl apply -f https://raw.githubusercontent.com/nginxinc/kubernetes-ingress/v2.4.1/examples/custom-resources/waf/webapp.yaml - ``` - -### Step 2. Deploy the AP Policy - -1. Create the syslog service and pod for the App Protect security logs: - ``` - $ kubectl apply -f https://raw.githubusercontent.com/nginxinc/kubernetes-ingress/v2.4.1/examples/custom-resources/waf/syslog.yaml - ``` - -2. Create the User Defined Signature, App Protect WAF policy, and log configuration: - - ``` - $ kubectl apply -f https://raw.githubusercontent.com/nginxinc/kubernetes-ingress/v2.4.1/examples/custom-resources/waf/ap-apple-uds.yaml - $ kubectl apply -f https://raw.githubusercontent.com/nginxinc/kubernetes-ingress/v2.4.1/examples/custom-resources/waf/ap-dataguard-alarm-policy.yaml - $ kubectl apply -f https://raw.githubusercontent.com/nginxinc/kubernetes-ingress/v2.4.1/examples/custom-resources/waf/ap-logconf.yaml - ``` - -### Step 3 - Deploy the WAF Policy - -Create the WAF policy - ``` - $ kubectl apply -f https://raw.githubusercontent.com/nginxinc/kubernetes-ingress/v2.4.1/examples/custom-resources/waf/waf.yaml - ``` - Note the App Protect configuration settings in the Policy resource. They enable WAF protection by configuring App Protect with the policy and log configuration created in the previous step. - -### Step 4 - Configure Load Balancing - -1. Create the VirtualServer Resource: - ``` - $ kubectl apply -f https://raw.githubusercontent.com/nginxinc/kubernetes-ingress/v2.4.1/examples/custom-resources/waf/virtual-server.yaml - ``` -Note that the VirtualServer references the policy waf-policy created in Step 3. - -### Step 5 - Test the Application - -To access the application, curl the coffee and the tea services. We'll use the --resolve option to set the Host header of a request with `webapp.example.com` - -1. Send a request to the application: - ``` - $ curl --resolve webapp.example.com:$IC_HTTP_PORT:$IC_IP http://webapp.example.com:$IC_HTTP_PORT/ - Server address: 10.12.0.18:80 - Server name: webapp-7586895968-r26zn - ``` - -2. Now, let's try to send a request with a suspicious URL: - ``` - $ curl --resolve webapp.example.com:$IC_HTTP_PORT:$IC_IP "http://webapp.example.com:$IC_HTTP_PORT/'", + f'{req_url}{v_s_route_setup.route_m.paths[0]}+""', headers={"host": v_s_route_setup.vs_host}, ) print(response.text) diff --git a/tests/suite/test_app_protect_waf_policies_grpc.py b/tests/suite/test_app_protect_waf_policies_grpc.py index c2a1d8e317..1c92237ed0 100644 --- a/tests/suite/test_app_protect_waf_policies_grpc.py +++ b/tests/suite/test_app_protect_waf_policies_grpc.py @@ -78,12 +78,13 @@ def appprotect_setup( elif vs_or_vsr == "vsr": (src_pol_name, vsr_ns, vs_host, vs_name, vsr) = ap_vsr_setup(kube_apis, test_namespace, policy_method) wait_before_test(120) - except Exception as ex: + except Exception: cleanup(kube_apis, ingress_controller_prerequisites, src_pol_name, test_namespace, vs_or_vsr, vs_name, vsr) def fin(): - print("Clean up:") - cleanup(kube_apis, ingress_controller_prerequisites, src_pol_name, test_namespace, vs_or_vsr, vs_name, vsr) + if request.config.getoption("--skip-fixture-teardown") == "no": + print("Clean up:") + cleanup(kube_apis, ingress_controller_prerequisites, src_pol_name, test_namespace, vs_or_vsr, vs_name, vsr) request.addfinalizer(fin) if vs_or_vsr == "vs": @@ -207,6 +208,7 @@ def grpc_waf_allow(kube_apis, test_namespace, public_ip, vs_host, port_ssl): @pytest.mark.skip_for_nginx_oss @pytest.mark.appprotect +@pytest.mark.appprotect_waf_policies_grpc @pytest.mark.parametrize( "crd_ingress_controller_with_ap", [ @@ -254,10 +256,10 @@ def test_responses_grpc_block( syslog_pod = kube_apis.v1.list_namespaced_pod(test_namespace).items[-1].metadata.name log_contents = get_file_contents(kube_apis.v1, log_loc, syslog_pod, test_namespace) assert ( - 'ASM:attack_type="Directory Indexing"' in log_contents - and 'violations="Illegal gRPC method"' in log_contents - and 'severity="Error"' in log_contents - and 'outcome="REJECTED"' in log_contents + "ASM:attack_type=" in str(log_contents) + and "violations=" in str(log_contents) + and "severity=" in str(log_contents) + and "outcome=" in str(log_contents) ) @pytest.mark.parametrize( @@ -292,15 +294,16 @@ def test_responses_grpc_allow( syslog_pod = kube_apis.v1.list_namespaced_pod(test_namespace).items[-1].metadata.name log_contents = get_file_contents(kube_apis.v1, log_loc, syslog_pod, test_namespace) assert ( - 'ASM:attack_type="N/A"' in log_contents - and 'violations="N/A"' in log_contents - and 'severity="Informational"' in log_contents - and 'outcome="PASSED"' in log_contents + "ASM:attack_type=" in str(log_contents) + and "violations=" in str(log_contents) + and "severity=" in str(log_contents) + and "outcome=" in str(log_contents) ) @pytest.mark.skip_for_nginx_oss @pytest.mark.appprotect +@pytest.mark.appprotect_waf_policies_grpc @pytest.mark.parametrize( "crd_ingress_controller_with_ap", [ diff --git a/tests/suite/test_app_protect_wafv5_integration.py b/tests/suite/test_app_protect_wafv5_integration.py new file mode 100644 index 0000000000..777d9beb2d --- /dev/null +++ b/tests/suite/test_app_protect_wafv5_integration.py @@ -0,0 +1,163 @@ +import pytest +import requests +from settings import TEST_DATA +from suite.utils.policy_resources_utils import create_policy_from_yaml, delete_policy +from suite.utils.resources_utils import wait_before_test +from suite.utils.vs_vsr_resources_utils import ( + create_virtual_server_from_yaml, + delete_virtual_server, + patch_v_s_route_from_yaml, + patch_virtual_server_from_yaml, +) + + +@pytest.fixture(scope="class") +def waf_setup(kube_apis, test_namespace) -> None: + waf = f"{TEST_DATA}/ap-waf-v5/policies/waf.yaml" + create_policy_from_yaml(kube_apis.custom_objects, waf, test_namespace) + wait_before_test() + + +@pytest.mark.skip_for_nginx_oss +@pytest.mark.appprotect_waf_v5 +@pytest.mark.parametrize( + "crd_ingress_controller_with_waf_v5, virtual_server_setup", + [ + ( + { + "type": "complete", + "extra_args": [ + f"-enable-app-protect", + ], + }, + { + "example": "ap-waf-v5", + "app_type": "simple", + }, + ) + ], + indirect=True, +) +class TestAppProtectWAFv5IntegrationVS: + def restore_default_vs(self, kube_apis, virtual_server_setup) -> None: + """ + Restore VirtualServer without policy spec + """ + std_vs_src = f"{TEST_DATA}/ap-waf-v5/standard/virtual-server.yaml" + delete_virtual_server(kube_apis.custom_objects, virtual_server_setup.vs_name, virtual_server_setup.namespace) + create_virtual_server_from_yaml(kube_apis.custom_objects, std_vs_src, virtual_server_setup.namespace) + wait_before_test() + + @pytest.mark.parametrize( + "vs_src", + [f"{TEST_DATA}/ap-waf-v5/virtual-server-waf-spec.yaml", f"{TEST_DATA}/ap-waf-v5/virtual-server-waf-route.yaml"], + ) + def test_ap_waf_v5_policy_block_vs( + self, + kube_apis, + ingress_controller_prerequisites, + crd_ingress_controller_with_waf_v5, + test_namespace, + virtual_server_setup, + waf_setup, + vs_src, + ): + patch_virtual_server_from_yaml( + kube_apis.custom_objects, + virtual_server_setup.vs_name, + vs_src, + virtual_server_setup.namespace, + ) + + print("----------------------- Send request with embedded malicious script----------------------") + count = 0 + response = requests.get( + virtual_server_setup.backend_1_url + "", + headers={"host": virtual_server_setup.vs_host}, + ) + while count < 5 and "Request Rejected" not in response.text: + response = requests.get( + virtual_server_setup.backend_1_url + "", + headers={"host": virtual_server_setup.vs_host}, + ) + wait_before_test() + count += 1 + self.restore_default_vs(kube_apis, virtual_server_setup) + assert response.status_code == 200 + assert "The requested URL was rejected. Please consult with your administrator." in response.text + + +@pytest.mark.skip_for_nginx_oss +@pytest.mark.appprotect_waf_v5 +@pytest.mark.parametrize( + "crd_ingress_controller_with_waf_v5, v_s_route_setup", + [ + ( + { + "type": "complete", + "extra_args": [ + f"-enable-app-protect", + ], + }, + { + "example": "virtual-server-route", + }, + ) + ], + indirect=True, +) +class TestAppProtectWAFv5IntegrationVSR: + + def restore_default_vsr(self, kube_apis, v_s_route_setup) -> None: + """ + Function to revert vsr deployments to standard state + """ + patch_src_m = f"{TEST_DATA}/virtual-server-route/route-multiple.yaml" + patch_v_s_route_from_yaml( + kube_apis.custom_objects, + v_s_route_setup.route_m.name, + patch_src_m, + v_s_route_setup.route_m.namespace, + ) + wait_before_test() + + def test_ap_waf_v5_policy_block_vsr( + self, + kube_apis, + ingress_controller_prerequisites, + crd_ingress_controller_with_waf_v5, + test_namespace, + v_s_route_setup, + ): + req_url = f"http://{v_s_route_setup.public_endpoint.public_ip}:{v_s_route_setup.public_endpoint.port}" + waf_subroute_vsr_src = f"{TEST_DATA}/ap-waf-v5/virtual-server-route-waf-subroute.yaml" + pol = create_policy_from_yaml( + kube_apis.custom_objects, + f"{TEST_DATA}/ap-waf-v5/policies/waf.yaml", + v_s_route_setup.route_m.namespace, + ) + wait_before_test() + patch_v_s_route_from_yaml( + kube_apis.custom_objects, + v_s_route_setup.route_m.name, + waf_subroute_vsr_src, + v_s_route_setup.route_m.namespace, + ) + wait_before_test() + print("----------------------- Send request with embedded malicious script----------------------") + count = 0 + response = requests.get( + f'{req_url}{v_s_route_setup.route_m.paths[0]}+""', + headers={"host": v_s_route_setup.vs_host}, + ) + while count < 5 and "Request Rejected" not in response.text: + response = requests.get( + f'{req_url}{v_s_route_setup.route_m.paths[0]}+""', + headers={"host": v_s_route_setup.vs_host}, + ) + wait_before_test() + count += 1 + self.restore_default_vsr(kube_apis, v_s_route_setup) + delete_policy(kube_apis.custom_objects, pol, v_s_route_setup.route_m.namespace) + assert response.status_code == 200 + assert "The requested URL was rejected. Please consult with your administrator." in response.text diff --git a/tests/suite/test_app_protect_wafv5_integration_rorfs.py b/tests/suite/test_app_protect_wafv5_integration_rorfs.py new file mode 100644 index 0000000000..3da05e5615 --- /dev/null +++ b/tests/suite/test_app_protect_wafv5_integration_rorfs.py @@ -0,0 +1,163 @@ +import pytest +import requests +from settings import TEST_DATA +from suite.utils.policy_resources_utils import create_policy_from_yaml, delete_policy +from suite.utils.resources_utils import wait_before_test +from suite.utils.vs_vsr_resources_utils import ( + create_virtual_server_from_yaml, + delete_virtual_server, + patch_v_s_route_from_yaml, + patch_virtual_server_from_yaml, +) + + +@pytest.fixture(scope="class") +def waf_setup(kube_apis, test_namespace) -> None: + waf = f"{TEST_DATA}/ap-waf-v5/policies/waf.yaml" + create_policy_from_yaml(kube_apis.custom_objects, waf, test_namespace) + wait_before_test() + + +@pytest.mark.skip_for_nginx_oss +@pytest.mark.appprotect_waf_v5 +@pytest.mark.parametrize( + "crd_ingress_controller_with_waf_v5, virtual_server_setup", + [ + ( + { + "type": "rorfs", + "extra_args": [ + f"-enable-app-protect", + ], + }, + { + "example": "ap-waf-v5", + "app_type": "simple", + }, + ), + ], + indirect=True, +) +class TestAppProtectWAFv5IntegrationVSrorfs: + def restore_default_vs(self, kube_apis, virtual_server_setup) -> None: + """ + Restore VirtualServer without policy spec + """ + std_vs_src = f"{TEST_DATA}/ap-waf-v5/standard/virtual-server.yaml" + delete_virtual_server(kube_apis.custom_objects, virtual_server_setup.vs_name, virtual_server_setup.namespace) + create_virtual_server_from_yaml(kube_apis.custom_objects, std_vs_src, virtual_server_setup.namespace) + wait_before_test() + + @pytest.mark.parametrize( + "vs_src", + [f"{TEST_DATA}/ap-waf-v5/virtual-server-waf-spec.yaml"], + ) + def test_ap_waf_v5_policy_block_vs( + self, + kube_apis, + ingress_controller_prerequisites, + crd_ingress_controller_with_waf_v5, + test_namespace, + virtual_server_setup, + waf_setup, + vs_src, + ): + patch_virtual_server_from_yaml( + kube_apis.custom_objects, + virtual_server_setup.vs_name, + vs_src, + virtual_server_setup.namespace, + ) + + print("----------------------- Send request with embedded malicious script----------------------") + count = 0 + response = requests.get( + virtual_server_setup.backend_1_url + "", + headers={"host": virtual_server_setup.vs_host}, + ) + while count < 5 and "Request Rejected" not in response.text: + response = requests.get( + virtual_server_setup.backend_1_url + "", + headers={"host": virtual_server_setup.vs_host}, + ) + wait_before_test() + count += 1 + self.restore_default_vs(kube_apis, virtual_server_setup) + assert response.status_code == 200 + assert "The requested URL was rejected. Please consult with your administrator." in response.text + + +@pytest.mark.skip_for_nginx_oss +@pytest.mark.appprotect_waf_v5 +@pytest.mark.parametrize( + "crd_ingress_controller_with_waf_v5, v_s_route_setup", + [ + ( + { + "type": "rorfs", + "extra_args": [ + f"-enable-app-protect", + ], + }, + { + "example": "virtual-server-route", + }, + ) + ], + indirect=True, +) +class TestAppProtectWAFv5IntegrationVSRrorfs: + + def restore_default_vsr(self, kube_apis, v_s_route_setup) -> None: + """ + Function to revert vsr deployments to standard state + """ + patch_src_m = f"{TEST_DATA}/virtual-server-route/route-multiple.yaml" + patch_v_s_route_from_yaml( + kube_apis.custom_objects, + v_s_route_setup.route_m.name, + patch_src_m, + v_s_route_setup.route_m.namespace, + ) + wait_before_test() + + def test_ap_waf_v5_policy_block_vsr( + self, + kube_apis, + ingress_controller_prerequisites, + crd_ingress_controller_with_waf_v5, + test_namespace, + v_s_route_setup, + ): + req_url = f"http://{v_s_route_setup.public_endpoint.public_ip}:{v_s_route_setup.public_endpoint.port}" + waf_subroute_vsr_src = f"{TEST_DATA}/ap-waf-v5/virtual-server-route-waf-subroute.yaml" + pol = create_policy_from_yaml( + kube_apis.custom_objects, + f"{TEST_DATA}/ap-waf-v5/policies/waf.yaml", + v_s_route_setup.route_m.namespace, + ) + wait_before_test() + patch_v_s_route_from_yaml( + kube_apis.custom_objects, + v_s_route_setup.route_m.name, + waf_subroute_vsr_src, + v_s_route_setup.route_m.namespace, + ) + wait_before_test() + print("----------------------- Send request with embedded malicious script----------------------") + count = 0 + response = requests.get( + f'{req_url}{v_s_route_setup.route_m.paths[0]}+""', + headers={"host": v_s_route_setup.vs_host}, + ) + while count < 5 and "Request Rejected" not in response.text: + response = requests.get( + f'{req_url}{v_s_route_setup.route_m.paths[0]}+""', + headers={"host": v_s_route_setup.vs_host}, + ) + wait_before_test() + count += 1 + self.restore_default_vsr(kube_apis, v_s_route_setup) + delete_policy(kube_apis.custom_objects, pol, v_s_route_setup.route_m.namespace) + assert response.status_code == 200 + assert "The requested URL was rejected. Please consult with your administrator." in response.text diff --git a/tests/suite/test_app_protect_watch_namespace.py b/tests/suite/test_app_protect_watch_namespace.py index e979cea86b..d91f74a898 100644 --- a/tests/suite/test_app_protect_watch_namespace.py +++ b/tests/suite/test_app_protect_watch_namespace.py @@ -102,16 +102,17 @@ def backend_setup(request, kube_apis, ingress_controller_endpoint) -> BackendSet wait_before_test() def fin(): - print("Clean up:") - src_ing_yaml = f"{TEST_DATA}/appprotect/appprotect-ingress.yaml" - delete_items_from_yaml(kube_apis, src_ing_yaml, test_namespace) - delete_ap_policy(kube_apis.custom_objects, pol_name, policy_namespace) - delete_namespace(kube_apis.v1, policy_namespace) - delete_ap_logconf(kube_apis.custom_objects, log_name, test_namespace) - delete_common_app(kube_apis, "simple", test_namespace) - src_sec_yaml = f"{TEST_DATA}/appprotect/appprotect-secret.yaml" - delete_items_from_yaml(kube_apis, src_sec_yaml, test_namespace) - delete_namespace(kube_apis.v1, test_namespace) + if request.config.getoption("--skip-fixture-teardown") == "no": + print("Clean up:") + src_ing_yaml = f"{TEST_DATA}/appprotect/appprotect-ingress.yaml" + delete_items_from_yaml(kube_apis, src_ing_yaml, test_namespace) + delete_ap_policy(kube_apis.custom_objects, pol_name, policy_namespace) + delete_namespace(kube_apis.v1, policy_namespace) + delete_ap_logconf(kube_apis.custom_objects, log_name, test_namespace) + delete_common_app(kube_apis, "simple", test_namespace) + src_sec_yaml = f"{TEST_DATA}/appprotect/appprotect-secret.yaml" + delete_items_from_yaml(kube_apis, src_sec_yaml, test_namespace) + delete_namespace(kube_apis.v1, test_namespace) request.addfinalizer(fin) @@ -124,6 +125,7 @@ def fin(): @pytest.mark.skip_for_nginx_oss @pytest.mark.appprotect +@pytest.mark.appprotect_watch @pytest.mark.parametrize( "crd_ingress_controller_with_ap", [{"extra_args": [f"-enable-custom-resources", f"-enable-app-protect", f"-enable-prometheus-metrics"]}], @@ -156,6 +158,7 @@ def test_responses(self, request, kube_apis, crd_ingress_controller_with_ap, bac @pytest.mark.skip_for_nginx_oss @pytest.mark.appprotect +@pytest.mark.appprotect_watch @pytest.mark.parametrize( "crd_ingress_controller_with_ap", [ diff --git a/tests/suite/test_app_protect_watch_namespace_label.py b/tests/suite/test_app_protect_watch_namespace_label.py new file mode 100644 index 0000000000..8aedce9cfc --- /dev/null +++ b/tests/suite/test_app_protect_watch_namespace_label.py @@ -0,0 +1,218 @@ +import time + +import pytest +import requests +from settings import TEST_DATA +from suite.utils.ap_resources_utils import ( + create_ap_logconf_from_yaml, + create_ap_policy_from_yaml, + delete_ap_logconf, + delete_ap_policy, +) +from suite.utils.resources_utils import ( + create_example_app, + create_ingress_with_ap_annotations, + create_items_from_yaml, + create_namespace_with_name_from_yaml, + delete_common_app, + delete_items_from_yaml, + delete_namespace, + ensure_connection_to_public_endpoint, + ensure_response_from_backend, + patch_namespace_with_label, + wait_before_test, + wait_until_all_pods_are_ready, +) +from suite.utils.yaml_utils import get_first_ingress_host_from_yaml + +# This test shows that a policy outside of the namespace test_namespace is not picked up by IC. + +valid_resp_body = "Server name:" +invalid_resp_body = "The requested URL was rejected. Please consult with your administrator." +reload_times = {} + + +class BackendSetup: + """ + Encapsulate the example details. + + Attributes: + req_url (str): + ingress_host (str): + """ + + def __init__(self, req_url, req_url_2, metrics_url, ingress_host, test_namespace, policy_namespace): + self.req_url = req_url + self.req_url_2 = req_url_2 + self.metrics_url = metrics_url + self.ingress_host = ingress_host + self.test_namespace = test_namespace + self.policy_namespace = policy_namespace + + +@pytest.fixture(scope="class") +def backend_setup(request, kube_apis, ingress_controller_endpoint) -> BackendSetup: + """ + Deploy a simple application and AppProtect manifests. + + :param request: pytest fixture + :param kube_apis: client apis + :param ingress_controller_endpoint: public endpoint + :param test_namespace: + :return: BackendSetup + """ + timestamp = round(time.time() * 1000) + test_namespace = f"test-namespace-{str(timestamp)}" + policy_namespace = f"policy-test-namespace-{str(timestamp)}" + policy = "file-block" + + create_namespace_with_name_from_yaml(kube_apis.v1, test_namespace, f"{TEST_DATA}/common/ns.yaml") + print("------------------------- Deploy backend application -------------------------") + + create_example_app(kube_apis, "simple", test_namespace) + req_url = f"https://{ingress_controller_endpoint.public_ip}:{ingress_controller_endpoint.port_ssl}/backend1" + req_url_2 = f"https://{ingress_controller_endpoint.public_ip}:{ingress_controller_endpoint.port_ssl}/backend2" + metrics_url = f"http://{ingress_controller_endpoint.public_ip}:{ingress_controller_endpoint.metrics_port}/metrics" + wait_until_all_pods_are_ready(kube_apis.v1, test_namespace) + ensure_connection_to_public_endpoint( + ingress_controller_endpoint.public_ip, + ingress_controller_endpoint.port, + ingress_controller_endpoint.port_ssl, + ) + + print("------------------------- Deploy Secret -----------------------------") + src_sec_yaml = f"{TEST_DATA}/appprotect/appprotect-secret.yaml" + create_items_from_yaml(kube_apis, src_sec_yaml, test_namespace) + + print("------------------------- Deploy logconf -----------------------------") + src_log_yaml = f"{TEST_DATA}/appprotect/logconf.yaml" + log_name = create_ap_logconf_from_yaml(kube_apis.custom_objects, src_log_yaml, test_namespace) + + print(f"------------------------- Deploy namespace: {policy_namespace} ---------------------------") + create_namespace_with_name_from_yaml(kube_apis.v1, policy_namespace, f"{TEST_DATA}/common/ns.yaml") + + print(f"------------------------- Deploy appolicy: {policy} ---------------------------") + src_pol_yaml = f"{TEST_DATA}/appprotect/{policy}.yaml" + pol_name = create_ap_policy_from_yaml(kube_apis.custom_objects, src_pol_yaml, policy_namespace) + + print("------------------------- Deploy ingress -----------------------------") + ingress_host = {} + src_ing_yaml = f"{TEST_DATA}/appprotect/appprotect-ingress.yaml" + create_ingress_with_ap_annotations( + kube_apis, src_ing_yaml, test_namespace, f"{policy_namespace}/{policy}", "True", "True", "127.0.0.1:514" + ) + ingress_host = get_first_ingress_host_from_yaml(src_ing_yaml) + wait_before_test() + + def fin(): + if request.config.getoption("--skip-fixture-teardown") == "no": + print("Clean up:") + src_ing_yaml = f"{TEST_DATA}/appprotect/appprotect-ingress.yaml" + delete_items_from_yaml(kube_apis, src_ing_yaml, test_namespace) + delete_ap_policy(kube_apis.custom_objects, pol_name, policy_namespace) + delete_namespace(kube_apis.v1, policy_namespace) + delete_ap_logconf(kube_apis.custom_objects, log_name, test_namespace) + delete_common_app(kube_apis, "simple", test_namespace) + src_sec_yaml = f"{TEST_DATA}/appprotect/appprotect-secret.yaml" + delete_items_from_yaml(kube_apis, src_sec_yaml, test_namespace) + delete_namespace(kube_apis.v1, test_namespace) + + request.addfinalizer(fin) + + return BackendSetup(req_url, req_url_2, metrics_url, ingress_host, test_namespace, policy_namespace) + + +@pytest.mark.skip_for_nginx_oss +@pytest.mark.appprotect +@pytest.mark.appprotect_watch +@pytest.mark.parametrize( + "crd_ingress_controller_with_ap", + [ + { + "extra_args": [ + f"-enable-custom-resources", + f"-enable-app-protect", + f"-enable-prometheus-metrics", + f"-watch-namespace-label=app=watch", + f"-log-level=debug", + ] + } + ], + indirect=True, +) +class TestAppProtectWatchNamespaceLabelEnabled: + def test_responses(self, request, kube_apis, crd_ingress_controller_with_ap, backend_setup): + """ + Test file-block AppProtect policy with -watch-namespace-label + """ + patch_namespace_with_label( + kube_apis.v1, backend_setup.test_namespace, "watch", f"{TEST_DATA}/common/ns-patch.yaml" + ) + wait_before_test() + print("------------- Run test for AP policy: file-block not enforced --------------") + # The policy namespace does not have the watched label, show the policy is not enforced + print(f"Request URL: {backend_setup.req_url} and Host: {backend_setup.ingress_host}") + + ensure_response_from_backend(backend_setup.req_url, backend_setup.ingress_host, check404=True) + + print("----------------------- Send request ----------------------") + resp = requests.get( + f"{backend_setup.req_url}/test.bat", headers={"host": backend_setup.ingress_host}, verify=False + ) + + print(resp.text) + + assert valid_resp_body in resp.text + assert resp.status_code == 200 + + # Add the label to the policy namespace, show the policy is now enforced + patch_namespace_with_label( + kube_apis.v1, backend_setup.policy_namespace, "watch", f"{TEST_DATA}/common/ns-patch.yaml" + ) + wait_before_test(15) + print("------------- Run test for AP policy: file-block is enforced now --------------") + print(f"Request URL: {backend_setup.req_url} and Host: {backend_setup.ingress_host}") + + ensure_response_from_backend(backend_setup.req_url, backend_setup.ingress_host, check404=True) + + print("----------------------- Send request ----------------------") + resp = requests.get( + f"{backend_setup.req_url}/test.bat", headers={"host": backend_setup.ingress_host}, verify=False + ) + retry = 0 + while invalid_resp_body not in resp.text and retry <= 60: + resp = requests.get( + f"{backend_setup.req_url}/test.bat", headers={"host": backend_setup.ingress_host}, verify=False + ) + retry += 1 + wait_before_test(1) + print(f"Policy not yet enforced, retrying... #{retry}") + + assert invalid_resp_body in resp.text + assert resp.status_code == 200 + + # Remove the label again fro the policy namespace, show the policy is not enforced again + patch_namespace_with_label( + kube_apis.v1, backend_setup.policy_namespace, "nowatch", f"{TEST_DATA}/common/ns-patch.yaml" + ) + wait_before_test(15) + print("------------- Run test for AP policy: file-block not enforced again --------------") + print(f"Request URL: {backend_setup.req_url} and Host: {backend_setup.ingress_host}") + + ensure_response_from_backend(backend_setup.req_url, backend_setup.ingress_host, check404=True) + + print("----------------------- Send request ----------------------") + resp = requests.get( + f"{backend_setup.req_url}/test.bat", headers={"host": backend_setup.ingress_host}, verify=False + ) + retry = 0 + while valid_resp_body not in resp.text and retry <= 60: + resp = requests.get( + f"{backend_setup.req_url}/test.bat", headers={"host": backend_setup.ingress_host}, verify=False + ) + retry += 1 + wait_before_test(1) + print(f"Policy not yet removed, retrying... #{retry}") + + assert valid_resp_body in resp.text + assert resp.status_code == 200 diff --git a/tests/suite/test_auth_basic_auth_mergeable.py b/tests/suite/test_auth_basic_auth_mergeable.py index de55853ab4..9a9b4d746f 100644 --- a/tests/suite/test_auth_basic_auth_mergeable.py +++ b/tests/suite/test_auth_basic_auth_mergeable.py @@ -74,19 +74,22 @@ def auth_basic_auth_setup( wait_before_test(2) def fin(): - print("Delete Master Secret:") - if is_secret_present(kube_apis.v1, master_secret_name, test_namespace): - delete_secret(kube_apis.v1, master_secret_name, test_namespace) - - print("Delete Minion Secret:") - if is_secret_present(kube_apis.v1, minion_secret_name, test_namespace): - delete_secret(kube_apis.v1, minion_secret_name, test_namespace) - - print("Clean up the Auth Basic Auth Mergeable Minions Application:") - delete_common_app(kube_apis, "simple", test_namespace) - delete_items_from_yaml( - kube_apis, f"{TEST_DATA}/auth-basic-auth-mergeable/mergeable/auth-basic-auth-ingress.yaml", test_namespace - ) + if request.config.getoption("--skip-fixture-teardown") == "no": + print("Delete Master Secret:") + if is_secret_present(kube_apis.v1, master_secret_name, test_namespace): + delete_secret(kube_apis.v1, master_secret_name, test_namespace) + + print("Delete Minion Secret:") + if is_secret_present(kube_apis.v1, minion_secret_name, test_namespace): + delete_secret(kube_apis.v1, minion_secret_name, test_namespace) + + print("Clean up the Auth Basic Auth Mergeable Minions Application:") + delete_common_app(kube_apis, "simple", test_namespace) + delete_items_from_yaml( + kube_apis, + f"{TEST_DATA}/auth-basic-auth-mergeable/mergeable/auth-basic-auth-ingress.yaml", + test_namespace, + ) request.addfinalizer(fin) @@ -155,6 +158,7 @@ def get_credentials_from_file(creds_type) -> str: @pytest.mark.ingresses +@pytest.mark.basic_auth class TestAuthBasicAuthMergeableMinions: def test_auth_basic_auth_response_codes(self, kube_apis, auth_basic_auth_setup, test_namespace): print("Step 1: execute check after secrets creation") diff --git a/tests/suite/test_auth_basic_policies.py b/tests/suite/test_auth_basic_policies.py index ba3bafa97f..2a4bd380d0 100644 --- a/tests/suite/test_auth_basic_policies.py +++ b/tests/suite/test_auth_basic_policies.py @@ -59,7 +59,6 @@ def to_base64(b64_string): "type": "complete", "extra_args": [ f"-enable-custom-resources", - f"-enable-preview-policies", f"-enable-leader-election=false", ], }, diff --git a/tests/suite/test_auth_basic_policies_vsr.py b/tests/suite/test_auth_basic_policies_vsr.py index b1e3d59a36..b527f7858f 100644 --- a/tests/suite/test_auth_basic_policies_vsr.py +++ b/tests/suite/test_auth_basic_policies_vsr.py @@ -49,7 +49,6 @@ def to_base64(b64_string): "type": "complete", "extra_args": [ f"-enable-custom-resources", - f"-enable-preview-policies", f"-enable-leader-election=false", ], }, diff --git a/tests/suite/test_auth_basic_secrets.py b/tests/suite/test_auth_basic_secrets.py index 5052f96cc6..0a9d2f6c67 100644 --- a/tests/suite/test_auth_basic_secrets.py +++ b/tests/suite/test_auth_basic_secrets.py @@ -72,11 +72,14 @@ def auth_basic_secrets_setup( ) def fin(): - print("Clean up the Auth Basic Secrets Application:") - delete_common_app(kube_apis, "simple", test_namespace) - delete_items_from_yaml( - kube_apis, f"{TEST_DATA}/auth-basic-secrets/{request.param}/auth-basic-secrets-ingress.yaml", test_namespace - ) + if request.config.getoption("--skip-fixture-teardown") == "no": + print("Clean up the Auth Basic Secrets Application:") + delete_common_app(kube_apis, "simple", test_namespace) + delete_items_from_yaml( + kube_apis, + f"{TEST_DATA}/auth-basic-secrets/{request.param}/auth-basic-secrets-ingress.yaml", + test_namespace, + ) request.addfinalizer(fin) @@ -93,9 +96,10 @@ def auth_basic_secret( wait_before_test(1) def fin(): - print("Delete Secret:") - if is_secret_present(kube_apis.v1, secret_name, test_namespace): - delete_secret(kube_apis.v1, secret_name, test_namespace) + if request.config.getoption("--skip-fixture-teardown") == "no": + print("Delete Secret:") + if is_secret_present(kube_apis.v1, secret_name, test_namespace): + delete_secret(kube_apis.v1, secret_name, test_namespace) request.addfinalizer(fin) @@ -103,6 +107,7 @@ def fin(): @pytest.mark.ingresses +@pytest.mark.basic_auth class TestAuthBasicSecrets: def test_response_code_200_and_server_name(self, auth_basic_secrets_setup, auth_basic_secret): req_url = f"http://{auth_basic_secrets_setup.public_endpoint.public_ip}:{auth_basic_secrets_setup.public_endpoint.port}/backend2" diff --git a/tests/suite/test_batch_reloads.py b/tests/suite/test_batch_reloads.py index ac86c787fd..6dde4033e6 100644 --- a/tests/suite/test_batch_reloads.py +++ b/tests/suite/test_batch_reloads.py @@ -216,6 +216,7 @@ def fin(): @pytest.mark.skip_for_nginx_oss @pytest.mark.batch_start @pytest.mark.appprotect +@pytest.mark.appprotect_batch @pytest.mark.parametrize( "crd_ingress_controller_with_ap", [ @@ -373,6 +374,7 @@ def fin(): @pytest.mark.skip_for_nginx_oss @pytest.mark.batch_start @pytest.mark.appprotect +@pytest.mark.appprotect_batch @pytest.mark.parametrize( "crd_ingress_controller_with_ap, virtual_server_setup", [ diff --git a/tests/suite/test_batch_startup_times.py b/tests/suite/test_batch_startup_times.py index eceec15098..037bb1a05c 100644 --- a/tests/suite/test_batch_startup_times.py +++ b/tests/suite/test_batch_startup_times.py @@ -216,6 +216,7 @@ def fin(): @pytest.mark.skip_for_nginx_oss @pytest.mark.batch_start @pytest.mark.appprotect +@pytest.mark.appprotect_batch @pytest.mark.parametrize( "crd_ingress_controller_with_ap", [ @@ -379,6 +380,7 @@ def fin(): @pytest.mark.skip_for_nginx_oss @pytest.mark.batch_start @pytest.mark.appprotect +@pytest.mark.appprotect_batch @pytest.mark.parametrize( "crd_ingress_controller_with_ap, virtual_server_setup", [ diff --git a/tests/suite/test_build_info.py b/tests/suite/test_build_info.py index 41359e04b5..42c5611849 100644 --- a/tests/suite/test_build_info.py +++ b/tests/suite/test_build_info.py @@ -3,7 +3,9 @@ import time import pytest -from suite.utils.resources_utils import get_first_pod_name, wait_until_all_pods_are_ready +import yaml +from settings import HELM_CHARTS +from suite.utils.resources_utils import get_first_pod_name, wait_before_test, wait_until_all_pods_are_ready @pytest.mark.ingresses @@ -11,12 +13,23 @@ class TestBuildVersion: def test_build_version(self, ingress_controller, kube_apis, ingress_controller_prerequisites): """ - Test Version tag of build i.e. 'Version=' + Test Version tag of build i.e. 'Version=' is same as the version in the chart.yaml file """ + with open(f"{HELM_CHARTS}/Chart.yaml") as f: + chart = yaml.safe_load(f) + ic_ver = chart["appVersion"] + print(f"NIC version from chart: {ic_ver}") _info = self.send_build_info(kube_apis, ingress_controller_prerequisites) - _version = _info[_info.find("Version=") + len("Version=") : _info.rfind("GitCommit=")] + count = 0 + while "Version=" not in _info and count < 5: + _info = self.send_build_info(kube_apis, ingress_controller_prerequisites) + count += 1 + time.sleep(1) + _version = _info[_info.find("Version=") + len("Version=") : _info.rfind("Commit=")] logging.info(_version) + print(f"Version from pod logs: {_version}") assert _version != " " + assert ic_ver in _version def send_build_info(self, kube_apis, ingress_controller_prerequisites) -> str: """ @@ -27,7 +40,7 @@ def send_build_info(self, kube_apis, ingress_controller_prerequisites) -> str: pod_name = get_first_pod_name(kube_apis.v1, ingress_controller_prerequisites.namespace) wait_until_all_pods_are_ready(kube_apis.v1, ingress_controller_prerequisites.namespace) while not ready: - time.sleep(1) + wait_before_test() try: api_response = kube_apis.v1.read_namespaced_pod_log( name=pod_name, @@ -49,8 +62,9 @@ def send_build_info(self, kube_apis, ingress_controller_prerequisites) -> str: _log = br.readline().strip() try: _info = _log[_log.find("Version") :].strip() + print(f"Version and GitCommit info: {_info}") logging.info(f"Version and GitCommit info: {_info}") - except Exception as e: + except Exception: logging.exception(f"Tag labels not found") return _info diff --git a/tests/suite/test_custom_annotations.py b/tests/suite/test_custom_annotations.py index c36525fa65..aa0491f4a3 100644 --- a/tests/suite/test_custom_annotations.py +++ b/tests/suite/test_custom_annotations.py @@ -54,14 +54,15 @@ def custom_annotations_setup( ic_pod_name = get_first_pod_name(kube_apis.v1, ingress_controller_prerequisites.namespace) def fin(): - print("Clean up Custom Annotations Example:") - replace_configmap_from_yaml( - kube_apis.v1, - ingress_controller_prerequisites.config_map["metadata"]["name"], - ingress_controller_prerequisites.namespace, - f"{DEPLOYMENTS}/common/nginx-config.yaml", - ) - delete_items_from_yaml(kube_apis, ing_src, test_namespace) + if request.config.getoption("--skip-fixture-teardown") == "no": + print("Clean up Custom Annotations Example:") + replace_configmap_from_yaml( + kube_apis.v1, + ingress_controller_prerequisites.config_map["metadata"]["name"], + ingress_controller_prerequisites.namespace, + f"{DEPLOYMENTS}/common/nginx-config.yaml", + ) + delete_items_from_yaml(kube_apis, ing_src, test_namespace) request.addfinalizer(fin) diff --git a/tests/suite/test_default_server.py b/tests/suite/test_default_server.py index 537890b4c0..34e0761e04 100644 --- a/tests/suite/test_default_server.py +++ b/tests/suite/test_default_server.py @@ -1,7 +1,9 @@ from ssl import SSLError import pytest -from settings import DEPLOYMENTS, TEST_DATA +import requests +from requests.exceptions import ConnectionError +from settings import TEST_DATA from suite.utils.resources_utils import ( create_secret_from_yaml, delete_secret, @@ -29,7 +31,7 @@ def assert_unrecognized_name_error(endpoint): assert "TLSV1_UNRECOGNIZED_NAME" in e.reason -secret_path = f"{DEPLOYMENTS}/common/default-server-secret.yaml" +secret_path = f"{TEST_DATA}/common/default-server-secret.yaml" test_data_path = f"{TEST_DATA}/default-server" invalid_secret_path = f"{test_data_path}/invalid-tls-secret.yaml" new_secret_path = f"{test_data_path}/new-tls-secret.yaml" @@ -42,14 +44,21 @@ def default_server_setup(ingress_controller_endpoint, ingress_controller): ensure_connection(f"http://{ingress_controller_endpoint.public_ip}:{ingress_controller_endpoint.port}/") +@pytest.fixture(scope="class") +def default_server_setup_custom_port(ingress_controller_endpoint, ingress_controller): + ensure_connection(f"http://{ingress_controller_endpoint.public_ip}:{ingress_controller_endpoint.custom_http}/") + ensure_connection(f"https://{ingress_controller_endpoint.public_ip}:{ingress_controller_endpoint.custom_https}/") + + @pytest.fixture(scope="class") def secret_setup(request, kube_apis): def fin(): - if is_secret_present(kube_apis.v1, secret_name, secret_namespace): - print("cleaning up secret!") - delete_secret(kube_apis.v1, secret_name, secret_namespace) - # restore the original secret created in ingress_controller_prerequisites fixture - create_secret_from_yaml(kube_apis.v1, secret_namespace, secret_path) + if request.config.getoption("--skip-fixture-teardown") == "no": + if is_secret_present(kube_apis.v1, secret_name, secret_namespace): + print("cleaning up secret!") + delete_secret(kube_apis.v1, secret_name, secret_namespace) + # restore the original secret created in ingress_controller_prerequisites fixture + create_secret_from_yaml(kube_apis.v1, secret_namespace, secret_path) request.addfinalizer(fin) @@ -94,3 +103,45 @@ def test_with_default_tls_secret(self, kube_apis, ingress_controller_endpoint, s def test_without_default_tls_secret(self, ingress_controller_endpoint, default_server_setup): print("Ensure connection to HTTPS cannot be established") assert_unrecognized_name_error(ingress_controller_endpoint) + + @pytest.mark.parametrize( + "ingress_controller", + [ + pytest.param( + {"extra_args": [f"-default-http-listener-port=8085", f"-default-https-listener-port=8445"]}, + ), + ], + indirect=True, + ) + def test_disable_default_listeners_true(self, ingress_controller_endpoint, ingress_controller): + print("Ensure ports 80 and 443 return result in an ERR_CONNECTION_REFUSED") + request_url_80 = f"http://{ingress_controller_endpoint.public_ip}:{ingress_controller_endpoint.port}/" + with pytest.raises(ConnectionError, match="Connection refused") as e: + requests.get(request_url_80, headers={}) + + request_url_443 = f"https://{ingress_controller_endpoint.public_ip}:{ingress_controller_endpoint.port_ssl}/" + with pytest.raises(ConnectionError, match="Connection refused") as e: + requests.get(request_url_443, headers={}, verify=False) + + @pytest.mark.parametrize( + "ingress_controller", + [ + pytest.param( + {"extra_args": [f"-default-http-listener-port=8085", f"-default-https-listener-port=8445"]}, + ), + ], + indirect=True, + ) + def test_custom_default_listeners( + self, kube_apis, ingress_controller_endpoint, ingress_controller, default_server_setup_custom_port + ): + print("Ensure custom ports for default listeners return 404") + request_url_http = f"http://{ingress_controller_endpoint.public_ip}:{ingress_controller_endpoint.custom_http}/" + resp = requests.get(request_url_http, headers={}) + assert resp.status_code == 404 + + request_url_https = ( + f"https://{ingress_controller_endpoint.public_ip}:{ingress_controller_endpoint.custom_https}/" + ) + resp = requests.get(request_url_https, headers={}, verify=False) + assert resp.status_code == 404 diff --git a/tests/suite/test_disable_ipv6.py b/tests/suite/test_disable_ipv6.py index 6aa67e0f09..74aa807b7d 100644 --- a/tests/suite/test_disable_ipv6.py +++ b/tests/suite/test_disable_ipv6.py @@ -21,6 +21,7 @@ @pytest.mark.vs @pytest.mark.ts +@pytest.mark.vs_ipv6 @pytest.mark.parametrize( "crd_ingress_controller, virtual_server_setup, transport_server_setup", [ @@ -110,10 +111,11 @@ def ingress_setup( ic_pod_name = get_first_pod_name(kube_apis.v1, ingress_controller_prerequisites.namespace) def fin(): - print("Clean up the Disable IPV6 Application:") - delete_common_app(kube_apis, "simple", test_namespace) - delete_items_from_yaml(kube_apis, f"{TEST_DATA}/smoke/standard/smoke-ingress.yaml", test_namespace) - delete_secret(kube_apis.v1, secret_name, test_namespace) + if request.config.getoption("--skip-fixture-teardown") == "no": + print("Clean up the Disable IPV6 Application:") + delete_common_app(kube_apis, "simple", test_namespace) + delete_items_from_yaml(kube_apis, f"{TEST_DATA}/smoke/standard/smoke-ingress.yaml", test_namespace) + delete_secret(kube_apis.v1, secret_name, test_namespace) request.addfinalizer(fin) diff --git a/tests/suite/test_dos.py b/tests/suite/test_dos.py index a8f2be1609..533e16b466 100644 --- a/tests/suite/test_dos.py +++ b/tests/suite/test_dos.py @@ -32,6 +32,7 @@ ensure_response_from_backend, get_file_contents, get_ingress_nginx_template_conf, + get_nginx_template_conf, get_pods_amount_with_name, get_test_file_name, nginx_reload, @@ -56,12 +57,14 @@ class DosSetup: Encapsulate the example details. Attributes: req_url (str): + protected_name (str) pol_name (str): log_name (str): """ - def __init__(self, req_url, pol_name, log_name): + def __init__(self, req_url, protected_name, pol_name, log_name): self.req_url = req_url + self.protected_name = protected_name self.pol_name = pol_name self.log_name = log_name @@ -103,7 +106,7 @@ def dos_setup( ) print("------------------------- Deploy Secret -----------------------------") - src_sec_yaml = f"{TEST_DATA}/dos/dos-secret.yaml" + src_sec_yaml = f"{TEST_DATA}/dos/tls-secret.yaml" create_items_from_yaml(kube_apis, src_sec_yaml, test_namespace) print("------------------------- Deploy logconf -----------------------------") @@ -125,21 +128,23 @@ def dos_setup( nginx_reload(kube_apis.v1, item.metadata.name, ingress_controller_prerequisites.namespace) def fin(): - print("Clean up:") - delete_dos_policy(kube_apis.custom_objects, pol_name, test_namespace) - delete_dos_logconf(kube_apis.custom_objects, log_name, test_namespace) - delete_dos_protected(kube_apis.custom_objects, protected_name, test_namespace) - delete_common_app(kube_apis, "dos", test_namespace) - delete_items_from_yaml(kube_apis, src_sec_yaml, test_namespace) - write_to_json(f"reload-{get_test_file_name(request.node.fspath)}.json", reload_times) - clean_good_bad_clients() + if request.config.getoption("--skip-fixture-teardown") == "no": + print("Clean up:") + delete_dos_policy(kube_apis.custom_objects, pol_name, test_namespace) + delete_dos_logconf(kube_apis.custom_objects, log_name, test_namespace) + delete_dos_protected(kube_apis.custom_objects, protected_name, test_namespace) + delete_common_app(kube_apis, "dos", test_namespace) + delete_items_from_yaml(kube_apis, src_sec_yaml, test_namespace) + write_to_json(f"reload-{get_test_file_name(request.node.fspath)}.json", reload_times) + clean_good_bad_clients() request.addfinalizer(fin) - return DosSetup(req_url, pol_name, log_name) + return DosSetup(req_url, protected_name, pol_name, log_name) @pytest.mark.dos +@pytest.mark.dos_ingress @pytest.mark.parametrize( "crd_ingress_controller_with_dos", [ @@ -147,7 +152,7 @@ def fin(): "extra_args": [ f"-enable-custom-resources", f"-enable-app-protect-dos", - f"-v=3", + f"-log-level=debug", f"-app-protect-dos-debug", ] } @@ -175,8 +180,13 @@ def test_ap_nginx_config_entries( f"app_protect_dos_policy_file /etc/nginx/dos/policies/{test_namespace}_{dos_setup.pol_name}.json;", f"app_protect_dos_security_log_enable on;", f"app_protect_dos_security_log /etc/nginx/dos/logconfs/{test_namespace}_{dos_setup.log_name}.json syslog:server=syslog-svc.{ingress_controller_prerequisites.namespace}.svc.cluster.local:514;", + f"set $loggable '0';", + f"access_log syslog:server=accesslog-svc.{ingress_controller_prerequisites.namespace}.svc.cluster.local:514 log_dos if=$loggable;", + f'app_protect_dos_access_file "/etc/nginx/dos/allowlist/{test_namespace}_{dos_setup.protected_name}.json";', ] + conf_nginx_directive = ["app_protect_dos_api on;", "location = /dashboard-dos.html"] + create_ingress_with_dos_annotations( kube_apis, src_ing_yaml, @@ -193,11 +203,16 @@ def test_ap_nginx_config_entries( kube_apis.v1, test_namespace, "dos-ingress", pod_name, "nginx-ingress" ) + nginx_config = get_nginx_template_conf(kube_apis.v1, ingress_controller_prerequisites.namespace, pod_name) + delete_items_from_yaml(kube_apis, src_ing_yaml, test_namespace) for _ in conf_directive: assert _ in result_conf + for _ in conf_nginx_directive: + assert _ in nginx_config + def test_dos_sec_logs_on( self, kube_apis, @@ -227,21 +242,68 @@ def test_dos_sec_logs_on( get_ingress_nginx_template_conf(kube_apis.v1, test_namespace, "dos-ingress", pod_name, "nginx-ingress") print("----------------------- Send request ----------------------") + wait_before_test(5) response = requests.get(dos_setup.req_url, headers={"host": "dos.example.com"}, verify=False) print(response.text) - wait_before_test(10) print(f"log_loc {log_loc} syslog_pod {syslog_pod} namespace {ingress_controller_prerequisites.namespace}") - log_contents = get_file_contents(kube_apis.v1, log_loc, syslog_pod, ingress_controller_prerequisites.namespace) - delete_items_from_yaml(kube_apis, src_ing_yaml, test_namespace) + log_contents = "" + retry = 0 + while 'product="app-protect-dos"' not in log_contents and retry < 20: + wait_before_test(1) + log_contents = get_file_contents( + kube_apis.v1, log_loc, syslog_pod, ingress_controller_prerequisites.namespace, print_log=False + ) + retry += 1 print(log_contents) - assert 'product="app-protect-dos"' in log_contents + delete_items_from_yaml(kube_apis, src_ing_yaml, test_namespace) + assert f'vs_name="{test_namespace}/dos-protected/name"' in log_contents assert "bad_actor" in log_contents + def test_dos_allowlist( + self, kube_apis, ingress_controller_prerequisites, crd_ingress_controller_with_dos, dos_setup, test_namespace + ): + """ + Test App Protect Dos: Block bad clients attack with learning + """ + log_loc = f"/var/log/messages" + print("----------------------- Get accesslog pod name ----------------------") + accesslog_pod = self.getPodNameThatContains(kube_apis, ingress_controller_prerequisites.namespace, "accesslog") + assert "accesslog" in accesslog_pod + clear_file_contents(kube_apis.v1, log_loc, accesslog_pod, ingress_controller_prerequisites.namespace) + print(f"log_loc {log_loc} syslog_pod {accesslog_pod} namespace {ingress_controller_prerequisites.namespace}") + + print("------------------------- Deploy ingress -----------------------------") + create_ingress_with_dos_annotations(kube_apis, src_ing_yaml, test_namespace, test_namespace + "/dos-protected") + get_first_ingress_host_from_yaml(src_ing_yaml) + + print("----------------------- Send request to check allowlist ----------------------") + wait_before_test(5) + response = requests.get( + dos_setup.req_url, headers={"host": "dos.example.com", "X-Forwarded-For": "10.10.10.10"}, verify=False + ) + print(response.text) + + retry = 0 + log_contents = "" + while 'reason=AllowList"' not in log_contents and retry < 20: + wait_before_test(1) + log_contents = get_file_contents( + kube_apis.v1, log_loc, accesslog_pod, ingress_controller_prerequisites.namespace, print_log=False + ) + retry += 1 + + delete_items_from_yaml(kube_apis, src_ing_yaml, test_namespace) + + print(log_contents) + + assert "reason=Allowlist" in log_contents + + @pytest.mark.dos_learning def test_dos_under_attack_with_learning( self, kube_apis, ingress_controller_prerequisites, crd_ingress_controller_with_dos, dos_setup, test_namespace ): diff --git a/tests/suite/test_egress_mtls.py b/tests/suite/test_egress_mtls.py new file mode 100644 index 0000000000..f5ea880883 --- /dev/null +++ b/tests/suite/test_egress_mtls.py @@ -0,0 +1,169 @@ +import pytest +from settings import TEST_DATA +from suite.utils.policy_resources_utils import create_policy_from_yaml, delete_policy +from suite.utils.resources_utils import create_secret_from_yaml, delete_secret, wait_before_test +from suite.utils.ssl_utils import create_sni_session +from suite.utils.vs_vsr_resources_utils import delete_and_create_vs_from_yaml, patch_virtual_server_from_yaml, read_vs + +std_vs_src = f"{TEST_DATA}/virtual-server/standard/virtual-server.yaml" +std_vsr_src = f"{TEST_DATA}/virtual-server-route/route-multiple.yaml" +std_vs_vsr_src = f"{TEST_DATA}/virtual-server-route/standard/virtual-server.yaml" + +mtls_sec_valid_src = f"{TEST_DATA}/egress-mtls/secret/egress-mtls-secret.yaml" +mtls_sec_valid_crl_src = f"{TEST_DATA}/egress-mtls/secret/egress-mtls-secret-crl.yaml" +tls_sec_valid_src = f"{TEST_DATA}/egress-mtls/secret/tls-secret.yaml" + +mtls_pol_valid_src = f"{TEST_DATA}/egress-mtls/policies/egress-mtls.yaml" +mtls_pol_invalid_src = f"{TEST_DATA}/egress-mtls/policies/egress-mtls-invalid.yaml" + +mtls_vs_spec_src = f"{TEST_DATA}/egress-mtls/spec/virtual-server-mtls.yaml" +mtls_vs_route_src = f"{TEST_DATA}/egress-mtls/route-subroute/virtual-server-mtls.yaml" +mtls_vsr_subroute_src = f"{TEST_DATA}/egress-mtls/route-subroute/virtual-server-route-mtls.yaml" +mtls_vs_vsr_src = f"{TEST_DATA}/egress-mtls/route-subroute/virtual-server-vsr.yaml" + + +def setup_policy(kube_apis, test_namespace, mtls_secret, tls_secret, policy): + print(f"Create egress-mtls secret") + mtls_secret_name = create_secret_from_yaml(kube_apis.v1, test_namespace, mtls_secret) + + print(f"Create tls secret") + tls_secret_name = create_secret_from_yaml(kube_apis.v1, test_namespace, tls_secret) + + print(f"Create egress-mtls policy") + pol_name = create_policy_from_yaml(kube_apis.custom_objects, policy, test_namespace) + + return mtls_secret_name, tls_secret_name, pol_name + + +def teardown_policy(kube_apis, test_namespace, tls_secret, pol_name, mtls_secret): + print("Delete policy and related secrets") + delete_secret(kube_apis.v1, tls_secret, test_namespace) + delete_policy(kube_apis.custom_objects, pol_name, test_namespace) + delete_secret(kube_apis.v1, mtls_secret, test_namespace) + + +@pytest.mark.policies +@pytest.mark.policies_mtls +@pytest.mark.parametrize( + "crd_ingress_controller, virtual_server_setup", + [ + ( + { + "type": "complete", + "extra_args": [ + f"-enable-leader-election=false", + ], + }, + { + "example": "virtual-server", + "app_type": "secure-ca", + }, + ) + ], + indirect=True, +) +class TestEgressMtlsPolicyVS: + @pytest.mark.parametrize( + "policy_src, vs_src, mtls_ca_secret, expected_code, expected_text, vs_message, vs_state, test_description", + [ + ( + mtls_pol_valid_src, + mtls_vs_spec_src, + mtls_sec_valid_src, + 200, + "hello from pod secure-app", + "was added or updated", + "Valid", + "Test valid EgressMTLS policy applied to a VirtualServer spec", + ), + ( + mtls_pol_valid_src, + mtls_vs_route_src, + mtls_sec_valid_src, + 200, + "hello from pod secure-app", + "was added or updated", + "Valid", + "Test valid EgressMTLS policy applied to a VirtualServer path", + ), + ( + mtls_pol_valid_src, + mtls_vs_spec_src, + mtls_sec_valid_crl_src, + 200, + "hello from pod secure-app", + "was added or updated", + "Valid", + "Test valid EgressMTLS policy applied to a VirtualServer with a CRL", + ), + ( + mtls_pol_invalid_src, + mtls_vs_spec_src, + mtls_sec_valid_src, + 500, + "Internal Server Error", + "is missing or invalid", + "Warning", + "Test invalid EgressMTLS policy applied to a VirtualServer", + ), + ], + ) + def test_egress_mtls_policy( + self, + kube_apis, + crd_ingress_controller, + virtual_server_setup, + test_namespace, + policy_src, + vs_src, + mtls_ca_secret, + expected_code, + expected_text, + vs_message, + vs_state, + test_description, + ): + """ + Test egress-mtls with valid and invalid policy in vs spec and route contexts. + """ + print("------------------------- {} -----------------------------------".format(test_description)) + session = create_sni_session() + mtls_secret, tls_secret, pol_name = setup_policy( + kube_apis, + test_namespace, + mtls_ca_secret, + tls_sec_valid_src, + policy_src, + ) + + print(f"Patch vs with policy: {policy_src}") + delete_and_create_vs_from_yaml( + kube_apis.custom_objects, + virtual_server_setup.vs_name, + vs_src, + virtual_server_setup.namespace, + ) + wait_before_test() + resp = session.get( + virtual_server_setup.backend_1_url, + headers={"host": virtual_server_setup.vs_host}, + allow_redirects=False, + verify=False, + ) + + vs_events = read_vs(kube_apis.custom_objects, test_namespace, virtual_server_setup.vs_name) + teardown_policy(kube_apis, test_namespace, tls_secret, pol_name, mtls_secret) + + patch_virtual_server_from_yaml( + kube_apis.custom_objects, + virtual_server_setup.vs_name, + std_vs_src, + virtual_server_setup.namespace, + ) + + assert ( + resp.status_code == expected_code + and expected_text in resp.text + and vs_message in vs_events["status"]["message"] + and vs_events["status"]["state"] == vs_state + ) diff --git a/tests/suite/test_externalname_service.py b/tests/suite/test_externalname_service.py index a7fde6997f..b96aa5b444 100644 --- a/tests/suite/test_externalname_service.py +++ b/tests/suite/test_externalname_service.py @@ -89,16 +89,17 @@ def external_name_setup( ic_pod_name = get_first_pod_name(kube_apis.v1, ingress_controller_prerequisites.namespace) def fin(): - print("Clean up External-Name-Example:") - delete_namespace(kube_apis.v1, external_ns) - replace_configmap( - kube_apis.v1, - config_map_name, - ingress_controller_prerequisites.namespace, - ingress_controller_prerequisites.config_map, - ) - delete_ingress(kube_apis.networking_v1, ingress_name, test_namespace) - delete_service(kube_apis.v1, svc_name, test_namespace) + if request.config.getoption("--skip-fixture-teardown") == "no": + print("Clean up External-Name-Example:") + delete_namespace(kube_apis.v1, external_ns) + replace_configmap( + kube_apis.v1, + config_map_name, + ingress_controller_prerequisites.namespace, + ingress_controller_prerequisites.config_map, + ) + delete_ingress(kube_apis.networking_v1, ingress_name, test_namespace) + delete_service(kube_apis.v1, svc_name, test_namespace) request.addfinalizer(fin) diff --git a/tests/suite/test_filter_secrets.py b/tests/suite/test_filter_secrets.py index cfc30cdb56..abcb121eb7 100644 --- a/tests/suite/test_filter_secrets.py +++ b/tests/suite/test_filter_secrets.py @@ -20,10 +20,11 @@ def setup_single_secret_and_ns(request, kube_apis): wait_before_test(1) def fin(): - print("Clean up:") - if is_secret_present(kube_apis.v1, filtered_secret_1, filtered_ns_1): - delete_secret(kube_apis.v1, filtered_secret_1, filtered_ns_1) - delete_namespace(kube_apis.v1, filtered_ns_1) + if request.config.getoption("--skip-fixture-teardown") == "no": + print("Clean up:") + if is_secret_present(kube_apis.v1, filtered_secret_1, filtered_ns_1): + delete_secret(kube_apis.v1, filtered_secret_1, filtered_ns_1) + delete_namespace(kube_apis.v1, filtered_ns_1) request.addfinalizer(fin) @@ -31,7 +32,7 @@ def fin(): @pytest.mark.ingresses @pytest.mark.parametrize( "ingress_controller", - [pytest.param({"extra_args": ["-v=3"]})], + [pytest.param({"extra_args": ["-log-level=debug"]})], indirect=["ingress_controller"], ) class TestFilterSecret: @@ -44,7 +45,7 @@ def test_filter_secret_single_namespace(self, request, kube_apis, ingress_contro @pytest.mark.ingresses @pytest.mark.parametrize( "ingress_controller", - [pytest.param({"extra_args": ["-v=3"]})], + [pytest.param({"extra_args": ["-log-level=debug"]})], indirect=["ingress_controller"], ) class TestFilterAfterIcCreated: @@ -81,15 +82,16 @@ def setup_multiple_ns_and_multiple_secrets(request, kube_apis): wait_before_test(1) def fin(): - print("Clean up:") - if is_secret_present(kube_apis.v1, filtered_secret_1, filtered_ns_1): - delete_secret(kube_apis.v1, filtered_secret_1, filtered_ns_1) - if is_secret_present(kube_apis.v1, filtered_secret_2, filtered_ns_2): - delete_secret(kube_apis.v1, filtered_secret_2, filtered_ns_2) - if is_secret_present(kube_apis.v1, nginx_ingress_secret, "nginx-ingress"): - delete_secret(kube_apis.v1, nginx_ingress_secret, "nginx-ingress") - delete_namespace(kube_apis.v1, filtered_ns_1) - delete_namespace(kube_apis.v1, filtered_ns_2) + if request.config.getoption("--skip-fixture-teardown") == "no": + print("Clean up:") + if is_secret_present(kube_apis.v1, filtered_secret_1, filtered_ns_1): + delete_secret(kube_apis.v1, filtered_secret_1, filtered_ns_1) + if is_secret_present(kube_apis.v1, filtered_secret_2, filtered_ns_2): + delete_secret(kube_apis.v1, filtered_secret_2, filtered_ns_2) + if is_secret_present(kube_apis.v1, nginx_ingress_secret, "nginx-ingress"): + delete_secret(kube_apis.v1, nginx_ingress_secret, "nginx-ingress") + delete_namespace(kube_apis.v1, filtered_ns_1) + delete_namespace(kube_apis.v1, filtered_ns_2) request.addfinalizer(fin) @@ -97,7 +99,7 @@ def fin(): @pytest.mark.ingresses @pytest.mark.parametrize( "ingress_controller", - [pytest.param({"extra_args": ["-v=3", "-watch-namespace=filtered-ns-1,filtered-ns-2"]})], + [pytest.param({"extra_args": ["-log-level=debug", "-watch-namespace=filtered-ns-1,filtered-ns-2"]})], indirect=["ingress_controller"], ) class TestFilterSecretMultipuleNamespace: @@ -112,7 +114,7 @@ def test_filter_secret_multi_namespace( @pytest.mark.ingresses @pytest.mark.parametrize( "ingress_controller", - [pytest.param({"extra_args": ["-v=3", "-watch-namespace=filtered-ns-1,filtered-ns-2"]})], + [pytest.param({"extra_args": ["-log-level=debug", "-watch-namespace=filtered-ns-1,filtered-ns-2"]})], indirect=["ingress_controller"], ) class TestFilterSecretMultipleNamespaceAfterIcCreated: diff --git a/tests/suite/test_hsts.py b/tests/suite/test_hsts.py index a0303b6827..31bc69c73f 100644 --- a/tests/suite/test_hsts.py +++ b/tests/suite/test_hsts.py @@ -61,9 +61,10 @@ def hsts_setup( ensure_response_from_backend(req_https_url, ingress_host) def fin(): - print("Clean up HSTS Example:") - delete_common_app(kube_apis, "simple", test_namespace) - delete_items_from_yaml(kube_apis, f"{TEST_DATA}/hsts/{request.param}/hsts-ingress.yaml", test_namespace) + if request.config.getoption("--skip-fixture-teardown") == "no": + print("Clean up HSTS Example:") + delete_common_app(kube_apis, "simple", test_namespace) + delete_items_from_yaml(kube_apis, f"{TEST_DATA}/hsts/{request.param}/hsts-ingress.yaml", test_namespace) request.addfinalizer(fin) @@ -77,6 +78,7 @@ def fin(): @pytest.mark.ingresses +@pytest.mark.hsts @pytest.mark.parametrize("hsts_setup", ["standard-tls", "mergeable-tls"], indirect=True) class TestTLSHSTSFlows: def test_headers(self, kube_apis, hsts_setup, ingress_controller_prerequisites): @@ -114,6 +116,7 @@ def test_headers(self, kube_apis, hsts_setup, ingress_controller_prerequisites): @pytest.mark.ingresses +@pytest.mark.hsts @pytest.mark.parametrize("hsts_setup", ["tls-no-secret"], indirect=True) class TestBrokenTLSHSTSFlows: def test_headers_without_secret(self, kube_apis, hsts_setup, ingress_controller_prerequisites): @@ -128,6 +131,7 @@ def test_headers_without_secret(self, kube_apis, hsts_setup, ingress_controller_ @pytest.mark.ingresses +@pytest.mark.hsts @pytest.mark.parametrize("hsts_setup", ["standard", "mergeable"], indirect=True) class TestNoTLSHSTS: def test_headers(self, kube_apis, hsts_setup, ingress_controller_prerequisites): diff --git a/tests/suite/test_ingress_class.py b/tests/suite/test_ingress_class.py index eda5d4786c..ff2c974eff 100644 --- a/tests/suite/test_ingress_class.py +++ b/tests/suite/test_ingress_class.py @@ -60,11 +60,12 @@ def backend_setup(request, kube_apis, ingress_controller_endpoint, test_namespac wait_before_test(2) def fin(): - print("Clean up:") - delete_common_app(kube_apis, "simple", test_namespace) - for item in ingresses_under_test: - src_ing_yaml = f"{TEST_DATA}/ingress-class/{item}-ingress.yaml" - delete_items_from_yaml(kube_apis, src_ing_yaml, test_namespace) + if request.config.getoption("--skip-fixture-teardown") == "no": + print("Clean up:") + delete_common_app(kube_apis, "simple", test_namespace) + for item in ingresses_under_test: + src_ing_yaml = f"{TEST_DATA}/ingress-class/{item}-ingress.yaml" + delete_items_from_yaml(kube_apis, src_ing_yaml, test_namespace) request.addfinalizer(fin) diff --git a/tests/suite/test_ingress_mtls.py b/tests/suite/test_ingress_mtls.py index 6ebe7922c4..185814ad8e 100644 --- a/tests/suite/test_ingress_mtls.py +++ b/tests/suite/test_ingress_mtls.py @@ -7,6 +7,7 @@ from suite.utils.resources_utils import create_secret_from_yaml, delete_secret, wait_before_test from suite.utils.ssl_utils import create_sni_session from suite.utils.vs_vsr_resources_utils import ( + delete_and_create_vs_from_yaml, patch_v_s_route_from_yaml, patch_virtual_server_from_yaml, read_vs, @@ -33,6 +34,14 @@ invalid_crt = f"{TEST_DATA}/ingress-mtls/client-auth/invalid/client-cert.pem" invalid_key = f"{TEST_DATA}/ingress-mtls/client-auth/invalid/client-cert.pem" +mtls_secret_crl = f"{TEST_DATA}/ingress-mtls/secret/ingress-mtls-secret-crl.yaml" +mtls_pol_crl = f"{TEST_DATA}/ingress-mtls/policies/ingress-mtls-crl.yaml" + +crt_not_revoked = f"{TEST_DATA}/ingress-mtls/client-auth/not-revoked/client-cert.pem" +key_not_revoked = f"{TEST_DATA}/ingress-mtls/client-auth/not-revoked/client-key.pem" +crt_revoked = f"{TEST_DATA}/ingress-mtls/client-auth/revoked/client-cert.pem" +key_revoked = f"{TEST_DATA}/ingress-mtls/client-auth/revoked/client-key.pem" + def setup_policy(kube_apis, test_namespace, mtls_secret, tls_secret, policy): print(f"Create ingress-mtls secret") @@ -47,7 +56,6 @@ def setup_policy(kube_apis, test_namespace, mtls_secret, tls_secret, policy): def teardown_policy(kube_apis, test_namespace, tls_secret, pol_name, mtls_secret): - print("Delete policy and related secrets") delete_secret(kube_apis.v1, tls_secret, test_namespace) delete_policy(kube_apis.custom_objects, pol_name, test_namespace) @@ -55,6 +63,7 @@ def teardown_policy(kube_apis, test_namespace, tls_secret, pol_name, mtls_secret @pytest.mark.policies +@pytest.mark.policies_mtls @pytest.mark.parametrize( "crd_ingress_controller, virtual_server_setup", [ @@ -137,13 +146,21 @@ def test_ingress_mtls_policy( virtual_server_setup.namespace, ) wait_before_test() - resp = session.get( - virtual_server_setup.backend_1_url_ssl, - cert=(crt, key), - headers={"host": virtual_server_setup.vs_host}, - allow_redirects=False, - verify=False, - ) + resp = mock.Mock() + resp.status_code == 502 + counter = 0 + + while resp.status_code != expected_code and counter < 10: + resp = session.get( + virtual_server_setup.backend_1_url_ssl, + cert=(crt, key), + headers={"host": virtual_server_setup.vs_host}, + allow_redirects=False, + verify=False, + ) + wait_before_test() + counter += 1 + vs_res = read_vs(kube_apis.custom_objects, test_namespace, virtual_server_setup.vs_name) teardown_policy(kube_apis, test_namespace, tls_secret, pol_name, mtls_secret) @@ -200,21 +217,194 @@ def test_ingress_mtls_policy_cert( ) wait_before_test() ssl_exception = "" - resp = "" - try: + resp = mock.Mock() + resp.status_code == 502 + counter = 0 + + while resp.status_code != expected_code and counter < 10: + try: + resp = session.get( + virtual_server_setup.backend_1_url_ssl, + cert=certificate, + headers={"host": virtual_server_setup.vs_host}, + allow_redirects=False, + verify=False, + ) + wait_before_test() + counter += 1 + + except requests.exceptions.SSLError as e: + print(f"SSL certificate exception: {e}") + ssl_exception = str(e) + resp = mock.Mock() + resp.status_code = "None" + resp.text = "None" + + teardown_policy(kube_apis, test_namespace, tls_secret, pol_name, mtls_secret) + + patch_virtual_server_from_yaml( + kube_apis.custom_objects, + virtual_server_setup.vs_name, + std_vs_src, + virtual_server_setup.namespace, + ) + assert resp.status_code == expected_code and expected_text in resp.text and exception in ssl_exception + + @pytest.mark.smoke + @pytest.mark.parametrize( + "policy_src, vs_src, mtls_secret_in, expected_code, expected_text, vs_message, vs_state", + [ + ( + mtls_pol_valid_src, + mtls_vs_spec_src, + mtls_secret_crl, + 200, + "Server address:", + "added or updated", + "Valid", + ), + ( + mtls_pol_crl, + mtls_vs_spec_src, + mtls_sec_valid_src, + 404, + "Not Found", + "added or updated", + "Invalid", + ), + ( + mtls_pol_crl, + mtls_vs_spec_src, + mtls_secret_crl, + 404, + "Not Found", + "added or updated ; with warning(s)", + "Invalid", + ), + ], + ) + def test_ingress_mtls_policy_crl( + self, + kube_apis, + crd_ingress_controller, + virtual_server_setup, + test_namespace, + policy_src, + mtls_secret_in, + vs_src, + expected_code, + expected_text, + vs_message, + vs_state, + ): + session = create_sni_session() + mtls_secret, tls_secret, pol_name = setup_policy( + kube_apis, + test_namespace, + mtls_secret_in, + tls_sec_valid_src, + policy_src, + ) + + delete_and_create_vs_from_yaml( + kube_apis.custom_objects, + virtual_server_setup.vs_name, + vs_src, + virtual_server_setup.namespace, + ) + wait_before_test() + resp = mock.Mock() + resp.status_code == 502 + counter = 0 + + while resp.status_code != expected_code and counter < 10: resp = session.get( virtual_server_setup.backend_1_url_ssl, - cert=certificate, + cert=(crt_not_revoked, key_not_revoked), headers={"host": virtual_server_setup.vs_host}, allow_redirects=False, verify=False, ) - except requests.exceptions.SSLError as e: - print(f"SSL certificate exception: {e}") - ssl_exception = str(e) - resp = mock.Mock() - resp.status_code = "None" - resp.text = "None" + wait_before_test() + counter += 1 + + vs_res = read_vs(kube_apis.custom_objects, test_namespace, virtual_server_setup.vs_name) + teardown_policy(kube_apis, test_namespace, tls_secret, pol_name, mtls_secret) + + delete_and_create_vs_from_yaml( + kube_apis.custom_objects, + virtual_server_setup.vs_name, + std_vs_src, + virtual_server_setup.namespace, + ) + assert ( + resp.status_code == expected_code + and expected_text in resp.text + and vs_message in vs_res["status"]["message"] + and vs_res["status"]["state"] == vs_state + ) + + @pytest.mark.parametrize( + "certificate, expected_code, expected_text, exception", + [ + ((crt_not_revoked, key_not_revoked), 200, "Server address:", ""), + ("", 400, "No required SSL certificate was sent", ""), + ((crt_revoked, key_revoked), 400, "The SSL certificate error", ""), + ], + ) + def test_ingress_mtls_policy_cert_crl( + self, + kube_apis, + crd_ingress_controller, + virtual_server_setup, + test_namespace, + certificate, + expected_code, + expected_text, + exception, + ): + """ + Test ingress-mtls with valid and invalid policy + """ + session = create_sni_session() + mtls_secret, tls_secret, pol_name = setup_policy( + kube_apis, + test_namespace, + mtls_secret_crl, + tls_sec_valid_src, + mtls_pol_valid_src, + ) + + print(f"Patch vs with policy: {mtls_pol_valid_src}") + patch_virtual_server_from_yaml( + kube_apis.custom_objects, + virtual_server_setup.vs_name, + mtls_vs_spec_src, + virtual_server_setup.namespace, + ) + wait_before_test() + ssl_exception = "" + resp = mock.Mock() + resp.status_code == 502 + counter = 0 + + while resp.status_code != expected_code and counter < 10: + try: + resp = session.get( + virtual_server_setup.backend_1_url_ssl, + cert=certificate, + headers={"host": virtual_server_setup.vs_host}, + allow_redirects=False, + verify=False, + ) + wait_before_test() + counter += 1 + except requests.exceptions.SSLError as e: + print(f"SSL certificate exception: {e}") + ssl_exception = str(e) + resp = mock.Mock() + resp.status_code = "None" + resp.text = "None" teardown_policy(kube_apis, test_namespace, tls_secret, pol_name, mtls_secret) @@ -228,6 +418,7 @@ def test_ingress_mtls_policy_cert( @pytest.mark.policies +@pytest.mark.policies_mtls @pytest.mark.parametrize( "crd_ingress_controller, v_s_route_setup", [ diff --git a/tests/suite/test_jwt_auth_mergeable.py b/tests/suite/test_jwt_auth_mergeable.py index 3ec97d9487..b576e0e548 100644 --- a/tests/suite/test_jwt_auth_mergeable.py +++ b/tests/suite/test_jwt_auth_mergeable.py @@ -60,19 +60,20 @@ def jwt_auth_setup( wait_before_test(2) def fin(): - print("Delete Master Secret:") - if is_secret_present(kube_apis.v1, master_secret_name, test_namespace): - delete_secret(kube_apis.v1, master_secret_name, test_namespace) - - print("Delete Minion Secret:") - if is_secret_present(kube_apis.v1, minion_secret_name, test_namespace): - delete_secret(kube_apis.v1, minion_secret_name, test_namespace) - - print("Clean up the JWT Auth Mergeable Minions Application:") - delete_common_app(kube_apis, "simple", test_namespace) - delete_items_from_yaml( - kube_apis, f"{TEST_DATA}/jwt-auth-mergeable/mergeable/jwt-auth-ingress.yaml", test_namespace - ) + if request.config.getoption("--skip-fixture-teardown") == "no": + print("Delete Master Secret:") + if is_secret_present(kube_apis.v1, master_secret_name, test_namespace): + delete_secret(kube_apis.v1, master_secret_name, test_namespace) + + print("Delete Minion Secret:") + if is_secret_present(kube_apis.v1, minion_secret_name, test_namespace): + delete_secret(kube_apis.v1, minion_secret_name, test_namespace) + + print("Clean up the JWT Auth Mergeable Minions Application:") + delete_common_app(kube_apis, "simple", test_namespace) + delete_items_from_yaml( + kube_apis, f"{TEST_DATA}/jwt-auth-mergeable/mergeable/jwt-auth-ingress.yaml", test_namespace + ) request.addfinalizer(fin) diff --git a/tests/suite/test_jwt_policies.py b/tests/suite/test_jwt_policies.py index bc6b2b11c4..cee9eea96e 100644 --- a/tests/suite/test_jwt_policies.py +++ b/tests/suite/test_jwt_policies.py @@ -27,6 +27,7 @@ @pytest.mark.skip_for_nginx_oss @pytest.mark.policies +@pytest.mark.policies_jwt @pytest.mark.parametrize( "crd_ingress_controller, virtual_server_setup", [ diff --git a/tests/suite/test_jwt_policies_jwksuri.py b/tests/suite/test_jwt_policies_jwksuri.py new file mode 100644 index 0000000000..ffd1e3bb68 --- /dev/null +++ b/tests/suite/test_jwt_policies_jwksuri.py @@ -0,0 +1,358 @@ +from unittest import mock + +import pytest +import requests +from settings import TEST_DATA +from suite.utils.policy_resources_utils import create_policy_from_yaml, delete_policy +from suite.utils.resources_utils import replace_configmap_from_yaml, wait_before_test +from suite.utils.vs_vsr_resources_utils import ( + create_virtual_server_from_yaml, + delete_and_create_vs_from_yaml, + delete_virtual_server, +) + +std_vs_src = f"{TEST_DATA}/virtual-server/standard/virtual-server.yaml" +jwt_pol_valid_src = f"{TEST_DATA}/jwt-policy-jwksuri/policies/jwt-policy-valid.yaml" +jwt_pol_invalid_src = f"{TEST_DATA}/jwt-policy-jwksuri/policies/jwt-policy-invalid.yaml" +jwt_vs_spec_src = f"{TEST_DATA}/jwt-policy-jwksuri/virtual-server/virtual-server-policy-spec.yaml" +jwt_vs_route_src = f"{TEST_DATA}/jwt-policy-jwksuri/virtual-server/virtual-server-policy-route.yaml" +jwt_spec_and_route_src = f"{TEST_DATA}/jwt-policy-jwksuri/virtual-server/virtual-server-policy-spec-and-route.yaml" +jwt_vs_route_subpath_src = f"{TEST_DATA}/jwt-policy-jwksuri/virtual-server/virtual-server-policy-route-subpath.yaml" +jwt_vs_route_subpath_diff_host_src = ( + f"{TEST_DATA}/jwt-policy-jwksuri/virtual-server/virtual-server-policy-route-subpath-diff-host.yaml" +) +jwt_vs_invalid_pol_spec_src = f"{TEST_DATA}/jwt-policy-jwksuri/virtual-server/virtual-server-invalid-policy-spec.yaml" +jwt_vs_invalid_pol_route_src = f"{TEST_DATA}/jwt-policy-jwksuri/virtual-server/virtual-server-invalid-policy-route.yaml" +jwt_vs_invalid_pol_route_subpath_src = ( + f"{TEST_DATA}/jwt-policy-jwksuri/virtual-server/virtual-server-invalid-policy-route-subpath.yaml" +) +jwt_cm_src = f"{TEST_DATA}/jwt-policy-jwksuri/configmap/nginx-config.yaml" +ad_tenant = "dd3dfd2f-6a3b-40d1-9be0-bf8327d81c50" +client_id = "8a172a83-a630-41a4-9ca6-1e5ef03cd7e7" + + +def get_token(request): + """ + get jwt token from azure ad endpoint + """ + data = { + "client_id": f"{client_id}", + "scope": ".default", + "client_secret": request.config.getoption("--ad-secret"), + "grant_type": "client_credentials", + } + ad_response = requests.get( + f"https://login.microsoftonline.com/{ad_tenant}/oauth2/token", + data=data, + timeout=5, + headers={"User-Agent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:47.0) Gecko/20100101 Chrome/76.0.3809.100"}, + ) + + if ad_response.status_code == 200: + return ad_response.json()["access_token"] + pytest.fail("Unable to request Azure token endpoint") + + +@pytest.mark.skip_for_nginx_oss +@pytest.mark.skip(reason="issues with IdP communication") +@pytest.mark.policies +@pytest.mark.parametrize( + "crd_ingress_controller, virtual_server_setup", + [ + ( + { + "type": "complete", + "extra_args": [ + f"-enable-custom-resources", + f"-enable-leader-election=false", + ], + }, + { + "example": "virtual-server", + "app_type": "simple", + }, + ) + ], + indirect=True, +) +class TestJWTPoliciesVsJwksuri: + @pytest.mark.parametrize("jwt_virtual_server", [jwt_vs_spec_src, jwt_vs_route_src, jwt_spec_and_route_src]) + def test_jwt_policy_jwksuri( + self, + request, + kube_apis, + ingress_controller_prerequisites, + crd_ingress_controller, + virtual_server_setup, + test_namespace, + jwt_virtual_server, + ): + """ + Test jwt-policy in Virtual Server (spec, route and both at the same time) with keys fetched form Azure + """ + replace_configmap_from_yaml( + kube_apis.v1, + ingress_controller_prerequisites.config_map["metadata"]["name"], + ingress_controller_prerequisites.namespace, + jwt_cm_src, + ) + pol_name = create_policy_from_yaml(kube_apis.custom_objects, jwt_pol_valid_src, test_namespace) + wait_before_test() + + print(f"Patch vs with policy: {jwt_virtual_server}") + delete_and_create_vs_from_yaml( + kube_apis.custom_objects, + virtual_server_setup.vs_name, + jwt_virtual_server, + virtual_server_setup.namespace, + ) + resp_no_token = mock.Mock() + resp_no_token.status_code == 502 + counter = 0 + + while resp_no_token.status_code != 401 and counter < 20: + resp_no_token = requests.get( + virtual_server_setup.backend_1_url, + headers={"host": virtual_server_setup.vs_host}, + ) + wait_before_test() + counter += 1 + + token = get_token(request) + + resp_valid_token = requests.get( + virtual_server_setup.backend_1_url, + headers={"host": virtual_server_setup.vs_host, "token": token}, + timeout=5, + ) + + delete_policy(kube_apis.custom_objects, pol_name, test_namespace) + wait_before_test() + + delete_and_create_vs_from_yaml( + kube_apis.custom_objects, + virtual_server_setup.vs_name, + std_vs_src, + virtual_server_setup.namespace, + ) + + assert resp_no_token.status_code == 401 and f"Authorization Required" in resp_no_token.text + assert resp_valid_token.status_code == 200 and f"Request ID:" in resp_valid_token.text + + @pytest.mark.parametrize("jwt_virtual_server", [jwt_vs_invalid_pol_spec_src, jwt_vs_invalid_pol_route_src]) + def test_jwt_invalid_policy_jwksuri( + self, + request, + kube_apis, + ingress_controller_prerequisites, + crd_ingress_controller, + virtual_server_setup, + test_namespace, + jwt_virtual_server, + ): + """ + Test invalid jwt-policy in Virtual Server (spec and route) with keys fetched form Azure + """ + replace_configmap_from_yaml( + kube_apis.v1, + ingress_controller_prerequisites.config_map["metadata"]["name"], + ingress_controller_prerequisites.namespace, + jwt_cm_src, + ) + pol_name = create_policy_from_yaml(kube_apis.custom_objects, jwt_pol_invalid_src, test_namespace) + wait_before_test() + + print(f"Patch vs with policy: {jwt_virtual_server}") + delete_and_create_vs_from_yaml( + kube_apis.custom_objects, + virtual_server_setup.vs_name, + jwt_virtual_server, + virtual_server_setup.namespace, + ) + wait_before_test() + + resp1 = requests.get( + virtual_server_setup.backend_1_url, + headers={"host": virtual_server_setup.vs_host}, + ) + + token = get_token(request) + + resp2 = requests.get( + virtual_server_setup.backend_1_url, + headers={"host": virtual_server_setup.vs_host, "token": token}, + timeout=5, + ) + + delete_policy(kube_apis.custom_objects, pol_name, test_namespace) + delete_and_create_vs_from_yaml( + kube_apis.custom_objects, + virtual_server_setup.vs_name, + std_vs_src, + virtual_server_setup.namespace, + ) + + assert resp1.status_code == 500 and f"Internal Server Error" in resp1.text + assert resp2.status_code == 500 and f"Internal Server Error" in resp2.text + + @pytest.mark.parametrize("jwt_virtual_server", [jwt_vs_route_subpath_src]) + def test_jwt_policy_subroute_jwksuri( + self, + request, + kube_apis, + ingress_controller_prerequisites, + crd_ingress_controller, + virtual_server_setup, + test_namespace, + jwt_virtual_server, + ): + """ + Test jwt-policy in Virtual Server using subpaths with keys fetched form Azure + """ + replace_configmap_from_yaml( + kube_apis.v1, + ingress_controller_prerequisites.config_map["metadata"]["name"], + ingress_controller_prerequisites.namespace, + jwt_cm_src, + ) + pol_name = create_policy_from_yaml(kube_apis.custom_objects, jwt_pol_valid_src, test_namespace) + wait_before_test() + + print(f"Patch vs with policy: {jwt_virtual_server}") + delete_and_create_vs_from_yaml( + kube_apis.custom_objects, + virtual_server_setup.vs_name, + jwt_virtual_server, + virtual_server_setup.namespace, + ) + resp_no_token = mock.Mock() + resp_no_token.status_code == 502 + counter = 0 + + while resp_no_token.status_code != 401 and counter < 20: + resp_no_token = requests.get( + virtual_server_setup.backend_1_url + "/subpath1", + headers={"host": virtual_server_setup.vs_host}, + ) + wait_before_test() + counter += 1 + + token = get_token(request) + + resp_valid_token = requests.get( + virtual_server_setup.backend_1_url + "/subpath1", + headers={"host": virtual_server_setup.vs_host, "token": token}, + timeout=5, + ) + + delete_policy(kube_apis.custom_objects, pol_name, test_namespace) + wait_before_test() + + delete_and_create_vs_from_yaml( + kube_apis.custom_objects, + virtual_server_setup.vs_name, + std_vs_src, + virtual_server_setup.namespace, + ) + + assert resp_no_token.status_code == 401 and f"Authorization Required" in resp_no_token.text + assert resp_valid_token.status_code == 200 and f"Request ID:" in resp_valid_token.text + + def test_jwt_policy_subroute_jwksuri_multiple_vs( + self, + request, + kube_apis, + ingress_controller_prerequisites, + crd_ingress_controller, + virtual_server_setup, + test_namespace, + ): + """ + Test jwt-policy applied to two Virtual Servers with different hosts and the same subpaths + """ + replace_configmap_from_yaml( + kube_apis.v1, + ingress_controller_prerequisites.config_map["metadata"]["name"], + ingress_controller_prerequisites.namespace, + jwt_cm_src, + ) + pol_name = create_policy_from_yaml(kube_apis.custom_objects, jwt_pol_valid_src, test_namespace) + wait_before_test() + + print(f"Patch first vs with policy: {jwt_vs_route_subpath_src}") + delete_and_create_vs_from_yaml( + kube_apis.custom_objects, + virtual_server_setup.vs_name, + jwt_vs_route_subpath_src, + virtual_server_setup.namespace, + ) + + print(f"Create second vs with policy: {jwt_vs_route_subpath_diff_host_src}") + create_virtual_server_from_yaml( + kube_apis.custom_objects, + jwt_vs_route_subpath_diff_host_src, + virtual_server_setup.namespace, + ) + + wait_before_test() + + resp_1_no_token = mock.Mock() + resp_1_no_token.status_code == 502 + + resp_2_no_token = mock.Mock() + resp_2_no_token.status_code == 502 + counter = 0 + + while resp_1_no_token.status_code != 401 and counter < 20: + resp_1_no_token = requests.get( + virtual_server_setup.backend_1_url + "/subpath1", + headers={"host": virtual_server_setup.vs_host}, + ) + wait_before_test() + counter += 1 + + counter = 0 + + while resp_2_no_token.status_code != 401 and counter < 20: + resp_2_no_token = requests.get( + virtual_server_setup.backend_1_url + "/subpath1", + headers={"host": "virtual-server-2.example.com"}, + ) + wait_before_test() + counter += 1 + + token = get_token(request) + + resp_1_valid_token = requests.get( + virtual_server_setup.backend_1_url + "/subpath1", + headers={"host": virtual_server_setup.vs_host, "token": token}, + timeout=5, + ) + + resp_2_valid_token = requests.get( + virtual_server_setup.backend_1_url + "/subpath1", + headers={"host": "virtual-server-2.example.com", "token": token}, + timeout=5, + ) + + delete_policy(kube_apis.custom_objects, pol_name, test_namespace) + wait_before_test() + + delete_and_create_vs_from_yaml( + kube_apis.custom_objects, + virtual_server_setup.vs_name, + std_vs_src, + virtual_server_setup.namespace, + ) + + delete_virtual_server( + kube_apis.custom_objects, + "virtual-server-2", + virtual_server_setup.namespace, + ) + + assert resp_1_no_token.status_code == 401 and f"Authorization Required" in resp_1_no_token.text + assert resp_1_valid_token.status_code == 200 and f"Request ID:" in resp_1_valid_token.text + + assert resp_2_no_token.status_code == 401 and f"Authorization Required" in resp_2_no_token.text + assert resp_2_valid_token.status_code == 200 and f"Request ID:" in resp_2_valid_token.text diff --git a/tests/suite/test_jwt_policies_jwksuri_vsr.py b/tests/suite/test_jwt_policies_jwksuri_vsr.py new file mode 100644 index 0000000000..df912efdac --- /dev/null +++ b/tests/suite/test_jwt_policies_jwksuri_vsr.py @@ -0,0 +1,195 @@ +from unittest import mock + +import pytest +import requests +from settings import TEST_DATA +from suite.utils.policy_resources_utils import create_policy_from_yaml, delete_policy +from suite.utils.resources_utils import replace_configmap_from_yaml, wait_before_test +from suite.utils.vs_vsr_resources_utils import patch_v_s_route_from_yaml + +std_vsr_src = f"{TEST_DATA}/virtual-server-route/route-multiple.yaml" +jwt_pol_valid_src = f"{TEST_DATA}/jwt-policy-jwksuri/policies/jwt-policy-valid.yaml" +jwt_pol_invalid_src = f"{TEST_DATA}/jwt-policy-jwksuri/policies/jwt-policy-invalid.yaml" +jwt_vsr_subroute_src = f"{TEST_DATA}/jwt-policy-jwksuri/virtual-server-route/virtual-server-route-policy-subroute.yaml" +jwt_vsr_invalid_pol_subroute_src = ( + f"{TEST_DATA}/jwt-policy-jwksuri/virtual-server-route/virtual-server-route-invalid-policy-subroute.yaml" +) +jwt_cm_src = f"{TEST_DATA}/jwt-policy-jwksuri/configmap/nginx-config.yaml" +ad_tenant = "dd3dfd2f-6a3b-40d1-9be0-bf8327d81c50" +client_id = "8a172a83-a630-41a4-9ca6-1e5ef03cd7e7" + + +def get_token(request): + """ + get jwt token from azure ad endpoint + """ + data = { + "client_id": f"{client_id}", + "scope": ".default", + "client_secret": request.config.getoption("--ad-secret"), + "grant_type": "client_credentials", + } + ad_response = requests.get( + f"https://login.microsoftonline.com/{ad_tenant}/oauth2/token", + data=data, + timeout=5, + headers={"User-Agent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:47.0) Gecko/20100101 Chrome/76.0.3809.100"}, + ) + + if ad_response.status_code == 200: + return ad_response.json()["access_token"] + pytest.fail("Unable to request Azure token endpoint") + + +@pytest.mark.skip_for_nginx_oss +@pytest.mark.policies +@pytest.mark.skip(reason="issues with IdP communication") +@pytest.mark.parametrize( + "crd_ingress_controller, v_s_route_setup", + [ + ( + { + "type": "complete", + "extra_args": [ + f"-enable-custom-resources", + f"-enable-leader-election=false", + ], + }, + { + "example": "virtual-server-route", + }, + ) + ], + indirect=True, +) +class TestJWTPoliciesVSRJwksuri: + @pytest.mark.parametrize("jwt_virtual_server_route", [jwt_vsr_subroute_src]) + def test_jwt_policy_jwksuri( + self, + request, + kube_apis, + ingress_controller_prerequisites, + crd_ingress_controller, + v_s_route_app_setup, + v_s_route_setup, + test_namespace, + jwt_virtual_server_route, + ): + """ + Test jwt-policy in Virtual Server Route with keys fetched form Azure + """ + req_url = f"http://{v_s_route_setup.public_endpoint.public_ip}:{v_s_route_setup.public_endpoint.port}" + replace_configmap_from_yaml( + kube_apis.v1, + ingress_controller_prerequisites.config_map["metadata"]["name"], + ingress_controller_prerequisites.namespace, + jwt_cm_src, + ) + pol_name = create_policy_from_yaml( + kube_apis.custom_objects, jwt_pol_valid_src, v_s_route_setup.route_m.namespace + ) + wait_before_test() + + print(f"Patch vsr with policy: {jwt_virtual_server_route}") + patch_v_s_route_from_yaml( + kube_apis.custom_objects, + v_s_route_setup.route_m.name, + jwt_virtual_server_route, + v_s_route_setup.route_m.namespace, + ) + resp_no_token = mock.Mock() + resp_no_token.status_code == 502 + counter = 0 + + while resp_no_token.status_code != 401 and counter < 20: + resp_no_token = requests.get( + f"{req_url}{v_s_route_setup.route_m.paths[0]}", + headers={"host": v_s_route_setup.vs_host}, + ) + wait_before_test() + counter += 1 + + token = get_token(request) + + resp_valid_token = requests.get( + f"{req_url}{v_s_route_setup.route_m.paths[0]}", + headers={"host": v_s_route_setup.vs_host, "token": token}, + ) + + delete_policy(kube_apis.custom_objects, pol_name, v_s_route_setup.route_m.namespace) + wait_before_test() + + resp_pol_deleted = requests.get( + f"{req_url}{v_s_route_setup.route_m.paths[0]}", + headers={"host": v_s_route_setup.vs_host, "token": token}, + ) + + patch_v_s_route_from_yaml( + kube_apis.custom_objects, + v_s_route_setup.route_m.name, + std_vsr_src, + v_s_route_setup.route_m.namespace, + ) + + assert resp_no_token.status_code == 401 and f"Authorization Required" in resp_no_token.text + assert resp_valid_token.status_code == 200 and f"Request ID:" in resp_valid_token.text + assert resp_pol_deleted.status_code == 500 and f"Internal Server Error" in resp_pol_deleted.text + + @pytest.mark.parametrize("jwt_virtual_server_route", [jwt_vsr_invalid_pol_subroute_src]) + def test_jwt_invalid_policy_jwksuri( + self, + request, + kube_apis, + ingress_controller_prerequisites, + crd_ingress_controller, + v_s_route_app_setup, + v_s_route_setup, + test_namespace, + jwt_virtual_server_route, + ): + """ + Test invalid jwt-policy in Virtual Server Route with keys fetched form Azure + """ + req_url = f"http://{v_s_route_setup.public_endpoint.public_ip}:{v_s_route_setup.public_endpoint.port}" + replace_configmap_from_yaml( + kube_apis.v1, + ingress_controller_prerequisites.config_map["metadata"]["name"], + ingress_controller_prerequisites.namespace, + jwt_cm_src, + ) + pol_name = create_policy_from_yaml( + kube_apis.custom_objects, jwt_pol_invalid_src, v_s_route_setup.route_m.namespace + ) + wait_before_test() + + print(f"Patch vsr with policy: {jwt_virtual_server_route}") + patch_v_s_route_from_yaml( + kube_apis.custom_objects, + v_s_route_setup.route_m.name, + jwt_virtual_server_route, + v_s_route_setup.route_m.namespace, + ) + wait_before_test() + + resp1 = requests.get( + f"{req_url}{v_s_route_setup.route_m.paths[0]}", + headers={"host": v_s_route_setup.vs_host}, + ) + + token = get_token(request) + + resp2 = requests.get( + f"{req_url}{v_s_route_setup.route_m.paths[0]}", + headers={"host": v_s_route_setup.vs_host, "token": token}, + ) + + delete_policy(kube_apis.custom_objects, pol_name, v_s_route_setup.route_m.namespace) + patch_v_s_route_from_yaml( + kube_apis.custom_objects, + v_s_route_setup.route_m.name, + std_vsr_src, + v_s_route_setup.route_m.namespace, + ) + + assert resp1.status_code == 500 and f"Internal Server Error" in resp1.text + assert resp2.status_code == 500 and f"Internal Server Error" in resp2.text diff --git a/tests/suite/test_jwt_policies_vsr.py b/tests/suite/test_jwt_policies_vsr.py index 44c03aaf7a..82e847a898 100644 --- a/tests/suite/test_jwt_policies_vsr.py +++ b/tests/suite/test_jwt_policies_vsr.py @@ -27,6 +27,7 @@ @pytest.mark.skip_for_nginx_oss @pytest.mark.policies +@pytest.mark.policies_jwt @pytest.mark.parametrize( "crd_ingress_controller, v_s_route_setup", [ diff --git a/tests/suite/test_jwt_secrets.py b/tests/suite/test_jwt_secrets.py index e3ebd8f6e9..a99e687983 100644 --- a/tests/suite/test_jwt_secrets.py +++ b/tests/suite/test_jwt_secrets.py @@ -64,11 +64,12 @@ def jwt_secrets_setup( ) def fin(): - print("Clean up the JWT Secrets Application:") - delete_common_app(kube_apis, "simple", test_namespace) - delete_items_from_yaml( - kube_apis, f"{TEST_DATA}/jwt-secrets/{request.param}/jwt-secrets-ingress.yaml", test_namespace - ) + if request.config.getoption("--skip-fixture-teardown") == "no": + print("Clean up the JWT Secrets Application:") + delete_common_app(kube_apis, "simple", test_namespace) + delete_items_from_yaml( + kube_apis, f"{TEST_DATA}/jwt-secrets/{request.param}/jwt-secrets-ingress.yaml", test_namespace + ) request.addfinalizer(fin) @@ -81,9 +82,10 @@ def jwt_secret(request, kube_apis, ingress_controller_endpoint, jwt_secrets_setu wait_before_test(1) def fin(): - print("Delete Secret:") - if is_secret_present(kube_apis.v1, secret_name, test_namespace): - delete_secret(kube_apis.v1, secret_name, test_namespace) + if request.config.getoption("--skip-fixture-teardown") == "no": + print("Delete Secret:") + if is_secret_present(kube_apis.v1, secret_name, test_namespace): + delete_secret(kube_apis.v1, secret_name, test_namespace) request.addfinalizer(fin) diff --git a/tests/suite/test_mgmt_configmap_keys.py b/tests/suite/test_mgmt_configmap_keys.py new file mode 100644 index 0000000000..a55e15dd26 --- /dev/null +++ b/tests/suite/test_mgmt_configmap_keys.py @@ -0,0 +1,236 @@ +import re + +import pytest +from settings import TEST_DATA +from suite.utils.resources_utils import ( + create_license, + create_secret_from_yaml, + ensure_connection_to_public_endpoint, + get_events_for_object, + get_first_pod_name, + get_nginx_template_conf, + get_reload_count, + is_secret_present, + replace_configmap_from_yaml, + wait_before_test, +) + + +def assert_event(event_list, event_type, reason, message_substring): + """ + Assert that an event with specific type, reason, and message substring exists. + + :param event_list: List of events + :param event_type: 'Normal' or 'Warning' + :param reason: Event reason + :param message_substring: Substring expected in the event message + """ + for event in event_list: + if event.type == event_type and event.reason == reason and message_substring in event.message: + return + assert ( + False + ), f"Expected event with type '{event_type}', reason '{reason}', and message containing '{message_substring}' not found." + + +@pytest.mark.skip_for_nginx_oss +@pytest.mark.ingresses +@pytest.mark.smoke +class TestMGMTConfigMap: + @pytest.mark.parametrize( + "ingress_controller", + [ + pytest.param( + {"extra_args": ["-enable-prometheus-metrics"]}, + ) + ], + indirect=["ingress_controller"], + ) + def test_mgmt_configmap_events( + self, + cli_arguments, + kube_apis, + ingress_controller_prerequisites, + ingress_controller, + ingress_controller_endpoint, + ): + """ + Test that updating the license secret name in the mgmt configmap + will update the secret on the file system, and reload nginx + and generate an event on the pod and the configmap + """ + ensure_connection_to_public_endpoint( + ingress_controller_endpoint.public_ip, + ingress_controller_endpoint.port, + ingress_controller_endpoint.port_ssl, + ) + ic_pod_name = get_first_pod_name(kube_apis.v1, ingress_controller_prerequisites.namespace) + metrics_url = ( + f"http://{ingress_controller_endpoint.public_ip}:{ingress_controller_endpoint.metrics_port}/metrics" + ) + + print("Step 1: get reload count") + reload_count = get_reload_count(metrics_url) + + wait_before_test(1) + print(f"Step 1a: initial reload count is {reload_count}") + + print("Step 2: create duplicate existing secret with new name") + license_name = create_license( + kube_apis.v1, + ingress_controller_prerequisites.namespace, + cli_arguments["plus-jwt"], + license_token_name="license-token-changed", + ) + assert is_secret_present(kube_apis.v1, license_name, ingress_controller_prerequisites.namespace) + + mgmt_configmap_name = "nginx-config-mgmt" + + print("Step 3: update the ConfigMap/license-token-secret-name to the new secret") + replace_configmap_from_yaml( + kube_apis.v1, + mgmt_configmap_name, + ingress_controller_prerequisites.namespace, + f"{TEST_DATA}/mgmt-configmap-keys/plus-token-name-keys.yaml", + ) + + wait_before_test() + + print("Step 4: check reload count has incremented") + new_reload_count = get_reload_count(metrics_url) + print(f"Step 4a: new reload count is {new_reload_count}") + assert new_reload_count > reload_count + + print("Step 5: check pod for SecretUpdated event") + pod_events = get_events_for_object( + kube_apis.v1, + ingress_controller_prerequisites.namespace, + ic_pod_name, + ) + + # Assert that the 'SecretUpdated' event is present + assert_event( + pod_events, + "Normal", + "SecretUpdated", + f"the special Secret {ingress_controller_prerequisites.namespace}/{license_name} was updated", + ) + + config_events = get_events_for_object( + kube_apis.v1, ingress_controller_prerequisites.namespace, mgmt_configmap_name + ) + + assert_event( + config_events, + "Normal", + "Updated", + f"MGMT ConfigMap {ingress_controller_prerequisites.namespace}/{mgmt_configmap_name} updated without error", + ) + + @pytest.mark.parametrize( + "ingress_controller", + [ + pytest.param( + {"extra_args": ["-enable-prometheus-metrics"]}, + ) + ], + indirect=["ingress_controller"], + ) + def test_full_mgmt_configmap( + self, + cli_arguments, + kube_apis, + ingress_controller_prerequisites, + ingress_controller, + ingress_controller_endpoint, + ): + """ + Test that all mgmt config map params are reflected in the nginx conf + """ + ensure_connection_to_public_endpoint( + ingress_controller_endpoint.public_ip, + ingress_controller_endpoint.port, + ingress_controller_endpoint.port_ssl, + ) + get_first_pod_name(kube_apis.v1, ingress_controller_prerequisites.namespace) + metrics_url = ( + f"http://{ingress_controller_endpoint.public_ip}:{ingress_controller_endpoint.metrics_port}/metrics" + ) + + print("Step 1: get reload count") + reload_count = get_reload_count(metrics_url) + + wait_before_test(1) + print(f"Step 1a: initial reload count is {reload_count}") + + print("Step 2: get the current nginx config") + nginx_config = get_nginx_template_conf(kube_apis.v1, ingress_controller_prerequisites.namespace) + mgmt_config_search = re.search("mgmt {(.|\n)*}", nginx_config) + assert mgmt_config_search is not None + mgmt_config = mgmt_config_search[0] + + print("Step 3: assert that unspecified options are not in the nginx.conf") + assert "usage_report" not in mgmt_config + assert "ssl_verify" not in mgmt_config + assert "resolver" not in mgmt_config + assert "ssl_trusted_certificate" not in mgmt_config + assert "ssl_crl" not in mgmt_config + assert "ssl_certificate_key" not in mgmt_config + assert "ssl_certificate" not in mgmt_config + + mgmt_configmap_name = "nginx-config-mgmt" + + print("Step 4: create ssl trusted certificate secret") + create_secret_from_yaml( + kube_apis.v1, + ingress_controller_prerequisites.namespace, + f"{TEST_DATA}/mgmt-configmap-keys/ssl-trusted-cert.yaml", + ) + + print("Step 5: create ssl certificate secret") + create_secret_from_yaml( + kube_apis.v1, ingress_controller_prerequisites.namespace, f"{TEST_DATA}/mgmt-configmap-keys/ssl-cert.yaml" + ) + + print("Step 6: update the mgmt config map with all options on") + replace_configmap_from_yaml( + kube_apis.v1, + mgmt_configmap_name, + ingress_controller_prerequisites.namespace, + f"{TEST_DATA}/mgmt-configmap-keys/all-options.yaml", + ) + + wait_before_test() + + print("Step 7: get the updated nginx config") + nginx_config = get_nginx_template_conf(kube_apis.v1, ingress_controller_prerequisites.namespace) + mgmt_config_search = re.search("mgmt {(.|\n)*}", nginx_config) + assert mgmt_config_search is not None + mgmt_config = mgmt_config_search[0] + + print("Step 8: check that the nginx config contains the expected params") + assert "usage_report endpoint=product.connect.nginx.com interval=2h;" in mgmt_config + assert "ssl_verify off;" in mgmt_config + assert "resolver 1.1.1.1 8.8.8.8 valid=1h ipv6=off;" in mgmt_config + assert "ssl_trusted_certificate /etc/nginx/secrets/mgmt/ca.crt;" in mgmt_config + assert "ssl_crl /etc/nginx/secrets/mgmt/ca.crl;" in mgmt_config + assert "ssl_certificate /etc/nginx/secrets/mgmt/client;" in mgmt_config + assert "ssl_certificate_key /etc/nginx/secrets/mgmt/client;" in mgmt_config + + print("Step 9: check reload count has incremented") + wait_before_test() + new_reload_count = get_reload_count(metrics_url) + print("new_reload_count", new_reload_count) + assert new_reload_count > reload_count + + print("Step 10: check that the mgmt config map has been updated without error") + config_events = get_events_for_object( + kube_apis.v1, ingress_controller_prerequisites.namespace, mgmt_configmap_name + ) + + assert_event( + config_events, + "Normal", + "Updated", + f"MGMT ConfigMap {ingress_controller_prerequisites.namespace}/{mgmt_configmap_name} updated without error", + ) diff --git a/tests/suite/test_multiple_ns_perf.py b/tests/suite/test_multiple_ns_perf.py index cba889c5ba..4ac14f774e 100644 --- a/tests/suite/test_multiple_ns_perf.py +++ b/tests/suite/test_multiple_ns_perf.py @@ -51,7 +51,7 @@ def ingress_ns_setup( watched_namespace = create_namespace_with_name_from_yaml(kube_apis.v1, f"ns-{i}", f"{TEST_DATA}/common/ns.yaml") multi_ns = multi_ns + f"{watched_namespace}," create_example_app(kube_apis, "simple", watched_namespace) - secret_name = create_secret_from_yaml(kube_apis.v1, watched_namespace, f"{TEST_DATA}/smoke/smoke-secret.yaml") + create_secret_from_yaml(kube_apis.v1, watched_namespace, f"{TEST_DATA}/smoke/smoke-secret.yaml") with open(manifest) as f: doc = yaml.safe_load(f) doc["metadata"]["name"] = f"smoke-ingress-{i}" diff --git a/tests/suite/test_prometheus_metrics.py b/tests/suite/test_prometheus_metrics.py index 92c3daac74..bd183dd2f1 100644 --- a/tests/suite/test_prometheus_metrics.py +++ b/tests/suite/test_prometheus_metrics.py @@ -47,7 +47,8 @@ def prometheus_secret_setup(request, kube_apis, test_namespace): ) def fin(): - delete_secret(kube_apis.v1, prometheus_secret_name, "nginx-ingress") + if request.config.getoption("--skip-fixture-teardown") == "no": + delete_secret(kube_apis.v1, prometheus_secret_name, "nginx-ingress") request.addfinalizer(fin) @@ -68,10 +69,11 @@ def ingress_setup(request, kube_apis, ingress_controller_endpoint, test_namespac req_url = f"https://{ingress_controller_endpoint.public_ip}:{ingress_controller_endpoint.port_ssl}/backend1" def fin(): - print("Clean up simple app") - delete_common_app(kube_apis, "simple", test_namespace) - delete_items_from_yaml(kube_apis, f"{TEST_DATA}/smoke/standard/smoke-ingress.yaml", test_namespace) - delete_secret(kube_apis.v1, secret_name, test_namespace) + if request.config.getoption("--skip-fixture-teardown") == "no": + print("Clean up simple app") + delete_common_app(kube_apis, "simple", test_namespace) + delete_items_from_yaml(kube_apis, f"{TEST_DATA}/smoke/standard/smoke-ingress.yaml", test_namespace) + delete_secret(kube_apis.v1, secret_name, test_namespace) request.addfinalizer(fin) @@ -164,7 +166,7 @@ def test_latency_metrics( "extra_args": [ "-enable-prometheus-metrics", "-enable-latency-metrics", - "-prometheus-tls-secret=nginx-ingress/prometheus-test-secret", + "-prometheus-tls-secret=nginx-ingress/tls-secret", ] }, [ @@ -210,7 +212,8 @@ def ts_setup(request, kube_apis, crd_ingress_controller): gc_resource = create_gc_from_yaml(kube_apis.custom_objects, global_config_file, "nginx-ingress") def fin(): - delete_gc(kube_apis.custom_objects, gc_resource, "nginx-ingress") + if request.config.getoption("--skip-fixture-teardown") == "no": + delete_gc(kube_apis.custom_objects, gc_resource, "nginx-ingress") request.addfinalizer(fin) @@ -295,3 +298,45 @@ def test_total_metrics( wait_before_test() assert_ts_total_metric(ingress_controller_endpoint, ts_type, 0) + + +@pytest.mark.ingresses +@pytest.mark.smoke +@pytest.mark.skip_for_nginx_oss +class TestPrometheusExporterPlus: + @pytest.mark.parametrize( + "ingress_controller, expected_metrics", + [ + pytest.param( + {"extra_args": ["-enable-prometheus-metrics", "-enable-oidc"]}, + [ + 'nginx_ingress_nginxplus_cache_bypass_bytes{class="nginx",zone="jwk"}', + 'nginx_ingress_nginxplus_cache_expired_bytes{class="nginx",zone="jwk"}', + 'nginx_ingress_nginxplus_cache_hit_responses{class="nginx",zone="jwk"}', + 'nginx_ingress_nginxplus_cache_miss_responses{class="nginx",zone="jwk"}', + 'nginx_ingress_nginxplus_cache_size{class="nginx",zone="jwk"}', + 'nginxplus_worker_connection_accepted{class="nginx"', + 'nginxplus_worker_connection_active{class="nginx"', + 'nginxplus_worker_http_requests_total{class="nginx"', + ], + ) + ], + indirect=["ingress_controller"], + ) + def test_plus_metrics( + self, + ingress_controller_endpoint, + ingress_controller, + expected_metrics, + ingress_setup, + ): + ensure_connection(ingress_setup.req_url, 200, {"host": ingress_setup.ingress_host}) + resp = requests.get(ingress_setup.req_url, headers={"host": ingress_setup.ingress_host}, verify=False) + assert resp.status_code == 200 + req_url = f"http://{ingress_controller_endpoint.public_ip}:{ingress_controller_endpoint.metrics_port}/metrics" + ensure_connection(req_url, 200) + resp = requests.get(req_url) + assert resp.status_code == 200, f"Expected 200 code for /metrics but got {resp.status_code}" + resp_content = resp.content.decode("utf-8") + for item in expected_metrics: + assert item in resp_content diff --git a/tests/suite/test_rewrites.py b/tests/suite/test_rewrites.py index 4f07913b60..eee14bdcf2 100644 --- a/tests/suite/test_rewrites.py +++ b/tests/suite/test_rewrites.py @@ -23,7 +23,8 @@ def hello_app(request, kube_apis, test_namespace): wait_until_all_pods_are_ready(kube_apis.v1, test_namespace) def fin(): - delete_items_from_yaml(kube_apis, hello_app_yaml, test_namespace) + if request.config.getoption("--skip-fixture-teardown") == "no": + delete_items_from_yaml(kube_apis, hello_app_yaml, test_namespace) request.addfinalizer(fin) @@ -44,7 +45,8 @@ def vs_rewrites_setup( wait_before_test() def fin(): - delete_virtual_server(kube_apis.custom_objects, vs, test_namespace) + if request.config.getoption("--skip-fixture-teardown") == "no": + delete_virtual_server(kube_apis.custom_objects, vs, test_namespace) request.addfinalizer(fin) @@ -68,10 +70,11 @@ def vsr_rewrites_setup( wait_before_test() def fin(): - delete_virtual_server(kube_apis.custom_objects, vs_parent, test_namespace) - delete_v_s_route(kube_apis.custom_objects, vsr_prefixes, test_namespace) - delete_v_s_route(kube_apis.custom_objects, vsr_regex1, test_namespace) - delete_v_s_route(kube_apis.custom_objects, vsr_regex2, test_namespace) + if request.config.getoption("--skip-fixture-teardown") == "no": + delete_virtual_server(kube_apis.custom_objects, vs_parent, test_namespace) + delete_v_s_route(kube_apis.custom_objects, vsr_prefixes, test_namespace) + delete_v_s_route(kube_apis.custom_objects, vsr_regex1, test_namespace) + delete_v_s_route(kube_apis.custom_objects, vsr_regex2, test_namespace) request.addfinalizer(fin) @@ -97,6 +100,7 @@ def fin(): @pytest.mark.parametrize("crd_ingress_controller", [({"type": "complete"})], indirect=True) class TestRewrites: @pytest.mark.vs + @pytest.mark.vs_rewrite @pytest.mark.parametrize("path,args,cookies,expected", test_data) def test_vs_rewrite(self, vs_rewrites_setup, path, args, cookies, expected): """ @@ -108,6 +112,7 @@ def test_vs_rewrite(self, vs_rewrites_setup, path, args, cookies, expected): assert f"URI: {expected}\nRequest" in resp.text @pytest.mark.vsr + @pytest.mark.vsr_rewrite @pytest.mark.parametrize("path,args,cookies,expected", test_data) def test_vsr_rewrite(self, vsr_rewrites_setup, path, args, cookies, expected): """ diff --git a/tests/suite/test_rl_ingress.py b/tests/suite/test_rl_ingress.py new file mode 100644 index 0000000000..fe9fddecf8 --- /dev/null +++ b/tests/suite/test_rl_ingress.py @@ -0,0 +1,152 @@ +import time + +import pytest +import requests +from settings import TEST_DATA +from suite.fixtures.fixtures import PublicEndpoint +from suite.utils.resources_utils import ( + are_all_pods_in_ready_state, + create_example_app, + create_items_from_yaml, + delete_common_app, + delete_items_from_yaml, + ensure_connection_to_public_endpoint, + ensure_response_from_backend, + get_first_pod_name, + get_ingress_nginx_template_conf, + get_pod_list, + scale_deployment, + wait_before_test, + wait_until_all_pods_are_ready, +) +from suite.utils.yaml_utils import get_first_ingress_host_from_yaml, get_name_from_yaml + + +class AnnotationsSetup: + """Encapsulate Annotations example details. + + Attributes: + public_endpoint: PublicEndpoint + ingress_src_file: + ingress_name: + ingress_pod_name: + ingress_host: + namespace: example namespace + """ + + def __init__( + self, + public_endpoint: PublicEndpoint, + ingress_src_file, + ingress_name, + ingress_host, + ingress_pod_name, + namespace, + request_url, + ): + self.public_endpoint = public_endpoint + self.ingress_name = ingress_name + self.ingress_pod_name = ingress_pod_name + self.namespace = namespace + self.ingress_host = ingress_host + self.ingress_src_file = ingress_src_file + self.request_url = request_url + + +@pytest.fixture(scope="class") +def annotations_setup( + request, + kube_apis, + ingress_controller_prerequisites, + ingress_controller_endpoint, + ingress_controller, + test_namespace, +) -> AnnotationsSetup: + print("------------------------- Deploy Ingress with rate-limit annotations -----------------------------------") + src = f"{TEST_DATA}/rate-limit/ingress/{request.param}/annotations-rl-ingress.yaml" + create_items_from_yaml(kube_apis, src, test_namespace) + ingress_name = get_name_from_yaml(src) + ingress_host = get_first_ingress_host_from_yaml(src) + request_url = f"http://{ingress_controller_endpoint.public_ip}:{ingress_controller_endpoint.port}/backend1" + + create_example_app(kube_apis, "simple", test_namespace) + wait_until_all_pods_are_ready(kube_apis.v1, test_namespace) + + ensure_connection_to_public_endpoint( + ingress_controller_endpoint.public_ip, ingress_controller_endpoint.port, ingress_controller_endpoint.port_ssl + ) + ic_pod_name = get_first_pod_name(kube_apis.v1, ingress_controller_prerequisites.namespace) + + def fin(): + if request.config.getoption("--skip-fixture-teardown") == "no": + print("Clean up:") + delete_common_app(kube_apis, "simple", test_namespace) + delete_items_from_yaml(kube_apis, src, test_namespace) + + request.addfinalizer(fin) + + return AnnotationsSetup( + ingress_controller_endpoint, + src, + ingress_name, + ingress_host, + ic_pod_name, + test_namespace, + request_url, + ) + + +@pytest.mark.annotations +@pytest.mark.parametrize("annotations_setup", ["standard", "mergeable"], indirect=True) +class TestRateLimitIngress: + def test_ingress_rate_limit(self, kube_apis, annotations_setup, ingress_controller_prerequisites, test_namespace): + """ + Test if rate-limit applies with 1rps for standard and mergeable ingresses + """ + ensure_response_from_backend(annotations_setup.request_url, annotations_setup.ingress_host, check404=True) + print("----------------------- Send request ----------------------") + counter = [] + t_end = time.perf_counter() + 2 # send requests for 2 seconds + while time.perf_counter() < t_end: + resp = requests.get( + annotations_setup.request_url, + headers={"host": annotations_setup.ingress_host}, + ) + counter.append(resp.status_code) + assert (counter.count(200)) <= 2 and (429 in counter) # check for only 2 200s in the list + + +@pytest.mark.annotations +@pytest.mark.parametrize("annotations_setup", ["standard-scaled", "mergeable-scaled"], indirect=True) +class TestRateLimitIngressScaled: + def test_ingress_rate_limit_scaled( + self, kube_apis, annotations_setup, ingress_controller_prerequisites, test_namespace + ): + """ + Test if rate-limit scaling works with standard and mergeable ingresses + """ + ns = ingress_controller_prerequisites.namespace + scale_deployment(kube_apis.v1, kube_apis.apps_v1_api, "nginx-ingress", ns, 4) + count = 0 + while (not are_all_pods_in_ready_state(kube_apis.v1, ns)) and count < 10: + count += 1 + wait_before_test() + + ic_pods = get_pod_list(kube_apis.v1, ns) + flag = False + retries = 0 + while flag is False and retries < 10: + retries += 1 + wait_before_test() + for i in range(len(ic_pods)): + conf = get_ingress_nginx_template_conf( + kube_apis.v1, + annotations_setup.namespace, + annotations_setup.ingress_name, + ic_pods[i].metadata.name, + ingress_controller_prerequisites.namespace, + ) + flag = ("rate=10r/s" in conf) or ("rate=13r/s" in conf) + + assert flag + scale_deployment(kube_apis.v1, kube_apis.apps_v1_api, "nginx-ingress", ns, 1) diff --git a/tests/suite/test_rl_policies.py b/tests/suite/test_rl_policies.py index efed38b4f6..bc167d7ff2 100644 --- a/tests/suite/test_rl_policies.py +++ b/tests/suite/test_rl_policies.py @@ -5,7 +5,7 @@ from settings import TEST_DATA from suite.utils.custom_resources_utils import read_custom_resource from suite.utils.policy_resources_utils import create_policy_from_yaml, delete_policy -from suite.utils.resources_utils import wait_before_test +from suite.utils.resources_utils import get_pod_list, get_vs_nginx_template_conf, scale_deployment, wait_before_test from suite.utils.vs_vsr_resources_utils import ( create_virtual_server_from_yaml, delete_virtual_server, @@ -14,7 +14,9 @@ std_vs_src = f"{TEST_DATA}/rate-limit/standard/virtual-server.yaml" rl_pol_pri_src = f"{TEST_DATA}/rate-limit/policies/rate-limit-primary.yaml" +rl_pol_pri_sca_src = f"{TEST_DATA}/rate-limit/policies/rate-limit-primary-scaled.yaml" rl_vs_pri_src = f"{TEST_DATA}/rate-limit/spec/virtual-server-primary.yaml" +rl_vs_pri_sca_src = f"{TEST_DATA}/rate-limit/spec/virtual-server-primary-scaled.yaml" rl_pol_sec_src = f"{TEST_DATA}/rate-limit/policies/rate-limit-secondary.yaml" rl_vs_sec_src = f"{TEST_DATA}/rate-limit/spec/virtual-server-secondary.yaml" rl_pol_invalid = f"{TEST_DATA}/rate-limit/policies/rate-limit-invalid.yaml" @@ -25,6 +27,7 @@ @pytest.mark.policies +@pytest.mark.policies_rl @pytest.mark.parametrize( "crd_ingress_controller, virtual_server_setup", [ @@ -306,3 +309,51 @@ def test_rl_override_spec_route( delete_policy(kube_apis.custom_objects, pol_name_sec, test_namespace) self.restore_default_vs(kube_apis, virtual_server_setup) assert rate_sec >= occur.count(200) >= (rate_sec - 2) + + @pytest.mark.parametrize("src", [rl_vs_pri_sca_src]) + def test_rl_policy_scaled( + self, + kube_apis, + ingress_controller_prerequisites, + crd_ingress_controller, + virtual_server_setup, + test_namespace, + src, + ): + """ + Test if rate-limit scaling is being calculated correctly + """ + ns = ingress_controller_prerequisites.namespace + scale_deployment(kube_apis.v1, kube_apis.apps_v1_api, "nginx-ingress", ns, 4) + + print(f"Create rl policy") + pol_name = create_policy_from_yaml(kube_apis.custom_objects, rl_pol_pri_sca_src, test_namespace) + print(f"Patch vs with policy: {src}") + patch_virtual_server_from_yaml( + kube_apis.custom_objects, + virtual_server_setup.vs_name, + src, + virtual_server_setup.namespace, + ) + wait_before_test() + + policy_info = read_custom_resource(kube_apis.custom_objects, test_namespace, "policies", pol_name) + ic_pods = get_pod_list(kube_apis.v1, ns) + for i in range(len(ic_pods)): + conf = get_vs_nginx_template_conf( + kube_apis.v1, + virtual_server_setup.namespace, + virtual_server_setup.vs_name, + ic_pods[i].metadata.name, + ingress_controller_prerequisites.namespace, + ) + assert "rate=10r/s" in conf + # restore replicas, policy and vs + scale_deployment(kube_apis.v1, kube_apis.apps_v1_api, "nginx-ingress", ns, 1) + delete_policy(kube_apis.custom_objects, pol_name, test_namespace) + self.restore_default_vs(kube_apis, virtual_server_setup) + assert ( + policy_info["status"] + and policy_info["status"]["reason"] == "AddedOrUpdated" + and policy_info["status"]["state"] == "Valid" + ) diff --git a/tests/suite/test_rl_policies_vsr.py b/tests/suite/test_rl_policies_vsr.py index c28fc43bf2..a31bff2281 100644 --- a/tests/suite/test_rl_policies_vsr.py +++ b/tests/suite/test_rl_policies_vsr.py @@ -5,12 +5,18 @@ from settings import TEST_DATA from suite.utils.custom_resources_utils import read_custom_resource from suite.utils.policy_resources_utils import create_policy_from_yaml, delete_policy -from suite.utils.resources_utils import wait_before_test -from suite.utils.vs_vsr_resources_utils import patch_v_s_route_from_yaml, patch_virtual_server_from_yaml +from suite.utils.resources_utils import get_pod_list, scale_deployment, wait_before_test +from suite.utils.vs_vsr_resources_utils import ( + get_vs_nginx_template_conf, + patch_v_s_route_from_yaml, + patch_virtual_server_from_yaml, +) std_vs_src = f"{TEST_DATA}/virtual-server-route/standard/virtual-server.yaml" rl_pol_pri_src = f"{TEST_DATA}/rate-limit/policies/rate-limit-primary.yaml" +rl_pol_pri_sca_src = f"{TEST_DATA}/rate-limit/policies/rate-limit-primary-scaled.yaml" rl_vsr_pri_src = f"{TEST_DATA}/rate-limit/route-subroute/virtual-server-route-pri-subroute.yaml" +rl_vsr_pri_sca_src = f"{TEST_DATA}/rate-limit/route-subroute/virtual-server-route-pri-subroute-scaled.yaml" rl_pol_sec_src = f"{TEST_DATA}/rate-limit/policies/rate-limit-secondary.yaml" rl_vsr_sec_src = f"{TEST_DATA}/rate-limit/route-subroute/virtual-server-route-sec-subroute.yaml" rl_pol_invalid_src = f"{TEST_DATA}/rate-limit/policies/rate-limit-invalid.yaml" @@ -21,6 +27,7 @@ @pytest.mark.policies +@pytest.mark.policies_rl @pytest.mark.parametrize( "crd_ingress_controller, v_s_route_setup", [ @@ -354,3 +361,58 @@ def test_override_vs_vsr( kube_apis.custom_objects, v_s_route_setup.vs_name, std_vs_src, v_s_route_setup.namespace ) assert rate_sec >= occur.count(200) >= (rate_sec - 2) + + @pytest.mark.parametrize("src", [rl_vsr_pri_sca_src]) + def test_rl_policy_scaled_vsr( + self, + kube_apis, + ingress_controller_prerequisites, + crd_ingress_controller, + v_s_route_app_setup, + v_s_route_setup, + test_namespace, + src, + ): + """ + Test if rate-limiting policy is working with ~1 rps in vsr:subroute + """ + + ns = ingress_controller_prerequisites.namespace + scale_deployment(kube_apis.v1, kube_apis.apps_v1_api, "nginx-ingress", ns, 4) + + print(f"Create rl policy") + pol_name = create_policy_from_yaml( + kube_apis.custom_objects, rl_pol_pri_sca_src, v_s_route_setup.route_m.namespace + ) + print(f"Patch vsr with policy: {src}") + patch_v_s_route_from_yaml( + kube_apis.custom_objects, + v_s_route_setup.route_m.name, + src, + v_s_route_setup.route_m.namespace, + ) + + wait_before_test() + policy_info = read_custom_resource( + kube_apis.custom_objects, v_s_route_setup.route_m.namespace, "policies", pol_name + ) + + ic_pods = get_pod_list(kube_apis.v1, ns) + for i in range(len(ic_pods)): + conf = get_vs_nginx_template_conf( + kube_apis.v1, + v_s_route_setup.route_m.namespace, + v_s_route_setup.vs_name, + ic_pods[i].metadata.name, + ingress_controller_prerequisites.namespace, + ) + assert "rate=10r/s" in conf + # restore replicas, policy and vsr + scale_deployment(kube_apis.v1, kube_apis.apps_v1_api, "nginx-ingress", ns, 1) + delete_policy(kube_apis.custom_objects, pol_name, v_s_route_setup.route_m.namespace) + self.restore_default_vsr(kube_apis, v_s_route_setup) + assert ( + policy_info["status"] + and policy_info["status"]["reason"] == "AddedOrUpdated" + and policy_info["status"]["state"] == "Valid" + ) diff --git a/tests/suite/test_smoke.py b/tests/suite/test_smoke.py index b051f75554..39013c7b8a 100644 --- a/tests/suite/test_smoke.py +++ b/tests/suite/test_smoke.py @@ -44,7 +44,7 @@ def __init__(self, public_endpoint: PublicEndpoint, ingress_host): self.ingress_host = ingress_host -@pytest.fixture(scope="class", params=["standard", "mergeable"]) +@pytest.fixture(scope="class", params=["standard", "mergeable", "implementation-specific-pathtype"]) def smoke_setup(request, kube_apis, ingress_controller_endpoint, ingress_controller, test_namespace) -> SmokeSetup: print("------------------------- Deploy Smoke Example -----------------------------------") secret_name = create_secret_from_yaml(kube_apis.v1, test_namespace, f"{TEST_DATA}/smoke/smoke-secret.yaml") @@ -59,11 +59,12 @@ def smoke_setup(request, kube_apis, ingress_controller_endpoint, ingress_control ) def fin(): - print("Clean up the Smoke Application:") - delete_common_app(kube_apis, "simple", test_namespace) - delete_items_from_yaml(kube_apis, f"{TEST_DATA}/smoke/{request.param}/smoke-ingress.yaml", test_namespace) - delete_secret(kube_apis.v1, secret_name, test_namespace) - write_to_json(f"reload-{get_test_file_name(request.node.fspath)}.json", reload_times) + if request.config.getoption("--skip-fixture-teardown") == "no": + print("Clean up the Smoke Application:") + delete_common_app(kube_apis, "simple", test_namespace) + delete_items_from_yaml(kube_apis, f"{TEST_DATA}/smoke/{request.param}/smoke-ingress.yaml", test_namespace) + delete_secret(kube_apis.v1, secret_name, test_namespace) + write_to_json(f"reload-{get_test_file_name(request.node.fspath)}.json", reload_times) request.addfinalizer(fin) diff --git a/tests/suite/test_snippet_flag.py b/tests/suite/test_snippet_flag.py index 65ac53e90f..0e088d3c65 100644 --- a/tests/suite/test_snippet_flag.py +++ b/tests/suite/test_snippet_flag.py @@ -14,7 +14,6 @@ @pytest.mark.ingresses class TestSnippetAnnotation: - """ Checks if ingress snippets are enabled as a cli arg, that the value from a snippet annotation defined in an ingress resource is set in the nginx conf. diff --git a/tests/suite/test_tls.py b/tests/suite/test_tls.py index 72e17a79b0..59a2162e4c 100644 --- a/tests/suite/test_tls.py +++ b/tests/suite/test_tls.py @@ -7,6 +7,7 @@ delete_items_from_yaml, delete_secret, ensure_connection_to_public_endpoint, + get_reload_count, is_secret_present, replace_secret, wait_before_test, @@ -43,12 +44,13 @@ def assert_gb_subject(endpoint, host): class TLSSetup: - def __init__(self, ingress_host, secret_name, secret_path, new_secret_path, invalid_secret_path): + def __init__(self, ingress_host, secret_name, secret_path, new_secret_path, invalid_secret_path, metrics_url): self.ingress_host = ingress_host self.secret_name = secret_name self.secret_path = secret_path self.new_secret_path = new_secret_path self.invalid_secret_path = invalid_secret_path + self.metrics_url = metrics_url @pytest.fixture(scope="class") @@ -63,6 +65,7 @@ def tls_setup( print("------------------------- Deploy TLS setup -----------------------------------") test_data_path = f"{TEST_DATA}/tls" + metrics_url = f"http://{ingress_controller_endpoint.public_ip}:{ingress_controller_endpoint.metrics_port}/metrics" ingress_path = f"{test_data_path}/{request.param}/ingress.yaml" create_ingress_from_yaml(kube_apis.networking_v1, test_namespace, ingress_path) @@ -76,10 +79,11 @@ def tls_setup( ) def fin(): - print("Clean up TLS setup") - delete_items_from_yaml(kube_apis, ingress_path, test_namespace) - if is_secret_present(kube_apis.v1, secret_name, test_namespace): - delete_secret(kube_apis.v1, secret_name, test_namespace) + if request.config.getoption("--skip-fixture-teardown") == "no": + print("Clean up TLS setup") + delete_items_from_yaml(kube_apis, ingress_path, test_namespace) + if is_secret_present(kube_apis.v1, secret_name, test_namespace): + delete_secret(kube_apis.v1, secret_name, test_namespace) request.addfinalizer(fin) @@ -89,11 +93,19 @@ def fin(): f"{test_data_path}/tls-secret.yaml", f"{test_data_path}/new-tls-secret.yaml", f"{test_data_path}/invalid-tls-secret.yaml", + metrics_url, ) @pytest.mark.ingresses -@pytest.mark.parametrize("tls_setup", ["standard", "mergeable"], indirect=True) +@pytest.mark.parametrize( + "ingress_controller, tls_setup", + [ + pytest.param({"extra_args": ["-enable-prometheus-metrics", "-ssl-dynamic-reload=false"]}, "standard"), + pytest.param({"extra_args": ["-enable-prometheus-metrics", "-ssl-dynamic-reload=false"]}, "mergeable"), + ], + indirect=True, +) class TestIngressTLS: def test_tls_termination(self, kube_apis, ingress_controller_endpoint, test_namespace, tls_setup): print("Step 1: no secret") @@ -126,7 +138,50 @@ def test_tls_termination(self, kube_apis, ingress_controller_endpoint, test_name wait_before_test(1) assert_us_subject(ingress_controller_endpoint, tls_setup.ingress_host) + # with -ssl-dynamic-reload=false, we expect + # replacing a secret to trigger a reload + count_before_replace = get_reload_count(tls_setup.metrics_url) + print("Step 7: update secret and check") replace_secret(kube_apis.v1, tls_setup.secret_name, test_namespace, tls_setup.new_secret_path) wait_before_test(1) assert_gb_subject(ingress_controller_endpoint, tls_setup.ingress_host) + + count_after = get_reload_count(tls_setup.metrics_url) + reloads = count_after - count_before_replace + expected_reloads = 1 + assert reloads == expected_reloads, f"expected {expected_reloads} reloads, got {reloads}" + + +@pytest.mark.ingresses +@pytest.mark.parametrize( + "ingress_controller, tls_setup", + [ + pytest.param({"extra_args": ["-enable-prometheus-metrics"]}, "standard"), + pytest.param({"extra_args": ["-enable-prometheus-metrics"]}, "mergeable"), + ], + indirect=True, +) +class TestIngressTLSDynamicReloads: + def test_tls_termination(self, kube_apis, ingress_controller_endpoint, test_namespace, tls_setup): + print("Step 1: no secret") + assert_unrecognized_name_error(ingress_controller_endpoint, tls_setup.ingress_host) + + print("Step 2: deploy secret and check") + create_secret_from_yaml(kube_apis.v1, test_namespace, tls_setup.secret_path) + wait_before_test(1) + assert_us_subject(ingress_controller_endpoint, tls_setup.ingress_host) + + # for Plus with -ssl-dynamic-reload=true, we expect + # replacing a secret not to trigger a reload + count_before_replace = get_reload_count(tls_setup.metrics_url) + + print("Step 3: update secret and check") + replace_secret(kube_apis.v1, tls_setup.secret_name, test_namespace, tls_setup.new_secret_path) + wait_before_test(1) + assert_gb_subject(ingress_controller_endpoint, tls_setup.ingress_host) + + count_after = get_reload_count(tls_setup.metrics_url) + reloads = count_after - count_before_replace + expected_reloads = 0 + assert reloads == expected_reloads, f"expected {expected_reloads} reloads, got {reloads}" diff --git a/tests/suite/test_transport_server.py b/tests/suite/test_transport_server.py index fa72fbd694..4af17653dc 100644 --- a/tests/suite/test_transport_server.py +++ b/tests/suite/test_transport_server.py @@ -55,10 +55,11 @@ def test_snippets( transport_server_setup.namespace, ) - assert ( - "limit_conn_zone $binary_remote_addr zone=addr:10m;" in conf # stream-snippets - and "limit_conn addr 1;" in conf # server-snippets - ) + conf_lines = [line.strip() for line in conf.split("\n")] + assert "limit_conn_zone $binary_remote_addr zone=addr:10m;" in conf_lines # stream-snippets on separate line + assert "limit_conn addr 1;" in conf_lines # server-snippets on separate line + assert "# a comment is allowed in snippets" in conf_lines # comments are allowed in server snippets + assert 'add_header X-test-header "test-value";' in conf_lines # new line in server-snippets on separate line def test_configurable_timeout_directives( self, kube_apis, crd_ingress_controller, transport_server_setup, ingress_controller_prerequisites diff --git a/tests/suite/test_transport_server_backup_service.py b/tests/suite/test_transport_server_backup_service.py new file mode 100644 index 0000000000..cde078824c --- /dev/null +++ b/tests/suite/test_transport_server_backup_service.py @@ -0,0 +1,297 @@ +import pytest +from settings import TEST_DATA +from suite.fixtures.fixtures import PublicEndpoint +from suite.utils.custom_resources_utils import create_ts_from_yaml, delete_ts, patch_ts_from_yaml +from suite.utils.resources_utils import ( + create_configmap_from_yaml, + create_items_from_yaml, + create_namespace_with_name_from_yaml, + create_secret_from_yaml, + create_secure_app_deployment_with_name, + create_service_from_yaml, + create_service_with_name, + delete_items_from_yaml, + delete_namespace, + ensure_connection, + ensure_response_from_backend, + get_first_pod_name, + get_ts_nginx_template_conf, + replace_configmap, + replace_configmap_from_yaml, + scale_deployment, + wait_before_test, + wait_until_all_pods_are_ready, +) +from suite.utils.ssl_utils import create_sni_session +from suite.utils.yaml_utils import get_first_host_from_yaml + +secure_app_secret = f"{TEST_DATA}/common/app/secure/secret/app-tls-secret.yaml" +secure_app_config_map = f"{TEST_DATA}/common/app/secure/config-map/secure-config.yaml" +ts_with_backup = f"{TEST_DATA}/transport-server-backup-service/transport-server-with-backup.yaml" + + +class ExternalNameSetup: + """Encapsulate ExternalName example details. + + Attributes: + ic_pod_name: + external_host: external service host + """ + + def __init__(self, ic_pod_name, external_svc, external_host): + self.ic_pod_name = ic_pod_name + self.external_svc = external_svc + self.external_host = external_host + + +@pytest.fixture(scope="class") +def ts_externalname_setup( + request, kube_apis, ingress_controller_prerequisites, transport_server_tls_passthrough_setup, test_namespace +) -> ExternalNameSetup: + print("------------------------- Deploy External-Backend -----------------------------------") + external_ns = create_namespace_with_name_from_yaml(kube_apis.v1, "external-ns", f"{TEST_DATA}/common/ns.yaml") + external_svc_name = create_service_with_name(kube_apis.v1, external_ns, "external-backend-svc", 8443, 8443) + create_secret_from_yaml(kube_apis.v1, external_ns, secure_app_secret) + create_configmap_from_yaml(kube_apis.v1, external_ns, secure_app_config_map) + create_secure_app_deployment_with_name(kube_apis.apps_v1_api, external_ns, "external-backend") + print("------------------------- Prepare ExternalName Setup -----------------------------------") + external_svc_src = f"{TEST_DATA}/transport-server-backup-service/backup-svc.yaml" + external_svc_host = f"{external_svc_name}.{external_ns}.svc.cluster.local" + config_map_name = ingress_controller_prerequisites.config_map["metadata"]["name"] + replace_configmap_from_yaml( + kube_apis.v1, + config_map_name, + ingress_controller_prerequisites.namespace, + f"{TEST_DATA}/transport-server-backup-service/nginx-config.yaml", + ) + external_svc = create_service_from_yaml(kube_apis.v1, test_namespace, external_svc_src) + req_url = ( + f"https://{transport_server_tls_passthrough_setup.public_endpoint.public_ip}:" + f"{transport_server_tls_passthrough_setup.public_endpoint.port_ssl}" + ) + wait_before_test(2) + ensure_connection(req_url) + ic_pod_name = get_first_pod_name(kube_apis.v1, ingress_controller_prerequisites.namespace) + ensure_response_from_backend( + req_url, + transport_server_tls_passthrough_setup.ts_host, + ) + + def fin(): + if request.config.getoption("--skip-fixture-teardown") == "no": + print("\nClean up ExternalName Setup:") + delete_namespace(kube_apis.v1, external_ns) + replace_configmap( + kube_apis.v1, + config_map_name, + ingress_controller_prerequisites.namespace, + ingress_controller_prerequisites.config_map, + ) + + request.addfinalizer(fin) + + return ExternalNameSetup(ic_pod_name, external_svc, external_svc_host) + + +class TransportServerTlsSetup: + """ + Encapsulate Transport Server details. + + Attributes: + public_endpoint (object): + tls_passthrough_port (int): + ts_resource (dict): + name (str): + namespace (str): + ts_host (str): + """ + + def __init__( + self, public_endpoint: PublicEndpoint, tls_passthrough_port: int, ts_resource, name, namespace, ts_host + ): + self.public_endpoint = public_endpoint + self.tls_passthrough_port = tls_passthrough_port + self.ts_resource = ts_resource + self.name = name + self.namespace = namespace + self.ts_host = ts_host + + +@pytest.fixture(scope="class") +def transport_server_tls_passthrough_setup( + request, kube_apis, test_namespace, ingress_controller_endpoint +) -> TransportServerTlsSetup: + """ + Prepare Transport Server Example. + + :param request: internal pytest fixture to parametrize this method + :param kube_apis: client apis + :param test_namespace: namespace for test resources + :param ingress_controller_endpoint: ip and port information + :return TransportServerTlsSetup: + """ + print("------------------------- Deploy Transport Server with tls passthrough -----------------------------------") + # deploy secure_app + secure_app_file = f"{TEST_DATA}/{request.param['example']}/standard/secure-app.yaml" + create_items_from_yaml(kube_apis, secure_app_file, test_namespace) + + # deploy transport server + transport_server_std_src = f"{TEST_DATA}/{request.param['example']}/standard/transport-server.yaml" + ts_resource = create_ts_from_yaml(kube_apis.custom_objects, transport_server_std_src, test_namespace) + ts_host = get_first_host_from_yaml(transport_server_std_src) + wait_until_all_pods_are_ready(kube_apis.v1, test_namespace) + + def fin(): + if request.config.getoption("--skip-fixture-teardown") == "no": + print("Clean up TransportServer and app:") + delete_ts(kube_apis.custom_objects, ts_resource, test_namespace) + delete_items_from_yaml(kube_apis, secure_app_file, test_namespace) + + request.addfinalizer(fin) + + return TransportServerTlsSetup( + ingress_controller_endpoint, + request.param["tls_passthrough_port"], + ts_resource, + ts_resource["metadata"]["name"], + test_namespace, + ts_host, + ) + + +@pytest.mark.ts +@pytest.mark.skip_for_nginx_oss +@pytest.mark.parametrize( + "crd_ingress_controller, transport_server_tls_passthrough_setup", + [ + ( + { + "type": "complete", + "extra_args": [ + "-enable-tls-passthrough=true", + "-log-level=debug", + ], + }, + {"example": "transport-server-backup-service", "tls_passthrough_port": 443}, + ), + ], + indirect=True, + ids=["tls_passthrough_with_default_port"], +) +class TestTransportServerWithBackupService: + """ + This test validates that we still get a response back from the default + service, and not the backup service, as long as the default service is still available + """ + + def test_get_response_from_application( + self, + kube_apis, + ingress_controller_prerequisites, + crd_ingress_controller, + ts_externalname_setup, + transport_server_tls_passthrough_setup, + test_namespace, + ): + patch_ts_from_yaml( + kube_apis.custom_objects, + transport_server_tls_passthrough_setup.name, + ts_with_backup, + transport_server_tls_passthrough_setup.namespace, + ) + wait_before_test() + session = create_sni_session() + req_url = ( + f"https://{transport_server_tls_passthrough_setup.public_endpoint.public_ip}:" + f"{transport_server_tls_passthrough_setup.public_endpoint.port_ssl}" + ) + print(req_url) + wait_before_test() + resp = session.get( + req_url, + headers={"host": transport_server_tls_passthrough_setup.ts_host}, + verify=False, + ) + + assert resp.status_code == 200 + assert f"hello from pod {get_first_pod_name(kube_apis.v1, test_namespace)}" in resp.text + + result_conf = get_ts_nginx_template_conf( + kube_apis.v1, + test_namespace, + transport_server_tls_passthrough_setup.name, + ts_externalname_setup.ic_pod_name, + ingress_controller_prerequisites.namespace, + ) + + assert "least_conn;" in result_conf + assert f"server {ts_externalname_setup.external_host}:8443 resolve backup;" in result_conf + + """ + This test validates that we get a response back from the backup service. + This test also scales the application back to 2 replicas after confirming a response from the backup service. + """ + + def test_get_response_from_backup( + self, + kube_apis, + ingress_controller_prerequisites, + crd_ingress_controller, + ts_externalname_setup, + transport_server_tls_passthrough_setup, + test_namespace, + ): + patch_ts_from_yaml( + kube_apis.custom_objects, + transport_server_tls_passthrough_setup.name, + ts_with_backup, + transport_server_tls_passthrough_setup.namespace, + ) + wait_before_test() + session = create_sni_session() + req_url = ( + f"https://{transport_server_tls_passthrough_setup.public_endpoint.public_ip}:" + f"{transport_server_tls_passthrough_setup.public_endpoint.port_ssl}" + ) + wait_before_test() + resp = session.get( + req_url, + headers={"host": transport_server_tls_passthrough_setup.ts_host}, + verify=False, + ) + + assert resp.status_code == 200 + assert f"hello from pod {get_first_pod_name(kube_apis.v1, test_namespace)}" in resp.text + + result_conf = get_ts_nginx_template_conf( + kube_apis.v1, + test_namespace, + transport_server_tls_passthrough_setup.name, + ts_externalname_setup.ic_pod_name, + ingress_controller_prerequisites.namespace, + ) + assert "least_conn;" in result_conf + assert f"server {ts_externalname_setup.external_host}:8443 resolve backup;" in result_conf + + scale_deployment(kube_apis.v1, kube_apis.apps_v1_api, "secure-app", test_namespace, 0) + wait_before_test() + + result_conf = get_ts_nginx_template_conf( + kube_apis.v1, + test_namespace, + transport_server_tls_passthrough_setup.name, + ts_externalname_setup.ic_pod_name, + ingress_controller_prerequisites.namespace, + ) + assert "least_conn;" in result_conf + assert f"server {ts_externalname_setup.external_host}:8443 resolve backup;" in result_conf + + resp_after_scale = session.get( + req_url, + headers={"host": transport_server_tls_passthrough_setup.ts_host}, + verify=False, + ) + + assert resp_after_scale.status_code == 200 + + scale_deployment(kube_apis.v1, kube_apis.apps_v1_api, "secure-app", test_namespace, 1) diff --git a/tests/suite/test_transport_server_custom_ip_listener.py b/tests/suite/test_transport_server_custom_ip_listener.py new file mode 100644 index 0000000000..dd6983472b --- /dev/null +++ b/tests/suite/test_transport_server_custom_ip_listener.py @@ -0,0 +1,65 @@ +import pytest +from settings import TEST_DATA +from suite.utils.custom_resources_utils import patch_gc_from_yaml, patch_ts_from_yaml +from suite.utils.resources_utils import get_events_for_object, get_ts_nginx_template_conf, wait_before_test + + +@pytest.mark.ts +@pytest.mark.parametrize( + "crd_ingress_controller, transport_server_setup", + [ + ( + { + "type": "complete", + "extra_args": [ + "-global-configuration=nginx-ingress/nginx-configuration", + "-enable-leader-election=false", + ], + }, + {"example": "transport-server-status"}, + ) + ], + indirect=True, +) +class TestTransportServerCustomIPListener: + def test_ts_custom_ip_listener( + self, kube_apis, crd_ingress_controller, transport_server_setup, ingress_controller_prerequisites + ): + """ + Test transport server with custom IP listener + """ + + global_config_file = f"{TEST_DATA}/transport-server-custom-ip-listener/global-configuration.yaml" + patch_gc_from_yaml(kube_apis.custom_objects, "nginx-configuration", global_config_file, "nginx-ingress") + + patch_src = f"{TEST_DATA}/transport-server-custom-ip-listener/transport-server.yaml" + patch_ts_from_yaml( + kube_apis.custom_objects, + transport_server_setup.name, + patch_src, + transport_server_setup.namespace, + ) + wait_before_test() + + conf = get_ts_nginx_template_conf( + kube_apis.v1, + transport_server_setup.namespace, + transport_server_setup.name, + transport_server_setup.ingress_pod_name, + ingress_controller_prerequisites.namespace, + ) + print(conf) + + conf_lines = [line.strip() for line in conf.split("\n")] + assert "listen 127.0.0.1:5353;" in conf_lines + assert "listen [::1]:5353;" in conf_lines + + gc_events = get_events_for_object(kube_apis.v1, "nginx-ingress", "nginx-configuration") + gc_event_latest = gc_events[-1] + print(gc_event_latest) + + assert ( + gc_event_latest.reason == "Updated" + and gc_event_latest.type == "Normal" + and "GlobalConfiguration nginx-ingress/nginx-configuration was added or updated" in gc_event_latest.message + ) diff --git a/tests/suite/test_transport_server_external_name.py b/tests/suite/test_transport_server_external_name.py index 51d90fe3e2..a676edf9a7 100644 --- a/tests/suite/test_transport_server_external_name.py +++ b/tests/suite/test_transport_server_external_name.py @@ -56,15 +56,16 @@ def ts_externalname_setup( ic_pod_name = get_first_pod_name(kube_apis.v1, ingress_controller_prerequisites.namespace) def fin(): - print("Clean up ExternalName Setup:") - delete_service(kube_apis.v1, external_svc, transport_server_setup.namespace) - delete_namespace(kube_apis.v1, external_ns) - replace_configmap_from_yaml( - kube_apis.v1, - config_map_name, - ingress_controller_prerequisites.namespace, - f"{DEPLOYMENTS}/common/nginx-config.yaml", - ) + if request.config.getoption("--skip-fixture-teardown") == "no": + print("Clean up ExternalName Setup:") + delete_service(kube_apis.v1, external_svc, transport_server_setup.namespace) + delete_namespace(kube_apis.v1, external_ns) + replace_configmap_from_yaml( + kube_apis.v1, + config_map_name, + ingress_controller_prerequisites.namespace, + f"{DEPLOYMENTS}/common/nginx-config.yaml", + ) request.addfinalizer(fin) @@ -119,7 +120,7 @@ def test_template_config( ts_externalname_setup.ic_pod_name, ingress_controller_prerequisites.namespace, ) - retry = +1 + retry = retry + 1 assert resolver_count == 2 # one for http and other for stream context assert ( diff --git a/tests/suite/test_transport_server_service_insight.py b/tests/suite/test_transport_server_service_insight.py new file mode 100644 index 0000000000..5b41f93a76 --- /dev/null +++ b/tests/suite/test_transport_server_service_insight.py @@ -0,0 +1,199 @@ +from unittest import mock + +import pytest +import requests +from settings import TEST_DATA +from suite.fixtures.fixtures import PublicEndpoint +from suite.utils.custom_resources_utils import create_ts_from_yaml, delete_ts +from suite.utils.resources_utils import ( + create_items_from_yaml, + create_secret_from_yaml, + delete_items_from_yaml, + delete_secret, + get_first_pod_name, + wait_before_test, + wait_until_all_pods_are_ready, +) +from suite.utils.ssl_utils import create_sni_session +from suite.utils.yaml_utils import get_first_host_from_yaml + + +class TransportServerTlsSetup: + """ + Encapsulate Transport Server details. + + Attributes: + public_endpoint (object): + ts_resource (dict): + name (str): + namespace (str): + ts_host (str): + """ + + def __init__(self, public_endpoint: PublicEndpoint, ts_resource, name, namespace, ts_host): + self.public_endpoint = public_endpoint + self.ts_resource = ts_resource + self.name = name + self.namespace = namespace + self.ts_host = ts_host + + +@pytest.fixture(scope="class") +def transport_server_tls_passthrough_setup( + request, kube_apis, test_namespace, ingress_controller_endpoint +) -> TransportServerTlsSetup: + """ + Prepare Transport Server Example. + + :param request: internal pytest fixture to parametrize this method + :param kube_apis: client apis + :param test_namespace: namespace for test resources + :param ingress_controller_endpoint: ip and port information + :return TransportServerTlsSetup: + """ + print("------------------------- Deploy Transport Server with tls passthrough -----------------------------------") + # deploy secure_app + secure_app_file = f"{TEST_DATA}/{request.param['example']}/standard/secure-app.yaml" + create_items_from_yaml(kube_apis, secure_app_file, test_namespace) + + # deploy transport server + transport_server_std_src = f"{TEST_DATA}/{request.param['example']}/standard/transport-server.yaml" + ts_resource = create_ts_from_yaml(kube_apis.custom_objects, transport_server_std_src, test_namespace) + ts_host = get_first_host_from_yaml(transport_server_std_src) + wait_until_all_pods_are_ready(kube_apis.v1, test_namespace) + + def fin(): + if request.config.getoption("--skip-fixture-teardown") == "no": + print("Clean up TransportServer and app:") + delete_ts(kube_apis.custom_objects, ts_resource, test_namespace) + delete_items_from_yaml(kube_apis, secure_app_file, test_namespace) + + request.addfinalizer(fin) + + return TransportServerTlsSetup( + ingress_controller_endpoint, + ts_resource, + ts_resource["metadata"]["name"], + test_namespace, + ts_host, + ) + + +@pytest.mark.ts +@pytest.mark.skip_for_nginx_oss +@pytest.mark.parametrize( + "crd_ingress_controller, transport_server_tls_passthrough_setup", + [ + ( + { + "type": "complete", + "extra_args": [ + "-enable-tls-passthrough=true", + "-enable-service-insight", + ], + }, + {"example": "transport-server-tls-passthrough"}, + ) + ], + indirect=True, +) +class TestTransportServerTSServiceInsightHTTP: + def test_ts_service_insight( + self, + kube_apis, + crd_ingress_controller, + transport_server_tls_passthrough_setup, + test_namespace, + ingress_controller_endpoint, + ): + """ + Test Service Insight Endpoint with Transport Server on HTTP port. + """ + session = create_sni_session() + req_url = ( + f"https://{transport_server_tls_passthrough_setup.public_endpoint.public_ip}:" + f"{transport_server_tls_passthrough_setup.public_endpoint.port_ssl}" + ) + wait_before_test() + resp = session.get( + req_url, + headers={"host": transport_server_tls_passthrough_setup.ts_host}, + verify=False, + ) + assert resp.status_code == 200 + assert f"hello from pod {get_first_pod_name(kube_apis.v1, test_namespace)}" in resp.text + + # Service Insight test + retry = 0 + resp = mock.Mock() + resp.json.return_value = {} + resp.status_code == 502 + + service_insight_endpoint = f"http://{ingress_controller_endpoint.public_ip}:{ingress_controller_endpoint.service_insight_port}/probe/ts/secure-app" + resp = requests.get(service_insight_endpoint) + assert resp.status_code == 200, f"Expected 200 code for /probe/ts/secure-app but got {resp.status_code}" + + while (resp.json() != {"Total": 1, "Up": 1, "Unhealthy": 0}) and retry < 5: + resp = requests.get(service_insight_endpoint) + wait_before_test() + retry = retry + 1 + assert resp.json() == {"Total": 1, "Up": 1, "Unhealthy": 0} + + +@pytest.fixture(scope="class") +def https_secret_setup(request, kube_apis, test_namespace): + print("------------------------- Deploy Secret -----------------------------------") + secret_name = create_secret_from_yaml(kube_apis.v1, "nginx-ingress", f"{TEST_DATA}/service-insight/secret.yaml") + + def fin(): + delete_secret(kube_apis.v1, secret_name, "nginx-ingress") + + request.addfinalizer(fin) + + +@pytest.mark.ts +@pytest.mark.skip_for_nginx_oss +@pytest.mark.parametrize( + "crd_ingress_controller, transport_server_tls_passthrough_setup", + [ + ( + { + "type": "complete", + "extra_args": [ + "-enable-tls-passthrough=true", + "-enable-service-insight", + "-service-insight-tls-secret=nginx-ingress/test-secret", + ], + }, + {"example": "transport-server-tls-passthrough"}, + ) + ], + indirect=True, +) +class TestTransportServerTSServiceInsightHTTPS: + def test_ts_service_insight_https( + self, + kube_apis, + https_secret_setup, + crd_ingress_controller, + transport_server_tls_passthrough_setup, + test_namespace, + ingress_controller_endpoint, + ): + """ + Test Service Insight Endpoint with Transport Server on HTTPS port. + """ + retry = 0 + resp = mock.Mock() + resp.json.return_value = {} + resp.status_code == 502 + + service_insight_endpoint = f"https://{ingress_controller_endpoint.public_ip}:{ingress_controller_endpoint.service_insight_port}/probe/ts/secure-app" + resp = requests.get(service_insight_endpoint, verify=False) + assert resp.status_code == 200, f"Expected 200 code for /probe/ts/secure-app but got {resp.status_code}" + + while (resp.json() != {"Total": 1, "Up": 1, "Unhealthy": 0}) and retry < 5: + resp = requests.get(service_insight_endpoint, verify=False) + wait_before_test() + retry = retry + 1 + assert resp.json() == {"Total": 1, "Up": 1, "Unhealthy": 0} diff --git a/tests/suite/test_transport_server_status.py b/tests/suite/test_transport_server_status.py index d8e7c34dcd..490809e5ed 100644 --- a/tests/suite/test_transport_server_status.py +++ b/tests/suite/test_transport_server_status.py @@ -1,7 +1,7 @@ import pytest from settings import TEST_DATA -from suite.utils.custom_resources_utils import patch_ts_from_yaml, read_ts -from suite.utils.resources_utils import wait_before_test +from suite.utils.custom_resources_utils import patch_gc_from_yaml, patch_ts_from_yaml, read_ts +from suite.utils.resources_utils import get_events_for_object, wait_before_test @pytest.mark.ts @@ -14,6 +14,7 @@ "extra_args": [ "-global-configuration=nginx-ingress/nginx-configuration", "-enable-leader-election=false", + "-enable-prometheus-metrics=true", ], }, {"example": "transport-server-status", "app_type": "simple"}, @@ -82,6 +83,7 @@ def test_status_warning( response["status"] and response["status"]["reason"] == "Rejected" and response["status"]["state"] == "Warning" + and "Listener invalid-listener doesn't exist" in response["status"]["message"] ) def test_status_invalid( @@ -111,4 +113,84 @@ def test_status_invalid( response["status"] and response["status"]["reason"] == "Rejected" and response["status"]["state"] == "Invalid" + and 'spec.listener.protocol: Invalid value: "invalid-protocol": must specify a valid protocol. ' + "Accepted values: HTTP,TCP,UDP" in response["status"]["message"] + ) + + def test_valid_status_invalid_udp_listener(self, kube_apis, crd_ingress_controller, transport_server_setup): + """ + Test TransportServer status with another listener invalid. + """ + global_config_file = ( + f"{TEST_DATA}/transport-server-status/standard/global-configuration-invalid-preceding-udp.yaml" + ) + gc_resource = patch_gc_from_yaml( + kube_apis.custom_objects, "nginx-configuration", global_config_file, "nginx-ingress" + ) + wait_before_test() + patch_src = f"{TEST_DATA}/transport-server-status/standard/transport-server.yaml" + patch_ts_from_yaml( + kube_apis.custom_objects, + transport_server_setup.name, + patch_src, + transport_server_setup.namespace, + ) + response = read_ts( + kube_apis.custom_objects, + transport_server_setup.namespace, + transport_server_setup.name, + ) + self.restore_ts(kube_apis, transport_server_setup) + assert ( + response["status"] + and response["status"]["reason"] == "AddedOrUpdated" + and response["status"]["state"] == "Valid" + ) + + gc_events = get_events_for_object(kube_apis.v1, "nginx-ingress", "nginx-configuration") + gc_event_latest = gc_events[-1] + print(gc_event_latest) + assert ( + gc_event_latest.reason == "AddedOrUpdatedWithError" + and gc_event_latest.type == "Warning" + and "GlobalConfiguration nginx-ingress/nginx-configuration is updated with errors: " + "spec.listeners[0].port: Forbidden: Listener dns-udp: port 9113 is forbidden" in gc_event_latest.message + ) + + def test_valid_status_invalid_tcp_listener(self, kube_apis, crd_ingress_controller, transport_server_setup): + """ + Test TransportServer status with listener using invalid port. + """ + global_config_file = f"{TEST_DATA}/transport-server-status/standard/global-configuration-invalid-tcp.yaml" + gc_resource = patch_gc_from_yaml( + kube_apis.custom_objects, "nginx-configuration", global_config_file, "nginx-ingress" + ) + wait_before_test() + patch_src = f"{TEST_DATA}/transport-server-status/standard/transport-server.yaml" + patch_ts_from_yaml( + kube_apis.custom_objects, + transport_server_setup.name, + patch_src, + transport_server_setup.namespace, + ) + response = read_ts( + kube_apis.custom_objects, + transport_server_setup.namespace, + transport_server_setup.name, + ) + self.restore_ts(kube_apis, transport_server_setup) + assert ( + response["status"] + and response["status"]["reason"] == "Rejected" + and response["status"]["state"] == "Warning" + and "Listener dns-tcp doesn't exist" in response["status"]["message"] + ) + gc_events = get_events_for_object(kube_apis.v1, "nginx-ingress", "nginx-configuration") + gc_event_latest = gc_events[-1] + print(gc_event_latest) + assert ( + gc_event_latest.reason == "AddedOrUpdatedWithError" + and gc_event_latest.type == "Warning" + and "GlobalConfiguration nginx-ingress/nginx-configuration is updated with errors: " + "spec.listeners[1].port: Forbidden: Listener dns-tcp: port 9113 is forbidden" in gc_event_latest.message ) diff --git a/tests/suite/test_transport_server_tcp_load_balance.py b/tests/suite/test_transport_server_tcp_load_balance.py index ec9f3ad6e6..675df61cbc 100644 --- a/tests/suite/test_transport_server_tcp_load_balance.py +++ b/tests/suite/test_transport_server_tcp_load_balance.py @@ -1,10 +1,21 @@ import re import socket +import ssl +from datetime import datetime import pytest from settings import TEST_DATA from suite.utils.custom_resources_utils import create_ts_from_yaml, delete_ts, patch_ts_from_yaml, read_ts -from suite.utils.resources_utils import get_ts_nginx_template_conf, scale_deployment, wait_before_test +from suite.utils.resources_utils import ( + create_secret_from_yaml, + delete_items_from_yaml, + get_reload_count, + get_ts_nginx_template_conf, + replace_secret, + scale_deployment, + wait_before_test, +) +from suite.utils.yaml_utils import get_secret_name_from_vs_or_ts_yaml @pytest.mark.ts @@ -18,6 +29,8 @@ "extra_args": [ "-global-configuration=nginx-ingress/nginx-configuration", "-enable-leader-election=false", + "-enable-prometheus-metrics", + "-ssl-dynamic-reload=false", ], }, {"example": "transport-server-tcp-load-balance"}, @@ -173,7 +186,7 @@ def test_tcp_request_load_balanced_multiple(self, kube_apis, crd_ingress_control response["status"] and response["status"]["reason"] == "Rejected" and response["status"]["state"] == "Warning" - and response["status"]["message"] == "Listener tcp-server is taken by another resource" + and response["status"]["message"] == "Listener tcp-server with host empty host is taken by another resource" ) # Step 3, remove the default TransportServer with the same port @@ -560,3 +573,136 @@ def test_tcp_failing_healthcheck_with_match( # Step 3 - restore self.restore_ts(kube_apis, transport_server_setup) + + def test_secure_tcp_request_load_balanced( + self, kube_apis, crd_ingress_controller, transport_server_setup, ingress_controller_prerequisites + ): + """ + Sends requests to a TLS enabled load balanced TCP service. + """ + src_sec_yaml = f"{TEST_DATA}/transport-server-tcp-load-balance/tcp-tls-secret.yaml" + src_new_sec_yaml = f"{TEST_DATA}/transport-server-tcp-load-balance/new-tls-secret.yaml" + create_secret_from_yaml(kube_apis.v1, transport_server_setup.namespace, src_sec_yaml) + patch_src = f"{TEST_DATA}/transport-server-tcp-load-balance/transport-server-tls.yaml" + patch_ts_from_yaml( + kube_apis.custom_objects, + transport_server_setup.name, + patch_src, + transport_server_setup.namespace, + ) + wait_before_test() + + result_conf = get_ts_nginx_template_conf( + kube_apis.v1, + transport_server_setup.namespace, + transport_server_setup.name, + transport_server_setup.ingress_pod_name, + ingress_controller_prerequisites.namespace, + ) + + port = transport_server_setup.public_endpoint.tcp_server_port + host = transport_server_setup.public_endpoint.public_ip + + sec_name = get_secret_name_from_vs_or_ts_yaml(patch_src) + cert_name = f"{transport_server_setup.namespace}-{sec_name}" + + assert f"listen 3333 ssl;" in result_conf + assert f"ssl_certificate /etc/nginx/secrets/{cert_name};" in result_conf + assert f"ssl_certificate_key /etc/nginx/secrets/{cert_name};" in result_conf + + print(f"sending tcp requests to: {host}:{port}") + + host = host.strip("[]") + with socket.create_connection((host, port)) as sock: + ctx = ssl.SSLContext() + ctx.options |= ssl.OP_NO_TLSv1 | ssl.OP_NO_TLSv1_1 # only secure TLSv1_2+ is allowed + ssock = ctx.wrap_socket(sock) + print(ssock.version()) + ssock.sendall(b"connect") + response = ssock.recv(4096) + endpoint = response.decode() + print(f"Connected securely to: {endpoint}") + + # with -ssl-dynamic-reload=false, we expect + # replacing a secret to trigger a reload + count_before_replace = get_reload_count(transport_server_setup.metrics_url) + print(f"replacing: {sec_name} in {transport_server_setup.namespace}") + replace_secret(kube_apis.v1, sec_name, transport_server_setup.namespace, src_new_sec_yaml) + wait_before_test() + print(f"waited to {datetime.now().strftime('%m/%d/%Y, %H:%M:%S')}") + count_after = get_reload_count(transport_server_setup.metrics_url) + reloads = count_after - count_before_replace + expected_reloads = 1 + assert reloads == expected_reloads, f"expected {expected_reloads} reloads, got {reloads}" + + self.restore_ts(kube_apis, transport_server_setup) + delete_items_from_yaml(kube_apis, src_sec_yaml, transport_server_setup.namespace) + + +@pytest.mark.ts +@pytest.mark.skip_for_loadbalancer +@pytest.mark.parametrize( + "crd_ingress_controller, transport_server_setup", + [ + ( + { + "type": "complete", + "extra_args": [ + "-global-configuration=nginx-ingress/nginx-configuration", + "-enable-leader-election=false", + "-enable-prometheus-metrics", + "-log-level=debug", + ], + }, + {"example": "transport-server-tcp-load-balance"}, + ) + ], + indirect=True, +) +class TestTransportServerTcpLoadBalanceDynamicReload: + def test_dynamic_reload( + self, kube_apis, crd_ingress_controller, transport_server_setup, ingress_controller_prerequisites + ): + """ + Updates a secret used by the transport server and verifies that NGINX is not reloaded. + """ + src_sec_yaml = f"{TEST_DATA}/transport-server-tcp-load-balance/tcp-tls-secret.yaml" + src_new_sec_yaml = f"{TEST_DATA}/transport-server-tcp-load-balance/new-tls-secret.yaml" + create_secret_from_yaml(kube_apis.v1, transport_server_setup.namespace, src_sec_yaml) + patch_src = f"{TEST_DATA}/transport-server-tcp-load-balance/transport-server-tls.yaml" + patch_ts_from_yaml( + kube_apis.custom_objects, + transport_server_setup.name, + patch_src, + transport_server_setup.namespace, + ) + wait_before_test() + + result_conf = get_ts_nginx_template_conf( + kube_apis.v1, + transport_server_setup.namespace, + transport_server_setup.name, + transport_server_setup.ingress_pod_name, + ingress_controller_prerequisites.namespace, + ) + + sec_name = get_secret_name_from_vs_or_ts_yaml(patch_src) + cert_name = f"{transport_server_setup.namespace}-{sec_name}" + + assert f"listen 3333 ssl;" in result_conf + assert f"ssl_certificate $secret_dir_path/{cert_name};" in result_conf + assert f"ssl_certificate_key $secret_dir_path/{cert_name};" in result_conf + + # for Plus with -ssl-dynamic-reload=true, we expect + # replacing a secret not to trigger a reload + count_before_replace = get_reload_count(transport_server_setup.metrics_url) + print(f"replacing: {sec_name} in {transport_server_setup.namespace}") + replace_secret(kube_apis.v1, sec_name, transport_server_setup.namespace, src_new_sec_yaml) + wait_before_test() + print(f"waited to {datetime.now().strftime('%m/%d/%Y, %H:%M:%S')}") + count_after = get_reload_count(transport_server_setup.metrics_url) + reloads = count_after - count_before_replace + expected_reloads = 0 + assert reloads == expected_reloads, f"expected {expected_reloads} reloads, got {reloads}" + + delete_items_from_yaml(kube_apis, src_sec_yaml, transport_server_setup.namespace) diff --git a/tests/suite/test_transport_server_udp_load_balance.py b/tests/suite/test_transport_server_udp_load_balance.py index 841fc43bf9..86fa8596ec 100644 --- a/tests/suite/test_transport_server_udp_load_balance.py +++ b/tests/suite/test_transport_server_udp_load_balance.py @@ -204,7 +204,7 @@ def test_udp_request_load_balanced_multiple(self, kube_apis, crd_ingress_control response["status"] and response["status"]["reason"] == "Rejected" and response["status"]["state"] == "Warning" - and response["status"]["message"] == "Listener udp-server is taken by another resource" + and response["status"]["message"] == "Listener udp-server with host empty host is taken by another resource" ) # Step 3, remove the default TransportServer with the same port diff --git a/tests/suite/test_transport_server_with_host.py b/tests/suite/test_transport_server_with_host.py new file mode 100644 index 0000000000..ca65df5556 --- /dev/null +++ b/tests/suite/test_transport_server_with_host.py @@ -0,0 +1,80 @@ +import pytest +from settings import TEST_DATA +from suite.utils.custom_resources_utils import patch_ts_from_yaml, read_custom_resource +from suite.utils.resources_utils import ( + create_secret_from_yaml, + get_events_for_object, + get_ts_nginx_template_conf, + wait_before_test, +) + + +@pytest.mark.ts +@pytest.mark.parametrize( + "crd_ingress_controller, transport_server_setup", + [ + ( + { + "type": "complete", + "extra_args": [ + "-global-configuration=nginx-ingress/nginx-configuration", + "-enable-leader-election=false", + "-enable-snippets", + ], + }, + {"example": "transport-server-status"}, + ) + ], + indirect=True, +) +class TestTransportServerWithHost: + def test_ts_with_host( + self, kube_apis, crd_ingress_controller, transport_server_setup, ingress_controller_prerequisites + ): + """ + Test TransportServer with Host field without TLS Passthrough + """ + + # TS with Host needs a secret + secret_src = f"{TEST_DATA}/transport-server-with-host/cafe-secret.yaml" + create_secret_from_yaml(kube_apis.v1, transport_server_setup.namespace, secret_src) + + # Update the status TS from the example with one which uses a Host + patch_src = f"{TEST_DATA}/transport-server-with-host/transport-server-with-host.yaml" + patch_ts_from_yaml( + kube_apis.custom_objects, + transport_server_setup.name, + patch_src, + transport_server_setup.namespace, + ) + wait_before_test() + + conf = get_ts_nginx_template_conf( + kube_apis.v1, + transport_server_setup.namespace, + transport_server_setup.name, + transport_server_setup.ingress_pod_name, + ingress_controller_prerequisites.namespace, + ) + print(conf) + + std_src = f"{TEST_DATA}/transport-server-status/standard/transport-server.yaml" + patch_ts_from_yaml( + kube_apis.custom_objects, + transport_server_setup.name, + std_src, + transport_server_setup.namespace, + ) + + conf_lines = [line.strip() for line in conf.split("\n")] + assert 'server_name "cafe.example.com";' in conf_lines + + ts_events = get_events_for_object(kube_apis.v1, transport_server_setup.namespace, transport_server_setup.name) + ts_latest_event = ts_events[-1] + print(ts_latest_event) + assert ts_latest_event.reason == "AddedOrUpdated" and ts_latest_event.type == "Normal" + + ts_info = read_custom_resource( + kube_apis.custom_objects, transport_server_setup.namespace, "transportservers", transport_server_setup.name + ) + assert ts_info["status"] and ts_info["status"]["state"] == "Valid" diff --git a/tests/suite/test_ts_tls_passthrough.py b/tests/suite/test_ts_tls_passthrough.py index 59ff97dea4..14dd36fc67 100644 --- a/tests/suite/test_ts_tls_passthrough.py +++ b/tests/suite/test_ts_tls_passthrough.py @@ -24,14 +24,18 @@ class TransportServerTlsSetup: Attributes: public_endpoint (object): + tls_passthrough_port (int): ts_resource (dict): name (str): namespace (str): ts_host (str): """ - def __init__(self, public_endpoint: PublicEndpoint, ts_resource, name, namespace, ts_host): + def __init__( + self, public_endpoint: PublicEndpoint, tls_passthrough_port: int, ts_resource, name, namespace, ts_host + ): self.public_endpoint = public_endpoint + self.tls_passthrough_port = tls_passthrough_port self.ts_resource = ts_resource self.name = name self.namespace = namespace @@ -63,14 +67,16 @@ def transport_server_tls_passthrough_setup( wait_until_all_pods_are_ready(kube_apis.v1, test_namespace) def fin(): - print("Clean up TransportServer and app:") - delete_ts(kube_apis.custom_objects, ts_resource, test_namespace) - delete_items_from_yaml(kube_apis, secure_app_file, test_namespace) + if request.config.getoption("--skip-fixture-teardown") == "no": + print("Clean up TransportServer and app:") + delete_ts(kube_apis.custom_objects, ts_resource, test_namespace) + delete_items_from_yaml(kube_apis, secure_app_file, test_namespace) request.addfinalizer(fin) return TransportServerTlsSetup( ingress_controller_endpoint, + request.param["tls_passthrough_port"], ts_resource, ts_resource["metadata"]["name"], test_namespace, @@ -90,10 +96,23 @@ def fin(): "-enable-tls-passthrough=true", ], }, - {"example": "transport-server-tls-passthrough"}, - ) + {"example": "transport-server-tls-passthrough", "tls_passthrough_port": 443}, + ), + ( + { + "type": "tls-passthrough-custom-port", + # set publicEndpoint.port_ssl to 8443 when checking connection to public endpoint and in all tests + "extra_args": [ + "-enable-leader-election=false", + "-enable-tls-passthrough=true", + "-tls-passthrough-port=8443", + ], + }, + {"example": "transport-server-tls-passthrough", "tls_passthrough_port": 8443}, + ), ], indirect=True, + ids=["tls_passthrough_with_default_port", "tls_passthrough_with_custom_port"], ) class TestTransportServerTlsPassthrough: def restore_ts(self, kube_apis, transport_server_tls_passthrough_setup) -> None: @@ -153,8 +172,8 @@ def test_tls_passthrough_proxy_protocol_config( ) wait_before_test(1) config = get_nginx_template_conf(kube_apis.v1, ingress_controller_prerequisites.namespace) - assert "listen 443 proxy_protocol;" in config - assert "listen [::]:443 proxy_protocol;" in config + assert f"listen {transport_server_tls_passthrough_setup.tls_passthrough_port} proxy_protocol;" in config + assert f"listen [::]:{transport_server_tls_passthrough_setup.tls_passthrough_port} proxy_protocol;" in config std_cm_src = f"{DEPLOYMENTS}/common/nginx-config.yaml" replace_configmap_from_yaml( kube_apis.v1, diff --git a/tests/suite/test_udp_http_listeners_together.py b/tests/suite/test_udp_http_listeners_together.py new file mode 100644 index 0000000000..ca99e3fbd2 --- /dev/null +++ b/tests/suite/test_udp_http_listeners_together.py @@ -0,0 +1,104 @@ +import time + +import pytest +from settings import TEST_DATA +from suite.utils.custom_resources_utils import ( + create_ts_from_yaml, + delete_ts, + patch_gc_from_yaml, + read_custom_resource, + read_ts, +) +from suite.utils.resources_utils import get_first_pod_name, get_ts_nginx_template_conf, wait_before_test +from suite.utils.vs_vsr_resources_utils import get_vs_nginx_template_conf, patch_virtual_server_from_yaml + + +@pytest.mark.vs +@pytest.mark.ts +@pytest.mark.parametrize( + "crd_ingress_controller, virtual_server_setup, transport_server_setup", + [ + ( + { + "type": "complete", + "extra_args": [ + "-enable-custom-resources", + "-global-configuration=nginx-ingress/nginx-configuration", + ], + }, + {"example": "virtual-server-status", "app_type": "simple"}, + {"example": "transport-server-status", "app_type": "simple"}, + ) + ], + indirect=True, +) +class TestUDPandHTTPListenersTogether: + def test_udp_and_http_listeners_together( + self, + kube_apis, + ingress_controller_prerequisites, + crd_ingress_controller, + virtual_server_setup, + transport_server_setup, + ): + + wait_before_test() + existing_ts = read_ts(kube_apis.custom_objects, transport_server_setup.namespace, transport_server_setup.name) + delete_ts(kube_apis.custom_objects, existing_ts, transport_server_setup.namespace) + + global_config_file = f"{TEST_DATA}/udp-http-listeners-together/global-configuration.yaml" + transport_server_file = f"{TEST_DATA}/udp-http-listeners-together/transport-server.yaml" + virtual_server_file = f"{TEST_DATA}/udp-http-listeners-together/virtual-server.yaml" + gc_resource_name = "nginx-configuration" + gc_namespace = "nginx-ingress" + + patch_gc_from_yaml(kube_apis.custom_objects, gc_resource_name, global_config_file, gc_namespace) + create_ts_from_yaml(kube_apis.custom_objects, transport_server_file, transport_server_setup.namespace) + patch_virtual_server_from_yaml( + kube_apis.custom_objects, "virtual-server-status", virtual_server_file, virtual_server_setup.namespace + ) + wait_before_test() + + ic_pod_name = get_first_pod_name(kube_apis.v1, ingress_controller_prerequisites.namespace) + ts_config = get_ts_nginx_template_conf( + kube_apis.v1, + transport_server_setup.namespace, + transport_server_setup.name, + ic_pod_name, + ingress_controller_prerequisites.namespace, + ) + vs_config = get_vs_nginx_template_conf( + kube_apis.v1, + virtual_server_setup.namespace, + virtual_server_setup.vs_name, + ic_pod_name, + ingress_controller_prerequisites.namespace, + ) + assert "listen 5454;" in vs_config + assert "listen 5454 udp;" in ts_config + + for _ in range(30): + transport_server_response = read_custom_resource( + kube_apis.custom_objects, + transport_server_setup.namespace, + "transportservers", + "transport-server", + ) + if "status" in transport_server_response and transport_server_response["status"]["state"] == "Valid": + break + time.sleep(1) + else: + pytest.fail("TransportServer status did not become 'Valid' within the timeout period") + + for _ in range(30): + virtual_server_response = read_custom_resource( + kube_apis.custom_objects, + virtual_server_setup.namespace, + "virtualservers", + "virtual-server-status", + ) + if "status" in virtual_server_response and virtual_server_response["status"]["state"] == "Valid": + break + time.sleep(1) + else: + pytest.fail("VirtualServer status did not become 'Valid' within the timeout period") diff --git a/tests/suite/test_upgrade_resources.py b/tests/suite/test_upgrade_resources.py new file mode 100644 index 0000000000..ac2c4cecf3 --- /dev/null +++ b/tests/suite/test_upgrade_resources.py @@ -0,0 +1,126 @@ +import os +import tempfile + +import pytest +import yaml +from settings import TEST_DATA +from suite.utils.custom_resources_utils import create_resource_from_manifest +from suite.utils.resources_utils import create_ingress, create_items_from_yaml, create_namespace, delete_namespace +from suite.utils.vs_vsr_resources_utils import create_virtual_server + +tcp_deployment = f"{TEST_DATA}/upgrade-test-resources/tcp-deployment.yaml" +deployment = f"{TEST_DATA}/upgrade-test-resources/deployment.yaml" +service = f"{TEST_DATA}/upgrade-test-resources/service.yaml" +ns = f"{TEST_DATA}/upgrade-test-resources/ns.yaml" +ingress = f"{TEST_DATA}/upgrade-test-resources/ingress.yaml" +vs = f"{TEST_DATA}/upgrade-test-resources/virtual-server.yaml" +ts = f"{TEST_DATA}/upgrade-test-resources/transport-server.yaml" +secret = f"{TEST_DATA}/upgrade-test-resources/secret.yaml" + +""" +Test class below only deployes resources for upgrade testing, NIC deployment should be done manually via helm. +Run `make upgrade-resources PYTEST_ARGS="create OR delete"` to create OR delete resources. +""" + + +@pytest.mark.upgrade +class TestUpgrade: + @pytest.mark.create + def test_create(self, request, kube_apis): + count = int(request.config.getoption("--num")) + + for i in range(1, count + 1): + with open(ns) as f: + doc = yaml.safe_load(f) + doc["metadata"]["name"] = f"ns-{i}" + with tempfile.NamedTemporaryFile(mode="w+", suffix=".yml", delete=False) as temp: + temp.write(yaml.safe_dump(doc) + "---\n") + namespace = create_namespace(kube_apis.v1, doc) + os.remove(temp.name) + + with open(deployment) as f: + doc = yaml.safe_load(f) + doc["metadata"]["name"] = f"backend-{i}" + doc["spec"]["selector"]["matchLabels"]["app"] = f"backend-{i}" + doc["spec"]["template"]["metadata"]["labels"]["app"] = f"backend-{i}" + doc["metadata"]["name"] = f"backend-{i}" + with tempfile.NamedTemporaryFile(mode="w+", suffix=".yml", delete=False) as temp: + temp.write(yaml.safe_dump(doc) + "---\n") + create_items_from_yaml(kube_apis, temp.name, namespace) + os.remove(temp.name) + + with open(service) as f: + doc = yaml.safe_load(f) + doc["metadata"]["name"] = f"backend-svc-{i}" + doc["spec"]["selector"]["app"] = f"backend-{i}" + with tempfile.NamedTemporaryFile(mode="w+", suffix=".yml", delete=False) as temp: + temp.write(yaml.safe_dump(doc) + "---\n") + create_items_from_yaml(kube_apis, temp.name, namespace) + os.remove(temp.name) + + with open(secret) as f: + doc = yaml.safe_load(f) + doc["metadata"]["name"] = f"secret-{i}" + with tempfile.NamedTemporaryFile(mode="w+", suffix=".yml", delete=False) as temp: + temp.write(yaml.safe_dump(doc) + "---\n") + create_items_from_yaml(kube_apis, temp.name, namespace) + os.remove(temp.name) + + # VirtualServer + with open(vs) as f: + doc = yaml.safe_load(f) + doc["metadata"]["name"] = f"vs-{i}" + doc["spec"]["host"] = f"vs-{i}.example.com" + doc["spec"]["tls"]["secret"] = f"secret-{i}" + doc["spec"]["upstreams"][0]["name"] = f"backend-{i}" + doc["spec"]["upstreams"][0]["service"] = f"backend-svc-{i}" + doc["spec"]["routes"][0]["action"]["pass"] = f"backend-{i}" + create_virtual_server(kube_apis.custom_objects, doc, namespace) + + # Ingress + with open(ingress) as f: + doc = yaml.safe_load(f) + doc["metadata"]["name"] = f"ingress-{i}" + doc["spec"]["tls"][0]["hosts"][0] = f"ingress-{i}.example.com" + doc["spec"]["tls"][0]["secretName"] = f"secret-{i}" + doc["spec"]["rules"][0]["host"] = f"ingress-{i}.example.com" + doc["spec"]["rules"][0]["http"]["paths"][0]["path"] = f"/backend-{i}" + doc["spec"]["rules"][0]["http"]["paths"][0]["backend"]["service"]["name"] = f"backend-svc-{i}" + create_ingress(kube_apis.networking_v1, namespace, doc) + + # TransportServer + with open(tcp_deployment) as f: + doc = yaml.safe_load(f) + doc["metadata"]["name"] = f"tcp-{i}" + doc["spec"]["selector"]["matchLabels"]["app"] = f"tcp-{i}" + doc["spec"]["template"]["metadata"]["labels"]["app"] = f"tcp-{i}" + with tempfile.NamedTemporaryFile(mode="w+", suffix=".yml", delete=False) as temp: + temp.write(yaml.safe_dump(doc) + "---\n") + create_items_from_yaml(kube_apis, temp.name, namespace) + os.remove(temp.name) + + with open(service) as f: + doc = yaml.safe_load(f) + doc["metadata"]["name"] = f"tcp-svc-{i}" + doc["spec"]["selector"]["app"] = f"tcp-{i}" + with tempfile.NamedTemporaryFile(mode="w+", suffix=".yml", delete=False) as temp: + temp.write(yaml.safe_dump(doc) + "---\n") + create_items_from_yaml(kube_apis, temp.name, namespace) + os.remove(temp.name) + + with open(ts) as f: + doc = yaml.safe_load(f) + doc["metadata"]["name"] = f"ts-{i}" + doc["spec"]["listener"]["name"] = "dns-tcp" + doc["spec"]["upstreams"][0]["name"] = f"tcp-{i}" + doc["spec"]["upstreams"][0]["service"] = f"tcp-svc-{i}" + doc["spec"]["upstreams"][0]["port"] = 5353 + doc["spec"]["action"]["pass"] = f"tcp-{i}" + create_resource_from_manifest(kube_apis.custom_objects, doc, namespace, "transportservers") + + @pytest.mark.delete + def test_delete(self, request, kube_apis): + count = int(request.config.getoption("--num")) + # delete namespaces + for i in range(1, count + 1): + delete_namespace(kube_apis.v1, f"ns-{i}") diff --git a/tests/suite/test_use_cluster_ip.py b/tests/suite/test_use_cluster_ip.py new file mode 100644 index 0000000000..c0bf886623 --- /dev/null +++ b/tests/suite/test_use_cluster_ip.py @@ -0,0 +1,101 @@ +import pytest +from settings import TEST_DATA +from suite.utils.resources_utils import ( + create_example_app, + create_ingress_from_yaml, + delete_common_app, + delete_items_from_yaml, + ensure_connection_to_public_endpoint, + get_reload_count, + scale_deployment, + wait_before_test, +) +from suite.utils.yaml_utils import get_first_ingress_host_from_yaml + +from tests.suite.utils.custom_assertions import assert_pods_scaled_to_count + + +class UseClusterIPSetup: + def __init__(self, ingress_host, metrics_url): + self.ingress_host = ingress_host + self.metrics_url = metrics_url + + +@pytest.fixture(scope="class") +def use_cluster_ip_setup( + request, + kube_apis, + ingress_controller_prerequisites, + ingress_controller_endpoint, + ingress_controller, + test_namespace, +) -> UseClusterIPSetup: + print("------------------------- Deploy use-cluster-ip setup -----------------------------------") + + test_data_path = f"{TEST_DATA}/use-cluster-ip/ingress" + metrics_url = f"http://{ingress_controller_endpoint.public_ip}:{ingress_controller_endpoint.metrics_port}/metrics" + + ingress_path = f"{test_data_path}/{request.param}/use-cluster-ip-ingress.yaml" + create_ingress_from_yaml(kube_apis.networking_v1, test_namespace, ingress_path) + if request.param == "mergeable": + create_ingress_from_yaml( + kube_apis.networking_v1, test_namespace, f"{test_data_path}/{request.param}/minion-ingress.yaml" + ) + create_example_app(kube_apis, "simple", test_namespace) + + wait_before_test(1) + + ingress_host = get_first_ingress_host_from_yaml(ingress_path) + + ensure_connection_to_public_endpoint( + ingress_controller_endpoint.public_ip, ingress_controller_endpoint.port, ingress_controller_endpoint.port_ssl + ) + + def fin(): + if request.config.getoption("--skip-fixture-teardown") == "no": + print("Clean up use-cluster-ip setup") + delete_items_from_yaml(kube_apis, ingress_path, test_namespace) + delete_common_app(kube_apis, "simple", test_namespace) + if request.param == "mergeable": + delete_items_from_yaml( + kube_apis, + f"{test_data_path}/{request.param}/minion-ingress.yaml", + test_namespace, + ) + + request.addfinalizer(fin) + + return UseClusterIPSetup( + ingress_host, + metrics_url, + ) + + +@pytest.mark.ingresses +@pytest.mark.parametrize( + "ingress_controller, use_cluster_ip_setup", + [ + pytest.param({"extra_args": ["-enable-prometheus-metrics"]}, "standard"), + pytest.param({"extra_args": ["-enable-prometheus-metrics"]}, "mergeable"), + ], + indirect=True, +) +class TestIngressUseClusterIPReloads: + def test_ingress_use_cluster_ip_reloads( + self, kube_apis, ingress_controller_endpoint, test_namespace, use_cluster_ip_setup + ): + print("Step 1: get initial reload count") + initial_reload_count = get_reload_count(use_cluster_ip_setup.metrics_url) + + print("Step 2: scale the deployment down") + scale_deployment(kube_apis.v1, kube_apis.apps_v1_api, "backend1", test_namespace, 1) + assert_pods_scaled_to_count(kube_apis.apps_v1_api, kube_apis.v1, "backend1", test_namespace, 1) + + print("Step 3: scale the deployment up") + scale_deployment(kube_apis.v1, kube_apis.apps_v1_api, "backend1", test_namespace, 4) + assert_pods_scaled_to_count(kube_apis.apps_v1_api, kube_apis.v1, "backend1", test_namespace, 4) + + print("Step 4: get reload count after scaling") + reload_count_after_scaling = get_reload_count(use_cluster_ip_setup.metrics_url) + + assert reload_count_after_scaling == initial_reload_count, "Expected: no new reloads" diff --git a/tests/suite/test_v_s_route.py b/tests/suite/test_v_s_route.py index 4b28ea0e26..d5684df622 100644 --- a/tests/suite/test_v_s_route.py +++ b/tests/suite/test_v_s_route.py @@ -49,6 +49,7 @@ def assert_locations_not_in_config(config, paths): @pytest.mark.smoke @pytest.mark.vsr +@pytest.mark.vsr_basic @pytest.mark.parametrize( "crd_ingress_controller, v_s_route_setup", [({"type": "complete", "extra_args": [f"-enable-custom-resources"]}, {"example": "virtual-server-route"})], @@ -267,6 +268,7 @@ def test_responses_and_events_in_flow( @pytest.mark.vsr +@pytest.mark.vsr_basic @pytest.mark.parametrize( "crd_ingress_controller, v_s_route_setup", [({"type": "complete", "extra_args": [f"-enable-custom-resources"]}, {"example": "virtual-server-route"})], @@ -369,6 +371,7 @@ def test_openapi_validation_flow( @pytest.mark.vsr +@pytest.mark.vsr_basic @pytest.mark.flaky(max_runs=3) @pytest.mark.parametrize( "crd_ingress_controller, v_s_route_setup", diff --git a/tests/suite/test_v_s_route_advanced_routing.py b/tests/suite/test_v_s_route_advanced_routing.py index b9a34c26cf..b656ede340 100644 --- a/tests/suite/test_v_s_route_advanced_routing.py +++ b/tests/suite/test_v_s_route_advanced_routing.py @@ -1,3 +1,5 @@ +from unittest import mock + import pytest import requests from settings import TEST_DATA @@ -17,6 +19,10 @@ ) from suite.utils.yaml_utils import get_first_host_from_yaml, get_paths_from_vsr_yaml, get_route_namespace_from_vs_yaml +resp_1 = mock.Mock() +resp_2 = mock.Mock() +resp_3 = mock.Mock() + def execute_assertions(resp_1, resp_2, resp_3): assert resp_1.status_code == 200 @@ -91,8 +97,9 @@ def vsr_adv_routing_setup( wait_until_all_pods_are_ready(kube_apis.v1, ns_1) def fin(): - print("Delete test namespace") - delete_namespace(kube_apis.v1, ns_1) + if request.config.getoption("--skip-fixture-teardown") == "no": + print("Delete test namespace") + delete_namespace(kube_apis.v1, ns_1) request.addfinalizer(fin) @@ -100,6 +107,7 @@ def fin(): @pytest.mark.vsr +@pytest.mark.vsr_routing @pytest.mark.parametrize( "crd_ingress_controller, vsr_adv_routing_setup", [ @@ -113,18 +121,22 @@ def fin(): class TestVSRAdvancedRouting: def test_flow_with_header(self, kube_apis, crd_ingress_controller, vsr_adv_routing_setup): ensure_responses_from_backends(vsr_adv_routing_setup.backends_url, vsr_adv_routing_setup.vs_host) - - resp_1 = requests.get( - vsr_adv_routing_setup.backends_url, headers={"host": vsr_adv_routing_setup.vs_host, "x-version": "future"} - ) - resp_2 = requests.get( - vsr_adv_routing_setup.backends_url, - headers={"host": vsr_adv_routing_setup.vs_host, "x-version": "deprecated"}, - ) - resp_3 = requests.get( - vsr_adv_routing_setup.backends_url, - headers={"host": vsr_adv_routing_setup.vs_host, "x-version-invalid": "deprecated"}, - ) + wait_before_test() + global resp_1, resp_2, resp_3 + resp_1.status_code = resp_2.status_code = resp_3.status_code = 502 + while resp_1.status_code == 502 and resp_2.status_code == 502 and resp_3.status_code == 502: + resp_1 = requests.get( + vsr_adv_routing_setup.backends_url, + headers={"host": vsr_adv_routing_setup.vs_host, "x-version": "future"}, + ) + resp_2 = requests.get( + vsr_adv_routing_setup.backends_url, + headers={"host": vsr_adv_routing_setup.vs_host, "x-version": "deprecated"}, + ) + resp_3 = requests.get( + vsr_adv_routing_setup.backends_url, + headers={"host": vsr_adv_routing_setup.vs_host, "x-version-invalid": "deprecated"}, + ) execute_assertions(resp_1, resp_2, resp_3) def test_flow_with_argument(self, kube_apis, crd_ingress_controller, vsr_adv_routing_setup): @@ -134,17 +146,20 @@ def test_flow_with_argument(self, kube_apis, crd_ingress_controller, vsr_adv_rou f"{TEST_DATA}/virtual-server-route-advanced-routing/virtual-server-route-argument.yaml", vsr_adv_routing_setup.namespace, ) - wait_before_test(1) - - resp_1 = requests.get( - vsr_adv_routing_setup.backends_url + "?arg1=v1", headers={"host": vsr_adv_routing_setup.vs_host} - ) - resp_2 = requests.get( - vsr_adv_routing_setup.backends_url + "?arg1=v2", headers={"host": vsr_adv_routing_setup.vs_host} - ) - resp_3 = requests.get( - vsr_adv_routing_setup.backends_url + "?argument1=v1", headers={"host": vsr_adv_routing_setup.vs_host} - ) + ensure_response_from_backend(vsr_adv_routing_setup.backends_url, vsr_adv_routing_setup.vs_host, check404=True) + wait_before_test() + global resp_1, resp_2, resp_3 + resp_1.status_code = resp_2.status_code = resp_3.status_code = 502 + while resp_1.status_code == 502 and resp_2.status_code == 502 and resp_3.status_code == 502: + resp_1 = requests.get( + vsr_adv_routing_setup.backends_url + "?arg1=v1", headers={"host": vsr_adv_routing_setup.vs_host} + ) + resp_2 = requests.get( + vsr_adv_routing_setup.backends_url + "?arg1=v2", headers={"host": vsr_adv_routing_setup.vs_host} + ) + resp_3 = requests.get( + vsr_adv_routing_setup.backends_url + "?argument1=v1", headers={"host": vsr_adv_routing_setup.vs_host} + ) execute_assertions(resp_1, resp_2, resp_3) def test_flow_with_cookie(self, kube_apis, crd_ingress_controller, vsr_adv_routing_setup): @@ -154,21 +169,26 @@ def test_flow_with_cookie(self, kube_apis, crd_ingress_controller, vsr_adv_routi f"{TEST_DATA}/virtual-server-route-advanced-routing/virtual-server-route-cookie.yaml", vsr_adv_routing_setup.namespace, ) - wait_before_test(1) - - resp_1 = requests.get( - vsr_adv_routing_setup.backends_url, - headers={"host": vsr_adv_routing_setup.vs_host}, - cookies={"user": "some"}, - ) - resp_2 = requests.get( - vsr_adv_routing_setup.backends_url, headers={"host": vsr_adv_routing_setup.vs_host}, cookies={"user": "bad"} - ) - resp_3 = requests.get( - vsr_adv_routing_setup.backends_url, - headers={"host": vsr_adv_routing_setup.vs_host}, - cookies={"user": "anonymous"}, - ) + ensure_response_from_backend(vsr_adv_routing_setup.backends_url, vsr_adv_routing_setup.vs_host, check404=True) + wait_before_test() + global resp_1, resp_2, resp_3 + resp_1.status_code = resp_2.status_code = resp_3.status_code = 502 + while resp_1.status_code == 502 and resp_2.status_code == 502 and resp_3.status_code == 502: + resp_1 = requests.get( + vsr_adv_routing_setup.backends_url, + headers={"host": vsr_adv_routing_setup.vs_host}, + cookies={"user": "some"}, + ) + resp_2 = requests.get( + vsr_adv_routing_setup.backends_url, + headers={"host": vsr_adv_routing_setup.vs_host}, + cookies={"user": "bad"}, + ) + resp_3 = requests.get( + vsr_adv_routing_setup.backends_url, + headers={"host": vsr_adv_routing_setup.vs_host}, + cookies={"user": "anonymous"}, + ) execute_assertions(resp_1, resp_2, resp_3) def test_flow_with_variable(self, kube_apis, crd_ingress_controller, vsr_adv_routing_setup): @@ -178,11 +198,14 @@ def test_flow_with_variable(self, kube_apis, crd_ingress_controller, vsr_adv_rou f"{TEST_DATA}/virtual-server-route-advanced-routing/virtual-server-route-variable.yaml", vsr_adv_routing_setup.namespace, ) - wait_before_test(1) - - resp_1 = requests.get(vsr_adv_routing_setup.backends_url, headers={"host": vsr_adv_routing_setup.vs_host}) - resp_2 = requests.post(vsr_adv_routing_setup.backends_url, headers={"host": vsr_adv_routing_setup.vs_host}) - resp_3 = requests.put(vsr_adv_routing_setup.backends_url, headers={"host": vsr_adv_routing_setup.vs_host}) + ensure_response_from_backend(vsr_adv_routing_setup.backends_url, vsr_adv_routing_setup.vs_host, check404=True) + wait_before_test() + global resp_1, resp_2, resp_3 + resp_1.status_code = resp_2.status_code = resp_3.status_code = 502 + while resp_1.status_code == 502 and resp_2.status_code == 502 and resp_3.status_code == 502: + resp_1 = requests.get(vsr_adv_routing_setup.backends_url, headers={"host": vsr_adv_routing_setup.vs_host}) + resp_2 = requests.post(vsr_adv_routing_setup.backends_url, headers={"host": vsr_adv_routing_setup.vs_host}) + resp_3 = requests.put(vsr_adv_routing_setup.backends_url, headers={"host": vsr_adv_routing_setup.vs_host}) execute_assertions(resp_1, resp_2, resp_3) def test_flow_with_complex_conditions(self, kube_apis, crd_ingress_controller, vsr_adv_routing_setup): @@ -192,21 +215,24 @@ def test_flow_with_complex_conditions(self, kube_apis, crd_ingress_controller, v f"{TEST_DATA}/virtual-server-route-advanced-routing/virtual-server-route-complex.yaml", vsr_adv_routing_setup.namespace, ) - wait_before_test(1) - - resp_1 = requests.get( - vsr_adv_routing_setup.backends_url + "?arg1=v1", - headers={"host": vsr_adv_routing_setup.vs_host, "x-version": "future"}, - cookies={"user": "some"}, - ) - resp_2 = requests.post( - vsr_adv_routing_setup.backends_url + "?arg1=v2", - headers={"host": vsr_adv_routing_setup.vs_host, "x-version": "deprecated"}, - cookies={"user": "bad"}, - ) - resp_3 = requests.get( - vsr_adv_routing_setup.backends_url + "?arg1=v2", - headers={"host": vsr_adv_routing_setup.vs_host, "x-version": "deprecated"}, - cookies={"user": "bad"}, - ) + ensure_response_from_backend(vsr_adv_routing_setup.backends_url, vsr_adv_routing_setup.vs_host, check404=True) + wait_before_test() + global resp_1, resp_2, resp_3 + resp_1.status_code = resp_2.status_code = resp_3.status_code = 502 + while resp_1.status_code == 502 and resp_2.status_code == 502 and resp_3.status_code == 502: + resp_1 = requests.get( + vsr_adv_routing_setup.backends_url + "?arg1=v1", + headers={"host": vsr_adv_routing_setup.vs_host, "x-version": "future"}, + cookies={"user": "some"}, + ) + resp_2 = requests.post( + vsr_adv_routing_setup.backends_url + "?arg1=v2", + headers={"host": vsr_adv_routing_setup.vs_host, "x-version": "deprecated"}, + cookies={"user": "bad"}, + ) + resp_3 = requests.get( + vsr_adv_routing_setup.backends_url + "?arg1=v2", + headers={"host": vsr_adv_routing_setup.vs_host, "x-version": "deprecated"}, + cookies={"user": "bad"}, + ) execute_assertions(resp_1, resp_2, resp_3) diff --git a/tests/suite/test_v_s_route_api.py b/tests/suite/test_v_s_route_api.py index 97703b6cc3..82e9a1a5ff 100644 --- a/tests/suite/test_v_s_route_api.py +++ b/tests/suite/test_v_s_route_api.py @@ -8,6 +8,7 @@ @pytest.mark.vsr +@pytest.mark.vsr_api @pytest.mark.skip_for_nginx_oss @pytest.mark.parametrize( "crd_ingress_controller, v_s_route_setup", diff --git a/tests/suite/test_v_s_route_canned_responses.py b/tests/suite/test_v_s_route_canned_responses.py index b5c4df8e17..221314cf06 100644 --- a/tests/suite/test_v_s_route_canned_responses.py +++ b/tests/suite/test_v_s_route_canned_responses.py @@ -15,6 +15,7 @@ @pytest.mark.vsr +@pytest.mark.vsr_canned @pytest.mark.parametrize( "crd_ingress_controller, v_s_route_setup", [ @@ -103,7 +104,11 @@ def test_update(self, kube_apis, crd_ingress_controller, v_s_route_setup): wait_and_assert_status_code(201, req_url_2, v_s_route_setup.vs_host) resp = requests.get(req_url_2, headers={"host": v_s_route_setup.vs_host}) resp_content = resp.content.decode("utf-8") - assert resp.headers["content-type"] == "user-type" and resp_content == "line1\nline2" + assert ( + resp.headers["content-type"] == "user-type" + and resp_content == "line1\nline2" + and resp.headers["coffee-test-header"] == "espresso" + ) new_events_ns = get_events(kube_apis.v1, v_s_route_setup.namespace) assert_event_count_increased(vs_event_text, initial_count_vs, new_events_ns) @@ -148,6 +153,7 @@ def test_openapi_validation_flow( and "action.return.type in body must be of type" in ex.body and "action.return.body in body must be of type" in ex.body and "action.return.code in body must be of type" in ex.body + and "action.return.headers in body must be of type" in ex.body ) except Exception as ex: pytest.fail(f"An unexpected exception is raised: {ex}") diff --git a/tests/suite/test_v_s_route_error_pages.py b/tests/suite/test_v_s_route_error_pages.py index cf5d28d132..14b4d1947b 100644 --- a/tests/suite/test_v_s_route_error_pages.py +++ b/tests/suite/test_v_s_route_error_pages.py @@ -14,6 +14,7 @@ @pytest.mark.vsr +@pytest.mark.vsr_error_pages @pytest.mark.parametrize( "crd_ingress_controller, v_s_route_setup", [ diff --git a/tests/suite/test_v_s_route_externalname.py b/tests/suite/test_v_s_route_externalname.py index bcb3af1362..a363e17d5b 100644 --- a/tests/suite/test_v_s_route_externalname.py +++ b/tests/suite/test_v_s_route_externalname.py @@ -116,9 +116,10 @@ def vsr_externalname_setup( ensure_response_from_backend(f"{req_url}{route.paths[0]}", vs_host) def fin(): - print("Delete test namespaces") - delete_namespace(kube_apis.v1, external_ns) - delete_namespace(kube_apis.v1, ns_1) + if request.config.getoption("--skip-fixture-teardown") == "no": + print("Delete test namespaces") + delete_namespace(kube_apis.v1, external_ns) + delete_namespace(kube_apis.v1, ns_1) request.addfinalizer(fin) @@ -128,6 +129,7 @@ def fin(): @pytest.mark.vsr +@pytest.mark.vsr_external_name @pytest.mark.skip_for_nginx_oss @pytest.mark.parametrize( "crd_ingress_controller, vsr_externalname_setup", diff --git a/tests/suite/test_v_s_route_focused_canary.py b/tests/suite/test_v_s_route_focused_canary.py index 3ed3936ae5..f4ff31eec6 100644 --- a/tests/suite/test_v_s_route_focused_canary.py +++ b/tests/suite/test_v_s_route_focused_canary.py @@ -105,8 +105,9 @@ def vsr_canary_setup( wait_until_all_pods_are_ready(kube_apis.v1, ns_1) def fin(): - print("Delete test namespace") - delete_namespace(kube_apis.v1, ns_1) + if request.config.getoption("--skip-fixture-teardown") == "no": + print("Delete test namespace") + delete_namespace(kube_apis.v1, ns_1) request.addfinalizer(fin) @@ -115,6 +116,7 @@ def fin(): @pytest.mark.flaky(max_runs=3) @pytest.mark.vsr +@pytest.mark.vsr_canary @pytest.mark.parametrize( "crd_ingress_controller, vsr_canary_setup", [ @@ -138,9 +140,16 @@ def test_flow_with_header(self, kube_apis, crd_ingress_controller, vsr_canary_se counter_v1, counter_v2 = 0, 0 for _ in range(100): - resp = requests.get( - vsr_canary_setup.backends_url, headers={"host": vsr_canary_setup.vs_host, "x-version": "canary"} + ensure_response_from_backend( + vsr_canary_setup.backends_url, vsr_canary_setup.vs_host, {"x-version": "canary"}, check404=True ) + status_code = 502 + while status_code == 502: + resp = requests.get( + vsr_canary_setup.backends_url, headers={"host": vsr_canary_setup.vs_host, "x-version": "canary"} + ) + status_code = resp.status_code + if upstreams[0] in resp.text in resp.text: counter_v1 = counter_v1 + 1 elif upstreams[1] in resp.text in resp.text: diff --git a/tests/suite/test_v_s_route_grpc.py b/tests/suite/test_v_s_route_grpc.py index ca4d31a557..247de0e17b 100644 --- a/tests/suite/test_v_s_route_grpc.py +++ b/tests/suite/test_v_s_route_grpc.py @@ -42,7 +42,7 @@ def backend_setup(request, kube_apis, ingress_controller_prerequisites, test_nam app_name = request.param.get("app_type") create_example_app(kube_apis, app_name, test_namespace) wait_until_all_pods_are_ready(kube_apis.v1, test_namespace) - except Exception as ex: + except Exception: print("Failed to complete setup, cleaning up..") replace_configmap_from_yaml( kube_apis.v1, @@ -54,19 +54,21 @@ def backend_setup(request, kube_apis, ingress_controller_prerequisites, test_nam pytest.fail(f"VSR GRPC setup failed") def fin(): - print("Clean up:") - replace_configmap_from_yaml( - kube_apis.v1, - ingress_controller_prerequisites.config_map["metadata"]["name"], - ingress_controller_prerequisites.namespace, - f"{DEPLOYMENTS}/common/nginx-config.yaml", - ) - delete_common_app(kube_apis, app_name, test_namespace) + if request.config.getoption("--skip-fixture-teardown") == "no": + print("Clean up:") + replace_configmap_from_yaml( + kube_apis.v1, + ingress_controller_prerequisites.config_map["metadata"]["name"], + ingress_controller_prerequisites.namespace, + f"{DEPLOYMENTS}/common/nginx-config.yaml", + ) + delete_common_app(kube_apis, app_name, test_namespace) request.addfinalizer(fin) @pytest.mark.vsr +@pytest.mark.vsr_grpc @pytest.mark.smoke @pytest.mark.parametrize( "crd_ingress_controller, v_s_route_setup", diff --git a/tests/suite/test_v_s_route_redirects.py b/tests/suite/test_v_s_route_redirects.py index 5ca73c6b80..f05f496065 100644 --- a/tests/suite/test_v_s_route_redirects.py +++ b/tests/suite/test_v_s_route_redirects.py @@ -13,6 +13,7 @@ @pytest.mark.vsr +@pytest.mark.vsr_redirects @pytest.mark.parametrize( "crd_ingress_controller, v_s_route_setup", [ diff --git a/tests/suite/test_v_s_route_regexp_location.py b/tests/suite/test_v_s_route_regexp_location.py index f867fb12c1..aad6535b37 100644 --- a/tests/suite/test_v_s_route_regexp_location.py +++ b/tests/suite/test_v_s_route_regexp_location.py @@ -21,6 +21,7 @@ @pytest.mark.vsr +@pytest.mark.vsr_regexes @pytest.mark.parametrize( "crd_ingress_controller, v_s_route_setup", [ @@ -214,6 +215,7 @@ def vsr_regexp_setup( @pytest.mark.vsr +@pytest.mark.vsr_regexes @pytest.mark.parametrize( "crd_ingress_controller, vsr_regexp_setup", [ diff --git a/tests/suite/test_v_s_route_split_traffic.py b/tests/suite/test_v_s_route_split_traffic.py index 963c73c20d..9c39afcdfd 100644 --- a/tests/suite/test_v_s_route_split_traffic.py +++ b/tests/suite/test_v_s_route_split_traffic.py @@ -40,6 +40,7 @@ def get_upstreams_of_splitting(file) -> []: @pytest.mark.vsr +@pytest.mark.vsr_splits @pytest.mark.parametrize( "crd_ingress_controller, v_s_route_setup", [ @@ -56,7 +57,6 @@ def test_several_requests(self, kube_apis, crd_ingress_controller, v_s_route_set req_url = ( f"http://{v_s_route_setup.public_endpoint.public_ip}:{v_s_route_setup.public_endpoint.port}{split_path[0]}" ) - ensure_response_from_backend(req_url, v_s_route_setup.vs_host) weights = get_weights_of_splitting(f"{TEST_DATA}/virtual-server-route-split-traffic/route-multiple.yaml") upstreams = get_upstreams_of_splitting(f"{TEST_DATA}/virtual-server-route-split-traffic/route-multiple.yaml") sum_weights = sum(weights) @@ -64,9 +64,13 @@ def test_several_requests(self, kube_apis, crd_ingress_controller, v_s_route_set counter_v1, counter_v2 = 0, 0 for _ in range(100): - resp = requests.get(req_url, headers={"host": v_s_route_setup.vs_host}) - if resp.status_code == 502: - print("Backend is not ready yet, skip.") + ensure_response_from_backend(req_url, v_s_route_setup.vs_host) + status_code = 502 + while status_code == 502: + resp = requests.get(req_url, headers={"host": v_s_route_setup.vs_host}) + status_code = resp.status_code + if status_code == 502: + print("Backend is not ready yet, skip.") if upstreams[0] in resp.text in resp.text: counter_v1 = counter_v1 + 1 elif upstreams[1] in resp.text in resp.text: diff --git a/tests/suite/test_v_s_route_status.py b/tests/suite/test_v_s_route_status.py index e762899368..e917e547b4 100644 --- a/tests/suite/test_v_s_route_status.py +++ b/tests/suite/test_v_s_route_status.py @@ -11,6 +11,7 @@ @pytest.mark.vsr +@pytest.mark.vsr_status @pytest.mark.parametrize( "crd_ingress_controller, v_s_route_setup", [ diff --git a/tests/suite/test_v_s_route_upstream_options.py b/tests/suite/test_v_s_route_upstream_options.py index 88bf20b200..d967b46cde 100644 --- a/tests/suite/test_v_s_route_upstream_options.py +++ b/tests/suite/test_v_s_route_upstream_options.py @@ -26,6 +26,7 @@ indirect=True, ) @pytest.mark.vsr +@pytest.mark.vsr_upstream class TestVSRouteUpstreamOptions: def test_nginx_config_upstreams_defaults( self, kube_apis, ingress_controller_prerequisites, crd_ingress_controller, v_s_route_setup, v_s_route_app_setup @@ -393,6 +394,7 @@ def test_v_s_r_overrides_config_map( @pytest.mark.vsr +@pytest.mark.vsr_upstream @pytest.mark.parametrize( "crd_ingress_controller, v_s_route_setup", [ @@ -544,6 +546,7 @@ def test_openapi_validation_flow( @pytest.mark.vsr +@pytest.mark.vsr_upstream @pytest.mark.skip_for_nginx_oss @pytest.mark.parametrize( "crd_ingress_controller, v_s_route_setup", @@ -576,8 +579,7 @@ class TestOptionsSpecificForPlus: }, }, [ - "health_check uri=/ port=8080 interval=5s jitter=0s", - "fails=1 passes=1", + "health_check uri=/ port=8080 interval=5s jitter=0s fails=1 passes=1 keepalive_time=60s;", "slow_start=3h", "queue 100 timeout=60s;", "sticky cookie TestCookie expires=max domain=virtual-server-route.example.com httponly secure path=/some-valid/path;", @@ -590,6 +592,7 @@ class TestOptionsSpecificForPlus: "enable": True, "path": "/health", "interval": "15s", + "keepalive-time": "120s", "jitter": "3", "fails": 2, "passes": 2, @@ -605,16 +608,14 @@ class TestOptionsSpecificForPlus: "queue": {"size": 1000, "timeout": "66s"}, }, [ - "health_check uri=/health port=8080 interval=15s jitter=3", - "fails=2 passes=2 match=", - "proxy_pass https://vs", + "slow_start=0s", "status 200;", "proxy_connect_timeout 35s;", "proxy_read_timeout 45s;", "proxy_send_timeout 55s;", 'proxy_set_header Host "virtual-server.example.com";', - "slow_start=0s", - "queue 1000 timeout=66s;", + "proxy_pass https://vs", + "health_check uri=/health port=8080 interval=15s jitter=3s fails=2 passes=2 match=vs_backends-namespace_virtual-server-route_vsr_backend2-namespace_backend2_backend2_match keepalive_time=120s;", ], ), ], diff --git a/tests/suite/test_v_s_route_upstream_tls.py b/tests/suite/test_v_s_route_upstream_tls.py index 1eb500932d..8b39efc0de 100644 --- a/tests/suite/test_v_s_route_upstream_tls.py +++ b/tests/suite/test_v_s_route_upstream_tls.py @@ -47,18 +47,20 @@ def v_s_route_secure_app_setup(request, kube_apis, v_s_route_setup) -> None: wait_until_all_pods_are_ready(kube_apis.v1, v_s_route_setup.route_s.namespace) def fin(): - print("Clean up the Application:") - delete_items_from_yaml( - kube_apis, f"{TEST_DATA}/common/app/vsr/secure/multiple.yaml", v_s_route_setup.route_m.namespace - ) - delete_items_from_yaml( - kube_apis, f"{TEST_DATA}/common/app/vsr/secure/single.yaml", v_s_route_setup.route_s.namespace - ) + if request.config.getoption("--skip-fixture-teardown") == "no": + print("Clean up the Application:") + delete_items_from_yaml( + kube_apis, f"{TEST_DATA}/common/app/vsr/secure/multiple.yaml", v_s_route_setup.route_m.namespace + ) + delete_items_from_yaml( + kube_apis, f"{TEST_DATA}/common/app/vsr/secure/single.yaml", v_s_route_setup.route_s.namespace + ) request.addfinalizer(fin) @pytest.mark.vsr +@pytest.mark.vsr_upstream @pytest.mark.parametrize( "crd_ingress_controller, v_s_route_setup", [ diff --git a/tests/suite/test_v_s_route_weight_changes_dynamic_reload.py b/tests/suite/test_v_s_route_weight_changes_dynamic_reload.py new file mode 100644 index 0000000000..bd27a53607 --- /dev/null +++ b/tests/suite/test_v_s_route_weight_changes_dynamic_reload.py @@ -0,0 +1,185 @@ +import pytest +import requests +from settings import TEST_DATA +from suite.fixtures.custom_resource_fixtures import VirtualServerRoute +from suite.utils.resources_utils import ( + create_example_app, + create_namespace_with_name_from_yaml, + delete_namespace, + ensure_response_from_backend, + get_reload_count, + wait_before_test, + wait_until_all_pods_are_ready, +) +from suite.utils.yaml_utils import get_first_host_from_yaml, get_paths_from_vsr_yaml, get_route_namespace_from_vs_yaml + +from tests.suite.utils.custom_assertions import wait_and_assert_status_code +from tests.suite.utils.vs_vsr_resources_utils import ( + create_v_s_route_from_yaml, + create_virtual_server_from_yaml, + patch_v_s_route_from_yaml, +) + + +class VSRWeightChangesDynamicReloadSetup: + """ + Encapsulate weight changes without reload details. + + Attributes: + namespace (str): + vs_host (str): + vs_name (str): + route (VirtualServerRoute): + backends_url (str): backend url + """ + + def __init__(self, namespace, vs_host, vs_name, route: VirtualServerRoute, backends_url, metrics_url): + self.namespace = namespace + self.vs_host = vs_host + self.vs_name = vs_name + self.route = route + self.backends_url = backends_url + self.metrics_url = metrics_url + + +@pytest.fixture(scope="class") +def vsr_weight_changes_dynamic_reload_setup( + request, kube_apis, ingress_controller_prerequisites, ingress_controller_endpoint +) -> VSRWeightChangesDynamicReloadSetup: + """ + Prepare an example app for weight changes without reload VSR. + + Single namespace with VS+VSR and weight changes without reload app. + + :param request: internal pytest fixture + :param kube_apis: client apis + :param ingress_controller_endpoint: + :param ingress_controller_prerequisites: + :return: + """ + + metrics_url = f"http://{ingress_controller_endpoint.public_ip}:{ingress_controller_endpoint.metrics_port}/metrics" + vs_routes_ns = get_route_namespace_from_vs_yaml( + f"{TEST_DATA}/{request.param['example']}/standard/virtual-server.yaml" + ) + ns_1 = create_namespace_with_name_from_yaml(kube_apis.v1, vs_routes_ns[0], f"{TEST_DATA}/common/ns.yaml") + print("------------------------- Deploy Virtual Server -----------------------------------") + vs_name = create_virtual_server_from_yaml( + kube_apis.custom_objects, f"{TEST_DATA}/{request.param['example']}/standard/virtual-server.yaml", ns_1 + ) + vs_host = get_first_host_from_yaml(f"{TEST_DATA}/{request.param['example']}/standard/virtual-server.yaml") + + print("------------------------- Deploy Virtual Server Route -----------------------------------") + vsr_name = create_v_s_route_from_yaml( + kube_apis.custom_objects, f"{TEST_DATA}/{request.param['example']}/virtual-server-route-initial.yaml", ns_1 + ) + vsr_paths = get_paths_from_vsr_yaml(f"{TEST_DATA}/{request.param['example']}/virtual-server-route-initial.yaml") + route = VirtualServerRoute(ns_1, vsr_name, vsr_paths) + backends_url = f"http://{ingress_controller_endpoint.public_ip}:{ingress_controller_endpoint.port}{vsr_paths[0]}" + + print("---------------------- Deploy weight changes without reload vsr app ----------------------------") + create_example_app(kube_apis, "weight-changes-dynamic-reload-vsr", ns_1) + wait_until_all_pods_are_ready(kube_apis.v1, ns_1) + + def fin(): + if request.config.getoption("--skip-fixture-teardown") == "no": + print("Delete test namespace") + delete_namespace(kube_apis.v1, ns_1) + + request.addfinalizer(fin) + + return VSRWeightChangesDynamicReloadSetup(ns_1, vs_host, vs_name, route, backends_url, metrics_url) + + +@pytest.mark.vsr +@pytest.mark.vsr_splits +@pytest.mark.smoke +@pytest.mark.skip_for_nginx_oss +@pytest.mark.parametrize( + "crd_ingress_controller,vsr_weight_changes_dynamic_reload_setup, expect_reload", + [ + ( + { + "type": "complete", + "extra_args": [ + "-enable-custom-resources", + "-enable-prometheus-metrics", + "-weight-changes-dynamic-reload=true", + ], + }, + {"example": "virtual-server-route-weight-changes-dynamic-reload"}, + False, + ), + ( + { + "type": "complete", + "extra_args": [ + "-enable-custom-resources", + "-enable-prometheus-metrics", + "-weight-changes-dynamic-reload=false", + ], + }, + {"example": "virtual-server-route-weight-changes-dynamic-reload"}, + True, + ), + ], + indirect=["crd_ingress_controller", "vsr_weight_changes_dynamic_reload_setup"], + ids=["WithoutReload", "WithReload"], +) +class TestVSRWeightChangesWithReloadCondition: + + def test_vsr_weight_changes_reload_behavior( + self, kube_apis, crd_ingress_controller, vsr_weight_changes_dynamic_reload_setup, expect_reload + ): + swap_weights_config = ( + f"{TEST_DATA}/virtual-server-route-weight-changes-dynamic-reload/virtual-server-route-swap.yaml" + ) + + print("Step 1: Get a response from the backend.") + wait_and_assert_status_code( + 200, vsr_weight_changes_dynamic_reload_setup.backends_url, vsr_weight_changes_dynamic_reload_setup.vs_host + ) + resp = requests.get( + vsr_weight_changes_dynamic_reload_setup.backends_url, + headers={"host": vsr_weight_changes_dynamic_reload_setup.vs_host}, + ) + assert "backend1" in resp.text + + print("Step 2: Record the initial number of reloads.") + count_before = get_reload_count(vsr_weight_changes_dynamic_reload_setup.metrics_url) + print(f"Reload count before: {count_before}") + + print("Step 3: Apply a configuration that swaps the weights (0 100) to (100 0).") + patch_v_s_route_from_yaml( + kube_apis.custom_objects, + vsr_weight_changes_dynamic_reload_setup.route.name, + swap_weights_config, + vsr_weight_changes_dynamic_reload_setup.route.namespace, + ) + + wait_before_test(5) + + print("Step 4: Verify hitting the other backend.") + ensure_response_from_backend( + vsr_weight_changes_dynamic_reload_setup.backends_url, vsr_weight_changes_dynamic_reload_setup.vs_host + ) + wait_and_assert_status_code( + 200, vsr_weight_changes_dynamic_reload_setup.backends_url, vsr_weight_changes_dynamic_reload_setup.vs_host + ) + resp = requests.get( + vsr_weight_changes_dynamic_reload_setup.backends_url, + headers={"host": vsr_weight_changes_dynamic_reload_setup.vs_host}, + ) + assert "backend2" in resp.text + + print("Step 5: Verify reload behavior based on the weight-changes-dynamic-reload flag.") + count_after = get_reload_count(vsr_weight_changes_dynamic_reload_setup.metrics_url) + print(f"Reload count after: {count_after}") + if expect_reload: + assert ( + count_before < count_after + ), "The reload count should increase when weights are swapped and weight-changes-dynamic-reload=false." + else: + assert ( + count_before == count_after + ), "The reload count should not change when weights are swapped and weight-changes-dynamic-reload=true." diff --git a/tests/suite/test_v_s_route_weight_changes_dynamic_reload_many_splits.py b/tests/suite/test_v_s_route_weight_changes_dynamic_reload_many_splits.py new file mode 100644 index 0000000000..6310eb35e4 --- /dev/null +++ b/tests/suite/test_v_s_route_weight_changes_dynamic_reload_many_splits.py @@ -0,0 +1,181 @@ +import pytest +import requests +from settings import TEST_DATA +from suite.fixtures.custom_resource_fixtures import VirtualServerRoute +from suite.utils.resources_utils import ( + create_example_app, + create_namespace_with_name_from_yaml, + delete_namespace, + ensure_response_from_backend, + replace_configmap, + replace_configmap_from_yaml, + wait_before_test, + wait_until_all_pods_are_ready, +) +from suite.utils.yaml_utils import get_first_host_from_yaml, get_paths_from_vsr_yaml, get_route_namespace_from_vs_yaml + +from tests.suite.utils.custom_assertions import wait_and_assert_status_code +from tests.suite.utils.vs_vsr_resources_utils import ( + create_v_s_route_from_yaml, + create_virtual_server_from_yaml, + patch_v_s_route_from_yaml, +) + + +class VSRWeightChangesDynamicReloadManySplitsSetup: + """ + Encapsulate weight changes without reload details. + + Attributes: + namespace (str): + vs_host (str): + vs_name (str): + route (VirtualServerRoute): + backends_url (str): backend url + """ + + def __init__(self, namespace, vs_host, vs_name, route: VirtualServerRoute, backends_url, metrics_url): + self.namespace = namespace + self.vs_host = vs_host + self.vs_name = vs_name + self.route = route + self.backends_url = backends_url + self.metrics_url = metrics_url + + +@pytest.fixture(scope="class") +def vsr_weight_changes_dynamic_reload_many_splits_setup( + request, kube_apis, ingress_controller_prerequisites, ingress_controller_endpoint +) -> VSRWeightChangesDynamicReloadManySplitsSetup: + """ + Prepare an example app for weight changes without reload VSR. + + Single namespace with VS+VSR and weight changes without reload app. + + :param request: internal pytest fixture + :param kube_apis: client apis + :param ingress_controller_endpoint: + :param ingress_controller_prerequisites: + :return: + """ + + metrics_url = f"http://{ingress_controller_endpoint.public_ip}:{ingress_controller_endpoint.metrics_port}/metrics" + vs_routes_ns = get_route_namespace_from_vs_yaml( + f"{TEST_DATA}/{request.param['example']}/standard/virtual-server-many.yaml" + ) + ns_1 = create_namespace_with_name_from_yaml(kube_apis.v1, vs_routes_ns[0], f"{TEST_DATA}/common/ns.yaml") + print("------------------------- Deploy Virtual Server -----------------------------------") + vs_name = create_virtual_server_from_yaml( + kube_apis.custom_objects, f"{TEST_DATA}/{request.param['example']}/standard/virtual-server-many.yaml", ns_1 + ) + vs_host = get_first_host_from_yaml(f"{TEST_DATA}/{request.param['example']}/standard/virtual-server-many.yaml") + + print("------------------------- Deploy Virtual Server Route -----------------------------------") + vsr_name = create_v_s_route_from_yaml( + kube_apis.custom_objects, + f"{TEST_DATA}/{request.param['example']}/virtual-server-route-many-splits-initial.yaml", + ns_1, + ) + vsr_paths = get_paths_from_vsr_yaml( + f"{TEST_DATA}/{request.param['example']}/virtual-server-route-many-splits-initial.yaml" + ) + route = VirtualServerRoute(ns_1, vsr_name, vsr_paths) + backends_url = ( + f"http://{ingress_controller_endpoint.public_ip}:{ingress_controller_endpoint.port}{vsr_paths[0][:-1]}" + ) + + print("-----------------------Apply Config Map---------------------------------------------------") + config_map_name = ingress_controller_prerequisites.config_map["metadata"]["name"] + replace_configmap_from_yaml( + kube_apis.v1, + config_map_name, + ingress_controller_prerequisites.namespace, + f"{TEST_DATA}/{request.param['example']}/configmap/nginx-config.yaml", + ) + + print("---------------------- Deploy weight changes dynamic reload vsr app ----------------------------") + create_example_app(kube_apis, "weight-changes-dynamic-reload-vsr-many-splits", ns_1) + wait_until_all_pods_are_ready(kube_apis.v1, ns_1) + + def fin(): + if request.config.getoption("--skip-fixture-teardown") == "no": + print("Delete test namespace") + delete_namespace(kube_apis.v1, ns_1) + replace_configmap( + kube_apis.v1, + config_map_name, + ingress_controller_prerequisites.namespace, + ingress_controller_prerequisites.config_map, + ) + + request.addfinalizer(fin) + + return VSRWeightChangesDynamicReloadManySplitsSetup(ns_1, vs_host, vs_name, route, backends_url, metrics_url) + + +@pytest.mark.vsr +@pytest.mark.vsr_splits +@pytest.mark.smoke +@pytest.mark.skip_for_nginx_oss +@pytest.mark.parametrize( + "crd_ingress_controller,vsr_weight_changes_dynamic_reload_many_splits_setup", + [ + ( + { + "type": "complete", + "extra_args": [ + "-enable-custom-resources", + "-enable-prometheus-metrics", + "-weight-changes-dynamic-reload=true", + "-log-level=debug", + ], + }, + {"example": "virtual-server-route-weight-changes-dynamic-reload"}, + ), + ], + indirect=["crd_ingress_controller", "vsr_weight_changes_dynamic_reload_many_splits_setup"], +) +class TestVSRWeightChangesDynamicReloadManySplits: + @pytest.mark.flaky(max_runs=3) + def test_vsr_weight_changes_dynamic_reload_many_splits( + self, kube_apis, crd_ingress_controller, vsr_weight_changes_dynamic_reload_many_splits_setup + ) -> None: + """ + This test checks if 32 splits can be created when the following values are specified in the configmap + map-hash-bucket-size: "512" + map-hash-max-size: "8192" + variables-hash-bucket-size: "256" + variables-hash-max-size: "16384" + + and also that weight-changes-dynamic-reload is set to true + """ + swap_weights_config = ( + f"{TEST_DATA}/virtual-server-route-weight-changes-dynamic-reload/virtual-server-route-many-splits-swap.yaml" + ) + + print("Step 1: Get a response from the backend.") + backends32_url = f"{vsr_weight_changes_dynamic_reload_many_splits_setup.backends_url}32" + wait_and_assert_status_code(200, backends32_url, vsr_weight_changes_dynamic_reload_many_splits_setup.vs_host) + resp = requests.get( + backends32_url, + headers={"host": vsr_weight_changes_dynamic_reload_many_splits_setup.vs_host}, + ) + assert "backend1" in resp.text + + print("Step 2: Apply a configuration that swaps the weights (0 100) to (100 0).") + patch_v_s_route_from_yaml( + kube_apis.custom_objects, + vsr_weight_changes_dynamic_reload_many_splits_setup.route.name, + swap_weights_config, + vsr_weight_changes_dynamic_reload_many_splits_setup.route.namespace, + ) + + print("Step 3: Verify hitting the other backend.") + ensure_response_from_backend(backends32_url, vsr_weight_changes_dynamic_reload_many_splits_setup.vs_host) + wait_and_assert_status_code(200, backends32_url, vsr_weight_changes_dynamic_reload_many_splits_setup.vs_host) + wait_before_test(1) + resp = requests.get( + backends32_url, + headers={"host": vsr_weight_changes_dynamic_reload_many_splits_setup.vs_host}, + ) + assert "backend2" in resp.text diff --git a/tests/suite/test_virtual_server.py b/tests/suite/test_virtual_server.py index b13c46838a..3d13fc131d 100644 --- a/tests/suite/test_virtual_server.py +++ b/tests/suite/test_virtual_server.py @@ -1,10 +1,11 @@ import pytest -from settings import DEPLOYMENTS, TEST_DATA +from settings import CRDS, DEPLOYMENTS, TEST_DATA from suite.utils.custom_assertions import wait_and_assert_status_code from suite.utils.custom_resources_utils import create_crd_from_yaml, delete_crd from suite.utils.resources_utils import ( create_service_from_yaml, delete_service, + get_first_pod_name, patch_rbac, read_service, replace_service, @@ -13,12 +14,14 @@ from suite.utils.vs_vsr_resources_utils import ( create_virtual_server_from_yaml, delete_virtual_server, + get_vs_nginx_template_conf, patch_virtual_server_from_yaml, ) from suite.utils.yaml_utils import get_first_host_from_yaml, get_name_from_yaml, get_paths_from_vs_yaml @pytest.mark.vs +@pytest.mark.vs_responses @pytest.mark.smoke @pytest.mark.parametrize( "crd_ingress_controller, virtual_server_setup", @@ -148,15 +151,13 @@ def test_responses_after_rbac_misconfiguration_on_the_fly( def test_responses_after_crd_removal_on_the_fly(self, kube_apis, crd_ingress_controller, virtual_server_setup): print("\nStep 12: remove CRD and check") - crd_name = get_name_from_yaml(f"{DEPLOYMENTS}/common/crds/k8s.nginx.org_virtualservers.yaml") + crd_name = get_name_from_yaml(f"{CRDS}/k8s.nginx.org_virtualservers.yaml") delete_crd(kube_apis.api_extensions_v1, crd_name) wait_and_assert_status_code(404, virtual_server_setup.backend_1_url, virtual_server_setup.vs_host) wait_and_assert_status_code(404, virtual_server_setup.backend_2_url, virtual_server_setup.vs_host) print("Step 13: restore CRD and VS and check") - create_crd_from_yaml( - kube_apis.api_extensions_v1, crd_name, f"{DEPLOYMENTS}/common/crds/k8s.nginx.org_virtualservers.yaml" - ) + create_crd_from_yaml(kube_apis.api_extensions_v1, crd_name, f"{CRDS}/k8s.nginx.org_virtualservers.yaml") wait_before_test(1) create_virtual_server_from_yaml( kube_apis.custom_objects, @@ -166,6 +167,45 @@ def test_responses_after_crd_removal_on_the_fly(self, kube_apis, crd_ingress_con wait_and_assert_status_code(200, virtual_server_setup.backend_1_url, virtual_server_setup.vs_host) wait_and_assert_status_code(200, virtual_server_setup.backend_2_url, virtual_server_setup.vs_host) + def test_responses_after_virtual_server_update_with_gunzip( + self, kube_apis, ingress_controller_prerequisites, crd_ingress_controller, virtual_server_setup + ): + print("Step 1: update gunzip in the VS and check") + patch_virtual_server_from_yaml( + kube_apis.custom_objects, + virtual_server_setup.vs_name, + f"{TEST_DATA}/virtual-server/virtual-server-gunzip.yaml", + virtual_server_setup.namespace, + ) + wait_before_test(1) + wait_and_assert_status_code(200, virtual_server_setup.backend_1_url, virtual_server_setup.vs_host) + wait_and_assert_status_code(200, virtual_server_setup.backend_2_url, virtual_server_setup.vs_host) + + print("Step 2: verify gunzip directive is present") + + pod_name = get_first_pod_name(kube_apis.v1, ingress_controller_prerequisites.namespace) + + confFile = get_vs_nginx_template_conf( + kube_apis.v1, + virtual_server_setup.namespace, + virtual_server_setup.vs_name, + pod_name, + ingress_controller_prerequisites.namespace, + ) + + assert "gunzip on;" in confFile + + print("Step 3: restore VS and check") + patch_virtual_server_from_yaml( + kube_apis.custom_objects, + virtual_server_setup.vs_name, + f"{TEST_DATA}/virtual-server/standard/virtual-server.yaml", + virtual_server_setup.namespace, + ) + wait_before_test(1) + wait_and_assert_status_code(200, virtual_server_setup.backend_1_url, virtual_server_setup.vs_host) + wait_and_assert_status_code(200, virtual_server_setup.backend_2_url, virtual_server_setup.vs_host) + @pytest.mark.vs @pytest.mark.parametrize( diff --git a/tests/suite/test_virtual_server_advanced_routing.py b/tests/suite/test_virtual_server_advanced_routing.py index 91dc6a939c..2dd62c9d3a 100644 --- a/tests/suite/test_virtual_server_advanced_routing.py +++ b/tests/suite/test_virtual_server_advanced_routing.py @@ -1,9 +1,15 @@ +from unittest import mock + import pytest import requests from settings import TEST_DATA from suite.utils.resources_utils import ensure_response_from_backend, wait_before_test from suite.utils.vs_vsr_resources_utils import patch_virtual_server_from_yaml +resp_1 = mock.Mock() +resp_2 = mock.Mock() +resp_3 = mock.Mock() + def execute_assertions(resp_1, resp_2, resp_3): assert resp_1.status_code == 200 @@ -35,18 +41,22 @@ def ensure_responses_from_backends(req_url, host) -> None: class TestAdvancedRouting: def test_flow_with_header(self, kube_apis, crd_ingress_controller, virtual_server_setup): ensure_responses_from_backends(virtual_server_setup.backend_1_url, virtual_server_setup.vs_host) - - resp_1 = requests.get( - virtual_server_setup.backend_1_url, headers={"host": virtual_server_setup.vs_host, "x-version": "future"} - ) - resp_2 = requests.get( - virtual_server_setup.backend_1_url, - headers={"host": virtual_server_setup.vs_host, "x-version": "deprecated"}, - ) - resp_3 = requests.get( - virtual_server_setup.backend_1_url, - headers={"host": virtual_server_setup.vs_host, "x-version-invalid": "deprecated"}, - ) + wait_before_test() + global resp_1, resp_2, resp_3 + resp_1.status_code = resp_2.status_code = resp_3.status_code = 502 + while resp_1.status_code == 502 and resp_2.status_code == 502 and resp_3.status_code == 502: + resp_1 = requests.get( + virtual_server_setup.backend_1_url, + headers={"host": virtual_server_setup.vs_host, "x-version": "future"}, + ) + resp_2 = requests.get( + virtual_server_setup.backend_1_url, + headers={"host": virtual_server_setup.vs_host, "x-version": "deprecated"}, + ) + resp_3 = requests.get( + virtual_server_setup.backend_1_url, + headers={"host": virtual_server_setup.vs_host, "x-version-invalid": "deprecated"}, + ) execute_assertions(resp_1, resp_2, resp_3) def test_flow_with_argument(self, kube_apis, crd_ingress_controller, virtual_server_setup): @@ -56,17 +66,20 @@ def test_flow_with_argument(self, kube_apis, crd_ingress_controller, virtual_ser f"{TEST_DATA}/virtual-server-advanced-routing/virtual-server-argument.yaml", virtual_server_setup.namespace, ) - wait_before_test(1) - - resp_1 = requests.get( - virtual_server_setup.backend_1_url + "?arg1=v1", headers={"host": virtual_server_setup.vs_host} - ) - resp_2 = requests.get( - virtual_server_setup.backend_1_url + "?arg1=v2", headers={"host": virtual_server_setup.vs_host} - ) - resp_3 = requests.get( - virtual_server_setup.backend_1_url + "?argument1=v1", headers={"host": virtual_server_setup.vs_host} - ) + ensure_response_from_backend(virtual_server_setup.backend_1_url, virtual_server_setup.vs_host) + wait_before_test() + global resp_1, resp_2, resp_3 + resp_1.status_code = resp_2.status_code = resp_3.status_code = 502 + while resp_1.status_code == 502 and resp_2.status_code == 502 and resp_3.status_code == 502: + resp_1 = requests.get( + virtual_server_setup.backend_1_url + "?arg1=v1", headers={"host": virtual_server_setup.vs_host} + ) + resp_2 = requests.get( + virtual_server_setup.backend_1_url + "?arg1=v2", headers={"host": virtual_server_setup.vs_host} + ) + resp_3 = requests.get( + virtual_server_setup.backend_1_url + "?argument1=v1", headers={"host": virtual_server_setup.vs_host} + ) execute_assertions(resp_1, resp_2, resp_3) def test_flow_with_cookie(self, kube_apis, crd_ingress_controller, virtual_server_setup): @@ -76,19 +89,26 @@ def test_flow_with_cookie(self, kube_apis, crd_ingress_controller, virtual_serve f"{TEST_DATA}/virtual-server-advanced-routing/virtual-server-cookie.yaml", virtual_server_setup.namespace, ) - wait_before_test(1) - - resp_1 = requests.get( - virtual_server_setup.backend_1_url, headers={"host": virtual_server_setup.vs_host}, cookies={"user": "some"} - ) - resp_2 = requests.get( - virtual_server_setup.backend_1_url, headers={"host": virtual_server_setup.vs_host}, cookies={"user": "bad"} - ) - resp_3 = requests.get( - virtual_server_setup.backend_1_url, - headers={"host": virtual_server_setup.vs_host}, - cookies={"user": "anonymous"}, - ) + ensure_response_from_backend(virtual_server_setup.backend_1_url, virtual_server_setup.vs_host) + wait_before_test() + global resp_1, resp_2, resp_3 + resp_1.status_code = resp_2.status_code = resp_3.status_code = 502 + while resp_1.status_code == 502 and resp_2.status_code == 502 and resp_3.status_code == 502: + resp_1 = requests.get( + virtual_server_setup.backend_1_url, + headers={"host": virtual_server_setup.vs_host}, + cookies={"user": "some"}, + ) + resp_2 = requests.get( + virtual_server_setup.backend_1_url, + headers={"host": virtual_server_setup.vs_host}, + cookies={"user": "bad"}, + ) + resp_3 = requests.get( + virtual_server_setup.backend_1_url, + headers={"host": virtual_server_setup.vs_host}, + cookies={"user": "anonymous"}, + ) execute_assertions(resp_1, resp_2, resp_3) def test_flow_with_variable(self, kube_apis, crd_ingress_controller, virtual_server_setup): @@ -98,11 +118,14 @@ def test_flow_with_variable(self, kube_apis, crd_ingress_controller, virtual_ser f"{TEST_DATA}/virtual-server-advanced-routing/virtual-server-variable.yaml", virtual_server_setup.namespace, ) - wait_before_test(1) - - resp_1 = requests.get(virtual_server_setup.backend_1_url, headers={"host": virtual_server_setup.vs_host}) - resp_2 = requests.post(virtual_server_setup.backend_1_url, headers={"host": virtual_server_setup.vs_host}) - resp_3 = requests.put(virtual_server_setup.backend_1_url, headers={"host": virtual_server_setup.vs_host}) + ensure_response_from_backend(virtual_server_setup.backend_1_url, virtual_server_setup.vs_host) + wait_before_test() + global resp_1, resp_2, resp_3 + resp_1.status_code = resp_2.status_code = resp_3.status_code = 502 + while resp_1.status_code == 502 and resp_2.status_code == 502 and resp_3.status_code == 502: + resp_1 = requests.get(virtual_server_setup.backend_1_url, headers={"host": virtual_server_setup.vs_host}) + resp_2 = requests.post(virtual_server_setup.backend_1_url, headers={"host": virtual_server_setup.vs_host}) + resp_3 = requests.put(virtual_server_setup.backend_1_url, headers={"host": virtual_server_setup.vs_host}) execute_assertions(resp_1, resp_2, resp_3) def test_flow_with_complex_conditions(self, kube_apis, crd_ingress_controller, virtual_server_setup): @@ -112,21 +135,24 @@ def test_flow_with_complex_conditions(self, kube_apis, crd_ingress_controller, v f"{TEST_DATA}/virtual-server-advanced-routing/virtual-server-complex.yaml", virtual_server_setup.namespace, ) - wait_before_test(1) - - resp_1 = requests.get( - virtual_server_setup.backend_1_url + "?arg1=v1", - headers={"host": virtual_server_setup.vs_host, "x-version": "future"}, - cookies={"user": "some"}, - ) - resp_2 = requests.post( - virtual_server_setup.backend_1_url + "?arg1=v2", - headers={"host": virtual_server_setup.vs_host, "x-version": "deprecated"}, - cookies={"user": "bad"}, - ) - resp_3 = requests.get( - virtual_server_setup.backend_1_url + "?arg1=v2", - headers={"host": virtual_server_setup.vs_host, "x-version": "deprecated"}, - cookies={"user": "bad"}, - ) + ensure_response_from_backend(virtual_server_setup.backend_1_url, virtual_server_setup.vs_host) + wait_before_test() + global resp_1, resp_2, resp_3 + resp_1.status_code = resp_2.status_code = resp_3.status_code = 502 + while resp_1.status_code == 502 and resp_2.status_code == 502 and resp_3.status_code == 502: + resp_1 = requests.get( + virtual_server_setup.backend_1_url + "?arg1=v1", + headers={"host": virtual_server_setup.vs_host, "x-version": "future"}, + cookies={"user": "some"}, + ) + resp_2 = requests.post( + virtual_server_setup.backend_1_url + "?arg1=v2", + headers={"host": virtual_server_setup.vs_host, "x-version": "deprecated"}, + cookies={"user": "bad"}, + ) + resp_3 = requests.get( + virtual_server_setup.backend_1_url + "?arg1=v2", + headers={"host": virtual_server_setup.vs_host, "x-version": "deprecated"}, + cookies={"user": "bad"}, + ) execute_assertions(resp_1, resp_2, resp_3) diff --git a/tests/suite/test_virtual_server_api.py b/tests/suite/test_virtual_server_api.py index de097e258f..ca615e19d2 100644 --- a/tests/suite/test_virtual_server_api.py +++ b/tests/suite/test_virtual_server_api.py @@ -8,6 +8,7 @@ @pytest.mark.vs +@pytest.mark.vs_api @pytest.mark.skip_for_nginx_oss @pytest.mark.parametrize( "crd_ingress_controller, virtual_server_setup", diff --git a/tests/suite/test_virtual_server_backup_service.py b/tests/suite/test_virtual_server_backup_service.py new file mode 100644 index 0000000000..e48d041e98 --- /dev/null +++ b/tests/suite/test_virtual_server_backup_service.py @@ -0,0 +1,366 @@ +import pytest +import requests +from settings import TEST_DATA +from suite.utils.resources_utils import ( + create_deployment_with_name, + create_namespace_with_name_from_yaml, + create_service_from_yaml, + create_service_with_name, + delete_namespace, + ensure_connection_to_public_endpoint, + ensure_response_from_backend, + get_first_pod_name, + get_vs_nginx_template_conf, + replace_configmap, + replace_configmap_from_yaml, + scale_deployment, + wait_before_test, +) +from suite.utils.vs_vsr_resources_utils import delete_and_create_vs_from_yaml + + +def make_request(url, host): + return requests.get( + url, + headers={"host": host}, + allow_redirects=False, + verify=False, + ) + + +def get_result_in_conf_with_retry( + kube_apis_v1, expected_conf_line, external_host, vs_name, vs_namespace, ic_pod_name, ic_pod_namespace +): + retry = 0 + result_conf = "" + while (expected_conf_line not in result_conf) and retry < 5: + wait_before_test() + result_conf = get_vs_nginx_template_conf( + kube_apis_v1, + vs_namespace, + vs_name, + ic_pod_name, + ic_pod_namespace, + ) + retry = retry + 1 + return result_conf + + +class ExternalNameSetup: + """Encapsulate ExternalName example details. + + Attributes: + ic_pod_name: + external_host: external service host + """ + + def __init__(self, ic_pod_name, external_svc, external_host): + self.ic_pod_name = ic_pod_name + self.external_svc = external_svc + self.external_host = external_host + + +@pytest.fixture(scope="class") +def vs_externalname_setup( + request, kube_apis, ingress_controller_prerequisites, virtual_server_setup +) -> ExternalNameSetup: + print("------------------------- Deploy External-Backend -----------------------------------") + external_ns = create_namespace_with_name_from_yaml(kube_apis.v1, "external-ns", f"{TEST_DATA}/common/ns.yaml") + external_svc_name = create_service_with_name(kube_apis.v1, external_ns, "external-backend-svc") + create_deployment_with_name(kube_apis.apps_v1_api, external_ns, "external-backend") + print("------------------------- Prepare ExternalName Setup -----------------------------------") + external_svc_src = f"{TEST_DATA}/virtual-server-backup-service/backup-svc.yaml" + external_svc_host = f"{external_svc_name}.{external_ns}.svc.cluster.local" + config_map_name = ingress_controller_prerequisites.config_map["metadata"]["name"] + replace_configmap_from_yaml( + kube_apis.v1, + config_map_name, + ingress_controller_prerequisites.namespace, + f"{TEST_DATA}/virtual-server-backup-service/nginx-config.yaml", + ) + external_svc = create_service_from_yaml(kube_apis.v1, virtual_server_setup.namespace, external_svc_src) + wait_before_test(2) + ensure_connection_to_public_endpoint( + virtual_server_setup.public_endpoint.public_ip, + virtual_server_setup.public_endpoint.port, + virtual_server_setup.public_endpoint.port_ssl, + ) + ic_pod_name = get_first_pod_name(kube_apis.v1, ingress_controller_prerequisites.namespace) + ensure_response_from_backend(virtual_server_setup.backend_1_url, virtual_server_setup.vs_host) + + def fin(): + if request.config.getoption("--skip-fixture-teardown") == "no": + print("\nClean up ExternalName Setup:") + delete_namespace(kube_apis.v1, external_ns) + replace_configmap( + kube_apis.v1, + config_map_name, + ingress_controller_prerequisites.namespace, + ingress_controller_prerequisites.config_map, + ) + + request.addfinalizer(fin) + + return ExternalNameSetup(ic_pod_name, external_svc, external_svc_host) + + +@pytest.mark.vs +@pytest.mark.vs_backup +@pytest.mark.skip_for_nginx_oss +@pytest.mark.skip(reason="issue with VS config") +@pytest.mark.parametrize( + "crd_ingress_controller, virtual_server_setup", + [ + ( + { + "type": "complete", + "extra_args": [ + f"-enable-custom-resources", + f"-log-level=debug", + ], + }, + { + "example": "virtual-server-backup-service", + "app_type": "simple", + }, + ) + ], + indirect=True, +) +class TestVirtualServerWithBackupService: + """ + This test validates that we still get a response back from the default + service, and not the backup service, as long as the default service is still available + """ + + def test_get_response_from_application( + self, + kube_apis, + ingress_controller_prerequisites, + crd_ingress_controller, + virtual_server_setup, + vs_externalname_setup, + ) -> None: + vs_backup_service = f"{TEST_DATA}/virtual-server-backup-service/virtual-server-backup.yaml" + delete_and_create_vs_from_yaml( + kube_apis.custom_objects, + virtual_server_setup.vs_name, + vs_backup_service, + virtual_server_setup.namespace, + ) + wait_before_test() + + print("\nStep 1: Get response from VS with backup service") + print(virtual_server_setup.backend_1_url + "\n") + res = make_request( + virtual_server_setup.backend_1_url, + virtual_server_setup.vs_host, + ) + + assert res.status_code == 200 + assert "backend1-" in res.text + assert "external-backend" not in res.text + + expected_conf_line = f"server {vs_externalname_setup.external_host}:80 backup resolve;" + result_conf = get_result_in_conf_with_retry( + kube_apis.v1, + expected_conf_line, + vs_externalname_setup.external_host, + virtual_server_setup.vs_name, + virtual_server_setup.namespace, + vs_externalname_setup.ic_pod_name, + ingress_controller_prerequisites.namespace, + ) + + assert "least_conn;" in result_conf + assert expected_conf_line in result_conf + + """ + This test validates that we get a response back from the backup service. + This test also scales the application back to 2 replicas after confirming a response from the backup service. + """ + + def test_get_response_from_backup( + self, + kube_apis, + ingress_controller_prerequisites, + crd_ingress_controller, + virtual_server_setup, + vs_externalname_setup, + ) -> None: + vs_backup_service = f"{TEST_DATA}/virtual-server-backup-service/virtual-server-backup.yaml" + delete_and_create_vs_from_yaml( + kube_apis.custom_objects, + virtual_server_setup.vs_name, + vs_backup_service, + virtual_server_setup.namespace, + ) + wait_before_test() + + print("\nStep 1: Get response from VS with backup service") + print(virtual_server_setup.backend_1_url + "\n") + res = make_request( + virtual_server_setup.backend_1_url, + virtual_server_setup.vs_host, + ) + + assert res.status_code == 200 + assert "backend1-" in res.text + assert "external-backend" not in res.text + + expected_conf_line = f"server {vs_externalname_setup.external_host}:80 backup resolve;" + result_conf = get_result_in_conf_with_retry( + kube_apis.v1, + expected_conf_line, + vs_externalname_setup.external_host, + virtual_server_setup.vs_name, + virtual_server_setup.namespace, + vs_externalname_setup.ic_pod_name, + ingress_controller_prerequisites.namespace, + ) + + assert "least_conn;" in result_conf + assert expected_conf_line in result_conf + + print("\nStep 2: Scale deployment to zero replicas") + scale_deployment(kube_apis.v1, kube_apis.apps_v1_api, "backend1", virtual_server_setup.namespace, 0) + wait_before_test() + + print("\nStep 3: Get response from backup service") + res_from_backup = make_request( + virtual_server_setup.backend_1_url, + virtual_server_setup.vs_host, + ) + + assert res_from_backup.status_code == 200 + assert "external-backend" in res_from_backup.text + assert "backend1-" not in res_from_backup.text + + result_conf = get_result_in_conf_with_retry( + kube_apis.v1, + expected_conf_line, + vs_externalname_setup.external_host, + virtual_server_setup.vs_name, + virtual_server_setup.namespace, + vs_externalname_setup.ic_pod_name, + ingress_controller_prerequisites.namespace, + ) + + assert "least_conn;" in result_conf + assert expected_conf_line in result_conf + + print("\nStep 4: Scale deployment back to 2 replicas") + scale_deployment(kube_apis.v1, kube_apis.apps_v1_api, "backend1", virtual_server_setup.namespace, 2) + wait_before_test() + + print("\nStep 5: Get response") + res_after_scaleup = make_request( + virtual_server_setup.backend_1_url, + virtual_server_setup.vs_host, + ) + + assert res_after_scaleup.status_code == 200 + assert "external-backend" not in res_after_scaleup.text + assert "backend1-" in res_after_scaleup.text + + result_conf = get_result_in_conf_with_retry( + kube_apis.v1, + expected_conf_line, + vs_externalname_setup.external_host, + virtual_server_setup.vs_name, + virtual_server_setup.namespace, + vs_externalname_setup.ic_pod_name, + ingress_controller_prerequisites.namespace, + ) + + assert "least_conn;" in result_conf + assert expected_conf_line in result_conf + + """ + This test validates that getting an error response after deleting the backup service. + """ + + def test_delete_backup_service( + self, + kube_apis, + ingress_controller_prerequisites, + crd_ingress_controller, + virtual_server_setup, + vs_externalname_setup, + ) -> None: + vs_backup_service = f"{TEST_DATA}/virtual-server-backup-service/virtual-server-backup.yaml" + delete_and_create_vs_from_yaml( + kube_apis.custom_objects, + virtual_server_setup.vs_name, + vs_backup_service, + virtual_server_setup.namespace, + ) + wait_before_test() + + print("\nStep 1: Get response from VS with backup service") + print(virtual_server_setup.backend_1_url + "\n") + res = make_request( + virtual_server_setup.backend_1_url, + virtual_server_setup.vs_host, + ) + + assert res.status_code == 200 + assert "backend1-" in res.text + assert "external-backend" not in res.text + + expected_conf_line = f"server {vs_externalname_setup.external_host}:80 backup resolve;" + result_conf = get_result_in_conf_with_retry( + kube_apis.v1, + expected_conf_line, + vs_externalname_setup.external_host, + virtual_server_setup.vs_name, + virtual_server_setup.namespace, + vs_externalname_setup.ic_pod_name, + ingress_controller_prerequisites.namespace, + ) + + assert "least_conn;" in result_conf + assert expected_conf_line in result_conf + + print("\nStep 2: Scale deployment to zero replicas") + scale_deployment(kube_apis.v1, kube_apis.apps_v1_api, "backend1", virtual_server_setup.namespace, 0) + wait_before_test() + + print("\nStep 3: Get response from backup service") + res_from_backup = make_request( + virtual_server_setup.backend_1_url, + virtual_server_setup.vs_host, + ) + + assert res_from_backup.status_code == 200 + assert "external-backend" in res_from_backup.text + assert "backend1-" not in res_from_backup.text + + result_conf = get_result_in_conf_with_retry( + kube_apis.v1, + expected_conf_line, + vs_externalname_setup.external_host, + virtual_server_setup.vs_name, + virtual_server_setup.namespace, + vs_externalname_setup.ic_pod_name, + ingress_controller_prerequisites.namespace, + ) + + assert "least_conn;" in result_conf + assert expected_conf_line in result_conf + + print("\nStep 4: Delete backup service by deleting the namespace") + delete_namespace(kube_apis.v1, "external-ns") + wait_before_test() + + print("\nStep 5: Get response") + res_after_delete = make_request( + virtual_server_setup.backend_1_url, + virtual_server_setup.vs_host, + ) + + assert res_after_delete.status_code != 200 + + # Re-add the external-ns namespace. + # This is done to ensure the vs_externalname_setup will cleanup correctly. + create_namespace_with_name_from_yaml(kube_apis.v1, "external-ns", f"{TEST_DATA}/common/ns.yaml") diff --git a/tests/suite/test_virtual_server_canned_responses.py b/tests/suite/test_virtual_server_canned_responses.py index 0248b0fa05..adde0e1219 100644 --- a/tests/suite/test_virtual_server_canned_responses.py +++ b/tests/suite/test_virtual_server_canned_responses.py @@ -15,6 +15,7 @@ @pytest.mark.vs +@pytest.mark.vs_responses @pytest.mark.parametrize( "crd_ingress_controller, virtual_server_setup", [ @@ -73,7 +74,11 @@ def test_default_canned_response(self, kube_apis, crd_ingress_controller, virtua wait_and_assert_status_code(200, virtual_server_setup.backend_2_url, virtual_server_setup.vs_host) resp = requests.get(virtual_server_setup.backend_2_url, headers={"host": virtual_server_setup.vs_host}) resp_content = resp.content.decode("utf-8") - assert resp.headers["content-type"] == "text/plain" and resp_content == "line1\nline2\nline3\n" + assert ( + resp.headers["content-type"] == "text/plain" + and resp_content == "line1\nline2\nline3\n" + and resp.headers["coffee-test-header"] == "espresso" + ) def test_update(self, kube_apis, crd_ingress_controller, virtual_server_setup): wait_before_test(1) @@ -93,7 +98,11 @@ def test_update(self, kube_apis, crd_ingress_controller, virtual_server_setup): wait_and_assert_status_code(201, virtual_server_setup.backend_2_url, virtual_server_setup.vs_host) resp = requests.get(virtual_server_setup.backend_2_url, headers={"host": virtual_server_setup.vs_host}) resp_content = resp.content.decode("utf-8") - assert resp.headers["content-type"] == "user-type" and resp_content == "line1\nline2" + assert ( + resp.headers["content-type"] == "user-type" + and resp_content == "line1\nline2" + and resp.headers["coffee-test-header"] == "latte" + ) vs_events = get_events(kube_apis.v1, virtual_server_setup.namespace) assert_event_count_increased(vs_event_text, initial_count, vs_events) @@ -135,6 +144,7 @@ def test_openapi_validation_flow( and "action.return.type in body must be of type" in ex.body and "action.return.body in body must be of type" in ex.body and "action.return.code in body must be of type" in ex.body + and "action.return.headers in body must be of type" in ex.body ) except Exception as ex: pytest.fail(f"An unexpected exception is raised: {ex}") diff --git a/tests/suite/test_virtual_server_certmanager.py b/tests/suite/test_virtual_server_certmanager.py index 87a4447be0..e860317373 100644 --- a/tests/suite/test_virtual_server_certmanager.py +++ b/tests/suite/test_virtual_server_certmanager.py @@ -1,12 +1,18 @@ import pytest from settings import TEST_DATA from suite.utils.custom_assertions import wait_and_assert_status_code -from suite.utils.resources_utils import create_secret_from_yaml, is_secret_present, wait_before_test +from suite.utils.resources_utils import ( + create_secret_from_yaml, + is_secret_present, + patch_namespace_with_label, + wait_before_test, +) from suite.utils.vs_vsr_resources_utils import patch_virtual_server_from_yaml -from suite.utils.yaml_utils import get_secret_name_from_vs_yaml +from suite.utils.yaml_utils import get_secret_name_from_vs_or_ts_yaml @pytest.mark.vs +@pytest.mark.vs_certmanager @pytest.mark.smoke @pytest.mark.parametrize( "crd_ingress_controller, create_certmanager, virtual_server_setup", @@ -22,7 +28,7 @@ class TestCertManagerVirtualServer: def test_responses_after_setup(self, kube_apis, crd_ingress_controller, create_certmanager, virtual_server_setup): print("\nStep 1: Verify secret exists") - secret_name = get_secret_name_from_vs_yaml( + secret_name = get_secret_name_from_vs_or_ts_yaml( f"{TEST_DATA}/virtual-server-certmanager/standard/virtual-server.yaml" ) retry = 0 @@ -37,6 +43,7 @@ def test_responses_after_setup(self, kube_apis, crd_ingress_controller, create_c @pytest.mark.vs +@pytest.mark.vs_certmanager @pytest.mark.smoke @pytest.mark.parametrize( "crd_ingress_controller, create_certmanager, virtual_server_setup", @@ -53,7 +60,7 @@ class TestCertManagerVirtualServerCA: def test_responses_after_setup(self, kube_apis, crd_ingress_controller, create_certmanager, virtual_server_setup): vs_src = f"{TEST_DATA}/virtual-server-certmanager/virtual-server-updated.yaml" print("\nStep 1: Verify no secret exists with bad issuer name") - secret_name = get_secret_name_from_vs_yaml( + secret_name = get_secret_name_from_vs_or_ts_yaml( f"{TEST_DATA}/virtual-server-certmanager/standard/virtual-server.yaml" ) sec = is_secret_present(kube_apis.v1, secret_name, virtual_server_setup.namespace) @@ -62,7 +69,7 @@ def test_responses_after_setup(self, kube_apis, crd_ingress_controller, create_c kube_apis.custom_objects, virtual_server_setup.vs_name, vs_src, virtual_server_setup.namespace ) print("\nStep 2: Verify secret exists with updated issuer name") - secret_name = get_secret_name_from_vs_yaml( + secret_name = get_secret_name_from_vs_or_ts_yaml( f"{TEST_DATA}/virtual-server-certmanager/virtual-server-updated.yaml" ) @@ -94,3 +101,54 @@ def test_virtual_server_no_cm(self, kube_apis, crd_ingress_controller, create_ce ) wait_and_assert_status_code(200, virtual_server_setup.backend_1_url, virtual_server_setup.vs_host) wait_and_assert_status_code(200, virtual_server_setup.backend_2_url, virtual_server_setup.vs_host) + + +@pytest.mark.vs +@pytest.mark.vs_certmanager +@pytest.mark.smoke +@pytest.mark.parametrize( + "crd_ingress_controller, create_certmanager, virtual_server_setup", + [ + ( + { + "type": "complete", + "extra_args": [ + f"-enable-custom-resources", + f"-enable-cert-manager", + f"-watch-namespace-label=app=watch", + ], + }, + {"issuer_name": "self-signed"}, + {"example": "virtual-server-certmanager", "app_type": "simple"}, + ) + ], + indirect=True, +) +class TestCertManagerVirtualServerWatchLabel: + def test_responses_after_setup( + self, kube_apis, crd_ingress_controller, create_certmanager, virtual_server_setup, test_namespace + ): + print("\nStep 1: Not watching namespace - verify secret does not exist") + secret_name = get_secret_name_from_vs_or_ts_yaml( + f"{TEST_DATA}/virtual-server-certmanager/standard/virtual-server.yaml" + ) + # add a wait to avoid a false negative + wait_before_test(10) + check = is_secret_present(kube_apis.v1, secret_name, virtual_server_setup.namespace) + assert check == False + + print("\nStep 2: Add label to namespace - Verify secret exists now") + patch_namespace_with_label(kube_apis.v1, test_namespace, "watch", f"{TEST_DATA}/common/ns-patch.yaml") + wait_before_test() + secret_name = get_secret_name_from_vs_or_ts_yaml( + f"{TEST_DATA}/virtual-server-certmanager/standard/virtual-server.yaml" + ) + retry = 0 + while (not is_secret_present(kube_apis.v1, secret_name, virtual_server_setup.namespace)) and retry <= 10: + wait_before_test(1) + retry += 1 + print(f"Retrying {retry}") + + print("\nStep 2: verify connectivity") + wait_and_assert_status_code(200, virtual_server_setup.backend_1_url, virtual_server_setup.vs_host) + wait_and_assert_status_code(200, virtual_server_setup.backend_2_url, virtual_server_setup.vs_host) diff --git a/tests/suite/test_virtual_server_configmap_keys.py b/tests/suite/test_virtual_server_configmap_keys.py index 4a77ba524f..3fd4c6345d 100644 --- a/tests/suite/test_virtual_server_configmap_keys.py +++ b/tests/suite/test_virtual_server_configmap_keys.py @@ -2,6 +2,7 @@ from settings import DEPLOYMENTS, TEST_DATA from suite.utils.resources_utils import ( get_events, + get_events_for_object, get_file_contents, get_first_pod_name, get_pods_amount, @@ -114,13 +115,31 @@ def assert_defaults_of_keys_with_validation_in_main_config(config, unexpected_va def assert_ssl_keys(config): # based on f"{TEST_DATA}/virtual-server-configmap-keys/configmap-ssl-keys.yaml" assert "if ($schema = 'http') {" not in config - assert "listen 443 ssl http2 proxy_protocol;" in config + assert "listen 443 ssl proxy_protocol;" in config + assert "http2 on;" in config def assert_defaults_of_ssl_keys(config): assert "if ($schema = 'http') {" not in config assert "listen 443 ssl;" in config - assert "http2" not in config + assert "http2 on;" not in config + + +def assert_event(event_list, event_type, reason, message_substring): + """ + Assert that an event with specific type, reason, and message substring exists. + + :param event_list: List of events + :param event_type: 'Normal' or 'Warning' + :param reason: Event reason + :param message_substring: Substring expected in the event message + """ + for event in event_list: + if event.type == event_type and event.reason == reason and message_substring in event.message: + return + assert ( + False + ), f"Expected event with type '{event_type}', reason '{reason}', and message containing '{message_substring}' not found." @pytest.fixture(scope="function") @@ -136,17 +155,19 @@ def clean_up(request, kube_apis, ingress_controller_prerequisites, test_namespac """ def fin(): - replace_configmap_from_yaml( - kube_apis.v1, - ingress_controller_prerequisites.config_map["metadata"]["name"], - ingress_controller_prerequisites.namespace, - f"{DEPLOYMENTS}/common/nginx-config.yaml", - ) + if request.config.getoption("--skip-fixture-teardown") == "no": + replace_configmap_from_yaml( + kube_apis.v1, + ingress_controller_prerequisites.config_map["metadata"]["name"], + ingress_controller_prerequisites.namespace, + f"{DEPLOYMENTS}/common/nginx-config.yaml", + ) request.addfinalizer(fin) @pytest.mark.vs +@pytest.mark.vs_config_map @pytest.mark.parametrize( "crd_ingress_controller, virtual_server_setup", [ @@ -340,6 +361,7 @@ def test_keys_in_main_config( @pytest.mark.vs +@pytest.mark.vs_config_map @pytest.mark.parametrize( "crd_ingress_controller, virtual_server_setup", [ @@ -360,7 +382,6 @@ def test_ssl_keys( virtual_server_setup, clean_up, ): - ic_pods_amount = get_pods_amount(kube_apis.v1, ingress_controller_prerequisites.namespace) ic_pod_name = get_first_pod_name(kube_apis.v1, ingress_controller_prerequisites.namespace) initial_list = get_events(kube_apis.v1, virtual_server_setup.namespace) @@ -401,3 +422,69 @@ def test_ssl_keys( ) assert_update_event_count_increased(virtual_server_setup, step_2_events, step_1_events) assert_defaults_of_ssl_keys(step_2_config) + + def test_configmap_events( + self, + kube_apis, + ingress_controller_prerequisites, + crd_ingress_controller, + virtual_server_setup, + clean_up, + ): + ingress_controller_prerequisites.namespace + configmap_name = ingress_controller_prerequisites.config_map["metadata"]["name"] + configmap_namespace = ingress_controller_prerequisites.config_map["metadata"]["namespace"] + + # Step 1: Update ConfigMap with valid parameters + print("Updating ConfigMap with valid parameters") + replace_configmap_from_yaml( + kube_apis.v1, + configmap_name, + configmap_namespace, + f"{TEST_DATA}/virtual-server-configmap-keys/configmap-valid.yaml", + ) + wait_before_test(1) + + # Get events for the ConfigMap + events = get_events_for_object( + kube_apis.v1, + configmap_namespace, + configmap_name, + ) + + assert len(events) >= 1 + + # Assert that the 'updated without error' event is present + assert_event( + events, + "Normal", + "Updated", + f"ConfigMap {configmap_namespace}/{configmap_name} updated without error", + ) + + # Step 2: Update ConfigMap with invalid parameters + print("Updating ConfigMap with invalid parameters") + replace_configmap_from_yaml( + kube_apis.v1, + configmap_name, + configmap_namespace, + f"{TEST_DATA}/virtual-server-configmap-keys/configmap-invalid.yaml", + ) + wait_before_test(1) + + # Get events for the ConfigMap + events = get_events_for_object( + kube_apis.v1, + configmap_namespace, + configmap_name, + ) + + assert len(events) >= 1 + + # Assert that the 'updated with errors' event is present + assert_event( + events, + "Warning", + "UpdatedWithError", + f"ConfigMap {configmap_namespace}/{configmap_name} updated with errors. Ignoring invalid values", + ) diff --git a/tests/suite/test_virtual_server_custom_ip_listeners.py b/tests/suite/test_virtual_server_custom_ip_listeners.py new file mode 100644 index 0000000000..93ba1fde29 --- /dev/null +++ b/tests/suite/test_virtual_server_custom_ip_listeners.py @@ -0,0 +1,221 @@ +from typing import List, TypedDict + +import pytest +import requests +from settings import TEST_DATA +from suite.utils.custom_resources_utils import create_gc_from_yaml, delete_gc +from suite.utils.resources_utils import ( + create_secret_from_yaml, + delete_secret, + get_events_for_object, + get_first_pod_name, + wait_before_test, +) +from suite.utils.vs_vsr_resources_utils import get_vs_nginx_template_conf, patch_virtual_server_from_yaml, read_vs + + +def make_request(url, host): + return requests.get( + url, + headers={"host": host}, + allow_redirects=False, + verify=False, + ) + + +def restore_default_vs(kube_apis, virtual_server_setup) -> None: + """ + Function to revert VS deployment to valid state. + """ + patch_src = f"{TEST_DATA}/virtual-server-status/standard/virtual-server.yaml" + patch_virtual_server_from_yaml( + kube_apis.custom_objects, + virtual_server_setup.vs_name, + patch_src, + virtual_server_setup.namespace, + ) + wait_before_test() + + +@pytest.mark.vs +@pytest.mark.parametrize( + "crd_ingress_controller, virtual_server_setup", + [ + ( + { + "type": "complete", + "extra_args": [ + f"-global-configuration=nginx-ingress/nginx-configuration", + f"-enable-leader-election=false", + f"-enable-prometheus-metrics=true", + ], + }, + { + "example": "virtual-server-custom-listeners", + "app_type": "simple", + }, + ) + ], + indirect=True, +) +class TestVirtualServerCustomListeners: + TestSetup = TypedDict( + "TestSetup", + { + "gc_yaml": str, + "vs_yaml": str, + "http_listener_in_config": bool, + "https_listener_in_config": bool, + "expected_response_codes": List[int], # responses from requests to port 80, 443, 8085, 8445 + "expected_http_listener_ipv4ip": str, + "expected_https_listener_ipv4ip": str, + "expected_http_listener_ipv6ip": str, + "expected_https_listener_ipv6ip": str, + "expected_vs_error_msg": str, + "expected_gc_error_msg": str, + }, + ) + + @pytest.mark.parametrize( + "test_setup", + [ + { + "gc_yaml": "global-configuration-http-https-ipv4ip-http-https-ipv6ip", + "vs_yaml": "virtual-server", + "http_listener_in_config": True, + "https_listener_in_config": True, + "expected_response_codes": [200, 200], + "expected_http_listener_ipv4ip": "127.0.0.1", + "expected_https_listener_ipv4ip": "127.0.0.2", + "expected_http_listener_ipv6ip": "::1", + "expected_https_listener_ipv6ip": "::1", + "expected_vs_error_msg": "", + "expected_gc_error_msg": "", + }, + { + "gc_yaml": "global-configuration-http-ipv4ip-https-ipv6ip", + "vs_yaml": "virtual-server", + "http_listener_in_config": True, + "https_listener_in_config": True, + "expected_response_codes": [200, 200], + "expected_http_listener_ipv4ip": "127.0.0.1", + "expected_https_listener_ipv4ip": "", + "expected_http_listener_ipv6ip": "", + "expected_https_listener_ipv6ip": "::1", + "expected_vs_error_msg": "", + "expected_gc_error_msg": "", + }, + ], + ids=[ + "http-https-ipv4ip-http-https-ipv6ip", + "http-ipv4ip-https-ipv6ip", + ], + ) + def test_custom_listeners_update( + self, + kube_apis, + ingress_controller_prerequisites, + crd_ingress_controller, + virtual_server_setup, + test_setup: TestSetup, + ) -> None: + print("\nStep 1: Create GC resource") + secret_name = create_secret_from_yaml( + kube_apis.v1, virtual_server_setup.namespace, f"{TEST_DATA}/virtual-server-tls/tls-secret.yaml" + ) + if test_setup["gc_yaml"]: + global_config_file = f"{TEST_DATA}/virtual-server-custom-listeners/{test_setup['gc_yaml']}.yaml" + gc_resource = create_gc_from_yaml(kube_apis.custom_objects, global_config_file, "nginx-ingress") + + print("\nStep 2: Create VS with custom listeners") + vs_custom_listeners = f"{TEST_DATA}/virtual-server-custom-listeners/{test_setup['vs_yaml']}.yaml" + patch_virtual_server_from_yaml( + kube_apis.custom_objects, + virtual_server_setup.vs_name, + vs_custom_listeners, + virtual_server_setup.namespace, + ) + print("IP Listeners Detected - Waiting 30 Extra Seconds Required") + wait_before_test(30) + + print("\nStep 3: Test generated VS configs") + ic_pod_name = get_first_pod_name(kube_apis.v1, ingress_controller_prerequisites.namespace) + vs_config = get_vs_nginx_template_conf( + kube_apis.v1, + virtual_server_setup.namespace, + virtual_server_setup.vs_name, + ic_pod_name, + ingress_controller_prerequisites.namespace, + ) + + print(vs_config) + + if "http_listener_in_config" in test_setup and test_setup["http_listener_in_config"]: + if "expected_http_listener_ipv4ip" in test_setup and test_setup["expected_http_listener_ipv4ip"]: + assert f"listen {test_setup['expected_http_listener_ipv4ip']}:8085;" in vs_config + else: + assert "listen 8085;" in vs_config + + if "expected_http_listener_ipv6ip" in test_setup and test_setup["expected_http_listener_ipv6ip"]: + assert f"listen [{test_setup['expected_http_listener_ipv6ip']}]:8085;" in vs_config + else: + assert "listen [::]:8085;" in vs_config + else: + assert "listen 8085;" not in vs_config + assert "listen [::]:8085;" not in vs_config + + if "https_listener_in_config" in test_setup and test_setup["https_listener_in_config"]: + if "expected_https_listener_ipv4ip" in test_setup and test_setup["expected_https_listener_ipv4ip"]: + assert f"listen {test_setup['expected_https_listener_ipv4ip']}:8445 ssl;" in vs_config + else: + assert "listen 8445 ssl;" in vs_config + + if "expected_https_listener_ipv6ip" in test_setup and test_setup["expected_https_listener_ipv6ip"]: + assert f"listen [{test_setup['expected_https_listener_ipv6ip']}]:8445 ssl;" in vs_config + else: + assert "listen [::]:8445 ssl;" in vs_config + else: + assert "listen 8445 ssl;" not in vs_config + assert "listen [::]:8445 ssl;" not in vs_config + + assert "listen 80;" not in vs_config + assert "listen [::]:80;" not in vs_config + assert "listen 443 ssl;" not in vs_config + assert "listen [::]:443 ssl;" not in vs_config + + print("\nStep 4: Test Kubernetes VirtualServer warning events") + if test_setup["expected_vs_error_msg"]: + response = read_vs(kube_apis.custom_objects, virtual_server_setup.namespace, virtual_server_setup.vs_name) + print(response) + assert ( + response["status"]["reason"] == "AddedOrUpdatedWithWarning" + and response["status"]["state"] == "Warning" + and test_setup["expected_vs_error_msg"] in response["status"]["message"] + ) + + print("\nStep 5: Test Kubernetes GlobalConfiguration warning events") + if test_setup["gc_yaml"]: + gc_events = get_events_for_object(kube_apis.v1, "nginx-ingress", "nginx-configuration") + gc_event_latest = gc_events[-1] + print(gc_event_latest) + if test_setup["expected_gc_error_msg"]: + assert ( + gc_event_latest.reason == "AddedOrUpdatedWithError" + and gc_event_latest.type == "Warning" + and test_setup["expected_gc_error_msg"] in gc_event_latest.message + ) + else: + assert ( + gc_event_latest.reason == "Updated" + and gc_event_latest.type == "Normal" + and "GlobalConfiguration nginx-ingress/nginx-configuration was added or updated" + in gc_event_latest.message + ) + + print("\nStep 6: Restore test environments") + delete_secret(kube_apis.v1, secret_name, virtual_server_setup.namespace) + restore_default_vs(kube_apis, virtual_server_setup) + if test_setup["gc_yaml"]: + delete_gc(kube_apis.custom_objects, gc_resource, "nginx-ingress") + print(f"deleted GC : {gc_resource}") + wait_before_test(10) diff --git a/tests/suite/test_virtual_server_custom_listeners.py b/tests/suite/test_virtual_server_custom_listeners.py new file mode 100644 index 0000000000..034ed26f5f --- /dev/null +++ b/tests/suite/test_virtual_server_custom_listeners.py @@ -0,0 +1,495 @@ +from typing import List, TypedDict + +import pytest +import requests +from requests.exceptions import ConnectionError +from settings import TEST_DATA +from suite.utils.custom_resources_utils import create_gc_from_yaml, delete_gc, patch_gc_from_yaml +from suite.utils.resources_utils import ( + create_secret_from_yaml, + delete_secret, + get_events_for_object, + get_first_pod_name, + wait_before_test, +) +from suite.utils.vs_vsr_resources_utils import get_vs_nginx_template_conf, patch_virtual_server_from_yaml, read_vs + + +def make_request(url, host): + return requests.get( + url, + headers={"host": host}, + allow_redirects=False, + verify=False, + ) + + +def restore_default_vs(kube_apis, virtual_server_setup) -> None: + """ + Function to revert vs deployment to valid state + """ + patch_src = f"{TEST_DATA}/virtual-server-status/standard/virtual-server.yaml" + patch_virtual_server_from_yaml( + kube_apis.custom_objects, + virtual_server_setup.vs_name, + patch_src, + virtual_server_setup.namespace, + ) + wait_before_test() + + +@pytest.mark.vs +@pytest.mark.parametrize( + "crd_ingress_controller, virtual_server_setup", + [ + ( + { + "type": "complete", + "extra_args": [ + f"-global-configuration=nginx-ingress/nginx-configuration", + f"-enable-leader-election=false", + f"-enable-prometheus-metrics=true", + ], + }, + { + "example": "virtual-server-custom-listeners", + "app_type": "simple", + }, + ) + ], + indirect=True, +) +class TestVirtualServerCustomListeners: + TestSetup = TypedDict( + "TestSetup", + { + "gc_yaml": str, + "vs_yaml": str, + "http_listener_in_config": bool, + "https_listener_in_config": bool, + "expected_response_codes": List[int], # responses from requests to port 80, 433, 8085, 8445 + "expected_vs_error_msg": str, + "expected_gc_error_msg": str, + }, + ) + + @pytest.mark.parametrize( + "test_setup", + [ + { + "gc_yaml": "global-configuration", + "vs_yaml": "virtual-server", + "http_listener_in_config": True, + "https_listener_in_config": True, + "expected_response_codes": [404, 404, 200, 200], + "expected_vs_error_msg": "", + "expected_gc_error_msg": "", + }, + { + "gc_yaml": "global-configuration-missing-http", + "vs_yaml": "virtual-server", + "http_listener_in_config": False, + "https_listener_in_config": True, + "expected_response_codes": [404, 404, 0, 200], + "expected_vs_error_msg": "Listener http-8085 is not defined in GlobalConfiguration", + "expected_gc_error_msg": "", + }, + { + "gc_yaml": "global-configuration-missing-https", + "vs_yaml": "virtual-server", + "http_listener_in_config": True, + "https_listener_in_config": False, + "expected_response_codes": [404, 404, 200, 0], + "expected_vs_error_msg": "Listener https-8445 is not defined in GlobalConfiguration", + "expected_gc_error_msg": "", + }, + { + "gc_yaml": "global-configuration-missing-http-https", + "vs_yaml": "virtual-server", + "http_listener_in_config": False, + "https_listener_in_config": False, + "expected_response_codes": [404, 404, 0, 0], + "expected_vs_error_msg": "Listeners defined, but no GlobalConfiguration is deployed", + "expected_gc_error_msg": "", + }, + { + "gc_yaml": "global-configuration", + "vs_yaml": "virtual-server-http-listener-in-https-block", + "http_listener_in_config": False, + "https_listener_in_config": False, + "expected_response_codes": [404, 404, 0, 0], + "expected_vs_error_msg": "Listener http-8085 can't be use in `listener.https` context as SSL is not " + "enabled for that listener", + "expected_gc_error_msg": "", + }, + { + "gc_yaml": "global-configuration", + "vs_yaml": "virtual-server-https-listener-in-http-block", + "http_listener_in_config": False, + "https_listener_in_config": False, + "expected_response_codes": [404, 404, 0, 0], + "expected_vs_error_msg": "Listener https-8445 can't be use in `listener.http` context as SSL is enabled " + "for that listener.", + "expected_gc_error_msg": "", + }, + { + "gc_yaml": "global-configuration", + "vs_yaml": "virtual-server-http-https-listeners-switched", + "http_listener_in_config": False, + "https_listener_in_config": False, + "expected_response_codes": [404, 404, 0, 0], + "expected_vs_error_msg": "Listener https-8445 can't be use in `listener.http` context as SSL is enabled " + "for that listener.", + "expected_gc_error_msg": "", + }, + { + "gc_yaml": "", + "vs_yaml": "virtual-server", + "http_listener_in_config": False, + "https_listener_in_config": False, + "expected_response_codes": [404, 404, 0, 0], + "expected_vs_error_msg": "Listeners defined, but no GlobalConfiguration is deployed", + "expected_gc_error_msg": "", + }, + { + "gc_yaml": "global-configuration-repeated-http-port", + "vs_yaml": "virtual-server", + "http_listener_in_config": False, + "https_listener_in_config": True, + "expected_response_codes": [404, 404, 0, 200], + "expected_vs_error_msg": "Listener http-8085 is not defined in GlobalConfiguration", + "expected_gc_error_msg": "Listener http-8085: Duplicated ip:port protocol combination 0.0.0.0:8085 HTTP", + }, + { + "gc_yaml": "global-configuration-forbidden-port-http", + "vs_yaml": "virtual-server", + "http_listener_in_config": False, + "https_listener_in_config": True, + "expected_response_codes": [404, 404, 0, 200], + "expected_vs_error_msg": "Listener http-8085 is not defined in GlobalConfiguration", + "expected_gc_error_msg": "Listener http-8085: port 9113 is forbidden", + }, + { + "gc_yaml": "global-configuration-forbidden-port-preceding-udp", + "vs_yaml": "virtual-server", + "http_listener_in_config": True, + "https_listener_in_config": True, + "expected_response_codes": [404, 404, 200, 200], + "expected_vs_error_msg": "", + "expected_gc_error_msg": "Listener dns-udp: port 9113 is forbidden", + }, + ], + ids=[ + "valid_config", + "global_configuration_missing_http_listener", + "global_configuration_missing_https_listener", + "global_configuration_missing_both_http_and_https_listeners", + "http_listener_in_https_block", + "https_listener_in_http_block", + "http_https_listeners_switched", + "no_global_configuration", + "update_gc_http_listener_repeated_port", + "update_gc_http_listener_forbidden_port", + "update_gc_ts_listener_forbidden_port", + ], + ) + def test_custom_listeners( + self, + kube_apis, + ingress_controller_prerequisites, + crd_ingress_controller, + virtual_server_setup, + test_setup: TestSetup, + ) -> None: + print("\nStep 1: Create GC resource") + secret_name = create_secret_from_yaml( + kube_apis.v1, virtual_server_setup.namespace, f"{TEST_DATA}/virtual-server-tls/tls-secret.yaml" + ) + if test_setup["gc_yaml"]: + global_config_file = f"{TEST_DATA}/virtual-server-custom-listeners/{test_setup['gc_yaml']}.yaml" + gc_resource = create_gc_from_yaml(kube_apis.custom_objects, global_config_file, "nginx-ingress") + + print("\nStep 2: Create VS with custom listeners") + vs_custom_listeners = f"{TEST_DATA}/virtual-server-custom-listeners/{test_setup['vs_yaml']}.yaml" + patch_virtual_server_from_yaml( + kube_apis.custom_objects, + virtual_server_setup.vs_name, + vs_custom_listeners, + virtual_server_setup.namespace, + ) + wait_before_test() + + print("\nStep 3: Test generated VS configs") + ic_pod_name = get_first_pod_name(kube_apis.v1, ingress_controller_prerequisites.namespace) + vs_config = get_vs_nginx_template_conf( + kube_apis.v1, + virtual_server_setup.namespace, + virtual_server_setup.vs_name, + ic_pod_name, + ingress_controller_prerequisites.namespace, + ) + + print(vs_config) + + if test_setup["http_listener_in_config"]: + assert "listen 8085;" in vs_config + assert "listen [::]:8085;" in vs_config + + else: + assert "listen 8085;" not in vs_config + assert "listen [::]:8085;" not in vs_config + + if test_setup["https_listener_in_config"]: + assert "listen 8445 ssl;" in vs_config + assert "listen [::]:8445 ssl;" in vs_config + else: + assert "listen 8445 ssl;" not in vs_config + assert "listen [::]:8445 ssl;" not in vs_config + + assert "listen 80;" not in vs_config + assert "listen [::]:80;" not in vs_config + assert "listen 443 ssl;" not in vs_config + assert "listen [::]:443 ssl;" not in vs_config + + print("\nStep 4: Test HTTP responses") + urls = [ + virtual_server_setup.backend_1_url, + virtual_server_setup.backend_1_url_ssl, + virtual_server_setup.backend_1_url_custom, + virtual_server_setup.backend_1_url_custom_ssl, + ] + for url, expected_response in zip(urls, test_setup["expected_response_codes"]): + if expected_response > 0: + res = make_request(url, virtual_server_setup.vs_host) + assert res.status_code == expected_response + else: + with pytest.raises(ConnectionError, match="Connection refused") as e: + make_request(url, virtual_server_setup.vs_host) + + print("\nStep 5: Test Kubernetes VirtualServer warning events") + if test_setup["expected_vs_error_msg"]: + response = read_vs(kube_apis.custom_objects, virtual_server_setup.namespace, virtual_server_setup.vs_name) + print(response) + assert ( + response["status"]["reason"] == "AddedOrUpdatedWithWarning" + and response["status"]["state"] == "Warning" + and test_setup["expected_vs_error_msg"] in response["status"]["message"] + ) + + print("\nStep 6: Test Kubernetes GlobalConfiguration warning events") + if test_setup["gc_yaml"]: + gc_events = get_events_for_object(kube_apis.v1, "nginx-ingress", "nginx-configuration") + gc_event_latest = gc_events[-1] + print(gc_event_latest) + if test_setup["expected_gc_error_msg"]: + assert ( + gc_event_latest.reason == "AddedOrUpdatedWithError" + and gc_event_latest.type == "Warning" + and test_setup["expected_gc_error_msg"] in gc_event_latest.message + ) + else: + assert ( + gc_event_latest.reason == "Updated" + and gc_event_latest.type == "Normal" + and "GlobalConfiguration nginx-ingress/nginx-configuration was added " + "or updated" in gc_event_latest.message + ) + + print("\nStep 7: Restore test environments") + delete_secret(kube_apis.v1, secret_name, virtual_server_setup.namespace) + restore_default_vs(kube_apis, virtual_server_setup) + if test_setup["gc_yaml"]: + delete_gc(kube_apis.custom_objects, gc_resource, "nginx-ingress") + + @pytest.mark.parametrize( + "test_setup", + [ + { + "gc_yaml": "", # delete gc if empty + "vs_yaml": "virtual-server", + "http_listener_in_config": False, + "https_listener_in_config": False, + "expected_response_codes": [404, 404, 0, 0], + "expected_vs_error_msg": "Listeners defined, but no GlobalConfiguration is deployed", + "expected_gc_error_msg": "", + }, + { + "gc_yaml": "global-configuration-https-listener-without-ssl", + "vs_yaml": "virtual-server", + "http_listener_in_config": True, + "https_listener_in_config": False, + "expected_response_codes": [404, 404, 200, 0], + "expected_vs_error_msg": "Listener https-8445 can't be use in `listener.https` context as SSL is not " + "enabled for that listener.", + "expected_gc_error_msg": "", + }, + { + "gc_yaml": "global-configuration-http-listener-with-ssl", + "vs_yaml": "virtual-server", + "http_listener_in_config": False, + "https_listener_in_config": True, + "expected_response_codes": [404, 404, 0, 200], + "expected_vs_error_msg": "Listener http-8085 can't be use in `listener.http` context as SSL is enabled", + "expected_gc_error_msg": "", + }, + { + "gc_yaml": "global-configuration-repeated-http-port", + "vs_yaml": "virtual-server", + "http_listener_in_config": False, + "https_listener_in_config": True, + "expected_response_codes": [404, 404, 0, 200], + "expected_vs_error_msg": "Listener http-8085 is not defined in GlobalConfiguration", + "expected_gc_error_msg": "Listener http-8085: Duplicated ip:port protocol combination 0.0.0.0:8085 HTTP", + }, + { + "gc_yaml": "global-configuration-forbidden-port-http", + "vs_yaml": "virtual-server", + "http_listener_in_config": False, + "https_listener_in_config": True, + "expected_response_codes": [404, 404, 0, 200], + "expected_vs_error_msg": "Listener http-8085 is not defined in GlobalConfiguration", + "expected_gc_error_msg": "Listener http-8085: port 9113 is forbidden", + }, + { + "gc_yaml": "global-configuration-forbidden-port-preceding-udp", + "vs_yaml": "virtual-server", + "http_listener_in_config": True, + "https_listener_in_config": True, + "expected_response_codes": [404, 404, 200, 200], + "expected_vs_error_msg": "", + "expected_gc_error_msg": "Listener dns-udp: port 9113 is forbidden", + }, + ], + ids=[ + "delete_gc", + "update_gc_https_listener_ssl_false", + "update_gc_http_listener_ssl_true", + "update_gc_http_listener_repeated_port", + "update_gc_http_listener_forbidden_port", + "update_gc_ts_listener_forbidden_port", + ], + ) + def test_custom_listeners_update( + self, + kube_apis, + ingress_controller_prerequisites, + crd_ingress_controller, + virtual_server_setup, + test_setup: TestSetup, + ) -> None: + # Deploy a working global config and virtual server, and then tests for errors after gc update + print("\nStep 1: Create GC resource") + secret_name = create_secret_from_yaml( + kube_apis.v1, virtual_server_setup.namespace, f"{TEST_DATA}/virtual-server-tls/tls-secret.yaml" + ) + global_config_file = f"{TEST_DATA}/virtual-server-custom-listeners/global-configuration.yaml" + gc_resource = create_gc_from_yaml(kube_apis.custom_objects, global_config_file, "nginx-ingress") + vs_custom_listeners = f"{TEST_DATA}/virtual-server-custom-listeners/virtual-server.yaml" + + print("\nStep 2: Create VS with custom listener (http-8085, https-8445)") + patch_virtual_server_from_yaml( + kube_apis.custom_objects, + virtual_server_setup.vs_name, + vs_custom_listeners, + virtual_server_setup.namespace, + ) + wait_before_test() + + urls = [ + virtual_server_setup.backend_1_url, + virtual_server_setup.backend_1_url_ssl, + virtual_server_setup.backend_1_url_custom, + virtual_server_setup.backend_1_url_custom_ssl, + ] + + for url, expected_response in zip(urls, [404, 404, 200, 200]): + if expected_response > 0: + res = make_request(url, virtual_server_setup.vs_host) + assert res.status_code == expected_response + else: + with pytest.raises(ConnectionError, match="Connection refused"): + make_request(url, virtual_server_setup.vs_host) + + print("\nStep 3: Apply gc or vs update") + if test_setup["gc_yaml"]: + global_config_file = f"{TEST_DATA}/virtual-server-custom-listeners/{test_setup['gc_yaml']}.yaml" + patch_gc_from_yaml( + kube_apis.custom_objects, gc_resource["metadata"]["name"], global_config_file, "nginx-ingress" + ) + else: + delete_gc(kube_apis.custom_objects, gc_resource, "nginx-ingress") + wait_before_test() + + print("\nStep 4: Test generated VS configs") + ic_pod_name = get_first_pod_name(kube_apis.v1, ingress_controller_prerequisites.namespace) + vs_config = get_vs_nginx_template_conf( + kube_apis.v1, + virtual_server_setup.namespace, + virtual_server_setup.vs_name, + ic_pod_name, + ingress_controller_prerequisites.namespace, + ) + print(vs_config) + + if test_setup["http_listener_in_config"]: + assert "listen 8085;" in vs_config + assert "listen [::]:8085;" in vs_config + else: + assert "listen 8085;" not in vs_config + assert "listen [::]:8085;" not in vs_config + + if test_setup["https_listener_in_config"]: + assert "listen 8445 ssl;" in vs_config + assert "listen [::]:8445 ssl;" in vs_config + else: + assert "listen 8445 ssl;" not in vs_config + assert "listen [::]:8445 ssl;" not in vs_config + + assert "listen 80;" not in vs_config + assert "listen [::]:80;" not in vs_config + assert "listen 443 ssl;" not in vs_config + assert "listen [::]:443 ssl;" not in vs_config + + print("\nStep 5: Test HTTP responses") + for url, expected_response in zip(urls, test_setup["expected_response_codes"]): + if expected_response > 0: + res = make_request(url, virtual_server_setup.vs_host) + assert res.status_code == expected_response + else: + with pytest.raises(ConnectionError, match="Connection refused"): + make_request(url, virtual_server_setup.vs_host) + + print("\nStep 6: Test Kubernetes VirtualServer warning events") + if test_setup["expected_vs_error_msg"]: + response = read_vs(kube_apis.custom_objects, virtual_server_setup.namespace, virtual_server_setup.vs_name) + print(response) + assert ( + response["status"]["reason"] == "AddedOrUpdatedWithWarning" + and response["status"]["state"] == "Warning" + and test_setup["expected_vs_error_msg"] in response["status"]["message"] + ) + + print("\nStep 7: Test Kubernetes GlobalConfiguration warning events") + if test_setup["gc_yaml"]: + gc_events = get_events_for_object(kube_apis.v1, "nginx-ingress", "nginx-configuration") + gc_event_latest = gc_events[-1] + print(gc_event_latest) + if test_setup["expected_gc_error_msg"]: + assert ( + gc_event_latest.reason == "AddedOrUpdatedWithError" + and gc_event_latest.type == "Warning" + and test_setup["expected_gc_error_msg"] in gc_event_latest.message + ) + else: + assert ( + gc_event_latest.reason == "Updated" + and gc_event_latest.type == "Normal" + and "GlobalConfiguration nginx-ingress/nginx-configuration was added " + "or updated" in gc_event_latest.message + ) + + print("\nStep 8: Restore test environments") + delete_secret(kube_apis.v1, secret_name, virtual_server_setup.namespace) + restore_default_vs(kube_apis, virtual_server_setup) + if test_setup["gc_yaml"]: + delete_gc(kube_apis.custom_objects, gc_resource, "nginx-ingress") diff --git a/tests/suite/test_virtual_server_dos.py b/tests/suite/test_virtual_server_dos.py index eeaba0fdd6..2f1a3263ea 100644 --- a/tests/suite/test_virtual_server_dos.py +++ b/tests/suite/test_virtual_server_dos.py @@ -61,15 +61,17 @@ def virtual_server_setup_dos(request, kube_apis, ingress_controller_endpoint, te vs_name = create_virtual_server_from_yaml(kube_apis.custom_objects, vs_source, test_namespace) vs_host = get_first_host_from_yaml(vs_source) vs_paths = get_paths_from_vs_yaml(vs_source) + vs_paths[0] += f"good_path.html" if request.param["app_type"]: create_example_app(kube_apis, request.param["app_type"], test_namespace) wait_until_all_pods_are_ready(kube_apis.v1, test_namespace) def fin(): - print("Clean up Virtual Server Example:") - delete_virtual_server(kube_apis.custom_objects, vs_name, test_namespace) - if request.param["app_type"]: - delete_common_app(kube_apis, request.param["app_type"], test_namespace) + if request.config.getoption("--skip-fixture-teardown") == "no": + print("Clean up Virtual Server Example:") + delete_virtual_server(kube_apis.custom_objects, vs_name, test_namespace) + if request.param["app_type"]: + delete_common_app(kube_apis, request.param["app_type"], test_namespace) request.addfinalizer(fin) @@ -81,12 +83,14 @@ class DosSetup: Encapsulate the example details. Attributes: req_url (str): + protected_name (str) pol_name (str): log_name (str): """ - def __init__(self, req_url, pol_name, log_name): + def __init__(self, req_url, protected_name, pol_name, log_name): self.req_url = req_url + self.protected_name = protected_name self.pol_name = pol_name self.log_name = log_name @@ -137,15 +141,16 @@ def dos_setup( nginx_reload(kube_apis.v1, item.metadata.name, ingress_controller_prerequisites.namespace) def fin(): - print("Clean up:") - delete_dos_policy(kube_apis.custom_objects, pol_name, test_namespace) - delete_dos_logconf(kube_apis.custom_objects, log_name, test_namespace) - delete_dos_protected(kube_apis.custom_objects, protected_name, test_namespace) - clean_good_bad_clients() + if request.config.getoption("--skip-fixture-teardown") == "no": + print("Clean up:") + delete_dos_policy(kube_apis.custom_objects, pol_name, test_namespace) + delete_dos_logconf(kube_apis.custom_objects, log_name, test_namespace) + delete_dos_protected(kube_apis.custom_objects, protected_name, test_namespace) + clean_good_bad_clients() request.addfinalizer(fin) - return DosSetup(req_url, pol_name, log_name) + return DosSetup(req_url, protected_name, pol_name, log_name) @pytest.mark.dos @@ -158,7 +163,7 @@ def fin(): "extra_args": [ f"-enable-custom-resources", f"-enable-app-protect-dos", - f"-v=3", + f"-log-level=debug", ], }, {"example": "virtual-server-dos", "app_type": "dos"}, @@ -235,6 +240,8 @@ def test_vs_with_dos_config( f"app_protect_dos_policy_file /etc/nginx/dos/policies/{test_namespace}_{dos_setup.pol_name}.json;", f"app_protect_dos_security_log_enable on;", f"app_protect_dos_security_log /etc/nginx/dos/logconfs/{test_namespace}_{dos_setup.log_name}.json syslog:server=syslog-svc.{ingress_controller_prerequisites.namespace}.svc.cluster.local:514;", + f"access_log syslog:server=accesslog-svc.{ingress_controller_prerequisites.namespace}.svc.cluster.local:514 log_dos if=$loggable;", + f'app_protect_dos_access_file "/etc/nginx/dos/allowlist/{test_namespace}_{dos_setup.protected_name}.json";', ] print("\n confirm response for standard request") diff --git a/tests/suite/test_virtual_server_error_pages.py b/tests/suite/test_virtual_server_error_pages.py index c793033e1e..b44b30f498 100644 --- a/tests/suite/test_virtual_server_error_pages.py +++ b/tests/suite/test_virtual_server_error_pages.py @@ -1,6 +1,4 @@ import json -from unittest import mock -from unittest.mock import Mock import pytest import requests @@ -42,8 +40,8 @@ def test_redirect_strategy(self, kube_apis, crd_ingress_controller, virtual_serv ) print(f"redirect to uri: {resp.next.url}") except AttributeError as e: - print(f"Exception occured: {e}") - retry = +1 + print(f"Exception occurred: {e}") + retry = retry + 1 continue break assert f"http://{virtual_server_setup.vs_host}/error.html" in resp.next.url diff --git a/tests/suite/test_virtual_server_external_name.py b/tests/suite/test_virtual_server_external_name.py index 02d3ba3e00..0dc5038730 100644 --- a/tests/suite/test_virtual_server_external_name.py +++ b/tests/suite/test_virtual_server_external_name.py @@ -69,14 +69,15 @@ def vs_externalname_setup( ensure_response_from_backend(virtual_server_setup.backend_1_url, virtual_server_setup.vs_host) def fin(): - print("Clean up ExternalName Setup:") - delete_namespace(kube_apis.v1, external_ns) - replace_configmap( - kube_apis.v1, - config_map_name, - ingress_controller_prerequisites.namespace, - ingress_controller_prerequisites.config_map, - ) + if request.config.getoption("--skip-fixture-teardown") == "no": + print("Clean up ExternalName Setup:") + delete_namespace(kube_apis.v1, external_ns) + replace_configmap( + kube_apis.v1, + config_map_name, + ingress_controller_prerequisites.namespace, + ingress_controller_prerequisites.config_map, + ) request.addfinalizer(fin) @@ -84,12 +85,13 @@ def fin(): @pytest.mark.vs +@pytest.mark.vs_externalname @pytest.mark.skip_for_nginx_oss @pytest.mark.parametrize( "crd_ingress_controller, virtual_server_setup", [ ( - {"type": "complete", "extra_args": [f"-enable-custom-resources", "-v=3"]}, + {"type": "complete", "extra_args": [f"-enable-custom-resources", "-log-level=debug"]}, {"example": "virtual-server-externalname", "app_type": "simple"}, ) ], diff --git a/tests/suite/test_virtual_server_externaldns.py b/tests/suite/test_virtual_server_externaldns.py index 7830b84631..fe93f04dc5 100644 --- a/tests/suite/test_virtual_server_externaldns.py +++ b/tests/suite/test_virtual_server_externaldns.py @@ -1,8 +1,13 @@ import pytest from settings import TEST_DATA -from suite.utils.custom_assertions import assert_event -from suite.utils.custom_resources_utils import is_dnsendpoint_present -from suite.utils.resources_utils import get_events, wait_before_test +from suite.utils.custom_assertions import assert_event, assert_event_not_present +from suite.utils.custom_resources_utils import is_dnsendpoint_present, read_custom_resource +from suite.utils.resources_utils import ( + get_events, + patch_namespace_with_label, + wait_before_test, + wait_until_all_pods_are_ready, +) from suite.utils.vs_vsr_resources_utils import patch_virtual_server_from_yaml from suite.utils.yaml_utils import get_name_from_yaml, get_namespace_from_yaml @@ -10,6 +15,7 @@ @pytest.mark.vs +@pytest.mark.vs_externaldns @pytest.mark.smoke @pytest.mark.parametrize( "crd_ingress_controller_with_ed, create_externaldns, virtual_server_setup", @@ -38,6 +44,7 @@ def test_responses_after_setup( assert dep is True print("\nStep 2: Verify external-dns picked up the record") pod_ns = get_namespace_from_yaml(f"{TEST_DATA}/virtual-server-external-dns/external-dns.yaml") + wait_until_all_pods_are_ready(kube_apis.v1, pod_ns) pod_name = kube_apis.v1.list_namespaced_pod(pod_ns).items[0].metadata.name log_contents = kube_apis.v1.read_namespaced_pod_log(pod_name, pod_ns) wanted_string = "CREATE: virtual-server.example.com 0 IN A" @@ -48,6 +55,21 @@ def test_responses_after_setup( wait_before_test(1) print(f"External DNS not updated, retrying... #{retry}") assert wanted_string in log_contents + print("\nStep 3: Verify VS status is Valid and no bad config events occurred") + events = get_events(kube_apis.v1, virtual_server_setup.namespace) + vs_bad_config_event = "Error creating DNSEndpoint for VirtualServer resource" + assert_event_not_present(vs_bad_config_event, events) + response = read_custom_resource( + kube_apis.custom_objects, + virtual_server_setup.namespace, + "virtualservers", + virtual_server_setup.vs_name, + ) + assert ( + response["status"] + and response["status"]["reason"] == "AddedOrUpdated" + and response["status"]["state"] == "Valid" + ) def test_update_to_ed_in_vs( self, kube_apis, crd_ingress_controller_with_ed, create_externaldns, virtual_server_setup @@ -65,3 +87,88 @@ def test_update_to_ed_in_vs( wait_before_test(5) events = get_events(kube_apis.v1, virtual_server_setup.namespace) assert_event(vs_event_update_text, events) + print("\nStep 3: Verify VS status is Valid") + response = read_custom_resource( + kube_apis.custom_objects, + virtual_server_setup.namespace, + "virtualservers", + virtual_server_setup.vs_name, + ) + assert ( + response["status"] + and response["status"]["reason"] == "AddedOrUpdated" + and response["status"]["state"] == "Valid" + ) + + +@pytest.mark.vs +@pytest.mark.smoke +@pytest.mark.parametrize( + "crd_ingress_controller_with_ed, create_externaldns, virtual_server_setup", + [ + ( + { + "type": "complete", + "extra_args": [ + f"-enable-custom-resources", + f"-enable-external-dns", + f"-watch-namespace-label=app=watch", + ], + }, + {}, + {"example": "virtual-server-external-dns", "app_type": "simple"}, + ) + ], + indirect=True, +) +class TestExternalDNSVirtualServerWatchLabel: + def test_responses_after_setup( + self, kube_apis, crd_ingress_controller_with_ed, create_externaldns, virtual_server_setup, test_namespace + ): + dns_ep_name = get_name_from_yaml(VS_YAML) + print("\nStep 0: Verify DNSEndpoint is not created without watched label") + retry = 0 + dep = is_dnsendpoint_present(kube_apis.custom_objects, dns_ep_name, virtual_server_setup.namespace) + # add a wait to avoid a false negative + wait_before_test(30) + dep = is_dnsendpoint_present(kube_apis.custom_objects, dns_ep_name, virtual_server_setup.namespace) + assert dep is False + print("\nStep 1: Verify DNSEndpoint exists after label is added to namespace") + patch_namespace_with_label(kube_apis.v1, test_namespace, "watch", f"{TEST_DATA}/common/ns-patch.yaml") + wait_before_test() + retry = 0 + dep = is_dnsendpoint_present(kube_apis.custom_objects, dns_ep_name, virtual_server_setup.namespace) + while dep == False and retry <= 60: + dep = is_dnsendpoint_present(kube_apis.custom_objects, dns_ep_name, virtual_server_setup.namespace) + retry += 1 + wait_before_test(1) + print(f"DNSEndpoint not created, retrying... #{retry}") + assert dep is True + print("\nStep 2: Verify external-dns picked up the record") + pod_ns = get_namespace_from_yaml(f"{TEST_DATA}/virtual-server-external-dns/external-dns.yaml") + wait_until_all_pods_are_ready(kube_apis.v1, pod_ns) + pod_name = kube_apis.v1.list_namespaced_pod(pod_ns).items[0].metadata.name + log_contents = kube_apis.v1.read_namespaced_pod_log(pod_name, pod_ns) + wanted_string = "CREATE: virtual-server.example.com 0 IN A" + retry = 0 + while wanted_string not in log_contents and retry <= 60: + log_contents = kube_apis.v1.read_namespaced_pod_log(pod_name, pod_ns) + retry += 1 + wait_before_test(1) + print(f"External DNS not updated, retrying... #{retry}") + assert wanted_string in log_contents + print("\nStep 3: Verify VS status is Valid and no bad config events occurred") + events = get_events(kube_apis.v1, virtual_server_setup.namespace) + vs_bad_config_event = "Error creating DNSEndpoint for VirtualServer resource" + assert_event_not_present(vs_bad_config_event, events) + response = read_custom_resource( + kube_apis.custom_objects, + virtual_server_setup.namespace, + "virtualservers", + virtual_server_setup.vs_name, + ) + assert ( + response["status"] + and response["status"]["reason"] == "AddedOrUpdated" + and response["status"]["state"] == "Valid" + ) diff --git a/tests/suite/test_virtual_server_focused_canary.py b/tests/suite/test_virtual_server_focused_canary.py index 261e8424c5..5a7afdae92 100644 --- a/tests/suite/test_virtual_server_focused_canary.py +++ b/tests/suite/test_virtual_server_focused_canary.py @@ -38,6 +38,7 @@ def get_upstreams_of_splitting(file) -> []: @pytest.mark.vs +@pytest.mark.vs_canary @pytest.mark.parametrize( "crd_ingress_controller, virtual_server_setup", [ diff --git a/tests/suite/test_virtual_server_grpc.py b/tests/suite/test_virtual_server_grpc.py index ab662be5bf..d0a6727849 100644 --- a/tests/suite/test_virtual_server_grpc.py +++ b/tests/suite/test_virtual_server_grpc.py @@ -52,7 +52,7 @@ def backend_setup(request, kube_apis, ingress_controller_prerequisites, test_nam app_name = request.param.get("app_type") create_example_app(kube_apis, app_name, test_namespace) wait_until_all_pods_are_ready(kube_apis.v1, test_namespace) - except Exception as ex: + except Exception: print("Failed to complete setup, cleaning up..") delete_items_from_yaml(kube_apis, src_sec_yaml, test_namespace) replace_configmap_from_yaml( @@ -65,20 +65,22 @@ def backend_setup(request, kube_apis, ingress_controller_prerequisites, test_nam pytest.fail(f"VS GRPC setup failed") def fin(): - print("Clean up:") - delete_items_from_yaml(kube_apis, src_sec_yaml, test_namespace) - replace_configmap_from_yaml( - kube_apis.v1, - ingress_controller_prerequisites.config_map["metadata"]["name"], - ingress_controller_prerequisites.namespace, - f"{DEPLOYMENTS}/common/nginx-config.yaml", - ) - delete_common_app(kube_apis, app_name, test_namespace) + if request.config.getoption("--skip-fixture-teardown") == "no": + print("Clean up:") + delete_items_from_yaml(kube_apis, src_sec_yaml, test_namespace) + replace_configmap_from_yaml( + kube_apis.v1, + ingress_controller_prerequisites.config_map["metadata"]["name"], + ingress_controller_prerequisites.namespace, + f"{DEPLOYMENTS}/common/nginx-config.yaml", + ) + delete_common_app(kube_apis, app_name, test_namespace) request.addfinalizer(fin) @pytest.mark.vs +@pytest.mark.vs_grpc @pytest.mark.smoke @pytest.mark.parametrize( "crd_ingress_controller, virtual_server_setup", @@ -264,6 +266,7 @@ def test_config_after_enable_tls( @pytest.mark.vs +@pytest.mark.vs_grpc @pytest.mark.smoke @pytest.mark.skip_for_nginx_oss @pytest.mark.parametrize( @@ -294,7 +297,7 @@ def test_config_after_enable_healthcheck( param_list = [ "health_check port=50051 interval=1s jitter=2s", "type=grpc grpc_status=12", - "grpc_service=helloworld.Greeter;", + "grpc_service=none.None;", ] for p in param_list: assert p in config @@ -324,3 +327,70 @@ def test_grpc_healthcheck_validation( assert_vs_conf_not_exists( kube_apis, ic_pod_name, ingress_controller_prerequisites.namespace, virtual_server_setup ) + + @pytest.mark.parametrize("backend_setup", [{"app_type": "grpc-vs"}], indirect=True) + def test_grpc_healthcheck_send_hello( + self, kube_apis, ingress_controller_prerequisites, crd_ingress_controller, backend_setup, virtual_server_setup + ): + ic_pod_name = get_first_pod_name(kube_apis.v1, ingress_controller_prerequisites.namespace) + patch_virtual_server_from_yaml( + kube_apis.custom_objects, + virtual_server_setup.vs_name, + f"{TEST_DATA}/virtual-server-grpc/virtual-server-healthcheck.yaml", + virtual_server_setup.namespace, + ) + + # Ensure the initial health check configuration + config = get_vs_nginx_template_conf( + kube_apis.v1, + virtual_server_setup.namespace, + virtual_server_setup.vs_name, + ic_pod_name, + ingress_controller_prerequisites.namespace, + ) + assert "grpc_service=none.None" in config + + # Attempt to send a Hello message + cert = get_certificate( + virtual_server_setup.public_endpoint.public_ip, + virtual_server_setup.vs_host, + virtual_server_setup.public_endpoint.port_ssl, + ) + target = f"{virtual_server_setup.public_endpoint.public_ip}:{virtual_server_setup.public_endpoint.port_ssl}" + credentials = grpc.ssl_channel_credentials(root_certificates=cert.encode()) + options = (("grpc.ssl_target_name_override", virtual_server_setup.vs_host),) + + try: + with grpc.secure_channel(target, credentials, options) as channel: + stub = GreeterStub(channel) + response = stub.SayHello(HelloRequest(name="")) + valid_message = "Hello " + assert response.message == valid_message, f"Expected '{valid_message}', but got '{response.message}'" + except grpc.RpcError as e: + print(e.details()) + pytest.fail("RPC error was not expected during call, exiting...") + + # Update health check to expect gRPC status code 0 + patch_virtual_server_from_yaml( + kube_apis.custom_objects, + virtual_server_setup.vs_name, + f"{TEST_DATA}/virtual-server-grpc/virtual-server-healthcheck-fail.yaml", + virtual_server_setup.namespace, + ) + wait_before_test() + + # Re-fetch the config to confirm the update + config = get_vs_nginx_template_conf( + kube_apis.v1, + virtual_server_setup.namespace, + virtual_server_setup.vs_name, + ic_pod_name, + ingress_controller_prerequisites.namespace, + ) + assert "grpc_status=0" in config + + # Verify that the health check fails + log_contents = kube_apis.v1.read_namespaced_pod_log(ic_pod_name, ingress_controller_prerequisites.namespace) + assert ( + "peer is unhealthy while checking grpc response" in log_contents + ), "Expected 'peer is unhealthy while checking grpc response' in logs but it was not found" diff --git a/tests/suite/test_virtual_server_mixed_grpc.py b/tests/suite/test_virtual_server_mixed_grpc.py index 9b4802b417..c13b6b122e 100644 --- a/tests/suite/test_virtual_server_mixed_grpc.py +++ b/tests/suite/test_virtual_server_mixed_grpc.py @@ -47,7 +47,7 @@ def backend_setup(request, kube_apis, ingress_controller_prerequisites, test_nam app_name = request.param.get("app_type") create_example_app(kube_apis, app_name, test_namespace) wait_until_all_pods_are_ready(kube_apis.v1, test_namespace) - except Exception as ex: + except Exception: print("Failed to complete setup, cleaning up..") delete_items_from_yaml(kube_apis, src_sec_yaml, test_namespace) replace_configmap_from_yaml( @@ -60,20 +60,22 @@ def backend_setup(request, kube_apis, ingress_controller_prerequisites, test_nam pytest.fail(f"VS GRPC setup failed") def fin(): - print("Clean up:") - delete_items_from_yaml(kube_apis, src_sec_yaml, test_namespace) - replace_configmap_from_yaml( - kube_apis.v1, - ingress_controller_prerequisites.config_map["metadata"]["name"], - ingress_controller_prerequisites.namespace, - f"{DEPLOYMENTS}/common/nginx-config.yaml", - ) - delete_common_app(kube_apis, app_name, test_namespace) + if request.config.getoption("--skip-fixture-teardown") == "no": + print("Clean up:") + delete_items_from_yaml(kube_apis, src_sec_yaml, test_namespace) + replace_configmap_from_yaml( + kube_apis.v1, + ingress_controller_prerequisites.config_map["metadata"]["name"], + ingress_controller_prerequisites.namespace, + f"{DEPLOYMENTS}/common/nginx-config.yaml", + ) + delete_common_app(kube_apis, app_name, test_namespace) request.addfinalizer(fin) @pytest.mark.vs +@pytest.mark.vs_grpc @pytest.mark.smoke @pytest.mark.flaky(max_runs=3) @pytest.mark.parametrize( diff --git a/tests/suite/test_virtual_server_redirects.py b/tests/suite/test_virtual_server_redirects.py index 7f6066c6b0..1dd03c3e01 100644 --- a/tests/suite/test_virtual_server_redirects.py +++ b/tests/suite/test_virtual_server_redirects.py @@ -13,6 +13,7 @@ @pytest.mark.vs +@pytest.mark.vs_redirects @pytest.mark.parametrize( "crd_ingress_controller, virtual_server_setup", [ diff --git a/tests/suite/test_virtual_server_service_insight.py b/tests/suite/test_virtual_server_service_insight.py new file mode 100644 index 0000000000..0f2149e012 --- /dev/null +++ b/tests/suite/test_virtual_server_service_insight.py @@ -0,0 +1,134 @@ +from unittest import mock + +import pytest +import requests +from settings import TEST_DATA +from suite.utils.resources_utils import ( + create_secret_from_yaml, + delete_secret, + ensure_response_from_backend, + patch_deployment_from_yaml, + wait_before_test, +) +from suite.utils.yaml_utils import get_first_host_from_yaml + + +@pytest.mark.vs +@pytest.mark.skip_for_nginx_oss +@pytest.mark.parametrize( + "crd_ingress_controller, virtual_server_setup", + [ + ( + {"type": "complete", "extra_args": [f"-enable-custom-resources", f"-enable-service-insight"]}, + {"example": "virtual-server", "app_type": "simple"}, + ) + ], + indirect=True, +) +class TestVirtualServerServiceInsightHTTP: + def test_responses_svc_insight_http( + self, request, kube_apis, crd_ingress_controller, virtual_server_setup, ingress_controller_endpoint + ): + """test responses from service insight endpoint with http""" + retry = 0 + resp = mock.Mock() + resp.json.return_value = {} + resp.status_code == 502 + vs_source = f"{TEST_DATA}/virtual-server/standard/virtual-server.yaml" + host = get_first_host_from_yaml(vs_source) + req_url = f"http://{ingress_controller_endpoint.public_ip}:{ingress_controller_endpoint.service_insight_port}/probe/{host}" + ensure_response_from_backend(req_url, virtual_server_setup.vs_host) + while (resp.json() != {"Total": 3, "Up": 3, "Unhealthy": 0}) and retry < 5: + resp = requests.get(req_url) + wait_before_test() + retry = retry + 1 + + assert resp.status_code == 200, f"Expected 200 code for /probe/{host} but got {resp.status_code}" + assert resp.json() == {"Total": 3, "Up": 3, "Unhealthy": 0} + + +@pytest.fixture(scope="class") +def https_secret_setup(request, kube_apis, test_namespace): + print("------------------------- Deploy Secret -----------------------------------") + secret_name = create_secret_from_yaml(kube_apis.v1, "nginx-ingress", f"{TEST_DATA}/service-insight/secret.yaml") + + def fin(): + delete_secret(kube_apis.v1, secret_name, "nginx-ingress") + + request.addfinalizer(fin) + + +@pytest.mark.vs +@pytest.mark.skip_for_nginx_oss +@pytest.mark.parametrize( + "crd_ingress_controller, virtual_server_setup", + [ + ( + { + "type": "complete", + "extra_args": [ + f"-enable-custom-resources", + f"-enable-service-insight", + f"-service-insight-tls-secret=nginx-ingress/test-secret", + ], + }, + {"example": "virtual-server", "app_type": "simple"}, + ) + ], + indirect=True, +) +class TestVirtualServerServiceInsightHTTPS: + def test_responses_svc_insight_https( + self, + request, + kube_apis, + https_secret_setup, + ingress_controller_endpoint, + crd_ingress_controller, + virtual_server_setup, + ): + """test responses from service insight endpoint with https""" + retry = 0 + resp = mock.Mock() + resp.json.return_value = {} + resp.status_code == 502 + vs_source = f"{TEST_DATA}/virtual-server/standard/virtual-server.yaml" + host = get_first_host_from_yaml(vs_source) + req_url = f"https://{ingress_controller_endpoint.public_ip}:{ingress_controller_endpoint.service_insight_port}/probe/{host}" + ensure_response_from_backend(req_url, virtual_server_setup.vs_host) + while (resp.json() != {"Total": 3, "Up": 3, "Unhealthy": 0}) and retry < 5: + resp = requests.get(req_url, verify=False) + wait_before_test() + retry = retry + 1 + assert resp.status_code == 200, f"Expected 200 code for /probe/{host} but got {resp.status_code}" + assert resp.json() == {"Total": 3, "Up": 3, "Unhealthy": 0} + + def test_responses_svc_insight_update_pods( + self, + request, + kube_apis, + https_secret_setup, + ingress_controller_endpoint, + test_namespace, + crd_ingress_controller, + virtual_server_setup, + ): + """test responses from service insight endpoint with https and update number of replicas""" + retry = 0 + resp = mock.Mock() + resp.json.return_value = {} + resp.status_code == 502 + vs_source = f"{TEST_DATA}/virtual-server/standard/virtual-server.yaml" + host = get_first_host_from_yaml(vs_source) + req_url = f"https://{ingress_controller_endpoint.public_ip}:{ingress_controller_endpoint.service_insight_port}/probe/{host}" + ensure_response_from_backend(req_url, virtual_server_setup.vs_host) + + # patch backend1 deployment with 5 replicas + patch_deployment_from_yaml(kube_apis.apps_v1_api, test_namespace, f"{TEST_DATA}/service-insight/app.yaml") + ensure_response_from_backend(req_url, virtual_server_setup.vs_host) + while (resp.json() != {"Total": 6, "Up": 6, "Unhealthy": 0}) and retry < 5: + resp = requests.get(req_url, verify=False) + wait_before_test() + retry = retry + 1 + assert resp.status_code == 200, f"Expected 200 code for /probe/{host} but got {resp.status_code}" + assert resp.json() == {"Total": 6, "Up": 6, "Unhealthy": 0} diff --git a/tests/suite/test_virtual_server_split_traffic.py b/tests/suite/test_virtual_server_split_traffic.py index 84befef017..54f56e27d0 100644 --- a/tests/suite/test_virtual_server_split_traffic.py +++ b/tests/suite/test_virtual_server_split_traffic.py @@ -51,7 +51,6 @@ def get_upstreams_of_splitting(file) -> []: ) class TestTrafficSplitting: def test_several_requests(self, kube_apis, crd_ingress_controller, virtual_server_setup): - ensure_response_from_backend(virtual_server_setup.backend_1_url, virtual_server_setup.vs_host) weights = get_weights_of_splitting(f"{TEST_DATA}/virtual-server-split-traffic/standard/virtual-server.yaml") upstreams = get_upstreams_of_splitting(f"{TEST_DATA}/virtual-server-split-traffic/standard/virtual-server.yaml") sum_weights = sum(weights) @@ -59,9 +58,13 @@ def test_several_requests(self, kube_apis, crd_ingress_controller, virtual_serve counter_v1, counter_v2 = 0, 0 for _ in range(100): - resp = requests.get(virtual_server_setup.backend_1_url, headers={"host": virtual_server_setup.vs_host}) - if resp.status_code == 502: - print("Backend is not ready yet, skip.") + ensure_response_from_backend(virtual_server_setup.backend_1_url, virtual_server_setup.vs_host) + status_code = 502 + while status_code == 502: + resp = requests.get(virtual_server_setup.backend_1_url, headers={"host": virtual_server_setup.vs_host}) + status_code = resp.status_code + if status_code == 502: + print("Backend is not ready yet, skip.") if upstreams[0] in resp.text in resp.text: counter_v1 = counter_v1 + 1 elif upstreams[1] in resp.text in resp.text: diff --git a/tests/suite/test_virtual_server_tls.py b/tests/suite/test_virtual_server_tls.py index 5404ec23d8..28294997bf 100644 --- a/tests/suite/test_virtual_server_tls.py +++ b/tests/suite/test_virtual_server_tls.py @@ -4,6 +4,7 @@ from suite.utils.resources_utils import ( create_secret_from_yaml, delete_secret, + get_reload_count, is_secret_present, replace_secret, wait_before_test, @@ -25,9 +26,10 @@ def clean_up(request, kube_apis, test_namespace) -> None: secret_name = get_name_from_yaml(f"{TEST_DATA}/virtual-server-tls/tls-secret.yaml") def fin(): - print("Clean up after test:") - if is_secret_present(kube_apis.v1, secret_name, test_namespace): - delete_secret(kube_apis.v1, secret_name, test_namespace) + if request.config.getoption("--skip-fixture-teardown") == "no": + print("Clean up after test:") + if is_secret_present(kube_apis.v1, secret_name, test_namespace): + delete_secret(kube_apis.v1, secret_name, test_namespace) request.addfinalizer(fin) @@ -75,7 +77,14 @@ def assert_gb_subject(virtual_server_setup): "crd_ingress_controller, virtual_server_setup", [ ( - {"type": "complete", "extra_args": [f"-enable-custom-resources"]}, + { + "type": "complete", + "extra_args": [ + f"-enable-custom-resources", + f"-enable-prometheus-metrics", + f"-ssl-dynamic-reload=false", + ], + }, {"example": "virtual-server-tls", "app_type": "simple"}, ) ], @@ -121,6 +130,10 @@ def test_tls_termination(self, kube_apis, crd_ingress_controller, virtual_server wait_before_test(1) assert_us_subject(virtual_server_setup) + # with -ssl-dynamic-reload=false, we expect + # replacing a secret to trigger a reload + count_before_replace = get_reload_count(virtual_server_setup.metrics_url) + print("\nStep 7: update secret and check") replace_secret( kube_apis.v1, @@ -130,3 +143,58 @@ def test_tls_termination(self, kube_apis, crd_ingress_controller, virtual_server ) wait_before_test(1) assert_gb_subject(virtual_server_setup) + + count_after = get_reload_count(virtual_server_setup.metrics_url) + reloads = count_after - count_before_replace + expected_reloads = 1 + assert reloads == expected_reloads, f"expected {expected_reloads} reloads, got {reloads}" + + +@pytest.mark.vs +@pytest.mark.smoke +@pytest.mark.parametrize( + "crd_ingress_controller, virtual_server_setup", + [ + ( + { + "type": "complete", + "extra_args": [ + f"-enable-custom-resources", + f"-enable-prometheus-metrics", + ], + }, + {"example": "virtual-server-tls", "app_type": "simple"}, + ) + ], + indirect=True, +) +class TestVirtualServerTLSDynamicReloads: + def test_tls_termination(self, kube_apis, crd_ingress_controller, virtual_server_setup, clean_up): + print("\nStep 1: no secret") + assert_unrecognized_name_error(virtual_server_setup) + + print("\nStep 2: deploy secret and check") + secret_name = create_secret_from_yaml( + kube_apis.v1, virtual_server_setup.namespace, f"{TEST_DATA}/virtual-server-tls/tls-secret.yaml" + ) + wait_before_test(1) + assert_us_subject(virtual_server_setup) + + # for Plus with -ssl-dynamic-reload=true, we expect + # replacing a secret not to trigger a reload + count_before_replace = get_reload_count(virtual_server_setup.metrics_url) + + print("\nStep 3: update secret and check") + replace_secret( + kube_apis.v1, + secret_name, + virtual_server_setup.namespace, + f"{TEST_DATA}/virtual-server-tls/new-tls-secret.yaml", + ) + wait_before_test(1) + assert_gb_subject(virtual_server_setup) + + count_after = get_reload_count(virtual_server_setup.metrics_url) + reloads = count_after - count_before_replace + expected_reloads = 0 + assert reloads == expected_reloads, f"expected {expected_reloads} reloads, got {reloads}" diff --git a/tests/suite/test_virtual_server_tls_redirect.py b/tests/suite/test_virtual_server_tls_redirect.py index 4310daf488..1e356ac997 100644 --- a/tests/suite/test_virtual_server_tls_redirect.py +++ b/tests/suite/test_virtual_server_tls_redirect.py @@ -7,6 +7,7 @@ @pytest.mark.vs +@pytest.mark.vs_redirects @pytest.mark.parametrize( "crd_ingress_controller, virtual_server_setup", [ diff --git a/tests/suite/test_virtual_server_upstream_options.py b/tests/suite/test_virtual_server_upstream_options.py index c874ae249d..91d054b009 100644 --- a/tests/suite/test_virtual_server_upstream_options.py +++ b/tests/suite/test_virtual_server_upstream_options.py @@ -20,6 +20,7 @@ @pytest.mark.vs +@pytest.mark.vs_upstream @pytest.mark.parametrize( "crd_ingress_controller, virtual_server_setup", [ @@ -354,6 +355,7 @@ def test_v_s_overrides_config_map( @pytest.mark.vs +@pytest.mark.vs_upstream @pytest.mark.parametrize( "crd_ingress_controller, virtual_server_setup", [ @@ -471,6 +473,7 @@ def test_openapi_validation_flow( @pytest.mark.vs +@pytest.mark.vs_upstream @pytest.mark.skip_for_nginx_oss @pytest.mark.parametrize( "crd_ingress_controller, virtual_server_setup", @@ -501,17 +504,76 @@ class TestOptionsSpecificForPlus: "domain": "virtual-server-route.example.com", "httpOnly": True, "secure": True, + "samesite": "strict", }, }, [ "health_check uri=/ interval=5s jitter=0s", "fails=1 passes=1", - "mandatory persistent", - ";", + "mandatory persistent", + "keepalive_time=60s;", "slow_start=3h", "queue 100 timeout=60s;", "ntlm;", - "sticky cookie TestCookie expires=max domain=virtual-server-route.example.com httponly secure path=/some-valid/path;", + "sticky cookie TestCookie expires=max domain=virtual-server-route.example.com httponly samesite=strict secure path=/some-valid/path;", + ], + ), + ( + { + "lb-method": "least_conn", + "healthCheck": {"enable": True, "mandatory": True, "persistent": True}, + "slow-start": "3h", + "queue": {"size": 100}, + "ntlm": True, + "sessionCookie": { + "enable": True, + "name": "TestCookie", + "path": "/some-valid/path", + "expires": "max", + "domain": "virtual-server-route.example.com", + "httpOnly": True, + "secure": True, + "samesite": "lax", + }, + }, + [ + "health_check uri=/ interval=5s jitter=0s", + "fails=1 passes=1", + "mandatory persistent", + "keepalive_time=60s;", + "slow_start=3h", + "queue 100 timeout=60s;", + "ntlm;", + "sticky cookie TestCookie expires=max domain=virtual-server-route.example.com httponly samesite=lax secure path=/some-valid/path;", + ], + ), + ( + { + "lb-method": "least_conn", + "healthCheck": {"enable": True, "mandatory": True, "persistent": True}, + "slow-start": "3h", + "queue": {"size": 100}, + "ntlm": True, + "sessionCookie": { + "enable": True, + "name": "TestCookie", + "path": "/some-valid/path", + "expires": "max", + "domain": "virtual-server-route.example.com", + "httpOnly": True, + "secure": True, + "samesite": "none", + }, + }, + [ + "health_check uri=/ interval=5s jitter=0s", + "fails=1 passes=1", + "mandatory persistent", + "keepalive_time=60s;", + "slow_start=3h", + "queue 100 timeout=60s;", + "ntlm;", + "sticky cookie TestCookie expires=max domain=virtual-server-route.example.com httponly samesite=none secure path=/some-valid/path;", ], ), ( @@ -531,20 +593,21 @@ class TestOptionsSpecificForPlus: "read-timeout": "45s", "send-timeout": "55s", "headers": [{"name": "Host", "value": "virtual-server.example.com"}], + "keepalive-time": "120s", }, "queue": {"size": 1000, "timeout": "66s"}, "slow-start": "0s", "ntlm": True, }, [ - "health_check uri=/health port=8080 interval=15s jitter=3", - "fails=2 passes=2 match=", + "health_check uri=/health port=8080 interval=15s jitter=3s fails=2 passes=2 match=", "proxy_pass https://vs", "status 200;", "proxy_connect_timeout 35s;", "proxy_read_timeout 45s;", "proxy_send_timeout 55s;", - 'proxy_set_header Host "virtual-server.example.com";', + 'proxy_set_header Host "virtual-server.example.com"', + "keepalive_time=120s;", "slow_start=0s", "queue 1000 timeout=66s;", "ntlm;", @@ -632,6 +695,7 @@ def test_validation_flow( "upstreams[0].healthCheck.path", "upstreams[0].healthCheck.interval", "upstreams[0].healthCheck.jitter", + "upstreams[0].healthCheck.keepalive-time", "upstreams[0].healthCheck.fails", "upstreams[0].healthCheck.passes", "upstreams[0].healthCheck.connect-timeout", @@ -653,6 +717,7 @@ def test_validation_flow( "upstreams[1].healthCheck.path", "upstreams[1].healthCheck.interval", "upstreams[1].healthCheck.jitter", + "upstreams[1].healthCheck.keepalive-time", "upstreams[1].healthCheck.fails", "upstreams[1].healthCheck.passes", "upstreams[1].healthCheck.connect-timeout", @@ -693,6 +758,7 @@ def test_openapi_validation_flow( "healthCheck.path", "healthCheck.interval", "healthCheck.jitter", + "healthCheck.keepalive-time", "healthCheck.fails", "healthCheck.passes", "healthCheck.port", diff --git a/tests/suite/test_virtual_server_use_cluster_ip_reloads.py b/tests/suite/test_virtual_server_use_cluster_ip_reloads.py new file mode 100644 index 0000000000..248b6cb2db --- /dev/null +++ b/tests/suite/test_virtual_server_use_cluster_ip_reloads.py @@ -0,0 +1,45 @@ +import pytest +from suite.utils.resources_utils import get_reload_count, scale_deployment, wait_until_all_pods_are_ready + +from tests.suite.utils.custom_assertions import assert_pods_scaled_to_count + + +@pytest.mark.vs +@pytest.mark.vs_use_cluster_ip +@pytest.mark.parametrize( + "crd_ingress_controller, virtual_server_setup", + [ + ( + { + "type": "complete", + "extra_args": [ + "-enable-custom-resources", + "-enable-prometheus-metrics", + ], + }, + {"example": "virtual-server-use-cluster-ip", "app_type": "simple"}, + ) + ], + indirect=True, +) +class TestVSUseClusterIP: + @pytest.mark.flaky(max_runs=3) + def test_use_cluster_ip_reloads( + self, kube_apis, ingress_controller_endpoint, crd_ingress_controller, virtual_server_setup + ): + wait_until_all_pods_are_ready(kube_apis.v1, virtual_server_setup.namespace) + print("Step 1: get initial reload count") + initial_reload_count = get_reload_count(virtual_server_setup.metrics_url) + + print("Step 2: scale the deployment down") + scale_deployment(kube_apis.v1, kube_apis.apps_v1_api, "backend1", virtual_server_setup.namespace, 1) + assert_pods_scaled_to_count(kube_apis.apps_v1_api, kube_apis.v1, "backend1", virtual_server_setup.namespace, 1) + + print("Step 3: scale the deployment up") + scale_deployment(kube_apis.v1, kube_apis.apps_v1_api, "backend1", virtual_server_setup.namespace, 4) + assert_pods_scaled_to_count(kube_apis.apps_v1_api, kube_apis.v1, "backend1", virtual_server_setup.namespace, 4) + + print("Step 4: get reload count after scaling") + reload_count_after_scaling = get_reload_count(virtual_server_setup.metrics_url) + + assert reload_count_after_scaling == initial_reload_count, "Expected: no new reloads" diff --git a/tests/suite/test_virtual_server_validation.py b/tests/suite/test_virtual_server_validation.py index 4e18e1ceb2..8ce3597d9a 100644 --- a/tests/suite/test_virtual_server_validation.py +++ b/tests/suite/test_virtual_server_validation.py @@ -1,7 +1,7 @@ import pytest from settings import TEST_DATA from suite.utils.custom_assertions import assert_vs_conf_exists, assert_vs_conf_not_exists, wait_and_assert_status_code -from suite.utils.resources_utils import get_events, get_first_pod_name, get_pods_amount, wait_before_test +from suite.utils.resources_utils import get_events, get_first_pod_name, wait_before_test from suite.utils.vs_vsr_resources_utils import ( create_virtual_server_from_yaml, delete_virtual_server, @@ -50,11 +50,9 @@ class TestVirtualServerValidation: def test_virtual_server_behavior( self, kube_apis, cli_arguments, ingress_controller_prerequisites, crd_ingress_controller, virtual_server_setup ): - ic_pods_amount = get_pods_amount(kube_apis.v1, ingress_controller_prerequisites.namespace) ic_pod_name = get_first_pod_name(kube_apis.v1, ingress_controller_prerequisites.namespace) print("Step 1: initial check") - step_1_list = get_events(kube_apis.v1, virtual_server_setup.namespace) assert_vs_conf_exists(kube_apis, ic_pod_name, ingress_controller_prerequisites.namespace, virtual_server_setup) assert_response_200(virtual_server_setup) @@ -66,7 +64,6 @@ def test_virtual_server_behavior( virtual_server_setup.namespace, ) wait_before_test(1) - step_2_list = get_events(kube_apis.v1, virtual_server_setup.namespace) assert_vs_conf_not_exists( kube_apis, ic_pod_name, ingress_controller_prerequisites.namespace, virtual_server_setup ) @@ -107,7 +104,6 @@ def test_virtual_server_behavior( virtual_server_setup.namespace, ) wait_before_test(1) - step_5_list = get_events(kube_apis.v1, virtual_server_setup.namespace) assert_vs_conf_not_exists( kube_apis, ic_pod_name, ingress_controller_prerequisites.namespace, virtual_server_setup ) diff --git a/tests/suite/test_virtual_server_weight_changes_without_reload.py b/tests/suite/test_virtual_server_weight_changes_without_reload.py new file mode 100644 index 0000000000..f39ffa747c --- /dev/null +++ b/tests/suite/test_virtual_server_weight_changes_without_reload.py @@ -0,0 +1,100 @@ +import pytest +import requests +from settings import TEST_DATA +from suite.utils.custom_assertions import wait_and_assert_status_code +from suite.utils.resources_utils import ensure_response_from_backend, get_reload_count, wait_before_test +from suite.utils.vs_vsr_resources_utils import patch_virtual_server_from_yaml + + +@pytest.mark.vs +@pytest.mark.smoke +@pytest.mark.skip_for_nginx_oss +@pytest.mark.parametrize( + "crd_ingress_controller, virtual_server_setup, expect_reload", + [ + ( + { + "type": "complete", + "extra_args": [ + "-enable-custom-resources", + "-enable-prometheus-metrics", + "-weight-changes-dynamic-reload=true", + ], + }, + {"example": "virtual-server-weight-changes-dynamic-reload", "app_type": "split"}, + False, + ), + ( + { + "type": "complete", + "extra_args": [ + "-enable-custom-resources", + "-enable-prometheus-metrics", + "-weight-changes-dynamic-reload=false", + ], + }, + { + "example": "virtual-server-weight-changes-dynamic-reload", + "app_type": "split", + }, + True, + ), + ], + indirect=["crd_ingress_controller", "virtual_server_setup"], + ids=[ + "WithoutReload", + "WithReload", + ], +) +class TestWeightChangesWithReloadCondition: + def test_weight_changes_reload_behavior( + self, kube_apis, crd_ingress_controller, virtual_server_setup, expect_reload + ): + initial_weights_config = ( + f"{TEST_DATA}/virtual-server-weight-changes-dynamic-reload/standard/virtual-server.yaml" + ) + patch_virtual_server_from_yaml( + kube_apis.custom_objects, + virtual_server_setup.vs_name, + initial_weights_config, + virtual_server_setup.namespace, + ) + swap_weights_config = ( + f"{TEST_DATA}/virtual-server-weight-changes-dynamic-reload/virtual-server-weight-swap.yaml" + ) + + print("Step 1: Get a response from the backend.") + wait_and_assert_status_code(200, virtual_server_setup.backend_1_url, virtual_server_setup.vs_host) + resp = requests.get(virtual_server_setup.backend_1_url, headers={"host": virtual_server_setup.vs_host}) + assert "backend1-v1" in resp.text + + print("Step 2: Record the initial number of reloads.") + count_before = get_reload_count(virtual_server_setup.metrics_url) + print(f"Reload count before: {count_before}") + + print("Step 3: Apply a configuration that swaps the weights (0 100) to (100 0).") + patch_virtual_server_from_yaml( + kube_apis.custom_objects, virtual_server_setup.vs_name, swap_weights_config, virtual_server_setup.namespace + ) + + print("Wait after applying config") + wait_before_test(5) + + print("Step 4: Verify hitting the other backend.") + ensure_response_from_backend(virtual_server_setup.backend_1_url, virtual_server_setup.vs_host) + wait_and_assert_status_code(200, virtual_server_setup.backend_1_url, virtual_server_setup.vs_host) + resp = requests.get(virtual_server_setup.backend_1_url, headers={"host": virtual_server_setup.vs_host}) + assert "backend1-v2" in resp.text + + print("Step 5: Verify reload behavior based on the weight-changes-dynamic-reload flag.") + count_after = get_reload_count(virtual_server_setup.metrics_url) + print(f"Reload count after: {count_after}") + + if expect_reload: + assert ( + count_before < count_after + ), "The reload count should increase when weights are swapped and weight-changes-dynamic-reload=false." + else: + assert ( + count_before == count_after + ), "The reload count should not change when weights are swapped and weight-changes-dynamic-reload=true." diff --git a/tests/suite/test_virtual_server_wildcard.py b/tests/suite/test_virtual_server_wildcard.py index 98c763f3f6..e8a9c2e91f 100644 --- a/tests/suite/test_virtual_server_wildcard.py +++ b/tests/suite/test_virtual_server_wildcard.py @@ -21,7 +21,6 @@ ) class TestVirtualServerWildcard: def test_vs_status(self, kube_apis, crd_ingress_controller, virtual_server_setup): - wait_and_assert_status_code(200, virtual_server_setup.backend_1_url, virtual_server_setup.vs_host) wait_and_assert_status_code(200, virtual_server_setup.backend_2_url, virtual_server_setup.vs_host) wait_and_assert_status_code(404, virtual_server_setup.backend_1_url, "test.example.com") diff --git a/tests/suite/test_watch_namespace.py b/tests/suite/test_watch_namespace.py index 2a0d6e5175..0e278daad3 100644 --- a/tests/suite/test_watch_namespace.py +++ b/tests/suite/test_watch_namespace.py @@ -63,10 +63,11 @@ def backend_setup(request, kube_apis, ingress_controller_endpoint) -> BackendSet ) def fin(): - print("Clean up:") - delete_namespace(kube_apis.v1, watched_namespace) - delete_namespace(kube_apis.v1, foreign_namespace) - delete_namespace(kube_apis.v1, watched_namespace2) + if request.config.getoption("--skip-fixture-teardown") == "no": + print("Clean up:") + delete_namespace(kube_apis.v1, watched_namespace) + delete_namespace(kube_apis.v1, foreign_namespace) + delete_namespace(kube_apis.v1, watched_namespace2) request.addfinalizer(fin) @@ -74,6 +75,7 @@ def fin(): @pytest.mark.ingresses +@pytest.mark.watch_namespace @pytest.mark.parametrize( "ingress_controller, expected_responses", [ @@ -93,7 +95,8 @@ def test_response_codes(self, ingress_controller, backend_setup, expected_respon ), f"Expected: {expected_responses[ing]} response code for {backend_setup.ingress_hosts[ing]}" -@pytest.mark.test +@pytest.mark.ingresses +@pytest.mark.watch_namespace @pytest.mark.parametrize( "ingress_controller, expected_responses", [ @@ -113,7 +116,7 @@ def test_response_codes(self, ingress_controller, backend_setup, expected_respon retry = 0 while resp.status_code != expected_responses[ing] and retry < 3: resp = requests.get(backend_setup.req_url, headers={"host": backend_setup.ingress_hosts[ing]}) - retry = +1 + retry = retry + 1 wait_before_test() assert ( resp.status_code == expected_responses[ing] diff --git a/tests/suite/test_watch_namespace_label.py b/tests/suite/test_watch_namespace_label.py new file mode 100644 index 0000000000..7fcf439043 --- /dev/null +++ b/tests/suite/test_watch_namespace_label.py @@ -0,0 +1,234 @@ +from unittest import mock + +import pytest +import requests +from settings import TEST_DATA +from suite.utils.resources_utils import ( + create_example_app, + create_items_from_yaml, + create_namespace_with_name_from_yaml, + delete_namespace, + ensure_connection_to_public_endpoint, + ensure_response_from_backend, + patch_namespace_with_label, + wait_before_test, + wait_until_all_pods_are_ready, +) +from suite.utils.vs_vsr_resources_utils import create_virtual_server_from_yaml +from suite.utils.yaml_utils import get_first_host_from_yaml, get_first_ingress_host_from_yaml + + +class BackendSetup: + """ + Encapsulate the example details. + + Attributes: + req_url (str): + resource_hosts (dict): + """ + + def __init__(self, req_url, resource_hosts): + self.req_url = req_url + self.resource_hosts = resource_hosts + + +@pytest.fixture(scope="class") +def backend_setup(request, kube_apis, ingress_controller_endpoint) -> BackendSetup: + """ + Create 3 namespaces and deploy simple applications in them. + + :param request: pytest fixture + :param kube_apis: client apis + :param ingress_controller_endpoint: public endpoint + :return: BackendSetup + """ + resource_hosts = {} + namespaces = [] + for ns in ["watched-ns", "foreign-ns", "watched-ns2"]: + namespace, ingress_host = create_and_setup_namespace(kube_apis, ingress_controller_endpoint, ns) + resource_hosts[f"{ns}-ingress"] = ingress_host + namespaces.append(namespace) + + req_url = f"http://{ingress_controller_endpoint.public_ip}:{ingress_controller_endpoint.port}/backend1" + # add label to namespaces + patch_namespace_with_label(kube_apis.v1, "watched-ns", "watch", f"{TEST_DATA}/common/ns-patch.yaml") + patch_namespace_with_label(kube_apis.v1, "watched-ns2", "watch", f"{TEST_DATA}/common/ns-patch.yaml") + + def fin(): + if request.config.getoption("--skip-fixture-teardown") == "no": + print("Clean up:") + for ns in namespaces: + delete_namespace(kube_apis.v1, ns) + + request.addfinalizer(fin) + + return BackendSetup(req_url, resource_hosts) + + +@pytest.fixture(scope="class") +def backend_setup_vs(request, kube_apis, ingress_controller_endpoint) -> BackendSetup: + """ + Create 3 namespaces and deploy simple applications in them. + + :param request: pytest fixture + :param kube_apis: client apis + :param ingress_controller_endpoint: public endpoint + :return: BackendSetup + """ + resource_hosts = {} + namespaces = [] + for ns in ["watched-ns", "foreign-ns", "watched-ns2"]: + namespace, vs_host = create_and_setup_namespace(kube_apis, ingress_controller_endpoint, ns, is_vs=True) + resource_hosts[f"{ns}-vs"] = vs_host + namespaces.append(namespace) + + req_url = f"http://{ingress_controller_endpoint.public_ip}:{ingress_controller_endpoint.port}/backend1" + # add label to namespaces + patch_namespace_with_label(kube_apis.v1, "watched-ns", "watch", f"{TEST_DATA}/common/ns-patch.yaml") + patch_namespace_with_label(kube_apis.v1, "watched-ns2", "watch", f"{TEST_DATA}/common/ns-patch.yaml") + + def fin(): + if request.config.getoption("--skip-fixture-teardown") == "no": + print("Clean up:") + for ns in namespaces: + delete_namespace(kube_apis.v1, ns) + + request.addfinalizer(fin) + + return BackendSetup(req_url, resource_hosts) + + +def create_and_setup_namespace(kube_apis, ingress_controller_endpoint, ns_name, is_vs=False): + ns = create_namespace_with_name_from_yaml(kube_apis.v1, ns_name, f"{TEST_DATA}/common/ns.yaml") + print(f"------------------------- Deploy the backend in {ns} -----------------------------------") + create_example_app(kube_apis, "simple", ns) + src_ing_yaml = f"{TEST_DATA}/watch-namespace/{ns}-ingress.yaml" + src_vs_yaml = f"{TEST_DATA}/watch-namespace/{ns}-virtual-server.yaml" + if not is_vs: + create_items_from_yaml(kube_apis, src_ing_yaml, ns) + ingress_host = get_first_ingress_host_from_yaml(src_ing_yaml) + if is_vs: + create_virtual_server_from_yaml(kube_apis.custom_objects, src_vs_yaml, ns) + ingress_host = get_first_host_from_yaml(src_vs_yaml) + req_url = f"http://{ingress_controller_endpoint.public_ip}:{ingress_controller_endpoint.port}/backend1" + wait_until_all_pods_are_ready(kube_apis.v1, ns) + ensure_connection_to_public_endpoint( + ingress_controller_endpoint.public_ip, + ingress_controller_endpoint.port, + ingress_controller_endpoint.port_ssl, + ) + return ns, ingress_host + + +@pytest.mark.ingresses +@pytest.mark.watch_namespace +@pytest.mark.parametrize( + "ingress_controller, expected_responses", + [ + pytest.param( + {"extra_args": ["-watch-namespace-label=app=watch"]}, + {"watched-ns-ingress": 200, "watched-ns2-ingress": 200, "foreign-ns-ingress": 404}, + ) + ], + indirect=["ingress_controller"], +) +class TestWatchNamespaceLabelIngress: + def test_response_codes(self, kube_apis, ingress_controller, backend_setup, expected_responses): + for ing in ["watched-ns-ingress", "watched-ns2-ingress", "foreign-ns-ingress"]: + ensure_response_from_backend(backend_setup.req_url, backend_setup.resource_hosts[ing]) + resp = mock.Mock() + resp.status_code = "None" + retry = 0 + while resp.status_code != expected_responses[ing] and retry < 3: + resp = requests.get(backend_setup.req_url, headers={"host": backend_setup.resource_hosts[ing]}) + retry = retry + 1 + wait_before_test() + assert ( + resp.status_code == expected_responses[ing] + ), f"Expected: {expected_responses[ing]} response code for {backend_setup.resource_hosts[ing]}" + + # Add label to foreign-ns-ingress and show traffic being served + patch_namespace_with_label(kube_apis.v1, "foreign-ns", "watch", f"{TEST_DATA}/common/ns-patch.yaml") + ensure_response_from_backend(backend_setup.req_url, backend_setup.resource_hosts[ing]) + resp = mock.Mock() + resp.status_code = "None" + retry = 0 + ing = "foreign-ns-ingress" + while resp.status_code != 200 and retry < 3: + resp = requests.get(backend_setup.req_url, headers={"host": backend_setup.resource_hosts[ing]}) + retry = retry + 1 + wait_before_test() + assert ( + resp.status_code == 200 + ), f"Expected: 200 response code for {backend_setup.resource_hosts[ing]} after adding the correct label" + + # Remove label from foreign-ns-ingress and show traffic being ignored again + patch_namespace_with_label(kube_apis.v1, "foreign-ns", "nowatch", f"{TEST_DATA}/common/ns-patch.yaml") + ensure_response_from_backend(backend_setup.req_url, backend_setup.resource_hosts[ing]) + resp = mock.Mock() + resp.status_code = "None" + retry = 0 + while resp.status_code != expected_responses[ing] and retry < 3: + resp = requests.get(backend_setup.req_url, headers={"host": backend_setup.resource_hosts[ing]}) + retry = retry + 1 + wait_before_test() + assert ( + resp.status_code == expected_responses[ing] + ), f"Expected: {expected_responses[ing]} response code for {backend_setup.resource_hosts[ing]} after removing the watched label" + + +@pytest.mark.vs +@pytest.mark.vs_responses +@pytest.mark.parametrize( + "crd_ingress_controller, expected_responses", + [ + pytest.param( + {"type": "complete", "extra_args": ["-watch-namespace-label=app=watch", "-enable-custom-resources=true"]}, + {"watched-ns-vs": 200, "watched-ns2-vs": 200, "foreign-ns-vs": 404}, + ) + ], + indirect=["crd_ingress_controller"], +) +class TestWatchNamespaceLabelVS: + def test_response_codes(self, kube_apis, crd_ingress_controller, backend_setup_vs, expected_responses): + for vs in ["watched-ns-vs", "watched-ns2-vs", "foreign-ns-vs"]: + ensure_response_from_backend(backend_setup_vs.req_url, backend_setup_vs.resource_hosts[vs]) + resp = mock.Mock() + resp.status_code = "None" + retry = 0 + while resp.status_code != expected_responses[vs] and retry < 3: + resp = requests.get(backend_setup_vs.req_url, headers={"host": backend_setup_vs.resource_hosts[vs]}) + retry = retry + 1 + wait_before_test() + assert ( + resp.status_code == expected_responses[vs] + ), f"Expected: {expected_responses[vs]} response code for {backend_setup_vs.resource_hosts[vs]}" + + # Add label to foreign-ns-vs and show traffic being served + patch_namespace_with_label(kube_apis.v1, "foreign-ns", "watch", f"{TEST_DATA}/common/ns-patch.yaml") + ensure_response_from_backend(backend_setup_vs.req_url, backend_setup_vs.resource_hosts[vs]) + resp = mock.Mock() + resp.status_code = "None" + retry = 0 + vs = "foreign-ns-vs" + while resp.status_code != 200 and retry < 3: + resp = requests.get(backend_setup_vs.req_url, headers={"host": backend_setup_vs.resource_hosts[vs]}) + retry = retry + 1 + wait_before_test() + assert ( + resp.status_code == 200 + ), f"Expected: 200 response code for {backend_setup_vs.resource_hosts[vs]} after adding the correct label" + + # Remove label from foreign-ns-vs and show traffic being ignored again + patch_namespace_with_label(kube_apis.v1, "foreign-ns", "nowatch", f"{TEST_DATA}/common/ns-patch.yaml") + ensure_response_from_backend(backend_setup_vs.req_url, backend_setup_vs.resource_hosts[vs]) + resp = mock.Mock() + resp.status_code = "None" + retry = 0 + while resp.status_code != expected_responses[vs] and retry < 3: + resp = requests.get(backend_setup_vs.req_url, headers={"host": backend_setup_vs.resource_hosts[vs]}) + retry = retry + 1 + wait_before_test() + assert ( + resp.status_code == expected_responses[vs] + ), f"Expected: {expected_responses[vs]} response code for {backend_setup_vs.resource_hosts[vs]} after removing the watched label" diff --git a/tests/suite/test_watch_secret_namespace.py b/tests/suite/test_watch_secret_namespace.py index fee4878bc9..479f0cfa70 100644 --- a/tests/suite/test_watch_secret_namespace.py +++ b/tests/suite/test_watch_secret_namespace.py @@ -2,13 +2,13 @@ import pytest import requests -from kubernetes.client.rest import ApiException from settings import TEST_DATA -from suite.utils.resources_utils import create_secret_from_yaml, ensure_response_from_backend, wait_before_test +from suite.utils.resources_utils import create_secret_from_yaml, wait_before_test from suite.utils.ssl_utils import create_sni_session @pytest.mark.vsr +@pytest.mark.vsr_secrets @pytest.mark.parametrize( "crd_ingress_controller, v_s_route_setup", [ @@ -57,12 +57,13 @@ def test_responses( except requests.exceptions.SSLError as e: exception = str(e) print(f"SSL certificate exception: {exception}") - retry = +1 + retry = retry + 1 assert resp.status_code == 200 @pytest.mark.vsr +@pytest.mark.vsr_secrets @pytest.mark.parametrize( "crd_ingress_controller, v_s_route_setup", [ diff --git a/tests/suite/test_wildcard_tls_secret.py b/tests/suite/test_wildcard_tls_secret.py index bf13820612..6d40fabead 100644 --- a/tests/suite/test_wildcard_tls_secret.py +++ b/tests/suite/test_wildcard_tls_secret.py @@ -64,11 +64,12 @@ def wildcard_tls_secret_setup( wait_until_all_pods_are_ready(kube_apis.v1, test_namespace) def fin(): - print("Clean up Wildcard-Tls-Secret-Example:") - delete_items_from_yaml( - kube_apis, f"{TEST_DATA}/wildcard-tls-secret/{ing_type}/wildcard-secret-ingress.yaml", test_namespace - ) - delete_common_app(kube_apis, "simple", test_namespace) + if request.config.getoption("--skip-fixture-teardown") == "no": + print("Clean up Wildcard-Tls-Secret-Example:") + delete_items_from_yaml( + kube_apis, f"{TEST_DATA}/wildcard-tls-secret/{ing_type}/wildcard-secret-ingress.yaml", test_namespace + ) + delete_common_app(kube_apis, "simple", test_namespace) request.addfinalizer(fin) @@ -102,10 +103,11 @@ def wildcard_tls_secret_ingress_controller( ) def fin(): - print("Remove IC and wildcard secret:") - delete_ingress_controller(kube_apis.apps_v1_api, name, cli_arguments["deployment-type"], namespace) - if is_secret_present(kube_apis.v1, secret_name, namespace): - delete_secret(kube_apis.v1, secret_name, namespace) + if request.config.getoption("--skip-fixture-teardown") == "no": + print("Remove IC and wildcard secret:") + delete_ingress_controller(kube_apis.apps_v1_api, name, cli_arguments["deployment-type"], namespace) + if is_secret_present(kube_apis.v1, secret_name, namespace): + delete_secret(kube_apis.v1, secret_name, namespace) request.addfinalizer(fin) return IngressControllerWithSecret(secret_name) @@ -113,6 +115,7 @@ def fin(): @pytest.mark.ingresses @pytest.mark.smoke +@pytest.mark.wildcard_tls class TestTLSWildcardSecrets: @pytest.mark.parametrize("path", paths) def test_response_code_200(self, wildcard_tls_secret_ingress_controller, wildcard_tls_secret_setup, path): @@ -182,7 +185,7 @@ def test_certificate_subject_updates_after_secret_update( ) assert subject_dict[b"C"] == b"GB" assert subject_dict[b"ST"] == b"Cambridgeshire" - assert subject_dict[b"CN"] == b"cafe.example.com" + assert subject_dict[b"CN"] == b"example.com" def test_response_and_subject_remains_after_secret_delete( self, @@ -208,4 +211,4 @@ def test_response_and_subject_remains_after_secret_delete( ) assert subject_dict[b"C"] == b"GB" assert subject_dict[b"ST"] == b"Cambridgeshire" - assert subject_dict[b"CN"] == b"cafe.example.com" + assert subject_dict[b"CN"] == b"example.com" diff --git a/tests/suite/utils/custom_assertions.py b/tests/suite/utils/custom_assertions.py index d8f97e6826..6cfc926886 100644 --- a/tests/suite/utils/custom_assertions.py +++ b/tests/suite/utils/custom_assertions.py @@ -1,4 +1,5 @@ """Describe the custom assertion methods""" + import time import pytest @@ -143,6 +144,19 @@ def assert_event(event_text, events_list) -> None: pytest.fail(f'Failed to find the event "{event_text}" in the list. Exiting...') +def assert_event_not_present(event_text, events_list) -> None: + """ + Search for the event in the list. + + :param event_text: event text + :param events_list: list of events + :return: + """ + for i in range(len(events_list) - 1, -1, -1): + if event_text in events_list[i].message: + pytest.fail(f'Event "{event_text}" exists in the list. Exiting...') + + def assert_event_starts_with_text_and_contains_errors(event_text, events_list, fields_list) -> None: """ Search for the event starting with the expected text in the list and check its message. @@ -254,3 +268,36 @@ def assert_proxy_entries_exist(config) -> None: assert "proxy_next_upstream error timeout;" in config assert "proxy_next_upstream_timeout 0s;" in config assert "proxy_next_upstream_tries 0;" in config + + +def assert_pods_scaled_to_count(apps_v1_api, v1, deployment_name, namespace, expected_count, timeout=60, interval=1): + """ + Check if the number of pods for a given deployment has scaled down to the expected count. + + :param apps_v1_api: AppsV1Api + :param v1: CoreV1Api + :param deployment_name: name of the deployment to check. + :param namespace: namespace of the deployment. + :param expected_count: expected number of pods after scaling. + :param timeout: Maximum time to wait for the expected count to be met. + :param interval: Time to wait between checks. + """ + end_time = time.time() + timeout + while time.time() < end_time: + selector = ",".join( + [ + f"{key}={value}" + for key, value in apps_v1_api.read_namespaced_deployment( + deployment_name, namespace + ).spec.selector.match_labels.items() + ] + ) + pods = v1.list_namespaced_pod(namespace, label_selector=selector) + pod_count = len(pods.items) + if pod_count == expected_count: + print(f"Expected {expected_count} pods, found {pod_count} for '{deployment_name}' in '{namespace}'.") + return + time.sleep(interval) + assert ( + False + ), f"Expected {expected_count} pods, but found {pod_count} for '{deployment_name}' in '{namespace}' after {timeout} seconds." diff --git a/tests/suite/utils/custom_resources_utils.py b/tests/suite/utils/custom_resources_utils.py index 9fcff4bf78..2d9ef2bf73 100644 --- a/tests/suite/utils/custom_resources_utils.py +++ b/tests/suite/utils/custom_resources_utils.py @@ -1,4 +1,5 @@ """Describe methods to utilize the kubernetes-client.""" + import logging import time from pprint import pprint @@ -100,7 +101,26 @@ def is_dnsendpoint_present(custom_objects: CustomObjectsApi, name, namespace) -> return True -def read_custom_resource_v1alpha1(custom_objects: CustomObjectsApi, namespace, plural, name) -> object: +def create_resource_from_manifest(custom_objects: CustomObjectsApi, body, namespace, plural) -> None: + """ + Create a Resource based on manifest. + + :param custom_objects: CustomObjectsApi + :param body: manifest body + :param namespace: namespace where the resource will be created + :param plural: the plural of the resource + """ + try: + print("Create a Custom Resource: " + body["kind"]) + group, version = body["apiVersion"].split("/") + custom_objects.create_namespaced_custom_object(group, version, namespace, plural, body) + print(f"Custom resource {body['kind']} created with name '{body['metadata']['name']}'") + except ApiException as ex: + logging.exception(f"Exception: {ex} occurred while creating {body['kind']}: {body['metadata']['name']}") + raise ex + + +def read_custom_resource_v1(custom_objects: CustomObjectsApi, namespace, plural, name) -> object: """ Get CRD information (kubectl describe output) @@ -110,9 +130,9 @@ def read_custom_resource_v1alpha1(custom_objects: CustomObjectsApi, namespace, p :param name: the custom object's name :return: object """ - print(f"Getting info for v1alpha1 crd {name} in namespace {namespace}") + print(f"Getting info for v1 crd {name} in namespace {namespace}") try: - response = custom_objects.get_namespaced_custom_object("k8s.nginx.org", "v1alpha1", namespace, plural, name) + response = custom_objects.get_namespaced_custom_object("k8s.nginx.org", "v1", namespace, plural, name) pprint(response) return response @@ -125,7 +145,7 @@ def read_ts(custom_objects: CustomObjectsApi, namespace, name) -> object: """ Read TransportService resource. """ - return read_custom_resource_v1alpha1(custom_objects, namespace, "transportservers", name) + return read_custom_resource(custom_objects, namespace, "transportservers", name) def create_ts_from_yaml(custom_objects: CustomObjectsApi, yaml_manifest, namespace) -> dict: @@ -149,9 +169,23 @@ def create_gc_from_yaml(custom_objects: CustomObjectsApi, yaml_manifest, namespa :param namespace: :return: a dictionary representing the resource """ + print(f"Load {yaml_manifest}") return create_resource_from_yaml(custom_objects, yaml_manifest, namespace, "globalconfigurations") +def patch_gc_from_yaml(custom_objects: CustomObjectsApi, name, yaml_manifest, namespace) -> dict: + """ + Patch a GlobalConfiguration Resource based on yaml file. + + :param custom_objects: CustomObjectsApi + :param yaml_manifest: an absolute path to file + :param namespace: + :return: a dictionary representing the resource + """ + print(f"Load {yaml_manifest}") + return patch_custom_resource(custom_objects, name, yaml_manifest, namespace, "globalconfigurations") + + def create_resource_from_yaml(custom_objects: CustomObjectsApi, yaml_manifest, namespace, plural) -> dict: """ Create a Resource based on yaml file. @@ -282,6 +316,7 @@ def create_dos_protected_from_yaml(custom_objects: CustomObjectsApi, yaml_manife "", ing_namespace ) dep["spec"]["apDosPolicy"] = dep["spec"]["apDosPolicy"].replace("", namespace) + dep["spec"]["dosAccessLogDest"] = dep["spec"]["dosAccessLogDest"].replace("", ing_namespace) custom_objects.create_namespaced_custom_object( "appprotectdos.f5.com", "v1beta1", namespace, "dosprotectedresources", dep ) @@ -359,10 +394,10 @@ def patch_ts_from_yaml(custom_objects: CustomObjectsApi, name, yaml_manifest, na """ Patch a TransportServer based on yaml manifest """ - return patch_custom_resource_v1alpha1(custom_objects, name, yaml_manifest, namespace, "transportservers") + return patch_custom_resource(custom_objects, name, yaml_manifest, namespace, "transportservers") -def patch_custom_resource_v1alpha1(custom_objects: CustomObjectsApi, name, yaml_manifest, namespace, plural) -> None: +def patch_custom_resource(custom_objects: CustomObjectsApi, name, yaml_manifest, namespace, plural) -> None: """ Patch a custom resource based on yaml manifest """ @@ -371,7 +406,7 @@ def patch_custom_resource_v1alpha1(custom_objects: CustomObjectsApi, name, yaml_ dep = yaml.safe_load(f) try: - custom_objects.patch_namespaced_custom_object("k8s.nginx.org", "v1alpha1", namespace, plural, name, dep) + custom_objects.patch_namespaced_custom_object("k8s.nginx.org", "v1", namespace, plural, name, dep) except ApiException: logging.exception(f"Failed with exception while patching custom resource: {name}") raise @@ -386,9 +421,7 @@ def patch_ts(custom_objects: CustomObjectsApi, namespace, body) -> None: print(f"Update a Resource: {name}") try: - custom_objects.patch_namespaced_custom_object( - "k8s.nginx.org", "v1alpha1", namespace, "transportservers", name, body - ) + custom_objects.patch_namespaced_custom_object("k8s.nginx.org", "v1", namespace, "transportservers", name, body) except ApiException: logging.exception(f"Failed with exception while patching custom resource: {name}") raise diff --git a/tests/suite/utils/dos_utils.py b/tests/suite/utils/dos_utils.py index 116ce35f2c..39ad593aef 100644 --- a/tests/suite/utils/dos_utils.py +++ b/tests/suite/utils/dos_utils.py @@ -52,10 +52,15 @@ def check_learning_status_with_admd_s(kube_apis, syslog_pod, namespace, time): retry = 0 learning_sas = 0.0 learning_signature = 0.0 - while (learning_sas < 75 or learning_signature != 100) and retry <= time / 15: + retry_time = 15 + while (learning_sas < 75 or learning_signature != 100) and retry <= time / retry_time: retry += 1 - admd_contents = get_admd_s_contents(kube_apis.v1, syslog_pod, namespace, 15) + admd_contents = get_admd_s_contents(kube_apis.v1, syslog_pod, namespace, retry_time) admd_s_dic = admd_s_content_to_dic(admd_contents) + if "name.info.learning" not in admd_s_dic: + print("name.info.learning not found in admd_s_dic") + wait_before_test(retry_time) + continue learn = admd_s_dic["name.info.learning"].replace("[", "").replace("]", "").split(",") learning_sas = float(learn[0]) learning_signature = float(learn[3]) diff --git a/tests/suite/utils/kube_config_utils.py b/tests/suite/utils/kube_config_utils.py index 4f07db884b..179202e647 100644 --- a/tests/suite/utils/kube_config_utils.py +++ b/tests/suite/utils/kube_config_utils.py @@ -1,4 +1,5 @@ """Describe methods to work with kubeconfig file.""" + import pytest import yaml diff --git a/tests/suite/utils/nginx_api_utils.py b/tests/suite/utils/nginx_api_utils.py index febbd03de6..d249b7aae7 100644 --- a/tests/suite/utils/nginx_api_utils.py +++ b/tests/suite/utils/nginx_api_utils.py @@ -1,4 +1,5 @@ """Describe the methods to work with nginx api""" + import ast import pytest diff --git a/tests/suite/utils/resources_utils.py b/tests/suite/utils/resources_utils.py index 692ef4e058..06b3f0530d 100644 --- a/tests/suite/utils/resources_utils.py +++ b/tests/suite/utils/resources_utils.py @@ -1,4 +1,6 @@ """Describe methods to utilize the kubernetes-client.""" + +import base64 import json import os import re @@ -8,11 +10,19 @@ import pytest import requests import yaml -from kubernetes.client import AppsV1Api, CoreV1Api, NetworkingV1Api, RbacAuthorizationV1Api, V1Service +from kubernetes.client import ( + AppsV1Api, + CoreV1Api, + NetworkingV1Api, + RbacAuthorizationV1Api, + V1ObjectMeta, + V1Secret, + V1Service, +) from kubernetes.client.rest import ApiException from kubernetes.stream import stream from more_itertools import first -from settings import DEPLOYMENTS, PROJECT_ROOT, RECONFIGURATION_DELAY, TEST_DATA +from settings import DEPLOYMENTS, NGX_REG, PROJECT_ROOT, RECONFIGURATION_DELAY, TEST_DATA, WAF_V5_VERSION from suite.utils.ssl_utils import create_sni_session @@ -291,13 +301,32 @@ def wait_until_all_pods_are_ready(v1: CoreV1Api, namespace) -> None: while not are_all_pods_in_ready_state(v1, namespace) and counter < 200: # remove counter based condition from line #264 and #269 if --batch-start="True" print("There are pods that are not Ready. Wait for 1 sec...") - time.sleep(1) + wait_before_test() counter = counter + 1 if counter >= 300: + print("\n===================== IC Logs Start =====================") + try: + pod_name = get_pod_name_that_contains(kube_apis.v1, "nginx-ingress", "nginx-ingress") + logs = kube_apis.v1.read_namespaced_pod_log(pod_name, "nginx-ingress") + print(logs) + except: + print("Failed to load logs for nginx-ingress pod") + print("\n===================== IC Logs End =====================") raise PodNotReadyException() print("All pods are Ready") +def get_pod_list(v1: CoreV1Api, namespace) -> []: + """ + Get a list of pods in a namespace. + + :param v1: CoreV1Api + :param namespace: namespace + :return: [] + """ + return v1.list_namespaced_pod(namespace).items + + def get_first_pod_name(v1: CoreV1Api, namespace) -> str: """ Return 1st pod_name in a list of pods in a namespace. @@ -323,6 +352,7 @@ def are_all_pods_in_ready_state(v1: CoreV1Api, namespace) -> bool: return False pod_ready_amount = 0 for pod in pods.items: + print(f"Pod {pod.metadata.name} has image {pod.spec.containers[0].image}") if pod.status.conditions is None: return False for condition in pod.status.conditions: @@ -407,7 +437,7 @@ def create_service(v1: CoreV1Api, namespace, body) -> str: return resp.metadata.name -def create_service_with_name(v1: CoreV1Api, namespace, name) -> str: +def create_service_with_name(v1: CoreV1Api, namespace, name, port=80, targetPort=8080) -> str: """ Create a service with a specific name based on a common yaml manifest. @@ -421,10 +451,31 @@ def create_service_with_name(v1: CoreV1Api, namespace, name) -> str: dep = yaml.safe_load(f) dep["metadata"]["name"] = name dep["spec"]["selector"]["app"] = name.replace("-svc", "") + dep["spec"]["ports"][0]["port"] = port + dep["spec"]["ports"][0]["targetPort"] = targetPort return create_service(v1, namespace, dep) -def get_service_node_ports(v1: CoreV1Api, name, namespace) -> (int, int, int, int, int, int): +def create_secure_app_deployment_with_name(apps_v1_api: AppsV1Api, namespace, name) -> str: + """ + Deploys app in /common/app/secure in the configured name and namespace + + :param v1: CoreV1Api + :param namespace: namespace name + :param name: name + :return: str + """ + print(f"Create a Service with a specific name: {name}") + with open(f"{TEST_DATA}/common/app/secure/deployment/secure-app.yaml") as f: + dep = yaml.safe_load(f) + dep["metadata"]["name"] = name + dep["spec"]["selector"]["matchLabels"]["app"] = name + dep["spec"]["template"]["metadata"]["labels"]["app"] = name + dep["spec"]["template"]["spec"]["containers"][0]["name"] = name + return create_deployment(apps_v1_api, namespace, dep) + + +def get_service_node_ports(v1: CoreV1Api, name, namespace) -> (int, int, int, int, int, int, int): """ Get service allocated node_ports. @@ -434,10 +485,19 @@ def get_service_node_ports(v1: CoreV1Api, name, namespace) -> (int, int, int, in :return: (plain_port, ssl_port, api_port, exporter_port) """ resp = v1.read_namespaced_service(name, namespace) - if len(resp.spec.ports) == 6: + if len(resp.spec.ports) == 7: print("An unexpected amount of ports in a service. Check the configuration") + + print(f"Service with an HTTP port: {resp.spec.ports[0].node_port}") + print(f"Service with an HTTPS port: {resp.spec.ports[1].node_port}") print(f"Service with an API port: {resp.spec.ports[2].node_port}") print(f"Service with an Exporter port: {resp.spec.ports[3].node_port}") + print(f"Service with an TPC server port: {resp.spec.ports[4].node_port}") + print(f"Service with an UDP server port: {resp.spec.ports[5].node_port}") + print(f"Service with an Service Insight port: {resp.spec.ports[6].node_port}") + print(f"Service with an custom SSL port: {resp.spec.ports[7].node_port}") + print(f"Service with an custom http listener port: {resp.spec.ports[8].node_port}") + print(f"Service with an custom https listener port: {resp.spec.ports[9].node_port}") return ( resp.spec.ports[0].node_port, resp.spec.ports[1].node_port, @@ -445,6 +505,10 @@ def get_service_node_ports(v1: CoreV1Api, name, namespace) -> (int, int, int, in resp.spec.ports[3].node_port, resp.spec.ports[4].node_port, resp.spec.ports[5].node_port, + resp.spec.ports[6].node_port, + resp.spec.ports[7].node_port, + resp.spec.ports[8].node_port, + resp.spec.ports[9].node_port, ) @@ -500,6 +564,15 @@ def create_secret(v1: CoreV1Api, namespace, body) -> str: return body["metadata"]["name"] +def create_license(v1: CoreV1Api, namespace, jwt, license_token_name="license-token") -> str: + sec = V1Secret() + sec.type = "nginx.com/license" + sec.metadata = V1ObjectMeta(name=license_token_name) + sec.data = {"license.jwt": base64.b64encode(jwt.encode("ascii")).decode()} + v1.create_namespaced_secret(namespace=namespace, body=sec) + return license_token_name + + def replace_secret(v1: CoreV1Api, name, namespace, yaml_manifest) -> str: """ Replace a secret based on yaml file. @@ -707,6 +780,24 @@ def create_namespace_with_name_from_yaml(v1: CoreV1Api, name, yaml_manifest) -> return dep["metadata"]["name"] +def patch_namespace_with_label(v1: CoreV1Api, name, label, yaml_manifest) -> str: + """ + Update a namespace with a specific label based on a yaml manifest. + + :param v1: CoreV1Api + :param name: name + :param label: the name of the label + :param yaml_manifest: an absolute path to file + :return: str + """ + print(f"Update namespace {name} with label app={label}") + with open(yaml_manifest) as f: + dep = yaml.safe_load(f) + dep["metadata"]["labels"]["app"] = label + v1.patch_namespace(name, dep) + print(f"Namespace {name} updated with label: {label}") + + def create_service_account(v1: CoreV1Api, namespace, body) -> None: """ Create a ServiceAccount based on a dict. @@ -846,48 +937,32 @@ def get_file_contents(v1: CoreV1Api, file_path, pod_name, pod_namespace, print_l :return: str """ command = ["cat", file_path] - resp = stream( - v1.connect_get_namespaced_pod_exec, - pod_name, - pod_namespace, - command=command, - stderr=True, - stdin=False, - stdout=True, - tty=False, - ) + retries = 0 + while retries <= 3: + wait_before_test() + try: + resp = stream( + v1.connect_get_namespaced_pod_exec, + pod_name, + pod_namespace, + command=command, + stderr=True, + stdin=False, + stdout=True, + tty=False, + ) + break + except Exception as e: + print(f"Error: {e}") + retries += 1 + if retries == 3: + raise e result_conf = str(resp) if print_log: print("\nFile contents:\n" + result_conf) return result_conf -def clear_file_contents(v1: CoreV1Api, file_path, pod_name, pod_namespace) -> str: - """ - Execute 'truncate -s 0 file_path' command in a pod. - - :param v1: CoreV1Api - :param pod_name: pod name - :param pod_namespace: pod namespace - :param file_path: an absolute path to a file in the pod - :return: str - """ - command = ["truncate", "-s", "0", file_path] - resp = stream( - v1.connect_get_namespaced_pod_exec, - pod_name, - pod_namespace, - command=command, - stderr=True, - stdin=False, - stdout=True, - tty=False, - ) - result_conf = str(resp) - - return result_conf - - def nginx_reload(v1: CoreV1Api, pod_name, pod_namespace) -> str: """ Execute 'nginx -s reload' command in a pod. @@ -964,6 +1039,21 @@ def get_ingress_nginx_template_conf(v1: CoreV1Api, ingress_namespace, ingress_na return get_file_contents(v1, file_path, pod_name, pod_namespace) +def get_vs_nginx_template_conf(v1: CoreV1Api, vs_namespace, vs_name, pod_name, pod_namespace) -> str: + """ + Get contents of /etc/nginx/conf.d/vs_{namespace}_{ingress_name}.conf in the pod. + + :param v1: CoreV1Api + :param ingress_namespace: + :param ingress_name: + :param pod_name: + :param pod_namespace: + :return: str + """ + file_path = f"/etc/nginx/conf.d/vs_{vs_namespace}_{vs_name}.conf" + return get_file_contents(v1, file_path, pod_name, pod_namespace) + + def get_ts_nginx_template_conf(v1: CoreV1Api, resource_namespace, resource_name, pod_name, pod_namespace) -> str: """ Get contents of /etc/nginx/stream-conf.d/ts_{namespace}-{resource_name}.conf in the pod. @@ -1112,6 +1202,215 @@ def create_ingress_controller(v1: CoreV1Api, apps_v1_api: AppsV1Api, cli_argumen dep["spec"]["replicas"] = int(cli_arguments["replicas"]) dep["spec"]["template"]["spec"]["containers"][0]["image"] = cli_arguments["image"] dep["spec"]["template"]["spec"]["containers"][0]["imagePullPolicy"] = cli_arguments["image-pull-policy"] + dep["spec"]["template"]["spec"]["containers"][0]["args"].extend( + [ + f"-default-server-tls-secret=$(POD_NAMESPACE)/default-server-secret", + f"-enable-telemetry-reporting=false", + ] + ) + if args is not None: + dep["spec"]["template"]["spec"]["containers"][0]["args"].extend(args) + if cli_arguments["deployment-type"] == "deployment": + name = create_deployment(apps_v1_api, namespace, dep) + else: + name = create_daemon_set(apps_v1_api, namespace, dep) + before = time.time() + wait_until_all_pods_are_ready(v1, namespace) + after = time.time() + print(f"All pods came up in {int(after - before)} seconds") + print(f"Ingress Controller was created with name '{name}'") + return name + + +def create_ingress_controller_wafv5( + v1: CoreV1Api, apps_v1_api: AppsV1Api, cli_arguments, namespace, reg_secret, args=None, rorfs=False +) -> str: + """ + Create an Ingress Controller according to the params. + + :param v1: CoreV1Api + :param apps_v1_api: AppsV1Api + :param cli_arguments: context name as in kubeconfig + :param namespace: namespace name + :param args: a list of any extra cli arguments to start IC with + :return: str + """ + print(f"Create an Ingress Controller as {cli_arguments['ic-type']}") + yaml_manifest = f"{DEPLOYMENTS}/{cli_arguments['deployment-type']}/{cli_arguments['ic-type']}.yaml" + with open(yaml_manifest) as f: + dep = yaml.safe_load(f) + dep["spec"]["replicas"] = int(cli_arguments["replicas"]) + dep["spec"]["template"]["spec"]["containers"][0]["image"] = cli_arguments["image"] + dep["spec"]["template"]["spec"]["containers"][0]["imagePullPolicy"] = cli_arguments["image-pull-policy"] + if "readOnlyRootFilesystem" not in dep["spec"]["template"]["spec"]["containers"][0]["securityContext"]: + dep["spec"]["template"]["spec"]["containers"][0]["securityContext"]["readOnlyRootFilesystem"] = rorfs + + template_spec = dep["spec"]["template"]["spec"] + if "imagePullSecrets" not in template_spec: + template_spec["imagePullSecrets"] = [] + + template_spec["imagePullSecrets"].append({"name": f"{reg_secret}"}) + if "volumes" not in template_spec: + template_spec["volumes"] = [] + + if rorfs and "initContainers" not in template_spec: + template_spec["initContainers"] = [] + template_spec["initContainers"].extend( + [ + { + "name": "init-nginx-ingress", + "image": cli_arguments["image"], + "imagePullPolicy": "IfNotPresent", + "command": ["cp", "-vdR", "/etc/nginx/.", "/mnt/etc"], + "securityContext": { + "allowPrivilegeEscalation": False, + "readOnlyRootFilesystem": True, + "runAsUser": 101, # nginx + "runAsNonRoot": True, + "capabilities": {"drop": ["ALL"]}, + }, + "volumeMounts": [{"mountPath": "/mnt/etc", "name": "nginx-etc"}], + } + ] + ) + + if rorfs: + template_spec["volumes"].extend( + [ + { + "name": "app-protect-bd-config", + "emptyDir": {}, + }, + { + "name": "app-protect-config", + "emptyDir": {}, + }, + { + "name": "app-protect-bundles", + "emptyDir": {}, + }, + {"name": "nginx-etc", "emptyDir": {}}, + {"name": "nginx-log", "emptyDir": {}}, + {"name": "nginx-cache", "emptyDir": {}}, + {"name": "nginx-lib", "emptyDir": {}}, + ] + ) + else: + template_spec["volumes"].extend( + [ + { + "name": "app-protect-bd-config", + "emptyDir": {}, + }, + { + "name": "app-protect-config", + "emptyDir": {}, + }, + { + "name": "app-protect-bundles", + "emptyDir": {}, + }, + ] + ) + + container = dep["spec"]["template"]["spec"]["containers"][0] + if "volumeMounts" not in container: + container["volumeMounts"] = [] + + if rorfs: + container["volumeMounts"].extend( + [ + { + "name": "app-protect-bd-config", + "mountPath": "/opt/app_protect/bd_config", + }, + { + "name": "app-protect-config", + "mountPath": "/opt/app_protect/config", + }, + { + "name": "app-protect-bundles", + "mountPath": "/etc/app_protect/bundles", + }, + {"name": "nginx-etc", "mountPath": "/etc/nginx"}, + {"name": "nginx-log", "mountPath": "/var/log/nginx"}, + {"name": "nginx-cache", "mountPath": "/var/cache/nginx"}, + {"name": "nginx-lib", "mountPath": "/var/lib/nginx"}, + ] + ) + else: + container["volumeMounts"].extend( + [ + { + "name": "app-protect-bd-config", + "mountPath": "/opt/app_protect/bd_config", + }, + { + "name": "app-protect-config", + "mountPath": "/opt/app_protect/config", + }, + { + "name": "app-protect-bundles", + "mountPath": "/etc/app_protect/bundles", + }, + ] + ) + + dep["spec"]["template"]["spec"]["containers"][0]["args"].extend( + [ + f"-default-server-tls-secret=$(POD_NAMESPACE)/default-server-secret", + f"-enable-telemetry-reporting=false", + ] + ) + + waf_cfg_mgr = { + "name": "waf-config-mgr", + "image": f"{NGX_REG}/nap/waf-config-mgr:{WAF_V5_VERSION}", + "imagePullPolicy": "IfNotPresent", + "securityContext": { + "allowPrivilegeEscalation": False, + "capabilities": {"drop": ["all"]}, + "readOnlyRootFilesystem": rorfs, + }, + "volumeMounts": [ + { + "name": "app-protect-bd-config", + "mountPath": "/opt/app_protect/bd_config", + }, + { + "name": "app-protect-config", + "mountPath": "/opt/app_protect/config", + }, + { + "name": "app-protect-bundles", + "mountPath": "/etc/app_protect/bundles", + }, + ], + } + waf_enforcer = { + "name": "waf-enforcer", + "image": f"{NGX_REG}/nap/waf-enforcer:{WAF_V5_VERSION}", + "imagePullPolicy": "IfNotPresent", + "securityContext": { + "allowPrivilegeEscalation": False, + "capabilities": {"drop": ["all"]}, + "readOnlyRootFilesystem": rorfs, + }, + "env": [ + {"name": "ENFORCER_PORT", "value": "50000"}, + {"name": "ENFORCER_CONFIG_TIMEOUT", "value": "0"}, + ], + "volumeMounts": [ + { + "name": "app-protect-bd-config", + "mountPath": "/opt/app_protect/bd_config", + } + ], + } + + dep["spec"]["template"]["spec"]["containers"].append(waf_cfg_mgr) + dep["spec"]["template"]["spec"]["containers"].append(waf_enforcer) + if args is not None: dep["spec"]["template"]["spec"]["containers"][0]["args"].extend(args) if cli_arguments["deployment-type"] == "deployment": @@ -1418,6 +1717,20 @@ def replace_service(v1: CoreV1Api, name, namespace, body) -> str: return resp.metadata.name +def get_events_for_object(v1: CoreV1Api, namespace, object_name) -> []: + """ + Get the list of events of an objectin a namespace. + + :param v1: CoreV1Api + :param namespace: namespace + :param object_name: object name + :return: [] + """ + print(f"Get the events for {object_name} in the namespace: {namespace}") + events = v1.list_namespaced_event(namespace) + return [event for event in events.items if event.involved_object.name == object_name] + + def get_events(v1: CoreV1Api, namespace) -> []: """ Get the list of events in a namespace. @@ -1484,7 +1797,7 @@ def ensure_response_from_backend(req_url, host, additional_headers=None, check40 if resp.status_code != 502 and resp.status_code != 504: print(f"After {_} retries at 1 second interval, got non 502|504 response. Continue with tests...") return - time.sleep(1) + wait_before_test() pytest.fail(f"Keep getting 502|504 from {req_url} after 60 seconds. Exiting...") @@ -1632,3 +1945,69 @@ def get_last_log_entry(kube_apis, pod_name, namespace) -> str: # Our log entries end in '\n' which means the final entry when we split on a new line # is an empty string. Return the second to last entry instead. return logs.split("\n")[-2] + + +def get_resource_metrics(kube_apis, plural, namespace="nginx-ingress") -> str: + """ + :param kube_apis: kube apis + :param namespace: the namespace + :param plural: the plural of the resource + """ + if plural == "pods": + metrics = kube_apis.list_namespaced_custom_object("metrics.k8s.io", "v1beta1", namespace, plural) + while metrics["items"] == []: + wait_before_test() + try: + metrics = kube_apis.list_namespaced_custom_object("metrics.k8s.io", "v1beta1", namespace, plural) + except ApiException as e: + print(f"Error: {e}") + elif plural == "nodes": + metrics = kube_apis.list_cluster_custom_object("metrics.k8s.io", "v1beta1", plural) + while metrics["items"] == []: + wait_before_test() + try: + metrics = kube_apis.list_cluster_custom_object("metrics.k8s.io", "v1beta1", plural) + except ApiException as e: + print(f"Error: {e}") + else: + return "Invalid plural specified. Please use 'pods' or 'nodes' as the plural" + return metrics["items"] + + +def get_apikey_auth_secrets_from_yaml(yaml_manifest) -> list: + """ + Get apikey auth keys from yaml file. + + :param yaml_manifest: an absolute path to file + :return: []apikeys + """ + api_keys = [] + + with open(yaml_manifest) as file: + data = yaml.safe_load(file) + if "data" in data: + for key, encoded_value in data["data"].items(): + decoded_value = base64.b64decode(encoded_value).decode("utf-8") + api_keys.append(decoded_value) + return api_keys + + +def get_apikey_policy_details_from_yaml(yaml_manifest) -> dict: + """ + Extract headers and queries from an API key policy yaml file. + + :param yaml_manifest: an absolute path to file + :return: dictionary with 'headers' and 'queries' + """ + details = {"headers": [], "queries": []} + + with open(yaml_manifest) as file: + data = yaml.safe_load(file) + + if "spec" in data and "apiKey" in data["spec"] and "suppliedIn" in data["spec"]["apiKey"]: + if "header" in data["spec"]["apiKey"]["suppliedIn"]: + details["headers"] = data["spec"]["apiKey"]["suppliedIn"]["header"] + if "query" in data["spec"]["apiKey"]["suppliedIn"]: + details["queries"] = data["spec"]["apiKey"]["suppliedIn"]["query"] + + return details diff --git a/tests/suite/utils/ssl_utils.py b/tests/suite/utils/ssl_utils.py index e99fc640c2..feedfa5730 100644 --- a/tests/suite/utils/ssl_utils.py +++ b/tests/suite/utils/ssl_utils.py @@ -2,7 +2,6 @@ import socket import ssl -from urllib.parse import urlparse import OpenSSL import requests diff --git a/tests/suite/utils/vs_vsr_resources_utils.py b/tests/suite/utils/vs_vsr_resources_utils.py index c1fe02ef8c..2b2982aa08 100644 --- a/tests/suite/utils/vs_vsr_resources_utils.py +++ b/tests/suite/utils/vs_vsr_resources_utils.py @@ -6,7 +6,7 @@ from kubernetes.client import CoreV1Api, CustomObjectsApi from kubernetes.client.rest import ApiException from suite.utils.custom_resources_utils import read_custom_resource -from suite.utils.resources_utils import ensure_item_removal, get_file_contents +from suite.utils.resources_utils import ensure_item_removal, get_file_contents, wait_before_test def read_vs(custom_objects: CustomObjectsApi, namespace, name) -> object: @@ -144,6 +144,7 @@ def delete_and_create_vs_from_yaml(custom_objects: CustomObjectsApi, name, yaml_ try: delete_virtual_server(custom_objects, name, namespace) create_virtual_server_from_yaml(custom_objects, yaml_manifest, namespace) + wait_before_test() except ApiException: logging.exception(f"Failed with exception while patching VirtualServer: {name}") raise @@ -183,6 +184,7 @@ def patch_v_s_route_from_yaml(custom_objects: CustomObjectsApi, name, yaml_manif custom_objects.patch_namespaced_custom_object( "k8s.nginx.org", "v1", namespace, "virtualserverroutes", name, dep ) + wait_before_test() print(f"VirtualServerRoute updated with name '{dep['metadata']['name']}'") except ApiException: logging.exception(f"Failed with exception while patching VirtualServerRoute: {name}") diff --git a/tests/suite/utils/yaml_utils.py b/tests/suite/utils/yaml_utils.py index c7c8c43d62..7c7858df0e 100644 --- a/tests/suite/utils/yaml_utils.py +++ b/tests/suite/utils/yaml_utils.py @@ -123,7 +123,7 @@ def get_paths_from_vsr_yaml(file) -> []: return res -def get_secret_name_from_vs_yaml(file) -> str: +def get_secret_name_from_vs_or_ts_yaml(file) -> str: """ Parse yaml file and return the tls secret name. diff --git a/tests/test-servers/Dockerfile b/tests/test-servers/Dockerfile deleted file mode 100644 index 15d7f26c8b..0000000000 --- a/tests/test-servers/Dockerfile +++ /dev/null @@ -1,18 +0,0 @@ -# syntax=docker/dockerfile:1.4 -FROM golang:1.19-alpine as builder - -RUN mkdir /app - -ARG type - -COPY $type/main.go /app - -WORKDIR /app - -RUN GO111MODULE=off CGO_ENABLED=0 GOOS=linux go build -o main - -FROM scratch - -COPY --from=builder /app/main /app/ - -ENTRYPOINT ["/app/main"] diff --git a/tests/test-servers/README.md b/tests/test-servers/README.md deleted file mode 100644 index 5f4881aef6..0000000000 --- a/tests/test-servers/README.md +++ /dev/null @@ -1,48 +0,0 @@ -## TCP Server - -A Go server that accepts TCP requests and responds with the local address of the connection. - -### Description -If the server is run inside a Docker container, the local address is the IP of the docker container. This is useful -for distinguishing between instances of Docker containers. This server is used by the python tests in the -[load balancing tests](../suite/test_transport_server_tcp_load_balance.py). - -### Config -The default port the server listens to is `3333`. The server takes a single argument, `port`, to allow the port to be -overridden. - -## UDP Server - -A Go server that accepts UDP requests and responds with the local address of the connection. - -### Description -If the server is run inside a Docker container, the local address is the IP of the docker container. This is useful -for distinguishing between instances of Docker containers. This server is used by the python tests in the -[load balancing tests](../suite/test_transport_server_udp_load_balance.py). - -### Config -The default port the server listens to is `3334`. The server takes a single argument, `port`, to allow the port to be -overridden. - - -## Making changes -If you make changes to the TCP server: - - * Test the change: - * Use the minikube registry ```$ eval $(minikube docker-env)``` - * Build the docker image ```docker build --build-arg type=tcp -t tcp-server .``` - * Update the [service yaml](../data/transport-server-tcp-load-balance/standard/service_deployment.yaml) to use the - local version ```-> imagePullPolicy: Never``` - * Test the changes - * Include the change as part of the commit that requires the tcp-server change - * Build the docker image with an increased version number ```docker build --build-arg type=tcp -t nginxkic/tcp-server:X.Y .``` - * Push the docker image to the public repo ```docker push nginxkic/tcp-server:X.Y``` - * Update the tag [service yaml](../data/transport-server-tcp-load-balance/standard/service_deployment.yaml) to match -the new tag - * Commit the tag change as part of the commit that requires the tcp-server change - -For the UDP server: -``` -docker build --build-arg type=udp -t nginxkic/udp-server:X.Y . -docker push nginxkic/udp-server:X.Y -``` diff --git a/tests/test-servers/tcp/main.go b/tests/test-servers/tcp/main.go deleted file mode 100644 index 082f886320..0000000000 --- a/tests/test-servers/tcp/main.go +++ /dev/null @@ -1,60 +0,0 @@ -package main - -import ( - "flag" - "fmt" - "log" - "net" -) - -func main() { - port := flag.String("port", "3333", "Port") - flag.Parse() - - l, err := net.Listen("tcp", fmt.Sprintf(":%v", *port)) - if err != nil { - log.Panicln(err) - } - defer l.Close() - log.Printf("listening to tcp connections at: :%v\n", *port) - - for { - conn, err := l.Accept() - if err != nil { - log.Panicln(err) - } - - go handleRequest(conn) - } -} - -func handleRequest(conn net.Conn) { - log.Println("accepted new connection") - - buf := make([]byte, 1024) - n, err := conn.Read(buf) - if err != nil { - log.Println("Error reading:", err.Error()) - conn.Close() - return - } - instruction := string(buf[:n]) - log.Printf("instruction:%q\n", instruction) - if instruction != "hold" { - defer conn.Close() - defer log.Println("closed connection") - } - - response := conn.LocalAddr().String() - if instruction == "health" { - response = "healthy" - } - - log.Printf("write data to connection: %v\n", response) - - _, err = conn.Write([]byte(response)) - if err != nil { - log.Printf("error writing to connection: %v", err) - return - } -} diff --git a/tests/test-servers/udp/main.go b/tests/test-servers/udp/main.go deleted file mode 100644 index 11b36b2f5a..0000000000 --- a/tests/test-servers/udp/main.go +++ /dev/null @@ -1,48 +0,0 @@ -package main - -import ( - "flag" - "fmt" - "log" - "net" - "os" -) - -func main() { - ip := os.Getenv("POD_IP") - log.Printf("ip: %v\n", ip) - if ip == "" { - log.Fatalf("missing required env var: POD_IP") - } - port := flag.String("port", "3334", "The port the server listens to") - flag.Parse() - listener, err := net.ListenPacket("udp", fmt.Sprintf(":%v", *port)) - if err != nil { - log.Panicln(err) - } - defer listener.Close() - log.Printf("listening to udp connections at: :%v", *port) - buffer := make([]byte, 1024) - for { - n, addr, err := listener.ReadFrom(buffer) - if err != nil { - log.Panicln(err) - } - - request := string(buffer[:n]) - - log.Printf("packet-received: request=%q bytes=%d from=%s", request, n, addr.String()) - - response := fmt.Sprintf("%v:%v", ip, *port) - if request == "health" { - response = "healthy" - } - - log.Printf("write data to connection: %q", response) - n, err = listener.WriteTo([]byte(response), addr) - if err != nil { - log.Panicln(err) - } - log.Printf("packet-written: bytes=%d to=%s", n, addr.String()) - } -}